fix: make dashboard public and remove admin-only restriction from main pages
- Make homepage (/) accessible without authentication - Allow all authenticated users (user & admin) to access main pages: - /kinerja-divisi, /pengaduan, /jenna, /demografi - /keuangan, /bumdes, /sosial, /keamanan - /bantuan, /pengaturan - Reserve admin-only access for /admin/* routes - Update auth middleware to handle public routes properly Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
This commit is contained in:
@@ -1,13 +1,23 @@
|
|||||||
|
import {
|
||||||
|
Badge,
|
||||||
|
Button,
|
||||||
|
Card,
|
||||||
|
Grid,
|
||||||
|
GridCol,
|
||||||
|
Group,
|
||||||
|
Select,
|
||||||
|
Stack,
|
||||||
|
Table,
|
||||||
|
Text,
|
||||||
|
Title,
|
||||||
|
useMantineColorScheme,
|
||||||
|
} from "@mantine/core";
|
||||||
import {
|
import {
|
||||||
IconBuildingStore,
|
IconBuildingStore,
|
||||||
IconCategory,
|
IconCategory,
|
||||||
IconCurrency,
|
IconCurrency,
|
||||||
IconUsers,
|
IconUsers,
|
||||||
IconTrendingUp,
|
|
||||||
IconTrendingDown,
|
|
||||||
IconChevronDown,
|
|
||||||
} from "@tabler/icons-react";
|
} from "@tabler/icons-react";
|
||||||
import { useMantineColorScheme } from "@mantine/core";
|
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
|
||||||
const BumdesPage = () => {
|
const BumdesPage = () => {
|
||||||
@@ -15,98 +25,69 @@ const BumdesPage = () => {
|
|||||||
const dark = colorScheme === "dark";
|
const dark = colorScheme === "dark";
|
||||||
|
|
||||||
const [timeFilter, setTimeFilter] = useState<string>("bulan");
|
const [timeFilter, setTimeFilter] = useState<string>("bulan");
|
||||||
const [categoryFilter, setCategoryFilter] = useState<string>("semua");
|
|
||||||
|
|
||||||
// KPI Data
|
// Sample data for KPI cards
|
||||||
const kpiData = [
|
const kpiData = [
|
||||||
{
|
{
|
||||||
title: "UMKM Aktif",
|
title: "UMKM Aktif",
|
||||||
value: "45",
|
value: 45,
|
||||||
subtitle: "Beroperasi",
|
icon: <IconUsers size={24} />,
|
||||||
icon: IconUsers,
|
color: "darmasaba-blue",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "UMKM Terdaftar",
|
title: "UMKM Terdaftar",
|
||||||
value: "68",
|
value: 68,
|
||||||
subtitle: "Total terdaftar",
|
icon: <IconBuildingStore size={24} />,
|
||||||
icon: IconBuildingStore,
|
color: "darmasaba-success",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Omzet",
|
title: "Omzet",
|
||||||
value: "48 JT",
|
value: "Rp 48.000.000",
|
||||||
subtitle: "Bulan ini",
|
icon: <IconCurrency size={24} />,
|
||||||
icon: IconCurrency,
|
color: "darmasaba-warning",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Kategori UMKM",
|
title: "Kategori UMKM",
|
||||||
value: "34",
|
value: 34,
|
||||||
subtitle: "Jenis produk",
|
icon: <IconCategory size={24} />,
|
||||||
icon: IconCategory,
|
color: "darmasaba-danger",
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
// Mini stats data
|
// Sample data for top products
|
||||||
const miniStats = [
|
|
||||||
{
|
|
||||||
title: "Total Penjualan",
|
|
||||||
value: "Rp 30.900.000",
|
|
||||||
subtitle: "+18% vs bulan lalu",
|
|
||||||
isPositive: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Produk Aktif",
|
|
||||||
value: "7",
|
|
||||||
subtitle: "Kategori produk",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Total Transaksi",
|
|
||||||
value: "500",
|
|
||||||
subtitle: "Transaksi bulan ini",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
// Top 3 products data
|
|
||||||
const topProducts = [
|
const topProducts = [
|
||||||
{
|
{
|
||||||
rank: 1,
|
rank: 1,
|
||||||
name: "Beras Premium Organik",
|
name: "Beras Premium Organik",
|
||||||
umkmOwner: "Kelompok Tani Subak",
|
umkmOwner: "Warung Pak Joko",
|
||||||
sales: "Rp 8.500.000",
|
growth: "+12%",
|
||||||
volume: "650 Kg Terjual",
|
|
||||||
growth: "+15%",
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
rank: 2,
|
rank: 2,
|
||||||
name: "Keripik Singkong",
|
name: "Keripik Singkong",
|
||||||
umkmOwner: "Ibu Sari Snack",
|
umkmOwner: "Ibu Sari Snack",
|
||||||
sales: "Rp 4.200.000",
|
|
||||||
volume: "320 Kg Terjual",
|
|
||||||
growth: "+8%",
|
growth: "+8%",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
rank: 3,
|
rank: 3,
|
||||||
name: "Madu Alami",
|
name: "Madu Alami",
|
||||||
umkmOwner: "Peternakan Lebah",
|
umkmOwner: "Peternakan Lebah",
|
||||||
sales: "Rp 3.750.000",
|
|
||||||
volume: "150 Liter Terjual",
|
|
||||||
growth: "+5%",
|
growth: "+5%",
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
// Product sales data
|
// Sample data for product sales
|
||||||
const productSales = [
|
const productSales = [
|
||||||
{
|
{
|
||||||
produk: "Beras Premium Organik",
|
produk: "Beras Premium Organik",
|
||||||
umkm: "Kelompok Tani Subak",
|
|
||||||
penjualanBulanIni: "Rp 8.500.000",
|
penjualanBulanIni: "Rp 8.500.000",
|
||||||
bulanLalu: "Rp 7.400.000",
|
bulanLalu: "Rp 8.500.000",
|
||||||
trend: 15,
|
trend: 10,
|
||||||
volume: "650 Kg",
|
volume: "650 Kg",
|
||||||
stok: "850 Kg",
|
stok: "850 Kg",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
produk: "Keripik Singkong",
|
produk: "Keripik Singkong",
|
||||||
umkm: "Ibu Sari Snack",
|
|
||||||
penjualanBulanIni: "Rp 4.200.000",
|
penjualanBulanIni: "Rp 4.200.000",
|
||||||
bulanLalu: "Rp 3.800.000",
|
bulanLalu: "Rp 3.800.000",
|
||||||
trend: 10,
|
trend: 10,
|
||||||
@@ -115,7 +96,6 @@ const BumdesPage = () => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
produk: "Madu Alami",
|
produk: "Madu Alami",
|
||||||
umkm: "Peternakan Lebah",
|
|
||||||
penjualanBulanIni: "Rp 3.750.000",
|
penjualanBulanIni: "Rp 3.750.000",
|
||||||
bulanLalu: "Rp 4.100.000",
|
bulanLalu: "Rp 4.100.000",
|
||||||
trend: -8,
|
trend: -8,
|
||||||
@@ -124,7 +104,6 @@ const BumdesPage = () => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
produk: "Kecap Tradisional",
|
produk: "Kecap Tradisional",
|
||||||
umkm: "Bu Darmi",
|
|
||||||
penjualanBulanIni: "Rp 2.800.000",
|
penjualanBulanIni: "Rp 2.800.000",
|
||||||
bulanLalu: "Rp 2.500.000",
|
bulanLalu: "Rp 2.500.000",
|
||||||
trend: 12,
|
trend: 12,
|
||||||
@@ -133,457 +112,278 @@ const BumdesPage = () => {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const cardStyle = {
|
|
||||||
backgroundColor: dark ? "#1E293B" : "white",
|
|
||||||
border: `1px solid ${dark ? "#1E293B" : "white"}`,
|
|
||||||
};
|
|
||||||
|
|
||||||
const textStyle = {
|
|
||||||
color: dark ? "white" : "#1F2937",
|
|
||||||
};
|
|
||||||
|
|
||||||
const subtitleStyle = {
|
|
||||||
color: dark ? "#9CA3AF" : "#6B7280",
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<Stack gap="lg">
|
||||||
className="min-h-screen"
|
{/* KPI Cards */}
|
||||||
style={{
|
<Grid gutter="md">
|
||||||
backgroundColor: dark ? "#0F172A" : "#F3F4F6",
|
{kpiData.map((kpi, index) => (
|
||||||
minHeight: "100vh",
|
<GridCol key={index} span={{ base: 12, sm: 6, md: 3 }}>
|
||||||
padding: "1.5rem",
|
<Card
|
||||||
}}
|
p="md"
|
||||||
>
|
radius="md"
|
||||||
<div
|
withBorder
|
||||||
className="max-w-7xl mx-auto"
|
bg={dark ? "#141D34" : "white"}
|
||||||
style={{
|
style={{ borderColor: dark ? "#141D34" : "white" }}
|
||||||
maxWidth: "80rem",
|
|
||||||
marginLeft: "auto",
|
|
||||||
marginRight: "auto",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{/* Row 1: Top 4 Metrics Cards */}
|
|
||||||
<div
|
|
||||||
className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-6"
|
|
||||||
style={{
|
|
||||||
display: "grid",
|
|
||||||
gridTemplateColumns: "repeat(4, 1fr)",
|
|
||||||
gap: "1.5rem",
|
|
||||||
marginBottom: "1.5rem",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{kpiData.map((kpi, index) => (
|
|
||||||
<div
|
|
||||||
key={index}
|
|
||||||
className="rounded-xl shadow-sm p-6"
|
|
||||||
style={{
|
|
||||||
...cardStyle,
|
|
||||||
borderRadius: "12px",
|
|
||||||
boxShadow: "0 1px 3px rgba(0,0,0,0.1)",
|
|
||||||
padding: "1.5rem",
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<div className="flex items-center justify-between">
|
<Group justify="space-between" align="center">
|
||||||
<div className="flex-1">
|
<Stack gap={0}>
|
||||||
<h3
|
<Text size="sm" c={dark ? "dark.3" : "dimmed"}>
|
||||||
className="text-sm font-medium mb-1"
|
|
||||||
style={subtitleStyle}
|
|
||||||
>
|
|
||||||
{kpi.title}
|
{kpi.title}
|
||||||
</h3>
|
</Text>
|
||||||
<p
|
<Text size="xl" fw={700} c={dark ? "dark.0" : "black"}>
|
||||||
className="text-3xl font-bold mb-1"
|
{typeof kpi.value === "number"
|
||||||
style={textStyle}
|
? kpi.value.toLocaleString()
|
||||||
>
|
: kpi.value}
|
||||||
{kpi.value}
|
</Text>
|
||||||
</p>
|
</Stack>
|
||||||
<p
|
<Badge variant="light" color={kpi.color} p={8} radius="md">
|
||||||
className="text-xs"
|
{kpi.icon}
|
||||||
style={subtitleStyle}
|
</Badge>
|
||||||
>
|
</Group>
|
||||||
{kpi.subtitle}
|
</Card>
|
||||||
</p>
|
</GridCol>
|
||||||
</div>
|
))}
|
||||||
<div className="flex-shrink-0 ml-4">
|
</Grid>
|
||||||
<div
|
|
||||||
className="w-12 h-12 rounded-full flex items-center justify-center text-white"
|
|
||||||
style={{ backgroundColor: "#1F3A5F" }}
|
|
||||||
>
|
|
||||||
<kpi.icon size={24} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Row 2: Sales Update Header */}
|
{/* Update Penjualan Produk Header */}
|
||||||
<div
|
<Card
|
||||||
className="rounded-xl shadow-sm mb-6 overflow-hidden"
|
p="md"
|
||||||
style={{
|
radius="md"
|
||||||
...cardStyle,
|
withBorder
|
||||||
borderRadius: "12px",
|
bg={dark ? "#141D34" : "white"}
|
||||||
boxShadow: "0 1px 3px rgba(0,0,0,0.1)",
|
style={{ borderColor: dark ? "#141D34" : "white" }}
|
||||||
marginBottom: "1.5rem",
|
>
|
||||||
}}
|
<Group justify="space-between" align="center" px="md" py="xs">
|
||||||
>
|
<Title order={3} c={dark ? "dark.0" : "black"}>
|
||||||
<div
|
Update Penjualan Produk
|
||||||
className="px-6 py-4 flex items-center justify-between"
|
</Title>
|
||||||
style={{ backgroundColor: "#1F3A5F" }}
|
<Group>
|
||||||
>
|
<Button
|
||||||
<h3 className="text-lg font-semibold text-white">
|
variant={timeFilter === "minggu" ? "filled" : "light"}
|
||||||
Update Penjualan Produk
|
onClick={() => setTimeFilter("minggu")}
|
||||||
</h3>
|
color="darmasaba-blue"
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<button
|
|
||||||
onClick={() => setTimeFilter("minggu")}
|
|
||||||
className={`px-4 py-2 rounded-full text-sm font-medium transition-colors ${
|
|
||||||
timeFilter === "minggu"
|
|
||||||
? "bg-white text-[#1F3A5F]"
|
|
||||||
: "bg-white/20 text-white hover:bg-white/30"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
Minggu ini
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
onClick={() => setTimeFilter("bulan")}
|
|
||||||
className={`px-4 py-2 rounded-full text-sm font-medium transition-colors ${
|
|
||||||
timeFilter === "bulan"
|
|
||||||
? "bg-white text-[#1F3A5F]"
|
|
||||||
: "bg-white/20 text-white hover:bg-white/30"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
Bulan ini
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Row 3: Main Content Grid */}
|
|
||||||
<div
|
|
||||||
className="grid grid-cols-1 lg:grid-cols-10 gap-6"
|
|
||||||
style={{
|
|
||||||
display: "grid",
|
|
||||||
gridTemplateColumns: "repeat(10, 1fr)",
|
|
||||||
gap: "1.5rem",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{/* Left Column (30%) */}
|
|
||||||
<div className="lg:col-span-3 space-y-6">
|
|
||||||
{/* Produk Unggulan Section */}
|
|
||||||
<div
|
|
||||||
className="rounded-xl shadow-sm p-6"
|
|
||||||
style={{
|
|
||||||
...cardStyle,
|
|
||||||
borderRadius: "12px",
|
|
||||||
boxShadow: "0 1px 3px rgba(0,0,0,0.1)",
|
|
||||||
padding: "1.5rem",
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<h3
|
Minggu ini
|
||||||
className="text-lg font-semibold mb-4"
|
</Button>
|
||||||
style={textStyle}
|
<Button
|
||||||
>
|
variant={timeFilter === "bulan" ? "filled" : "light"}
|
||||||
Produk Unggulan
|
onClick={() => setTimeFilter("bulan")}
|
||||||
</h3>
|
color="darmasaba-blue"
|
||||||
|
>
|
||||||
|
Bulan ini
|
||||||
|
</Button>
|
||||||
|
</Group>
|
||||||
|
</Group>
|
||||||
|
</Card>
|
||||||
|
|
||||||
{/* Mini Stats Cards */}
|
<Grid gutter="md">
|
||||||
<div className="space-y-4 mb-6">
|
{/* Produk Unggulan (Left Column) */}
|
||||||
{miniStats.map((stat, index) => (
|
<GridCol span={{ base: 12, lg: 4 }}>
|
||||||
<div
|
<Stack gap="md">
|
||||||
key={index}
|
{/* Total Penjualan, Produk Aktif, Total Transaksi */}
|
||||||
className="p-4 rounded-lg"
|
<Card
|
||||||
style={{
|
p="md"
|
||||||
backgroundColor: dark ? "#334155" : "#F9FAFB",
|
radius="md"
|
||||||
}}
|
withBorder
|
||||||
|
bg={dark ? "#141D34" : "white"}
|
||||||
|
style={{ borderColor: dark ? "#141D34" : "white" }}
|
||||||
|
>
|
||||||
|
<Stack gap="md">
|
||||||
|
<Group justify="space-between">
|
||||||
|
<Text size="sm" c={dark ? "dark.3" : "dimmed"}>
|
||||||
|
Total Penjualan
|
||||||
|
</Text>
|
||||||
|
<Text size="lg" fw={700} c={dark ? "dark.0" : "black"}>
|
||||||
|
Rp 28.500.000
|
||||||
|
</Text>
|
||||||
|
</Group>
|
||||||
|
<Group justify="space-between">
|
||||||
|
<Text size="sm" c={dark ? "dark.3" : "dimmed"}>
|
||||||
|
Produk Aktif
|
||||||
|
</Text>
|
||||||
|
<Text size="lg" fw={700} c={dark ? "dark.0" : "black"}>
|
||||||
|
124 Produk
|
||||||
|
</Text>
|
||||||
|
</Group>
|
||||||
|
<Group justify="space-between">
|
||||||
|
<Text size="sm" c={dark ? "dark.3" : "dimmed"}>
|
||||||
|
Total Transaksi
|
||||||
|
</Text>
|
||||||
|
<Text size="lg" fw={700} c={dark ? "dark.0" : "black"}>
|
||||||
|
1.240 Transaksi
|
||||||
|
</Text>
|
||||||
|
</Group>
|
||||||
|
</Stack>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
{/* Top 3 Produk Terlaris */}
|
||||||
|
<Card
|
||||||
|
p="md"
|
||||||
|
radius="md"
|
||||||
|
withBorder
|
||||||
|
bg={dark ? "#141D34" : "white"}
|
||||||
|
style={{ borderColor: dark ? "#141D34" : "white" }}
|
||||||
|
>
|
||||||
|
<Title order={4} mb="md" c={dark ? "dark.0" : "black"}>
|
||||||
|
Top 3 Produk Terlaris
|
||||||
|
</Title>
|
||||||
|
<Stack gap="sm">
|
||||||
|
{topProducts.map((product) => (
|
||||||
|
<Group
|
||||||
|
key={product.rank}
|
||||||
|
justify="space-between"
|
||||||
|
align="center"
|
||||||
>
|
>
|
||||||
<p
|
<Group gap="sm">
|
||||||
className="text-sm font-medium mb-1"
|
<Badge
|
||||||
style={subtitleStyle}
|
variant="filled"
|
||||||
|
color={
|
||||||
|
product.rank === 1
|
||||||
|
? "gold"
|
||||||
|
: product.rank === 2
|
||||||
|
? "gray"
|
||||||
|
: "bronze"
|
||||||
|
}
|
||||||
|
radius="xl"
|
||||||
|
size="lg"
|
||||||
|
>
|
||||||
|
{product.rank}
|
||||||
|
</Badge>
|
||||||
|
<Stack gap={0}>
|
||||||
|
<Text fw={500} c={dark ? "dark.0" : "black"}>
|
||||||
|
{product.name}
|
||||||
|
</Text>
|
||||||
|
<Text size="sm" c={dark ? "dark.3" : "dimmed"}>
|
||||||
|
{product.umkmOwner}
|
||||||
|
</Text>
|
||||||
|
</Stack>
|
||||||
|
</Group>
|
||||||
|
<Badge
|
||||||
|
variant="light"
|
||||||
|
color={product.growth.startsWith("+") ? "green" : "red"}
|
||||||
>
|
>
|
||||||
{stat.title}
|
{product.growth}
|
||||||
</p>
|
</Badge>
|
||||||
<p
|
</Group>
|
||||||
className="text-xl font-bold"
|
))}
|
||||||
style={textStyle}
|
</Stack>
|
||||||
>
|
</Card>
|
||||||
{stat.value}
|
</Stack>
|
||||||
</p>
|
</GridCol>
|
||||||
{stat.subtitle && (
|
|
||||||
<p
|
{/* Detail Penjualan Produk (Right Column) */}
|
||||||
className="text-xs mt-1"
|
<GridCol span={{ base: 12, lg: 8 }}>
|
||||||
style={
|
<Card
|
||||||
stat.isPositive
|
p="md"
|
||||||
? { color: "#22C55E" }
|
radius="md"
|
||||||
: subtitleStyle
|
withBorder
|
||||||
|
bg={dark ? "#141D34" : "white"}
|
||||||
|
style={{ borderColor: dark ? "#141D34" : "white" }}
|
||||||
|
>
|
||||||
|
<Group justify="space-between" mb="md">
|
||||||
|
<Title order={4} c={dark ? "dark.0" : "black"}>
|
||||||
|
Detail Penjualan Produk
|
||||||
|
</Title>
|
||||||
|
<Select
|
||||||
|
placeholder="Filter kategori"
|
||||||
|
data={[
|
||||||
|
{ value: "semua", label: "Semua Kategori" },
|
||||||
|
{ value: "makanan", label: "Makanan" },
|
||||||
|
{ value: "minuman", label: "Minuman" },
|
||||||
|
{ value: "kerajinan", label: "Kerajinan" },
|
||||||
|
]}
|
||||||
|
defaultValue="semua"
|
||||||
|
w={200}
|
||||||
|
/>
|
||||||
|
</Group>
|
||||||
|
|
||||||
|
<Table striped highlightOnHover withColumnBorders>
|
||||||
|
<Table.Thead>
|
||||||
|
<Table.Tr>
|
||||||
|
<Table.Th>
|
||||||
|
<Text c={dark ? "white" : "dimmed"}>Produk</Text>
|
||||||
|
</Table.Th>
|
||||||
|
<Table.Th>
|
||||||
|
<Text c={dark ? "white" : "dimmed"}>
|
||||||
|
Penjualan Bulan Ini
|
||||||
|
</Text>
|
||||||
|
</Table.Th>
|
||||||
|
<Table.Th>
|
||||||
|
<Text c={dark ? "white" : "dimmed"}>Bulan Lalu</Text>
|
||||||
|
</Table.Th>
|
||||||
|
<Table.Th>
|
||||||
|
<Text c={dark ? "white" : "dimmed"}>Trend</Text>
|
||||||
|
</Table.Th>
|
||||||
|
<Table.Th>
|
||||||
|
<Text c={dark ? "white" : "dimmed"}>Volume</Text>
|
||||||
|
</Table.Th>
|
||||||
|
<Table.Th>
|
||||||
|
<Text c={dark ? "white" : "dimmed"}>Stok</Text>
|
||||||
|
</Table.Th>
|
||||||
|
<Table.Th>
|
||||||
|
<Text c={dark ? "white" : "dimmed"}>Aksi</Text>
|
||||||
|
</Table.Th>
|
||||||
|
</Table.Tr>
|
||||||
|
</Table.Thead>
|
||||||
|
<Table.Tbody>
|
||||||
|
{productSales.map((product, index) => (
|
||||||
|
<Table.Tr key={index}>
|
||||||
|
<Table.Td>
|
||||||
|
<Text fw={500} c={dark ? "dark.0" : "black"}>
|
||||||
|
{product.produk}
|
||||||
|
</Text>
|
||||||
|
</Table.Td>
|
||||||
|
<Table.Td>
|
||||||
|
<Text fz={"sm"} c={dark ? "dark.0" : "black"}>
|
||||||
|
{product.penjualanBulanIni}
|
||||||
|
</Text>
|
||||||
|
</Table.Td>
|
||||||
|
<Table.Td>
|
||||||
|
<Text fz={"sm"} c={dark ? "white" : "dimmed"}>
|
||||||
|
{product.bulanLalu}
|
||||||
|
</Text>
|
||||||
|
</Table.Td>
|
||||||
|
<Table.Td>
|
||||||
|
<Group gap="xs">
|
||||||
|
<Text c={product.trend >= 0 ? "green" : "red"}>
|
||||||
|
{product.trend >= 0 ? "↑" : "↓"}{" "}
|
||||||
|
{Math.abs(product.trend)}%
|
||||||
|
</Text>
|
||||||
|
</Group>
|
||||||
|
</Table.Td>
|
||||||
|
<Table.Td>
|
||||||
|
<Text fz={"sm"} c={dark ? "dark.0" : "black"}>
|
||||||
|
{product.volume}
|
||||||
|
</Text>
|
||||||
|
</Table.Td>
|
||||||
|
<Table.Td>
|
||||||
|
<Badge
|
||||||
|
variant="light"
|
||||||
|
color={
|
||||||
|
parseInt(product.stok) > 200 ? "green" : "yellow"
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{stat.subtitle}
|
{product.stok}
|
||||||
</p>
|
</Badge>
|
||||||
)}
|
</Table.Td>
|
||||||
</div>
|
<Table.Td>
|
||||||
|
<Button
|
||||||
|
variant="subtle"
|
||||||
|
size="compact-sm"
|
||||||
|
color="darmasaba-blue"
|
||||||
|
>
|
||||||
|
Detail
|
||||||
|
</Button>
|
||||||
|
</Table.Td>
|
||||||
|
</Table.Tr>
|
||||||
))}
|
))}
|
||||||
</div>
|
</Table.Tbody>
|
||||||
|
</Table>
|
||||||
{/* Top 3 Products */}
|
</Card>
|
||||||
<h4
|
</GridCol>
|
||||||
className="text-base font-semibold mb-4"
|
</Grid>
|
||||||
style={textStyle}
|
</Stack>
|
||||||
>
|
|
||||||
Top 3 Produk Terlaris
|
|
||||||
</h4>
|
|
||||||
<div className="space-y-4">
|
|
||||||
{topProducts.map((product) => (
|
|
||||||
<div
|
|
||||||
key={product.rank}
|
|
||||||
className="flex items-start gap-3 p-3 rounded-lg"
|
|
||||||
style={{
|
|
||||||
backgroundColor: dark ? "#334155" : "#F9FAFB",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="w-8 h-8 rounded-full flex items-center justify-center text-white font-bold text-sm flex-shrink-0"
|
|
||||||
style={{
|
|
||||||
backgroundColor:
|
|
||||||
product.rank === 1
|
|
||||||
? "#FFD700"
|
|
||||||
: product.rank === 2
|
|
||||||
? "#C0C0C0"
|
|
||||||
: "#CD7F32",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
#{product.rank}
|
|
||||||
</div>
|
|
||||||
<div className="flex-1">
|
|
||||||
<p
|
|
||||||
className="text-sm font-medium"
|
|
||||||
style={textStyle}
|
|
||||||
>
|
|
||||||
{product.name}
|
|
||||||
</p>
|
|
||||||
<p
|
|
||||||
className="text-xs mt-1"
|
|
||||||
style={subtitleStyle}
|
|
||||||
>
|
|
||||||
{product.umkmOwner}
|
|
||||||
</p>
|
|
||||||
<div className="flex items-center justify-between mt-2">
|
|
||||||
<p className="text-xs font-medium" style={{ color: "#22C55E" }}>
|
|
||||||
{product.sales}
|
|
||||||
</p>
|
|
||||||
<p
|
|
||||||
className="text-xs"
|
|
||||||
style={{ color: "#22C55E" }}
|
|
||||||
>
|
|
||||||
{product.growth}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<p className="text-xs mt-1" style={subtitleStyle}>
|
|
||||||
{product.volume}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Right Column (70%) */}
|
|
||||||
<div className="lg:col-span-7">
|
|
||||||
<div
|
|
||||||
className="rounded-xl shadow-sm p-6"
|
|
||||||
style={{
|
|
||||||
...cardStyle,
|
|
||||||
borderRadius: "12px",
|
|
||||||
boxShadow: "0 1px 3px rgba(0,0,0,0.1)",
|
|
||||||
padding: "1.5rem",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div className="flex items-center justify-between mb-6">
|
|
||||||
<h3
|
|
||||||
className="text-lg font-semibold"
|
|
||||||
style={textStyle}
|
|
||||||
>
|
|
||||||
Detail Penjualan Produk
|
|
||||||
</h3>
|
|
||||||
<div className="relative">
|
|
||||||
<select
|
|
||||||
value={categoryFilter}
|
|
||||||
onChange={(e) => setCategoryFilter(e.target.value)}
|
|
||||||
className="appearance-none px-4 py-2 pr-8 rounded-lg text-sm font-medium border-0 focus:ring-2 focus:ring-[#1F3A5F] cursor-pointer"
|
|
||||||
style={{
|
|
||||||
backgroundColor: dark ? "#334155" : "#F9FAFB",
|
|
||||||
color: dark ? "white" : "#1F2937",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<option value="semua">Semua Kategori</option>
|
|
||||||
<option value="makanan">Makanan</option>
|
|
||||||
<option value="minuman">Minuman</option>
|
|
||||||
<option value="kerajinan">Kerajinan</option>
|
|
||||||
</select>
|
|
||||||
<IconChevronDown
|
|
||||||
size={16}
|
|
||||||
className="absolute right-2 top-1/2 -translate-y-1/2 pointer-events-none"
|
|
||||||
style={{ color: dark ? "#9CA3AF" : "#6B7280" }}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Data Table */}
|
|
||||||
<div className="overflow-x-auto">
|
|
||||||
<table className="w-full">
|
|
||||||
<thead>
|
|
||||||
<tr
|
|
||||||
style={{
|
|
||||||
borderBottom: `2px solid ${dark ? "#334155" : "#E5E7EB"}`,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<th
|
|
||||||
className="text-left py-3 px-4 text-sm font-medium"
|
|
||||||
style={subtitleStyle}
|
|
||||||
>
|
|
||||||
Produk
|
|
||||||
</th>
|
|
||||||
<th
|
|
||||||
className="text-left py-3 px-4 text-sm font-medium"
|
|
||||||
style={subtitleStyle}
|
|
||||||
>
|
|
||||||
Penjualan Bulan Ini
|
|
||||||
</th>
|
|
||||||
<th
|
|
||||||
className="text-left py-3 px-4 text-sm font-medium"
|
|
||||||
style={subtitleStyle}
|
|
||||||
>
|
|
||||||
Bulan Lalu
|
|
||||||
</th>
|
|
||||||
<th
|
|
||||||
className="text-left py-3 px-4 text-sm font-medium"
|
|
||||||
style={subtitleStyle}
|
|
||||||
>
|
|
||||||
Trend
|
|
||||||
</th>
|
|
||||||
<th
|
|
||||||
className="text-left py-3 px-4 text-sm font-medium"
|
|
||||||
style={subtitleStyle}
|
|
||||||
>
|
|
||||||
Volume
|
|
||||||
</th>
|
|
||||||
<th
|
|
||||||
className="text-left py-3 px-4 text-sm font-medium"
|
|
||||||
style={subtitleStyle}
|
|
||||||
>
|
|
||||||
Stok
|
|
||||||
</th>
|
|
||||||
<th
|
|
||||||
className="text-left py-3 px-4 text-sm font-medium"
|
|
||||||
style={subtitleStyle}
|
|
||||||
>
|
|
||||||
Aksi
|
|
||||||
</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{productSales.map((product, index) => (
|
|
||||||
<tr
|
|
||||||
key={index}
|
|
||||||
style={{
|
|
||||||
borderBottom: `1px solid ${dark ? "#334155" : "#F3F4F6"}`,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<td className="py-4 px-4">
|
|
||||||
<p
|
|
||||||
className="text-sm font-medium"
|
|
||||||
style={textStyle}
|
|
||||||
>
|
|
||||||
{product.produk}
|
|
||||||
</p>
|
|
||||||
<p
|
|
||||||
className="text-xs mt-1"
|
|
||||||
style={subtitleStyle}
|
|
||||||
>
|
|
||||||
{product.umkm}
|
|
||||||
</p>
|
|
||||||
</td>
|
|
||||||
<td className="py-4 px-4">
|
|
||||||
<p
|
|
||||||
className="text-sm font-medium"
|
|
||||||
style={textStyle}
|
|
||||||
>
|
|
||||||
{product.penjualanBulanIni}
|
|
||||||
</p>
|
|
||||||
</td>
|
|
||||||
<td className="py-4 px-4">
|
|
||||||
<p
|
|
||||||
className="text-sm"
|
|
||||||
style={subtitleStyle}
|
|
||||||
>
|
|
||||||
{product.bulanLalu}
|
|
||||||
</p>
|
|
||||||
</td>
|
|
||||||
<td className="py-4 px-4">
|
|
||||||
<div
|
|
||||||
className="flex items-center gap-1 text-sm font-medium"
|
|
||||||
style={{
|
|
||||||
color: product.trend >= 0 ? "#22C55E" : "#EF4444",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{product.trend >= 0 ? (
|
|
||||||
<IconTrendingUp size={16} />
|
|
||||||
) : (
|
|
||||||
<IconTrendingDown size={16} />
|
|
||||||
)}
|
|
||||||
{product.trend >= 0 ? "+" : ""}
|
|
||||||
{product.trend}%
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td className="py-4 px-4">
|
|
||||||
<p
|
|
||||||
className="text-sm"
|
|
||||||
style={textStyle}
|
|
||||||
>
|
|
||||||
{product.volume}
|
|
||||||
</p>
|
|
||||||
</td>
|
|
||||||
<td className="py-4 px-4">
|
|
||||||
<span
|
|
||||||
className="inline-flex items-center px-3 py-1 rounded-full text-xs font-medium"
|
|
||||||
style={{
|
|
||||||
backgroundColor: parseInt(product.stok) > 200 ? "#DCFCE7" : "#FEE2E2",
|
|
||||||
color: parseInt(product.stok) > 200 ? "#166534" : "#991B1B",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{product.stok}
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
<td className="py-4 px-4">
|
|
||||||
<button
|
|
||||||
className="px-3 py-1.5 rounded-lg text-sm font-medium transition-colors"
|
|
||||||
style={{
|
|
||||||
backgroundColor: "#1F3A5F",
|
|
||||||
color: "white",
|
|
||||||
}}
|
|
||||||
onMouseEnter={(e) =>
|
|
||||||
(e.currentTarget.style.backgroundColor = "#2d4a6f")
|
|
||||||
}
|
|
||||||
onMouseLeave={(e) =>
|
|
||||||
(e.currentTarget.style.backgroundColor = "#1F3A5F")
|
|
||||||
}
|
|
||||||
>
|
|
||||||
Detail
|
|
||||||
</button>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
))}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default BumdesPage;
|
export default BumdesPage;
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
import {
|
import {
|
||||||
Briefcase,
|
|
||||||
Calendar,
|
Calendar,
|
||||||
CheckCircle,
|
CheckCircle,
|
||||||
FileText,
|
FileText,
|
||||||
@@ -28,9 +27,7 @@ import {
|
|||||||
Card, // Added for icon containers
|
Card, // Added for icon containers
|
||||||
Grid,
|
Grid,
|
||||||
Group,
|
Group,
|
||||||
Image,
|
|
||||||
Progress,
|
Progress,
|
||||||
SimpleGrid,
|
|
||||||
Stack,
|
Stack,
|
||||||
Text,
|
Text,
|
||||||
ThemeIcon,
|
ThemeIcon,
|
||||||
@@ -69,19 +66,6 @@ const eventData = [
|
|||||||
{ date: "19 Oktober 2025", title: "Rapat Koordinasi" },
|
{ date: "19 Oktober 2025", title: "Rapat Koordinasi" },
|
||||||
];
|
];
|
||||||
|
|
||||||
const apbdesData = [
|
|
||||||
{ name: "Belanja", value: 390, label: "390M" },
|
|
||||||
{ name: "Pendapatan", value: 470, label: "470M" },
|
|
||||||
{ name: "Pembiayaan", value: 290, label: "290M" },
|
|
||||||
];
|
|
||||||
|
|
||||||
const sdgsData = [
|
|
||||||
{ label: "Desa Berenergi Bersih Dan Terbarukan", value: 99.64, image: "/SDGS-7.png" },
|
|
||||||
{ label: "Desa Damai Berkeadilan", value: 78.65, image: "/SDGS-16.png" },
|
|
||||||
{ label: "Desa Sehat Dan Sejahtera", value: 77.37, image: "/SDGS-3.png" },
|
|
||||||
{ label: "Desa Tanpa Kemiskinan", value: 52.62, image: "/SDGS-1.png" }
|
|
||||||
];
|
|
||||||
|
|
||||||
export function DashboardContent() {
|
export function DashboardContent() {
|
||||||
const { colorScheme } = useMantineColorScheme();
|
const { colorScheme } = useMantineColorScheme();
|
||||||
const dark = colorScheme === "dark";
|
const dark = colorScheme === "dark";
|
||||||
@@ -119,7 +103,7 @@ export function DashboardContent() {
|
|||||||
variant="filled"
|
variant="filled"
|
||||||
size="xl"
|
size="xl"
|
||||||
radius="xl"
|
radius="xl"
|
||||||
color={dark ? "gray" : "darmasaba-navy"}
|
color={dark ? "gray" : "darmasaba-blue"}
|
||||||
>
|
>
|
||||||
<FileText style={{ width: "70%", height: "70%" }} />
|
<FileText style={{ width: "70%", height: "70%" }} />
|
||||||
</ThemeIcon>
|
</ThemeIcon>
|
||||||
@@ -153,7 +137,7 @@ export function DashboardContent() {
|
|||||||
variant="filled"
|
variant="filled"
|
||||||
size="xl"
|
size="xl"
|
||||||
radius="xl"
|
radius="xl"
|
||||||
color={dark ? "gray" : "darmasaba-navy"}
|
color={dark ? "gray" : "darmasaba-blue"}
|
||||||
>
|
>
|
||||||
<MessageCircle style={{ width: "70%", height: "70%" }} />
|
<MessageCircle style={{ width: "70%", height: "70%" }} />
|
||||||
</ThemeIcon>
|
</ThemeIcon>
|
||||||
@@ -190,7 +174,7 @@ export function DashboardContent() {
|
|||||||
variant="filled"
|
variant="filled"
|
||||||
size="xl"
|
size="xl"
|
||||||
radius="xl"
|
radius="xl"
|
||||||
color={dark ? "gray" : "darmasaba-navy"}
|
color={dark ? "gray" : "darmasaba-blue"}
|
||||||
>
|
>
|
||||||
<CheckCircle style={{ width: "70%", height: "70%" }} />
|
<CheckCircle style={{ width: "70%", height: "70%" }} />
|
||||||
</ThemeIcon>
|
</ThemeIcon>
|
||||||
@@ -224,7 +208,7 @@ export function DashboardContent() {
|
|||||||
variant="filled"
|
variant="filled"
|
||||||
size="xl"
|
size="xl"
|
||||||
radius="xl"
|
radius="xl"
|
||||||
color={dark ? "gray" : "darmasaba-navy"}
|
color={dark ? "gray" : "darmasaba-blue"}
|
||||||
>
|
>
|
||||||
<Users style={{ width: "70%", height: "70%" }} />
|
<Users style={{ width: "70%", height: "70%" }} />
|
||||||
</ThemeIcon>
|
</ThemeIcon>
|
||||||
@@ -292,7 +276,7 @@ export function DashboardContent() {
|
|||||||
<Tooltip />
|
<Tooltip />
|
||||||
<Bar
|
<Bar
|
||||||
dataKey="value"
|
dataKey="value"
|
||||||
fill="#1E3A5F"
|
fill="var(--mantine-color-blue-filled)"
|
||||||
radius={[4, 4, 0, 0]}
|
radius={[4, 4, 0, 0]}
|
||||||
/>
|
/>
|
||||||
</BarChart>
|
</BarChart>
|
||||||
@@ -385,7 +369,46 @@ export function DashboardContent() {
|
|||||||
<Group gap="xs" mb="lg">
|
<Group gap="xs" mb="lg">
|
||||||
<Box>
|
<Box>
|
||||||
{/* Original SVG icon */}
|
{/* Original SVG icon */}
|
||||||
<Briefcase color="#1E3A5F" />
|
<svg
|
||||||
|
width="24"
|
||||||
|
height="24"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<rect
|
||||||
|
x="3"
|
||||||
|
y="3"
|
||||||
|
width="7"
|
||||||
|
height="7"
|
||||||
|
rx="1"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
<rect
|
||||||
|
x="3"
|
||||||
|
y="14"
|
||||||
|
width="7"
|
||||||
|
height="7"
|
||||||
|
rx="1"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
<rect
|
||||||
|
x="14"
|
||||||
|
y="3"
|
||||||
|
width="7"
|
||||||
|
height="7"
|
||||||
|
rx="1"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
<rect
|
||||||
|
x="14"
|
||||||
|
y="14"
|
||||||
|
width="7"
|
||||||
|
height="7"
|
||||||
|
rx="1"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
</Box>
|
</Box>
|
||||||
<Title order={4}>Divisi Teraktif</Title>
|
<Title order={4}>Divisi Teraktif</Title>
|
||||||
</Group>
|
</Group>
|
||||||
@@ -404,7 +427,7 @@ export function DashboardContent() {
|
|||||||
value={(divisi.value / 37) * 100}
|
value={(divisi.value / 37) * 100}
|
||||||
size="sm"
|
size="sm"
|
||||||
radius="xl"
|
radius="xl"
|
||||||
color="#1E3A5F"
|
color="blue"
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
))}
|
))}
|
||||||
@@ -430,7 +453,7 @@ export function DashboardContent() {
|
|||||||
<Box
|
<Box
|
||||||
key={index}
|
key={index}
|
||||||
style={{
|
style={{
|
||||||
borderLeft: "4px solid #1E3A5F",
|
borderLeft: "4px solid var(--mantine-color-blue-filled)",
|
||||||
paddingLeft: 12,
|
paddingLeft: 12,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -457,60 +480,44 @@ export function DashboardContent() {
|
|||||||
Grafik APBDes
|
Grafik APBDes
|
||||||
</Title>
|
</Title>
|
||||||
<Stack gap="xs">
|
<Stack gap="xs">
|
||||||
{apbdesData.map((data, index) => (
|
<Group align="center" gap="md">
|
||||||
<Grid key={index} align="center">
|
<Text size="sm" fw={500} w={60}>
|
||||||
<Grid.Col span={3}>
|
Belanja
|
||||||
<Text size="sm" fw={500}>
|
</Text>
|
||||||
{data.name}
|
<Progress
|
||||||
</Text>
|
value={70}
|
||||||
</Grid.Col>
|
size="lg"
|
||||||
<Grid.Col span={7}>
|
radius="xl"
|
||||||
<Progress
|
color="blue"
|
||||||
value={(data.value / 470) * 100}
|
style={{ flex: 1 }}
|
||||||
size="lg"
|
/>
|
||||||
radius="xl"
|
</Group>
|
||||||
color="#1E3A5F"
|
<Group align="center" gap="md">
|
||||||
/>
|
<Text size="sm" fw={500} w={60}>
|
||||||
</Grid.Col>
|
Pendapatan
|
||||||
<Grid.Col span={2}>
|
</Text>
|
||||||
<Text size="sm" fw={600} ta="right">
|
<Progress
|
||||||
{data.label}
|
value={90}
|
||||||
</Text>
|
size="lg"
|
||||||
</Grid.Col>
|
radius="xl"
|
||||||
</Grid>
|
color="green"
|
||||||
))}
|
style={{ flex: 1 }}
|
||||||
|
/>
|
||||||
|
</Group>
|
||||||
|
<Group align="center" gap="md">
|
||||||
|
<Text size="sm" fw={500} w={60}>
|
||||||
|
Pembangunan
|
||||||
|
</Text>
|
||||||
|
<Progress
|
||||||
|
value={50}
|
||||||
|
size="lg"
|
||||||
|
radius="xl"
|
||||||
|
color="orange"
|
||||||
|
style={{ flex: 1 }}
|
||||||
|
/>
|
||||||
|
</Group>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
{/* SDGS Desa */}
|
|
||||||
<Card
|
|
||||||
p="md"
|
|
||||||
style={{ borderColor: dark ? "#141D34" : "white" }}
|
|
||||||
radius="md"
|
|
||||||
withBorder
|
|
||||||
bg={dark ? "#141D34" : "white"}
|
|
||||||
>
|
|
||||||
<Title order={4} mb="lg">
|
|
||||||
SDGS Desa
|
|
||||||
</Title>
|
|
||||||
<SimpleGrid cols={{ base: 2, md: 5 }}>
|
|
||||||
{sdgsData.map((data, index) => (
|
|
||||||
<Card key={index} withBorder bg={dark ? "#141D34" : "white"} p="md">
|
|
||||||
<Group gap="sm" align="center">
|
|
||||||
<Image src={data.image} width={40} height={40} />
|
|
||||||
<Box>
|
|
||||||
<Text size="sm" ta={"center"} fw={500} lineClamp={2}>
|
|
||||||
{data.label}
|
|
||||||
</Text>
|
|
||||||
<Text size="sm" ta={"center"} fw={600} c="darmasaba-blue">
|
|
||||||
{data.value}
|
|
||||||
</Text>
|
|
||||||
</Box>
|
|
||||||
</Group>
|
|
||||||
</Card>
|
|
||||||
))}
|
|
||||||
</SimpleGrid>
|
|
||||||
</Card>
|
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1,544 +1,411 @@
|
|||||||
|
import { BarChart, PieChart } from "@mantine/charts";
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Card,
|
||||||
|
Grid,
|
||||||
|
Group,
|
||||||
|
Stack,
|
||||||
|
Table,
|
||||||
|
Text,
|
||||||
|
Title,
|
||||||
|
useMantineColorScheme,
|
||||||
|
} from "@mantine/core";
|
||||||
import {
|
import {
|
||||||
IconArrowDown,
|
IconArrowDown,
|
||||||
IconArrowUp,
|
IconArrowUp,
|
||||||
IconBabyCarriage,
|
IconBabyCarriage,
|
||||||
IconSkull,
|
IconSkull,
|
||||||
IconUsers,
|
|
||||||
IconHome,
|
|
||||||
IconExclamationCircle,
|
|
||||||
} from "@tabler/icons-react";
|
} from "@tabler/icons-react";
|
||||||
import { useMantineColorScheme } from "@mantine/core";
|
import React from "react";
|
||||||
import {
|
|
||||||
Bar,
|
// Sample Data
|
||||||
BarChart,
|
const kpiData = [
|
||||||
CartesianGrid,
|
{
|
||||||
Cell,
|
id: 1,
|
||||||
Pie,
|
title: "Total Penduduk",
|
||||||
PieChart,
|
value: "5.634",
|
||||||
ResponsiveContainer,
|
sub: "Aktif terdaftar",
|
||||||
Tooltip,
|
icon: (
|
||||||
XAxis,
|
<svg
|
||||||
YAxis,
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
} from "recharts";
|
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>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
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>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
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"
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
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>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
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 },
|
||||||
|
];
|
||||||
|
|
||||||
|
const jobDistributionData = [
|
||||||
|
{ job: "Sipil", total: 1200 },
|
||||||
|
{ job: "Guru", total: 850 },
|
||||||
|
{ job: "Petani", total: 950 },
|
||||||
|
{ job: "Pedagang", total: 750 },
|
||||||
|
{ job: "Wiraswasta", total: 984 },
|
||||||
|
];
|
||||||
|
|
||||||
|
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" },
|
||||||
|
];
|
||||||
|
|
||||||
|
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 },
|
||||||
|
];
|
||||||
|
|
||||||
|
const dynamicStats = [
|
||||||
|
{
|
||||||
|
title: "Kelahiran",
|
||||||
|
value: "23",
|
||||||
|
icon: <IconBabyCarriage size={16} />,
|
||||||
|
color: "green",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Kematian",
|
||||||
|
value: "12",
|
||||||
|
icon: <IconSkull size={16} />,
|
||||||
|
color: "red",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Pindah Masuk",
|
||||||
|
value: "45",
|
||||||
|
icon: <IconArrowDown size={16} />,
|
||||||
|
color: "blue",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Pindah Keluar",
|
||||||
|
value: "32",
|
||||||
|
icon: <IconArrowUp size={16} />,
|
||||||
|
color: "orange",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
const DemografiPekerjaan = () => {
|
const DemografiPekerjaan = () => {
|
||||||
const { colorScheme } = useMantineColorScheme();
|
const { colorScheme } = useMantineColorScheme();
|
||||||
const dark = colorScheme === "dark";
|
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 (
|
return (
|
||||||
<div
|
<Box className="space-y-6">
|
||||||
className="min-h-screen"
|
<Stack gap="xl">
|
||||||
style={{
|
{/* KPI Cards */}
|
||||||
backgroundColor: dark ? "#10192D" : "#F3F4F6",
|
<Grid gutter="lg">
|
||||||
minHeight: "100vh",
|
|
||||||
padding: "1.5rem",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="max-w-7xl mx-auto"
|
|
||||||
style={{
|
|
||||||
maxWidth: "80rem",
|
|
||||||
marginLeft: "auto",
|
|
||||||
marginRight: "auto",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{/* Row 1: 4 Statistic Cards */}
|
|
||||||
<div
|
|
||||||
className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-6"
|
|
||||||
style={{
|
|
||||||
display: "grid",
|
|
||||||
gridTemplateColumns: "repeat(4, 1fr)",
|
|
||||||
gap: "1.5rem",
|
|
||||||
marginBottom: "1.5rem",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{kpiData.map((kpi) => (
|
{kpiData.map((kpi) => (
|
||||||
<div
|
<Grid.Col key={kpi.id} span={{ base: 12, md: 6, lg: 3 }}>
|
||||||
key={kpi.id}
|
<Card
|
||||||
className="rounded-xl shadow-sm p-6"
|
p="md"
|
||||||
style={{
|
radius="md"
|
||||||
...cardStyle,
|
withBorder
|
||||||
borderRadius: "12px",
|
bg={dark ? "#141D34" : "white"}
|
||||||
boxShadow: "0 1px 3px rgba(0,0,0,0.1)",
|
style={{ borderColor: dark ? "#141D34" : "white" }}
|
||||||
padding: "1.5rem",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<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"
|
|
||||||
style={{
|
|
||||||
display: "grid",
|
|
||||||
gridTemplateColumns: "repeat(2, 1fr)",
|
|
||||||
gap: "1.5rem",
|
|
||||||
marginBottom: "1.5rem",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{/* Age Distribution Bar Chart */}
|
|
||||||
<div
|
|
||||||
className="rounded-xl shadow-sm p-6"
|
|
||||||
style={{
|
|
||||||
...cardStyle,
|
|
||||||
borderRadius: "12px",
|
|
||||||
boxShadow: "0 1px 3px rgba(0,0,0,0.1)",
|
|
||||||
padding: "1.5rem",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<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,
|
|
||||||
borderRadius: "12px",
|
|
||||||
boxShadow: "0 1px 3px rgba(0,0,0,0.1)",
|
|
||||||
padding: "1.5rem",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<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"
|
|
||||||
style={{
|
|
||||||
display: "grid",
|
|
||||||
gridTemplateColumns: "repeat(3, 1fr)",
|
|
||||||
gap: "1.5rem",
|
|
||||||
marginBottom: "1.5rem",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{/* Religion Distribution Pie Chart */}
|
|
||||||
<div
|
|
||||||
className="rounded-xl shadow-sm p-6"
|
|
||||||
style={{
|
|
||||||
...cardStyle,
|
|
||||||
borderRadius: "12px",
|
|
||||||
boxShadow: "0 1px 3px rgba(0,0,0,0.1)",
|
|
||||||
padding: "1.5rem",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<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,
|
|
||||||
borderRadius: "12px",
|
|
||||||
boxShadow: "0 1px 3px rgba(0,0,0,0.1)",
|
|
||||||
padding: "1.5rem",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<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,
|
|
||||||
borderRadius: "12px",
|
|
||||||
boxShadow: "0 1px 3px rgba(0,0,0,0.1)",
|
|
||||||
padding: "1.5rem",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<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"
|
|
||||||
style={{
|
|
||||||
display: "grid",
|
|
||||||
gridTemplateColumns: "repeat(4, 1fr)",
|
|
||||||
gap: "1rem",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{dynamicStats.map((stat, index) => (
|
|
||||||
<div
|
|
||||||
key={index}
|
|
||||||
className="p-4 rounded-lg"
|
|
||||||
style={{
|
|
||||||
backgroundColor: dark ? "#1F2937" : "#F9FAFB",
|
|
||||||
borderRadius: "8px",
|
|
||||||
padding: "1rem",
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<div className="flex items-center justify-between">
|
<Group justify="space-between" align="flex-start" mb="xs">
|
||||||
<div>
|
<Text size="sm" fw={500} c={dark ? "dark.3" : "dimmed"}>
|
||||||
<p
|
{kpi.title}
|
||||||
className="text-sm font-medium mb-1"
|
</Text>
|
||||||
style={subtitleStyle}
|
{React.cloneElement(kpi.icon, {
|
||||||
>
|
className: "h-6 w-6",
|
||||||
{stat.title}
|
color: dark
|
||||||
</p>
|
? "var(--mantine-color-dark-3)"
|
||||||
<p
|
: "var(--mantine-color-dimmed)",
|
||||||
className="text-2xl font-bold"
|
})}
|
||||||
style={{ color: stat.color }}
|
</Group>
|
||||||
>
|
<Title order={3} fw={700} c={dark ? "dark.0" : "black"} mt="xs">
|
||||||
{stat.value}
|
{kpi.value}
|
||||||
</p>
|
</Title>
|
||||||
</div>
|
{kpi.delta && (
|
||||||
<div
|
<Text
|
||||||
className="w-10 h-10 rounded-full flex items-center justify-center"
|
size="xs"
|
||||||
style={{
|
c={
|
||||||
backgroundColor: `${stat.color}20`,
|
kpi.deltaType === "positive"
|
||||||
color: stat.color,
|
? "green"
|
||||||
}}
|
: kpi.deltaType === "negative"
|
||||||
|
? "red"
|
||||||
|
: dark
|
||||||
|
? "dark.3"
|
||||||
|
: "dimmed"
|
||||||
|
}
|
||||||
|
mt={4}
|
||||||
>
|
>
|
||||||
<stat.icon size={20} />
|
{kpi.delta}
|
||||||
</div>
|
</Text>
|
||||||
</div>
|
)}
|
||||||
</div>
|
{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 }}>
|
||||||
|
<Card
|
||||||
|
p="md"
|
||||||
|
radius="md"
|
||||||
|
withBorder
|
||||||
|
bg={dark ? "#141D34" : "white"}
|
||||||
|
style={{ borderColor: dark ? "#141D34" : "white" }}
|
||||||
|
>
|
||||||
|
<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
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
</Grid.Col>
|
||||||
|
|
||||||
|
{/* 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">
|
||||||
|
Demografi Pekerjaan
|
||||||
|
</Title>
|
||||||
|
<BarChart
|
||||||
|
h={300}
|
||||||
|
data={jobDistributionData}
|
||||||
|
dataKey="job"
|
||||||
|
series={[{ name: "total", color: "darmasaba-navy" }]}
|
||||||
|
withLegend
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
</Grid.Col>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
{/* 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
|
||||||
|
</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"}>
|
||||||
|
{stat.title}
|
||||||
|
</Text>
|
||||||
|
<Title order={4} fw={700} c={stat.color}>
|
||||||
|
{stat.value}
|
||||||
|
</Title>
|
||||||
|
</Box>
|
||||||
|
<Box c={stat.color}>{stat.icon}</Box>
|
||||||
|
</Group>
|
||||||
|
</Card>
|
||||||
|
</Grid.Col>
|
||||||
))}
|
))}
|
||||||
</div>
|
</Grid>
|
||||||
</div>
|
</Card>
|
||||||
</div>
|
</Stack>
|
||||||
</div>
|
</Box>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default DemografiPekerjaan;
|
export default DemografiPekerjaan;
|
||||||
@@ -1,72 +1,121 @@
|
|||||||
import {
|
import {
|
||||||
ActionIcon,
|
ActionIcon,
|
||||||
|
Avatar,
|
||||||
Badge,
|
Badge,
|
||||||
Box,
|
Box,
|
||||||
|
Divider,
|
||||||
Group,
|
Group,
|
||||||
Text,
|
Text,
|
||||||
|
Title,
|
||||||
useMantineColorScheme,
|
useMantineColorScheme,
|
||||||
} from "@mantine/core";
|
} from "@mantine/core";
|
||||||
import { useLocation } from "@tanstack/react-router";
|
import { IconUserShield } from "@tabler/icons-react";
|
||||||
import { Bell, Moon, Sun } from "lucide-react";
|
import { useLocation, useNavigate } from "@tanstack/react-router";
|
||||||
|
import { Bell, Moon, Sun, User as UserIcon } from "lucide-react"; // Renamed User to UserIcon to avoid conflict with Mantine's User component if it exists
|
||||||
|
|
||||||
export function Header() {
|
export function Header() {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const { colorScheme, toggleColorScheme } = useMantineColorScheme();
|
const { colorScheme, toggleColorScheme } = useMantineColorScheme();
|
||||||
const dark = colorScheme === "dark";
|
const dark = colorScheme === "dark";
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const title =
|
// Define page titles based on route
|
||||||
location.pathname === "/"
|
const getPageTitle = () => {
|
||||||
? "Desa Darmasaba"
|
switch (location.pathname) {
|
||||||
: "Desa Darmasaba";
|
case "/":
|
||||||
|
return "Beranda";
|
||||||
|
case "/kinerja-divisi":
|
||||||
|
return "Kinerja Divisi";
|
||||||
|
case "/pengaduan-layanan-publik":
|
||||||
|
return "Pengaduan & Layanan Publik";
|
||||||
|
case "/jenna-analytic":
|
||||||
|
return "Jenna Analytic";
|
||||||
|
case "/demografi-pekerjaan":
|
||||||
|
return "Demografi & Kependudukan";
|
||||||
|
case "/keuangan-anggaran":
|
||||||
|
return "Keuangan & Anggaran";
|
||||||
|
case "/bumdes":
|
||||||
|
return "Bumdes & UMKM Desa";
|
||||||
|
case "/sosial":
|
||||||
|
return "Sosial";
|
||||||
|
case "/keamanan":
|
||||||
|
return "Keamanan";
|
||||||
|
case "/bantuan":
|
||||||
|
return "Bantuan";
|
||||||
|
case "/pengaturan":
|
||||||
|
case "/pengaturan/umum":
|
||||||
|
case "/pengaturan/notifikasi":
|
||||||
|
case "/pengaturan/keamanan":
|
||||||
|
case "/pengaturan/akses-dan-tim":
|
||||||
|
return "Pengaturan";
|
||||||
|
default:
|
||||||
|
return "Desa Darmasaba";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Group justify="space-between" w="100%">
|
||||||
style={{
|
{/* Title */}
|
||||||
display: "grid",
|
<Title order={3} c={"white"}>
|
||||||
gridTemplateColumns: "1fr auto 1fr",
|
{getPageTitle()}
|
||||||
alignItems: "center",
|
</Title>
|
||||||
width: "100%",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{/* LEFT SPACER (burger sudah di luar) */}
|
|
||||||
<Box />
|
|
||||||
|
|
||||||
{/* CENTER TITLE */}
|
{/* Right Section */}
|
||||||
<Text
|
<Group gap="md">
|
||||||
c="white"
|
{/* User Info */}
|
||||||
fw={600}
|
<Group gap="sm">
|
||||||
size="md"
|
<Box ta="right">
|
||||||
style={{
|
<Text c={"white"} size="sm" fw={500}>
|
||||||
textAlign: "center",
|
I. B. Surya Prabhawa M...
|
||||||
whiteSpace: "nowrap",
|
</Text>
|
||||||
overflow: "hidden",
|
<Text c={"white"} size="xs">
|
||||||
textOverflow: "ellipsis",
|
Kepala Desa
|
||||||
}}
|
</Text>
|
||||||
>
|
</Box>
|
||||||
{title}
|
<Avatar color="blue" radius="xl">
|
||||||
</Text>
|
<UserIcon color="white" style={{ width: "70%", height: "70%" }} />
|
||||||
|
</Avatar>
|
||||||
|
</Group>
|
||||||
|
|
||||||
{/* RIGHT ICONS */}
|
{/* Divider */}
|
||||||
<Group gap="xs" justify="flex-end">
|
<Divider orientation="vertical" h={30} />
|
||||||
<ActionIcon
|
|
||||||
onClick={toggleColorScheme}
|
|
||||||
variant="subtle"
|
|
||||||
radius="xl"
|
|
||||||
>
|
|
||||||
{dark ? <Sun size={18} /> : <Moon size={18} />}
|
|
||||||
</ActionIcon>
|
|
||||||
|
|
||||||
<ActionIcon variant="subtle" radius="xl" pos="relative">
|
{/* Icons */}
|
||||||
<Bell size={18} />
|
<Group gap="sm">
|
||||||
<Badge
|
<ActionIcon
|
||||||
size="xs"
|
onClick={() => toggleColorScheme()}
|
||||||
color="red"
|
variant="subtle"
|
||||||
style={{ position: "absolute", top: -4, right: -4 }}
|
size="lg"
|
||||||
|
radius="xl"
|
||||||
|
aria-label="Toggle color scheme"
|
||||||
>
|
>
|
||||||
10
|
{dark ? (
|
||||||
</Badge>
|
<Sun color="white" style={{ width: "70%", height: "70%" }} />
|
||||||
</ActionIcon>
|
) : (
|
||||||
|
<Moon color="white" style={{ width: "70%", height: "70%" }} />
|
||||||
|
)}
|
||||||
|
</ActionIcon>
|
||||||
|
<ActionIcon variant="subtle" size="lg" radius="xl" pos="relative">
|
||||||
|
<Bell color="white" style={{ width: "70%", height: "70%" }} />
|
||||||
|
<Badge
|
||||||
|
size="xs"
|
||||||
|
color="red"
|
||||||
|
variant="filled"
|
||||||
|
style={{ position: "absolute", top: 0, right: 0 }}
|
||||||
|
radius={"xl"}
|
||||||
|
>
|
||||||
|
10
|
||||||
|
</Badge>
|
||||||
|
</ActionIcon>
|
||||||
|
<ActionIcon variant="subtle" size="lg" radius="xl">
|
||||||
|
<IconUserShield
|
||||||
|
color="white"
|
||||||
|
style={{ width: "70%", height: "70%" }}
|
||||||
|
onClick={() => navigate({ to: "/signin" })}
|
||||||
|
/>
|
||||||
|
</ActionIcon>
|
||||||
|
</Group>
|
||||||
</Group>
|
</Group>
|
||||||
</Box>
|
</Group>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -142,7 +142,14 @@ const HelpPage = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container size="lg" py="lg">
|
<Container size="lg" py="xl">
|
||||||
|
<Title order={1} mb="xl" ta="center">
|
||||||
|
Pusat Bantuan
|
||||||
|
</Title>
|
||||||
|
<Text size="lg" color="dimmed" ta="center" mb="xl">
|
||||||
|
Temukan jawaban untuk pertanyaan Anda atau hubungi tim support kami
|
||||||
|
</Text>
|
||||||
|
|
||||||
{/* Statistics Section */}
|
{/* Statistics Section */}
|
||||||
<SimpleGrid cols={3} spacing="lg" mb="xl">
|
<SimpleGrid cols={3} spacing="lg" mb="xl">
|
||||||
{stats.map((stat, index) => (
|
{stats.map((stat, index) => (
|
||||||
@@ -425,4 +432,4 @@ const HelpPage = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default HelpPage;
|
export default HelpPage;
|
||||||
@@ -1,328 +1,283 @@
|
|||||||
|
import { BarChart } from "@mantine/charts";
|
||||||
import {
|
import {
|
||||||
IconAlertTriangle,
|
Badge,
|
||||||
IconClock,
|
Box,
|
||||||
IconMessageChatbot,
|
Button,
|
||||||
IconSparkles,
|
Card,
|
||||||
} from "@tabler/icons-react";
|
Grid,
|
||||||
import { useMantineColorScheme } from "@mantine/core";
|
Group,
|
||||||
import {
|
Progress,
|
||||||
Bar,
|
Stack,
|
||||||
BarChart,
|
Text,
|
||||||
CartesianGrid,
|
Title,
|
||||||
Cell,
|
useMantineColorScheme,
|
||||||
ResponsiveContainer,
|
} from "@mantine/core";
|
||||||
Tooltip,
|
import React from "react";
|
||||||
XAxis,
|
|
||||||
YAxis,
|
// Sample Data
|
||||||
} from "recharts";
|
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>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
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>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
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>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
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>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
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 },
|
||||||
|
];
|
||||||
|
|
||||||
|
const topTopics = [
|
||||||
|
{ topic: "Cara mengurus KTP", count: 89 },
|
||||||
|
{ topic: "Syarat Kartu Keluarga", count: 76 },
|
||||||
|
{ topic: "Jadwal Posyandu", count: 64 },
|
||||||
|
{ topic: "Pengaduan jalan rusak", count: 52 },
|
||||||
|
{ topic: "Info program bansos", count: 48 },
|
||||||
|
];
|
||||||
|
|
||||||
|
const busyHours = [
|
||||||
|
{ period: "Pagi (08–12)", percentage: 30 },
|
||||||
|
{ period: "Siang (12–16)", percentage: 40 },
|
||||||
|
{ period: "Sore (16–20)", percentage: 20 },
|
||||||
|
{ period: "Malam (20–08)", percentage: 10 },
|
||||||
|
];
|
||||||
|
|
||||||
const JennaAnalytic = () => {
|
const JennaAnalytic = () => {
|
||||||
const { colorScheme } = useMantineColorScheme();
|
const { colorScheme } = useMantineColorScheme();
|
||||||
const dark = colorScheme === "dark";
|
const dark = colorScheme === "dark";
|
||||||
|
|
||||||
// KPI Data
|
|
||||||
const kpiData = [
|
|
||||||
{
|
|
||||||
id: 1,
|
|
||||||
title: "Interaksi Hari Ini",
|
|
||||||
value: "61",
|
|
||||||
subtitle: "+15% dari kemarin",
|
|
||||||
icon: IconMessageChatbot,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 2,
|
|
||||||
title: "Jawaban Otomatis",
|
|
||||||
value: "87%",
|
|
||||||
subtitle: "53 dari 61 interaksi",
|
|
||||||
icon: IconSparkles,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 3,
|
|
||||||
title: "Belum Ditindak",
|
|
||||||
value: "8",
|
|
||||||
subtitle: "Perlu respon manual",
|
|
||||||
icon: IconAlertTriangle,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 4,
|
|
||||||
title: "Waktu Respon",
|
|
||||||
value: "2.3s",
|
|
||||||
subtitle: "Rata-rata",
|
|
||||||
icon: IconClock,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
// Weekly chatbot interaction data
|
|
||||||
const weeklyData = [
|
|
||||||
{ day: "Sen", interactions: 100 },
|
|
||||||
{ day: "Sel", interactions: 120 },
|
|
||||||
{ day: "Rab", interactions: 90 },
|
|
||||||
{ day: "Kam", interactions: 150 },
|
|
||||||
{ day: "Jum", interactions: 110 },
|
|
||||||
{ day: "Sab", interactions: 80 },
|
|
||||||
{ day: "Min", interactions: 130 },
|
|
||||||
];
|
|
||||||
|
|
||||||
// Top topics data
|
|
||||||
const topTopics = [
|
|
||||||
{ topic: "Cara mengurus KTP", count: 89 },
|
|
||||||
{ topic: "Syarat Kartu Keluarga", count: 76 },
|
|
||||||
{ topic: "Jadwal Posyandu", count: 64 },
|
|
||||||
{ topic: "Pengaduan jalan rusak", count: 52 },
|
|
||||||
{ topic: "Info program bansos", count: 48 },
|
|
||||||
];
|
|
||||||
|
|
||||||
// Busy hour distribution
|
|
||||||
const busyHours = [
|
|
||||||
{ period: "Pagi (08–12)", percentage: 30 },
|
|
||||||
{ period: "Siang (12–16)", percentage: 40 },
|
|
||||||
{ period: "Sore (16–20)", percentage: 20 },
|
|
||||||
{ period: "Malam (20–08)", percentage: 10 },
|
|
||||||
];
|
|
||||||
|
|
||||||
const COLORS = ["#1E3A5F", "#3B82F6", "#60A5FA", "#93C5FD"];
|
|
||||||
|
|
||||||
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 (
|
return (
|
||||||
<div
|
<Box className="space-y-6">
|
||||||
className="min-h-screen"
|
<Stack gap="xl">
|
||||||
style={{
|
{/* KPI Cards */}
|
||||||
backgroundColor: dark ? "#10192D" : "#F3F4F6",
|
<Grid gutter="lg">
|
||||||
minHeight: "100vh",
|
|
||||||
padding: "1.5rem",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="max-w-7xl mx-auto"
|
|
||||||
style={{
|
|
||||||
maxWidth: "80rem",
|
|
||||||
marginLeft: "auto",
|
|
||||||
marginRight: "auto",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{/* Row 1: 4 Statistic Cards */}
|
|
||||||
<div
|
|
||||||
className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-6"
|
|
||||||
style={{
|
|
||||||
display: "grid",
|
|
||||||
gridTemplateColumns: "repeat(4, 1fr)",
|
|
||||||
gap: "1.5rem",
|
|
||||||
marginBottom: "1.5rem",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{kpiData.map((kpi) => (
|
{kpiData.map((kpi) => (
|
||||||
<div
|
<Grid.Col key={kpi.id} span={{ base: 12, md: 6, lg: 3 }}>
|
||||||
key={kpi.id}
|
<Card
|
||||||
className="rounded-xl shadow-sm p-6"
|
p="md"
|
||||||
style={{
|
radius="md"
|
||||||
...cardStyle,
|
withBorder
|
||||||
borderRadius: "12px",
|
bg={dark ? "#141D34" : "white"}
|
||||||
boxShadow: "0 1px 3px rgba(0,0,0,0.1)",
|
style={{ borderColor: dark ? "#141D34" : "white" }}
|
||||||
padding: "1.5rem",
|
>
|
||||||
}}
|
<Group justify="space-between" align="flex-start" mb="xs">
|
||||||
>
|
<Text size="sm" fw={500} c="dimmed">
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<div className="flex-1">
|
|
||||||
<h3
|
|
||||||
className="text-sm font-medium mb-1"
|
|
||||||
style={subtitleStyle}
|
|
||||||
>
|
|
||||||
{kpi.title}
|
{kpi.title}
|
||||||
</h3>
|
</Text>
|
||||||
<p
|
{React.cloneElement(kpi.icon, {
|
||||||
className="text-3xl font-bold mb-1"
|
className: "h-6 w-6", // Keeping classes for now, can be replaced by Mantine Icon component if available or styled with sx prop
|
||||||
style={textStyle}
|
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.value}
|
{kpi.delta}
|
||||||
</p>
|
</Text>
|
||||||
<p
|
)}
|
||||||
className="text-xs"
|
{kpi.sub && (
|
||||||
style={subtitleStyle}
|
<Text size="xs" c="dimmed" mt={2}>
|
||||||
>
|
{kpi.sub}
|
||||||
{kpi.subtitle}
|
</Text>
|
||||||
</p>
|
)}
|
||||||
</div>
|
</Card>
|
||||||
<div className="flex-shrink-0 ml-4">
|
</Grid.Col>
|
||||||
<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>
|
</Grid>
|
||||||
|
|
||||||
{/* Row 2: Full Width Weekly Bar Chart */}
|
<Card
|
||||||
<div
|
p="md"
|
||||||
className="rounded-xl shadow-sm p-6 mb-6"
|
radius="md"
|
||||||
style={{
|
withBorder
|
||||||
...cardStyle,
|
bg={dark ? "#141D34" : "white"}
|
||||||
borderRadius: "12px",
|
style={{ borderColor: dark ? "#141D34" : "white" }}
|
||||||
boxShadow: "0 1px 3px rgba(0,0,0,0.1)",
|
|
||||||
padding: "1.5rem",
|
|
||||||
marginBottom: "1.5rem",
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<h3
|
<Title order={3} fw={500} mb="md">
|
||||||
className="text-lg font-semibold mb-4"
|
Interaksi Chatbot
|
||||||
style={textStyle}
|
</Title>
|
||||||
>
|
<BarChart
|
||||||
Interaksi Chatbot Mingguan
|
h={300}
|
||||||
</h3>
|
data={chartData}
|
||||||
<ResponsiveContainer width="100%" height={300}>
|
dataKey="day"
|
||||||
<BarChart data={weeklyData}>
|
series={[{ name: "total", color: "blue" }]}
|
||||||
<CartesianGrid
|
withLegend
|
||||||
strokeDasharray="3 3"
|
/>
|
||||||
vertical={false}
|
</Card>
|
||||||
stroke={dark ? "#2d3748" : "#E5E7EB"}
|
|
||||||
/>
|
{/* Charts and Lists Section */}
|
||||||
<XAxis
|
<Grid gutter="lg">
|
||||||
dataKey="day"
|
{/* Grafik Interaksi Chatbot (now Bar Chart) */}
|
||||||
axisLine={false}
|
<Grid.Col span={{ base: 12, lg: 6 }}>
|
||||||
tickLine={false}
|
<Card
|
||||||
tick={{ fill: dark ? "#9CA3AF" : "#6B7280" }}
|
p="md"
|
||||||
/>
|
radius="md"
|
||||||
<YAxis
|
withBorder
|
||||||
axisLine={false}
|
bg={dark ? "#141D34" : "white"}
|
||||||
tickLine={false}
|
style={{ borderColor: dark ? "#141D34" : "white" }}
|
||||||
tick={{ fill: dark ? "#9CA3AF" : "#6B7280" }}
|
h="100%"
|
||||||
/>
|
>
|
||||||
<Tooltip
|
<Title order={3} fw={500} mb="md">
|
||||||
contentStyle={{
|
Jam Tersibuk
|
||||||
backgroundColor: dark ? "#1F2937" : "white",
|
</Title>
|
||||||
border: `1px solid ${dark ? "#374151" : "#E5E7EB"}`,
|
<Stack gap="sm">
|
||||||
borderRadius: "8px",
|
{busyHours.map((item, index) => (
|
||||||
color: dark ? "white" : "#1F2937",
|
<Box key={index}>
|
||||||
}}
|
<Text size="sm">{item.period}</Text>
|
||||||
/>
|
<Group align="center">
|
||||||
<Bar dataKey="interactions" radius={[4, 4, 0, 0]}>
|
<Progress value={item.percentage} flex={1} />
|
||||||
{weeklyData.map((entry, index) => (
|
<Text size="sm" fw={500}>
|
||||||
<Cell
|
{item.percentage}%
|
||||||
key={`cell-${index}`}
|
</Text>
|
||||||
fill={COLORS[index % COLORS.length]}
|
</Group>
|
||||||
/>
|
</Box>
|
||||||
))}
|
))}
|
||||||
</Bar>
|
</Stack>
|
||||||
</BarChart>
|
</Card>
|
||||||
</ResponsiveContainer>
|
</Grid.Col>
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Row 3: Two Insight Cards */}
|
{/* Topik Pertanyaan Terbanyak & Jam Tersibuk */}
|
||||||
<div
|
<Grid.Col span={{ base: 12, lg: 6 }}>
|
||||||
className="grid grid-cols-1 lg:grid-cols-2 gap-6"
|
<Stack gap="lg">
|
||||||
style={{
|
{/* Topik Pertanyaan Terbanyak */}
|
||||||
display: "grid",
|
<Card
|
||||||
gridTemplateColumns: "repeat(auto-fit, minmax(300px, 1fr))",
|
p="md"
|
||||||
gap: "1.5rem",
|
radius="md"
|
||||||
}}
|
withBorder
|
||||||
>
|
bg={dark ? "#141D34" : "white"}
|
||||||
{/* Left: Frequently Asked Topics */}
|
style={{ borderColor: dark ? "#141D34" : "white" }}
|
||||||
<div
|
h="100%"
|
||||||
className="rounded-xl shadow-sm p-6"
|
>
|
||||||
style={{
|
<Title order={3} fw={500} mb="md">
|
||||||
...cardStyle,
|
Topik Pertanyaan Terbanyak
|
||||||
borderRadius: "12px",
|
</Title>
|
||||||
boxShadow: "0 1px 3px rgba(0,0,0,0.1)",
|
<Stack gap="xs">
|
||||||
padding: "1.5rem",
|
{topTopics.map((item, index) => (
|
||||||
}}
|
<Group
|
||||||
>
|
key={index}
|
||||||
<h3
|
justify="space-between"
|
||||||
className="text-lg font-semibold mb-4"
|
align="center"
|
||||||
style={textStyle}
|
p="xs"
|
||||||
>
|
>
|
||||||
Topik Pertanyaan Terbanyak
|
<Text size="sm" fw={500}>
|
||||||
</h3>
|
{item.topic}
|
||||||
<div className="space-y-3">
|
</Text>
|
||||||
{topTopics.map((item, index) => (
|
<Badge variant="light" color="gray">
|
||||||
<div
|
{item.count}x
|
||||||
key={index}
|
</Badge>
|
||||||
className="flex items-center justify-between py-3"
|
</Group>
|
||||||
style={{
|
))}
|
||||||
borderBottom: `1px solid ${dark ? "#2d3748" : "#E5E7EB"}`,
|
</Stack>
|
||||||
}}
|
</Card>
|
||||||
>
|
|
||||||
<span
|
|
||||||
className="text-sm font-medium"
|
|
||||||
style={textStyle}
|
|
||||||
>
|
|
||||||
{item.topic}
|
|
||||||
</span>
|
|
||||||
<span className="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-darmasaba-blue-100 text-darmasaba-blue-800">
|
|
||||||
{item.count}x
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Right: Busy Hour Distribution */}
|
{/* Jam Tersibuk */}
|
||||||
<div
|
</Stack>
|
||||||
className="rounded-xl shadow-sm p-6"
|
</Grid.Col>
|
||||||
style={{
|
</Grid>
|
||||||
...cardStyle,
|
</Stack>
|
||||||
borderRadius: "12px",
|
</Box>
|
||||||
boxShadow: "0 1px 3px rgba(0,0,0,0.1)",
|
|
||||||
padding: "1.5rem",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<h3
|
|
||||||
className="text-lg font-semibold mb-4"
|
|
||||||
style={textStyle}
|
|
||||||
>
|
|
||||||
Distribusi Jam Tersibuk
|
|
||||||
</h3>
|
|
||||||
<div className="space-y-4">
|
|
||||||
{busyHours.map((item, index) => (
|
|
||||||
<div key={index}>
|
|
||||||
<div className="flex items-center justify-between mb-1">
|
|
||||||
<span
|
|
||||||
className="text-sm font-medium"
|
|
||||||
style={textStyle}
|
|
||||||
>
|
|
||||||
{item.period}
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
className="text-sm font-semibold"
|
|
||||||
style={textStyle}
|
|
||||||
>
|
|
||||||
{item.percentage}%
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className="w-full rounded-full h-2"
|
|
||||||
style={{ backgroundColor: dark ? "#2d3748" : "#E5E7EB" }}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="h-2 rounded-full transition-all"
|
|
||||||
style={{
|
|
||||||
width: `${item.percentage}%`,
|
|
||||||
backgroundColor: COLORS[index % COLORS.length],
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
export default JennaAnalytic;
|
||||||
export default JennaAnalytic;
|
|
||||||
@@ -118,6 +118,13 @@ const KeamananPage = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack gap="lg">
|
<Stack gap="lg">
|
||||||
|
{/* Page Header */}
|
||||||
|
<Group justify="space-between" align="center">
|
||||||
|
<Title order={2} c={dark ? "dark.0" : "black"}>
|
||||||
|
Keamanan Lingkungan Desa
|
||||||
|
</Title>
|
||||||
|
</Group>
|
||||||
|
|
||||||
{/* KPI Cards */}
|
{/* KPI Cards */}
|
||||||
<Grid gutter="md">
|
<Grid gutter="md">
|
||||||
{kpiData.map((kpi, index) => (
|
{kpiData.map((kpi, index) => (
|
||||||
@@ -315,4 +322,4 @@ const KeamananPage = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default KeamananPage;
|
export default KeamananPage;
|
||||||
@@ -1,568 +1,357 @@
|
|||||||
|
import { BarChart } from "@mantine/charts";
|
||||||
|
import {
|
||||||
|
Badge,
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
Card,
|
||||||
|
Grid,
|
||||||
|
Group,
|
||||||
|
Progress,
|
||||||
|
Stack,
|
||||||
|
Text,
|
||||||
|
Title,
|
||||||
|
useMantineColorScheme,
|
||||||
|
} from "@mantine/core";
|
||||||
import {
|
import {
|
||||||
IconCurrency,
|
IconCurrency,
|
||||||
IconTrendingDown,
|
IconTrendingDown,
|
||||||
IconTrendingUp,
|
IconTrendingUp,
|
||||||
IconCheck,
|
|
||||||
IconClock,
|
|
||||||
} from "@tabler/icons-react";
|
} from "@tabler/icons-react";
|
||||||
import { useMantineColorScheme } from "@mantine/core";
|
import React from "react";
|
||||||
import {
|
|
||||||
Bar,
|
// Sample Data
|
||||||
BarChart,
|
const kpiData = [
|
||||||
CartesianGrid,
|
{
|
||||||
Cell,
|
id: 1,
|
||||||
Line,
|
title: "Total APBDes",
|
||||||
LineChart,
|
value: "Rp 5.2M",
|
||||||
ResponsiveContainer,
|
sub: "Tahun 2025",
|
||||||
Tooltip,
|
icon: <IconCurrency className="h-6 w-6 text-muted-foreground" />,
|
||||||
XAxis,
|
},
|
||||||
YAxis,
|
{
|
||||||
} from "recharts";
|
id: 2,
|
||||||
|
title: "Realisasi",
|
||||||
|
value: "68%",
|
||||||
|
sub: "Rp 3.5M dari 5.2M",
|
||||||
|
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>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
title: "Pemasukan",
|
||||||
|
value: "Rp 580jt",
|
||||||
|
sub: "Bulan ini",
|
||||||
|
delta: "+8%",
|
||||||
|
deltaType: "positive",
|
||||||
|
icon: <IconTrendingUp className="h-6 w-6 text-muted-foreground" />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 4,
|
||||||
|
title: "Pengeluaran",
|
||||||
|
value: "Rp 520jt",
|
||||||
|
sub: "Bulan ini",
|
||||||
|
icon: <IconTrendingDown className="h-6 w-6 text-muted-foreground" />,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const incomeExpenseData = [
|
||||||
|
{ month: "Apr", income: 450, expense: 380 },
|
||||||
|
{ month: "Mei", income: 520, expense: 420 },
|
||||||
|
{ month: "Jun", income: 480, expense: 500 },
|
||||||
|
{ month: "Jul", income: 580, expense: 450 },
|
||||||
|
{ month: "Agu", income: 550, expense: 520 },
|
||||||
|
{ month: "Sep", income: 600, expense: 480 },
|
||||||
|
{ month: "Okt", income: 580, expense: 520 },
|
||||||
|
];
|
||||||
|
|
||||||
|
const allocationData = [
|
||||||
|
{ sector: "Pembangunan", amount: 1200 },
|
||||||
|
{ sector: "Kesehatan", amount: 800 },
|
||||||
|
{ sector: "Pendidikan", amount: 650 },
|
||||||
|
{ sector: "Sosial", amount: 550 },
|
||||||
|
{ sector: "Kebudayaan", amount: 400 },
|
||||||
|
{ sector: "Teknologi", amount: 300 },
|
||||||
|
];
|
||||||
|
|
||||||
|
const assistanceFundData = [
|
||||||
|
{ source: "Dana Desa (DD)", amount: 1800, status: "cair" },
|
||||||
|
{ source: "Alokasi Dana Desa (ADD)", amount: 950, status: "cair" },
|
||||||
|
{ source: "Bagi Hasil Pajak", amount: 450, status: "cair" },
|
||||||
|
{ source: "Hibah Provinsi", amount: 300, status: "proses" },
|
||||||
|
];
|
||||||
|
|
||||||
|
const apbdReport = {
|
||||||
|
income: [
|
||||||
|
{ category: "Dana Desa", amount: 1800 },
|
||||||
|
{ category: "Alokasi Dana Desa", amount: 480 },
|
||||||
|
{ category: "Bagi Hasil Pajak & Retribusi", amount: 300 },
|
||||||
|
{ category: "Pendapatan Asli Desa", amount: 200 },
|
||||||
|
{ category: "Hibah Bantuan", amount: 300 },
|
||||||
|
],
|
||||||
|
expenses: [
|
||||||
|
{ category: "Penyelenggaraan Pemerintah", amount: 425 },
|
||||||
|
{ category: "Pembangunan Desa", amount: 850 },
|
||||||
|
{ category: "Pembinaan Kemasyarakatan", amount: 320 },
|
||||||
|
{ category: "Pemberdayaan Masyarakat", amount: 380 },
|
||||||
|
{ category: "Penanggulangan Bencana", amount: 180 },
|
||||||
|
],
|
||||||
|
totalIncome: 3080,
|
||||||
|
totalExpenses: 2155,
|
||||||
|
};
|
||||||
|
|
||||||
const KeuanganAnggaran = () => {
|
const KeuanganAnggaran = () => {
|
||||||
const { colorScheme } = useMantineColorScheme();
|
const { colorScheme } = useMantineColorScheme();
|
||||||
const dark = colorScheme === "dark";
|
const dark = colorScheme === "dark";
|
||||||
|
|
||||||
// KPI Data
|
|
||||||
const kpiData = [
|
|
||||||
{
|
|
||||||
id: 1,
|
|
||||||
title: "Total APBDes",
|
|
||||||
value: "Rp 5.2M",
|
|
||||||
subtitle: "Tahun 2025",
|
|
||||||
icon: IconCurrency,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 2,
|
|
||||||
title: "Realisasi",
|
|
||||||
value: "68%",
|
|
||||||
subtitle: "Rp 3.5M dari 5.2M",
|
|
||||||
icon: IconCheck,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 3,
|
|
||||||
title: "Pemasukan",
|
|
||||||
value: "Rp 580jt",
|
|
||||||
subtitle: "Bulan ini",
|
|
||||||
delta: "+8%",
|
|
||||||
icon: IconTrendingUp,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 4,
|
|
||||||
title: "Pengeluaran",
|
|
||||||
value: "Rp 520jt",
|
|
||||||
subtitle: "Bulan ini",
|
|
||||||
icon: IconTrendingDown,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
// Income vs Expense data
|
|
||||||
const incomeExpenseData = [
|
|
||||||
{ month: "Apr", income: 450, expense: 380 },
|
|
||||||
{ month: "Mei", income: 520, expense: 420 },
|
|
||||||
{ month: "Jun", income: 480, expense: 500 },
|
|
||||||
{ month: "Jul", income: 580, expense: 450 },
|
|
||||||
{ month: "Agu", income: 550, expense: 520 },
|
|
||||||
{ month: "Sep", income: 600, expense: 480 },
|
|
||||||
{ month: "Okt", income: 580, expense: 520 },
|
|
||||||
];
|
|
||||||
|
|
||||||
// Allocation data
|
|
||||||
const allocationData = [
|
|
||||||
{ sector: "Pembangunan", amount: 1200 },
|
|
||||||
{ sector: "Kesehatan", amount: 800 },
|
|
||||||
{ sector: "Pendidikan", amount: 650 },
|
|
||||||
{ sector: "Sosial", amount: 550 },
|
|
||||||
{ sector: "Kebudayaan", amount: 400 },
|
|
||||||
{ sector: "Teknologi", amount: 300 },
|
|
||||||
];
|
|
||||||
|
|
||||||
// Assistance fund data
|
|
||||||
const assistanceFundData = [
|
|
||||||
{ source: "Dana Desa (DD)", amount: 1800, status: "cair" },
|
|
||||||
{ source: "Alokasi Dana Desa (ADD)", amount: 950, status: "cair" },
|
|
||||||
{ source: "Bagi Hasil Pajak", amount: 450, status: "cair" },
|
|
||||||
{ source: "Hibah Provinsi", amount: 300, status: "proses" },
|
|
||||||
];
|
|
||||||
|
|
||||||
// APBDes Report data
|
|
||||||
const apbdReport = {
|
|
||||||
income: [
|
|
||||||
{ category: "Dana Desa", amount: 1800 },
|
|
||||||
{ category: "Alokasi Dana Desa", amount: 480 },
|
|
||||||
{ category: "Bagi Hasil Pajak & Retribusi", amount: 300 },
|
|
||||||
{ category: "Pendapatan Asli Desa", amount: 200 },
|
|
||||||
{ category: "Hibah Bantuan", amount: 300 },
|
|
||||||
],
|
|
||||||
expenses: [
|
|
||||||
{ category: "Penyelenggaraan Pemerintah", amount: 425 },
|
|
||||||
{ category: "Pembangunan Desa", amount: 850 },
|
|
||||||
{ category: "Pembinaan Kemasyarakatan", amount: 320 },
|
|
||||||
{ category: "Pemberdayaan Masyarakat", amount: 380 },
|
|
||||||
{ category: "Penanggulangan Bencana", amount: 180 },
|
|
||||||
],
|
|
||||||
totalIncome: 3080,
|
|
||||||
totalExpenses: 2155,
|
|
||||||
};
|
|
||||||
|
|
||||||
const COLORS = ["#1E3A5F", "#3B82F6", "#60A5FA", "#93C5FD", "#DBEAFE"];
|
|
||||||
|
|
||||||
const cardStyle = {
|
|
||||||
backgroundColor: dark ? "#1E293B" : "white",
|
|
||||||
border: `1px solid ${dark ? "#1E293B" : "white"}`,
|
|
||||||
};
|
|
||||||
|
|
||||||
const textStyle = {
|
|
||||||
color: dark ? "white" : "#1F2937",
|
|
||||||
};
|
|
||||||
|
|
||||||
const subtitleStyle = {
|
|
||||||
color: dark ? "#9CA3AF" : "#6B7280",
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<Box>
|
||||||
className="min-h-screen"
|
<Stack gap="xl">
|
||||||
style={{
|
{/* KPI Cards */}
|
||||||
backgroundColor: dark ? "#0F172A" : "#F3F4F6",
|
<Grid gutter="lg">
|
||||||
minHeight: "100vh",
|
|
||||||
padding: "1.5rem",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="max-w-7xl mx-auto"
|
|
||||||
style={{
|
|
||||||
maxWidth: "80rem",
|
|
||||||
marginLeft: "auto",
|
|
||||||
marginRight: "auto",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{/* Row 1: 4 Summary Metrics Cards */}
|
|
||||||
<div
|
|
||||||
className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-6"
|
|
||||||
style={{
|
|
||||||
display: "grid",
|
|
||||||
gridTemplateColumns: "repeat(4, 1fr)",
|
|
||||||
gap: "1.5rem",
|
|
||||||
marginBottom: "1.5rem",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{kpiData.map((kpi) => (
|
{kpiData.map((kpi) => (
|
||||||
<div
|
<Grid.Col key={kpi.id} span={{ base: 12, md: 6, lg: 3 }}>
|
||||||
key={kpi.id}
|
<Card
|
||||||
className="rounded-xl shadow-sm p-6"
|
p="md"
|
||||||
style={{
|
radius="md"
|
||||||
...cardStyle,
|
withBorder
|
||||||
borderRadius: "12px",
|
bg={dark ? "#141D34" : "white"}
|
||||||
boxShadow: "0 1px 3px rgba(0,0,0,0.1)",
|
style={{ borderColor: dark ? "#141D34" : "white" }}
|
||||||
padding: "1.5rem",
|
h="100%"
|
||||||
}}
|
>
|
||||||
>
|
<Group justify="space-between" align="flex-start" mb="xs">
|
||||||
<div className="flex items-center justify-between">
|
<Text size="sm" fw={500} c="dimmed">
|
||||||
<div className="flex-1">
|
|
||||||
<h3
|
|
||||||
className="text-sm font-medium mb-1"
|
|
||||||
style={subtitleStyle}
|
|
||||||
>
|
|
||||||
{kpi.title}
|
{kpi.title}
|
||||||
</h3>
|
</Text>
|
||||||
<p
|
{React.cloneElement(kpi.icon, {
|
||||||
className="text-3xl font-bold mb-1"
|
className: "h-6 w-6",
|
||||||
style={textStyle}
|
color: "var(--mantine-color-dimmed)",
|
||||||
|
})}
|
||||||
|
</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.value}
|
{kpi.delta}
|
||||||
</p>
|
</Text>
|
||||||
<p
|
)}
|
||||||
className="text-xs"
|
{kpi.sub && (
|
||||||
style={subtitleStyle}
|
<Text size="xs" c="dimmed" mt="auto">
|
||||||
>
|
{kpi.sub}
|
||||||
{kpi.subtitle}
|
</Text>
|
||||||
</p>
|
)}
|
||||||
{kpi.delta && (
|
</Card>
|
||||||
<p className="text-xs mt-1" style={{ color: "#22C55E" }}>
|
</Grid.Col>
|
||||||
{kpi.delta}
|
|
||||||
</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: "#1F3A5F" }}
|
|
||||||
>
|
|
||||||
<kpi.icon size={24} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
))}
|
||||||
</div>
|
</Grid>
|
||||||
|
|
||||||
{/* Row 2: Line Chart Section */}
|
{/* Charts Section */}
|
||||||
<div
|
<Grid gutter="lg">
|
||||||
className="rounded-xl shadow-sm p-6 mb-6"
|
{/* Grafik Pemasukan vs Pengeluaran */}
|
||||||
style={{
|
<Grid.Col span={{ base: 12, lg: 6 }}>
|
||||||
...cardStyle,
|
<Card
|
||||||
borderRadius: "12px",
|
p="md"
|
||||||
boxShadow: "0 1px 3px rgba(0,0,0,0.1)",
|
radius="md"
|
||||||
padding: "1.5rem",
|
withBorder
|
||||||
marginBottom: "1.5rem",
|
bg={dark ? "#141D34" : "white"}
|
||||||
}}
|
style={{ borderColor: dark ? "#141D34" : "white" }}
|
||||||
>
|
>
|
||||||
<h3
|
<Title order={3} fw={500} mb="md">
|
||||||
className="text-lg font-semibold mb-4"
|
Pemasukan vs Pengeluaran
|
||||||
style={textStyle}
|
</Title>
|
||||||
>
|
<BarChart
|
||||||
Pemasukan vs Pengeluaran
|
h={300}
|
||||||
</h3>
|
data={incomeExpenseData}
|
||||||
<ResponsiveContainer width="100%" height={300}>
|
|
||||||
<LineChart data={incomeExpenseData}>
|
|
||||||
<CartesianGrid
|
|
||||||
strokeDasharray="3 3"
|
|
||||||
vertical={false}
|
|
||||||
stroke={dark ? "#334155" : "#E5E7EB"}
|
|
||||||
/>
|
|
||||||
<XAxis
|
|
||||||
dataKey="month"
|
dataKey="month"
|
||||||
axisLine={false}
|
series={[
|
||||||
tickLine={false}
|
{ name: "income", color: "green", label: "Pemasukan" },
|
||||||
tick={{ fill: dark ? "#9CA3AF" : "#6B7280" }}
|
{ name: "expense", color: "red", label: "Pengeluaran" },
|
||||||
|
]}
|
||||||
|
withLegend
|
||||||
/>
|
/>
|
||||||
<YAxis
|
</Card>
|
||||||
axisLine={false}
|
</Grid.Col>
|
||||||
tickLine={false}
|
|
||||||
tick={{ fill: dark ? "#9CA3AF" : "#6B7280" }}
|
|
||||||
tickFormatter={(value) => `${value}jt`}
|
|
||||||
/>
|
|
||||||
<Tooltip
|
|
||||||
contentStyle={{
|
|
||||||
backgroundColor: dark ? "#1F2937" : "white",
|
|
||||||
border: `1px solid ${dark ? "#374151" : "#E5E7EB"}`,
|
|
||||||
borderRadius: "8px",
|
|
||||||
color: dark ? "white" : "#1F2937",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<Line
|
|
||||||
type="monotone"
|
|
||||||
dataKey="income"
|
|
||||||
stroke="#22C55E"
|
|
||||||
strokeWidth={3}
|
|
||||||
dot={{ fill: "#22C55E", strokeWidth: 2, r: 5 }}
|
|
||||||
activeDot={{ r: 7 }}
|
|
||||||
name="Pemasukan"
|
|
||||||
/>
|
|
||||||
<Line
|
|
||||||
type="monotone"
|
|
||||||
dataKey="expense"
|
|
||||||
stroke="#EF4444"
|
|
||||||
strokeWidth={3}
|
|
||||||
dot={{ fill: "#EF4444", strokeWidth: 2, r: 5 }}
|
|
||||||
activeDot={{ r: 7 }}
|
|
||||||
name="Pengeluaran"
|
|
||||||
/>
|
|
||||||
</LineChart>
|
|
||||||
</ResponsiveContainer>
|
|
||||||
|
|
||||||
{/* Legend */}
|
{/* Alokasi Anggaran Per Sektor */}
|
||||||
<div className="flex items-center gap-6 mt-4">
|
<Grid.Col span={{ base: 12, lg: 6 }}>
|
||||||
<div className="flex items-center gap-2">
|
<Card
|
||||||
<div
|
p="md"
|
||||||
className="w-3 h-3 rounded-full"
|
radius="md"
|
||||||
style={{ backgroundColor: "#22C55E" }}
|
withBorder
|
||||||
/>
|
bg={dark ? "#141D34" : "white"}
|
||||||
<span className="text-sm" style={subtitleStyle}>
|
style={{ borderColor: dark ? "#141D34" : "white" }}
|
||||||
Pemasukan
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<div
|
|
||||||
className="w-3 h-3 rounded-full"
|
|
||||||
style={{ backgroundColor: "#EF4444" }}
|
|
||||||
/>
|
|
||||||
<span className="text-sm" style={subtitleStyle}>
|
|
||||||
Pengeluaran
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Row 3: Analytics Section */}
|
|
||||||
|
|
||||||
|
|
||||||
<div
|
|
||||||
className="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-6"
|
|
||||||
style={{
|
|
||||||
display: "grid",
|
|
||||||
gridTemplateColumns: "repeat(2, 1fr)",
|
|
||||||
gap: "1.5rem",
|
|
||||||
marginBottom: "1.5rem",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{/* Left: Horizontal Bar Chart */}
|
|
||||||
<div
|
|
||||||
className="rounded-xl shadow-sm p-6"
|
|
||||||
style={{
|
|
||||||
...cardStyle,
|
|
||||||
borderRadius: "12px",
|
|
||||||
boxShadow: "0 1px 3px rgba(0,0,0,0.1)",
|
|
||||||
padding: "1.5rem",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<h3
|
|
||||||
className="text-lg font-semibold mb-4"
|
|
||||||
style={textStyle}
|
|
||||||
>
|
>
|
||||||
Alokasi Anggaran Per Sektor
|
<Title order={3} fw={500} mb="md">
|
||||||
</h3>
|
Alokasi Anggaran Per Sektor
|
||||||
<ResponsiveContainer width="100%" height={300}>
|
</Title>
|
||||||
<BarChart data={allocationData} layout="vertical">
|
<BarChart
|
||||||
<CartesianGrid
|
h={300}
|
||||||
strokeDasharray="3 3"
|
data={allocationData}
|
||||||
horizontal={false}
|
dataKey="sector"
|
||||||
stroke={dark ? "#334155" : "#E5E7EB"}
|
series={[
|
||||||
/>
|
{ name: "amount", color: "darmasaba-navy", label: "Jumlah" },
|
||||||
<XAxis
|
]}
|
||||||
type="number"
|
withLegend
|
||||||
axisLine={false}
|
orientation="horizontal"
|
||||||
tickLine={false}
|
/>
|
||||||
tick={{ fill: dark ? "#9CA3AF" : "#6B7280" }}
|
</Card>
|
||||||
tickFormatter={(value) => `${value}jt`}
|
</Grid.Col>
|
||||||
/>
|
</Grid>
|
||||||
<YAxis
|
|
||||||
dataKey="sector"
|
<Grid gutter="lg">
|
||||||
type="category"
|
{/* Dana Bantuan & Hibah */}
|
||||||
axisLine={false}
|
<Grid.Col span={{ base: 12, lg: 6 }}>
|
||||||
tickLine={false}
|
<Card
|
||||||
tick={{ fill: dark ? "#9CA3AF" : "#374151" }}
|
p="md"
|
||||||
width={120}
|
radius="md"
|
||||||
/>
|
withBorder
|
||||||
<Tooltip
|
bg={dark ? "#141D34" : "white"}
|
||||||
contentStyle={{
|
style={{ borderColor: dark ? "#141D34" : "white" }}
|
||||||
backgroundColor: dark ? "#1F2937" : "white",
|
>
|
||||||
border: `1px solid ${dark ? "#374151" : "#E5E7EB"}`,
|
<Title order={3} fw={500} mb="md">
|
||||||
borderRadius: "8px",
|
Dana Bantuan & Hibah
|
||||||
color: dark ? "white" : "#1F2937",
|
</Title>
|
||||||
}}
|
<Stack gap="sm">
|
||||||
/>
|
{assistanceFundData.map((fund, index) => (
|
||||||
<Bar dataKey="amount" radius={[0, 4, 4, 0]}>
|
<Group
|
||||||
{allocationData.map((entry, index) => (
|
key={index}
|
||||||
<Cell
|
justify="space-between"
|
||||||
key={`cell-${index}`}
|
align="center"
|
||||||
fill={COLORS[index % COLORS.length]}
|
p="sm"
|
||||||
/>
|
style={{
|
||||||
|
border: "1px solid var(--mantine-color-gray-3)",
|
||||||
|
borderRadius: "var(--mantine-radius-sm)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box>
|
||||||
|
<Text size="sm" fw={500}>
|
||||||
|
{fund.source}
|
||||||
|
</Text>
|
||||||
|
<Text size="sm" c="dimmed">
|
||||||
|
Rp {fund.amount.toLocaleString()}jt
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
<Badge
|
||||||
|
variant="light"
|
||||||
|
color={fund.status === "cair" ? "green" : "yellow"}
|
||||||
|
>
|
||||||
|
{fund.status}
|
||||||
|
</Badge>
|
||||||
|
</Group>
|
||||||
|
))}
|
||||||
|
</Stack>
|
||||||
|
</Card>
|
||||||
|
</Grid.Col>
|
||||||
|
|
||||||
|
{/* Laporan APBDes */}
|
||||||
|
<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">
|
||||||
|
Laporan APBDes
|
||||||
|
</Title>
|
||||||
|
|
||||||
|
<Box mb="md">
|
||||||
|
<Title order={4} mb="sm">
|
||||||
|
Pendapatan
|
||||||
|
</Title>
|
||||||
|
<Stack gap="xs">
|
||||||
|
{apbdReport.income.map((item, index) => (
|
||||||
|
<Group key={index} justify="space-between">
|
||||||
|
<Text size="sm">{item.category}</Text>
|
||||||
|
<Text size="sm" c="green">
|
||||||
|
Rp {item.amount.toLocaleString()}jt
|
||||||
|
</Text>
|
||||||
|
</Group>
|
||||||
))}
|
))}
|
||||||
</Bar>
|
<Group justify="space-between" mt="sm">
|
||||||
</BarChart>
|
<Text fw={700}>Total Pendapatan:</Text>
|
||||||
</ResponsiveContainer>
|
<Text fw={700} c="green">
|
||||||
</div>
|
Rp {apbdReport.totalIncome.toLocaleString()}jt
|
||||||
{/* Right: Assistance Funds List */}
|
</Text>
|
||||||
<div
|
</Group>
|
||||||
className="rounded-xl shadow-sm p-6"
|
</Stack>
|
||||||
style={{
|
</Box>
|
||||||
...cardStyle,
|
|
||||||
borderRadius: "12px",
|
|
||||||
boxShadow: "0 1px 3px rgba(0,0,0,0.1)",
|
|
||||||
padding: "1.5rem",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<h3
|
|
||||||
className="text-lg font-semibold mb-4"
|
|
||||||
style={textStyle}
|
|
||||||
>
|
|
||||||
Dana Bantuan dan Hibah
|
|
||||||
</h3>
|
|
||||||
<div className="space-y-3">
|
|
||||||
{assistanceFundData.map((fund, index) => (
|
|
||||||
<div
|
|
||||||
key={index}
|
|
||||||
className="flex items-center justify-between p-4 rounded-lg"
|
|
||||||
style={{
|
|
||||||
backgroundColor: dark ? "#334155" : "#F9FAFB",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<div>
|
|
||||||
<p
|
|
||||||
className="text-sm font-medium"
|
|
||||||
style={textStyle}
|
|
||||||
>
|
|
||||||
{fund.source}
|
|
||||||
</p>
|
|
||||||
<p
|
|
||||||
className="text-xs mt-1"
|
|
||||||
style={subtitleStyle}
|
|
||||||
>
|
|
||||||
Rp {fund.amount.toLocaleString()}jt
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<span
|
|
||||||
className="inline-flex items-center px-3 py-1 rounded-full text-xs font-medium"
|
|
||||||
style={{
|
|
||||||
backgroundColor: fund.status === "cair" ? "#DCFCE7" : "#FEF3C7",
|
|
||||||
color: fund.status === "cair" ? "#166534" : "#92400E",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{fund.status === "cair" ? (
|
|
||||||
<IconCheck size={14} className="mr-1" />
|
|
||||||
) : (
|
|
||||||
<IconClock size={14} className="mr-1" />
|
|
||||||
)}
|
|
||||||
{fund.status}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
<Box>
|
||||||
{/* Row 4: Report Section */}
|
<Title order={4} mb="sm">
|
||||||
<div
|
Belanja
|
||||||
className="rounded-xl shadow-sm p-6"
|
</Title>
|
||||||
style={{
|
<Stack gap="xs">
|
||||||
...cardStyle,
|
{apbdReport.expenses.map((item, index) => (
|
||||||
borderRadius: "12px",
|
<Group key={index} justify="space-between">
|
||||||
boxShadow: "0 1px 3px rgba(0,0,0,0.1)",
|
<Text size="sm">{item.category}</Text>
|
||||||
padding: "1.5rem",
|
<Text size="sm" c="red">
|
||||||
}}
|
Rp {item.amount.toLocaleString()}jt
|
||||||
>
|
</Text>
|
||||||
<h3
|
</Group>
|
||||||
className="text-lg font-semibold mb-6"
|
))}
|
||||||
style={textStyle}
|
<Group justify="space-between" mt="sm">
|
||||||
>
|
<Text fw={700}>Total Belanja:</Text>
|
||||||
Laporan APBDes
|
<Text fw={700} c="red">
|
||||||
</h3>
|
Rp {apbdReport.totalExpenses.toLocaleString()}jt
|
||||||
<div
|
</Text>
|
||||||
className="grid grid-cols-1 md:grid-cols-2 gap-8"
|
</Group>
|
||||||
style={{
|
</Stack>
|
||||||
display: "grid",
|
</Box>
|
||||||
gridTemplateColumns: "repeat(2, 1fr)",
|
|
||||||
gap: "2rem",
|
<Box
|
||||||
}}
|
mt="md"
|
||||||
>
|
pt="md"
|
||||||
{/* Left: Pendapatan */}
|
style={{ borderTop: "1px solid var(--mantine-color-gray-3)" }}
|
||||||
<div>
|
|
||||||
<h4
|
|
||||||
className="text-base font-semibold mb-4"
|
|
||||||
style={textStyle}
|
|
||||||
>
|
>
|
||||||
Pendapatan
|
<Group justify="space-between">
|
||||||
</h4>
|
<Text fw={700}>Saldo:</Text>
|
||||||
<div className="space-y-3">
|
<Text
|
||||||
{apbdReport.income.map((item, index) => (
|
fw={700}
|
||||||
<div
|
c={
|
||||||
key={index}
|
apbdReport.totalIncome > apbdReport.totalExpenses
|
||||||
className="flex items-center justify-between py-2"
|
? "green"
|
||||||
style={{
|
: "red"
|
||||||
borderBottom: `1px solid ${dark ? "#334155" : "#F3F4F6"}`,
|
}
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<span className="text-sm" style={subtitleStyle}>
|
Rp{" "}
|
||||||
{item.category}
|
{(
|
||||||
</span>
|
apbdReport.totalIncome - apbdReport.totalExpenses
|
||||||
<span
|
).toLocaleString()}
|
||||||
className="text-sm font-medium"
|
jt
|
||||||
style={{ color: "#22C55E" }}
|
</Text>
|
||||||
>
|
</Group>
|
||||||
Rp {item.amount.toLocaleString()}jt
|
</Box>
|
||||||
</span>
|
</Card>
|
||||||
</div>
|
</Grid.Col>
|
||||||
))}
|
</Grid>
|
||||||
<div
|
</Stack>
|
||||||
className="flex items-center justify-between py-3 mt-4 pt-4"
|
</Box>
|
||||||
style={{
|
|
||||||
borderTop: `2px solid ${dark ? "#334155" : "#E5E7EB"}`,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<span className="text-base font-bold" style={textStyle}>
|
|
||||||
Total Pendapatan
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
className="text-base font-bold"
|
|
||||||
style={{ color: "#22C55E" }}
|
|
||||||
>
|
|
||||||
Rp {apbdReport.totalIncome.toLocaleString()}jt
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Right: Belanja */}
|
|
||||||
<div>
|
|
||||||
<h4
|
|
||||||
className="text-base font-semibold mb-4"
|
|
||||||
style={textStyle}
|
|
||||||
>
|
|
||||||
Belanja
|
|
||||||
</h4>
|
|
||||||
<div className="space-y-3">
|
|
||||||
{apbdReport.expenses.map((item, index) => (
|
|
||||||
<div
|
|
||||||
key={index}
|
|
||||||
className="flex items-center justify-between py-2"
|
|
||||||
style={{
|
|
||||||
borderBottom: `1px solid ${dark ? "#334155" : "#F3F4F6"}`,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<span className="text-sm" style={subtitleStyle}>
|
|
||||||
{item.category}
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
className="text-sm font-medium"
|
|
||||||
style={{ color: "#EF4444" }}
|
|
||||||
>
|
|
||||||
Rp {item.amount.toLocaleString()}jt
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
<div
|
|
||||||
className="flex items-center justify-between py-3 mt-4 pt-4"
|
|
||||||
style={{
|
|
||||||
borderTop: `2px solid ${dark ? "#334155" : "#E5E7EB"}`,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<span className="text-base font-bold" style={textStyle}>
|
|
||||||
Total Belanja
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
className="text-base font-bold"
|
|
||||||
style={{ color: "#EF4444" }}
|
|
||||||
>
|
|
||||||
Rp {apbdReport.totalExpenses.toLocaleString()}jt
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Footer: Balance */}
|
|
||||||
<div
|
|
||||||
className="flex items-center justify-between py-4 mt-6 pt-6"
|
|
||||||
style={{
|
|
||||||
borderTop: `2px solid ${dark ? "#334155" : "#E5E7EB"}`,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<span className="text-lg font-bold" style={textStyle}>
|
|
||||||
Saldo
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
className="text-lg font-bold"
|
|
||||||
style={{
|
|
||||||
color:
|
|
||||||
apbdReport.totalIncome > apbdReport.totalExpenses
|
|
||||||
? "#22C55E"
|
|
||||||
: "#EF4444",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Rp{" "}
|
|
||||||
{(
|
|
||||||
apbdReport.totalIncome - apbdReport.totalExpenses
|
|
||||||
).toLocaleString()}
|
|
||||||
jt
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default KeuanganAnggaran;
|
export default KeuanganAnggaran;
|
||||||
@@ -1,3 +1,21 @@
|
|||||||
|
import {
|
||||||
|
ActionIcon,
|
||||||
|
Box,
|
||||||
|
Card,
|
||||||
|
Divider,
|
||||||
|
Grid,
|
||||||
|
GridCol,
|
||||||
|
Group,
|
||||||
|
List,
|
||||||
|
Badge as MantineBadge,
|
||||||
|
Progress as MantineProgress,
|
||||||
|
Skeleton,
|
||||||
|
Stack,
|
||||||
|
Text,
|
||||||
|
ThemeIcon,
|
||||||
|
Title,
|
||||||
|
useMantineColorScheme,
|
||||||
|
} from "@mantine/core";
|
||||||
import {
|
import {
|
||||||
Bar,
|
Bar,
|
||||||
BarChart,
|
BarChart,
|
||||||
@@ -10,381 +28,518 @@ import {
|
|||||||
XAxis,
|
XAxis,
|
||||||
YAxis,
|
YAxis,
|
||||||
} from "recharts";
|
} from "recharts";
|
||||||
import { useMantineColorScheme } from "@mantine/core";
|
import { Button } from "@/components/ui/button";
|
||||||
import { IconMessage } from "@tabler/icons-react";
|
|
||||||
|
|
||||||
const KinerjaDivisi = () => {
|
const KinerjaDivisi = () => {
|
||||||
const { colorScheme } = useMantineColorScheme();
|
const { colorScheme } = useMantineColorScheme();
|
||||||
const dark = colorScheme === "dark";
|
const dark = colorScheme === "dark";
|
||||||
|
|
||||||
// Top row - 4 activity cards
|
// Data for division progress chart
|
||||||
const activities = [
|
const divisionProgressData = [
|
||||||
|
{ name: "Sekretariat", selesai: 12, berjalan: 5, tertunda: 2 },
|
||||||
|
{ name: "Keuangan", selesai: 8, berjalan: 7, tertunda: 1 },
|
||||||
|
{ name: "Sosial", selesai: 10, berjalan: 3, tertunda: 4 },
|
||||||
|
{ name: "Humas", selesai: 6, berjalan: 9, tertunda: 3 },
|
||||||
|
];
|
||||||
|
|
||||||
|
// Division task summaries
|
||||||
|
const divisionTasks = [
|
||||||
{
|
{
|
||||||
title: "Rakor 2025",
|
name: "Sekretariat",
|
||||||
progress: 100,
|
tasks: [
|
||||||
date: "15 Jan 2025",
|
{ title: "Laporan Bulanan", status: "selesai" },
|
||||||
|
{ title: "Arsip Dokumen", status: "berjalan" },
|
||||||
|
{ title: "Undangan Rapat", status: "tertunda" },
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Pemutakhiran Indeks Desa",
|
name: "Keuangan",
|
||||||
progress: 100,
|
tasks: [
|
||||||
date: "20 Feb 2025",
|
{ title: "Laporan APBDes", status: "selesai" },
|
||||||
|
{ title: "Verifikasi Dana", status: "tertunda" },
|
||||||
|
{ title: "Pengeluaran Harian", status: "berjalan" },
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Mengurus akta cerai warga",
|
name: "Sosial",
|
||||||
progress: 100,
|
tasks: [
|
||||||
date: "5 Mar 2025",
|
{ title: "Program Bantuan", status: "selesai" },
|
||||||
|
{ title: "Kegiatan Posyandu", status: "berjalan" },
|
||||||
|
{ title: "Monitoring Stunting", status: "tertunda" },
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Pasek 7 desa adat",
|
name: "Humas",
|
||||||
|
tasks: [
|
||||||
|
{ title: "Publikasi Kegiatan", status: "selesai" },
|
||||||
|
{ title: "Koordinasi Media", status: "berjalan" },
|
||||||
|
{ title: "Laporan Kegiatan", status: "tertunda" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
// Archive items
|
||||||
|
const archiveItems = [
|
||||||
|
{ name: "Surat Keputusan", count: 12 },
|
||||||
|
{ name: "Laporan Keuangan", count: 8 },
|
||||||
|
{ name: "Dokumentasi", count: 24 },
|
||||||
|
{ name: "Notulensi Rapat", count: 15 },
|
||||||
|
];
|
||||||
|
|
||||||
|
// Activity progress
|
||||||
|
const activityProgress = [
|
||||||
|
{
|
||||||
|
name: "Pembangunan Jalan",
|
||||||
|
progress: 75,
|
||||||
|
date: "15 Feb 2026",
|
||||||
|
status: "berjalan",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Posyandu Bulanan",
|
||||||
progress: 100,
|
progress: 100,
|
||||||
date: "10 Mar 2025",
|
date: "10 Feb 2026",
|
||||||
|
status: "selesai",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Vaksinasi Massal",
|
||||||
|
progress: 45,
|
||||||
|
date: "20 Feb 2026",
|
||||||
|
status: "berjalan",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Festival Budaya",
|
||||||
|
progress: 20,
|
||||||
|
date: "5 Mar 2026",
|
||||||
|
status: "berjalan",
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
// Document statistics
|
// Document statistics
|
||||||
const documentStats = [
|
const documentStats = [
|
||||||
{ name: "Gambar", value: 300, color: "#FAC858" },
|
{ name: "Gambar", value: 42 },
|
||||||
{ name: "Dokumen", value: 310, color: "#92CC76" },
|
{ name: "Dokumen", value: 87 },
|
||||||
];
|
];
|
||||||
|
|
||||||
// Activity progress statistics
|
// Activity progress statistics
|
||||||
const activityProgressStats = [
|
const activityProgressStats = [
|
||||||
{ name: "Selesai", value: 83.33, fill: "#92CC76" },
|
{ name: "Selesai", value: 12 },
|
||||||
{ name: "Dikerjakan", value: 16.67, fill: "#FAC858" },
|
{ name: "Dikerjakan", value: 8 },
|
||||||
{ name: "Segera Dikerjakan", value: 0, fill: "#5470C6" },
|
{ name: "Segera Dikerjakan", value: 5 },
|
||||||
{ name: "Dibatalkan", value: 0, fill: "#EE6767" },
|
{ name: "Dibatalkan", value: 2 },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const COLORS = ["#10B981", "#F59E0B", "#EF4444", "#6B7280"];
|
||||||
|
const STATUS_COLORS: Record<string, string> = {
|
||||||
|
selesai: "green",
|
||||||
|
berjalan: "blue",
|
||||||
|
tertunda: "red",
|
||||||
|
proses: "yellow",
|
||||||
|
};
|
||||||
|
|
||||||
// Discussion data
|
// Discussion data
|
||||||
const discussions = [
|
const discussions = [
|
||||||
{
|
{
|
||||||
title: "Pembahasan APBDes 2026",
|
title: "Pembahasan APBDes 2026",
|
||||||
sender: "Kepala Desa",
|
sender: "Kepala Desa",
|
||||||
date: "10 Mar 2025",
|
timestamp: "2 jam yang lalu",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Kegiatan Posyandu",
|
title: "Kegiatan Posyandu",
|
||||||
sender: "Divisi Sosial",
|
sender: "Divisi Sosial",
|
||||||
date: "9 Mar 2025",
|
timestamp: "5 jam yang lalu",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Festival Budaya",
|
title: "Festival Budaya",
|
||||||
sender: "Divisi Humas",
|
sender: "Divisi Humas",
|
||||||
date: "8 Mar 2025",
|
timestamp: "1 hari yang lalu",
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// Today's agenda
|
||||||
|
const todayAgenda = [
|
||||||
|
{ time: "09:00", event: "Rapat Evaluasi Bulanan" },
|
||||||
|
{ time: "14:00", event: "Koordinasi Program Bantuan" },
|
||||||
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<Stack gap="lg">
|
||||||
className="min-h-screen"
|
{/* Grafik Progres Tugas per Divisi */}
|
||||||
style={{
|
<Card
|
||||||
backgroundColor: dark ? "#10192D" : "#F3F4F6",
|
p="md"
|
||||||
minHeight: "100vh",
|
radius="md"
|
||||||
padding: "1.5rem",
|
withBorder
|
||||||
}}
|
bg={dark ? "#141D34" : "white"}
|
||||||
>
|
style={{ borderColor: dark ? "#141D34" : "white" }}
|
||||||
{/* Top Row - 4 Activity Cards */}
|
|
||||||
<div
|
|
||||||
className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-6"
|
|
||||||
style={{
|
|
||||||
display: "grid",
|
|
||||||
gridTemplateColumns: "repeat(4, 1fr)",
|
|
||||||
gap: "1.5rem",
|
|
||||||
marginBottom: "1.5rem",
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
{activities.map((activity, index) => (
|
<Title order={4} mb="md" c={dark ? "white" : "darmasaba-navy"}>
|
||||||
<div
|
Grafik Progres Tugas per Divisi
|
||||||
key={index}
|
</Title>
|
||||||
className="rounded-xl shadow-sm p-5"
|
<ResponsiveContainer width="100%" height={300}>
|
||||||
style={{
|
<BarChart data={divisionProgressData}>
|
||||||
backgroundColor: dark ? "#141D34" : "white",
|
<CartesianGrid
|
||||||
border: `1px solid ${dark ? "#141D34" : "white"}`,
|
strokeDasharray="3 3"
|
||||||
borderRadius: "12px",
|
vertical={false}
|
||||||
boxShadow: "0 1px 3px rgba(0,0,0,0.1)",
|
stroke={dark ? "#141D34" : "white"}
|
||||||
padding: "1.25rem",
|
/>
|
||||||
}}
|
<XAxis
|
||||||
>
|
dataKey="name"
|
||||||
{/* Dark blue title bar */}
|
axisLine={false}
|
||||||
<div
|
tickLine={false}
|
||||||
className="text-white px-3 py-2 rounded-t-lg -mx-5 -mt-5 mb-4"
|
tick={{
|
||||||
style={{ backgroundColor: "#1E3A5F" }}
|
fill: dark
|
||||||
|
? "var(--mantine-color-text)"
|
||||||
|
: "var(--mantine-color-text)",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<YAxis
|
||||||
|
axisLine={false}
|
||||||
|
tickLine={false}
|
||||||
|
tick={{
|
||||||
|
fill: dark
|
||||||
|
? "var(--mantine-color-text)"
|
||||||
|
: "var(--mantine-color-text)",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Tooltip
|
||||||
|
contentStyle={
|
||||||
|
dark
|
||||||
|
? {
|
||||||
|
backgroundColor: "var(--mantine-color-dark-7)",
|
||||||
|
borderColor: "var(--mantine-color-dark-6)",
|
||||||
|
}
|
||||||
|
: {}
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Bar
|
||||||
|
dataKey="selesai"
|
||||||
|
stackId="a"
|
||||||
|
fill="#10B981"
|
||||||
|
name="Selesai"
|
||||||
|
radius={[4, 4, 0, 0]}
|
||||||
|
/>
|
||||||
|
<Bar
|
||||||
|
dataKey="berjalan"
|
||||||
|
stackId="a"
|
||||||
|
fill="#3B82F6"
|
||||||
|
name="Berjalan"
|
||||||
|
radius={[4, 4, 0, 0]}
|
||||||
|
/>
|
||||||
|
<Bar
|
||||||
|
dataKey="tertunda"
|
||||||
|
stackId="a"
|
||||||
|
fill="#EF4444"
|
||||||
|
name="Tertunda"
|
||||||
|
radius={[4, 4, 0, 0]}
|
||||||
|
/>
|
||||||
|
</BarChart>
|
||||||
|
</ResponsiveContainer>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
{/* Ringkasan Tugas per Divisi */}
|
||||||
|
<Grid gutter="md">
|
||||||
|
{divisionTasks.map((division, index) => (
|
||||||
|
<GridCol key={index} span={{ base: 12, md: 6, lg: 3 }}>
|
||||||
|
<Card
|
||||||
|
p="md"
|
||||||
|
radius="md"
|
||||||
|
withBorder
|
||||||
|
bg={dark ? "#141D34" : "white"}
|
||||||
|
style={{ borderColor: dark ? "#141D34" : "white" }}
|
||||||
|
h="100%"
|
||||||
>
|
>
|
||||||
<h3 className="text-sm font-semibold">{activity.title}</h3>
|
<Title order={4} mb="sm" c={dark ? "white" : "darmasaba-navy"}>
|
||||||
</div>
|
{division.name}
|
||||||
|
</Title>
|
||||||
{/* Orange progress bar */}
|
<Stack gap="sm">
|
||||||
<div
|
{division.tasks.map((task, taskIndex) => (
|
||||||
className="w-full rounded-full h-2 mb-3"
|
<Box key={taskIndex}>
|
||||||
style={{ backgroundColor: dark ? "#2d3748" : "#E5E7EB" }}
|
<Group justify="space-between">
|
||||||
>
|
<Text size="sm" c={dark ? "white" : "darmasaba-navy"}>
|
||||||
<div
|
{task.title}
|
||||||
className="bg-orange-500 h-2 rounded-full"
|
</Text>
|
||||||
style={{ width: `${activity.progress}%` }}
|
<MantineBadge
|
||||||
/>
|
color={STATUS_COLORS[task.status] || "gray"}
|
||||||
</div>
|
variant="light"
|
||||||
|
>
|
||||||
{/* Date and badge */}
|
{task.status}
|
||||||
<div className="flex justify-between items-center">
|
</MantineBadge>
|
||||||
<span
|
</Group>
|
||||||
className="text-xs"
|
</Box>
|
||||||
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"
|
|
||||||
style={{
|
|
||||||
display: "grid",
|
|
||||||
gridTemplateColumns: "repeat(auto-fit, minmax(300px, 1fr))",
|
|
||||||
gap: "1.5rem",
|
|
||||||
marginBottom: "1.5rem",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{/* 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"}`,
|
|
||||||
borderRadius: "12px",
|
|
||||||
boxShadow: "0 1px 3px rgba(0,0,0,0.1)",
|
|
||||||
padding: "1.25rem",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<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>
|
</Stack>
|
||||||
</BarChart>
|
</Card>
|
||||||
</ResponsiveContainer>
|
</GridCol>
|
||||||
</div>
|
))}
|
||||||
|
</Grid>
|
||||||
|
|
||||||
{/* Right Card - Progres Kegiatan (Pie Chart) */}
|
{/* Arsip Digital Perangkat Desa */}
|
||||||
<div
|
<Card
|
||||||
className="rounded-xl shadow-sm p-5"
|
p="md"
|
||||||
style={{
|
radius="md"
|
||||||
backgroundColor: dark ? "#141D34" : "white",
|
withBorder
|
||||||
border: `1px solid ${dark ? "#141D34" : "white"}`,
|
bg={dark ? "#141D34" : "white"}
|
||||||
borderRadius: "12px",
|
style={{ borderColor: dark ? "#141D34" : "white" }}
|
||||||
boxShadow: "0 1px 3px rgba(0,0,0,0.1)",
|
|
||||||
padding: "1.25rem",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<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"
|
|
||||||
style={{
|
|
||||||
display: "grid",
|
|
||||||
gridTemplateColumns: "repeat(auto-fit, minmax(300px, 1fr))",
|
|
||||||
gap: "1.5rem",
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
{/* Left Card - Diskusi */}
|
<Title order={4} mb="md" c={dark ? "white" : "darmasaba-navy"}>
|
||||||
<div
|
Arsip Digital Perangkat Desa
|
||||||
className="rounded-xl shadow-sm p-5"
|
</Title>
|
||||||
style={{
|
<Grid gutter="md">
|
||||||
backgroundColor: dark ? "#141D34" : "white",
|
{archiveItems.map((item, index) => (
|
||||||
border: `1px solid ${dark ? "#141D34" : "white"}`,
|
<GridCol key={index} span={{ base: 12, md: 6, lg: 3 }}>
|
||||||
borderRadius: "12px",
|
<Card
|
||||||
boxShadow: "0 1px 3px rgba(0,0,0,0.1)",
|
p="md"
|
||||||
padding: "1.25rem",
|
radius="md"
|
||||||
}}
|
withBorder
|
||||||
>
|
bg={dark ? "#263852ff" : "#F1F5F9"}
|
||||||
<h3
|
style={{ borderColor: dark ? "#263852ff" : "#F1F5F9" }}
|
||||||
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">
|
<Group justify="space-between">
|
||||||
<div
|
<Text c={dark ? "white" : "darmasaba-navy"} fw={500}>
|
||||||
className="w-8 h-8 rounded-full flex items-center justify-center"
|
{item.name}
|
||||||
style={{ backgroundColor: "#DBEAFE" }}
|
</Text>
|
||||||
>
|
<Text c={dark ? "white" : "darmasaba-navy"} fw={700}>
|
||||||
<IconMessage
|
{item.count}
|
||||||
className="w-4 h-4"
|
</Text>
|
||||||
style={{ color: "#1E3A5F" }}
|
</Group>
|
||||||
stroke={2}
|
</Card>
|
||||||
/>
|
</GridCol>
|
||||||
</div>
|
))}
|
||||||
</div>
|
</Grid>
|
||||||
<div className="flex-1">
|
</Card>
|
||||||
<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 */}
|
{/* Kartu Progres Kegiatan */}
|
||||||
<div
|
<Card
|
||||||
className="rounded-xl shadow-sm p-5"
|
p="md"
|
||||||
style={{
|
radius="md"
|
||||||
backgroundColor: dark ? "#141D34" : "white",
|
withBorder
|
||||||
border: `1px solid ${dark ? "#141D34" : "white"}`,
|
bg={dark ? "#141D34" : "white"}
|
||||||
borderRadius: "12px",
|
style={{ borderColor: dark ? "#141D34" : "white" }}
|
||||||
boxShadow: "0 1px 3px rgba(0,0,0,0.1)",
|
>
|
||||||
padding: "1.25rem",
|
<Title order={4} mb="md" c={dark ? "white" : "darmasaba-navy"}>
|
||||||
}}
|
Progres Kegiatan / Program
|
||||||
>
|
</Title>
|
||||||
<h3
|
<Stack gap="md">
|
||||||
className="text-lg font-semibold mb-4"
|
{activityProgress.map((activity, index) => (
|
||||||
style={{ color: dark ? "white" : "#1F2937" }}
|
<Card
|
||||||
>
|
key={index}
|
||||||
Acara Hari Ini
|
p="md"
|
||||||
</h3>
|
radius="md"
|
||||||
<div className="flex items-center justify-center h-32">
|
withBorder
|
||||||
<p
|
bg={dark ? "#263852ff" : "#F1F5F9"}
|
||||||
className="text-sm"
|
style={{ borderColor: dark ? "#263852ff" : "#F1F5F9" }}
|
||||||
style={{ color: dark ? "#9CA3AF" : "#6B7280" }}
|
|
||||||
>
|
>
|
||||||
Tidak ada acara hari ini
|
<Group justify="space-between" mb="sm">
|
||||||
</p>
|
<Text c={dark ? "white" : "darmasaba-navy"} fw={500}>
|
||||||
</div>
|
{activity.name}
|
||||||
</div>
|
</Text>
|
||||||
</div>
|
<MantineBadge
|
||||||
</div>
|
color={STATUS_COLORS[activity.status] || "gray"}
|
||||||
|
variant="light"
|
||||||
|
>
|
||||||
|
{activity.status}
|
||||||
|
</MantineBadge>
|
||||||
|
</Group>
|
||||||
|
<Group justify="space-between">
|
||||||
|
<MantineProgress
|
||||||
|
value={activity.progress}
|
||||||
|
size="sm"
|
||||||
|
radius="xl"
|
||||||
|
color={activity.progress === 100 ? "green" : "blue"}
|
||||||
|
w="calc(100% - 80px)"
|
||||||
|
/>
|
||||||
|
<Text size="sm" c={dark ? "white" : "darmasaba-navy"}>
|
||||||
|
{activity.progress}%
|
||||||
|
</Text>
|
||||||
|
</Group>
|
||||||
|
<Text size="sm" c="dimmed" mt="sm">
|
||||||
|
{activity.date}
|
||||||
|
</Text>
|
||||||
|
</Card>
|
||||||
|
))}
|
||||||
|
</Stack>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
{/* Statistik Dokumen & Progres Kegiatan */}
|
||||||
|
<Grid gutter="md">
|
||||||
|
<GridCol span={{ base: 12, lg: 6 }}>
|
||||||
|
<Card
|
||||||
|
p="md"
|
||||||
|
radius="md"
|
||||||
|
withBorder
|
||||||
|
bg={dark ? "#141D34" : "white"}
|
||||||
|
style={{ borderColor: dark ? "#141D34" : "white" }}
|
||||||
|
>
|
||||||
|
<Title order={4} mb="md" c={dark ? "white" : "darmasaba-navy"}>
|
||||||
|
Jumlah Dokumen
|
||||||
|
</Title>
|
||||||
|
<ResponsiveContainer width="100%" height={200}>
|
||||||
|
<BarChart data={documentStats}>
|
||||||
|
<CartesianGrid
|
||||||
|
strokeDasharray="3 3"
|
||||||
|
vertical={false}
|
||||||
|
stroke={dark ? "#141D34" : "white"}
|
||||||
|
/>
|
||||||
|
<XAxis
|
||||||
|
dataKey="name"
|
||||||
|
axisLine={false}
|
||||||
|
tickLine={false}
|
||||||
|
tick={{
|
||||||
|
fill: dark
|
||||||
|
? "var(--mantine-color-text)"
|
||||||
|
: "var(--mantine-color-text)",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<YAxis
|
||||||
|
axisLine={false}
|
||||||
|
tickLine={false}
|
||||||
|
tick={{
|
||||||
|
fill: dark
|
||||||
|
? "var(--mantine-color-text)"
|
||||||
|
: "var(--mantine-color-text)",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Tooltip
|
||||||
|
contentStyle={
|
||||||
|
dark
|
||||||
|
? {
|
||||||
|
backgroundColor: "var(--mantine-color-dark-7)",
|
||||||
|
borderColor: "var(--mantine-color-dark-6)",
|
||||||
|
}
|
||||||
|
: {}
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Bar
|
||||||
|
dataKey="value"
|
||||||
|
fill={
|
||||||
|
dark
|
||||||
|
? "var(--mantine-color-blue-6)"
|
||||||
|
: "var(--mantine-color-blue-filled)"
|
||||||
|
}
|
||||||
|
radius={[4, 4, 0, 0]}
|
||||||
|
/>
|
||||||
|
</BarChart>
|
||||||
|
</ResponsiveContainer>
|
||||||
|
</Card>
|
||||||
|
</GridCol>
|
||||||
|
|
||||||
|
<GridCol span={{ base: 12, lg: 6 }}>
|
||||||
|
<Card
|
||||||
|
p="md"
|
||||||
|
radius="md"
|
||||||
|
withBorder
|
||||||
|
bg={dark ? "#141D34" : "white"}
|
||||||
|
style={{ borderColor: dark ? "#141D34" : "white" }}
|
||||||
|
>
|
||||||
|
<Title order={4} mb="md" c={dark ? "white" : "darmasaba-navy"}>
|
||||||
|
Progres Kegiatan
|
||||||
|
</Title>
|
||||||
|
<ResponsiveContainer width="100%" height={200}>
|
||||||
|
<PieChart>
|
||||||
|
<Pie
|
||||||
|
data={activityProgressStats}
|
||||||
|
cx="50%"
|
||||||
|
cy="50%"
|
||||||
|
labelLine={false}
|
||||||
|
outerRadius={80}
|
||||||
|
fill="#8884d8"
|
||||||
|
dataKey="value"
|
||||||
|
label={({ name, percent }) =>
|
||||||
|
`${name}: ${percent ? (percent * 100).toFixed(0) : "0"}%`
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{activityProgressStats.map((entry, index) => (
|
||||||
|
<Cell
|
||||||
|
key={`cell-${index}`}
|
||||||
|
fill={COLORS[index % COLORS.length]}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Pie>
|
||||||
|
<Tooltip
|
||||||
|
contentStyle={
|
||||||
|
dark
|
||||||
|
? {
|
||||||
|
backgroundColor: "var(--mantine-color-dark-7)",
|
||||||
|
borderColor: "var(--mantine-color-dark-6)",
|
||||||
|
}
|
||||||
|
: {}
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</PieChart>
|
||||||
|
</ResponsiveContainer>
|
||||||
|
</Card>
|
||||||
|
</GridCol>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
{/* Diskusi Internal */}
|
||||||
|
<Card
|
||||||
|
p="md"
|
||||||
|
radius="md"
|
||||||
|
withBorder
|
||||||
|
bg={dark ? "#141D34" : "white"}
|
||||||
|
style={{ borderColor: dark ? "#141D34" : "white" }}
|
||||||
|
>
|
||||||
|
<Title order={4} mb="md" c={dark ? "white" : "darmasaba-navy"}>
|
||||||
|
Diskusi Internal
|
||||||
|
</Title>
|
||||||
|
<Stack gap="sm">
|
||||||
|
{discussions.map((discussion, index) => (
|
||||||
|
<Card
|
||||||
|
key={index}
|
||||||
|
p="md"
|
||||||
|
radius="md"
|
||||||
|
withBorder
|
||||||
|
bg={dark ? "#263852ff" : "#F1F5F9"}
|
||||||
|
style={{ borderColor: dark ? "#263852ff" : "#F1F5F9" }}
|
||||||
|
>
|
||||||
|
<Group justify="space-between">
|
||||||
|
<Text c={dark ? "white" : "darmasaba-navy"} fw={500}>
|
||||||
|
{discussion.title}
|
||||||
|
</Text>
|
||||||
|
<Text size="sm" c="dimmed">
|
||||||
|
{discussion.timestamp}
|
||||||
|
</Text>
|
||||||
|
</Group>
|
||||||
|
<Text size="sm" c="dimmed">
|
||||||
|
{discussion.sender}
|
||||||
|
</Text>
|
||||||
|
</Card>
|
||||||
|
))}
|
||||||
|
</Stack>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
{/* Agenda / Acara Hari Ini */}
|
||||||
|
<Card
|
||||||
|
p="md"
|
||||||
|
radius="md"
|
||||||
|
withBorder
|
||||||
|
bg={dark ? "#141D34" : "white"}
|
||||||
|
style={{ borderColor: dark ? "#141D34" : "white" }}
|
||||||
|
>
|
||||||
|
<Title order={4} mb="md" c={dark ? "white" : "darmasaba-navy"}>
|
||||||
|
Agenda / Acara Hari Ini
|
||||||
|
</Title>
|
||||||
|
{todayAgenda.length > 0 ? (
|
||||||
|
<Stack gap="sm">
|
||||||
|
{todayAgenda.map((agenda, index) => (
|
||||||
|
<Group key={index} align="flex-start">
|
||||||
|
<Box w={60}>
|
||||||
|
<Text c="dimmed">{agenda.time}</Text>
|
||||||
|
</Box>
|
||||||
|
<Divider orientation="vertical" mx="sm" />
|
||||||
|
<Text c={dark ? "white" : "darmasaba-navy"}>
|
||||||
|
{agenda.event}
|
||||||
|
</Text>
|
||||||
|
</Group>
|
||||||
|
))}
|
||||||
|
</Stack>
|
||||||
|
) : (
|
||||||
|
<Text c="dimmed" ta="center" py="md">
|
||||||
|
Tidak ada acara hari ini
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
</Card>
|
||||||
|
</Stack>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default KinerjaDivisi;
|
export default KinerjaDivisi;
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,11 +1,13 @@
|
|||||||
import {
|
import {
|
||||||
|
Badge,
|
||||||
Box,
|
Box,
|
||||||
Collapse,
|
Collapse,
|
||||||
Image,
|
Group,
|
||||||
Input,
|
Input,
|
||||||
NavLink as MantineNavLink,
|
NavLink as MantineNavLink,
|
||||||
Stack,
|
Stack,
|
||||||
useMantineColorScheme
|
Text,
|
||||||
|
useMantineColorScheme,
|
||||||
} from "@mantine/core";
|
} from "@mantine/core";
|
||||||
import { useLocation, useNavigate } from "@tanstack/react-router";
|
import { useLocation, useNavigate } from "@tanstack/react-router";
|
||||||
import { ChevronDown, ChevronUp, Search } from "lucide-react";
|
import { ChevronDown, ChevronUp, Search } from "lucide-react";
|
||||||
@@ -25,35 +27,29 @@ export function Sidebar({ className }: SidebarProps) {
|
|||||||
|
|
||||||
// State for settings submenu collapse
|
// State for settings submenu collapse
|
||||||
const [settingsOpen, setSettingsOpen] = useState(
|
const [settingsOpen, setSettingsOpen] = useState(
|
||||||
location.pathname.startsWith("/dashboard/pengaturan"),
|
location.pathname.startsWith("/pengaturan"),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Define menu items with their paths
|
// Define menu items with their paths
|
||||||
const menuItems = [
|
const menuItems = [
|
||||||
{ name: "Beranda", path: "/dashboard" },
|
{ name: "Beranda", path: "/" },
|
||||||
{ name: "Kinerja Divisi", path: "/dashboard/kinerja-divisi" },
|
{ name: "Kinerja Divisi", path: "/kinerja-divisi" },
|
||||||
{
|
{ name: "Pengaduan & Layanan Publik", path: "/pengaduan-layanan-publik" },
|
||||||
name: "Pengaduan & Layanan Publik",
|
{ name: "Jenna Analytic", path: "/jenna-analytic" },
|
||||||
path: "/dashboard/pengaduan-layanan-publik",
|
{ name: "Demografi & Kependudukan", path: "/demografi-pekerjaan" },
|
||||||
},
|
{ name: "Keuangan & Anggaran", path: "/keuangan-anggaran" },
|
||||||
{ name: "Jenna Analytic", path: "/dashboard/jenna-analytic" },
|
{ name: "Bumdes & UMKM Desa", path: "/bumdes" },
|
||||||
{
|
{ name: "Sosial", path: "/sosial" },
|
||||||
name: "Demografi & Kependudukan",
|
{ name: "Keamanan", path: "/keamanan" },
|
||||||
path: "/dashboard/demografi-pekerjaan",
|
{ name: "Bantuan", path: "/bantuan" },
|
||||||
},
|
|
||||||
{ name: "Keuangan & Anggaran", path: "/dashboard/keuangan-anggaran" },
|
|
||||||
{ name: "Bumdes & UMKM Desa", path: "/dashboard/bumdes" },
|
|
||||||
{ name: "Sosial", path: "/dashboard/sosial" },
|
|
||||||
{ name: "Keamanan", path: "/dashboard/keamanan" },
|
|
||||||
{ name: "Bantuan", path: "/dashboard/bantuan" },
|
|
||||||
];
|
];
|
||||||
|
|
||||||
// Settings submenu items
|
// Settings submenu items
|
||||||
const settingsItems = [
|
const settingsItems = [
|
||||||
{ name: "Umum", path: "/dashboard/pengaturan/umum" },
|
{ name: "Umum", path: "/pengaturan/umum" },
|
||||||
{ name: "Notifikasi", path: "/dashboard/pengaturan/notifikasi" },
|
{ name: "Notifikasi", path: "/pengaturan/notifikasi" },
|
||||||
{ name: "Keamanan", path: "/dashboard/pengaturan/keamanan" },
|
{ name: "Keamanan", path: "/pengaturan/keamanan" },
|
||||||
{ name: "Akses & Tim", path: "/dashboard/pengaturan/akses-dan-tim" },
|
{ name: "Akses & Tim", path: "/pengaturan/akses-dan-tim" },
|
||||||
];
|
];
|
||||||
|
|
||||||
// Check if any settings submenu is active
|
// Check if any settings submenu is active
|
||||||
@@ -61,12 +57,33 @@ export function Sidebar({ className }: SidebarProps) {
|
|||||||
(item) => location.pathname === item.path,
|
(item) => location.pathname === item.path,
|
||||||
);
|
);
|
||||||
|
|
||||||
const headerBgColor = colorScheme === "dark" ? "#ebedf0ff" : "#19355E";
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box className={className}>
|
<Box className={className}>
|
||||||
{/* Logo */}
|
{/* Logo */}
|
||||||
<Image src={"/logo-desa-plus.png"} width={201} height={84} />
|
<Box
|
||||||
|
p="md"
|
||||||
|
style={{ borderBottom: "1px solid var(--mantine-color-gray-3)" }}
|
||||||
|
>
|
||||||
|
<Group gap="xs">
|
||||||
|
<Badge
|
||||||
|
color="dark"
|
||||||
|
variant="filled"
|
||||||
|
size="xl"
|
||||||
|
radius="md"
|
||||||
|
py="xs"
|
||||||
|
px="md"
|
||||||
|
style={{ fontSize: "1.5rem", fontWeight: "bold" }}
|
||||||
|
>
|
||||||
|
DESA
|
||||||
|
</Badge>
|
||||||
|
<Badge color="green" variant="filled" size="md" radius="md">
|
||||||
|
+
|
||||||
|
</Badge>
|
||||||
|
</Group>
|
||||||
|
<Text size="xs" c="dimmed" mt="xs">
|
||||||
|
Digitalisasi Desa Transparansi Kerja
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
|
||||||
{/* Search */}
|
{/* Search */}
|
||||||
<Box p="md">
|
<Box p="md">
|
||||||
@@ -94,7 +111,7 @@ export function Sidebar({ className }: SidebarProps) {
|
|||||||
label={item.name}
|
label={item.name}
|
||||||
active={isActive}
|
active={isActive}
|
||||||
variant="subtle"
|
variant="subtle"
|
||||||
color={headerBgColor}
|
color="blue"
|
||||||
style={{
|
style={{
|
||||||
background: isActive ? isActiveBg : "transparent",
|
background: isActive ? isActiveBg : "transparent",
|
||||||
fontWeight: isActive ? "bold" : "normal",
|
fontWeight: isActive ? "bold" : "normal",
|
||||||
@@ -186,6 +203,5 @@ export function Sidebar({ className }: SidebarProps) {
|
|||||||
</Box>
|
</Box>
|
||||||
</Stack>
|
</Stack>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1,478 +1,465 @@
|
|||||||
|
import {
|
||||||
|
Badge,
|
||||||
|
Card,
|
||||||
|
Grid,
|
||||||
|
GridCol,
|
||||||
|
Group,
|
||||||
|
List,
|
||||||
|
Progress,
|
||||||
|
Stack,
|
||||||
|
Text,
|
||||||
|
ThemeIcon,
|
||||||
|
Title,
|
||||||
|
useMantineColorScheme,
|
||||||
|
} from "@mantine/core";
|
||||||
import {
|
import {
|
||||||
IconAward,
|
IconAward,
|
||||||
IconBabyCarriage,
|
IconBabyCarriage,
|
||||||
|
IconBook,
|
||||||
IconCalendarEvent,
|
IconCalendarEvent,
|
||||||
IconHeartbeat,
|
IconHeartbeat,
|
||||||
IconMedicalCross,
|
IconMedicalCross,
|
||||||
IconSchool,
|
IconSchool,
|
||||||
IconStethoscope,
|
IconStethoscope,
|
||||||
IconUsers,
|
|
||||||
} from "@tabler/icons-react";
|
} from "@tabler/icons-react";
|
||||||
import { useMantineColorScheme, Title } from "@mantine/core";
|
import { useState } from "react";
|
||||||
|
|
||||||
const SosialPage = () => {
|
const SosialPage = () => {
|
||||||
const { colorScheme } = useMantineColorScheme();
|
const { colorScheme } = useMantineColorScheme();
|
||||||
const dark = colorScheme === "dark";
|
const dark = colorScheme === "dark";
|
||||||
|
|
||||||
// Health statistics data
|
// Sample data for health statistics
|
||||||
const healthStats = [
|
const healthStats = {
|
||||||
{
|
ibuHamil: 87,
|
||||||
title: "Ibu Hamil Aktif",
|
balita: 342,
|
||||||
value: "87",
|
alertStunting: 12,
|
||||||
subtitle: "Aktif",
|
posyanduAktif: 8,
|
||||||
icon: IconHeartbeat,
|
};
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Balita Terdaftar",
|
|
||||||
value: "342",
|
|
||||||
subtitle: "Terdaftar",
|
|
||||||
icon: IconBabyCarriage,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Alert Stunting",
|
|
||||||
value: "12",
|
|
||||||
subtitle: "Perlu perhatian",
|
|
||||||
icon: IconStethoscope,
|
|
||||||
alert: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Posyandu Aktif",
|
|
||||||
value: "8",
|
|
||||||
subtitle: "Beroperasi",
|
|
||||||
icon: IconMedicalCross,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
// Health progress data
|
// Sample data for health progress
|
||||||
const healthProgress = [
|
const healthProgress = [
|
||||||
{ label: "Imunisasi Lengkap", value: 92 },
|
{ label: "Imunisasi Lengkap", value: 92, color: "green" },
|
||||||
{ label: "Pemeriksaan Rutin", value: 88 },
|
{ label: "Pemeriksaan Rutin", value: 88, color: "blue" },
|
||||||
{ label: "Gizi Baik", value: 86 },
|
{ label: "Gizi Baik", value: 86, color: "teal" },
|
||||||
{ label: "Target Stunting", value: 14, isAlert: true },
|
{ label: "Target Stunting", value: 14, color: "red" },
|
||||||
];
|
];
|
||||||
|
|
||||||
// Posyandu schedule data
|
// Sample data for posyandu schedule
|
||||||
const posyanduSchedule = [
|
const posyanduSchedule = [
|
||||||
{
|
{
|
||||||
nama: "Posyandu Barat",
|
nama: "Posyandu Mawar",
|
||||||
tanggal: "5 Oktober 2025",
|
tanggal: "Senin, 15 Feb 2026",
|
||||||
jam: "08:00 - 11:00",
|
jam: "08:00 - 11:00",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
nama: "Posyandu Timur",
|
nama: "Posyandu Melati",
|
||||||
tanggal: "6 Oktober 2025",
|
tanggal: "Selasa, 16 Feb 2026",
|
||||||
jam: "08:00 - 11:00",
|
jam: "08:00 - 11:00",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
nama: "Posyandu Utara",
|
nama: "Posyandu Dahlia",
|
||||||
tanggal: "7 Oktober 2025",
|
tanggal: "Rabu, 17 Feb 2026",
|
||||||
jam: "08:00 - 11:00",
|
jam: "08:00 - 11:00",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
nama: "Posyandu Selatan",
|
nama: "Posyandu Anggrek",
|
||||||
tanggal: "8 Oktober 2025",
|
tanggal: "Kamis, 18 Feb 2026",
|
||||||
jam: "08:00 - 11:00",
|
jam: "08:00 - 11:00",
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
// Education stats data
|
// Sample data for education stats
|
||||||
const educationStats = [
|
const educationStats = {
|
||||||
{ level: "TK / PAUD", value: "500" },
|
siswa: {
|
||||||
{ level: "Siswa SD", value: "458" },
|
tk: 125,
|
||||||
{ level: "Siswa SMP", value: "234" },
|
sd: 480,
|
||||||
{ level: "Siswa SMA", value: "189" },
|
smp: 210,
|
||||||
];
|
sma: 150,
|
||||||
|
},
|
||||||
|
sekolah: {
|
||||||
|
jumlah: 8,
|
||||||
|
guru: 42,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
// School info data
|
// Sample data for scholarships
|
||||||
const schoolInfo = [
|
|
||||||
{ label: "Lembaga Pendidikan", value: "10" },
|
|
||||||
{ label: "Tenaga Pengajar", value: "3" },
|
|
||||||
];
|
|
||||||
|
|
||||||
// Scholarship data
|
|
||||||
const scholarshipData = {
|
const scholarshipData = {
|
||||||
penerima: "250+",
|
penerima: 45,
|
||||||
dana: "1.5M",
|
dana: "Rp 1.200.000.000",
|
||||||
tahunAjaran: "2025/2026",
|
tahunAjaran: "2025/2026",
|
||||||
};
|
};
|
||||||
|
|
||||||
// Cultural events data
|
// Sample data for cultural events
|
||||||
const culturalEvents = [
|
const culturalEvents = [
|
||||||
{
|
{
|
||||||
nama: "Lomba Baris Berbaris",
|
nama: "Hari Kesaktian Pancasila",
|
||||||
tanggal: "1 Desember 2025",
|
tanggal: "1 Oktober 2025",
|
||||||
|
lokasi: "Balai Desa",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
nama: "Festival Budaya Desa",
|
||||||
|
tanggal: "20 Mei 2026",
|
||||||
lokasi: "Lapangan Desa",
|
lokasi: "Lapangan Desa",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
nama: "Lomba Tari Tradisional",
|
nama: "Perayaan HUT Desa",
|
||||||
tanggal: "10 Desember 2025",
|
tanggal: "17 Agustus 2026",
|
||||||
lokasi: "Banjar Desa",
|
lokasi: "Balai Desa",
|
||||||
},
|
|
||||||
{
|
|
||||||
nama: "Davoz",
|
|
||||||
tanggal: "20 Desember 2025",
|
|
||||||
lokasi: "Kantor Desa",
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<Stack gap="lg">
|
||||||
className={`min-h-screen py-6 px-4 sm:px-6 lg:px-8 ${
|
{/* Health Statistics Cards */}
|
||||||
dark ? "bg-slate-900" : "bg-gray-100"
|
<Grid gutter="md">
|
||||||
}`}
|
<GridCol span={{ base: 12, sm: 6, md: 3 }}>
|
||||||
>
|
<Card
|
||||||
<div className="max-w-7xl mx-auto w-full">
|
p="md"
|
||||||
{/* Row 1: Top 4 Metrics Cards */}
|
radius="md"
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 mb-6">
|
withBorder
|
||||||
{healthStats.map((stat, index) => (
|
bg={dark ? "#141D34" : "white"}
|
||||||
<div
|
style={{ borderColor: dark ? "#141D34" : "white" }}
|
||||||
key={index}
|
>
|
||||||
className={`rounded-xl shadow-sm p-6 ${
|
<Group justify="space-between" align="center">
|
||||||
dark ? "bg-slate-800 border border-slate-800" : "bg-white border border-white"
|
<Stack gap={0}>
|
||||||
}`}
|
<Text size="sm" c={dark ? "dark.3" : "dimmed"}>
|
||||||
>
|
Ibu Hamil Aktif
|
||||||
<div className="flex items-center justify-between">
|
</Text>
|
||||||
<div className="flex-1">
|
<Text size="xl" fw={700} c={dark ? "dark.0" : "black"}>
|
||||||
<h3
|
{healthStats.ibuHamil}
|
||||||
className={`text-sm font-medium mb-1 ${
|
</Text>
|
||||||
dark ? "text-gray-400" : "text-gray-500"
|
</Stack>
|
||||||
}`}
|
<ThemeIcon
|
||||||
>
|
variant="light"
|
||||||
{stat.title}
|
color="darmasaba-blue"
|
||||||
</h3>
|
size="xl"
|
||||||
<p
|
radius="xl"
|
||||||
className={`text-3xl font-bold mb-1 ${
|
>
|
||||||
stat.alert
|
<IconHeartbeat size={24} />
|
||||||
? "text-red-500"
|
</ThemeIcon>
|
||||||
: dark
|
</Group>
|
||||||
? "text-white"
|
</Card>
|
||||||
: "text-gray-800"
|
</GridCol>
|
||||||
}`}
|
|
||||||
>
|
<GridCol span={{ base: 12, sm: 6, md: 3 }}>
|
||||||
{stat.value}
|
<Card
|
||||||
</p>
|
p="md"
|
||||||
<p
|
radius="md"
|
||||||
className={`text-xs ${
|
withBorder
|
||||||
dark ? "text-gray-400" : "text-gray-500"
|
bg={dark ? "#141D34" : "white"}
|
||||||
}`}
|
style={{ borderColor: dark ? "#141D34" : "white" }}
|
||||||
>
|
>
|
||||||
{stat.subtitle}
|
<Group justify="space-between" align="center">
|
||||||
</p>
|
<Stack gap={0}>
|
||||||
</div>
|
<Text size="sm" c={dark ? "dark.3" : "dimmed"}>
|
||||||
<div className="flex-shrink-0 ml-4">
|
Balita Terdaftar
|
||||||
<div
|
</Text>
|
||||||
className={`w-12 h-12 rounded-full flex items-center justify-center text-white ${
|
<Text size="xl" fw={700} c={dark ? "dark.0" : "black"}>
|
||||||
stat.alert ? "bg-red-500" : "bg-blue-900"
|
{healthStats.balita}
|
||||||
}`}
|
</Text>
|
||||||
>
|
</Stack>
|
||||||
<stat.icon size={24} />
|
<ThemeIcon
|
||||||
</div>
|
variant="light"
|
||||||
</div>
|
color="darmasaba-success"
|
||||||
</div>
|
size="xl"
|
||||||
|
radius="xl"
|
||||||
|
>
|
||||||
|
<IconBabyCarriage size={24} />
|
||||||
|
</ThemeIcon>
|
||||||
|
</Group>
|
||||||
|
</Card>
|
||||||
|
</GridCol>
|
||||||
|
|
||||||
|
<GridCol span={{ base: 12, sm: 6, md: 3 }}>
|
||||||
|
<Card
|
||||||
|
p="md"
|
||||||
|
radius="md"
|
||||||
|
withBorder
|
||||||
|
bg={dark ? "#141D34" : "white"}
|
||||||
|
style={{ borderColor: dark ? "#141D34" : "white" }}
|
||||||
|
>
|
||||||
|
<Group justify="space-between" align="center">
|
||||||
|
<Stack gap={0}>
|
||||||
|
<Text size="sm" c={dark ? "dark.3" : "dimmed"}>
|
||||||
|
Alert Stunting
|
||||||
|
</Text>
|
||||||
|
<Text size="xl" fw={700} c="red">
|
||||||
|
{healthStats.alertStunting}
|
||||||
|
</Text>
|
||||||
|
</Stack>
|
||||||
|
<ThemeIcon variant="light" color="red" size="xl" radius="xl">
|
||||||
|
<IconStethoscope size={24} />
|
||||||
|
</ThemeIcon>
|
||||||
|
</Group>
|
||||||
|
</Card>
|
||||||
|
</GridCol>
|
||||||
|
|
||||||
|
<GridCol span={{ base: 12, sm: 6, md: 3 }}>
|
||||||
|
<Card
|
||||||
|
p="md"
|
||||||
|
radius="md"
|
||||||
|
withBorder
|
||||||
|
bg={dark ? "#141D34" : "white"}
|
||||||
|
style={{ borderColor: dark ? "#141D34" : "white" }}
|
||||||
|
>
|
||||||
|
<Group justify="space-between" align="center">
|
||||||
|
<Stack gap={0}>
|
||||||
|
<Text size="sm" c={dark ? "dark.3" : "dimmed"}>
|
||||||
|
Posyandu Aktif
|
||||||
|
</Text>
|
||||||
|
<Text size="xl" fw={700} c={dark ? "dark.0" : "black"}>
|
||||||
|
{healthStats.posyanduAktif}
|
||||||
|
</Text>
|
||||||
|
</Stack>
|
||||||
|
<ThemeIcon
|
||||||
|
variant="light"
|
||||||
|
color="darmasaba-warning"
|
||||||
|
size="xl"
|
||||||
|
radius="xl"
|
||||||
|
>
|
||||||
|
<IconMedicalCross size={24} />
|
||||||
|
</ThemeIcon>
|
||||||
|
</Group>
|
||||||
|
</Card>
|
||||||
|
</GridCol>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
{/* Health Progress Bars */}
|
||||||
|
<Card
|
||||||
|
p="md"
|
||||||
|
radius="md"
|
||||||
|
withBorder
|
||||||
|
bg={dark ? "#141D34" : "white"}
|
||||||
|
style={{ borderColor: dark ? "#141D34" : "white" }}
|
||||||
|
>
|
||||||
|
<Title order={3} mb="md" c={dark ? "dark.0" : "black"}>
|
||||||
|
Statistik Kesehatan
|
||||||
|
</Title>
|
||||||
|
<Stack gap="md">
|
||||||
|
{healthProgress.map((item, index) => (
|
||||||
|
<div key={index}>
|
||||||
|
<Group justify="space-between" mb={5}>
|
||||||
|
<Text size="sm" fw={500} c={dark ? "dark.0" : "black"}>
|
||||||
|
{item.label}
|
||||||
|
</Text>
|
||||||
|
<Text size="sm" fw={600} c={dark ? "dark.0" : "black"}>
|
||||||
|
{item.value}%
|
||||||
|
</Text>
|
||||||
|
</Group>
|
||||||
|
<Progress
|
||||||
|
value={item.value}
|
||||||
|
size="lg"
|
||||||
|
radius="xl"
|
||||||
|
color={item.color}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</Stack>
|
||||||
|
</Card>
|
||||||
|
|
||||||
{/* Row 2: Statistik Kesehatan */}
|
<Grid gutter="md">
|
||||||
<div
|
{/* Jadwal Posyandu */}
|
||||||
className={`rounded-xl shadow-sm p-6 mb-6 ${
|
<GridCol span={{ base: 12, lg: 6 }}>
|
||||||
dark ? "bg-slate-800 border border-slate-800" : "bg-white border border-white"
|
<Card
|
||||||
}`}
|
p="md"
|
||||||
>
|
radius="md"
|
||||||
<h3
|
withBorder
|
||||||
className={`text-lg font-semibold mb-6 ${
|
bg={dark ? "#141D34" : "white"}
|
||||||
dark ? "text-white" : "text-gray-800"
|
style={{ borderColor: dark ? "#141D34" : "white" }}
|
||||||
}`}
|
|
||||||
>
|
>
|
||||||
Statistik Kesehatan
|
<Title order={3} mb="md" c={dark ? "dark.0" : "black"}>
|
||||||
</h3>
|
|
||||||
<div className="space-y-4">
|
|
||||||
{healthProgress.map((item, index) => (
|
|
||||||
<div key={index}>
|
|
||||||
<div className="flex items-center justify-between mb-2">
|
|
||||||
<span
|
|
||||||
className={`text-sm font-medium ${
|
|
||||||
dark ? "text-white" : "text-gray-800"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{item.label}
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
className={`text-sm font-semibold ${
|
|
||||||
item.isAlert
|
|
||||||
? "text-red-500"
|
|
||||||
: dark
|
|
||||||
? "text-white"
|
|
||||||
: "text-gray-800"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{item.value}%
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className={`w-full rounded-full h-2 ${
|
|
||||||
dark ? "bg-slate-700" : "bg-gray-200"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className={`h-2 rounded-full transition-all ${
|
|
||||||
item.isAlert ? "bg-red-500" : "bg-blue-900"
|
|
||||||
}`}
|
|
||||||
style={{ width: `${item.value}%` }}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Row 3: Jadwal Posyandu & Pendidikan */}
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 mb-6">
|
|
||||||
{/* Jadwal Posyandu */}
|
|
||||||
<div
|
|
||||||
className={`rounded-xl shadow-sm p-6 ${
|
|
||||||
dark ? "bg-slate-800 border border-slate-800" : "bg-white border border-white"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<h3
|
|
||||||
className={`text-lg font-semibold mb-4 ${
|
|
||||||
dark ? "text-white" : "text-gray-800"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
Jadwal Posyandu
|
Jadwal Posyandu
|
||||||
</h3>
|
</Title>
|
||||||
<div className="space-y-3">
|
<Stack gap="sm">
|
||||||
{posyanduSchedule.map((item, index) => (
|
{posyanduSchedule.map((item, index) => (
|
||||||
<div
|
<Card
|
||||||
key={index}
|
key={index}
|
||||||
className={`p-4 rounded-lg ${
|
p="md"
|
||||||
dark ? "bg-slate-700" : "bg-gray-50"
|
radius="md"
|
||||||
}`}
|
withBorder
|
||||||
|
bg={dark ? "#263852ff" : "#F1F5F9"}
|
||||||
|
style={{ borderColor: dark ? "#263852ff" : "#F1F5F9" }}
|
||||||
|
h="100%"
|
||||||
>
|
>
|
||||||
<div className="flex items-center justify-between">
|
<Group justify="space-between">
|
||||||
<div>
|
<Stack gap={0}>
|
||||||
<p
|
<Text fw={500} c={dark ? "dark.0" : "black"}>
|
||||||
className={`text-sm font-medium ${
|
|
||||||
dark ? "text-white" : "text-gray-800"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{item.nama}
|
{item.nama}
|
||||||
</p>
|
</Text>
|
||||||
<p
|
<Text size="sm" c={dark ? "dark.0" : "black"}>
|
||||||
className={`text-xs mt-1 ${
|
|
||||||
dark ? "text-gray-400" : "text-gray-500"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{item.tanggal}
|
{item.tanggal}
|
||||||
</p>
|
</Text>
|
||||||
</div>
|
</Stack>
|
||||||
<span
|
<Badge variant="light" color="darmasaba-blue">
|
||||||
className="inline-flex items-center px-3 py-1 rounded-full text-xs font-medium bg-blue-100 text-blue-900"
|
|
||||||
>
|
|
||||||
{item.jam}
|
{item.jam}
|
||||||
</span>
|
</Badge>
|
||||||
</div>
|
</Group>
|
||||||
</div>
|
</Card>
|
||||||
))}
|
))}
|
||||||
</div>
|
</Stack>
|
||||||
</div>
|
</Card>
|
||||||
|
</GridCol>
|
||||||
|
|
||||||
{/* Pendidikan Section */}
|
{/* Pendidikan */}
|
||||||
<div
|
<GridCol span={{ base: 12, lg: 6 }}>
|
||||||
className={`rounded-xl shadow-sm p-6 ${
|
<Card
|
||||||
dark ? "bg-slate-800 border border-slate-800" : "bg-white border border-white"
|
p="md"
|
||||||
}`}
|
radius="md"
|
||||||
|
withBorder
|
||||||
|
bg={dark ? "#141D34" : "white"}
|
||||||
|
style={{ borderColor: dark ? "#141D34" : "white" }}
|
||||||
|
h="100%"
|
||||||
>
|
>
|
||||||
<h3
|
<Title order={3} mb="md" c={dark ? "dark.0" : "black"}>
|
||||||
className={`text-lg font-semibold mb-4 ${
|
|
||||||
dark ? "text-white" : "text-gray-800"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
Pendidikan
|
Pendidikan
|
||||||
</h3>
|
</Title>
|
||||||
<div className="space-y-3 mb-6">
|
<Stack gap="md">
|
||||||
{educationStats.map((item, index) => (
|
<Group justify="space-between">
|
||||||
<div
|
<Text fw={500} c={dark ? "dark.0" : "black"}>
|
||||||
key={index}
|
TK / PAUD
|
||||||
className={`flex items-center justify-between py-2 ${
|
</Text>
|
||||||
dark ? "border-b border-slate-700" : "border-b border-gray-100"
|
<Text fw={700} c={dark ? "dark.0" : "black"}>
|
||||||
}`}
|
{educationStats.siswa.tk}
|
||||||
>
|
</Text>
|
||||||
<span className="text-sm" style={{ color: dark ? "#9CA3AF" : "#6B7280" }}>
|
</Group>
|
||||||
{item.level}
|
<Group justify="space-between">
|
||||||
</span>
|
<Text fw={500} c={dark ? "dark.0" : "black"}>
|
||||||
<span
|
SD
|
||||||
className={`text-sm font-semibold ${
|
</Text>
|
||||||
dark ? "text-white" : "text-gray-800"
|
<Text fw={700} c={dark ? "dark.0" : "black"}>
|
||||||
}`}
|
{educationStats.siswa.sd}
|
||||||
>
|
</Text>
|
||||||
{item.value}
|
</Group>
|
||||||
</span>
|
<Group justify="space-between">
|
||||||
</div>
|
<Text fw={500} c={dark ? "dark.0" : "black"}>
|
||||||
))}
|
SMP
|
||||||
</div>
|
</Text>
|
||||||
|
<Text fw={700} c={dark ? "dark.0" : "black"}>
|
||||||
|
{educationStats.siswa.smp}
|
||||||
|
</Text>
|
||||||
|
</Group>
|
||||||
|
<Group justify="space-between">
|
||||||
|
<Text fw={500} c={dark ? "dark.0" : "black"}>
|
||||||
|
SMA
|
||||||
|
</Text>
|
||||||
|
<Text fw={700} c={dark ? "dark.0" : "black"}>
|
||||||
|
{educationStats.siswa.sma}
|
||||||
|
</Text>
|
||||||
|
</Group>
|
||||||
|
|
||||||
{/* Info Sekolah */}
|
<Card
|
||||||
<h4
|
withBorder
|
||||||
className={`text-base font-semibold mb-4 ${
|
radius="md"
|
||||||
dark ? "text-white" : "text-gray-800"
|
p="md"
|
||||||
}`}
|
mt="md"
|
||||||
>
|
bg={dark ? "#263852ff" : "#F1F5F9"}
|
||||||
Info Sekolah
|
style={{ borderColor: dark ? "#263852ff" : "#F1F5F9" }}
|
||||||
</h4>
|
|
||||||
<div className="space-y-3">
|
|
||||||
{schoolInfo.map((item, index) => (
|
|
||||||
<div
|
|
||||||
key={index}
|
|
||||||
className={`flex items-center justify-between py-3 px-4 rounded-lg ${
|
|
||||||
dark ? "bg-slate-700" : "bg-gray-50"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<span className="text-sm" style={{ color: dark ? "#9CA3AF" : "#6B7280" }}>
|
|
||||||
{item.label}
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
className={`text-lg font-bold ${
|
|
||||||
dark ? "text-white" : "text-gray-800"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{item.value}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Row 4: Beasiswa Desa & Kalender Event Budaya */}
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
|
||||||
{/* Beasiswa Desa */}
|
|
||||||
<div
|
|
||||||
className={`rounded-xl shadow-sm p-6 ${
|
|
||||||
dark ? "bg-slate-800 border border-slate-800" : "bg-white border border-white"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<div className="flex items-center justify-between mb-6">
|
|
||||||
<h3
|
|
||||||
className={`text-lg font-semibold ${
|
|
||||||
dark ? "text-white" : "text-gray-800"
|
|
||||||
}`}
|
|
||||||
>
|
>
|
||||||
Beasiswa Desa
|
<Group justify="space-between">
|
||||||
</h3>
|
<Text fw={500} c={dark ? "dark.0" : "black"}>
|
||||||
<div
|
Jumlah Lembaga Pendidikan
|
||||||
className="w-12 h-12 rounded-full flex items-center justify-center text-white bg-green-500"
|
</Text>
|
||||||
|
<Text fw={700} c={dark ? "dark.0" : "black"}>
|
||||||
|
{educationStats.sekolah.jumlah}
|
||||||
|
</Text>
|
||||||
|
</Group>
|
||||||
|
<Group justify="space-between" mt="sm">
|
||||||
|
<Text fw={500} c={dark ? "dark.0" : "black"}>
|
||||||
|
Jumlah Tenaga Pengajar
|
||||||
|
</Text>
|
||||||
|
<Text fw={700} c={dark ? "dark.0" : "black"}>
|
||||||
|
{educationStats.sekolah.guru}
|
||||||
|
</Text>
|
||||||
|
</Group>
|
||||||
|
</Card>
|
||||||
|
</Stack>
|
||||||
|
</Card>
|
||||||
|
</GridCol>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<Grid gutter="md">
|
||||||
|
{/* Beasiswa Desa */}
|
||||||
|
<GridCol span={{ base: 12, lg: 6 }}>
|
||||||
|
<Card
|
||||||
|
p="md"
|
||||||
|
radius="md"
|
||||||
|
withBorder
|
||||||
|
bg={dark ? "#141D34" : "white"}
|
||||||
|
style={{ borderColor: dark ? "#141D34" : "white" }}
|
||||||
|
h="100%"
|
||||||
|
>
|
||||||
|
<Group justify="space-between" align="center">
|
||||||
|
<Stack gap={0}>
|
||||||
|
<Text size="sm" c={dark ? "dark.3" : "dimmed"}>
|
||||||
|
Beasiswa Desa
|
||||||
|
</Text>
|
||||||
|
<Text size="xl" fw={700} c={dark ? "dark.0" : "black"}>
|
||||||
|
Penerima: {scholarshipData.penerima}
|
||||||
|
</Text>
|
||||||
|
</Stack>
|
||||||
|
<ThemeIcon
|
||||||
|
variant="light"
|
||||||
|
color="darmasaba-success"
|
||||||
|
size="xl"
|
||||||
|
radius="xl"
|
||||||
>
|
>
|
||||||
<IconAward size={24} />
|
<IconAward size={24} />
|
||||||
</div>
|
</ThemeIcon>
|
||||||
</div>
|
</Group>
|
||||||
|
<Text mt="md" c={dark ? "dark.0" : "black"}>
|
||||||
|
Dana Tersalurkan:{" "}
|
||||||
|
<Text span fw={700}>
|
||||||
|
{scholarshipData.dana}
|
||||||
|
</Text>
|
||||||
|
</Text>
|
||||||
|
<Text mt="sm" c={dark ? "dark.3" : "dimmed"}>
|
||||||
|
Tahun Ajaran: {scholarshipData.tahunAjaran}
|
||||||
|
</Text>
|
||||||
|
</Card>
|
||||||
|
</GridCol>
|
||||||
|
|
||||||
{/* Two centered metrics */}
|
{/* Kalender Event Budaya */}
|
||||||
<div className="grid grid-cols-2 gap-4 mb-6">
|
<GridCol span={{ base: 12, lg: 6 }}>
|
||||||
<div
|
<Card
|
||||||
className={`p-4 rounded-lg text-center ${
|
p="md"
|
||||||
dark ? "bg-slate-700" : "bg-gray-50"
|
radius="md"
|
||||||
}`}
|
withBorder
|
||||||
>
|
bg={dark ? "#141D34" : "white"}
|
||||||
<p
|
style={{ borderColor: dark ? "#141D34" : "white" }}
|
||||||
className={`text-3xl font-bold mb-1 ${
|
h="100%"
|
||||||
dark ? "text-white" : "text-gray-800"
|
>
|
||||||
}`}
|
<Title order={3} mb="md" c={dark ? "dark.0" : "black"}>
|
||||||
>
|
Kalender Event Budaya
|
||||||
{scholarshipData.penerima}
|
</Title>
|
||||||
</p>
|
<List spacing="sm">
|
||||||
<p
|
{culturalEvents.map((event, index) => (
|
||||||
className={`text-xs ${
|
<List.Item
|
||||||
dark ? "text-gray-400" : "text-gray-500"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
Penerima Beasiswa
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
className={`p-4 rounded-lg text-center ${
|
|
||||||
dark ? "bg-slate-700" : "bg-gray-50"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<p
|
|
||||||
className={`text-3xl font-bold mb-1 text-green-500`}
|
|
||||||
>
|
|
||||||
{scholarshipData.dana}
|
|
||||||
</p>
|
|
||||||
<p
|
|
||||||
className={`text-xs ${
|
|
||||||
dark ? "text-gray-400" : "text-gray-500"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
Dana Tersalurkan
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Footer text */}
|
|
||||||
<p
|
|
||||||
className={`text-center text-sm ${
|
|
||||||
dark ? "text-gray-400" : "text-gray-500"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
Tahun Ajaran {scholarshipData.tahunAjaran}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Kalender Event Budaya */}
|
|
||||||
<div
|
|
||||||
className={`rounded-xl shadow-sm p-6 ${
|
|
||||||
dark ? "bg-slate-800 border border-slate-800" : "bg-white border border-white"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
<Title order={3} mb="md" c={dark ? "dark.0" : "black"}>
|
|
||||||
Kalender Event Budaya
|
|
||||||
</Title>
|
|
||||||
|
|
||||||
<div className="space-y-4">
|
|
||||||
{culturalEvents.map((event, index) => (
|
|
||||||
<div
|
|
||||||
key={index}
|
key={index}
|
||||||
className={`flex items-start gap-3 p-4 rounded-lg ${
|
icon={
|
||||||
dark ? "bg-slate-700" : "bg-gray-50"
|
<ThemeIcon color="darmasaba-blue" size={24} radius="xl">
|
||||||
}`}
|
<IconCalendarEvent size={12} />
|
||||||
|
</ThemeIcon>
|
||||||
|
}
|
||||||
>
|
>
|
||||||
<div
|
<Group justify="space-between">
|
||||||
className="w-10 h-10 rounded-full flex items-center justify-center flex-shrink-0 bg-blue-100 text-blue-900"
|
<Text fw={500} c={dark ? "dark.0" : "black"}>
|
||||||
>
|
|
||||||
<IconCalendarEvent size={20} />
|
|
||||||
</div>
|
|
||||||
<div className="flex-1">
|
|
||||||
<p
|
|
||||||
className={`text-sm font-medium ${
|
|
||||||
dark ? "text-white" : "text-gray-800"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{event.nama}
|
{event.nama}
|
||||||
</p>
|
</Text>
|
||||||
<p
|
<Text size="sm" c={dark ? "dark.3" : "dimmed"}>
|
||||||
className={`text-xs mt-1 ${
|
{event.lokasi}
|
||||||
dark ? "text-gray-400" : "text-gray-500"
|
</Text>
|
||||||
}`}
|
</Group>
|
||||||
>
|
<Text size="sm" c={dark ? "dark.3" : "dimmed"}>
|
||||||
{event.tanggal}
|
{event.tanggal}
|
||||||
</p>
|
</Text>
|
||||||
<p
|
</List.Item>
|
||||||
className={`text-xs mt-1 ${
|
|
||||||
dark ? "text-gray-400" : "text-gray-500"
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
Location: {event.lokasi}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))}
|
))}
|
||||||
</div>
|
</List>
|
||||||
</div>
|
</Card>
|
||||||
</div>
|
</GridCol>
|
||||||
</div>
|
</Grid>
|
||||||
</div>
|
</Stack>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default SosialPage;
|
export default SosialPage;
|
||||||
101
src/index.css
101
src/index.css
@@ -1,100 +1 @@
|
|||||||
@import "tailwindcss";
|
@import "tailwindcss";
|
||||||
|
|
||||||
/* Custom CSS variables for Tailwind */
|
|
||||||
:root {
|
|
||||||
/* Darmasaba Navy Colors */
|
|
||||||
--darmasaba-navy-50: #E1E4F2;
|
|
||||||
--darmasaba-navy-100: #B9C2DD;
|
|
||||||
--darmasaba-navy-200: #91A0C9;
|
|
||||||
--darmasaba-navy-300: #697EBA;
|
|
||||||
--darmasaba-navy-400: #4C6CAE;
|
|
||||||
--darmasaba-navy-500: #3B5B97;
|
|
||||||
--darmasaba-navy-600: #2C497F;
|
|
||||||
--darmasaba-navy-700: #1E3766;
|
|
||||||
--darmasaba-navy-800: #12264D;
|
|
||||||
--darmasaba-navy-900: #071833;
|
|
||||||
--darmasaba-navy: #1E3A5F;
|
|
||||||
|
|
||||||
/* Darmasaba Blue Colors */
|
|
||||||
--darmasaba-blue-50: #E3F0FF;
|
|
||||||
--darmasaba-blue-100: #B6D9FF;
|
|
||||||
--darmasaba-blue-200: #89C2FF;
|
|
||||||
--darmasaba-blue-300: #5CA9FF;
|
|
||||||
--darmasaba-blue-400: #3B8FFF;
|
|
||||||
--darmasaba-blue-500: #237AE0;
|
|
||||||
--darmasaba-blue-600: #1C6BBF;
|
|
||||||
--darmasaba-blue-700: #155BA0;
|
|
||||||
--darmasaba-blue-800: #0E4980;
|
|
||||||
--darmasaba-blue-900: #073260;
|
|
||||||
--darmasaba-blue: #3B82F6;
|
|
||||||
|
|
||||||
/* Darmasaba Success Colors */
|
|
||||||
--darmasaba-success-50: #E3F9E7;
|
|
||||||
--darmasaba-success-100: #BFEEC7;
|
|
||||||
--darmasaba-success-200: #9BD8A7;
|
|
||||||
--darmasaba-success-300: #77C387;
|
|
||||||
--darmasaba-success-400: #5DB572;
|
|
||||||
--darmasaba-success-500: #499A5D;
|
|
||||||
--darmasaba-success-600: #3C7F4A;
|
|
||||||
--darmasaba-success-700: #2F6438;
|
|
||||||
--darmasaba-success-800: #234926;
|
|
||||||
--darmasaba-success-900: #17301B;
|
|
||||||
--darmasaba-success: #22C55E;
|
|
||||||
|
|
||||||
/* Darmasaba Warning Colors */
|
|
||||||
--darmasaba-warning-50: #FFF8E1;
|
|
||||||
--darmasaba-warning-100: #FEE7B3;
|
|
||||||
--darmasaba-warning-200: #FDD785;
|
|
||||||
--darmasaba-warning-300: #FDC757;
|
|
||||||
--darmasaba-warning-400: #FBBF3B;
|
|
||||||
--darmasaba-warning-500: #E1AC23;
|
|
||||||
--darmasaba-warning-600: #C2981D;
|
|
||||||
--darmasaba-warning-700: #A38418;
|
|
||||||
--darmasaba-warning-800: #856F12;
|
|
||||||
--darmasaba-warning-900: #675A0D;
|
|
||||||
--darmasaba-warning: #FACC15;
|
|
||||||
|
|
||||||
/* Darmasaba Danger Colors */
|
|
||||||
--darmasaba-danger-50: #FFE3E3;
|
|
||||||
--darmasaba-danger-100: #FFBABA;
|
|
||||||
--darmasaba-danger-200: #FF9191;
|
|
||||||
--darmasaba-danger-300: #FF6868;
|
|
||||||
--darmasaba-danger-400: #FA4B4B;
|
|
||||||
--darmasaba-danger-500: #E03333;
|
|
||||||
--darmasaba-danger-600: #C22A2A;
|
|
||||||
--darmasaba-danger-700: #A32020;
|
|
||||||
--darmasaba-danger-800: #851616;
|
|
||||||
--darmasaba-danger-900: #670C0C;
|
|
||||||
--darmasaba-danger: #EF4444;
|
|
||||||
|
|
||||||
/* Darmasaba Background */
|
|
||||||
--darmasaba-background: #F5F8FB;
|
|
||||||
|
|
||||||
/* Standard colors for dark mode */
|
|
||||||
--slate-900: #0F172A;
|
|
||||||
--slate-800: #1E293B;
|
|
||||||
--slate-700: #334155;
|
|
||||||
--slate-600: #475569;
|
|
||||||
--gray-50: #F9FAFB;
|
|
||||||
--gray-100: #F3F4F6;
|
|
||||||
--gray-200: #E5E7EB;
|
|
||||||
--gray-600: #4B5563;
|
|
||||||
--gray-700: #1F2937;
|
|
||||||
--gray-400: #9CA3AF;
|
|
||||||
--gray-500: #6B7280;
|
|
||||||
--gray-800: #1F2937;
|
|
||||||
--blue-50: #EFF6FF;
|
|
||||||
--blue-100: #DBEAFE;
|
|
||||||
--blue-900: #1E3A5F;
|
|
||||||
--red-500: #EF4444;
|
|
||||||
--green-500: #22C55E;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Dark mode support */
|
|
||||||
[data-mantine-color-scheme="dark"] {
|
|
||||||
color-scheme: dark;
|
|
||||||
}
|
|
||||||
|
|
||||||
[data-mantine-color-scheme="light"] {
|
|
||||||
color-scheme: light;
|
|
||||||
}
|
|
||||||
@@ -60,13 +60,36 @@ type RouteRule = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const routeRules: RouteRule[] = [
|
const routeRules: RouteRule[] = [
|
||||||
|
// Public routes - no auth required
|
||||||
|
{
|
||||||
|
match: (p) => p === "/" || p === "/signin" || p === "/signup",
|
||||||
|
requireAuth: false,
|
||||||
|
},
|
||||||
|
// Profile routes - auth required for all roles
|
||||||
{
|
{
|
||||||
match: (p) => p === "/profile" || p.startsWith("/profile/"),
|
match: (p) => p === "/profile" || p.startsWith("/profile/"),
|
||||||
requireAuth: true,
|
requireAuth: true,
|
||||||
redirectTo: "/signin",
|
redirectTo: "/signin",
|
||||||
},
|
},
|
||||||
|
// Dashboard and main pages - auth required for all roles (not just admin)
|
||||||
{
|
{
|
||||||
match: (p) => p === "/admin" || p.startsWith("/admin/"),
|
match: (p) =>
|
||||||
|
p.startsWith("/kinerja-divisi") ||
|
||||||
|
p.startsWith("/pengaduan") ||
|
||||||
|
p.startsWith("/jenna") ||
|
||||||
|
p.startsWith("/demografi") ||
|
||||||
|
p.startsWith("/keuangan") ||
|
||||||
|
p.startsWith("/bumdes") ||
|
||||||
|
p.startsWith("/sosial") ||
|
||||||
|
p.startsWith("/keamanan") ||
|
||||||
|
p.startsWith("/bantuan") ||
|
||||||
|
p.startsWith("/pengaturan"),
|
||||||
|
requireAuth: true,
|
||||||
|
redirectTo: "/signin",
|
||||||
|
},
|
||||||
|
// Admin routes - auth required with admin role only
|
||||||
|
{
|
||||||
|
match: (p) => p.startsWith("/admin"),
|
||||||
requireAuth: true,
|
requireAuth: true,
|
||||||
requiredRole: "admin",
|
requiredRole: "admin",
|
||||||
redirectTo: "/signin",
|
redirectTo: "/signin",
|
||||||
@@ -98,15 +121,22 @@ export function createProtectedRoute(options: ProtectedRouteOptions = {}) {
|
|||||||
location: { pathname: string; href: string };
|
location: { pathname: string; href: string };
|
||||||
}) => {
|
}) => {
|
||||||
const rule = findRouteRule(location.pathname);
|
const rule = findRouteRule(location.pathname);
|
||||||
|
|
||||||
|
// If no rule matches, allow access by default
|
||||||
if (!rule) return;
|
if (!rule) return;
|
||||||
|
|
||||||
|
// If route explicitly doesn't require auth, allow access
|
||||||
|
if (rule.requireAuth === false) return;
|
||||||
|
|
||||||
const session = await fetchSession();
|
const session = await fetchSession();
|
||||||
const user = session?.user;
|
const user = session?.user;
|
||||||
|
|
||||||
|
// If auth is required but user is not logged in, redirect to login
|
||||||
if (rule.requireAuth && !user) {
|
if (rule.requireAuth && !user) {
|
||||||
redirectToLogin(rule.redirectTo ?? redirectTo, location.href);
|
redirectToLogin(rule.redirectTo ?? redirectTo, location.href);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If specific role is required, check it
|
||||||
if (rule.requiredRole && user?.role !== rule.requiredRole) {
|
if (rule.requiredRole && user?.role !== rule.requiredRole) {
|
||||||
redirectToLogin(rule.redirectTo ?? redirectTo, location.href);
|
redirectToLogin(rule.redirectTo ?? redirectTo, location.href);
|
||||||
}
|
}
|
||||||
@@ -122,4 +152,4 @@ export function createProtectedRoute(options: ProtectedRouteOptions = {}) {
|
|||||||
* Default Middleware Export
|
* Default Middleware Export
|
||||||
* ================================ */
|
* ================================ */
|
||||||
|
|
||||||
export const protectedRouteMiddleware = createProtectedRoute();
|
export const protectedRouteMiddleware = createProtectedRoute();
|
||||||
@@ -9,35 +9,38 @@
|
|||||||
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
|
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
|
||||||
|
|
||||||
import { Route as rootRouteImport } from './routes/__root'
|
import { Route as rootRouteImport } from './routes/__root'
|
||||||
|
import { Route as SosialRouteImport } from './routes/sosial'
|
||||||
import { Route as SignupRouteImport } from './routes/signup'
|
import { Route as SignupRouteImport } from './routes/signup'
|
||||||
import { Route as SigninRouteImport } from './routes/signin'
|
import { Route as SigninRouteImport } from './routes/signin'
|
||||||
import { Route as DashboardRouteRouteImport } from './routes/dashboard/route'
|
import { Route as PengaduanLayananPublikRouteImport } from './routes/pengaduan-layanan-publik'
|
||||||
|
import { Route as KinerjaDivisiRouteImport } from './routes/kinerja-divisi'
|
||||||
|
import { Route as KeuanganAnggaranRouteImport } from './routes/keuangan-anggaran'
|
||||||
|
import { Route as KeamananRouteImport } from './routes/keamanan'
|
||||||
|
import { Route as JennaAnalyticRouteImport } from './routes/jenna-analytic'
|
||||||
|
import { Route as DemografiPekerjaanRouteImport } from './routes/demografi-pekerjaan'
|
||||||
|
import { Route as BumdesRouteImport } from './routes/bumdes'
|
||||||
|
import { Route as BantuanRouteImport } from './routes/bantuan'
|
||||||
|
import { Route as PengaturanRouteRouteImport } from './routes/pengaturan/route'
|
||||||
import { Route as AdminRouteRouteImport } from './routes/admin/route'
|
import { Route as AdminRouteRouteImport } from './routes/admin/route'
|
||||||
import { Route as IndexRouteImport } from './routes/index'
|
import { Route as IndexRouteImport } from './routes/index'
|
||||||
import { Route as UsersIndexRouteImport } from './routes/users/index'
|
import { Route as UsersIndexRouteImport } from './routes/users/index'
|
||||||
import { Route as ProfileIndexRouteImport } from './routes/profile/index'
|
import { Route as ProfileIndexRouteImport } from './routes/profile/index'
|
||||||
import { Route as DashboardIndexRouteImport } from './routes/dashboard/index'
|
|
||||||
import { Route as AdminIndexRouteImport } from './routes/admin/index'
|
import { Route as AdminIndexRouteImport } from './routes/admin/index'
|
||||||
import { Route as UsersIdRouteImport } from './routes/users/$id'
|
import { Route as UsersIdRouteImport } from './routes/users/$id'
|
||||||
import { Route as ProfileEditRouteImport } from './routes/profile/edit'
|
import { Route as ProfileEditRouteImport } from './routes/profile/edit'
|
||||||
import { Route as DashboardSosialRouteImport } from './routes/dashboard/sosial'
|
import { Route as PengaturanUmumRouteImport } from './routes/pengaturan/umum'
|
||||||
import { Route as DashboardPengaduanLayananPublikRouteImport } from './routes/dashboard/pengaduan-layanan-publik'
|
import { Route as PengaturanNotifikasiRouteImport } from './routes/pengaturan/notifikasi'
|
||||||
import { Route as DashboardKinerjaDivisiRouteImport } from './routes/dashboard/kinerja-divisi'
|
import { Route as PengaturanKeamananRouteImport } from './routes/pengaturan/keamanan'
|
||||||
import { Route as DashboardKeuanganAnggaranRouteImport } from './routes/dashboard/keuangan-anggaran'
|
import { Route as PengaturanAksesDanTimRouteImport } from './routes/pengaturan/akses-dan-tim'
|
||||||
import { Route as DashboardKeamananRouteImport } from './routes/dashboard/keamanan'
|
|
||||||
import { Route as DashboardJennaAnalyticRouteImport } from './routes/dashboard/jenna-analytic'
|
|
||||||
import { Route as DashboardDemografiPekerjaanRouteImport } from './routes/dashboard/demografi-pekerjaan'
|
|
||||||
import { Route as DashboardBumdesRouteImport } from './routes/dashboard/bumdes'
|
|
||||||
import { Route as DashboardBantuanRouteImport } from './routes/dashboard/bantuan'
|
|
||||||
import { Route as AdminUsersRouteImport } from './routes/admin/users'
|
import { Route as AdminUsersRouteImport } from './routes/admin/users'
|
||||||
import { Route as AdminSettingsRouteImport } from './routes/admin/settings'
|
import { Route as AdminSettingsRouteImport } from './routes/admin/settings'
|
||||||
import { Route as AdminApikeyRouteImport } from './routes/admin/apikey'
|
import { Route as AdminApikeyRouteImport } from './routes/admin/apikey'
|
||||||
import { Route as DashboardPengaturanRouteRouteImport } from './routes/dashboard/pengaturan/route'
|
|
||||||
import { Route as DashboardPengaturanUmumRouteImport } from './routes/dashboard/pengaturan/umum'
|
|
||||||
import { Route as DashboardPengaturanNotifikasiRouteImport } from './routes/dashboard/pengaturan/notifikasi'
|
|
||||||
import { Route as DashboardPengaturanKeamananRouteImport } from './routes/dashboard/pengaturan/keamanan'
|
|
||||||
import { Route as DashboardPengaturanAksesDanTimRouteImport } from './routes/dashboard/pengaturan/akses-dan-tim'
|
|
||||||
|
|
||||||
|
const SosialRoute = SosialRouteImport.update({
|
||||||
|
id: '/sosial',
|
||||||
|
path: '/sosial',
|
||||||
|
getParentRoute: () => rootRouteImport,
|
||||||
|
} as any)
|
||||||
const SignupRoute = SignupRouteImport.update({
|
const SignupRoute = SignupRouteImport.update({
|
||||||
id: '/signup',
|
id: '/signup',
|
||||||
path: '/signup',
|
path: '/signup',
|
||||||
@@ -48,9 +51,49 @@ const SigninRoute = SigninRouteImport.update({
|
|||||||
path: '/signin',
|
path: '/signin',
|
||||||
getParentRoute: () => rootRouteImport,
|
getParentRoute: () => rootRouteImport,
|
||||||
} as any)
|
} as any)
|
||||||
const DashboardRouteRoute = DashboardRouteRouteImport.update({
|
const PengaduanLayananPublikRoute = PengaduanLayananPublikRouteImport.update({
|
||||||
id: '/dashboard',
|
id: '/pengaduan-layanan-publik',
|
||||||
path: '/dashboard',
|
path: '/pengaduan-layanan-publik',
|
||||||
|
getParentRoute: () => rootRouteImport,
|
||||||
|
} as any)
|
||||||
|
const KinerjaDivisiRoute = KinerjaDivisiRouteImport.update({
|
||||||
|
id: '/kinerja-divisi',
|
||||||
|
path: '/kinerja-divisi',
|
||||||
|
getParentRoute: () => rootRouteImport,
|
||||||
|
} as any)
|
||||||
|
const KeuanganAnggaranRoute = KeuanganAnggaranRouteImport.update({
|
||||||
|
id: '/keuangan-anggaran',
|
||||||
|
path: '/keuangan-anggaran',
|
||||||
|
getParentRoute: () => rootRouteImport,
|
||||||
|
} as any)
|
||||||
|
const KeamananRoute = KeamananRouteImport.update({
|
||||||
|
id: '/keamanan',
|
||||||
|
path: '/keamanan',
|
||||||
|
getParentRoute: () => rootRouteImport,
|
||||||
|
} as any)
|
||||||
|
const JennaAnalyticRoute = JennaAnalyticRouteImport.update({
|
||||||
|
id: '/jenna-analytic',
|
||||||
|
path: '/jenna-analytic',
|
||||||
|
getParentRoute: () => rootRouteImport,
|
||||||
|
} as any)
|
||||||
|
const DemografiPekerjaanRoute = DemografiPekerjaanRouteImport.update({
|
||||||
|
id: '/demografi-pekerjaan',
|
||||||
|
path: '/demografi-pekerjaan',
|
||||||
|
getParentRoute: () => rootRouteImport,
|
||||||
|
} as any)
|
||||||
|
const BumdesRoute = BumdesRouteImport.update({
|
||||||
|
id: '/bumdes',
|
||||||
|
path: '/bumdes',
|
||||||
|
getParentRoute: () => rootRouteImport,
|
||||||
|
} as any)
|
||||||
|
const BantuanRoute = BantuanRouteImport.update({
|
||||||
|
id: '/bantuan',
|
||||||
|
path: '/bantuan',
|
||||||
|
getParentRoute: () => rootRouteImport,
|
||||||
|
} as any)
|
||||||
|
const PengaturanRouteRoute = PengaturanRouteRouteImport.update({
|
||||||
|
id: '/pengaturan',
|
||||||
|
path: '/pengaturan',
|
||||||
getParentRoute: () => rootRouteImport,
|
getParentRoute: () => rootRouteImport,
|
||||||
} as any)
|
} as any)
|
||||||
const AdminRouteRoute = AdminRouteRouteImport.update({
|
const AdminRouteRoute = AdminRouteRouteImport.update({
|
||||||
@@ -73,11 +116,6 @@ const ProfileIndexRoute = ProfileIndexRouteImport.update({
|
|||||||
path: '/profile/',
|
path: '/profile/',
|
||||||
getParentRoute: () => rootRouteImport,
|
getParentRoute: () => rootRouteImport,
|
||||||
} as any)
|
} as any)
|
||||||
const DashboardIndexRoute = DashboardIndexRouteImport.update({
|
|
||||||
id: '/',
|
|
||||||
path: '/',
|
|
||||||
getParentRoute: () => DashboardRouteRoute,
|
|
||||||
} as any)
|
|
||||||
const AdminIndexRoute = AdminIndexRouteImport.update({
|
const AdminIndexRoute = AdminIndexRouteImport.update({
|
||||||
id: '/',
|
id: '/',
|
||||||
path: '/',
|
path: '/',
|
||||||
@@ -93,53 +131,25 @@ const ProfileEditRoute = ProfileEditRouteImport.update({
|
|||||||
path: '/profile/edit',
|
path: '/profile/edit',
|
||||||
getParentRoute: () => rootRouteImport,
|
getParentRoute: () => rootRouteImport,
|
||||||
} as any)
|
} as any)
|
||||||
const DashboardSosialRoute = DashboardSosialRouteImport.update({
|
const PengaturanUmumRoute = PengaturanUmumRouteImport.update({
|
||||||
id: '/sosial',
|
id: '/umum',
|
||||||
path: '/sosial',
|
path: '/umum',
|
||||||
getParentRoute: () => DashboardRouteRoute,
|
getParentRoute: () => PengaturanRouteRoute,
|
||||||
} as any)
|
} as any)
|
||||||
const DashboardPengaduanLayananPublikRoute =
|
const PengaturanNotifikasiRoute = PengaturanNotifikasiRouteImport.update({
|
||||||
DashboardPengaduanLayananPublikRouteImport.update({
|
id: '/notifikasi',
|
||||||
id: '/pengaduan-layanan-publik',
|
path: '/notifikasi',
|
||||||
path: '/pengaduan-layanan-publik',
|
getParentRoute: () => PengaturanRouteRoute,
|
||||||
getParentRoute: () => DashboardRouteRoute,
|
|
||||||
} as any)
|
|
||||||
const DashboardKinerjaDivisiRoute = DashboardKinerjaDivisiRouteImport.update({
|
|
||||||
id: '/kinerja-divisi',
|
|
||||||
path: '/kinerja-divisi',
|
|
||||||
getParentRoute: () => DashboardRouteRoute,
|
|
||||||
} as any)
|
} as any)
|
||||||
const DashboardKeuanganAnggaranRoute =
|
const PengaturanKeamananRoute = PengaturanKeamananRouteImport.update({
|
||||||
DashboardKeuanganAnggaranRouteImport.update({
|
|
||||||
id: '/keuangan-anggaran',
|
|
||||||
path: '/keuangan-anggaran',
|
|
||||||
getParentRoute: () => DashboardRouteRoute,
|
|
||||||
} as any)
|
|
||||||
const DashboardKeamananRoute = DashboardKeamananRouteImport.update({
|
|
||||||
id: '/keamanan',
|
id: '/keamanan',
|
||||||
path: '/keamanan',
|
path: '/keamanan',
|
||||||
getParentRoute: () => DashboardRouteRoute,
|
getParentRoute: () => PengaturanRouteRoute,
|
||||||
} as any)
|
} as any)
|
||||||
const DashboardJennaAnalyticRoute = DashboardJennaAnalyticRouteImport.update({
|
const PengaturanAksesDanTimRoute = PengaturanAksesDanTimRouteImport.update({
|
||||||
id: '/jenna-analytic',
|
id: '/akses-dan-tim',
|
||||||
path: '/jenna-analytic',
|
path: '/akses-dan-tim',
|
||||||
getParentRoute: () => DashboardRouteRoute,
|
getParentRoute: () => PengaturanRouteRoute,
|
||||||
} as any)
|
|
||||||
const DashboardDemografiPekerjaanRoute =
|
|
||||||
DashboardDemografiPekerjaanRouteImport.update({
|
|
||||||
id: '/demografi-pekerjaan',
|
|
||||||
path: '/demografi-pekerjaan',
|
|
||||||
getParentRoute: () => DashboardRouteRoute,
|
|
||||||
} as any)
|
|
||||||
const DashboardBumdesRoute = DashboardBumdesRouteImport.update({
|
|
||||||
id: '/bumdes',
|
|
||||||
path: '/bumdes',
|
|
||||||
getParentRoute: () => DashboardRouteRoute,
|
|
||||||
} as any)
|
|
||||||
const DashboardBantuanRoute = DashboardBantuanRouteImport.update({
|
|
||||||
id: '/bantuan',
|
|
||||||
path: '/bantuan',
|
|
||||||
getParentRoute: () => DashboardRouteRoute,
|
|
||||||
} as any)
|
} as any)
|
||||||
const AdminUsersRoute = AdminUsersRouteImport.update({
|
const AdminUsersRoute = AdminUsersRouteImport.update({
|
||||||
id: '/users',
|
id: '/users',
|
||||||
@@ -156,222 +166,192 @@ const AdminApikeyRoute = AdminApikeyRouteImport.update({
|
|||||||
path: '/apikey',
|
path: '/apikey',
|
||||||
getParentRoute: () => AdminRouteRoute,
|
getParentRoute: () => AdminRouteRoute,
|
||||||
} as any)
|
} as any)
|
||||||
const DashboardPengaturanRouteRoute =
|
|
||||||
DashboardPengaturanRouteRouteImport.update({
|
|
||||||
id: '/pengaturan',
|
|
||||||
path: '/pengaturan',
|
|
||||||
getParentRoute: () => DashboardRouteRoute,
|
|
||||||
} as any)
|
|
||||||
const DashboardPengaturanUmumRoute = DashboardPengaturanUmumRouteImport.update({
|
|
||||||
id: '/umum',
|
|
||||||
path: '/umum',
|
|
||||||
getParentRoute: () => DashboardPengaturanRouteRoute,
|
|
||||||
} as any)
|
|
||||||
const DashboardPengaturanNotifikasiRoute =
|
|
||||||
DashboardPengaturanNotifikasiRouteImport.update({
|
|
||||||
id: '/notifikasi',
|
|
||||||
path: '/notifikasi',
|
|
||||||
getParentRoute: () => DashboardPengaturanRouteRoute,
|
|
||||||
} as any)
|
|
||||||
const DashboardPengaturanKeamananRoute =
|
|
||||||
DashboardPengaturanKeamananRouteImport.update({
|
|
||||||
id: '/keamanan',
|
|
||||||
path: '/keamanan',
|
|
||||||
getParentRoute: () => DashboardPengaturanRouteRoute,
|
|
||||||
} as any)
|
|
||||||
const DashboardPengaturanAksesDanTimRoute =
|
|
||||||
DashboardPengaturanAksesDanTimRouteImport.update({
|
|
||||||
id: '/akses-dan-tim',
|
|
||||||
path: '/akses-dan-tim',
|
|
||||||
getParentRoute: () => DashboardPengaturanRouteRoute,
|
|
||||||
} as any)
|
|
||||||
|
|
||||||
export interface FileRoutesByFullPath {
|
export interface FileRoutesByFullPath {
|
||||||
'/': typeof IndexRoute
|
'/': typeof IndexRoute
|
||||||
'/admin': typeof AdminRouteRouteWithChildren
|
'/admin': typeof AdminRouteRouteWithChildren
|
||||||
'/dashboard': typeof DashboardRouteRouteWithChildren
|
'/pengaturan': typeof PengaturanRouteRouteWithChildren
|
||||||
|
'/bantuan': typeof BantuanRoute
|
||||||
|
'/bumdes': typeof BumdesRoute
|
||||||
|
'/demografi-pekerjaan': typeof DemografiPekerjaanRoute
|
||||||
|
'/jenna-analytic': typeof JennaAnalyticRoute
|
||||||
|
'/keamanan': typeof KeamananRoute
|
||||||
|
'/keuangan-anggaran': typeof KeuanganAnggaranRoute
|
||||||
|
'/kinerja-divisi': typeof KinerjaDivisiRoute
|
||||||
|
'/pengaduan-layanan-publik': typeof PengaduanLayananPublikRoute
|
||||||
'/signin': typeof SigninRoute
|
'/signin': typeof SigninRoute
|
||||||
'/signup': typeof SignupRoute
|
'/signup': typeof SignupRoute
|
||||||
'/dashboard/pengaturan': typeof DashboardPengaturanRouteRouteWithChildren
|
'/sosial': typeof SosialRoute
|
||||||
'/admin/apikey': typeof AdminApikeyRoute
|
'/admin/apikey': typeof AdminApikeyRoute
|
||||||
'/admin/settings': typeof AdminSettingsRoute
|
'/admin/settings': typeof AdminSettingsRoute
|
||||||
'/admin/users': typeof AdminUsersRoute
|
'/admin/users': typeof AdminUsersRoute
|
||||||
'/dashboard/bantuan': typeof DashboardBantuanRoute
|
'/pengaturan/akses-dan-tim': typeof PengaturanAksesDanTimRoute
|
||||||
'/dashboard/bumdes': typeof DashboardBumdesRoute
|
'/pengaturan/keamanan': typeof PengaturanKeamananRoute
|
||||||
'/dashboard/demografi-pekerjaan': typeof DashboardDemografiPekerjaanRoute
|
'/pengaturan/notifikasi': typeof PengaturanNotifikasiRoute
|
||||||
'/dashboard/jenna-analytic': typeof DashboardJennaAnalyticRoute
|
'/pengaturan/umum': typeof PengaturanUmumRoute
|
||||||
'/dashboard/keamanan': typeof DashboardKeamananRoute
|
|
||||||
'/dashboard/keuangan-anggaran': typeof DashboardKeuanganAnggaranRoute
|
|
||||||
'/dashboard/kinerja-divisi': typeof DashboardKinerjaDivisiRoute
|
|
||||||
'/dashboard/pengaduan-layanan-publik': typeof DashboardPengaduanLayananPublikRoute
|
|
||||||
'/dashboard/sosial': typeof DashboardSosialRoute
|
|
||||||
'/profile/edit': typeof ProfileEditRoute
|
'/profile/edit': typeof ProfileEditRoute
|
||||||
'/users/$id': typeof UsersIdRoute
|
'/users/$id': typeof UsersIdRoute
|
||||||
'/admin/': typeof AdminIndexRoute
|
'/admin/': typeof AdminIndexRoute
|
||||||
'/dashboard/': typeof DashboardIndexRoute
|
|
||||||
'/profile/': typeof ProfileIndexRoute
|
'/profile/': typeof ProfileIndexRoute
|
||||||
'/users/': typeof UsersIndexRoute
|
'/users/': typeof UsersIndexRoute
|
||||||
'/dashboard/pengaturan/akses-dan-tim': typeof DashboardPengaturanAksesDanTimRoute
|
|
||||||
'/dashboard/pengaturan/keamanan': typeof DashboardPengaturanKeamananRoute
|
|
||||||
'/dashboard/pengaturan/notifikasi': typeof DashboardPengaturanNotifikasiRoute
|
|
||||||
'/dashboard/pengaturan/umum': typeof DashboardPengaturanUmumRoute
|
|
||||||
}
|
}
|
||||||
export interface FileRoutesByTo {
|
export interface FileRoutesByTo {
|
||||||
'/': typeof IndexRoute
|
'/': typeof IndexRoute
|
||||||
|
'/pengaturan': typeof PengaturanRouteRouteWithChildren
|
||||||
|
'/bantuan': typeof BantuanRoute
|
||||||
|
'/bumdes': typeof BumdesRoute
|
||||||
|
'/demografi-pekerjaan': typeof DemografiPekerjaanRoute
|
||||||
|
'/jenna-analytic': typeof JennaAnalyticRoute
|
||||||
|
'/keamanan': typeof KeamananRoute
|
||||||
|
'/keuangan-anggaran': typeof KeuanganAnggaranRoute
|
||||||
|
'/kinerja-divisi': typeof KinerjaDivisiRoute
|
||||||
|
'/pengaduan-layanan-publik': typeof PengaduanLayananPublikRoute
|
||||||
'/signin': typeof SigninRoute
|
'/signin': typeof SigninRoute
|
||||||
'/signup': typeof SignupRoute
|
'/signup': typeof SignupRoute
|
||||||
'/dashboard/pengaturan': typeof DashboardPengaturanRouteRouteWithChildren
|
'/sosial': typeof SosialRoute
|
||||||
'/admin/apikey': typeof AdminApikeyRoute
|
'/admin/apikey': typeof AdminApikeyRoute
|
||||||
'/admin/settings': typeof AdminSettingsRoute
|
'/admin/settings': typeof AdminSettingsRoute
|
||||||
'/admin/users': typeof AdminUsersRoute
|
'/admin/users': typeof AdminUsersRoute
|
||||||
'/dashboard/bantuan': typeof DashboardBantuanRoute
|
'/pengaturan/akses-dan-tim': typeof PengaturanAksesDanTimRoute
|
||||||
'/dashboard/bumdes': typeof DashboardBumdesRoute
|
'/pengaturan/keamanan': typeof PengaturanKeamananRoute
|
||||||
'/dashboard/demografi-pekerjaan': typeof DashboardDemografiPekerjaanRoute
|
'/pengaturan/notifikasi': typeof PengaturanNotifikasiRoute
|
||||||
'/dashboard/jenna-analytic': typeof DashboardJennaAnalyticRoute
|
'/pengaturan/umum': typeof PengaturanUmumRoute
|
||||||
'/dashboard/keamanan': typeof DashboardKeamananRoute
|
|
||||||
'/dashboard/keuangan-anggaran': typeof DashboardKeuanganAnggaranRoute
|
|
||||||
'/dashboard/kinerja-divisi': typeof DashboardKinerjaDivisiRoute
|
|
||||||
'/dashboard/pengaduan-layanan-publik': typeof DashboardPengaduanLayananPublikRoute
|
|
||||||
'/dashboard/sosial': typeof DashboardSosialRoute
|
|
||||||
'/profile/edit': typeof ProfileEditRoute
|
'/profile/edit': typeof ProfileEditRoute
|
||||||
'/users/$id': typeof UsersIdRoute
|
'/users/$id': typeof UsersIdRoute
|
||||||
'/admin': typeof AdminIndexRoute
|
'/admin': typeof AdminIndexRoute
|
||||||
'/dashboard': typeof DashboardIndexRoute
|
|
||||||
'/profile': typeof ProfileIndexRoute
|
'/profile': typeof ProfileIndexRoute
|
||||||
'/users': typeof UsersIndexRoute
|
'/users': typeof UsersIndexRoute
|
||||||
'/dashboard/pengaturan/akses-dan-tim': typeof DashboardPengaturanAksesDanTimRoute
|
|
||||||
'/dashboard/pengaturan/keamanan': typeof DashboardPengaturanKeamananRoute
|
|
||||||
'/dashboard/pengaturan/notifikasi': typeof DashboardPengaturanNotifikasiRoute
|
|
||||||
'/dashboard/pengaturan/umum': typeof DashboardPengaturanUmumRoute
|
|
||||||
}
|
}
|
||||||
export interface FileRoutesById {
|
export interface FileRoutesById {
|
||||||
__root__: typeof rootRouteImport
|
__root__: typeof rootRouteImport
|
||||||
'/': typeof IndexRoute
|
'/': typeof IndexRoute
|
||||||
'/admin': typeof AdminRouteRouteWithChildren
|
'/admin': typeof AdminRouteRouteWithChildren
|
||||||
'/dashboard': typeof DashboardRouteRouteWithChildren
|
'/pengaturan': typeof PengaturanRouteRouteWithChildren
|
||||||
|
'/bantuan': typeof BantuanRoute
|
||||||
|
'/bumdes': typeof BumdesRoute
|
||||||
|
'/demografi-pekerjaan': typeof DemografiPekerjaanRoute
|
||||||
|
'/jenna-analytic': typeof JennaAnalyticRoute
|
||||||
|
'/keamanan': typeof KeamananRoute
|
||||||
|
'/keuangan-anggaran': typeof KeuanganAnggaranRoute
|
||||||
|
'/kinerja-divisi': typeof KinerjaDivisiRoute
|
||||||
|
'/pengaduan-layanan-publik': typeof PengaduanLayananPublikRoute
|
||||||
'/signin': typeof SigninRoute
|
'/signin': typeof SigninRoute
|
||||||
'/signup': typeof SignupRoute
|
'/signup': typeof SignupRoute
|
||||||
'/dashboard/pengaturan': typeof DashboardPengaturanRouteRouteWithChildren
|
'/sosial': typeof SosialRoute
|
||||||
'/admin/apikey': typeof AdminApikeyRoute
|
'/admin/apikey': typeof AdminApikeyRoute
|
||||||
'/admin/settings': typeof AdminSettingsRoute
|
'/admin/settings': typeof AdminSettingsRoute
|
||||||
'/admin/users': typeof AdminUsersRoute
|
'/admin/users': typeof AdminUsersRoute
|
||||||
'/dashboard/bantuan': typeof DashboardBantuanRoute
|
'/pengaturan/akses-dan-tim': typeof PengaturanAksesDanTimRoute
|
||||||
'/dashboard/bumdes': typeof DashboardBumdesRoute
|
'/pengaturan/keamanan': typeof PengaturanKeamananRoute
|
||||||
'/dashboard/demografi-pekerjaan': typeof DashboardDemografiPekerjaanRoute
|
'/pengaturan/notifikasi': typeof PengaturanNotifikasiRoute
|
||||||
'/dashboard/jenna-analytic': typeof DashboardJennaAnalyticRoute
|
'/pengaturan/umum': typeof PengaturanUmumRoute
|
||||||
'/dashboard/keamanan': typeof DashboardKeamananRoute
|
|
||||||
'/dashboard/keuangan-anggaran': typeof DashboardKeuanganAnggaranRoute
|
|
||||||
'/dashboard/kinerja-divisi': typeof DashboardKinerjaDivisiRoute
|
|
||||||
'/dashboard/pengaduan-layanan-publik': typeof DashboardPengaduanLayananPublikRoute
|
|
||||||
'/dashboard/sosial': typeof DashboardSosialRoute
|
|
||||||
'/profile/edit': typeof ProfileEditRoute
|
'/profile/edit': typeof ProfileEditRoute
|
||||||
'/users/$id': typeof UsersIdRoute
|
'/users/$id': typeof UsersIdRoute
|
||||||
'/admin/': typeof AdminIndexRoute
|
'/admin/': typeof AdminIndexRoute
|
||||||
'/dashboard/': typeof DashboardIndexRoute
|
|
||||||
'/profile/': typeof ProfileIndexRoute
|
'/profile/': typeof ProfileIndexRoute
|
||||||
'/users/': typeof UsersIndexRoute
|
'/users/': typeof UsersIndexRoute
|
||||||
'/dashboard/pengaturan/akses-dan-tim': typeof DashboardPengaturanAksesDanTimRoute
|
|
||||||
'/dashboard/pengaturan/keamanan': typeof DashboardPengaturanKeamananRoute
|
|
||||||
'/dashboard/pengaturan/notifikasi': typeof DashboardPengaturanNotifikasiRoute
|
|
||||||
'/dashboard/pengaturan/umum': typeof DashboardPengaturanUmumRoute
|
|
||||||
}
|
}
|
||||||
export interface FileRouteTypes {
|
export interface FileRouteTypes {
|
||||||
fileRoutesByFullPath: FileRoutesByFullPath
|
fileRoutesByFullPath: FileRoutesByFullPath
|
||||||
fullPaths:
|
fullPaths:
|
||||||
| '/'
|
| '/'
|
||||||
| '/admin'
|
| '/admin'
|
||||||
| '/dashboard'
|
| '/pengaturan'
|
||||||
|
| '/bantuan'
|
||||||
|
| '/bumdes'
|
||||||
|
| '/demografi-pekerjaan'
|
||||||
|
| '/jenna-analytic'
|
||||||
|
| '/keamanan'
|
||||||
|
| '/keuangan-anggaran'
|
||||||
|
| '/kinerja-divisi'
|
||||||
|
| '/pengaduan-layanan-publik'
|
||||||
| '/signin'
|
| '/signin'
|
||||||
| '/signup'
|
| '/signup'
|
||||||
| '/dashboard/pengaturan'
|
| '/sosial'
|
||||||
| '/admin/apikey'
|
| '/admin/apikey'
|
||||||
| '/admin/settings'
|
| '/admin/settings'
|
||||||
| '/admin/users'
|
| '/admin/users'
|
||||||
| '/dashboard/bantuan'
|
| '/pengaturan/akses-dan-tim'
|
||||||
| '/dashboard/bumdes'
|
| '/pengaturan/keamanan'
|
||||||
| '/dashboard/demografi-pekerjaan'
|
| '/pengaturan/notifikasi'
|
||||||
| '/dashboard/jenna-analytic'
|
| '/pengaturan/umum'
|
||||||
| '/dashboard/keamanan'
|
|
||||||
| '/dashboard/keuangan-anggaran'
|
|
||||||
| '/dashboard/kinerja-divisi'
|
|
||||||
| '/dashboard/pengaduan-layanan-publik'
|
|
||||||
| '/dashboard/sosial'
|
|
||||||
| '/profile/edit'
|
| '/profile/edit'
|
||||||
| '/users/$id'
|
| '/users/$id'
|
||||||
| '/admin/'
|
| '/admin/'
|
||||||
| '/dashboard/'
|
|
||||||
| '/profile/'
|
| '/profile/'
|
||||||
| '/users/'
|
| '/users/'
|
||||||
| '/dashboard/pengaturan/akses-dan-tim'
|
|
||||||
| '/dashboard/pengaturan/keamanan'
|
|
||||||
| '/dashboard/pengaturan/notifikasi'
|
|
||||||
| '/dashboard/pengaturan/umum'
|
|
||||||
fileRoutesByTo: FileRoutesByTo
|
fileRoutesByTo: FileRoutesByTo
|
||||||
to:
|
to:
|
||||||
| '/'
|
| '/'
|
||||||
|
| '/pengaturan'
|
||||||
|
| '/bantuan'
|
||||||
|
| '/bumdes'
|
||||||
|
| '/demografi-pekerjaan'
|
||||||
|
| '/jenna-analytic'
|
||||||
|
| '/keamanan'
|
||||||
|
| '/keuangan-anggaran'
|
||||||
|
| '/kinerja-divisi'
|
||||||
|
| '/pengaduan-layanan-publik'
|
||||||
| '/signin'
|
| '/signin'
|
||||||
| '/signup'
|
| '/signup'
|
||||||
| '/dashboard/pengaturan'
|
| '/sosial'
|
||||||
| '/admin/apikey'
|
| '/admin/apikey'
|
||||||
| '/admin/settings'
|
| '/admin/settings'
|
||||||
| '/admin/users'
|
| '/admin/users'
|
||||||
| '/dashboard/bantuan'
|
| '/pengaturan/akses-dan-tim'
|
||||||
| '/dashboard/bumdes'
|
| '/pengaturan/keamanan'
|
||||||
| '/dashboard/demografi-pekerjaan'
|
| '/pengaturan/notifikasi'
|
||||||
| '/dashboard/jenna-analytic'
|
| '/pengaturan/umum'
|
||||||
| '/dashboard/keamanan'
|
|
||||||
| '/dashboard/keuangan-anggaran'
|
|
||||||
| '/dashboard/kinerja-divisi'
|
|
||||||
| '/dashboard/pengaduan-layanan-publik'
|
|
||||||
| '/dashboard/sosial'
|
|
||||||
| '/profile/edit'
|
| '/profile/edit'
|
||||||
| '/users/$id'
|
| '/users/$id'
|
||||||
| '/admin'
|
| '/admin'
|
||||||
| '/dashboard'
|
|
||||||
| '/profile'
|
| '/profile'
|
||||||
| '/users'
|
| '/users'
|
||||||
| '/dashboard/pengaturan/akses-dan-tim'
|
|
||||||
| '/dashboard/pengaturan/keamanan'
|
|
||||||
| '/dashboard/pengaturan/notifikasi'
|
|
||||||
| '/dashboard/pengaturan/umum'
|
|
||||||
id:
|
id:
|
||||||
| '__root__'
|
| '__root__'
|
||||||
| '/'
|
| '/'
|
||||||
| '/admin'
|
| '/admin'
|
||||||
| '/dashboard'
|
| '/pengaturan'
|
||||||
|
| '/bantuan'
|
||||||
|
| '/bumdes'
|
||||||
|
| '/demografi-pekerjaan'
|
||||||
|
| '/jenna-analytic'
|
||||||
|
| '/keamanan'
|
||||||
|
| '/keuangan-anggaran'
|
||||||
|
| '/kinerja-divisi'
|
||||||
|
| '/pengaduan-layanan-publik'
|
||||||
| '/signin'
|
| '/signin'
|
||||||
| '/signup'
|
| '/signup'
|
||||||
| '/dashboard/pengaturan'
|
| '/sosial'
|
||||||
| '/admin/apikey'
|
| '/admin/apikey'
|
||||||
| '/admin/settings'
|
| '/admin/settings'
|
||||||
| '/admin/users'
|
| '/admin/users'
|
||||||
| '/dashboard/bantuan'
|
| '/pengaturan/akses-dan-tim'
|
||||||
| '/dashboard/bumdes'
|
| '/pengaturan/keamanan'
|
||||||
| '/dashboard/demografi-pekerjaan'
|
| '/pengaturan/notifikasi'
|
||||||
| '/dashboard/jenna-analytic'
|
| '/pengaturan/umum'
|
||||||
| '/dashboard/keamanan'
|
|
||||||
| '/dashboard/keuangan-anggaran'
|
|
||||||
| '/dashboard/kinerja-divisi'
|
|
||||||
| '/dashboard/pengaduan-layanan-publik'
|
|
||||||
| '/dashboard/sosial'
|
|
||||||
| '/profile/edit'
|
| '/profile/edit'
|
||||||
| '/users/$id'
|
| '/users/$id'
|
||||||
| '/admin/'
|
| '/admin/'
|
||||||
| '/dashboard/'
|
|
||||||
| '/profile/'
|
| '/profile/'
|
||||||
| '/users/'
|
| '/users/'
|
||||||
| '/dashboard/pengaturan/akses-dan-tim'
|
|
||||||
| '/dashboard/pengaturan/keamanan'
|
|
||||||
| '/dashboard/pengaturan/notifikasi'
|
|
||||||
| '/dashboard/pengaturan/umum'
|
|
||||||
fileRoutesById: FileRoutesById
|
fileRoutesById: FileRoutesById
|
||||||
}
|
}
|
||||||
export interface RootRouteChildren {
|
export interface RootRouteChildren {
|
||||||
IndexRoute: typeof IndexRoute
|
IndexRoute: typeof IndexRoute
|
||||||
AdminRouteRoute: typeof AdminRouteRouteWithChildren
|
AdminRouteRoute: typeof AdminRouteRouteWithChildren
|
||||||
DashboardRouteRoute: typeof DashboardRouteRouteWithChildren
|
PengaturanRouteRoute: typeof PengaturanRouteRouteWithChildren
|
||||||
|
BantuanRoute: typeof BantuanRoute
|
||||||
|
BumdesRoute: typeof BumdesRoute
|
||||||
|
DemografiPekerjaanRoute: typeof DemografiPekerjaanRoute
|
||||||
|
JennaAnalyticRoute: typeof JennaAnalyticRoute
|
||||||
|
KeamananRoute: typeof KeamananRoute
|
||||||
|
KeuanganAnggaranRoute: typeof KeuanganAnggaranRoute
|
||||||
|
KinerjaDivisiRoute: typeof KinerjaDivisiRoute
|
||||||
|
PengaduanLayananPublikRoute: typeof PengaduanLayananPublikRoute
|
||||||
SigninRoute: typeof SigninRoute
|
SigninRoute: typeof SigninRoute
|
||||||
SignupRoute: typeof SignupRoute
|
SignupRoute: typeof SignupRoute
|
||||||
|
SosialRoute: typeof SosialRoute
|
||||||
ProfileEditRoute: typeof ProfileEditRoute
|
ProfileEditRoute: typeof ProfileEditRoute
|
||||||
UsersIdRoute: typeof UsersIdRoute
|
UsersIdRoute: typeof UsersIdRoute
|
||||||
ProfileIndexRoute: typeof ProfileIndexRoute
|
ProfileIndexRoute: typeof ProfileIndexRoute
|
||||||
@@ -380,6 +360,13 @@ export interface RootRouteChildren {
|
|||||||
|
|
||||||
declare module '@tanstack/react-router' {
|
declare module '@tanstack/react-router' {
|
||||||
interface FileRoutesByPath {
|
interface FileRoutesByPath {
|
||||||
|
'/sosial': {
|
||||||
|
id: '/sosial'
|
||||||
|
path: '/sosial'
|
||||||
|
fullPath: '/sosial'
|
||||||
|
preLoaderRoute: typeof SosialRouteImport
|
||||||
|
parentRoute: typeof rootRouteImport
|
||||||
|
}
|
||||||
'/signup': {
|
'/signup': {
|
||||||
id: '/signup'
|
id: '/signup'
|
||||||
path: '/signup'
|
path: '/signup'
|
||||||
@@ -394,11 +381,67 @@ declare module '@tanstack/react-router' {
|
|||||||
preLoaderRoute: typeof SigninRouteImport
|
preLoaderRoute: typeof SigninRouteImport
|
||||||
parentRoute: typeof rootRouteImport
|
parentRoute: typeof rootRouteImport
|
||||||
}
|
}
|
||||||
'/dashboard': {
|
'/pengaduan-layanan-publik': {
|
||||||
id: '/dashboard'
|
id: '/pengaduan-layanan-publik'
|
||||||
path: '/dashboard'
|
path: '/pengaduan-layanan-publik'
|
||||||
fullPath: '/dashboard'
|
fullPath: '/pengaduan-layanan-publik'
|
||||||
preLoaderRoute: typeof DashboardRouteRouteImport
|
preLoaderRoute: typeof PengaduanLayananPublikRouteImport
|
||||||
|
parentRoute: typeof rootRouteImport
|
||||||
|
}
|
||||||
|
'/kinerja-divisi': {
|
||||||
|
id: '/kinerja-divisi'
|
||||||
|
path: '/kinerja-divisi'
|
||||||
|
fullPath: '/kinerja-divisi'
|
||||||
|
preLoaderRoute: typeof KinerjaDivisiRouteImport
|
||||||
|
parentRoute: typeof rootRouteImport
|
||||||
|
}
|
||||||
|
'/keuangan-anggaran': {
|
||||||
|
id: '/keuangan-anggaran'
|
||||||
|
path: '/keuangan-anggaran'
|
||||||
|
fullPath: '/keuangan-anggaran'
|
||||||
|
preLoaderRoute: typeof KeuanganAnggaranRouteImport
|
||||||
|
parentRoute: typeof rootRouteImport
|
||||||
|
}
|
||||||
|
'/keamanan': {
|
||||||
|
id: '/keamanan'
|
||||||
|
path: '/keamanan'
|
||||||
|
fullPath: '/keamanan'
|
||||||
|
preLoaderRoute: typeof KeamananRouteImport
|
||||||
|
parentRoute: typeof rootRouteImport
|
||||||
|
}
|
||||||
|
'/jenna-analytic': {
|
||||||
|
id: '/jenna-analytic'
|
||||||
|
path: '/jenna-analytic'
|
||||||
|
fullPath: '/jenna-analytic'
|
||||||
|
preLoaderRoute: typeof JennaAnalyticRouteImport
|
||||||
|
parentRoute: typeof rootRouteImport
|
||||||
|
}
|
||||||
|
'/demografi-pekerjaan': {
|
||||||
|
id: '/demografi-pekerjaan'
|
||||||
|
path: '/demografi-pekerjaan'
|
||||||
|
fullPath: '/demografi-pekerjaan'
|
||||||
|
preLoaderRoute: typeof DemografiPekerjaanRouteImport
|
||||||
|
parentRoute: typeof rootRouteImport
|
||||||
|
}
|
||||||
|
'/bumdes': {
|
||||||
|
id: '/bumdes'
|
||||||
|
path: '/bumdes'
|
||||||
|
fullPath: '/bumdes'
|
||||||
|
preLoaderRoute: typeof BumdesRouteImport
|
||||||
|
parentRoute: typeof rootRouteImport
|
||||||
|
}
|
||||||
|
'/bantuan': {
|
||||||
|
id: '/bantuan'
|
||||||
|
path: '/bantuan'
|
||||||
|
fullPath: '/bantuan'
|
||||||
|
preLoaderRoute: typeof BantuanRouteImport
|
||||||
|
parentRoute: typeof rootRouteImport
|
||||||
|
}
|
||||||
|
'/pengaturan': {
|
||||||
|
id: '/pengaturan'
|
||||||
|
path: '/pengaturan'
|
||||||
|
fullPath: '/pengaturan'
|
||||||
|
preLoaderRoute: typeof PengaturanRouteRouteImport
|
||||||
parentRoute: typeof rootRouteImport
|
parentRoute: typeof rootRouteImport
|
||||||
}
|
}
|
||||||
'/admin': {
|
'/admin': {
|
||||||
@@ -429,13 +472,6 @@ declare module '@tanstack/react-router' {
|
|||||||
preLoaderRoute: typeof ProfileIndexRouteImport
|
preLoaderRoute: typeof ProfileIndexRouteImport
|
||||||
parentRoute: typeof rootRouteImport
|
parentRoute: typeof rootRouteImport
|
||||||
}
|
}
|
||||||
'/dashboard/': {
|
|
||||||
id: '/dashboard/'
|
|
||||||
path: '/'
|
|
||||||
fullPath: '/dashboard/'
|
|
||||||
preLoaderRoute: typeof DashboardIndexRouteImport
|
|
||||||
parentRoute: typeof DashboardRouteRoute
|
|
||||||
}
|
|
||||||
'/admin/': {
|
'/admin/': {
|
||||||
id: '/admin/'
|
id: '/admin/'
|
||||||
path: '/'
|
path: '/'
|
||||||
@@ -457,68 +493,33 @@ declare module '@tanstack/react-router' {
|
|||||||
preLoaderRoute: typeof ProfileEditRouteImport
|
preLoaderRoute: typeof ProfileEditRouteImport
|
||||||
parentRoute: typeof rootRouteImport
|
parentRoute: typeof rootRouteImport
|
||||||
}
|
}
|
||||||
'/dashboard/sosial': {
|
'/pengaturan/umum': {
|
||||||
id: '/dashboard/sosial'
|
id: '/pengaturan/umum'
|
||||||
path: '/sosial'
|
path: '/umum'
|
||||||
fullPath: '/dashboard/sosial'
|
fullPath: '/pengaturan/umum'
|
||||||
preLoaderRoute: typeof DashboardSosialRouteImport
|
preLoaderRoute: typeof PengaturanUmumRouteImport
|
||||||
parentRoute: typeof DashboardRouteRoute
|
parentRoute: typeof PengaturanRouteRoute
|
||||||
}
|
}
|
||||||
'/dashboard/pengaduan-layanan-publik': {
|
'/pengaturan/notifikasi': {
|
||||||
id: '/dashboard/pengaduan-layanan-publik'
|
id: '/pengaturan/notifikasi'
|
||||||
path: '/pengaduan-layanan-publik'
|
path: '/notifikasi'
|
||||||
fullPath: '/dashboard/pengaduan-layanan-publik'
|
fullPath: '/pengaturan/notifikasi'
|
||||||
preLoaderRoute: typeof DashboardPengaduanLayananPublikRouteImport
|
preLoaderRoute: typeof PengaturanNotifikasiRouteImport
|
||||||
parentRoute: typeof DashboardRouteRoute
|
parentRoute: typeof PengaturanRouteRoute
|
||||||
}
|
}
|
||||||
'/dashboard/kinerja-divisi': {
|
'/pengaturan/keamanan': {
|
||||||
id: '/dashboard/kinerja-divisi'
|
id: '/pengaturan/keamanan'
|
||||||
path: '/kinerja-divisi'
|
|
||||||
fullPath: '/dashboard/kinerja-divisi'
|
|
||||||
preLoaderRoute: typeof DashboardKinerjaDivisiRouteImport
|
|
||||||
parentRoute: typeof DashboardRouteRoute
|
|
||||||
}
|
|
||||||
'/dashboard/keuangan-anggaran': {
|
|
||||||
id: '/dashboard/keuangan-anggaran'
|
|
||||||
path: '/keuangan-anggaran'
|
|
||||||
fullPath: '/dashboard/keuangan-anggaran'
|
|
||||||
preLoaderRoute: typeof DashboardKeuanganAnggaranRouteImport
|
|
||||||
parentRoute: typeof DashboardRouteRoute
|
|
||||||
}
|
|
||||||
'/dashboard/keamanan': {
|
|
||||||
id: '/dashboard/keamanan'
|
|
||||||
path: '/keamanan'
|
path: '/keamanan'
|
||||||
fullPath: '/dashboard/keamanan'
|
fullPath: '/pengaturan/keamanan'
|
||||||
preLoaderRoute: typeof DashboardKeamananRouteImport
|
preLoaderRoute: typeof PengaturanKeamananRouteImport
|
||||||
parentRoute: typeof DashboardRouteRoute
|
parentRoute: typeof PengaturanRouteRoute
|
||||||
}
|
}
|
||||||
'/dashboard/jenna-analytic': {
|
'/pengaturan/akses-dan-tim': {
|
||||||
id: '/dashboard/jenna-analytic'
|
id: '/pengaturan/akses-dan-tim'
|
||||||
path: '/jenna-analytic'
|
path: '/akses-dan-tim'
|
||||||
fullPath: '/dashboard/jenna-analytic'
|
fullPath: '/pengaturan/akses-dan-tim'
|
||||||
preLoaderRoute: typeof DashboardJennaAnalyticRouteImport
|
preLoaderRoute: typeof PengaturanAksesDanTimRouteImport
|
||||||
parentRoute: typeof DashboardRouteRoute
|
parentRoute: typeof PengaturanRouteRoute
|
||||||
}
|
|
||||||
'/dashboard/demografi-pekerjaan': {
|
|
||||||
id: '/dashboard/demografi-pekerjaan'
|
|
||||||
path: '/demografi-pekerjaan'
|
|
||||||
fullPath: '/dashboard/demografi-pekerjaan'
|
|
||||||
preLoaderRoute: typeof DashboardDemografiPekerjaanRouteImport
|
|
||||||
parentRoute: typeof DashboardRouteRoute
|
|
||||||
}
|
|
||||||
'/dashboard/bumdes': {
|
|
||||||
id: '/dashboard/bumdes'
|
|
||||||
path: '/bumdes'
|
|
||||||
fullPath: '/dashboard/bumdes'
|
|
||||||
preLoaderRoute: typeof DashboardBumdesRouteImport
|
|
||||||
parentRoute: typeof DashboardRouteRoute
|
|
||||||
}
|
|
||||||
'/dashboard/bantuan': {
|
|
||||||
id: '/dashboard/bantuan'
|
|
||||||
path: '/bantuan'
|
|
||||||
fullPath: '/dashboard/bantuan'
|
|
||||||
preLoaderRoute: typeof DashboardBantuanRouteImport
|
|
||||||
parentRoute: typeof DashboardRouteRoute
|
|
||||||
}
|
}
|
||||||
'/admin/users': {
|
'/admin/users': {
|
||||||
id: '/admin/users'
|
id: '/admin/users'
|
||||||
@@ -541,41 +542,6 @@ declare module '@tanstack/react-router' {
|
|||||||
preLoaderRoute: typeof AdminApikeyRouteImport
|
preLoaderRoute: typeof AdminApikeyRouteImport
|
||||||
parentRoute: typeof AdminRouteRoute
|
parentRoute: typeof AdminRouteRoute
|
||||||
}
|
}
|
||||||
'/dashboard/pengaturan': {
|
|
||||||
id: '/dashboard/pengaturan'
|
|
||||||
path: '/pengaturan'
|
|
||||||
fullPath: '/dashboard/pengaturan'
|
|
||||||
preLoaderRoute: typeof DashboardPengaturanRouteRouteImport
|
|
||||||
parentRoute: typeof DashboardRouteRoute
|
|
||||||
}
|
|
||||||
'/dashboard/pengaturan/umum': {
|
|
||||||
id: '/dashboard/pengaturan/umum'
|
|
||||||
path: '/umum'
|
|
||||||
fullPath: '/dashboard/pengaturan/umum'
|
|
||||||
preLoaderRoute: typeof DashboardPengaturanUmumRouteImport
|
|
||||||
parentRoute: typeof DashboardPengaturanRouteRoute
|
|
||||||
}
|
|
||||||
'/dashboard/pengaturan/notifikasi': {
|
|
||||||
id: '/dashboard/pengaturan/notifikasi'
|
|
||||||
path: '/notifikasi'
|
|
||||||
fullPath: '/dashboard/pengaturan/notifikasi'
|
|
||||||
preLoaderRoute: typeof DashboardPengaturanNotifikasiRouteImport
|
|
||||||
parentRoute: typeof DashboardPengaturanRouteRoute
|
|
||||||
}
|
|
||||||
'/dashboard/pengaturan/keamanan': {
|
|
||||||
id: '/dashboard/pengaturan/keamanan'
|
|
||||||
path: '/keamanan'
|
|
||||||
fullPath: '/dashboard/pengaturan/keamanan'
|
|
||||||
preLoaderRoute: typeof DashboardPengaturanKeamananRouteImport
|
|
||||||
parentRoute: typeof DashboardPengaturanRouteRoute
|
|
||||||
}
|
|
||||||
'/dashboard/pengaturan/akses-dan-tim': {
|
|
||||||
id: '/dashboard/pengaturan/akses-dan-tim'
|
|
||||||
path: '/akses-dan-tim'
|
|
||||||
fullPath: '/dashboard/pengaturan/akses-dan-tim'
|
|
||||||
preLoaderRoute: typeof DashboardPengaturanAksesDanTimRouteImport
|
|
||||||
parentRoute: typeof DashboardPengaturanRouteRoute
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -597,64 +563,39 @@ const AdminRouteRouteWithChildren = AdminRouteRoute._addFileChildren(
|
|||||||
AdminRouteRouteChildren,
|
AdminRouteRouteChildren,
|
||||||
)
|
)
|
||||||
|
|
||||||
interface DashboardPengaturanRouteRouteChildren {
|
interface PengaturanRouteRouteChildren {
|
||||||
DashboardPengaturanAksesDanTimRoute: typeof DashboardPengaturanAksesDanTimRoute
|
PengaturanAksesDanTimRoute: typeof PengaturanAksesDanTimRoute
|
||||||
DashboardPengaturanKeamananRoute: typeof DashboardPengaturanKeamananRoute
|
PengaturanKeamananRoute: typeof PengaturanKeamananRoute
|
||||||
DashboardPengaturanNotifikasiRoute: typeof DashboardPengaturanNotifikasiRoute
|
PengaturanNotifikasiRoute: typeof PengaturanNotifikasiRoute
|
||||||
DashboardPengaturanUmumRoute: typeof DashboardPengaturanUmumRoute
|
PengaturanUmumRoute: typeof PengaturanUmumRoute
|
||||||
}
|
}
|
||||||
|
|
||||||
const DashboardPengaturanRouteRouteChildren: DashboardPengaturanRouteRouteChildren =
|
const PengaturanRouteRouteChildren: PengaturanRouteRouteChildren = {
|
||||||
{
|
PengaturanAksesDanTimRoute: PengaturanAksesDanTimRoute,
|
||||||
DashboardPengaturanAksesDanTimRoute: DashboardPengaturanAksesDanTimRoute,
|
PengaturanKeamananRoute: PengaturanKeamananRoute,
|
||||||
DashboardPengaturanKeamananRoute: DashboardPengaturanKeamananRoute,
|
PengaturanNotifikasiRoute: PengaturanNotifikasiRoute,
|
||||||
DashboardPengaturanNotifikasiRoute: DashboardPengaturanNotifikasiRoute,
|
PengaturanUmumRoute: PengaturanUmumRoute,
|
||||||
DashboardPengaturanUmumRoute: DashboardPengaturanUmumRoute,
|
|
||||||
}
|
|
||||||
|
|
||||||
const DashboardPengaturanRouteRouteWithChildren =
|
|
||||||
DashboardPengaturanRouteRoute._addFileChildren(
|
|
||||||
DashboardPengaturanRouteRouteChildren,
|
|
||||||
)
|
|
||||||
|
|
||||||
interface DashboardRouteRouteChildren {
|
|
||||||
DashboardPengaturanRouteRoute: typeof DashboardPengaturanRouteRouteWithChildren
|
|
||||||
DashboardBantuanRoute: typeof DashboardBantuanRoute
|
|
||||||
DashboardBumdesRoute: typeof DashboardBumdesRoute
|
|
||||||
DashboardDemografiPekerjaanRoute: typeof DashboardDemografiPekerjaanRoute
|
|
||||||
DashboardJennaAnalyticRoute: typeof DashboardJennaAnalyticRoute
|
|
||||||
DashboardKeamananRoute: typeof DashboardKeamananRoute
|
|
||||||
DashboardKeuanganAnggaranRoute: typeof DashboardKeuanganAnggaranRoute
|
|
||||||
DashboardKinerjaDivisiRoute: typeof DashboardKinerjaDivisiRoute
|
|
||||||
DashboardPengaduanLayananPublikRoute: typeof DashboardPengaduanLayananPublikRoute
|
|
||||||
DashboardSosialRoute: typeof DashboardSosialRoute
|
|
||||||
DashboardIndexRoute: typeof DashboardIndexRoute
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const DashboardRouteRouteChildren: DashboardRouteRouteChildren = {
|
const PengaturanRouteRouteWithChildren = PengaturanRouteRoute._addFileChildren(
|
||||||
DashboardPengaturanRouteRoute: DashboardPengaturanRouteRouteWithChildren,
|
PengaturanRouteRouteChildren,
|
||||||
DashboardBantuanRoute: DashboardBantuanRoute,
|
|
||||||
DashboardBumdesRoute: DashboardBumdesRoute,
|
|
||||||
DashboardDemografiPekerjaanRoute: DashboardDemografiPekerjaanRoute,
|
|
||||||
DashboardJennaAnalyticRoute: DashboardJennaAnalyticRoute,
|
|
||||||
DashboardKeamananRoute: DashboardKeamananRoute,
|
|
||||||
DashboardKeuanganAnggaranRoute: DashboardKeuanganAnggaranRoute,
|
|
||||||
DashboardKinerjaDivisiRoute: DashboardKinerjaDivisiRoute,
|
|
||||||
DashboardPengaduanLayananPublikRoute: DashboardPengaduanLayananPublikRoute,
|
|
||||||
DashboardSosialRoute: DashboardSosialRoute,
|
|
||||||
DashboardIndexRoute: DashboardIndexRoute,
|
|
||||||
}
|
|
||||||
|
|
||||||
const DashboardRouteRouteWithChildren = DashboardRouteRoute._addFileChildren(
|
|
||||||
DashboardRouteRouteChildren,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const rootRouteChildren: RootRouteChildren = {
|
const rootRouteChildren: RootRouteChildren = {
|
||||||
IndexRoute: IndexRoute,
|
IndexRoute: IndexRoute,
|
||||||
AdminRouteRoute: AdminRouteRouteWithChildren,
|
AdminRouteRoute: AdminRouteRouteWithChildren,
|
||||||
DashboardRouteRoute: DashboardRouteRouteWithChildren,
|
PengaturanRouteRoute: PengaturanRouteRouteWithChildren,
|
||||||
|
BantuanRoute: BantuanRoute,
|
||||||
|
BumdesRoute: BumdesRoute,
|
||||||
|
DemografiPekerjaanRoute: DemografiPekerjaanRoute,
|
||||||
|
JennaAnalyticRoute: JennaAnalyticRoute,
|
||||||
|
KeamananRoute: KeamananRoute,
|
||||||
|
KeuanganAnggaranRoute: KeuanganAnggaranRoute,
|
||||||
|
KinerjaDivisiRoute: KinerjaDivisiRoute,
|
||||||
|
PengaduanLayananPublikRoute: PengaduanLayananPublikRoute,
|
||||||
SigninRoute: SigninRoute,
|
SigninRoute: SigninRoute,
|
||||||
SignupRoute: SignupRoute,
|
SignupRoute: SignupRoute,
|
||||||
|
SosialRoute: SosialRoute,
|
||||||
ProfileEditRoute: ProfileEditRoute,
|
ProfileEditRoute: ProfileEditRoute,
|
||||||
UsersIdRoute: UsersIdRoute,
|
UsersIdRoute: UsersIdRoute,
|
||||||
ProfileIndexRoute: ProfileIndexRoute,
|
ProfileIndexRoute: ProfileIndexRoute,
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
/** biome-ignore-all lint/suspicious/noExplicitAny: <explanation */
|
/** biome-ignore-all lint/suspicious/noExplicitAny: <explanation */
|
||||||
|
import { protectedRouteMiddleware } from "@/middleware/authMiddleware";
|
||||||
import { authStore } from "@/store/auth";
|
import { authStore } from "@/store/auth";
|
||||||
import "@mantine/core/styles.css";
|
import "@mantine/core/styles.css";
|
||||||
import "@mantine/dates/styles.css";
|
import "@mantine/dates/styles.css";
|
||||||
@@ -6,25 +7,25 @@ import { createRootRoute, Outlet } from "@tanstack/react-router";
|
|||||||
|
|
||||||
export const Route = createRootRoute({
|
export const Route = createRootRoute({
|
||||||
component: RootComponent,
|
component: RootComponent,
|
||||||
beforeLoad: async () => {
|
beforeLoad: async ({ location }) => {
|
||||||
// Fetch session but don't block navigation
|
// Only apply auth middleware for routes that need it
|
||||||
try {
|
// Public routes: /, /signin, /signup
|
||||||
const res = await fetch("/api/session", {
|
const isPublicRoute =
|
||||||
method: "GET",
|
location.pathname === "/" ||
|
||||||
credentials: "include",
|
location.pathname === "/signin" ||
|
||||||
});
|
location.pathname === "/signup";
|
||||||
if (res.ok) {
|
|
||||||
const { data } = await res.json();
|
if (isPublicRoute) {
|
||||||
authStore.user = data?.user;
|
return;
|
||||||
authStore.session = data;
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
// Ignore errors, allow public access
|
|
||||||
}
|
}
|
||||||
return {};
|
|
||||||
|
// Apply protected route middleware for all other routes
|
||||||
|
const context = await protectedRouteMiddleware({ location });
|
||||||
|
authStore.user = context?.user as any;
|
||||||
|
authStore.session = context?.session as any;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
function RootComponent() {
|
function RootComponent() {
|
||||||
return <Outlet />;
|
return <Outlet />;
|
||||||
}
|
}
|
||||||
51
src/routes/bantuan.tsx
Normal file
51
src/routes/bantuan.tsx
Normal file
@@ -0,0 +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 HelpPage from "@/components/help-page";
|
||||||
|
import { Sidebar } from "@/components/sidebar";
|
||||||
|
|
||||||
|
export const Route = createFileRoute("/bantuan")({
|
||||||
|
component: BantuanPage,
|
||||||
|
});
|
||||||
|
|
||||||
|
function BantuanPage() {
|
||||||
|
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}>
|
||||||
|
<HelpPage />
|
||||||
|
</AppShell.Main>
|
||||||
|
</AppShell>
|
||||||
|
);
|
||||||
|
}
|
||||||
9
src/routes/bumdes.tsx
Normal file
9
src/routes/bumdes.tsx
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
|
|
||||||
|
export const Route = createFileRoute("/bumdes")({
|
||||||
|
component: RouteComponent,
|
||||||
|
});
|
||||||
|
|
||||||
|
function RouteComponent() {
|
||||||
|
return <div>Hello "/bumdes"!</div>;
|
||||||
|
}
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
import { createFileRoute } from "@tanstack/react-router";
|
|
||||||
import HelpPage from "@/components/help-page";
|
|
||||||
|
|
||||||
export const Route = createFileRoute("/dashboard/bantuan")({
|
|
||||||
component: HelpPage,
|
|
||||||
});
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
import { createFileRoute } from "@tanstack/react-router";
|
|
||||||
import BumdesPage from "@/components/bumdes-page";
|
|
||||||
|
|
||||||
export const Route = createFileRoute("/dashboard/bumdes")({
|
|
||||||
component: BumdesPage,
|
|
||||||
});
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
import { createFileRoute } from "@tanstack/react-router";
|
|
||||||
import DemografiPekerjaan from "../../components/demografi-pekerjaan";
|
|
||||||
|
|
||||||
export const Route = createFileRoute("/dashboard/demografi-pekerjaan")({
|
|
||||||
component: DemografiPekerjaan,
|
|
||||||
});
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
import { createFileRoute } from "@tanstack/react-router";
|
|
||||||
import { DashboardContent } from "@/components/dashboard-content";
|
|
||||||
export const Route = createFileRoute("/dashboard/")({
|
|
||||||
component: DashboardContent,
|
|
||||||
});
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
import { createFileRoute } from "@tanstack/react-router";
|
|
||||||
import JennaAnalytic from "@/components/jenna-analytic";
|
|
||||||
|
|
||||||
export const Route = createFileRoute("/dashboard/jenna-analytic")({
|
|
||||||
component: JennaAnalytic,
|
|
||||||
});
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
import { createFileRoute } from "@tanstack/react-router";
|
|
||||||
import KeamananPage from "@/components/keamanan-page";
|
|
||||||
|
|
||||||
export const Route = createFileRoute("/dashboard/keamanan")({
|
|
||||||
component: KeamananPage,
|
|
||||||
});
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
import { createFileRoute } from "@tanstack/react-router";
|
|
||||||
import KeuanganAnggaran from "@/components/keuangan-anggaran";
|
|
||||||
|
|
||||||
export const Route = createFileRoute("/dashboard/keuangan-anggaran")({
|
|
||||||
component: KeuanganAnggaran,
|
|
||||||
});
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
import { createFileRoute } from "@tanstack/react-router";
|
|
||||||
import KinerjaDivisi from "@/components/kinerja-divisi";
|
|
||||||
export const Route = createFileRoute("/dashboard/kinerja-divisi")({
|
|
||||||
component: KinerjaDivisi,
|
|
||||||
});
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
import { createFileRoute } from "@tanstack/react-router";
|
|
||||||
import PengaduanLayananPublik from "@/components/pengaduan-layanan-publik";
|
|
||||||
export const Route = createFileRoute("/dashboard/pengaduan-layanan-publik")({
|
|
||||||
component: PengaduanLayananPublik,
|
|
||||||
});
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
import { createFileRoute } from "@tanstack/react-router";
|
|
||||||
import AksesDanTimSettings from "@/components/pengaturan/akses-dan-tim";
|
|
||||||
|
|
||||||
export const Route = createFileRoute("/dashboard/pengaturan/akses-dan-tim")({
|
|
||||||
component: AksesDanTimSettings,
|
|
||||||
});
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
import { createFileRoute } from "@tanstack/react-router";
|
|
||||||
import KeamananSettings from "@/components/pengaturan/keamanan";
|
|
||||||
|
|
||||||
export const Route = createFileRoute("/dashboard/pengaturan/keamanan")({
|
|
||||||
component: KeamananSettings,
|
|
||||||
});
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
import { createFileRoute } from "@tanstack/react-router";
|
|
||||||
import NotifikasiSettings from "@/components/pengaturan/notifikasi";
|
|
||||||
|
|
||||||
export const Route = createFileRoute("/dashboard/pengaturan/notifikasi")({
|
|
||||||
component: NotifikasiSettings,
|
|
||||||
});
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
import { createFileRoute, Outlet } from "@tanstack/react-router";
|
|
||||||
|
|
||||||
export const Route = createFileRoute("/dashboard/pengaturan")({
|
|
||||||
component: () => (
|
|
||||||
<div className="p-2">
|
|
||||||
<Outlet />
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
});
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
import { createFileRoute } from "@tanstack/react-router";
|
|
||||||
import SocialPage from "@/components/sosial-page";
|
|
||||||
|
|
||||||
export const Route = createFileRoute("/dashboard/sosial")({
|
|
||||||
component: SocialPage,
|
|
||||||
});
|
|
||||||
51
src/routes/demografi-pekerjaan.tsx
Normal file
51
src/routes/demografi-pekerjaan.tsx
Normal file
@@ -0,0 +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 { Sidebar } from "@/components/sidebar";
|
||||||
|
import DemografiPekerjaan from "../components/demografi-pekerjaan";
|
||||||
|
|
||||||
|
export const Route = createFileRoute("/demografi-pekerjaan")({
|
||||||
|
component: DemografiPekerjaanPage,
|
||||||
|
});
|
||||||
|
|
||||||
|
function DemografiPekerjaanPage() {
|
||||||
|
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}>
|
||||||
|
<DemografiPekerjaan />
|
||||||
|
</AppShell.Main>
|
||||||
|
</AppShell>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,7 +1,51 @@
|
|||||||
import { createFileRoute, redirect } from "@tanstack/react-router";
|
import { AppShell, Burger, Group, useMantineColorScheme } from "@mantine/core";
|
||||||
|
import { useDisclosure } from "@mantine/hooks";
|
||||||
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
|
import { DashboardContent } from "@/components/dashboard-content";
|
||||||
|
import { Header } from "@/components/header";
|
||||||
|
import { Sidebar } from "@/components/sidebar";
|
||||||
|
|
||||||
export const Route = createFileRoute("/")({
|
export const Route = createFileRoute("/")({
|
||||||
beforeLoad: () => {
|
component: DashboardPage,
|
||||||
throw redirect({ to: "/dashboard" });
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function DashboardPage() {
|
||||||
|
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}>
|
||||||
|
<DashboardContent />
|
||||||
|
</AppShell.Main>
|
||||||
|
</AppShell>
|
||||||
|
);
|
||||||
|
}
|
||||||
9
src/routes/jenna-analytic.tsx
Normal file
9
src/routes/jenna-analytic.tsx
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
|
|
||||||
|
export const Route = createFileRoute("/jenna-analytic")({
|
||||||
|
component: RouteComponent,
|
||||||
|
});
|
||||||
|
|
||||||
|
function RouteComponent() {
|
||||||
|
return <div>Hello "/jenna-analytic"!</div>;
|
||||||
|
}
|
||||||
9
src/routes/keamanan.tsx
Normal file
9
src/routes/keamanan.tsx
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
|
|
||||||
|
export const Route = createFileRoute("/keamanan")({
|
||||||
|
component: RouteComponent,
|
||||||
|
});
|
||||||
|
|
||||||
|
function RouteComponent() {
|
||||||
|
return <div>Hello "/keamanan"!</div>;
|
||||||
|
}
|
||||||
51
src/routes/keuangan-anggaran.tsx
Normal file
51
src/routes/keuangan-anggaran.tsx
Normal file
@@ -0,0 +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 KeuanganAnggaran from "@/components/keuangan-anggaran";
|
||||||
|
import { Sidebar } from "@/components/sidebar";
|
||||||
|
|
||||||
|
export const Route = createFileRoute("/keuangan-anggaran")({
|
||||||
|
component: KeuanganAnggaranPage,
|
||||||
|
});
|
||||||
|
|
||||||
|
function KeuanganAnggaranPage() {
|
||||||
|
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}>
|
||||||
|
<KeuanganAnggaran />
|
||||||
|
</AppShell.Main>
|
||||||
|
</AppShell>
|
||||||
|
);
|
||||||
|
}
|
||||||
51
src/routes/kinerja-divisi.tsx
Normal file
51
src/routes/kinerja-divisi.tsx
Normal file
@@ -0,0 +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 KinerjaDivisi from "@/components/kinerja-divisi";
|
||||||
|
import { Sidebar } from "@/components/sidebar";
|
||||||
|
|
||||||
|
export const Route = createFileRoute("/kinerja-divisi")({
|
||||||
|
component: KinerjaDivisiPage,
|
||||||
|
});
|
||||||
|
|
||||||
|
function KinerjaDivisiPage() {
|
||||||
|
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}>
|
||||||
|
<KinerjaDivisi />
|
||||||
|
</AppShell.Main>
|
||||||
|
</AppShell>
|
||||||
|
);
|
||||||
|
}
|
||||||
51
src/routes/pengaduan-layanan-publik.tsx
Normal file
51
src/routes/pengaduan-layanan-publik.tsx
Normal file
@@ -0,0 +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 PengaduanLayananPublik from "@/components/pengaduan-layanan-publik";
|
||||||
|
import { Sidebar } from "@/components/sidebar";
|
||||||
|
|
||||||
|
export const Route = createFileRoute("/pengaduan-layanan-publik")({
|
||||||
|
component: PengaduanLayananPublikPage,
|
||||||
|
});
|
||||||
|
|
||||||
|
function PengaduanLayananPublikPage() {
|
||||||
|
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}>
|
||||||
|
<PengaduanLayananPublik />
|
||||||
|
</AppShell.Main>
|
||||||
|
</AppShell>
|
||||||
|
);
|
||||||
|
}
|
||||||
9
src/routes/pengaturan/akses-dan-tim.tsx
Normal file
9
src/routes/pengaturan/akses-dan-tim.tsx
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
|
|
||||||
|
export const Route = createFileRoute("/pengaturan/akses-dan-tim")({
|
||||||
|
component: RouteComponent,
|
||||||
|
});
|
||||||
|
|
||||||
|
function RouteComponent() {
|
||||||
|
return <div>Hello "/pengaturan/akses-dan-tim"!</div>;
|
||||||
|
}
|
||||||
9
src/routes/pengaturan/keamanan.tsx
Normal file
9
src/routes/pengaturan/keamanan.tsx
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
|
|
||||||
|
export const Route = createFileRoute("/pengaturan/keamanan")({
|
||||||
|
component: RouteComponent,
|
||||||
|
});
|
||||||
|
|
||||||
|
function RouteComponent() {
|
||||||
|
return <div>Hello "/pengaturan/keamanan"!</div>;
|
||||||
|
}
|
||||||
9
src/routes/pengaturan/notifikasi.tsx
Normal file
9
src/routes/pengaturan/notifikasi.tsx
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
|
|
||||||
|
export const Route = createFileRoute("/pengaturan/notifikasi")({
|
||||||
|
component: RouteComponent,
|
||||||
|
});
|
||||||
|
|
||||||
|
function RouteComponent() {
|
||||||
|
return <div>Hello "/pengaturan/notifikasi"!</div>;
|
||||||
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
<<<<<<< HEAD:src/routes/dashboard/route.tsx
|
||||||
import {
|
import {
|
||||||
AppShell,
|
AppShell,
|
||||||
Burger,
|
Burger,
|
||||||
@@ -8,13 +9,19 @@ import {
|
|||||||
import { useDisclosure, useMediaQuery } from "@mantine/hooks";
|
import { useDisclosure, useMediaQuery } from "@mantine/hooks";
|
||||||
import { createFileRoute, Outlet, useRouterState } from "@tanstack/react-router";
|
import { createFileRoute, Outlet, useRouterState } from "@tanstack/react-router";
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
|
=======
|
||||||
|
import { AppShell, Burger, Group, useMantineColorScheme } from "@mantine/core";
|
||||||
|
import { useDisclosure } from "@mantine/hooks";
|
||||||
|
import { createFileRoute, Outlet } from "@tanstack/react-router";
|
||||||
|
>>>>>>> 89c8ca8 (fix: make dashboard public and remove admin-only restriction from main pages):src/routes/pengaturan/route.tsx
|
||||||
import { Header } from "@/components/header";
|
import { Header } from "@/components/header";
|
||||||
import { Sidebar } from "@/components/sidebar";
|
import { Sidebar } from "@/components/sidebar";
|
||||||
|
|
||||||
export const Route = createFileRoute("/dashboard")({
|
export const Route = createFileRoute("/pengaturan")({
|
||||||
component: RouteComponent,
|
component: PengaturanLayout,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
<<<<<<< HEAD:src/routes/dashboard/route.tsx
|
||||||
function RouteComponent() {
|
function RouteComponent() {
|
||||||
const [opened, { toggle, close }] = useDisclosure();
|
const [opened, { toggle, close }] = useDisclosure();
|
||||||
const { colorScheme } = useMantineColorScheme();
|
const { colorScheme } = useMantineColorScheme();
|
||||||
@@ -23,10 +30,16 @@ function RouteComponent() {
|
|||||||
|
|
||||||
const isMobile = useMediaQuery("(max-width: 48em)");
|
const isMobile = useMediaQuery("(max-width: 48em)");
|
||||||
|
|
||||||
|
=======
|
||||||
|
function PengaturanLayout() {
|
||||||
|
const [opened, { toggle }] = useDisclosure();
|
||||||
|
const { colorScheme } = useMantineColorScheme();
|
||||||
|
>>>>>>> 89c8ca8 (fix: make dashboard public and remove admin-only restriction from main pages):src/routes/pengaturan/route.tsx
|
||||||
const headerBgColor = colorScheme === "dark" ? "#11192D" : "#19355E";
|
const headerBgColor = colorScheme === "dark" ? "#11192D" : "#19355E";
|
||||||
const navbarBgColor = colorScheme === "dark" ? "#11192D" : "white";
|
const navbarBgColor = colorScheme === "dark" ? "#11192D" : "white";
|
||||||
const mainBgColor = colorScheme === "dark" ? "#11192D" : "#edf3f8ff";
|
const mainBgColor = colorScheme === "dark" ? "#11192D" : "#edf3f8ff";
|
||||||
|
|
||||||
|
<<<<<<< HEAD:src/routes/dashboard/route.tsx
|
||||||
// ✅ AUTO CLOSE NAVBAR ON ROUTE CHANGE (MOBILE ONLY)
|
// ✅ AUTO CLOSE NAVBAR ON ROUTE CHANGE (MOBILE ONLY)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isMobile && opened) {
|
if (isMobile && opened) {
|
||||||
@@ -34,6 +47,8 @@ function RouteComponent() {
|
|||||||
}
|
}
|
||||||
}, [routerState.location.pathname]);
|
}, [routerState.location.pathname]);
|
||||||
|
|
||||||
|
=======
|
||||||
|
>>>>>>> 89c8ca8 (fix: make dashboard public and remove admin-only restriction from main pages):src/routes/pengaturan/route.tsx
|
||||||
return (
|
return (
|
||||||
<AppShell
|
<AppShell
|
||||||
header={{ height: 60 }}
|
header={{ height: 60 }}
|
||||||
@@ -73,7 +88,9 @@ function RouteComponent() {
|
|||||||
</AppShell.Navbar>
|
</AppShell.Navbar>
|
||||||
|
|
||||||
<AppShell.Main bg={mainBgColor}>
|
<AppShell.Main bg={mainBgColor}>
|
||||||
<Outlet />
|
<div className="p-2">
|
||||||
|
<Outlet />
|
||||||
|
</div>
|
||||||
</AppShell.Main>
|
</AppShell.Main>
|
||||||
</AppShell>
|
</AppShell>
|
||||||
);
|
);
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { createFileRoute } from "@tanstack/react-router";
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
import UmumSettings from "@/components/pengaturan/umum";
|
import UmumSettings from "@/components/pengaturan/umum";
|
||||||
|
|
||||||
export const Route = createFileRoute("/dashboard/pengaturan/umum")({
|
export const Route = createFileRoute("/pengaturan/umum")({
|
||||||
component: UmumSettings,
|
component: UmumSettings,
|
||||||
});
|
});
|
||||||
9
src/routes/sosial.tsx
Normal file
9
src/routes/sosial.tsx
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
|
|
||||||
|
export const Route = createFileRoute("/sosial")({
|
||||||
|
component: RouteComponent,
|
||||||
|
});
|
||||||
|
|
||||||
|
function RouteComponent() {
|
||||||
|
return <div>Hello "/sosial"!</div>;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user