feat: update components with Mantine UI and improve dark mode support
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
This commit is contained in:
303
src/components/bumdes-page.tsx
Normal file
303
src/components/bumdes-page.tsx
Normal file
@@ -0,0 +1,303 @@
|
||||
import { useState } from "react";
|
||||
import {
|
||||
Card,
|
||||
Grid,
|
||||
GridCol,
|
||||
Group,
|
||||
Text,
|
||||
Title,
|
||||
Button,
|
||||
Badge,
|
||||
Table,
|
||||
Stack,
|
||||
Select,
|
||||
useMantineColorScheme
|
||||
} from "@mantine/core";
|
||||
import { IconBuildingStore, IconCategory, IconCurrency, IconUsers } from "@tabler/icons-react";
|
||||
|
||||
const BumdesPage = () => {
|
||||
const { colorScheme } = useMantineColorScheme();
|
||||
const dark = colorScheme === 'dark';
|
||||
|
||||
const [timeFilter, setTimeFilter] = useState<string>("bulan");
|
||||
|
||||
// Sample data for KPI cards
|
||||
const kpiData = [
|
||||
{
|
||||
title: "UMKM Aktif",
|
||||
value: 45,
|
||||
icon: <IconUsers size={24} />,
|
||||
color: "darmasaba-blue",
|
||||
},
|
||||
{
|
||||
title: "UMKM Terdaftar",
|
||||
value: 68,
|
||||
icon: <IconBuildingStore size={24} />,
|
||||
color: "darmasaba-success",
|
||||
},
|
||||
{
|
||||
title: "Omzet",
|
||||
value: "Rp 48.000.000",
|
||||
icon: <IconCurrency size={24} />,
|
||||
color: "darmasaba-warning",
|
||||
},
|
||||
{
|
||||
title: "Kategori UMKM",
|
||||
value: 34,
|
||||
icon: <IconCategory size={24} />,
|
||||
color: "darmasaba-danger",
|
||||
},
|
||||
];
|
||||
|
||||
// Sample data for top products
|
||||
const topProducts = [
|
||||
{
|
||||
rank: 1,
|
||||
name: "Beras Premium Organik",
|
||||
umkmOwner: "Warung Pak Joko",
|
||||
growth: "+12%",
|
||||
},
|
||||
{
|
||||
rank: 2,
|
||||
name: "Keripik Singkong",
|
||||
umkmOwner: "Ibu Sari Snack",
|
||||
growth: "+8%",
|
||||
},
|
||||
{
|
||||
rank: 3,
|
||||
name: "Madu Alami",
|
||||
umkmOwner: "Peternakan Lebah",
|
||||
growth: "+5%",
|
||||
},
|
||||
];
|
||||
|
||||
// Sample data for product sales
|
||||
const productSales = [
|
||||
{
|
||||
produk: "Beras Premium Organik",
|
||||
penjualanBulanIni: "Rp 8.500.000",
|
||||
bulanLalu: "Rp 8.500.000",
|
||||
trend: 10,
|
||||
volume: "650 Kg",
|
||||
stok: "850 Kg",
|
||||
},
|
||||
{
|
||||
produk: "Keripik Singkong",
|
||||
penjualanBulanIni: "Rp 4.200.000",
|
||||
bulanLalu: "Rp 3.800.000",
|
||||
trend: 10,
|
||||
volume: "320 Kg",
|
||||
stok: "120 Kg",
|
||||
},
|
||||
{
|
||||
produk: "Madu Alami",
|
||||
penjualanBulanIni: "Rp 3.750.000",
|
||||
bulanLalu: "Rp 4.100.000",
|
||||
trend: -8,
|
||||
volume: "150 Liter",
|
||||
stok: "45 Liter",
|
||||
},
|
||||
{
|
||||
produk: "Kecap Tradisional",
|
||||
penjualanBulanIni: "Rp 2.800.000",
|
||||
bulanLalu: "Rp 2.500.000",
|
||||
trend: 12,
|
||||
volume: "280 Botol",
|
||||
stok: "95 Botol",
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<Stack gap="lg">
|
||||
{/* Page Header */}
|
||||
<Group justify="space-between" align="center">
|
||||
<Title order={2} c={dark ? "dark.0" : "black"}>
|
||||
BUMDes & UMKM Desa
|
||||
</Title>
|
||||
</Group>
|
||||
|
||||
{/* KPI Cards */}
|
||||
<Grid gutter="md">
|
||||
{kpiData.map((kpi, index) => (
|
||||
<GridCol key={index} span={{ base: 12, sm: 6, md: 3 }}>
|
||||
<Card withBorder radius="md" padding="lg">
|
||||
<Group justify="space-between" align="center">
|
||||
<Stack gap={0}>
|
||||
<Text size="sm" c={dark ? "dark.3" : "dimmed"}>
|
||||
{kpi.title}
|
||||
</Text>
|
||||
<Text size="xl" fw={700} c={dark ? "dark.0" : "black"}>
|
||||
{typeof kpi.value === 'number' ? kpi.value.toLocaleString() : kpi.value}
|
||||
</Text>
|
||||
</Stack>
|
||||
<Badge
|
||||
variant="light"
|
||||
color={kpi.color}
|
||||
p={8}
|
||||
radius="md"
|
||||
>
|
||||
{kpi.icon}
|
||||
</Badge>
|
||||
</Group>
|
||||
</Card>
|
||||
</GridCol>
|
||||
))}
|
||||
</Grid>
|
||||
|
||||
{/* Update Penjualan Produk Header */}
|
||||
<Card withBorder radius="md" bg={dark ? "dark.8" : "darmasaba-blue.0"}>
|
||||
<Group justify="space-between" align="center" px="md" py="xs">
|
||||
<Title order={3} c={dark ? "dark.0" : "black"}>
|
||||
Update Penjualan Produk
|
||||
</Title>
|
||||
<Group>
|
||||
<Button
|
||||
variant={timeFilter === "minggu" ? "filled" : "light"}
|
||||
onClick={() => setTimeFilter("minggu")}
|
||||
color="darmasaba-blue"
|
||||
>
|
||||
Minggu ini
|
||||
</Button>
|
||||
<Button
|
||||
variant={timeFilter === "bulan" ? "filled" : "light"}
|
||||
onClick={() => setTimeFilter("bulan")}
|
||||
color="darmasaba-blue"
|
||||
>
|
||||
Bulan ini
|
||||
</Button>
|
||||
</Group>
|
||||
</Group>
|
||||
</Card>
|
||||
|
||||
<Grid gutter="md">
|
||||
{/* Produk Unggulan (Left Column) */}
|
||||
<GridCol span={{ base: 12, lg: 4 }}>
|
||||
<Stack gap="md">
|
||||
{/* Total Penjualan, Produk Aktif, Total Transaksi */}
|
||||
<Card withBorder radius="md" p="lg">
|
||||
<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 withBorder radius="md" p="lg">
|
||||
<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">
|
||||
<Group gap="sm">
|
||||
<Badge
|
||||
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"}
|
||||
>
|
||||
{product.growth}
|
||||
</Badge>
|
||||
</Group>
|
||||
))}
|
||||
</Stack>
|
||||
</Card>
|
||||
</Stack>
|
||||
</GridCol>
|
||||
|
||||
{/* Detail Penjualan Produk (Right Column) */}
|
||||
<GridCol span={{ base: 12, lg: 8 }}>
|
||||
<Card withBorder radius="md" p="lg">
|
||||
<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 ? "dark.3" : "dimmed"}>Produk</Text></Table.Th>
|
||||
<Table.Th><Text c={dark ? "dark.3" : "dimmed"}>Penjualan Bulan Ini</Text></Table.Th>
|
||||
<Table.Th><Text c={dark ? "dark.3" : "dimmed"}>Bulan Lalu</Text></Table.Th>
|
||||
<Table.Th><Text c={dark ? "dark.3" : "dimmed"}>Trend</Text></Table.Th>
|
||||
<Table.Th><Text c={dark ? "dark.3" : "dimmed"}>Volume</Text></Table.Th>
|
||||
<Table.Th><Text c={dark ? "dark.3" : "dimmed"}>Stok</Text></Table.Th>
|
||||
<Table.Th><Text c={dark ? "dark.3" : "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 ? "dark.3" : "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"}
|
||||
>
|
||||
{product.stok}
|
||||
</Badge>
|
||||
</Table.Td>
|
||||
<Table.Td>
|
||||
<Button variant="subtle" size="compact-sm" color="darmasaba-blue">
|
||||
Detail
|
||||
</Button>
|
||||
</Table.Td>
|
||||
</Table.Tr>
|
||||
))}
|
||||
</Table.Tbody>
|
||||
</Table>
|
||||
</Card>
|
||||
</GridCol>
|
||||
</Grid>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export default BumdesPage;
|
||||
@@ -74,7 +74,7 @@ export function DashboardContent() {
|
||||
{/* Stats Cards */}
|
||||
<Grid gutter="md">
|
||||
<Grid.Col span={{ base: 12, md: 6, lg: 3 }}>
|
||||
<Card p="md" radius="md" h="100%" withBorder>
|
||||
<Card p="md" style={{borderColor: dark ? "#141D34" : "white"}} radius="md" h="100%" withBorder bg={dark ? "#141D34" : "white"}>
|
||||
<Group justify="space-between" align="flex-start" w="100%">
|
||||
<Box style={{ flex: 1 }}>
|
||||
<Text size="sm" c="dimmed" mb="xs">
|
||||
@@ -99,7 +99,7 @@ export function DashboardContent() {
|
||||
</Card>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={{ base: 12, md: 6, lg: 3 }}>
|
||||
<Card p="md" radius="md" h="100%" withBorder>
|
||||
<Card p="md" style={{borderColor: dark ? "#141D34" : "white"}} radius="md" h="100%" withBorder bg={dark ? "#141D34" : "white"}>
|
||||
<Group justify="space-between" align="flex-start" w="100%">
|
||||
<Box style={{ flex: 1 }}>
|
||||
<Text size="sm" c="dimmed" mb="xs">
|
||||
@@ -121,7 +121,7 @@ export function DashboardContent() {
|
||||
</Card>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={{ base: 12, md: 6, lg: 3 }}>
|
||||
<Card p="md" radius="md" h="100%" withBorder>
|
||||
<Card p="md" style={{borderColor: dark ? "#141D34" : "white"}} radius="md" h="100%" withBorder bg={dark ? "#141D34" : "white"}>
|
||||
<Group justify="space-between" align="flex-start" w="100%">
|
||||
<Box style={{ flex: 1 }}>
|
||||
<Text size="sm" c="dimmed" mb="xs">
|
||||
@@ -146,7 +146,7 @@ export function DashboardContent() {
|
||||
</Card>
|
||||
</Grid.Col>
|
||||
<Grid.Col span={{ base: 12, md: 6, lg: 3 }}>
|
||||
<Card p="md" radius="md" h="100%" withBorder>
|
||||
<Card p="md" style={{borderColor: dark ? "#141D34" : "white"}} radius="md" h="100%" withBorder bg={dark ? "#141D34" : "white"}>
|
||||
<Group justify="space-between" align="flex-start" w="100%">
|
||||
<Box style={{ flex: 1 }}>
|
||||
<Text size="sm" c="dimmed" mb="xs">
|
||||
@@ -171,7 +171,7 @@ export function DashboardContent() {
|
||||
<Grid gutter="lg">
|
||||
{/* Bar Chart */}
|
||||
<Grid.Col span={{ base: 12, lg: 6 }}>
|
||||
<Card p="md" radius="md" withBorder>
|
||||
<Card p="md" style={{borderColor: dark ? "#141D34" : "white"}} radius="md" withBorder bg={dark ? "#141D34" : "white"}>
|
||||
<Group justify="space-between" mb="md">
|
||||
<Box>
|
||||
<Title order={4} mb={5}>
|
||||
@@ -232,7 +232,7 @@ export function DashboardContent() {
|
||||
|
||||
{/* Pie Chart */}
|
||||
<Grid.Col span={{ base: 12, lg: 6 }}>
|
||||
<Card p="md" radius="md" withBorder>
|
||||
<Card p="md" style={{borderColor: dark ? "#141D34" : "white"}} radius="md" withBorder bg={dark ? "#141D34" : "white"}>
|
||||
<Title order={4} mb={5}>
|
||||
Tingkat Kepuasan
|
||||
</Title>
|
||||
@@ -283,7 +283,7 @@ export function DashboardContent() {
|
||||
<Grid gutter="lg">
|
||||
{/* Divisi Teraktif */}
|
||||
<Grid.Col span={{ base: 12, lg: 6 }}>
|
||||
<Card p="md" radius="md" withBorder>
|
||||
<Card p="md" style={{borderColor: dark ? "#141D34" : "white"}} radius="md" withBorder bg={dark ? "#141D34" : "white"}>
|
||||
<Group gap="xs" mb="lg">
|
||||
<Box>
|
||||
{/* Original SVG icon */}
|
||||
@@ -355,7 +355,7 @@ export function DashboardContent() {
|
||||
|
||||
{/* Kalender */}
|
||||
<Grid.Col span={{ base: 12, lg: 6 }}>
|
||||
<Card p="md" radius="md" withBorder>
|
||||
<Card p="md" style={{borderColor: dark ? "#141D34" : "white"}} radius="md" withBorder bg={dark ? "#141D34" : "white"}>
|
||||
<Group gap="xs" mb="lg">
|
||||
<Calendar style={{ width: 20, height: 20 }} />
|
||||
<Title order={4}>Kalender & Kegiatan Mendatang</Title>
|
||||
@@ -378,7 +378,7 @@ export function DashboardContent() {
|
||||
</Grid>
|
||||
|
||||
{/* APBDes Chart */}
|
||||
<Card p="md" radius="md" withBorder>
|
||||
<Card p="md" style={{borderColor: dark ? "#141D34" : "white"}} radius="md" withBorder bg={dark ? "#141D34" : "white"}>
|
||||
<Title order={4} mb="lg">
|
||||
Grafik APBDes
|
||||
</Title>
|
||||
|
||||
@@ -140,7 +140,7 @@ const DemografiPekerjaan = () => {
|
||||
<Box className="space-y-6">
|
||||
<Stack gap="xl">
|
||||
<Group justify="space-between" align="center">
|
||||
<Title order={1} fw={700}>
|
||||
<Title order={2} fw={700}>
|
||||
Demografi & Kependudukan
|
||||
</Title>
|
||||
<Button variant="filled">Export Data</Button>
|
||||
|
||||
@@ -22,7 +22,7 @@ export function Header() {
|
||||
const getPageTitle = () => {
|
||||
switch (location.pathname) {
|
||||
case "/":
|
||||
return "Dashboard";
|
||||
return "Desa Darmasaba";
|
||||
case "/kinerja-divisi":
|
||||
return "Kinerja Divisi";
|
||||
case "/pengaduan":
|
||||
@@ -43,14 +43,14 @@ export function Header() {
|
||||
case "/pengaturan":
|
||||
return "Pengaturan";
|
||||
default:
|
||||
return "Dashboard";
|
||||
return "Desa Darmasaba";
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Group justify="space-between" w="100%">
|
||||
{/* Title */}
|
||||
<Title order={2}>{getPageTitle()}</Title>
|
||||
<Title order={3} c={"white"}>{getPageTitle()}</Title>
|
||||
|
||||
{/* Right Section */}
|
||||
<Group gap="md">
|
||||
@@ -59,15 +59,15 @@ export function Header() {
|
||||
{/* User Info */}
|
||||
<Group gap="sm">
|
||||
<Box ta="right">
|
||||
<Text size="sm" fw={500}>
|
||||
<Text c={"white"} size="sm" fw={500}>
|
||||
I. B. Surya Prabhawa M...
|
||||
</Text>
|
||||
<Text size="xs" c="dimmed">
|
||||
<Text c={"white"} size="xs">
|
||||
Kepala Desa
|
||||
</Text>
|
||||
</Box>
|
||||
<Avatar color="blue" radius="xl">
|
||||
<UserIcon style={{ width: "70%", height: "70%" }} />
|
||||
<UserIcon color="white" style={{ width: "70%", height: "70%" }} />
|
||||
</Avatar>
|
||||
</Group>
|
||||
|
||||
@@ -84,13 +84,13 @@ export function Header() {
|
||||
aria-label="Toggle color scheme"
|
||||
>
|
||||
{dark ? (
|
||||
<Sun style={{ width: "70%", height: "70%" }} />
|
||||
<Sun color="white" style={{ width: "70%", height: "70%" }} />
|
||||
) : (
|
||||
<Moon style={{ width: "70%", height: "70%" }} />
|
||||
<Moon color="white" style={{ width: "70%", height: "70%" }} />
|
||||
)}
|
||||
</ActionIcon>
|
||||
<ActionIcon variant="subtle" size="lg" radius="xl" pos="relative">
|
||||
<Bell style={{ width: "70%", height: "70%" }} />
|
||||
<Bell color="white" style={{ width: "70%", height: "70%" }} />
|
||||
<Badge
|
||||
size="xs"
|
||||
color="red"
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
Stack,
|
||||
Grid,
|
||||
Box,
|
||||
useMantineColorScheme,
|
||||
} from "@mantine/core";
|
||||
import { BarChart } from "@mantine/charts";
|
||||
|
||||
@@ -133,21 +134,17 @@ const busyHours = [
|
||||
];
|
||||
|
||||
const JennaAnalytic = () => {
|
||||
const { colorScheme } = useMantineColorScheme();
|
||||
const dark = colorScheme === "dark";
|
||||
|
||||
return (
|
||||
<Box className="space-y-6">
|
||||
<Stack gap="xl">
|
||||
<Group justify="space-between" align="center">
|
||||
<Title order={1} fw={700}>
|
||||
Jenna Analytic
|
||||
</Title>
|
||||
<Button variant="filled">Export Data</Button>
|
||||
</Group>
|
||||
|
||||
{/* KPI Cards */}
|
||||
<Grid gutter="lg">
|
||||
{kpiData.map((kpi) => (
|
||||
<Grid.Col key={kpi.id} span={{ base: 12, md: 6, lg: 3 }}>
|
||||
<Card shadow="sm" padding="lg" radius="md" withBorder>
|
||||
<Card p="md" radius="md" withBorder bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }}>
|
||||
<Group justify="space-between" align="flex-start" mb="xs">
|
||||
<Text size="sm" fw={500} c="dimmed">
|
||||
{kpi.title}
|
||||
@@ -185,21 +182,42 @@ const JennaAnalytic = () => {
|
||||
))}
|
||||
</Grid>
|
||||
|
||||
<Card p="md" radius="md" withBorder bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }}>
|
||||
<Title order={3} fw={500} mb="md">
|
||||
Interaksi Chatbot
|
||||
</Title>
|
||||
<BarChart
|
||||
h={300}
|
||||
data={chartData}
|
||||
dataKey="day"
|
||||
series={[{ name: 'total', color: 'blue' }]}
|
||||
withLegend
|
||||
/>
|
||||
</Card>
|
||||
|
||||
{/* Charts and Lists Section */}
|
||||
<Grid gutter="lg">
|
||||
{/* Grafik Interaksi Chatbot (now Bar Chart) */}
|
||||
<Grid.Col span={{ base: 12, lg: 6 }}>
|
||||
<Card shadow="sm" padding="lg" radius="md" withBorder>
|
||||
<Card p="md" radius="md" withBorder bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }} h="100%">
|
||||
<Title order={3} fw={500} mb="md">
|
||||
Interaksi Chatbot
|
||||
Jam Tersibuk
|
||||
</Title>
|
||||
<BarChart
|
||||
h={300}
|
||||
data={chartData}
|
||||
dataKey="day"
|
||||
series={[{ name: 'total', color: 'blue' }]}
|
||||
withLegend
|
||||
/>
|
||||
<Stack gap="sm">
|
||||
{busyHours.map((item, index) => (
|
||||
<Box key={index}>
|
||||
<Text size="sm">
|
||||
{item.period}
|
||||
</Text>
|
||||
<Group align="center">
|
||||
<Progress value={item.percentage} flex={1} />
|
||||
<Text size="sm" fw={500}>
|
||||
{item.percentage}%
|
||||
</Text>
|
||||
</Group>
|
||||
</Box>
|
||||
))}
|
||||
</Stack>
|
||||
</Card>
|
||||
</Grid.Col>
|
||||
|
||||
@@ -207,7 +225,7 @@ const JennaAnalytic = () => {
|
||||
<Grid.Col span={{ base: 12, lg: 6 }}>
|
||||
<Stack gap="lg">
|
||||
{/* Topik Pertanyaan Terbanyak */}
|
||||
<Card shadow="sm" padding="lg" radius="md" withBorder>
|
||||
<Card p="md" radius="md" withBorder bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }} h="100%">
|
||||
<Title order={3} fw={500} mb="md">
|
||||
Topik Pertanyaan Terbanyak
|
||||
</Title>
|
||||
@@ -231,29 +249,12 @@ const JennaAnalytic = () => {
|
||||
</Card>
|
||||
|
||||
{/* Jam Tersibuk */}
|
||||
<Card shadow="sm" padding="lg" radius="md" withBorder>
|
||||
<Title order={3} fw={500} mb="md">
|
||||
Jam Tersibuk
|
||||
</Title>
|
||||
<Stack gap="sm">
|
||||
{busyHours.map((item, index) => (
|
||||
<Group key={index} align="center">
|
||||
<Text w={80} size="sm">
|
||||
{item.period}
|
||||
</Text>
|
||||
<Progress value={item.percentage} flex={1} />
|
||||
<Text size="sm" fw={500}>
|
||||
{item.percentage}%
|
||||
</Text>
|
||||
</Group>
|
||||
))}
|
||||
</Stack>
|
||||
</Card>
|
||||
</Stack>
|
||||
</Grid.Col>
|
||||
</Grid>
|
||||
</Stack>
|
||||
</Box>
|
||||
</Grid >
|
||||
|
||||
</Stack >
|
||||
</Box >
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
225
src/components/keamanan-page.tsx
Normal file
225
src/components/keamanan-page.tsx
Normal file
@@ -0,0 +1,225 @@
|
||||
import { useState } from "react";
|
||||
import {
|
||||
Card,
|
||||
Grid,
|
||||
GridCol,
|
||||
Group,
|
||||
Text,
|
||||
Title,
|
||||
Stack,
|
||||
useMantineColorScheme,
|
||||
Badge,
|
||||
List,
|
||||
ThemeIcon,
|
||||
Box
|
||||
} from "@mantine/core";
|
||||
import {
|
||||
IconCamera,
|
||||
IconAlertTriangle,
|
||||
IconMapPin,
|
||||
IconClock,
|
||||
IconEye,
|
||||
IconShieldLock
|
||||
} from "@tabler/icons-react";
|
||||
|
||||
const KeamananPage = () => {
|
||||
const { colorScheme } = useMantineColorScheme();
|
||||
const dark = colorScheme === 'dark';
|
||||
|
||||
// Sample data for KPI cards
|
||||
const kpiData = [
|
||||
{
|
||||
title: "CCTV Aktif",
|
||||
value: 20,
|
||||
subtitle: "Kamera Online",
|
||||
icon: <IconCamera size={24} />,
|
||||
color: "darmasaba-success",
|
||||
},
|
||||
{
|
||||
title: "Laporan Keamanan",
|
||||
value: 15,
|
||||
subtitle: "Minggu ini",
|
||||
icon: <IconAlertTriangle size={24} />,
|
||||
color: "darmasaba-danger",
|
||||
},
|
||||
];
|
||||
|
||||
// Sample data for CCTV locations
|
||||
const cctvLocations = [
|
||||
{ id: "CCTV-01", lat: -8.5, lng: 115.2, status: "active", lastSeen: "2 jam yang lalu", location: "Balai Desa" },
|
||||
{ id: "CCTV-02", lat: -8.6, lng: 115.3, status: "active", lastSeen: "1 jam yang lalu", location: "Pintu Masuk Desa" },
|
||||
{ id: "CCTV-03", lat: -8.4, lng: 115.1, status: "offline", lastSeen: "1 hari yang lalu", location: "Taman Desa" },
|
||||
{ id: "CCTV-04", lat: -8.7, lng: 115.4, status: "active", lastSeen: "30 menit yang lalu", location: "Pasar Desa" },
|
||||
];
|
||||
|
||||
// Sample data for security reports
|
||||
const securityReports = [
|
||||
{
|
||||
id: "REP-001",
|
||||
title: "Pencurian Motor",
|
||||
reportedAt: "2 jam yang lalu",
|
||||
date: "12 Feb 2026, 14:30",
|
||||
location: "Jl. Kecubung 20",
|
||||
status: "baru",
|
||||
},
|
||||
{
|
||||
id: "REP-002",
|
||||
title: "Kerusuhan Antar Warga",
|
||||
reportedAt: "4 jam yang lalu",
|
||||
date: "12 Feb 2026, 12:15",
|
||||
location: "RT 05 RW 02",
|
||||
status: "baru",
|
||||
},
|
||||
{
|
||||
id: "REP-003",
|
||||
title: "Kebakaran Rumah",
|
||||
reportedAt: "1 hari yang lalu",
|
||||
date: "11 Feb 2026, 08:45",
|
||||
location: "Jl. Flamboyan 15",
|
||||
status: "diproses",
|
||||
},
|
||||
{
|
||||
id: "REP-004",
|
||||
title: "Kehilangan Barang",
|
||||
reportedAt: "2 hari yang lalu",
|
||||
date: "10 Feb 2026, 16:20",
|
||||
location: "Taman Desa",
|
||||
status: "selesai",
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<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 */}
|
||||
<Grid gutter="md">
|
||||
{kpiData.map((kpi, index) => (
|
||||
<GridCol key={index} span={{ base: 12, sm: 6, md: 6 }}>
|
||||
<Card withBorder radius="md" padding="lg">
|
||||
<Group justify="space-between" align="center">
|
||||
<Stack gap={0}>
|
||||
<Text size="sm" c={dark ? "dark.3" : "dimmed"}>
|
||||
{kpi.subtitle}
|
||||
</Text>
|
||||
<Group gap="xs" align="center">
|
||||
<Text size="xl" fw={700} c={dark ? "dark.0" : "black"}>
|
||||
{kpi.value}
|
||||
</Text>
|
||||
<Text size="sm" c={dark ? "dark.3" : "dimmed"}>
|
||||
{kpi.title}
|
||||
</Text>
|
||||
</Group>
|
||||
</Stack>
|
||||
<ThemeIcon variant="light" color={kpi.color} size="xl" radius="xl">
|
||||
{kpi.icon}
|
||||
</ThemeIcon>
|
||||
</Group>
|
||||
</Card>
|
||||
</GridCol>
|
||||
))}
|
||||
</Grid>
|
||||
|
||||
<Grid gutter="md">
|
||||
{/* Peta Keamanan CCTV */}
|
||||
<GridCol span={{ base: 12, lg: 6 }}>
|
||||
<Card withBorder radius="md" p="lg">
|
||||
<Title order={3} mb="md" c={dark ? "dark.0" : "black"}>Peta Keamanan CCTV</Title>
|
||||
<Text size="sm" c={dark ? "dark.3" : "dimmed"} mb="md">Titik Lokasi CCTV</Text>
|
||||
|
||||
{/* Placeholder for map */}
|
||||
<Box
|
||||
style={{
|
||||
backgroundColor: dark ? '#2d3748' : '#e2e8f0',
|
||||
borderRadius: '8px',
|
||||
height: '400px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center'
|
||||
}}
|
||||
>
|
||||
<Stack align="center">
|
||||
<IconMapPin size={48} stroke={1.5} color={dark ? '#94a3b8' : '#64748b'} />
|
||||
<Text c={dark ? "dark.3" : "dimmed"}>Peta Lokasi CCTV</Text>
|
||||
<Text size="sm" c={dark ? "dark.3" : "dimmed"} ta="center">Integrasi dengan Google Maps atau Mapbox akan ditampilkan di sini</Text>
|
||||
</Stack>
|
||||
</Box>
|
||||
|
||||
{/* CCTV Locations List */}
|
||||
<Stack mt="md" gap="sm">
|
||||
<Title order={4} c={dark ? "dark.0" : "black"}>Daftar CCTV</Title>
|
||||
{cctvLocations.map((cctv, index) => (
|
||||
<Card key={index} withBorder radius="md" p="md">
|
||||
<Group justify="space-between">
|
||||
<Stack gap={0}>
|
||||
<Group gap="xs">
|
||||
<Text fw={500} c={dark ? "dark.0" : "black"}>{cctv.id}</Text>
|
||||
<Badge
|
||||
variant="dot"
|
||||
color={cctv.status === "active" ? "green" : "gray"}
|
||||
>
|
||||
{cctv.status === "active" ? "Online" : "Offline"}
|
||||
</Badge>
|
||||
</Group>
|
||||
<Text size="sm" c={dark ? "dark.3" : "dimmed"}>{cctv.location}</Text>
|
||||
</Stack>
|
||||
<Group gap="xs">
|
||||
<IconClock size={16} stroke={1.5} />
|
||||
<Text size="sm" c={dark ? "dark.3" : "dimmed"}>{cctv.lastSeen}</Text>
|
||||
</Group>
|
||||
</Group>
|
||||
</Card>
|
||||
))}
|
||||
</Stack>
|
||||
</Card>
|
||||
</GridCol>
|
||||
|
||||
{/* Daftar Laporan Keamanan */}
|
||||
<GridCol span={{ base: 12, lg: 6 }}>
|
||||
<Card withBorder radius="md" p="lg">
|
||||
<Title order={3} mb="md" c={dark ? "dark.0" : "black"}>Laporan Keamanan Lingkungan</Title>
|
||||
|
||||
<Stack gap="sm">
|
||||
{securityReports.map((report, index) => (
|
||||
<Card key={index} withBorder radius="md" p="md">
|
||||
<Group justify="space-between" mb="sm">
|
||||
<Text fw={500} c={dark ? "dark.0" : "black"}>{report.title}</Text>
|
||||
<Badge
|
||||
variant="light"
|
||||
color={
|
||||
report.status === "baru" ? "red" :
|
||||
report.status === "diproses" ? "yellow" : "green"
|
||||
}
|
||||
>
|
||||
{report.status}
|
||||
</Badge>
|
||||
</Group>
|
||||
|
||||
<Group justify="space-between">
|
||||
<Group gap="xs">
|
||||
<IconMapPin size={16} stroke={1.5} />
|
||||
<Text size="sm" c={dark ? "dark.3" : "dimmed"}>{report.location}</Text>
|
||||
</Group>
|
||||
<Group gap="xs">
|
||||
<IconClock size={16} stroke={1.5} />
|
||||
<Text size="sm" c={dark ? "dark.3" : "dimmed"}>{report.reportedAt}</Text>
|
||||
</Group>
|
||||
</Group>
|
||||
|
||||
<Text size="sm" c={dark ? "dark.3" : "dimmed"} mt="sm">{report.date}</Text>
|
||||
</Card>
|
||||
))}
|
||||
</Stack>
|
||||
</Card>
|
||||
</GridCol>
|
||||
</Grid>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export default KeamananPage;
|
||||
@@ -116,10 +116,10 @@ const apbdReport = {
|
||||
|
||||
const KeuanganAnggaran = () => {
|
||||
return (
|
||||
<Box p="md">
|
||||
<Box>
|
||||
<Stack gap="xl">
|
||||
<Group justify="space-between" align="center">
|
||||
<Title order={1} fw={700}>
|
||||
<Title order={2} fw={700}>
|
||||
Keuangan & Anggaran
|
||||
</Title>
|
||||
<Button variant="filled">Export Laporan</Button>
|
||||
|
||||
@@ -1,265 +1,343 @@
|
||||
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button"; // Correct import for Button
|
||||
import {
|
||||
Stack,
|
||||
Grid,
|
||||
GridCol,
|
||||
Group,
|
||||
Text,
|
||||
Title,
|
||||
ActionIcon,
|
||||
Progress as MantineProgress,
|
||||
Box,
|
||||
Badge as MantineBadge,
|
||||
Card,
|
||||
CardContent,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/components/ui/card";
|
||||
import { Progress } from "@/components/ui/progress";
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "@/components/ui/table";
|
||||
useMantineColorScheme,
|
||||
ThemeIcon,
|
||||
List,
|
||||
Divider,
|
||||
Skeleton
|
||||
} from "@mantine/core";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Bar, BarChart, CartesianGrid, XAxis, YAxis, Tooltip, ResponsiveContainer, PieChart, Pie, Cell } from "recharts";
|
||||
|
||||
const KinerjaDivisi = () => {
|
||||
// Sample data for division performance
|
||||
const divisions = [
|
||||
const { colorScheme } = useMantineColorScheme();
|
||||
const dark = colorScheme === 'dark';
|
||||
|
||||
// Data for division progress chart
|
||||
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 = [
|
||||
{
|
||||
id: 1,
|
||||
name: "Divisi Teknologi",
|
||||
target: 95,
|
||||
achievement: 87,
|
||||
status: "On Track",
|
||||
projects: 12,
|
||||
budget: "Rp 2.5M",
|
||||
lastUpdate: "2 days ago",
|
||||
name: "Sekretariat",
|
||||
tasks: [
|
||||
{ title: "Laporan Bulanan", status: "selesai" },
|
||||
{ title: "Arsip Dokumen", status: "berjalan" },
|
||||
{ title: "Undangan Rapat", status: "tertunda" },
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: "Divisi Keuangan",
|
||||
target: 90,
|
||||
achievement: 92,
|
||||
status: "Above Target",
|
||||
projects: 8,
|
||||
budget: "Rp 1.8M",
|
||||
lastUpdate: "1 day ago",
|
||||
name: "Keuangan",
|
||||
tasks: [
|
||||
{ title: "Laporan APBDes", status: "selesai" },
|
||||
{ title: "Verifikasi Dana", status: "tertunda" },
|
||||
{ title: "Pengeluaran Harian", status: "berjalan" },
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: "Divisi SDM",
|
||||
target: 85,
|
||||
achievement: 78,
|
||||
status: "Needs Attention",
|
||||
projects: 6,
|
||||
budget: "Rp 1.2M",
|
||||
lastUpdate: "3 days ago",
|
||||
name: "Sosial",
|
||||
tasks: [
|
||||
{ title: "Program Bantuan", status: "selesai" },
|
||||
{ title: "Kegiatan Posyandu", status: "berjalan" },
|
||||
{ title: "Monitoring Stunting", status: "tertunda" },
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: "Divisi Operasional",
|
||||
target: 92,
|
||||
achievement: 89,
|
||||
status: "On Track",
|
||||
projects: 15,
|
||||
budget: "Rp 3.2M",
|
||||
lastUpdate: "5 hours ago",
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
name: "Divisi Pemasaran",
|
||||
target: 88,
|
||||
achievement: 91,
|
||||
status: "Above Target",
|
||||
projects: 10,
|
||||
budget: "Rp 2.1M",
|
||||
lastUpdate: "1 day ago",
|
||||
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, 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
|
||||
const documentStats = [
|
||||
{ name: "Gambar", value: 42 },
|
||||
{ name: "Dokumen", value: 87 },
|
||||
];
|
||||
|
||||
// Activity progress statistics
|
||||
const activityProgressStats = [
|
||||
{ name: "Selesai", value: 12 },
|
||||
{ name: "Dikerjakan", value: 8 },
|
||||
{ name: "Segera Dikerjakan", value: 5 },
|
||||
{ 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
|
||||
const discussions = [
|
||||
{ title: "Pembahasan APBDes 2026", sender: "Kepala Desa", timestamp: "2 jam yang lalu" },
|
||||
{ title: "Kegiatan Posyandu", sender: "Divisi Sosial", timestamp: "5 jam yang lalu" },
|
||||
{ title: "Festival Budaya", sender: "Divisi Humas", 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 (
|
||||
<div className="space-y-6">
|
||||
<div className="flex justify-between items-center">
|
||||
<h1 className="text-3xl font-bold text-gray-900 dark:text-white">
|
||||
Kinerja Divisi
|
||||
</h1>
|
||||
<div className="flex space-x-4">
|
||||
<Button variant="default">Export Data</Button>
|
||||
<Button variant="outline">Filter</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
<Card>
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle className="text-sm font-medium dark:text-gray-100">Total Divisi</CardTitle>
|
||||
<div className="h-6 w-6 text-muted-foreground">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
className="h-6 w-6"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M19 21V5a2 2 0 00-2-2H7a2 2 0 00-2 2v16m14 0h2m-2 0h-5m-9 0H3m2 0h5M9 7h1m-1 4h1m4-4h1m-1 4h1m-5 10v-5a1 1 0 011-1h2a1 1 0 011 1v5m-4 0h4"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold">5</div>
|
||||
<p className="text-xs text-muted-foreground">Jumlah divisi aktif</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle className="text-sm font-medium dark:text-gray-100">
|
||||
Rata-rata Pencapaian
|
||||
</CardTitle>
|
||||
<div className="h-6 w-6 text-muted-foreground">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
className="h-6 w-6"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold">87.4%</div>
|
||||
<p className="text-xs text-muted-foreground">Target tercapai</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle className="text-sm font-medium dark:text-gray-100">
|
||||
Divisi Melebihi Target
|
||||
</CardTitle>
|
||||
<div className="h-6 w-6 text-muted-foreground">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
className="h-6 w-6"
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M13 7h8m0 0v8m0-8l-8 8-4-4-6 6"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold">2</div>
|
||||
<p className="text-xs text-muted-foreground">Dari total 5 divisi</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="dark:text-gray-100">Detail Kinerja Divisi</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead className="dark:text-white">Nama Divisi</TableHead>
|
||||
<TableHead className="dark:text-white">Target (%)</TableHead>
|
||||
<TableHead className="dark:text-white">Pencapaian (%)</TableHead>
|
||||
<TableHead className="dark:text-white">Status</TableHead>
|
||||
<TableHead className="dark:text-white">Proyek Aktif</TableHead>
|
||||
<TableHead className="dark:text-white">Anggaran</TableHead>
|
||||
<TableHead className="dark:text-white">Terakhir Diperbarui</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{divisions.map((division) => (
|
||||
<TableRow key={division.id}>
|
||||
<TableCell className="font-medium dark:text-white">
|
||||
{division.name}
|
||||
</TableCell>
|
||||
<TableCell className="dark:text-white">
|
||||
{division.target}%
|
||||
</TableCell>
|
||||
<TableCell className="dark:text-white">
|
||||
{division.achievement}%
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<div className="flex items-center">
|
||||
<Progress
|
||||
value={division.achievement}
|
||||
max={100}
|
||||
className="w-24 mr-2"
|
||||
/>
|
||||
<Badge
|
||||
variant={
|
||||
division.status === "Above Target"
|
||||
? "success"
|
||||
: division.status === "On Track"
|
||||
? "secondary"
|
||||
: "destructive"
|
||||
}
|
||||
>
|
||||
{division.status}
|
||||
</Badge>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell className="dark:text-white">
|
||||
{division.projects}
|
||||
</TableCell>
|
||||
<TableCell className="dark:text-white">
|
||||
{division.budget}
|
||||
</TableCell>
|
||||
<TableCell className="dark:text-white">
|
||||
{division.lastUpdate}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</CardContent>
|
||||
<Stack gap="lg">
|
||||
{/* Grafik Progres Tugas per Divisi */}
|
||||
<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'}>
|
||||
Grafik Progres Tugas per Divisi
|
||||
</Title>
|
||||
<ResponsiveContainer width="100%" height={300}>
|
||||
<BarChart data={divisionProgressData}>
|
||||
<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="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>
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="dark:text-gray-100">Grafik Pencapaian Divisi</CardTitle>
|
||||
</CardHeader> <CardContent>
|
||||
<div className="h-80 flex items-center justify-center bg-gray-50 dark:bg-gray-700 rounded-lg">
|
||||
<p className="text-gray-500 dark:text-gray-300">
|
||||
Grafik pencapaian akan ditampilkan di sini
|
||||
</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</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%">
|
||||
<Title order={4} mb="sm" c={dark ? 'white' : 'darmasaba-navy'}>
|
||||
{division.name}
|
||||
</Title>
|
||||
<Stack gap="sm">
|
||||
{division.tasks.map((task, taskIndex) => (
|
||||
<Box key={taskIndex}>
|
||||
<Group justify="space-between">
|
||||
<Text size="sm" c={dark ? 'white' : 'darmasaba-navy'}>{task.title}</Text>
|
||||
<MantineBadge
|
||||
color={STATUS_COLORS[task.status] || 'gray'}
|
||||
variant="light"
|
||||
>
|
||||
{task.status}
|
||||
</MantineBadge>
|
||||
</Group>
|
||||
</Box>
|
||||
))}
|
||||
</Stack>
|
||||
</Card>
|
||||
</GridCol>
|
||||
))}
|
||||
</Grid>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="dark:text-gray-100">Distribusi Anggaran Divisi</CardTitle>
|
||||
</CardHeader> <CardContent>
|
||||
<div className="h-80 flex items-center justify-center bg-gray-50 dark:bg-gray-700 rounded-lg">
|
||||
<p className="text-gray-500 dark:text-gray-300">
|
||||
Diagram distribusi anggaran akan ditampilkan di sini
|
||||
</p>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
{/* Arsip Digital Perangkat Desa */}
|
||||
<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'}>
|
||||
Arsip Digital Perangkat Desa
|
||||
</Title>
|
||||
<Grid gutter="md">
|
||||
{archiveItems.map((item, index) => (
|
||||
<GridCol key={index} span={{ base: 12, md: 6, lg: 3 }}>
|
||||
<Card 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}>{item.name}</Text>
|
||||
<Text c={dark ? 'white' : 'darmasaba-navy'} fw={700}>{item.count}</Text>
|
||||
</Group>
|
||||
</Card>
|
||||
</GridCol>
|
||||
))}
|
||||
</Grid>
|
||||
</Card>
|
||||
|
||||
{/* Kartu Progres Kegiatan */}
|
||||
<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 / Program
|
||||
</Title>
|
||||
<Stack gap="md">
|
||||
{activityProgress.map((activity, index) => (
|
||||
<Card key={index} p="md" radius="md" withBorder bg={dark ? "#263852ff" : "#F1F5F9"} style={{ borderColor: dark ? "#263852ff" : "#F1F5F9" }}>
|
||||
<Group justify="space-between" mb="sm">
|
||||
<Text c={dark ? 'white' : 'darmasaba-navy'} fw={500}>{activity.name}</Text>
|
||||
<MantineBadge
|
||||
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;
|
||||
@@ -1,26 +1,76 @@
|
||||
import type React from "react";
|
||||
import { useState } from "react";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Button,
|
||||
Card,
|
||||
CardContent,
|
||||
CardHeader,
|
||||
CardTitle,
|
||||
} from "@/components/ui/card";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Select } from "@/components/ui/select";
|
||||
import {
|
||||
Grid,
|
||||
GridCol,
|
||||
Group,
|
||||
Text,
|
||||
Title,
|
||||
TextInput,
|
||||
Textarea,
|
||||
Select,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "@/components/ui/table";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
Badge,
|
||||
Stack,
|
||||
useMantineColorScheme,
|
||||
List,
|
||||
Divider,
|
||||
ActionIcon,
|
||||
Box
|
||||
} from "@mantine/core";
|
||||
import { IconMessage, IconAlertTriangle, IconClock, IconCheck, IconChevronRight } from "@tabler/icons-react";
|
||||
import { Line, LineChart, Bar, BarChart, CartesianGrid, XAxis, YAxis, Tooltip, ResponsiveContainer } from "recharts";
|
||||
|
||||
const PengaduanLayananPublik = () => {
|
||||
const { colorScheme } = useMantineColorScheme();
|
||||
const dark = colorScheme === 'dark';
|
||||
|
||||
// Summary data
|
||||
const summaryData = {
|
||||
total: 42,
|
||||
baru: 14,
|
||||
diproses: 14,
|
||||
selesai: 14
|
||||
};
|
||||
|
||||
// Tren pengaduan data
|
||||
const trenData = [
|
||||
{ bulan: "Jan", jumlah: 30 },
|
||||
{ bulan: "Feb", jumlah: 50 },
|
||||
{ bulan: "Mar", jumlah: 42 },
|
||||
{ bulan: "Apr", jumlah: 38 },
|
||||
{ bulan: "Mei", jumlah: 45 },
|
||||
{ bulan: "Jun", jumlah: 42 }
|
||||
];
|
||||
|
||||
// Surat terbanyak data
|
||||
const suratData = [
|
||||
{ jenis: "KTP", jumlah: 24 },
|
||||
{ jenis: "KK", jumlah: 18 },
|
||||
{ jenis: "Domisili", jumlah: 15 },
|
||||
{ jenis: "Usaha", jumlah: 12 },
|
||||
{ jenis: "Lainnya", jumlah: 8 }
|
||||
];
|
||||
|
||||
// Pengajuan terbaru data
|
||||
const pengajuanTerbaru = [
|
||||
{ nama: "Budi Santoso", jenis: "Ketertiban Umum", waktu: "2 jam yang lalu", status: "baru" },
|
||||
{ nama: "Siti Rahayu", jenis: "Pelayanan Kesehatan", waktu: "5 jam yang lalu", status: "diproses" },
|
||||
{ nama: "Ahmad Fauzi", jenis: "Infrastruktur", waktu: "1 hari yang lalu", status: "selesai" },
|
||||
{ nama: "Dewi Lestari", jenis: "Administrasi", waktu: "1 hari yang lalu", status: "baru" },
|
||||
{ nama: "Joko Widodo", jenis: "Keamanan", waktu: "2 hari yang lalu", status: "diproses" }
|
||||
];
|
||||
|
||||
// Ide inovatif data
|
||||
const ideInovatif = [
|
||||
{ nama: "Andi Prasetyo", judul: "Penerapan Smart Village", kategori: "Teknologi" },
|
||||
{ nama: "Rina Kusuma", judul: "Program Ekowisata Desa", kategori: "Ekonomi" },
|
||||
{ nama: "Bambang Suryono", judul: "Peningkatan Sanitasi", kategori: "Kesehatan" },
|
||||
{ nama: "Lina Marlina", judul: "Pusat Kreatif Anak Muda", kategori: "Pendidikan" }
|
||||
];
|
||||
|
||||
const [activeTab, setActiveTab] = useState<"complaints" | "services">(
|
||||
"complaints",
|
||||
);
|
||||
@@ -133,280 +183,463 @@ const PengaduanLayananPublik = () => {
|
||||
setNewComplaint({ title: "", category: "", description: "" });
|
||||
};
|
||||
|
||||
// Render complaint table rows
|
||||
const complaintRows = complaints.map((complaint) => (
|
||||
<Table.Tr key={complaint.id}>
|
||||
<Table.Td className="font-medium">
|
||||
<Text c={dark ? "white" : "dark.3"}>{complaint.title}</Text>
|
||||
</Table.Td>
|
||||
<Table.Td>
|
||||
<Text c={dark ? "white" : "dark.3"}>{complaint.category}</Text>
|
||||
</Table.Td>
|
||||
<Table.Td>
|
||||
<Badge
|
||||
variant="filled"
|
||||
color={
|
||||
complaint.status === "Resolved"
|
||||
? "green"
|
||||
: complaint.status === "In Progress"
|
||||
? "yellow"
|
||||
: "red"
|
||||
}
|
||||
>
|
||||
{complaint.status}
|
||||
</Badge>
|
||||
</Table.Td>
|
||||
<Table.Td>
|
||||
<Badge
|
||||
variant="filled"
|
||||
color={
|
||||
complaint.priority === "High"
|
||||
? "red"
|
||||
: complaint.priority === "Medium"
|
||||
? "yellow"
|
||||
: "blue"
|
||||
}
|
||||
>
|
||||
{complaint.priority}
|
||||
</Badge>
|
||||
</Table.Td>
|
||||
<Table.Td>
|
||||
<Text c={dark ? "white" : "dark.3"}>{complaint.date}</Text>
|
||||
</Table.Td>
|
||||
</Table.Tr>
|
||||
));
|
||||
|
||||
// Status badge color mapping
|
||||
const getStatusColor = (status: string) => {
|
||||
switch (status) {
|
||||
case 'baru': return 'red';
|
||||
case 'diproses': return 'yellow';
|
||||
case 'selesai': return 'green';
|
||||
default: return 'gray';
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="flex justify-between items-center">
|
||||
<h1 className="text-3xl font-bold text-gray-900 dark:text-white">
|
||||
Pengaduan & Layanan Publik
|
||||
</h1>
|
||||
<div className="flex space-x-4">
|
||||
<Button
|
||||
variant={activeTab === "complaints" ? "default" : "outline"}
|
||||
onClick={() => setActiveTab("complaints")}
|
||||
className="dark:bg-slate-700 dark:hover:bg-slate-600 dark:text-white"
|
||||
>
|
||||
Pengaduan
|
||||
</Button>
|
||||
<Button
|
||||
variant={activeTab === "services" ? "default" : "outline"}
|
||||
onClick={() => setActiveTab("services")}
|
||||
className="dark:bg-slate-700 dark:hover:bg-slate-600 dark:text-white"
|
||||
>
|
||||
Layanan Publik
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Stack gap="lg">
|
||||
{activeTab === "complaints" ? (
|
||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||||
{/* Complaint Submission Form */}
|
||||
<div className="lg:col-span-1">
|
||||
<Card className="dark:bg-gray-800 dark:border-gray-700">
|
||||
<CardHeader>
|
||||
<CardTitle className="dark:text-white">
|
||||
Ajukan Pengaduan
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<form onSubmit={handleSubmitComplaint} className="space-y-4">
|
||||
<div>
|
||||
<label
|
||||
htmlFor="title"
|
||||
className="block text-sm font-medium mb-1 dark:text-gray-300"
|
||||
>
|
||||
Judul Pengaduan
|
||||
</label>
|
||||
<Input
|
||||
id="title"
|
||||
name="title"
|
||||
value={newComplaint.title}
|
||||
onChange={handleInputChange}
|
||||
placeholder="Masukkan judul pengaduan"
|
||||
className="dark:bg-gray-700 dark:border-gray-600 dark:text-white"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label
|
||||
htmlFor="category"
|
||||
className="block text-sm font-medium mb-1 dark:text-gray-300"
|
||||
>
|
||||
Kategori
|
||||
</label>
|
||||
<Select
|
||||
id="category"
|
||||
name="category"
|
||||
value={newComplaint.category}
|
||||
onChange={handleSelectChange}
|
||||
placeholder="Pilih kategori"
|
||||
data={[
|
||||
{ value: "infrastruktur", label: "Infrastruktur" },
|
||||
{ value: "administrasi", label: "Administrasi" },
|
||||
{ value: "utilitas", label: "Utilitas" },
|
||||
{ value: "sanitasi", label: "Sanitasi" },
|
||||
{ value: "kesehatan", label: "Kesehatan" },
|
||||
{ value: "pendidikan", label: "Pendidikan" },
|
||||
]}
|
||||
className="dark:bg-gray-700 dark:border-gray-600 dark:text-white"
|
||||
clearable
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label
|
||||
htmlFor="description"
|
||||
className="block text-sm font-medium mb-1 dark:text-gray-300"
|
||||
>
|
||||
Deskripsi
|
||||
</label>
|
||||
<Textarea
|
||||
id="description"
|
||||
name="description"
|
||||
value={newComplaint.description}
|
||||
onChange={handleInputChange}
|
||||
placeholder="Jelaskan pengaduan Anda secara detail..."
|
||||
rows={4}
|
||||
className="dark:bg-gray-700 dark:border-gray-600 dark:text-white"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
type="submit"
|
||||
className="w-full dark:bg-blue-600 dark:hover:bg-blue-700"
|
||||
<>
|
||||
{/* Summary Cards */}
|
||||
<Grid gutter="md">
|
||||
<GridCol span={{ base: 12, md: 6, lg: 3 }}>
|
||||
<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"}>
|
||||
Total Pengaduan
|
||||
</Text>
|
||||
<Text size="xl" fw={700} c={dark ? "dark.0" : "black"}>
|
||||
{summaryData.total}
|
||||
</Text>
|
||||
</Stack>
|
||||
<Badge
|
||||
variant="light"
|
||||
color="darmasaba-blue"
|
||||
p={8}
|
||||
radius="md"
|
||||
>
|
||||
Kirim Pengaduan
|
||||
</Button>
|
||||
</form>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
<IconMessage size={20} />
|
||||
</Badge>
|
||||
</Group>
|
||||
</Card>
|
||||
</GridCol>
|
||||
|
||||
{/* Complaints List */}
|
||||
<div className="lg:col-span-2">
|
||||
<Card className="dark:bg-gray-800 dark:border-gray-700">
|
||||
<CardHeader>
|
||||
<CardTitle className="dark:text-white">
|
||||
Daftar Pengaduan
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead className="dark:text-gray-300">
|
||||
Judul
|
||||
</TableHead>
|
||||
<TableHead className="dark:text-gray-300">
|
||||
Kategori
|
||||
</TableHead>
|
||||
<TableHead className="dark:text-gray-300">
|
||||
Status
|
||||
</TableHead>
|
||||
<TableHead className="dark:text-gray-300">
|
||||
Prioritas
|
||||
</TableHead>
|
||||
<TableHead className="dark:text-gray-300">
|
||||
Tanggal
|
||||
</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{complaints.map((complaint) => (
|
||||
<TableRow key={complaint.id}>
|
||||
<TableCell className="font-medium dark:text-white">
|
||||
{complaint.title}
|
||||
</TableCell>
|
||||
<TableCell className="dark:text-gray-300">
|
||||
{complaint.category}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Badge
|
||||
variant={
|
||||
complaint.status === "Resolved"
|
||||
? "success"
|
||||
: complaint.status === "In Progress"
|
||||
? "secondary"
|
||||
: "destructive"
|
||||
}
|
||||
>
|
||||
{complaint.status}
|
||||
</Badge>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Badge
|
||||
variant={
|
||||
complaint.priority === "High"
|
||||
? "destructive"
|
||||
: complaint.priority === "Medium"
|
||||
? "secondary"
|
||||
: "default"
|
||||
}
|
||||
>
|
||||
{complaint.priority}
|
||||
</Badge>
|
||||
</TableCell>
|
||||
<TableCell className="dark:text-gray-300">
|
||||
{complaint.date}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
<GridCol span={{ base: 12, md: 6, lg: 3 }}>
|
||||
<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"}>
|
||||
Baru
|
||||
</Text>
|
||||
<Text size="xl" fw={700} c={dark ? "dark.0" : "black"}>
|
||||
{summaryData.baru}
|
||||
</Text>
|
||||
</Stack>
|
||||
<Badge
|
||||
variant="light"
|
||||
color="red"
|
||||
p={8}
|
||||
radius="md"
|
||||
>
|
||||
<IconAlertTriangle size={20} />
|
||||
</Badge>
|
||||
</Group>
|
||||
</Card>
|
||||
</GridCol>
|
||||
|
||||
<GridCol span={{ base: 12, md: 6, lg: 3 }}>
|
||||
<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"}>
|
||||
Diproses
|
||||
</Text>
|
||||
<Text size="xl" fw={700} c={dark ? "dark.0" : "black"}>
|
||||
{summaryData.diproses}
|
||||
</Text>
|
||||
</Stack>
|
||||
<Badge
|
||||
variant="light"
|
||||
color="yellow"
|
||||
p={8}
|
||||
radius="md"
|
||||
>
|
||||
<IconClock size={20} />
|
||||
</Badge>
|
||||
</Group>
|
||||
</Card>
|
||||
</GridCol>
|
||||
|
||||
<GridCol span={{ base: 12, md: 6, lg: 3 }}>
|
||||
<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"}>
|
||||
Selesai
|
||||
</Text>
|
||||
<Text size="xl" fw={700} c={dark ? "dark.0" : "black"}>
|
||||
{summaryData.selesai}
|
||||
</Text>
|
||||
</Stack>
|
||||
<Badge
|
||||
variant="light"
|
||||
color="green"
|
||||
p={8}
|
||||
radius="md"
|
||||
>
|
||||
<IconCheck size={20} />
|
||||
</Badge>
|
||||
</Group>
|
||||
</Card>
|
||||
</GridCol>
|
||||
</Grid>
|
||||
|
||||
{/* Grafik Tren Pengaduan */}
|
||||
<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"}>
|
||||
Grafik Tren Pengaduan
|
||||
</Title>
|
||||
<ResponsiveContainer width="100%" height={300}>
|
||||
<LineChart data={trenData}>
|
||||
<CartesianGrid
|
||||
strokeDasharray="3 3"
|
||||
vertical={false}
|
||||
stroke={dark ? "var(--mantine-color-gray-7)" : "var(--mantine-color-gray-3)"}
|
||||
/>
|
||||
<XAxis
|
||||
dataKey="bulan"
|
||||
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)' }
|
||||
: {}}
|
||||
/>
|
||||
<Line
|
||||
type="monotone"
|
||||
dataKey="jumlah"
|
||||
stroke={dark ? "var(--mantine-color-blue-6)" : "var(--mantine-color-blue-filled)"}
|
||||
strokeWidth={2}
|
||||
dot={{ stroke: dark ? "var(--mantine-color-blue-6)" : "var(--mantine-color-blue-filled)", strokeWidth: 2, r: 4 }}
|
||||
activeDot={{ r: 6, stroke: '#fff', strokeWidth: 2 }}
|
||||
/>
|
||||
</LineChart>
|
||||
</ResponsiveContainer>
|
||||
</Card>
|
||||
|
||||
{/* Surat Terbanyak & Pengajuan Terbaru & Ide Inovatif */}
|
||||
<Grid gutter="md">
|
||||
{/* Surat Terbanyak */}
|
||||
<GridCol span={{ base: 12, lg: 4 }}>
|
||||
<Card p="md" radius="md" withBorder bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }} h="100%">
|
||||
<Title order={4} mb="md" c={dark ? "dark.0" : "black"}>
|
||||
Surat Terbanyak
|
||||
</Title>
|
||||
<ResponsiveContainer width="100%" height={250}>
|
||||
<BarChart data={suratData} layout="horizontal">
|
||||
<CartesianGrid
|
||||
strokeDasharray="3 3"
|
||||
horizontal={false}
|
||||
stroke={dark ? "var(--mantine-color-gray-7)" : "var(--mantine-color-gray-3)"}
|
||||
/>
|
||||
<XAxis
|
||||
dataKey="jumlah"
|
||||
axisLine={false}
|
||||
tickLine={false}
|
||||
tick={{ fill: dark ? "var(--mantine-color-text)" : "var(--mantine-color-text)" }}
|
||||
/>
|
||||
<YAxis
|
||||
dataKey="jenis"
|
||||
type="category"
|
||||
axisLine={false}
|
||||
tickLine={false}
|
||||
tick={{ fill: dark ? "var(--mantine-color-text)" : "var(--mantine-color-text)" }}
|
||||
width={80}
|
||||
/>
|
||||
<Tooltip
|
||||
contentStyle={dark
|
||||
? { backgroundColor: 'var(--mantine-color-dark-7)', borderColor: 'var(--mantine-color-dark-6)' }
|
||||
: {}}
|
||||
/>
|
||||
<Bar
|
||||
dataKey="jumlah"
|
||||
fill={dark ? "var(--mantine-color-blue-6)" : "var(--mantine-color-blue-filled)"}
|
||||
radius={[0, 4, 4, 0]}
|
||||
/>
|
||||
</BarChart>
|
||||
</ResponsiveContainer>
|
||||
</Card>
|
||||
</GridCol>
|
||||
|
||||
{/* Pengajuan Terbaru */}
|
||||
<GridCol span={{ base: 12, lg: 4 }}>
|
||||
<Card p="md" radius="md" withBorder bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }} h="100%">
|
||||
<Title order={4} mb="md" c={dark ? "dark.0" : "black"}>
|
||||
Pengajuan Terbaru
|
||||
</Title>
|
||||
{pengajuanTerbaru.map((item, index) => (
|
||||
<Box key={index}>
|
||||
<Group justify="space-between">
|
||||
<Stack gap={0}>
|
||||
<Text fw={500} c={dark ? "dark.0" : "black"}>{item.nama}</Text>
|
||||
<Text size="sm" c={dark ? "dark.3" : "dimmed"}>{item.jenis}</Text>
|
||||
</Stack>
|
||||
<Stack gap={0} align="flex-end">
|
||||
<Badge color={getStatusColor(item.status)} variant="light">
|
||||
{item.status}
|
||||
</Badge>
|
||||
<Text size="xs" c={dark ? "dark.4" : "dimmed"}>{item.waktu}</Text>
|
||||
</Stack>
|
||||
</Group>
|
||||
<Divider my="sm" />
|
||||
</Box>
|
||||
))}
|
||||
</Card>
|
||||
</GridCol>
|
||||
|
||||
{/* Ajuan Ide Inovatif */}
|
||||
<GridCol span={{ base: 12, lg: 4 }}>
|
||||
<Card p="md" radius="md" withBorder bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }} h="100%">
|
||||
<Title order={4} mb="md" c={dark ? "dark.0" : "black"}>
|
||||
Ajuan Ide Inovatif
|
||||
</Title>
|
||||
{ideInovatif.map((item, index) => (
|
||||
<Box key={index}>
|
||||
<Group justify="space-between">
|
||||
<Stack gap={0}>
|
||||
<Text fw={500} c={dark ? "dark.0" : "black"}>{item.judul}</Text>
|
||||
<Text size="sm" c={dark ? "dark.3" : "dimmed"}>{item.nama}</Text>
|
||||
</Stack>
|
||||
<Group>
|
||||
<Badge color="blue" variant="light">
|
||||
{item.kategori}
|
||||
</Badge>
|
||||
<ActionIcon variant="subtle" color="darmasaba-blue">
|
||||
<IconChevronRight size={16} />
|
||||
</ActionIcon>
|
||||
</Group>
|
||||
</Group>
|
||||
<Divider my="sm" />
|
||||
</Box>
|
||||
))}
|
||||
</Card>
|
||||
</GridCol>
|
||||
</Grid>
|
||||
|
||||
{/* Complaint Submission Form and List */}
|
||||
<Grid gutter="md">
|
||||
{/* Complaint Submission Form */}
|
||||
<GridCol span={{ base: 12, lg: 4 }}>
|
||||
<Card p="md" withBorder radius="md" h="100%" bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }}>
|
||||
<Card.Section withBorder inheritPadding py="xs">
|
||||
<Title order={3} py="xs">Ajukan Pengaduan</Title>
|
||||
</Card.Section>
|
||||
<Card.Section>
|
||||
<form onSubmit={handleSubmitComplaint}>
|
||||
<Stack gap="md" p={"sm"}>
|
||||
<TextInput
|
||||
label="Judul Pengaduan"
|
||||
id="title"
|
||||
name="title"
|
||||
value={newComplaint.title}
|
||||
onChange={handleInputChange}
|
||||
placeholder="Masukkan judul pengaduan"
|
||||
required
|
||||
withAsterisk
|
||||
/>
|
||||
|
||||
<Select
|
||||
label="Kategori"
|
||||
id="category"
|
||||
name="category"
|
||||
value={newComplaint.category}
|
||||
onChange={handleSelectChange}
|
||||
placeholder="Pilih kategori"
|
||||
data={[
|
||||
{ value: "infrastruktur", label: "Infrastruktur" },
|
||||
{ value: "administrasi", label: "Administrasi" },
|
||||
{ value: "utilitas", label: "Utilitas" },
|
||||
{ value: "sanitasi", label: "Sanitasi" },
|
||||
{ value: "kesehatan", label: "Kesehatan" },
|
||||
{ value: "pendidikan", label: "Pendidikan" },
|
||||
]}
|
||||
clearable
|
||||
/>
|
||||
|
||||
<Textarea
|
||||
label="Deskripsi"
|
||||
id="description"
|
||||
name="description"
|
||||
value={newComplaint.description}
|
||||
onChange={handleInputChange}
|
||||
placeholder="Jelaskan pengaduan Anda secara detail..."
|
||||
minRows={4}
|
||||
required
|
||||
withAsterisk
|
||||
/>
|
||||
|
||||
<Button type="submit" mt="md" color="darmasaba-blue">
|
||||
Kirim Pengaduan
|
||||
</Button>
|
||||
</Stack>
|
||||
</form>
|
||||
</Card.Section>
|
||||
</Card>
|
||||
</GridCol>
|
||||
|
||||
{/* Complaints List */}
|
||||
<GridCol span={{ base: 12, lg: 8 }}>
|
||||
<Card withBorder radius="md" bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }}>
|
||||
<Card.Section withBorder inheritPadding py="xs">
|
||||
<Title order={3} py="xs">Daftar Pengaduan</Title>
|
||||
</Card.Section>
|
||||
<Card.Section py="md" px="xs">
|
||||
<Table withColumnBorders>
|
||||
<Table.Thead>
|
||||
<Table.Tr>
|
||||
<Table.Th><Text c={dark ? "white" : "dark.3" }>Judul</Text></Table.Th>
|
||||
<Table.Th><Text c={dark ? "white" : "dark.3" }>Kategori</Text></Table.Th>
|
||||
<Table.Th><Text c={dark ? "white" : "dark.3" }>Status</Text></Table.Th>
|
||||
<Table.Th><Text c={dark ? "white" : "dark.3" }>Prioritas</Text></Table.Th>
|
||||
<Table.Th><Text c={dark ? "white" : "dark.3" }>Tanggal</Text></Table.Th>
|
||||
</Table.Tr>
|
||||
</Table.Thead>
|
||||
<Table.Tbody>
|
||||
{complaintRows}
|
||||
</Table.Tbody>
|
||||
</Table>
|
||||
</Card.Section>
|
||||
</Card>
|
||||
</GridCol>
|
||||
</Grid>
|
||||
</>
|
||||
) : (
|
||||
<div>
|
||||
<Card className="dark:bg-gray-800 dark:border-gray-700">
|
||||
<CardHeader>
|
||||
<CardTitle className="dark:text-white">
|
||||
Layanan Publik Tersedia
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
<Stack gap="lg">
|
||||
<Card withBorder radius="md">
|
||||
<Card.Section withBorder inheritPadding py="xs">
|
||||
<Title order={3} py="xs">Layanan Publik Tersedia</Title>
|
||||
</Card.Section>
|
||||
<Card.Section pt="md">
|
||||
<Grid gutter="md">
|
||||
{services.map((service) => (
|
||||
<Card
|
||||
key={service.id}
|
||||
className="dark:bg-gray-700 dark:border-gray-600"
|
||||
>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-lg dark:text-white">
|
||||
{service.name}
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<p className="text-sm text-gray-600 dark:text-gray-300 mb-3">
|
||||
<GridCol key={service.id} span={{ base: 12, md: 6, lg: 4 }}>
|
||||
<Card withBorder radius="md" h="100%">
|
||||
<Title order={4} mb="sm">{service.name}</Title>
|
||||
<Text size="sm" c={dark ? "white" : "dark.3" } mb="md">
|
||||
{service.description}
|
||||
</p>
|
||||
<div className="flex justify-between items-center">
|
||||
</Text>
|
||||
<Group justify="space-between">
|
||||
<Badge
|
||||
variant={
|
||||
variant="filled"
|
||||
color={
|
||||
service.status === "Available"
|
||||
? "success"
|
||||
? "green"
|
||||
: service.status === "Limited"
|
||||
? "secondary"
|
||||
: "destructive"
|
||||
? "yellow"
|
||||
: "red"
|
||||
}
|
||||
>
|
||||
{service.status}
|
||||
</Badge>
|
||||
<span className="text-xs text-gray-500 dark:text-gray-400">
|
||||
<Text size="sm" c={dark ? "white" : "dark.3" }>
|
||||
{service.category}
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400 mt-2">
|
||||
</Text>
|
||||
</Group>
|
||||
<Text size="xs" c={dark ? "white" : "dark.3" } mt="sm">
|
||||
Terakhir diperbarui: {service.lastUpdated}
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Text>
|
||||
</Card>
|
||||
</GridCol>
|
||||
))}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Grid>
|
||||
</Card.Section>
|
||||
</Card>
|
||||
|
||||
<Card className="mt-6 dark:bg-gray-800 dark:border-gray-700">
|
||||
<CardHeader>
|
||||
<CardTitle className="dark:text-white">
|
||||
Statistik Layanan
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<div className="bg-gray-100 dark:bg-gray-700 p-4 rounded-lg">
|
||||
<h3 className="text-lg font-semibold dark:text-white">
|
||||
Jumlah Layanan Tersedia
|
||||
</h3>
|
||||
<p className="text-3xl font-bold text-blue-600 dark:text-blue-400">
|
||||
12
|
||||
</p>
|
||||
</div>
|
||||
<div className="bg-gray-100 dark:bg-gray-700 p-4 rounded-lg">
|
||||
<h3 className="text-lg font-semibold dark:text-white">
|
||||
Layanan Terpopuler
|
||||
</h3>
|
||||
<p className="text-3xl font-bold text-green-600 dark:text-green-400">
|
||||
4
|
||||
</p>
|
||||
</div>
|
||||
<div className="bg-gray-100 dark:bg-gray-700 p-4 rounded-lg">
|
||||
<h3 className="text-lg font-semibold dark:text-white">
|
||||
Permintaan Baru
|
||||
</h3>
|
||||
<p className="text-3xl font-bold text-purple-600 dark:text-purple-400">
|
||||
23
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
<Card withBorder radius="md">
|
||||
<Card.Section withBorder inheritPadding py="xs">
|
||||
<Title order={3} py="xs">Statistik Layanan</Title>
|
||||
</Card.Section>
|
||||
<Card.Section pt="md">
|
||||
<Grid gutter="md">
|
||||
<GridCol span={{ base: 12, md: 4 }}>
|
||||
<Card p="md" bg={dark ? "dark.7" : "gray.0"} radius="md">
|
||||
<Title order={4} mb="xs">Jumlah Layanan Tersedia</Title>
|
||||
<Text size="xl" fw={700} c="darmasaba-blue">
|
||||
12
|
||||
</Text>
|
||||
</Card>
|
||||
</GridCol>
|
||||
<GridCol span={{ base: 12, md: 4 }}>
|
||||
<Card p="md" bg={dark ? "dark.7" : "gray.0"} radius="md">
|
||||
<Title order={4} mb="xs">Layanan Terpopuler</Title>
|
||||
<Text size="xl" fw={700} c="darmasaba-success">
|
||||
4
|
||||
</Text>
|
||||
</Card>
|
||||
</GridCol>
|
||||
<GridCol span={{ base: 12, md: 4 }}>
|
||||
<Card p="md" bg={dark ? "dark.7" : "gray.0"} radius="md">
|
||||
<Title order={4} mb="xs">Permintaan Baru</Title>
|
||||
<Text size="xl" fw={700} c="darmasaba-warning">
|
||||
23
|
||||
</Text>
|
||||
</Card>
|
||||
</GridCol>
|
||||
</Grid>
|
||||
</Card.Section>
|
||||
</Card>
|
||||
</div>
|
||||
</Stack>
|
||||
)}
|
||||
</div>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export default PengaduanLayananPublik;
|
||||
export default PengaduanLayananPublik;
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
Input,
|
||||
NavLink as MantineNavLink,
|
||||
Box,
|
||||
useMantineColorScheme,
|
||||
} from "@mantine/core";
|
||||
|
||||
interface SidebarProps {
|
||||
@@ -17,6 +18,9 @@ interface SidebarProps {
|
||||
export function Sidebar({ className }: SidebarProps) {
|
||||
const location = useLocation();
|
||||
const navigate = useNavigate();
|
||||
const { colorScheme } = useMantineColorScheme();
|
||||
const isActiveBg = colorScheme === 'dark' ? "#182949" : "#E6F0FF";
|
||||
const isActiveBorder = colorScheme === 'dark' ? "#00398D" : "#1F41AE";
|
||||
|
||||
// Define menu items with their paths
|
||||
const menuItems = [
|
||||
@@ -75,16 +79,34 @@ export function Sidebar({ className }: SidebarProps) {
|
||||
|
||||
{/* Menu Items */}
|
||||
<Stack gap={0} px="xs" flex={1} style={{ overflowY: "auto" }}>
|
||||
{menuItems.map((item, index) => (
|
||||
<MantineNavLink
|
||||
key={index}
|
||||
onClick={() => navigate({ to: item.path })}
|
||||
label={item.name}
|
||||
active={location.pathname === item.path}
|
||||
variant="subtle"
|
||||
color="blue"
|
||||
/>
|
||||
))}
|
||||
{menuItems.map((item, index) => {
|
||||
const isActive = location.pathname === item.path;
|
||||
return (
|
||||
<MantineNavLink
|
||||
key={index}
|
||||
onClick={() => navigate({ to: item.path })}
|
||||
label={item.name}
|
||||
active={isActive}
|
||||
variant="subtle"
|
||||
color="blue"
|
||||
style={{
|
||||
background: isActive ? isActiveBg : "transparent",
|
||||
fontWeight: isActive ? "bold" : "normal",
|
||||
borderLeft: isActive ? `4px solid ${isActiveBorder}` : "4px solid transparent",
|
||||
borderRadius: "8px",
|
||||
transition: "all 200ms ease",
|
||||
margin: "2px 0",
|
||||
}}
|
||||
styles={{
|
||||
body: {
|
||||
"&:hover": {
|
||||
background: "#F1F5F9",
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</Stack>
|
||||
</Box>
|
||||
);
|
||||
|
||||
296
src/components/sosial-page.tsx
Normal file
296
src/components/sosial-page.tsx
Normal file
@@ -0,0 +1,296 @@
|
||||
import { useState } from "react";
|
||||
import {
|
||||
Card,
|
||||
Grid,
|
||||
GridCol,
|
||||
Group,
|
||||
Text,
|
||||
Title,
|
||||
Progress,
|
||||
Stack,
|
||||
useMantineColorScheme,
|
||||
Badge,
|
||||
List,
|
||||
ThemeIcon
|
||||
} from "@mantine/core";
|
||||
import {
|
||||
IconHeartbeat,
|
||||
IconBabyCarriage,
|
||||
IconStethoscope,
|
||||
IconMedicalCross,
|
||||
IconSchool,
|
||||
IconBook,
|
||||
IconCalendarEvent,
|
||||
IconAward
|
||||
} from "@tabler/icons-react";
|
||||
|
||||
const SosialPage = () => {
|
||||
const { colorScheme } = useMantineColorScheme();
|
||||
const dark = colorScheme === 'dark';
|
||||
|
||||
// Sample data for health statistics
|
||||
const healthStats = {
|
||||
ibuHamil: 87,
|
||||
balita: 342,
|
||||
alertStunting: 12,
|
||||
posyanduAktif: 8,
|
||||
};
|
||||
|
||||
// Sample data for health progress
|
||||
const healthProgress = [
|
||||
{ label: "Imunisasi Lengkap", value: 92, color: "green" },
|
||||
{ label: "Pemeriksaan Rutin", value: 88, color: "blue" },
|
||||
{ label: "Gizi Baik", value: 86, color: "teal" },
|
||||
{ label: "Target Stunting", value: 14, color: "red" },
|
||||
];
|
||||
|
||||
// Sample data for posyandu schedule
|
||||
const posyanduSchedule = [
|
||||
{ nama: "Posyandu Mawar", tanggal: "Senin, 15 Feb 2026", jam: "08:00 - 11:00" },
|
||||
{ nama: "Posyandu Melati", tanggal: "Selasa, 16 Feb 2026", jam: "08:00 - 11:00" },
|
||||
{ nama: "Posyandu Dahlia", tanggal: "Rabu, 17 Feb 2026", jam: "08:00 - 11:00" },
|
||||
{ nama: "Posyandu Anggrek", tanggal: "Kamis, 18 Feb 2026", jam: "08:00 - 11:00" },
|
||||
];
|
||||
|
||||
// Sample data for education stats
|
||||
const educationStats = {
|
||||
siswa: {
|
||||
tk: 125,
|
||||
sd: 480,
|
||||
smp: 210,
|
||||
sma: 150,
|
||||
},
|
||||
sekolah: {
|
||||
jumlah: 8,
|
||||
guru: 42,
|
||||
}
|
||||
};
|
||||
|
||||
// Sample data for scholarships
|
||||
const scholarshipData = {
|
||||
penerima: 45,
|
||||
dana: "Rp 1.200.000.000",
|
||||
tahunAjaran: "2025/2026",
|
||||
};
|
||||
|
||||
// Sample data for cultural events
|
||||
const culturalEvents = [
|
||||
{ nama: "Hari Kesaktian Pancasila", tanggal: "1 Oktober 2025", lokasi: "Balai Desa" },
|
||||
{ nama: "Festival Budaya Desa", tanggal: "20 Mei 2026", lokasi: "Lapangan Desa" },
|
||||
{ nama: "Perayaan HUT Desa", tanggal: "17 Agustus 2026", lokasi: "Balai Desa" },
|
||||
];
|
||||
|
||||
return (
|
||||
<Stack gap="lg">
|
||||
{/* Page Header */}
|
||||
<Group justify="space-between" align="center">
|
||||
<Title order={2} c={dark ? "dark.0" : "black"}>
|
||||
Sosial Desa
|
||||
</Title>
|
||||
</Group>
|
||||
|
||||
{/* Health Statistics Cards */}
|
||||
<Grid gutter="md">
|
||||
<GridCol span={{ base: 12, sm: 6, md: 3 }}>
|
||||
<Card withBorder radius="md" padding="lg">
|
||||
<Group justify="space-between" align="center">
|
||||
<Stack gap={0}>
|
||||
<Text size="sm" c={dark ? "dark.3" : "dimmed"}>
|
||||
Ibu Hamil Aktif
|
||||
</Text>
|
||||
<Text size="xl" fw={700} c={dark ? "dark.0" : "black"}>
|
||||
{healthStats.ibuHamil}
|
||||
</Text>
|
||||
</Stack>
|
||||
<ThemeIcon variant="light" color="darmasaba-blue" size="xl" radius="xl">
|
||||
<IconHeartbeat size={24} />
|
||||
</ThemeIcon>
|
||||
</Group>
|
||||
</Card>
|
||||
</GridCol>
|
||||
|
||||
<GridCol span={{ base: 12, sm: 6, md: 3 }}>
|
||||
<Card withBorder radius="md" padding="lg">
|
||||
<Group justify="space-between" align="center">
|
||||
<Stack gap={0}>
|
||||
<Text size="sm" c={dark ? "dark.3" : "dimmed"}>
|
||||
Balita Terdaftar
|
||||
</Text>
|
||||
<Text size="xl" fw={700} c={dark ? "dark.0" : "black"}>
|
||||
{healthStats.balita}
|
||||
</Text>
|
||||
</Stack>
|
||||
<ThemeIcon variant="light" color="darmasaba-success" size="xl" radius="xl">
|
||||
<IconBabyCarriage size={24} />
|
||||
</ThemeIcon>
|
||||
</Group>
|
||||
</Card>
|
||||
</GridCol>
|
||||
|
||||
<GridCol span={{ base: 12, sm: 6, md: 3 }}>
|
||||
<Card withBorder radius="md" padding="lg">
|
||||
<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 withBorder radius="md" padding="lg">
|
||||
<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 withBorder radius="md" p="lg">
|
||||
<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>
|
||||
))}
|
||||
</Stack>
|
||||
</Card>
|
||||
|
||||
<Grid gutter="md">
|
||||
{/* Jadwal Posyandu */}
|
||||
<GridCol span={{ base: 12, lg: 6 }}>
|
||||
<Card withBorder radius="md" p="lg">
|
||||
<Title order={3} mb="md" c={dark ? "dark.0" : "black"}>Jadwal Posyandu</Title>
|
||||
<Stack gap="sm">
|
||||
{posyanduSchedule.map((item, index) => (
|
||||
<Card key={index} withBorder radius="md" p="md">
|
||||
<Group justify="space-between">
|
||||
<Stack gap={0}>
|
||||
<Text fw={500} c={dark ? "dark.0" : "black"}>{item.nama}</Text>
|
||||
<Text size="sm" c={dark ? "dark.3" : "dimmed"}>{item.tanggal}</Text>
|
||||
</Stack>
|
||||
<Badge variant="light" color="darmasaba-blue">
|
||||
{item.jam}
|
||||
</Badge>
|
||||
</Group>
|
||||
</Card>
|
||||
))}
|
||||
</Stack>
|
||||
</Card>
|
||||
</GridCol>
|
||||
|
||||
{/* Pendidikan */}
|
||||
<GridCol span={{ base: 12, lg: 6 }}>
|
||||
<Card withBorder radius="md" p="lg">
|
||||
<Title order={3} mb="md" c={dark ? "dark.0" : "black"}>Pendidikan</Title>
|
||||
<Stack gap="md">
|
||||
<Group justify="space-between">
|
||||
<Text fw={500} c={dark ? "dark.0" : "black"}>TK / PAUD</Text>
|
||||
<Text fw={700} c={dark ? "dark.0" : "black"}>{educationStats.siswa.tk}</Text>
|
||||
</Group>
|
||||
<Group justify="space-between">
|
||||
<Text fw={500} c={dark ? "dark.0" : "black"}>SD</Text>
|
||||
<Text fw={700} c={dark ? "dark.0" : "black"}>{educationStats.siswa.sd}</Text>
|
||||
</Group>
|
||||
<Group justify="space-between">
|
||||
<Text fw={500} c={dark ? "dark.0" : "black"}>SMP</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>
|
||||
|
||||
<Card withBorder radius="md" p="md" mt="md">
|
||||
<Group justify="space-between">
|
||||
<Text fw={500} c={dark ? "dark.0" : "black"}>Jumlah Lembaga Pendidikan</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 withBorder radius="md" p="lg" bg={dark ? "dark.8" : "darmasaba-blue.0"}>
|
||||
<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} />
|
||||
</ThemeIcon>
|
||||
</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>
|
||||
|
||||
{/* Kalender Event Budaya */}
|
||||
<GridCol span={{ base: 12, lg: 6 }}>
|
||||
<Card withBorder radius="md" p="lg">
|
||||
<Title order={3} mb="md" c={dark ? "dark.0" : "black"}>Kalender Event Budaya</Title>
|
||||
<List spacing="sm">
|
||||
{culturalEvents.map((event, index) => (
|
||||
<List.Item key={index} icon={
|
||||
<ThemeIcon color="darmasaba-blue" size={24} radius="xl">
|
||||
<IconCalendarEvent size={12} />
|
||||
</ThemeIcon>
|
||||
}>
|
||||
<Group justify="space-between">
|
||||
<Text fw={500} c={dark ? "dark.0" : "black"}>{event.nama}</Text>
|
||||
<Text size="sm" c={dark ? "dark.3" : "dimmed"}>{event.lokasi}</Text>
|
||||
</Group>
|
||||
<Text size="sm" c={dark ? "dark.3" : "dimmed"}>{event.tanggal}</Text>
|
||||
</List.Item>
|
||||
))}
|
||||
</List>
|
||||
</Card>
|
||||
</GridCol>
|
||||
</Grid>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
export default SosialPage;
|
||||
Reference in New Issue
Block a user