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:
2026-03-13 12:05:46 +08:00
parent 6c3e7c86b6
commit 89c8ca83a8
55 changed files with 3810 additions and 2917 deletions

View File

@@ -1,296 +1,389 @@
import { useState } from "react"; import {
import { Badge,
Card, Button,
Grid, Card,
GridCol, Grid,
Group, GridCol,
Text, Group,
Title, Select,
Button, Stack,
Badge, Table,
Table, Text,
Stack, Title,
Select, useMantineColorScheme,
useMantineColorScheme
} from "@mantine/core"; } from "@mantine/core";
import { IconBuildingStore, IconCategory, IconCurrency, IconUsers } from "@tabler/icons-react"; import {
IconBuildingStore,
IconCategory,
IconCurrency,
IconUsers,
} from "@tabler/icons-react";
import { useState } from "react";
const BumdesPage = () => { const BumdesPage = () => {
const { colorScheme } = useMantineColorScheme(); const { colorScheme } = useMantineColorScheme();
const dark = colorScheme === 'dark'; 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 [timeFilter, setTimeFilter] = useState<string>("bulan");
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 // Sample data for KPI cards
const productSales = [ const kpiData = [
{ {
produk: "Beras Premium Organik", title: "UMKM Aktif",
penjualanBulanIni: "Rp 8.500.000", value: 45,
bulanLalu: "Rp 8.500.000", icon: <IconUsers size={24} />,
trend: 10, color: "darmasaba-blue",
volume: "650 Kg", },
stok: "850 Kg", {
}, title: "UMKM Terdaftar",
{ value: 68,
produk: "Keripik Singkong", icon: <IconBuildingStore size={24} />,
penjualanBulanIni: "Rp 4.200.000", color: "darmasaba-success",
bulanLalu: "Rp 3.800.000", },
trend: 10, {
volume: "320 Kg", title: "Omzet",
stok: "120 Kg", value: "Rp 48.000.000",
}, icon: <IconCurrency size={24} />,
{ color: "darmasaba-warning",
produk: "Madu Alami", },
penjualanBulanIni: "Rp 3.750.000", {
bulanLalu: "Rp 4.100.000", title: "Kategori UMKM",
trend: -8, value: 34,
volume: "150 Liter", icon: <IconCategory size={24} />,
stok: "45 Liter", color: "darmasaba-danger",
}, },
{ ];
produk: "Kecap Tradisional",
penjualanBulanIni: "Rp 2.800.000",
bulanLalu: "Rp 2.500.000",
trend: 12,
volume: "280 Botol",
stok: "95 Botol",
},
];
return ( // Sample data for top products
<Stack gap="lg"> const topProducts = [
{/* KPI Cards */} {
<Grid gutter="md"> rank: 1,
{kpiData.map((kpi, index) => ( name: "Beras Premium Organik",
<GridCol key={index} span={{ base: 12, sm: 6, md: 3 }}> umkmOwner: "Warung Pak Joko",
<Card p="md" radius="md" withBorder bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }}> growth: "+12%",
<Group justify="space-between" align="center"> },
<Stack gap={0}> {
<Text size="sm" c={dark ? "dark.3" : "dimmed"}> rank: 2,
{kpi.title} name: "Keripik Singkong",
</Text> umkmOwner: "Ibu Sari Snack",
<Text size="xl" fw={700} c={dark ? "dark.0" : "black"}> growth: "+8%",
{typeof kpi.value === 'number' ? kpi.value.toLocaleString() : kpi.value} },
</Text> {
</Stack> rank: 3,
<Badge name: "Madu Alami",
variant="light" umkmOwner: "Peternakan Lebah",
color={kpi.color} growth: "+5%",
p={8} },
radius="md" ];
>
{kpi.icon}
</Badge>
</Group>
</Card>
</GridCol>
))}
</Grid>
{/* Update Penjualan Produk Header */} // Sample data for product sales
<Card p="md" radius="md" withBorder bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }}> const productSales = [
<Group justify="space-between" align="center" px="md" py="xs"> {
<Title order={3} c={dark ? "dark.0" : "black"}> produk: "Beras Premium Organik",
Update Penjualan Produk penjualanBulanIni: "Rp 8.500.000",
</Title> bulanLalu: "Rp 8.500.000",
<Group> trend: 10,
<Button volume: "650 Kg",
variant={timeFilter === "minggu" ? "filled" : "light"} stok: "850 Kg",
onClick={() => setTimeFilter("minggu")} },
color="darmasaba-blue" {
> produk: "Keripik Singkong",
Minggu ini penjualanBulanIni: "Rp 4.200.000",
</Button> bulanLalu: "Rp 3.800.000",
<Button trend: 10,
variant={timeFilter === "bulan" ? "filled" : "light"} volume: "320 Kg",
onClick={() => setTimeFilter("bulan")} stok: "120 Kg",
color="darmasaba-blue" },
> {
Bulan ini produk: "Madu Alami",
</Button> penjualanBulanIni: "Rp 3.750.000",
</Group> bulanLalu: "Rp 4.100.000",
</Group> trend: -8,
</Card> 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",
},
];
<Grid gutter="md"> return (
{/* Produk Unggulan (Left Column) */} <Stack gap="lg">
<GridCol span={{ base: 12, lg: 4 }}> {/* KPI Cards */}
<Stack gap="md"> <Grid gutter="md">
{/* Total Penjualan, Produk Aktif, Total Transaksi */} {kpiData.map((kpi, index) => (
<Card p="md" radius="md" withBorder bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }}> <GridCol key={index} span={{ base: 12, sm: 6, md: 3 }}>
<Stack gap="md"> <Card
<Group justify="space-between"> p="md"
<Text size="sm" c={dark ? "dark.3" : "dimmed"}>Total Penjualan</Text> radius="md"
<Text size="lg" fw={700} c={dark ? "dark.0" : "black"}>Rp 28.500.000</Text> withBorder
</Group> bg={dark ? "#141D34" : "white"}
<Group justify="space-between"> style={{ borderColor: dark ? "#141D34" : "white" }}
<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 justify="space-between" align="center">
</Group> <Stack gap={0}>
<Group justify="space-between"> <Text size="sm" c={dark ? "dark.3" : "dimmed"}>
<Text size="sm" c={dark ? "dark.3" : "dimmed"}>Total Transaksi</Text> {kpi.title}
<Text size="lg" fw={700} c={dark ? "dark.0" : "black"}>1.240 Transaksi</Text> </Text>
</Group> <Text size="xl" fw={700} c={dark ? "dark.0" : "black"}>
</Stack> {typeof kpi.value === "number"
</Card> ? kpi.value.toLocaleString()
: kpi.value}
</Text>
</Stack>
<Badge variant="light" color={kpi.color} p={8} radius="md">
{kpi.icon}
</Badge>
</Group>
</Card>
</GridCol>
))}
</Grid>
{/* Top 3 Produk Terlaris */} {/* Update Penjualan Produk Header */}
<Card p="md" radius="md" withBorder bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }}> <Card
<Title order={4} mb="md" c={dark ? "dark.0" : "black"}>Top 3 Produk Terlaris</Title> p="md"
<Stack gap="sm"> radius="md"
{topProducts.map((product) => ( withBorder
<Group key={product.rank} justify="space-between" align="center"> bg={dark ? "#141D34" : "white"}
<Group gap="sm"> style={{ borderColor: dark ? "#141D34" : "white" }}
<Badge >
variant="filled" <Group justify="space-between" align="center" px="md" py="xs">
color={product.rank === 1 ? "gold" : product.rank === 2 ? "gray" : "bronze"} <Title order={3} c={dark ? "dark.0" : "black"}>
radius="xl" Update Penjualan Produk
size="lg" </Title>
> <Group>
{product.rank} <Button
</Badge> variant={timeFilter === "minggu" ? "filled" : "light"}
<Stack gap={0}> onClick={() => setTimeFilter("minggu")}
<Text fw={500} c={dark ? "dark.0" : "black"}>{product.name}</Text> color="darmasaba-blue"
<Text size="sm" c={dark ? "dark.3" : "dimmed"}>{product.umkmOwner}</Text> >
</Stack> Minggu ini
</Group> </Button>
<Badge <Button
variant="light" variant={timeFilter === "bulan" ? "filled" : "light"}
color={product.growth.startsWith('+') ? "green" : "red"} onClick={() => setTimeFilter("bulan")}
> color="darmasaba-blue"
{product.growth} >
</Badge> Bulan ini
</Group> </Button>
))} </Group>
</Stack> </Group>
</Card> </Card>
</Stack>
</GridCol>
{/* Detail Penjualan Produk (Right Column) */} <Grid gutter="md">
<GridCol span={{ base: 12, lg: 8 }}> {/* Produk Unggulan (Left Column) */}
<Card p="md" radius="md" withBorder bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }}> <GridCol span={{ base: 12, lg: 4 }}>
<Group justify="space-between" mb="md"> <Stack gap="md">
<Title order={4} c={dark ? "dark.0" : "black"}>Detail Penjualan Produk</Title> {/* Total Penjualan, Produk Aktif, Total Transaksi */}
<Select <Card
placeholder="Filter kategori" p="md"
data={[ radius="md"
{ value: 'semua', label: 'Semua Kategori' }, withBorder
{ value: 'makanan', label: 'Makanan' }, bg={dark ? "#141D34" : "white"}
{ value: 'minuman', label: 'Minuman' }, style={{ borderColor: dark ? "#141D34" : "white" }}
{ value: 'kerajinan', label: 'Kerajinan' }, >
]} <Stack gap="md">
defaultValue="semua" <Group justify="space-between">
w={200} <Text size="sm" c={dark ? "dark.3" : "dimmed"}>
/> Total Penjualan
</Group> </Text>
<Text size="lg" fw={700} c={dark ? "dark.0" : "black"}>
<Table striped highlightOnHover withColumnBorders> Rp 28.500.000
<Table.Thead> </Text>
<Table.Tr> </Group>
<Table.Th><Text c={dark ? "white" : "dimmed"}>Produk</Text></Table.Th> <Group justify="space-between">
<Table.Th><Text c={dark ? "white" : "dimmed"}>Penjualan Bulan Ini</Text></Table.Th> <Text size="sm" c={dark ? "dark.3" : "dimmed"}>
<Table.Th><Text c={dark ? "white" : "dimmed"}>Bulan Lalu</Text></Table.Th> Produk Aktif
<Table.Th><Text c={dark ? "white" : "dimmed"}>Trend</Text></Table.Th> </Text>
<Table.Th><Text c={dark ? "white" : "dimmed"}>Volume</Text></Table.Th> <Text size="lg" fw={700} c={dark ? "dark.0" : "black"}>
<Table.Th><Text c={dark ? "white" : "dimmed"}>Stok</Text></Table.Th> 124 Produk
<Table.Th><Text c={dark ? "white" : "dimmed"}>Aksi</Text></Table.Th> </Text>
</Table.Tr> </Group>
</Table.Thead> <Group justify="space-between">
<Table.Tbody> <Text size="sm" c={dark ? "dark.3" : "dimmed"}>
{productSales.map((product, index) => ( Total Transaksi
<Table.Tr key={index}> </Text>
<Table.Td> <Text size="lg" fw={700} c={dark ? "dark.0" : "black"}>
<Text fw={500} c={dark ? "dark.0" : "black"}>{product.produk}</Text> 1.240 Transaksi
</Table.Td> </Text>
<Table.Td> </Group>
<Text fz={"sm"} c={dark ? "dark.0" : "black"}>{product.penjualanBulanIni}</Text> </Stack>
</Table.Td> </Card>
<Table.Td>
<Text fz={"sm"} c={dark ? "white" : "dimmed"}>{product.bulanLalu}</Text> {/* Top 3 Produk Terlaris */}
</Table.Td> <Card
<Table.Td> p="md"
<Group gap="xs"> radius="md"
<Text c={product.trend >= 0 ? "green" : "red"}> withBorder
{product.trend >= 0 ? '↑' : '↓'} {Math.abs(product.trend)}% bg={dark ? "#141D34" : "white"}
</Text> style={{ borderColor: dark ? "#141D34" : "white" }}
</Group> >
</Table.Td> <Title order={4} mb="md" c={dark ? "dark.0" : "black"}>
<Table.Td> Top 3 Produk Terlaris
<Text fz={"sm"} c={dark ? "dark.0" : "black"}>{product.volume}</Text> </Title>
</Table.Td> <Stack gap="sm">
<Table.Td> {topProducts.map((product) => (
<Badge <Group
variant="light" key={product.rank}
color={parseInt(product.stok) > 200 ? "green" : "yellow"} justify="space-between"
> align="center"
{product.stok} >
</Badge> <Group gap="sm">
</Table.Td> <Badge
<Table.Td> variant="filled"
<Button variant="subtle" size="compact-sm" color="darmasaba-blue"> color={
Detail product.rank === 1
</Button> ? "gold"
</Table.Td> : product.rank === 2
</Table.Tr> ? "gray"
))} : "bronze"
</Table.Tbody> }
</Table> radius="xl"
</Card> size="lg"
</GridCol> >
</Grid> {product.rank}
</Stack> </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
p="md"
radius="md"
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"
}
>
{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; export default BumdesPage;

View File

@@ -1,6 +1,6 @@
import type { ReactNode } from "react";
// Import Mantine components directly // Import Mantine components directly
import { Group, Text, ThemeIcon, Badge } from "@mantine/core"; import { Badge, Group, Text, ThemeIcon } from "@mantine/core";
import type { ReactNode } from "react";
// Import custom Card and its sub-components // Import custom Card and its sub-components
import { Card } from "./ui/card"; import { Card } from "./ui/card";

View File

@@ -13,25 +13,25 @@ import {
Pie, Pie,
PieChart, PieChart,
ResponsiveContainer, ResponsiveContainer,
Tooltip, // Added Tooltip import
XAxis, XAxis,
YAxis, YAxis,
Tooltip, // Added Tooltip import
} from "recharts"; } from "recharts";
// Import Mantine components // Import Mantine components
import { import {
Grid,
Stack,
Group,
Text,
Title,
ActionIcon, ActionIcon,
Progress,
Box,
Badge, Badge,
ThemeIcon, Box,
Card, // Added for icon containers Card, // Added for icon containers
Grid,
Group,
Progress,
Stack,
Text,
ThemeIcon,
Title,
useMantineColorScheme, // Add this import useMantineColorScheme, // Add this import
} from "@mantine/core"; } from "@mantine/core";
@@ -68,13 +68,20 @@ const eventData = [
export function DashboardContent() { export function DashboardContent() {
const { colorScheme } = useMantineColorScheme(); const { colorScheme } = useMantineColorScheme();
const dark = colorScheme === 'dark'; const dark = colorScheme === "dark";
return ( return (
<Stack gap="lg"> <Stack gap="lg">
{/* Stats Cards */} {/* Stats Cards */}
<Grid gutter="md"> <Grid gutter="md">
<Grid.Col span={{ base: 12, md: 6, lg: 3 }}> <Grid.Col span={{ base: 12, md: 6, lg: 3 }}>
<Card p="md" style={{borderColor: dark ? "#141D34" : "white"}} radius="md" h="100%" withBorder bg={dark ? "#141D34" : "white"}> <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%"> <Group justify="space-between" align="flex-start" w="100%">
<Box style={{ flex: 1 }}> <Box style={{ flex: 1 }}>
<Text size="sm" c="dimmed" mb="xs"> <Text size="sm" c="dimmed" mb="xs">
@@ -92,14 +99,26 @@ export function DashboardContent() {
12% dari minggu lalu +12% 12% dari minggu lalu +12%
</Text> </Text>
</Box> </Box>
<ThemeIcon variant="filled" size="xl" radius="xl" color={dark ? 'gray' : 'darmasaba-blue'}> <ThemeIcon
variant="filled"
size="xl"
radius="xl"
color={dark ? "gray" : "darmasaba-blue"}
>
<FileText style={{ width: "70%", height: "70%" }} /> <FileText style={{ width: "70%", height: "70%" }} />
</ThemeIcon> </ThemeIcon>
</Group> </Group>
</Card> </Card>
</Grid.Col> </Grid.Col>
<Grid.Col span={{ base: 12, md: 6, lg: 3 }}> <Grid.Col span={{ base: 12, md: 6, lg: 3 }}>
<Card p="md" style={{borderColor: dark ? "#141D34" : "white"}} radius="md" h="100%" withBorder bg={dark ? "#141D34" : "white"}> <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%"> <Group justify="space-between" align="flex-start" w="100%">
<Box style={{ flex: 1 }}> <Box style={{ flex: 1 }}>
<Text size="sm" c="dimmed" mb="xs"> <Text size="sm" c="dimmed" mb="xs">
@@ -114,14 +133,26 @@ export function DashboardContent() {
14 baru, 14 diproses 14 baru, 14 diproses
</Text> </Text>
</Box> </Box>
<ThemeIcon variant="filled" size="xl" radius="xl" color={dark ? 'gray' : 'darmasaba-blue'}> <ThemeIcon
variant="filled"
size="xl"
radius="xl"
color={dark ? "gray" : "darmasaba-blue"}
>
<MessageCircle style={{ width: "70%", height: "70%" }} /> <MessageCircle style={{ width: "70%", height: "70%" }} />
</ThemeIcon> </ThemeIcon>
</Group> </Group>
</Card> </Card>
</Grid.Col> </Grid.Col>
<Grid.Col span={{ base: 12, md: 6, lg: 3 }}> <Grid.Col span={{ base: 12, md: 6, lg: 3 }}>
<Card p="md" style={{borderColor: dark ? "#141D34" : "white"}} radius="md" h="100%" withBorder bg={dark ? "#141D34" : "white"}> <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%"> <Group justify="space-between" align="flex-start" w="100%">
<Box style={{ flex: 1 }}> <Box style={{ flex: 1 }}>
<Text size="sm" c="dimmed" mb="xs"> <Text size="sm" c="dimmed" mb="xs">
@@ -139,14 +170,26 @@ export function DashboardContent() {
+8% +8%
</Text> </Text>
</Box> </Box>
<ThemeIcon variant="filled" size="xl" radius="xl" color={dark ? 'gray' : 'darmasaba-blue'}> <ThemeIcon
variant="filled"
size="xl"
radius="xl"
color={dark ? "gray" : "darmasaba-blue"}
>
<CheckCircle style={{ width: "70%", height: "70%" }} /> <CheckCircle style={{ width: "70%", height: "70%" }} />
</ThemeIcon> </ThemeIcon>
</Group> </Group>
</Card> </Card>
</Grid.Col> </Grid.Col>
<Grid.Col span={{ base: 12, md: 6, lg: 3 }}> <Grid.Col span={{ base: 12, md: 6, lg: 3 }}>
<Card p="md" style={{borderColor: dark ? "#141D34" : "white"}} radius="md" h="100%" withBorder bg={dark ? "#141D34" : "white"}> <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%"> <Group justify="space-between" align="flex-start" w="100%">
<Box style={{ flex: 1 }}> <Box style={{ flex: 1 }}>
<Text size="sm" c="dimmed" mb="xs"> <Text size="sm" c="dimmed" mb="xs">
@@ -161,7 +204,12 @@ export function DashboardContent() {
dari 482 responden dari 482 responden
</Text> </Text>
</Box> </Box>
<ThemeIcon variant="filled" size="xl" radius="xl" color={dark ? 'gray' : 'darmasaba-blue'}> <ThemeIcon
variant="filled"
size="xl"
radius="xl"
color={dark ? "gray" : "darmasaba-blue"}
>
<Users style={{ width: "70%", height: "70%" }} /> <Users style={{ width: "70%", height: "70%" }} />
</ThemeIcon> </ThemeIcon>
</Group> </Group>
@@ -171,7 +219,13 @@ export function DashboardContent() {
<Grid gutter="lg"> <Grid gutter="lg">
{/* Bar Chart */} {/* Bar Chart */}
<Grid.Col span={{ base: 12, lg: 6 }}> <Grid.Col span={{ base: 12, lg: 6 }}>
<Card p="md" style={{borderColor: dark ? "#141D34" : "white"}} radius="md" withBorder bg={dark ? "#141D34" : "white"}> <Card
p="md"
style={{ borderColor: dark ? "#141D34" : "white" }}
radius="md"
withBorder
bg={dark ? "#141D34" : "white"}
>
<Group justify="space-between" mb="md"> <Group justify="space-between" mb="md">
<Box> <Box>
<Title order={4} mb={5}> <Title order={4} mb={5}>
@@ -232,7 +286,13 @@ export function DashboardContent() {
{/* Pie Chart */} {/* Pie Chart */}
<Grid.Col span={{ base: 12, lg: 6 }}> <Grid.Col span={{ base: 12, lg: 6 }}>
<Card p="md" style={{borderColor: dark ? "#141D34" : "white"}} radius="md" withBorder bg={dark ? "#141D34" : "white"}> <Card
p="md"
style={{ borderColor: dark ? "#141D34" : "white" }}
radius="md"
withBorder
bg={dark ? "#141D34" : "white"}
>
<Title order={4} mb={5}> <Title order={4} mb={5}>
Tingkat Kepuasan Tingkat Kepuasan
</Title> </Title>
@@ -259,19 +319,35 @@ export function DashboardContent() {
</ResponsiveContainer> </ResponsiveContainer>
<Group justify="center" gap="md" mt="md"> <Group justify="center" gap="md" mt="md">
<Group gap="xs"> <Group gap="xs">
<Box w={12} h={12} style={{ backgroundColor: COLORS[0], borderRadius: "50%" }} /> <Box
w={12}
h={12}
style={{ backgroundColor: COLORS[0], borderRadius: "50%" }}
/>
<Text size="sm">Sangat puas (0%)</Text> <Text size="sm">Sangat puas (0%)</Text>
</Group> </Group>
<Group gap="xs"> <Group gap="xs">
<Box w={12} h={12} style={{ backgroundColor: COLORS[1], borderRadius: "50%" }} /> <Box
w={12}
h={12}
style={{ backgroundColor: COLORS[1], borderRadius: "50%" }}
/>
<Text size="sm">Puas (0%)</Text> <Text size="sm">Puas (0%)</Text>
</Group> </Group>
<Group gap="xs"> <Group gap="xs">
<Box w={12} h={12} style={{ backgroundColor: COLORS[2], borderRadius: "50%" }} /> <Box
w={12}
h={12}
style={{ backgroundColor: COLORS[2], borderRadius: "50%" }}
/>
<Text size="sm">Cukup (0%)</Text> <Text size="sm">Cukup (0%)</Text>
</Group> </Group>
<Group gap="xs"> <Group gap="xs">
<Box w={12} h={12} style={{ backgroundColor: COLORS[3], borderRadius: "50%" }} /> <Box
w={12}
h={12}
style={{ backgroundColor: COLORS[3], borderRadius: "50%" }}
/>
<Text size="sm">Kurang (0%)</Text> <Text size="sm">Kurang (0%)</Text>
</Group> </Group>
</Group> </Group>
@@ -283,7 +359,13 @@ export function DashboardContent() {
<Grid gutter="lg"> <Grid gutter="lg">
{/* Divisi Teraktif */} {/* Divisi Teraktif */}
<Grid.Col span={{ base: 12, lg: 6 }}> <Grid.Col span={{ base: 12, lg: 6 }}>
<Card p="md" style={{borderColor: dark ? "#141D34" : "white"}} radius="md" withBorder bg={dark ? "#141D34" : "white"}> <Card
p="md"
style={{ borderColor: dark ? "#141D34" : "white" }}
radius="md"
withBorder
bg={dark ? "#141D34" : "white"}
>
<Group gap="xs" mb="lg"> <Group gap="xs" mb="lg">
<Box> <Box>
{/* Original SVG icon */} {/* Original SVG icon */}
@@ -355,7 +437,13 @@ export function DashboardContent() {
{/* Kalender */} {/* Kalender */}
<Grid.Col span={{ base: 12, lg: 6 }}> <Grid.Col span={{ base: 12, lg: 6 }}>
<Card p="md" style={{borderColor: dark ? "#141D34" : "white"}} radius="md" withBorder bg={dark ? "#141D34" : "white"}> <Card
p="md"
style={{ borderColor: dark ? "#141D34" : "white" }}
radius="md"
withBorder
bg={dark ? "#141D34" : "white"}
>
<Group gap="xs" mb="lg"> <Group gap="xs" mb="lg">
<Calendar style={{ width: 20, height: 20 }} /> <Calendar style={{ width: 20, height: 20 }} />
<Title order={4}>Kalender & Kegiatan Mendatang</Title> <Title order={4}>Kalender & Kegiatan Mendatang</Title>
@@ -364,7 +452,10 @@ export function DashboardContent() {
{eventData.map((event, index) => ( {eventData.map((event, index) => (
<Box <Box
key={index} key={index}
style={{ borderLeft: "4px solid var(--mantine-color-blue-filled)", paddingLeft: 12 }} style={{
borderLeft: "4px solid var(--mantine-color-blue-filled)",
paddingLeft: 12,
}}
> >
<Text size="sm" c="dimmed"> <Text size="sm" c="dimmed">
{event.date} {event.date}
@@ -378,7 +469,13 @@ export function DashboardContent() {
</Grid> </Grid>
{/* APBDes Chart */} {/* APBDes Chart */}
<Card p="md" style={{borderColor: dark ? "#141D34" : "white"}} radius="md" withBorder bg={dark ? "#141D34" : "white"}> <Card
p="md"
style={{ borderColor: dark ? "#141D34" : "white" }}
radius="md"
withBorder
bg={dark ? "#141D34" : "white"}
>
<Title order={4} mb="lg"> <Title order={4} mb="lg">
Grafik APBDes Grafik APBDes
</Title> </Title>
@@ -387,19 +484,37 @@ export function DashboardContent() {
<Text size="sm" fw={500} w={60}> <Text size="sm" fw={500} w={60}>
Belanja Belanja
</Text> </Text>
<Progress value={70} size="lg" radius="xl" color="blue" style={{ flex: 1 }} /> <Progress
value={70}
size="lg"
radius="xl"
color="blue"
style={{ flex: 1 }}
/>
</Group> </Group>
<Group align="center" gap="md"> <Group align="center" gap="md">
<Text size="sm" fw={500} w={60}> <Text size="sm" fw={500} w={60}>
Pendapatan Pendapatan
</Text> </Text>
<Progress value={90} size="lg" radius="xl" color="green" style={{ flex: 1 }} /> <Progress
value={90}
size="lg"
radius="xl"
color="green"
style={{ flex: 1 }}
/>
</Group> </Group>
<Group align="center" gap="md"> <Group align="center" gap="md">
<Text size="sm" fw={500} w={60}> <Text size="sm" fw={500} w={60}>
Pembangunan Pembangunan
</Text> </Text>
<Progress value={50} size="lg" radius="xl" color="orange" style={{ flex: 1 }} /> <Progress
value={50}
size="lg"
radius="xl"
color="orange"
style={{ flex: 1 }}
/>
</Group> </Group>
</Stack> </Stack>
</Card> </Card>

View File

@@ -1,17 +1,22 @@
import React from "react"; import { BarChart, PieChart } from "@mantine/charts";
import { import {
Box,
Card, Card,
Title, Grid,
Text,
Group, Group,
Stack, Stack,
Grid,
Box,
Table, Table,
Text,
Title,
useMantineColorScheme, useMantineColorScheme,
} from "@mantine/core"; } from "@mantine/core";
import { IconBabyCarriage, IconSkull, IconArrowUp, IconArrowDown } from "@tabler/icons-react"; import {
import { BarChart, PieChart } from "@mantine/charts"; IconArrowDown,
IconArrowUp,
IconBabyCarriage,
IconSkull,
} from "@tabler/icons-react";
import React from "react";
// Sample Data // Sample Data
const kpiData = [ const kpiData = [
@@ -71,7 +76,11 @@ const kpiData = [
value: "23", value: "23",
sub: "Tahun ini", sub: "Tahun ini",
icon: ( icon: (
<IconBabyCarriage className="h-6 w-6 text-muted-foreground" role="img" aria-label="Icon kelahiran" /> <IconBabyCarriage
className="h-6 w-6 text-muted-foreground"
role="img"
aria-label="Icon kelahiran"
/>
), ),
}, },
{ {
@@ -136,10 +145,30 @@ const banjarData = [
]; ];
const dynamicStats = [ const dynamicStats = [
{ title: "Kelahiran", value: "23", icon: <IconBabyCarriage size={16} />, color: "green" }, {
{ title: "Kematian", value: "12", icon: <IconSkull size={16} />, color: "red" }, title: "Kelahiran",
{ title: "Pindah Masuk", value: "45", icon: <IconArrowDown size={16} />, color: "blue" }, value: "23",
{ title: "Pindah Keluar", value: "32", icon: <IconArrowUp size={16} />, color: "orange" }, 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 = () => {
@@ -152,14 +181,22 @@ const DemografiPekerjaan = () => {
<Grid gutter="lg"> <Grid gutter="lg">
{kpiData.map((kpi) => ( {kpiData.map((kpi) => (
<Grid.Col key={kpi.id} span={{ base: 12, md: 6, lg: 3 }}> <Grid.Col key={kpi.id} span={{ base: 12, md: 6, lg: 3 }}>
<Card p="md" radius="md" withBorder bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }}> <Card
p="md"
radius="md"
withBorder
bg={dark ? "#141D34" : "white"}
style={{ borderColor: dark ? "#141D34" : "white" }}
>
<Group justify="space-between" align="flex-start" mb="xs"> <Group justify="space-between" align="flex-start" mb="xs">
<Text size="sm" fw={500} c={dark ? "dark.3" : "dimmed"}> <Text size="sm" fw={500} c={dark ? "dark.3" : "dimmed"}>
{kpi.title} {kpi.title}
</Text> </Text>
{React.cloneElement(kpi.icon, { {React.cloneElement(kpi.icon, {
className: "h-6 w-6", className: "h-6 w-6",
color: dark ? "var(--mantine-color-dark-3)" : "var(--mantine-color-dimmed)", color: dark
? "var(--mantine-color-dark-3)"
: "var(--mantine-color-dimmed)",
})} })}
</Group> </Group>
<Title order={3} fw={700} c={dark ? "dark.0" : "black"} mt="xs"> <Title order={3} fw={700} c={dark ? "dark.0" : "black"} mt="xs">
@@ -173,7 +210,9 @@ const DemografiPekerjaan = () => {
? "green" ? "green"
: kpi.deltaType === "negative" : kpi.deltaType === "negative"
? "red" ? "red"
: dark ? "dark.3" : "dimmed" : dark
? "dark.3"
: "dimmed"
} }
mt={4} mt={4}
> >
@@ -194,7 +233,13 @@ const DemografiPekerjaan = () => {
<Grid gutter="lg"> <Grid gutter="lg">
{/* Grafik Pengelompokan Umur */} {/* Grafik Pengelompokan Umur */}
<Grid.Col span={{ base: 12, lg: 6 }}> <Grid.Col span={{ base: 12, lg: 6 }}>
<Card p="md" radius="md" withBorder bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }}> <Card
p="md"
radius="md"
withBorder
bg={dark ? "#141D34" : "white"}
style={{ borderColor: dark ? "#141D34" : "white" }}
>
<Title order={3} fw={500} mb="md"> <Title order={3} fw={500} mb="md">
Grafik Pengelompokan Umur Grafik Pengelompokan Umur
</Title> </Title>
@@ -202,7 +247,7 @@ const DemografiPekerjaan = () => {
h={300} h={300}
data={ageDistributionData} data={ageDistributionData}
dataKey="ageRange" dataKey="ageRange"
series={[{ name: 'total', color: 'darmasaba-navy' }]} series={[{ name: "total", color: "darmasaba-navy" }]}
withLegend withLegend
/> />
</Card> </Card>
@@ -210,7 +255,13 @@ const DemografiPekerjaan = () => {
{/* Demografi Pekerjaan */} {/* Demografi Pekerjaan */}
<Grid.Col span={{ base: 12, lg: 6 }}> <Grid.Col span={{ base: 12, lg: 6 }}>
<Card p="md" radius="md" withBorder bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }}> <Card
p="md"
radius="md"
withBorder
bg={dark ? "#141D34" : "white"}
style={{ borderColor: dark ? "#141D34" : "white" }}
>
<Title order={3} fw={500} mb="md"> <Title order={3} fw={500} mb="md">
Demografi Pekerjaan Demografi Pekerjaan
</Title> </Title>
@@ -218,7 +269,7 @@ const DemografiPekerjaan = () => {
h={300} h={300}
data={jobDistributionData} data={jobDistributionData}
dataKey="job" dataKey="job"
series={[{ name: 'total', color: 'darmasaba-navy' }]} series={[{ name: "total", color: "darmasaba-navy" }]}
withLegend withLegend
/> />
</Card> </Card>
@@ -229,16 +280,22 @@ const DemografiPekerjaan = () => {
<Grid gutter="lg"> <Grid gutter="lg">
{/* Distribusi Agama */} {/* Distribusi Agama */}
<Grid.Col span={{ base: 12, lg: 6 }}> <Grid.Col span={{ base: 12, lg: 6 }}>
<Card p="md" radius="md" withBorder bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }}> <Card
p="md"
radius="md"
withBorder
bg={dark ? "#141D34" : "white"}
style={{ borderColor: dark ? "#141D34" : "white" }}
>
<Title order={3} fw={500} mb="md"> <Title order={3} fw={500} mb="md">
Distribusi Agama Distribusi Agama
</Title> </Title>
<PieChart <PieChart
h={300} h={300}
data={religionData.map(item => ({ data={religionData.map((item) => ({
name: item.religion, name: item.religion,
value: item.total, value: item.total,
color: item.color color: item.color,
}))} }))}
withLabels withLabels
withLabelsLine withLabelsLine
@@ -250,27 +307,53 @@ const DemografiPekerjaan = () => {
{/* Data per Banjar */} {/* Data per Banjar */}
<Grid.Col span={{ base: 12, lg: 6 }}> <Grid.Col span={{ base: 12, lg: 6 }}>
<Card p="md" radius="md" withBorder bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }}> <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"> <Title order={3} fw={500} c={dark ? "dark.0" : "black"} mb="md">
Data per Banjar Data per Banjar
</Title> </Title>
<Table striped highlightOnHover> <Table striped highlightOnHover>
<Table.Thead> <Table.Thead>
<Table.Tr> <Table.Tr>
<Table.Th><Text c={dark ? "dark.0" : "black"}>Banjar</Text></Table.Th> <Table.Th>
<Table.Th><Text c={dark ? "dark.0" : "black"}>Penduduk</Text></Table.Th> <Text c={dark ? "dark.0" : "black"}>Banjar</Text>
<Table.Th><Text c={dark ? "dark.0" : "black"}>KK</Text></Table.Th> </Table.Th>
<Table.Th><Text c={dark ? "dark.0" : "black"}>Miskin</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.Tr>
</Table.Thead> </Table.Thead>
<Table.Tbody> <Table.Tbody>
{banjarData.map((item, index) => ( {banjarData.map((item, index) => (
<Table.Tr key={`${item.banjar}-${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> <Table.Td>
<Text c={dark ? "red.4" : "red"}>{item.poor.toLocaleString()}</Text> <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.Td>
</Table.Tr> </Table.Tr>
))} ))}
@@ -281,14 +364,29 @@ const DemografiPekerjaan = () => {
</Grid> </Grid>
{/* Statistik Dinamika Penduduk */} {/* Statistik Dinamika Penduduk */}
<Card p="md" radius="md" withBorder bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }}> <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"> <Title order={3} fw={500} c={dark ? "dark.0" : "black"} mb="md">
Statistik Dinamika Penduduk Statistik Dinamika Penduduk
</Title> </Title>
<Grid gutter="md"> <Grid gutter="md">
{dynamicStats.map((stat, index) => ( {dynamicStats.map((stat, index) => (
<Grid.Col key={`${stat.title}-${index}`} span={{ base: 12, md: 3 }}> <Grid.Col
<Card p="md" radius="md" withBorder bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }}> 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"> <Group justify="space-between" align="center">
<Box> <Box>
<Text size="sm" fw={500} c={dark ? "dark.3" : "dimmed"}> <Text size="sm" fw={500} c={dark ? "dark.3" : "dimmed"}>
@@ -298,9 +396,7 @@ const DemografiPekerjaan = () => {
{stat.value} {stat.value}
</Title> </Title>
</Box> </Box>
<Box c={stat.color}> <Box c={stat.color}>{stat.icon}</Box>
{stat.icon}
</Box>
</Group> </Group>
</Card> </Card>
</Grid.Col> </Grid.Col>
@@ -312,4 +408,4 @@ const DemografiPekerjaan = () => {
); );
}; };
export default DemografiPekerjaan; export default DemografiPekerjaan;

View File

@@ -1,46 +1,52 @@
import { useLocation } 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
import { import {
ActionIcon,
Avatar,
Badge,
Box,
Divider,
Group, Group,
Text, Text,
Title, Title,
ActionIcon,
Divider,
Avatar,
Box,
Badge,
useMantineColorScheme, useMantineColorScheme,
} from "@mantine/core"; } from "@mantine/core";
import { IconUserShield } from "@tabler/icons-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();
// Define page titles based on route // Define page titles based on route
const getPageTitle = () => { const getPageTitle = () => {
switch (location.pathname) { switch (location.pathname) {
case "/": case "/":
return "Desa Darmasaba"; return "Beranda";
case "/kinerja-divisi": case "/kinerja-divisi":
return "Kinerja Divisi"; return "Kinerja Divisi";
case "/pengaduan": case "/pengaduan-layanan-publik":
return "Pengaduan & Layanan Publik"; return "Pengaduan & Layanan Publik";
case "/analytic": case "/jenna-analytic":
return "Jenna Analytic"; return "Jenna Analytic";
case "/demografi": case "/demografi-pekerjaan":
return "Demografi & Kependudukan"; return "Demografi & Kependudukan";
case "/keuangan": case "/keuangan-anggaran":
return "Keuangan & Anggaran"; return "Keuangan & Anggaran";
case "/bumdes": case "/bumdes":
return "Bumdes & UMKM Desa"; return "Bumdes & UMKM Desa";
case "/sosial": case "/sosial":
return "Sosial";
case "/keamanan": case "/keamanan":
return "Keamanan"; return "Keamanan";
case "/bantuan": case "/bantuan":
return "Bantuan"; return "Bantuan";
case "/pengaturan": case "/pengaturan":
case "/pengaturan/umum":
case "/pengaturan/notifikasi":
case "/pengaturan/keamanan":
case "/pengaturan/akses-dan-tim":
return "Pengaturan"; return "Pengaturan";
default: default:
return "Desa Darmasaba"; return "Desa Darmasaba";
@@ -50,12 +56,12 @@ export function Header() {
return ( return (
<Group justify="space-between" w="100%"> <Group justify="space-between" w="100%">
{/* Title */} {/* Title */}
<Title order={3} c={"white"}>{getPageTitle()}</Title> <Title order={3} c={"white"}>
{getPageTitle()}
</Title>
{/* Right Section */} {/* Right Section */}
<Group gap="md"> <Group gap="md">
{/* User Info */} {/* User Info */}
<Group gap="sm"> <Group gap="sm">
<Box ta="right"> <Box ta="right">
@@ -101,6 +107,13 @@ export function Header() {
10 10
</Badge> </Badge>
</ActionIcon> </ActionIcon>
<ActionIcon variant="subtle" size="lg" radius="xl">
<IconUserShield
color="white"
style={{ width: "70%", height: "70%" }}
onClick={() => navigate({ to: "/signin" })}
/>
</ActionIcon>
</Group> </Group>
</Group> </Group>
</Group> </Group>

View File

@@ -1,248 +1,435 @@
import { Container, Grid, Title, Text, SimpleGrid, Box, Accordion, Stack, useMantineColorScheme } from '@mantine/core'; import {
import { HelpCard } from '@/components/ui/help-card'; Accordion,
import { IconBook, IconVideo, IconHelpCircle, IconMessage, IconFileText, IconHeadphones } from '@tabler/icons-react'; Box,
import { useState } from 'react'; Container,
Grid,
SimpleGrid,
Stack,
Text,
Title,
useMantineColorScheme,
} from "@mantine/core";
import {
IconBook,
IconFileText,
IconHeadphones,
IconHelpCircle,
IconMessage,
IconVideo,
} from "@tabler/icons-react";
import { useState } from "react";
import { HelpCard } from "@/components/ui/help-card";
const HelpPage = () => { const HelpPage = () => {
const { colorScheme } = useMantineColorScheme(); const { colorScheme } = useMantineColorScheme();
const dark = colorScheme === "dark"; const dark = colorScheme === "dark";
// Sample data for sections // Sample data for sections
const guideItems = [ const guideItems = [
{ title: 'Cara Login', description: 'Langkah-langkah untuk login ke dashboard' }, {
{ title: 'Navigasi Dashboard', description: 'Penjelasan tentang tata letak dan navigasi' }, title: "Cara Login",
{ title: 'Fitur Dasar', description: 'Panduan penggunaan fitur-fitur utama' }, description: "Langkah-langkah untuk login ke dashboard",
{ title: 'Tips & Trik', description: 'Tips untuk meningkatkan produktivitas' }, },
]; {
title: "Navigasi Dashboard",
description: "Penjelasan tentang tata letak dan navigasi",
},
{
title: "Fitur Dasar",
description: "Panduan penggunaan fitur-fitur utama",
},
{
title: "Tips & Trik",
description: "Tips untuk meningkatkan produktivitas",
},
];
const videoItems = [ const videoItems = [
{ title: 'Dashboard Overview', duration: '5:23' }, { title: "Dashboard Overview", duration: "5:23" },
{ title: 'Analisis Data', duration: '8:45' }, { title: "Analisis Data", duration: "8:45" },
{ title: 'Membuat Laporan', duration: '6:12' }, { title: "Membuat Laporan", duration: "6:12" },
{ title: 'Export Data', duration: '4:30' }, { title: "Export Data", duration: "4:30" },
]; ];
const faqItems = [ const faqItems = [
{ question: 'Bagaimana cara reset password?', answer: 'Anda dapat mereset password melalui halaman login dengan klik "Lupa Password"' }, {
{ question: 'Apakah saya bisa mengakses data offline?', answer: 'Saat ini aplikasi hanya dapat diakses secara online' }, question: "Bagaimana cara reset password?",
{ question: 'Berapa lama waktu respon support?', answer: 'Tim support kami biasanya merespon dalam waktu kurang dari 24 jam' }, answer:
{ question: 'Bagaimana cara menambahkan pengguna baru?', answer: 'Fitur penambahan pengguna dapat ditemukan di menu Pengaturan > Manajemen Pengguna' }, 'Anda dapat mereset password melalui halaman login dengan klik "Lupa Password"',
]; },
{
question: "Apakah saya bisa mengakses data offline?",
answer: "Saat ini aplikasi hanya dapat diakses secara online",
},
{
question: "Berapa lama waktu respon support?",
answer:
"Tim support kami biasanya merespon dalam waktu kurang dari 24 jam",
},
{
question: "Bagaimana cara menambahkan pengguna baru?",
answer:
"Fitur penambahan pengguna dapat ditemukan di menu Pengaturan > Manajemen Pengguna",
},
];
const documentationItems = [ const documentationItems = [
{ title: 'API Reference', description: 'Dokumentasi lengkap untuk integrasi API' }, {
{ title: 'Integrasi Sistem', description: 'Cara mengintegrasikan dengan sistem eksternal' }, title: "API Reference",
{ title: 'Format Data', description: 'Spesifikasi format data yang didukung' }, description: "Dokumentasi lengkap untuk integrasi API",
{ title: 'Best Practices', description: 'Praktik terbaik dalam penggunaan platform' }, },
]; {
title: "Integrasi Sistem",
description: "Cara mengintegrasikan dengan sistem eksternal",
},
{
title: "Format Data",
description: "Spesifikasi format data yang didukung",
},
{
title: "Best Practices",
description: "Praktik terbaik dalam penggunaan platform",
},
];
const stats = [ const stats = [
{ value: '150+', label: 'Artikel Panduan' }, { value: "150+", label: "Artikel Panduan" },
{ value: '50+', label: 'Video Tutorial' }, { value: "50+", label: "Video Tutorial" },
{ value: '24/7', label: 'Support Aktif' }, { value: "24/7", label: "Support Aktif" },
]; ];
// State for chat functionality // State for chat functionality
const [messages, setMessages] = useState([ const [messages, setMessages] = useState([
{ id: 1, text: 'Halo! Saya Jenna, asisten virtual Anda. Bagaimana saya bisa membantu hari ini?', sender: 'jenna' } {
]); id: 1,
const [inputValue, setInputValue] = useState(''); text: "Halo! Saya Jenna, asisten virtual Anda. Bagaimana saya bisa membantu hari ini?",
const [isLoading, setIsLoading] = useState(false); sender: "jenna",
},
]);
const [inputValue, setInputValue] = useState("");
const [isLoading, setIsLoading] = useState(false);
const handleSendMessage = () => { const handleSendMessage = () => {
if (inputValue.trim() === '') return; if (inputValue.trim() === "") return;
// Add user message // Add user message
const newUserMessage = { const newUserMessage = {
id: messages.length + 1, id: messages.length + 1,
text: inputValue, text: inputValue,
sender: 'user' sender: "user",
}; };
setMessages(prev => [...prev, newUserMessage]); setMessages((prev) => [...prev, newUserMessage]);
setInputValue(''); setInputValue("");
setIsLoading(true); setIsLoading(true);
// Simulate Jenna's response after delay // Simulate Jenna's response after delay
setTimeout(() => { setTimeout(() => {
const jennaResponse = { const jennaResponse = {
id: messages.length + 2, id: messages.length + 2,
text: 'Terima kasih atas pertanyaan Anda. Saat ini saya adalah versi awal dari asisten virtual. Tim kami sedang mengembangkan kemampuan saya lebih lanjut.', text: "Terima kasih atas pertanyaan Anda. Saat ini saya adalah versi awal dari asisten virtual. Tim kami sedang mengembangkan kemampuan saya lebih lanjut.",
sender: 'jenna' sender: "jenna",
}; };
setMessages(prev => [...prev, jennaResponse]); setMessages((prev) => [...prev, jennaResponse]);
setIsLoading(false); setIsLoading(false);
}, 1000); }, 1000);
}; };
const handleKeyPress = (e: React.KeyboardEvent) => { const handleKeyPress = (e: React.KeyboardEvent) => {
if (e.key === 'Enter' && !e.shiftKey) { if (e.key === "Enter" && !e.shiftKey) {
e.preventDefault(); e.preventDefault();
handleSendMessage(); handleSendMessage();
} }
}; };
return ( return (
<Container size="lg" py="xl"> <Container size="lg" py="xl">
<Title order={1} mb="xl" ta="center">Pusat Bantuan</Title> <Title order={1} mb="xl" ta="center">
<Text size="lg" color="dimmed" ta="center" mb="xl"> Pusat Bantuan
Temukan jawaban untuk pertanyaan Anda atau hubungi tim support kami </Title>
</Text> <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) => (
<HelpCard key={index} bg={dark ? "#141D34" : "white"} p="lg" style={{ textAlign: 'center', borderColor: dark ? "#141D34" : "white" }} h="100%" > <HelpCard
<Text size="xl" fw={700} style={{ fontSize: '32px' }}>{stat.value}</Text> key={index}
<Text size="sm" color="dimmed">{stat.label}</Text> bg={dark ? "#141D34" : "white"}
</HelpCard> p="lg"
))} style={{
</SimpleGrid> textAlign: "center",
borderColor: dark ? "#141D34" : "white",
}}
h="100%"
>
<Text size="xl" fw={700} style={{ fontSize: "32px" }}>
{stat.value}
</Text>
<Text size="sm" color="dimmed">
{stat.label}
</Text>
</HelpCard>
))}
</SimpleGrid>
<Stack gap="lg"> <Stack gap="lg">
<Box> <Box>
<Grid gutter="lg" justify="center"> <Grid gutter="lg" justify="center">
{/* Panduan Memulai */} {/* Panduan Memulai */}
<Grid.Col span={{ base: 12, sm: 6, md: 4 }}> <Grid.Col span={{ base: 12, sm: 6, md: 4 }}>
<HelpCard style={{ borderColor: dark ? "#141D34" : "white" }} bg={dark ? "#141D34" : "white"} icon={<IconBook size={24} />} title="Panduan Memulai" h="100%"> <HelpCard
<Box> style={{ borderColor: dark ? "#141D34" : "white" }}
{guideItems.map((item, index) => ( bg={dark ? "#141D34" : "white"}
<Box key={index} py="sm" style={{ borderBottom: '1px solid #eee', cursor: 'pointer' }} onClick={() => alert(`Navigasi ke ${item.title}`)}> icon={<IconBook size={24} />}
<Text fw={500}>{item.title}</Text> title="Panduan Memulai"
<Text size="sm" color="dimmed">{item.description}</Text> h="100%"
</Box> >
))} <Box>
</Box> {guideItems.map((item, index) => (
</HelpCard> <Box
</Grid.Col> key={index}
py="sm"
style={{
borderBottom: "1px solid #eee",
cursor: "pointer",
}}
onClick={() => alert(`Navigasi ke ${item.title}`)}
>
<Text fw={500}>{item.title}</Text>
<Text size="sm" color="dimmed">
{item.description}
</Text>
</Box>
))}
</Box>
</HelpCard>
</Grid.Col>
{/* Video Tutorial */} {/* Video Tutorial */}
<Grid.Col span={{ base: 12, sm: 6, md: 4 }}> <Grid.Col span={{ base: 12, sm: 6, md: 4 }}>
<HelpCard style={{ borderColor: dark ? "#141D34" : "white" }} bg={dark ? "#141D34" : "white"} icon={<IconVideo size={24} />} title="Video Tutorial" h="100%"> <HelpCard
<Box> style={{ borderColor: dark ? "#141D34" : "white" }}
{videoItems.map((item, index) => ( bg={dark ? "#141D34" : "white"}
<Box key={index} py="sm" style={{ borderBottom: '1px solid #eee', cursor: 'pointer' }} onClick={() => alert(`Buka video: ${item.title}`)}> icon={<IconVideo size={24} />}
<Text fw={500}>{item.title}</Text> title="Video Tutorial"
<Text size="sm" color="dimmed">{item.duration}</Text> h="100%"
</Box> >
))} <Box>
</Box> {videoItems.map((item, index) => (
</HelpCard> <Box
</Grid.Col> key={index}
py="sm"
style={{
borderBottom: "1px solid #eee",
cursor: "pointer",
}}
onClick={() => alert(`Buka video: ${item.title}`)}
>
<Text fw={500}>{item.title}</Text>
<Text size="sm" color="dimmed">
{item.duration}
</Text>
</Box>
))}
</Box>
</HelpCard>
</Grid.Col>
{/* FAQ */} {/* FAQ */}
<Grid.Col span={{ base: 12, sm: 6, md: 4 }}> <Grid.Col span={{ base: 12, sm: 6, md: 4 }}>
<HelpCard style={{ borderColor: dark ? "#141D34" : "white" }} bg={dark ? "#141D34" : "white"} icon={<IconHelpCircle size={24} />} title="FAQ" h="100%"> <HelpCard
<Accordion variant="separated" > style={{ borderColor: dark ? "#141D34" : "white" }}
{faqItems.map((item, index) => ( bg={dark ? "#141D34" : "white"}
<Accordion.Item style={{ backgroundColor: dark ? "#263852ff" : "#F1F5F9" }} key={index} value={`faq-${index}`}> icon={<IconHelpCircle size={24} />}
<Accordion.Control>{item.question}</Accordion.Control> title="FAQ"
<Accordion.Panel> h="100%"
<Text size="sm">{item.answer}</Text> >
</Accordion.Panel> <Accordion variant="separated">
</Accordion.Item> {faqItems.map((item, index) => (
))} <Accordion.Item
</Accordion> style={{
</HelpCard> backgroundColor: dark ? "#263852ff" : "#F1F5F9",
</Grid.Col> }}
</Grid> key={index}
</Box> value={`faq-${index}`}
>
<Accordion.Control>{item.question}</Accordion.Control>
<Accordion.Panel>
<Text size="sm">{item.answer}</Text>
</Accordion.Panel>
</Accordion.Item>
))}
</Accordion>
</HelpCard>
</Grid.Col>
</Grid>
</Box>
<Box> <Box>
<Grid> <Grid>
{/* Hubungi Support */} {/* Hubungi Support */}
<Grid.Col span={{ base: 12, sm: 6, md: 4 }}> <Grid.Col span={{ base: 12, sm: 6, md: 4 }}>
<HelpCard style={{ borderColor: dark ? "#141D34" : "white" }} bg={dark ? "#141D34" : "white"} icon={<IconHeadphones size={24} />} title="Hubungi Support" h="100%"> <HelpCard
<Box> style={{ borderColor: dark ? "#141D34" : "white" }}
<Text fw={500}>Email</Text> bg={dark ? "#141D34" : "white"}
<Text size="sm" color="dimmed" mb="md"><a href="mailto:support@example.com">support@example.com</a></Text> icon={<IconHeadphones size={24} />}
title="Hubungi Support"
h="100%"
>
<Box>
<Text fw={500}>Email</Text>
<Text size="sm" color="dimmed" mb="md">
<a href="mailto:support@example.com">support@example.com</a>
</Text>
<Text fw={500}>WhatsApp</Text> <Text fw={500}>WhatsApp</Text>
<Text size="sm" color="dimmed" mb="md"><a href="https://wa.me/1234567890">+62 123 456 7890</a></Text> <Text size="sm" color="dimmed" mb="md">
<a href="https://wa.me/1234567890">+62 123 456 7890</a>
</Text>
<Text fw={500}>Jam Kerja</Text> <Text fw={500}>Jam Kerja</Text>
<Text size="sm" color="dimmed">Senin - Jumat, 09:00 - 17:00 WIB</Text> <Text size="sm" color="dimmed">
Senin - Jumat, 09:00 - 17:00 WIB
</Text>
<Text fw={500} mt="md">Waktu Respon</Text> <Text fw={500} mt="md">
<Text size="sm" color="dimmed">Rata-rata 2-4 jam kerja</Text> Waktu Respon
</Box> </Text>
</HelpCard> <Text size="sm" color="dimmed">
</Grid.Col> Rata-rata 2-4 jam kerja
</Text>
</Box>
</HelpCard>
</Grid.Col>
{/* Dokumentasi */} {/* Dokumentasi */}
<Grid.Col span={{ base: 12, sm: 6, md: 4 }}> <Grid.Col span={{ base: 12, sm: 6, md: 4 }}>
<HelpCard style={{ borderColor: dark ? "#141D34" : "white" }} bg={dark ? "#141D34" : "white"} icon={<IconFileText size={24} />} title="Dokumentasi" h="100%"> <HelpCard
<Box> style={{ borderColor: dark ? "#141D34" : "white" }}
{documentationItems.map((item, index) => ( bg={dark ? "#141D34" : "white"}
<Box key={index} py="sm" style={{ borderBottom: '1px solid #eee', cursor: 'pointer' }} onClick={() => alert(`Navigasi ke dokumentasi: ${item.title}`)}> icon={<IconFileText size={24} />}
<Text fw={500}>{item.title}</Text> title="Dokumentasi"
<Text size="sm" color="dimmed">{item.description}</Text> h="100%"
</Box> >
))} <Box>
</Box> {documentationItems.map((item, index) => (
</HelpCard> <Box
</Grid.Col> key={index}
py="sm"
style={{
borderBottom: "1px solid #eee",
cursor: "pointer",
}}
onClick={() =>
alert(`Navigasi ke dokumentasi: ${item.title}`)
}
>
<Text fw={500}>{item.title}</Text>
<Text size="sm" color="dimmed">
{item.description}
</Text>
</Box>
))}
</Box>
</HelpCard>
</Grid.Col>
{/* Jenna - Virtual Assistant */} {/* Jenna - Virtual Assistant */}
<Grid.Col span={{ base: 12, sm: 6, md: 4 }}> <Grid.Col span={{ base: 12, sm: 6, md: 4 }}>
<HelpCard style={{ borderColor: dark ? "#141D34" : "white" }} bg={dark ? "#141D34" : "white"} icon={<IconMessage size={24} />} title="Jenna - Virtual Assistant" h="100%"> <HelpCard
<Box style={{ height: '300px', display: 'flex', flexDirection: 'column' }}> style={{ borderColor: dark ? "#141D34" : "white" }}
<Box style={{ flex: 1, overflowY: 'auto', marginBottom: '12px', maxHeight: '200px' }}> bg={dark ? "#141D34" : "white"}
{messages.map((msg) => ( icon={<IconMessage size={24} />}
<Box title="Jenna - Virtual Assistant"
key={msg.id} h="100%"
style={{ >
alignSelf: msg.sender === 'user' ? 'flex-end' : 'flex-start', <Box
backgroundColor: msg.sender === 'user' ? dark ? "#263852ff" : "#F1F5F9" : dark ? "#263852ff" : "#F1F5F9", style={{
color: msg.sender === 'user' ? dark ? "#F1F5F9" : "#263852ff" : dark ? "#F1F5F9" : "#263852ff", height: "300px",
padding: '8px 12px', display: "flex",
borderRadius: '8px', flexDirection: "column",
marginBottom: '8px', }}
maxWidth: '80%' >
}} <Box
> style={{
{msg.text} flex: 1,
</Box> overflowY: "auto",
))} marginBottom: "12px",
</Box> maxHeight: "200px",
}}
>
{messages.map((msg) => (
<Box
key={msg.id}
style={{
alignSelf:
msg.sender === "user" ? "flex-end" : "flex-start",
backgroundColor:
msg.sender === "user"
? dark
? "#263852ff"
: "#F1F5F9"
: dark
? "#263852ff"
: "#F1F5F9",
color:
msg.sender === "user"
? dark
? "#F1F5F9"
: "#263852ff"
: dark
? "#F1F5F9"
: "#263852ff",
padding: "8px 12px",
borderRadius: "8px",
marginBottom: "8px",
maxWidth: "80%",
}}
>
{msg.text}
</Box>
))}
</Box>
<Box style={{ display: 'flex', gap: '8px' }}> <Box style={{ display: "flex", gap: "8px" }}>
<input <input
type="text" type="text"
value={inputValue} value={inputValue}
onChange={(e) => setInputValue(e.target.value)} onChange={(e) => setInputValue(e.target.value)}
onKeyPress={handleKeyPress} onKeyPress={handleKeyPress}
placeholder="Ketik pesan Anda..." placeholder="Ketik pesan Anda..."
style={{ style={{
flex: 1, flex: 1,
padding: '8px 12px', padding: "8px 12px",
borderRadius: '20px', borderRadius: "20px",
border: '1px solid #ccc', border: "1px solid #ccc",
}} }}
disabled={isLoading} disabled={isLoading}
/> />
<button <button
onClick={handleSendMessage} onClick={handleSendMessage}
disabled={isLoading || inputValue.trim() === ''} disabled={isLoading || inputValue.trim() === ""}
style={{ style={{
padding: '8px 16px', padding: "8px 16px",
borderRadius: '20px', borderRadius: "20px",
backgroundColor: '#3B82F6', backgroundColor: "#3B82F6",
color: 'white', color: "white",
border: 'none', border: "none",
cursor: 'pointer', cursor: "pointer",
}} }}
> >
Kirim Kirim
</button> </button>
</Box> </Box>
</Box> </Box>
</HelpCard> </HelpCard>
</Grid.Col> </Grid.Col>
</Grid> </Grid>
</Box> </Box>
</Stack> </Stack>
</Container> </Container>
); );
}; };
export default HelpPage; export default HelpPage;

View File

@@ -1,18 +1,18 @@
import React from "react"; import { BarChart } from "@mantine/charts";
import { import {
Badge,
Box,
Button, Button,
Card, Card,
Badge,
Progress,
Title,
Text,
Group,
Stack,
Grid, Grid,
Box, Group,
Progress,
Stack,
Text,
Title,
useMantineColorScheme, useMantineColorScheme,
} from "@mantine/core"; } from "@mantine/core";
import { BarChart } from "@mantine/charts"; import React from "react";
// Sample Data // Sample Data
const kpiData = [ const kpiData = [
@@ -144,7 +144,13 @@ const JennaAnalytic = () => {
<Grid gutter="lg"> <Grid gutter="lg">
{kpiData.map((kpi) => ( {kpiData.map((kpi) => (
<Grid.Col key={kpi.id} span={{ base: 12, md: 6, lg: 3 }}> <Grid.Col key={kpi.id} span={{ base: 12, md: 6, lg: 3 }}>
<Card p="md" radius="md" withBorder bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }}> <Card
p="md"
radius="md"
withBorder
bg={dark ? "#141D34" : "white"}
style={{ borderColor: dark ? "#141D34" : "white" }}
>
<Group justify="space-between" align="flex-start" mb="xs"> <Group justify="space-between" align="flex-start" mb="xs">
<Text size="sm" fw={500} c="dimmed"> <Text size="sm" fw={500} c="dimmed">
{kpi.title} {kpi.title}
@@ -182,7 +188,13 @@ const JennaAnalytic = () => {
))} ))}
</Grid> </Grid>
<Card p="md" radius="md" withBorder bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }}> <Card
p="md"
radius="md"
withBorder
bg={dark ? "#141D34" : "white"}
style={{ borderColor: dark ? "#141D34" : "white" }}
>
<Title order={3} fw={500} mb="md"> <Title order={3} fw={500} mb="md">
Interaksi Chatbot Interaksi Chatbot
</Title> </Title>
@@ -190,7 +202,7 @@ const JennaAnalytic = () => {
h={300} h={300}
data={chartData} data={chartData}
dataKey="day" dataKey="day"
series={[{ name: 'total', color: 'blue' }]} series={[{ name: "total", color: "blue" }]}
withLegend withLegend
/> />
</Card> </Card>
@@ -199,16 +211,21 @@ const JennaAnalytic = () => {
<Grid gutter="lg"> <Grid gutter="lg">
{/* Grafik Interaksi Chatbot (now Bar Chart) */} {/* Grafik Interaksi Chatbot (now Bar Chart) */}
<Grid.Col span={{ base: 12, lg: 6 }}> <Grid.Col span={{ base: 12, lg: 6 }}>
<Card p="md" radius="md" withBorder bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }} h="100%"> <Card
p="md"
radius="md"
withBorder
bg={dark ? "#141D34" : "white"}
style={{ borderColor: dark ? "#141D34" : "white" }}
h="100%"
>
<Title order={3} fw={500} mb="md"> <Title order={3} fw={500} mb="md">
Jam Tersibuk Jam Tersibuk
</Title> </Title>
<Stack gap="sm"> <Stack gap="sm">
{busyHours.map((item, index) => ( {busyHours.map((item, index) => (
<Box key={index}> <Box key={index}>
<Text size="sm"> <Text size="sm">{item.period}</Text>
{item.period}
</Text>
<Group align="center"> <Group align="center">
<Progress value={item.percentage} flex={1} /> <Progress value={item.percentage} flex={1} />
<Text size="sm" fw={500}> <Text size="sm" fw={500}>
@@ -225,7 +242,14 @@ const JennaAnalytic = () => {
<Grid.Col span={{ base: 12, lg: 6 }}> <Grid.Col span={{ base: 12, lg: 6 }}>
<Stack gap="lg"> <Stack gap="lg">
{/* Topik Pertanyaan Terbanyak */} {/* Topik Pertanyaan Terbanyak */}
<Card p="md" radius="md" withBorder bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }} h="100%"> <Card
p="md"
radius="md"
withBorder
bg={dark ? "#141D34" : "white"}
style={{ borderColor: dark ? "#141D34" : "white" }}
h="100%"
>
<Title order={3} fw={500} mb="md"> <Title order={3} fw={500} mb="md">
Topik Pertanyaan Terbanyak Topik Pertanyaan Terbanyak
</Title> </Title>
@@ -251,12 +275,9 @@ const JennaAnalytic = () => {
{/* Jam Tersibuk */} {/* Jam Tersibuk */}
</Stack> </Stack>
</Grid.Col> </Grid.Col>
</Grid > </Grid>
</Stack>
</Stack > </Box>
</Box >
); );
};
}
export default JennaAnalytic; export default JennaAnalytic;

View File

@@ -1,225 +1,325 @@
import { useState } from "react"; import {
import { Badge,
Card, Box,
Grid, Card,
GridCol, Grid,
Group, GridCol,
Text, Group,
Title, List,
Stack, Stack,
useMantineColorScheme, Text,
Badge, ThemeIcon,
List, Title,
ThemeIcon, useMantineColorScheme,
Box
} from "@mantine/core"; } from "@mantine/core";
import { import {
IconCamera, IconAlertTriangle,
IconAlertTriangle, IconCamera,
IconMapPin, IconClock,
IconClock, IconEye,
IconEye, IconMapPin,
IconShieldLock IconShieldLock,
} from "@tabler/icons-react"; } from "@tabler/icons-react";
import { useState } from "react";
const KeamananPage = () => { const KeamananPage = () => {
const { colorScheme } = useMantineColorScheme(); const { colorScheme } = useMantineColorScheme();
const dark = colorScheme === 'dark'; 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 // Sample data for KPI cards
const cctvLocations = [ const kpiData = [
{ 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" }, title: "CCTV Aktif",
{ id: "CCTV-03", lat: -8.4, lng: 115.1, status: "offline", lastSeen: "1 hari yang lalu", location: "Taman Desa" }, value: 20,
{ id: "CCTV-04", lat: -8.7, lng: 115.4, status: "active", lastSeen: "30 menit yang lalu", location: "Pasar Desa" }, 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 security reports // Sample data for CCTV locations
const securityReports = [ const cctvLocations = [
{ {
id: "REP-001", id: "CCTV-01",
title: "Pencurian Motor", lat: -8.5,
reportedAt: "2 jam yang lalu", lng: 115.2,
date: "12 Feb 2026, 14:30", status: "active",
location: "Jl. Kecubung 20", lastSeen: "2 jam yang lalu",
status: "baru", location: "Balai Desa",
}, },
{ {
id: "REP-002", id: "CCTV-02",
title: "Kerusuhan Antar Warga", lat: -8.6,
reportedAt: "4 jam yang lalu", lng: 115.3,
date: "12 Feb 2026, 12:15", status: "active",
location: "RT 05 RW 02", lastSeen: "1 jam yang lalu",
status: "baru", location: "Pintu Masuk Desa",
}, },
{ {
id: "REP-003", id: "CCTV-03",
title: "Kebakaran Rumah", lat: -8.4,
reportedAt: "1 hari yang lalu", lng: 115.1,
date: "11 Feb 2026, 08:45", status: "offline",
location: "Jl. Flamboyan 15", lastSeen: "1 hari yang lalu",
status: "diproses", location: "Taman Desa",
}, },
{ {
id: "REP-004", id: "CCTV-04",
title: "Kehilangan Barang", lat: -8.7,
reportedAt: "2 hari yang lalu", lng: 115.4,
date: "10 Feb 2026, 16:20", status: "active",
location: "Taman Desa", lastSeen: "30 menit yang lalu",
status: "selesai", location: "Pasar Desa",
}, },
]; ];
return ( // Sample data for security reports
<Stack gap="lg"> const securityReports = [
{/* Page Header */} {
<Group justify="space-between" align="center"> id: "REP-001",
<Title order={2} c={dark ? "dark.0" : "black"}> title: "Pencurian Motor",
Keamanan Lingkungan Desa reportedAt: "2 jam yang lalu",
</Title> date: "12 Feb 2026, 14:30",
</Group> 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",
},
];
{/* KPI Cards */} return (
<Grid gutter="md"> <Stack gap="lg">
{kpiData.map((kpi, index) => ( {/* Page Header */}
<GridCol key={index} span={{ base: 12, sm: 6, md: 6 }}> <Group justify="space-between" align="center">
<Card p="md" radius="md" withBorder bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }} h="100%"> <Title order={2} c={dark ? "dark.0" : "black"}>
<Group justify="space-between" align="center"> Keamanan Lingkungan Desa
<Stack gap={0}> </Title>
<Text size="sm" c={dark ? "dark.3" : "dimmed"}> </Group>
{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"> {/* KPI Cards */}
{/* Peta Keamanan CCTV */} <Grid gutter="md">
<GridCol span={{ base: 12, lg: 6 }}> {kpiData.map((kpi, index) => (
<Card p="md" radius="md" withBorder bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }} h="100%"> <GridCol key={index} span={{ base: 12, sm: 6, md: 6 }}>
<Title order={3} mb="md" c={dark ? "dark.0" : "black"}>Peta Keamanan CCTV</Title> <Card
<Text size="sm" c={dark ? "dark.3" : "dimmed"} mb="md">Titik Lokasi CCTV</Text> p="md"
radius="md"
{/* Placeholder for map */} withBorder
<Box bg={dark ? "#141D34" : "white"}
style={{ style={{ borderColor: dark ? "#141D34" : "white" }}
backgroundColor: dark ? '#2d3748' : '#e2e8f0', h="100%"
borderRadius: '8px', >
height: '400px', <Group justify="space-between" align="center">
display: 'flex', <Stack gap={0}>
alignItems: 'center', <Text size="sm" c={dark ? "dark.3" : "dimmed"}>
justifyContent: 'center' {kpi.subtitle}
}} </Text>
> <Group gap="xs" align="center">
<Stack align="center"> <Text size="xl" fw={700} c={dark ? "dark.0" : "black"}>
<IconMapPin size={48} stroke={1.5} color={dark ? '#94a3b8' : '#64748b'} /> {kpi.value}
<Text c={dark ? "dark.3" : "dimmed"}>Peta Lokasi CCTV</Text> </Text>
<Text size="sm" c={dark ? "dark.3" : "dimmed"} ta="center">Integrasi dengan Google Maps atau Mapbox akan ditampilkan di sini</Text> <Text size="sm" c={dark ? "dark.3" : "dimmed"}>
</Stack> {kpi.title}
</Box> </Text>
</Group>
{/* CCTV Locations List */} </Stack>
<Stack mt="md" gap="sm"> <ThemeIcon
<Title order={4} c={dark ? "dark.0" : "black"}>Daftar CCTV</Title> variant="light"
{cctvLocations.map((cctv, index) => ( color={kpi.color}
<Card key={index} p="md" radius="md" withBorder bg={dark ? "#263852ff" : "#F1F5F9"} style={{ borderColor: dark ? "#263852ff" : "#F1F5F9" }}> size="xl"
<Group justify="space-between"> radius="xl"
<Stack gap={0}> >
<Group gap="xs"> {kpi.icon}
<Text fw={500} c={dark ? "dark.0" : "black"}>{cctv.id}</Text> </ThemeIcon>
<Badge </Group>
variant="dot" </Card>
color={cctv.status === "active" ? "green" : "gray"} </GridCol>
> ))}
{cctv.status === "active" ? "Online" : "Offline"} </Grid>
</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 */} <Grid gutter="md">
<GridCol span={{ base: 12, lg: 6 }}> {/* Peta Keamanan CCTV */}
<Card p="md" radius="md" withBorder bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }} h="100%"> <GridCol span={{ base: 12, lg: 6 }}>
<Title order={3} mb="md" c={dark ? "dark.0" : "black"}>Laporan Keamanan Lingkungan</Title> <Card
p="md"
<Stack gap="sm"> radius="md"
{securityReports.map((report, index) => ( withBorder
<Card key={index} p="md" radius="md" withBorder bg={dark ? "#263852ff" : "#F1F5F9"} style={{ borderColor: dark ? "#263852ff" : "#F1F5F9" }}> bg={dark ? "#141D34" : "white"}
<Group justify="space-between" mb="sm"> style={{ borderColor: dark ? "#141D34" : "white" }}
<Text fw={500} c={dark ? "dark.0" : "black"}>{report.title}</Text> h="100%"
<Badge >
variant="light" <Title order={3} mb="md" c={dark ? "dark.0" : "black"}>
color={ Peta Keamanan CCTV
report.status === "baru" ? "red" : </Title>
report.status === "diproses" ? "yellow" : "green" <Text size="sm" c={dark ? "dark.3" : "dimmed"} mb="md">
} Titik Lokasi CCTV
> </Text>
{report.status}
</Badge> {/* Placeholder for map */}
</Group> <Box
style={{
<Group justify="space-between"> backgroundColor: dark ? "#2d3748" : "#e2e8f0",
<Group gap="xs"> borderRadius: "8px",
<IconMapPin size={16} stroke={1.5} /> height: "400px",
<Text size="sm" c={dark ? "dark.3" : "dimmed"}>{report.location}</Text> display: "flex",
</Group> alignItems: "center",
<Group gap="xs"> justifyContent: "center",
<IconClock size={16} stroke={1.5} /> }}
<Text size="sm" c={dark ? "dark.3" : "dimmed"}>{report.reportedAt}</Text> >
</Group> <Stack align="center">
</Group> <IconMapPin
size={48}
<Text size="sm" c={dark ? "dark.3" : "dimmed"} mt="sm">{report.date}</Text> stroke={1.5}
</Card> color={dark ? "#94a3b8" : "#64748b"}
))} />
</Stack> <Text c={dark ? "dark.3" : "dimmed"}>Peta Lokasi CCTV</Text>
</Card> <Text size="sm" c={dark ? "dark.3" : "dimmed"} ta="center">
</GridCol> Integrasi dengan Google Maps atau Mapbox akan ditampilkan di
</Grid> sini
</Stack> </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}
p="md"
radius="md"
withBorder
bg={dark ? "#263852ff" : "#F1F5F9"}
style={{ borderColor: dark ? "#263852ff" : "#F1F5F9" }}
>
<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
p="md"
radius="md"
withBorder
bg={dark ? "#141D34" : "white"}
style={{ borderColor: dark ? "#141D34" : "white" }}
h="100%"
>
<Title order={3} mb="md" c={dark ? "dark.0" : "black"}>
Laporan Keamanan Lingkungan
</Title>
<Stack gap="sm">
{securityReports.map((report, 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 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; export default KeamananPage;

View File

@@ -1,19 +1,23 @@
import React from "react"; import { BarChart } from "@mantine/charts";
import { import {
Badge,
Box,
Button, Button,
Card, Card,
Badge,
Title,
Text,
Group,
Stack,
Grid, Grid,
Box, Group,
Progress, Progress,
Stack,
Text,
Title,
useMantineColorScheme, useMantineColorScheme,
} from "@mantine/core"; } from "@mantine/core";
import { IconTrendingUp, IconTrendingDown, IconCurrency } from "@tabler/icons-react"; import {
import { BarChart } from "@mantine/charts"; IconCurrency,
IconTrendingDown,
IconTrendingUp,
} from "@tabler/icons-react";
import React from "react";
// Sample Data // Sample Data
const kpiData = [ const kpiData = [
@@ -22,9 +26,7 @@ const kpiData = [
title: "Total APBDes", title: "Total APBDes",
value: "Rp 5.2M", value: "Rp 5.2M",
sub: "Tahun 2025", sub: "Tahun 2025",
icon: ( icon: <IconCurrency className="h-6 w-6 text-muted-foreground" />,
<IconCurrency className="h-6 w-6 text-muted-foreground" />
),
}, },
{ {
id: 2, id: 2,
@@ -55,18 +57,14 @@ const kpiData = [
sub: "Bulan ini", sub: "Bulan ini",
delta: "+8%", delta: "+8%",
deltaType: "positive", deltaType: "positive",
icon: ( icon: <IconTrendingUp className="h-6 w-6 text-muted-foreground" />,
<IconTrendingUp className="h-6 w-6 text-muted-foreground" />
),
}, },
{ {
id: 4, id: 4,
title: "Pengeluaran", title: "Pengeluaran",
value: "Rp 520jt", value: "Rp 520jt",
sub: "Bulan ini", sub: "Bulan ini",
icon: ( icon: <IconTrendingDown className="h-6 w-6 text-muted-foreground" />,
<IconTrendingDown className="h-6 w-6 text-muted-foreground" />
),
}, },
]; ];
@@ -125,7 +123,14 @@ const KeuanganAnggaran = () => {
<Grid gutter="lg"> <Grid gutter="lg">
{kpiData.map((kpi) => ( {kpiData.map((kpi) => (
<Grid.Col key={kpi.id} span={{ base: 12, md: 6, lg: 3 }}> <Grid.Col key={kpi.id} span={{ base: 12, md: 6, lg: 3 }}>
<Card p="md" radius="md" withBorder bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }} h="100%"> <Card
p="md"
radius="md"
withBorder
bg={dark ? "#141D34" : "white"}
style={{ borderColor: dark ? "#141D34" : "white" }}
h="100%"
>
<Group justify="space-between" align="flex-start" mb="xs"> <Group justify="space-between" align="flex-start" mb="xs">
<Text size="sm" fw={500} c="dimmed"> <Text size="sm" fw={500} c="dimmed">
{kpi.title} {kpi.title}
@@ -167,7 +172,13 @@ const KeuanganAnggaran = () => {
<Grid gutter="lg"> <Grid gutter="lg">
{/* Grafik Pemasukan vs Pengeluaran */} {/* Grafik Pemasukan vs Pengeluaran */}
<Grid.Col span={{ base: 12, lg: 6 }}> <Grid.Col span={{ base: 12, lg: 6 }}>
<Card p="md" radius="md" withBorder bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }}> <Card
p="md"
radius="md"
withBorder
bg={dark ? "#141D34" : "white"}
style={{ borderColor: dark ? "#141D34" : "white" }}
>
<Title order={3} fw={500} mb="md"> <Title order={3} fw={500} mb="md">
Pemasukan vs Pengeluaran Pemasukan vs Pengeluaran
</Title> </Title>
@@ -176,8 +187,8 @@ const KeuanganAnggaran = () => {
data={incomeExpenseData} data={incomeExpenseData}
dataKey="month" dataKey="month"
series={[ series={[
{ name: 'income', color: 'green', label: 'Pemasukan' }, { name: "income", color: "green", label: "Pemasukan" },
{ name: 'expense', color: 'red', label: 'Pengeluaran' }, { name: "expense", color: "red", label: "Pengeluaran" },
]} ]}
withLegend withLegend
/> />
@@ -186,7 +197,13 @@ const KeuanganAnggaran = () => {
{/* Alokasi Anggaran Per Sektor */} {/* Alokasi Anggaran Per Sektor */}
<Grid.Col span={{ base: 12, lg: 6 }}> <Grid.Col span={{ base: 12, lg: 6 }}>
<Card p="md" radius="md" withBorder bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }}> <Card
p="md"
radius="md"
withBorder
bg={dark ? "#141D34" : "white"}
style={{ borderColor: dark ? "#141D34" : "white" }}
>
<Title order={3} fw={500} mb="md"> <Title order={3} fw={500} mb="md">
Alokasi Anggaran Per Sektor Alokasi Anggaran Per Sektor
</Title> </Title>
@@ -194,7 +211,9 @@ const KeuanganAnggaran = () => {
h={300} h={300}
data={allocationData} data={allocationData}
dataKey="sector" dataKey="sector"
series={[{ name: 'amount', color: 'darmasaba-navy', label: 'Jumlah' }]} series={[
{ name: "amount", color: "darmasaba-navy", label: "Jumlah" },
]}
withLegend withLegend
orientation="horizontal" orientation="horizontal"
/> />
@@ -205,7 +224,13 @@ const KeuanganAnggaran = () => {
<Grid gutter="lg"> <Grid gutter="lg">
{/* Dana Bantuan & Hibah */} {/* Dana Bantuan & Hibah */}
<Grid.Col span={{ base: 12, lg: 6 }}> <Grid.Col span={{ base: 12, lg: 6 }}>
<Card p="md" radius="md" withBorder bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }}> <Card
p="md"
radius="md"
withBorder
bg={dark ? "#141D34" : "white"}
style={{ borderColor: dark ? "#141D34" : "white" }}
>
<Title order={3} fw={500} mb="md"> <Title order={3} fw={500} mb="md">
Dana Bantuan & Hibah Dana Bantuan & Hibah
</Title> </Title>
@@ -243,13 +268,21 @@ const KeuanganAnggaran = () => {
{/* Laporan APBDes */} {/* Laporan APBDes */}
<Grid.Col span={{ base: 12, lg: 6 }}> <Grid.Col span={{ base: 12, lg: 6 }}>
<Card p="md" radius="md" withBorder bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }}> <Card
p="md"
radius="md"
withBorder
bg={dark ? "#141D34" : "white"}
style={{ borderColor: dark ? "#141D34" : "white" }}
>
<Title order={3} fw={500} mb="md"> <Title order={3} fw={500} mb="md">
Laporan APBDes Laporan APBDes
</Title> </Title>
<Box mb="md"> <Box mb="md">
<Title order={4} mb="sm">Pendapatan</Title> <Title order={4} mb="sm">
Pendapatan
</Title>
<Stack gap="xs"> <Stack gap="xs">
{apbdReport.income.map((item, index) => ( {apbdReport.income.map((item, index) => (
<Group key={index} justify="space-between"> <Group key={index} justify="space-between">
@@ -269,7 +302,9 @@ const KeuanganAnggaran = () => {
</Box> </Box>
<Box> <Box>
<Title order={4} mb="sm">Belanja</Title> <Title order={4} mb="sm">
Belanja
</Title>
<Stack gap="xs"> <Stack gap="xs">
{apbdReport.expenses.map((item, index) => ( {apbdReport.expenses.map((item, index) => (
<Group key={index} justify="space-between"> <Group key={index} justify="space-between">
@@ -288,11 +323,26 @@ const KeuanganAnggaran = () => {
</Stack> </Stack>
</Box> </Box>
<Box mt="md" pt="md" style={{ borderTop: '1px solid var(--mantine-color-gray-3)' }}> <Box
mt="md"
pt="md"
style={{ borderTop: "1px solid var(--mantine-color-gray-3)" }}
>
<Group justify="space-between"> <Group justify="space-between">
<Text fw={700}>Saldo:</Text> <Text fw={700}>Saldo:</Text>
<Text fw={700} c={apbdReport.totalIncome > apbdReport.totalExpenses ? "green" : "red"}> <Text
Rp {(apbdReport.totalIncome - apbdReport.totalExpenses).toLocaleString()}jt fw={700}
c={
apbdReport.totalIncome > apbdReport.totalExpenses
? "green"
: "red"
}
>
Rp{" "}
{(
apbdReport.totalIncome - apbdReport.totalExpenses
).toLocaleString()}
jt
</Text> </Text>
</Group> </Group>
</Box> </Box>
@@ -304,4 +354,4 @@ const KeuanganAnggaran = () => {
); );
}; };
export default KeuanganAnggaran; export default KeuanganAnggaran;

View File

@@ -1,27 +1,38 @@
import { import {
Stack, ActionIcon,
Box,
Card,
Divider,
Grid, Grid,
GridCol, GridCol,
Group, Group,
Text,
Title,
ActionIcon,
Progress as MantineProgress,
Box,
Badge as MantineBadge,
Card,
useMantineColorScheme,
ThemeIcon,
List, List,
Divider, Badge as MantineBadge,
Skeleton Progress as MantineProgress,
Skeleton,
Stack,
Text,
ThemeIcon,
Title,
useMantineColorScheme,
} from "@mantine/core"; } from "@mantine/core";
import {
Bar,
BarChart,
CartesianGrid,
Cell,
Pie,
PieChart,
ResponsiveContainer,
Tooltip,
XAxis,
YAxis,
} from "recharts";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Bar, BarChart, CartesianGrid, XAxis, YAxis, Tooltip, ResponsiveContainer, PieChart, Pie, Cell } from "recharts";
const KinerjaDivisi = () => { const KinerjaDivisi = () => {
const { colorScheme } = useMantineColorScheme(); const { colorScheme } = useMantineColorScheme();
const dark = colorScheme === 'dark'; const dark = colorScheme === "dark";
// Data for division progress chart // Data for division progress chart
const divisionProgressData = [ const divisionProgressData = [
@@ -39,7 +50,7 @@ const KinerjaDivisi = () => {
{ title: "Laporan Bulanan", status: "selesai" }, { title: "Laporan Bulanan", status: "selesai" },
{ title: "Arsip Dokumen", status: "berjalan" }, { title: "Arsip Dokumen", status: "berjalan" },
{ title: "Undangan Rapat", status: "tertunda" }, { title: "Undangan Rapat", status: "tertunda" },
] ],
}, },
{ {
name: "Keuangan", name: "Keuangan",
@@ -47,7 +58,7 @@ const KinerjaDivisi = () => {
{ title: "Laporan APBDes", status: "selesai" }, { title: "Laporan APBDes", status: "selesai" },
{ title: "Verifikasi Dana", status: "tertunda" }, { title: "Verifikasi Dana", status: "tertunda" },
{ title: "Pengeluaran Harian", status: "berjalan" }, { title: "Pengeluaran Harian", status: "berjalan" },
] ],
}, },
{ {
name: "Sosial", name: "Sosial",
@@ -55,7 +66,7 @@ const KinerjaDivisi = () => {
{ title: "Program Bantuan", status: "selesai" }, { title: "Program Bantuan", status: "selesai" },
{ title: "Kegiatan Posyandu", status: "berjalan" }, { title: "Kegiatan Posyandu", status: "berjalan" },
{ title: "Monitoring Stunting", status: "tertunda" }, { title: "Monitoring Stunting", status: "tertunda" },
] ],
}, },
{ {
name: "Humas", name: "Humas",
@@ -63,7 +74,7 @@ const KinerjaDivisi = () => {
{ title: "Publikasi Kegiatan", status: "selesai" }, { title: "Publikasi Kegiatan", status: "selesai" },
{ title: "Koordinasi Media", status: "berjalan" }, { title: "Koordinasi Media", status: "berjalan" },
{ title: "Laporan Kegiatan", status: "tertunda" }, { title: "Laporan Kegiatan", status: "tertunda" },
] ],
}, },
]; ];
@@ -77,10 +88,30 @@ const KinerjaDivisi = () => {
// Activity progress // Activity progress
const activityProgress = [ 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: "Pembangunan Jalan",
{ name: "Vaksinasi Massal", progress: 45, date: "20 Feb 2026", status: "berjalan" }, progress: 75,
{ name: "Festival Budaya", progress: 20, date: "5 Mar 2026", status: "berjalan" }, 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 // Document statistics
@@ -97,19 +128,31 @@ const KinerjaDivisi = () => {
{ name: "Dibatalkan", value: 2 }, { name: "Dibatalkan", value: 2 },
]; ];
const COLORS = ['#10B981', '#F59E0B', '#EF4444', '#6B7280']; const COLORS = ["#10B981", "#F59E0B", "#EF4444", "#6B7280"];
const STATUS_COLORS: Record<string, string> = { const STATUS_COLORS: Record<string, string> = {
selesai: 'green', selesai: "green",
berjalan: 'blue', berjalan: "blue",
tertunda: 'red', tertunda: "red",
proses: 'yellow' proses: "yellow",
}; };
// Discussion data // Discussion data
const discussions = [ 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: "Pembahasan APBDes 2026",
{ title: "Festival Budaya", sender: "Divisi Humas", timestamp: "1 hari yang lalu" }, 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 // Today's agenda
@@ -121,32 +164,73 @@ const KinerjaDivisi = () => {
return ( return (
<Stack gap="lg"> <Stack gap="lg">
{/* Grafik Progres Tugas per Divisi */} {/* Grafik Progres Tugas per Divisi */}
<Card p="md" radius="md" withBorder bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }} > <Card
<Title order={4} mb="md" c={dark ? 'white' : 'darmasaba-navy'}> 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 Grafik Progres Tugas per Divisi
</Title> </Title>
<ResponsiveContainer width="100%" height={300}> <ResponsiveContainer width="100%" height={300}>
<BarChart data={divisionProgressData}> <BarChart data={divisionProgressData}>
<CartesianGrid strokeDasharray="3 3" vertical={false} stroke={dark ? "#141D34" : "white"} /> <CartesianGrid
strokeDasharray="3 3"
vertical={false}
stroke={dark ? "#141D34" : "white"}
/>
<XAxis <XAxis
dataKey="name" dataKey="name"
axisLine={false} axisLine={false}
tickLine={false} tickLine={false}
tick={{ fill: dark ? "var(--mantine-color-text)" : "var(--mantine-color-text)" }} tick={{
fill: dark
? "var(--mantine-color-text)"
: "var(--mantine-color-text)",
}}
/> />
<YAxis <YAxis
axisLine={false} axisLine={false}
tickLine={false} tickLine={false}
tick={{ fill: dark ? "var(--mantine-color-text)" : "var(--mantine-color-text)" }} tick={{
fill: dark
? "var(--mantine-color-text)"
: "var(--mantine-color-text)",
}}
/> />
<Tooltip <Tooltip
contentStyle={dark contentStyle={
? { backgroundColor: 'var(--mantine-color-dark-7)', borderColor: 'var(--mantine-color-dark-6)' } 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]}
/> />
<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> </BarChart>
</ResponsiveContainer> </ResponsiveContainer>
</Card> </Card>
@@ -155,17 +239,26 @@ const KinerjaDivisi = () => {
<Grid gutter="md"> <Grid gutter="md">
{divisionTasks.map((division, index) => ( {divisionTasks.map((division, index) => (
<GridCol key={index} span={{ base: 12, md: 6, lg: 3 }}> <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%"> <Card
<Title order={4} mb="sm" c={dark ? 'white' : 'darmasaba-navy'}> 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} {division.name}
</Title> </Title>
<Stack gap="sm"> <Stack gap="sm">
{division.tasks.map((task, taskIndex) => ( {division.tasks.map((task, taskIndex) => (
<Box key={taskIndex}> <Box key={taskIndex}>
<Group justify="space-between"> <Group justify="space-between">
<Text size="sm" c={dark ? 'white' : 'darmasaba-navy'}>{task.title}</Text> <Text size="sm" c={dark ? "white" : "darmasaba-navy"}>
{task.title}
</Text>
<MantineBadge <MantineBadge
color={STATUS_COLORS[task.status] || 'gray'} color={STATUS_COLORS[task.status] || "gray"}
variant="light" variant="light"
> >
{task.status} {task.status}
@@ -180,17 +273,33 @@ const KinerjaDivisi = () => {
</Grid> </Grid>
{/* Arsip Digital Perangkat Desa */} {/* Arsip Digital Perangkat Desa */}
<Card p="md" radius="md" withBorder bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }}> <Card
<Title order={4} mb="md" c={dark ? 'white' : 'darmasaba-navy'}> 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 Arsip Digital Perangkat Desa
</Title> </Title>
<Grid gutter="md"> <Grid gutter="md">
{archiveItems.map((item, index) => ( {archiveItems.map((item, index) => (
<GridCol key={index} span={{ base: 12, md: 6, lg: 3 }}> <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" }}> <Card
p="md"
radius="md"
withBorder
bg={dark ? "#263852ff" : "#F1F5F9"}
style={{ borderColor: dark ? "#263852ff" : "#F1F5F9" }}
>
<Group justify="space-between"> <Group justify="space-between">
<Text c={dark ? 'white' : 'darmasaba-navy'} fw={500}>{item.name}</Text> <Text c={dark ? "white" : "darmasaba-navy"} fw={500}>
<Text c={dark ? 'white' : 'darmasaba-navy'} fw={700}>{item.count}</Text> {item.name}
</Text>
<Text c={dark ? "white" : "darmasaba-navy"} fw={700}>
{item.count}
</Text>
</Group> </Group>
</Card> </Card>
</GridCol> </GridCol>
@@ -199,17 +308,32 @@ const KinerjaDivisi = () => {
</Card> </Card>
{/* Kartu Progres Kegiatan */} {/* Kartu Progres Kegiatan */}
<Card p="md" radius="md" withBorder bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }}> <Card
<Title order={4} mb="md" c={dark ? 'white' : 'darmasaba-navy'}> 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 Progres Kegiatan / Program
</Title> </Title>
<Stack gap="md"> <Stack gap="md">
{activityProgress.map((activity, index) => ( {activityProgress.map((activity, index) => (
<Card key={index} p="md" radius="md" withBorder bg={dark ? "#263852ff" : "#F1F5F9"} style={{ borderColor: dark ? "#263852ff" : "#F1F5F9" }}> <Card
key={index}
p="md"
radius="md"
withBorder
bg={dark ? "#263852ff" : "#F1F5F9"}
style={{ borderColor: dark ? "#263852ff" : "#F1F5F9" }}
>
<Group justify="space-between" mb="sm"> <Group justify="space-between" mb="sm">
<Text c={dark ? 'white' : 'darmasaba-navy'} fw={500}>{activity.name}</Text> <Text c={dark ? "white" : "darmasaba-navy"} fw={500}>
{activity.name}
</Text>
<MantineBadge <MantineBadge
color={STATUS_COLORS[activity.status] || 'gray'} color={STATUS_COLORS[activity.status] || "gray"}
variant="light" variant="light"
> >
{activity.status} {activity.status}
@@ -223,9 +347,13 @@ const KinerjaDivisi = () => {
color={activity.progress === 100 ? "green" : "blue"} color={activity.progress === 100 ? "green" : "blue"}
w="calc(100% - 80px)" w="calc(100% - 80px)"
/> />
<Text size="sm" c={dark ? 'white' : 'darmasaba-navy'}>{activity.progress}%</Text> <Text size="sm" c={dark ? "white" : "darmasaba-navy"}>
{activity.progress}%
</Text>
</Group> </Group>
<Text size="sm" c="dimmed" mt="sm">{activity.date}</Text> <Text size="sm" c="dimmed" mt="sm">
{activity.date}
</Text>
</Card> </Card>
))} ))}
</Stack> </Stack>
@@ -234,38 +362,75 @@ const KinerjaDivisi = () => {
{/* Statistik Dokumen & Progres Kegiatan */} {/* Statistik Dokumen & Progres Kegiatan */}
<Grid gutter="md"> <Grid gutter="md">
<GridCol span={{ base: 12, lg: 6 }}> <GridCol span={{ base: 12, lg: 6 }}>
<Card p="md" radius="md" withBorder bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }}> <Card
<Title order={4} mb="md" c={dark ? 'white' : 'darmasaba-navy'}> 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 Jumlah Dokumen
</Title> </Title>
<ResponsiveContainer width="100%" height={200}> <ResponsiveContainer width="100%" height={200}>
<BarChart data={documentStats}> <BarChart data={documentStats}>
<CartesianGrid strokeDasharray="3 3" vertical={false} stroke={dark ? "#141D34" : "white"} /> <CartesianGrid
strokeDasharray="3 3"
vertical={false}
stroke={dark ? "#141D34" : "white"}
/>
<XAxis <XAxis
dataKey="name" dataKey="name"
axisLine={false} axisLine={false}
tickLine={false} tickLine={false}
tick={{ fill: dark ? "var(--mantine-color-text)" : "var(--mantine-color-text)" }} tick={{
fill: dark
? "var(--mantine-color-text)"
: "var(--mantine-color-text)",
}}
/> />
<YAxis <YAxis
axisLine={false} axisLine={false}
tickLine={false} tickLine={false}
tick={{ fill: dark ? "var(--mantine-color-text)" : "var(--mantine-color-text)" }} tick={{
fill: dark
? "var(--mantine-color-text)"
: "var(--mantine-color-text)",
}}
/> />
<Tooltip <Tooltip
contentStyle={dark contentStyle={
? { backgroundColor: 'var(--mantine-color-dark-7)', borderColor: 'var(--mantine-color-dark-6)' } 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]}
/> />
<Bar dataKey="value" fill={dark ? "var(--mantine-color-blue-6)" : "var(--mantine-color-blue-filled)"} radius={[4, 4, 0, 0]} />
</BarChart> </BarChart>
</ResponsiveContainer> </ResponsiveContainer>
</Card> </Card>
</GridCol> </GridCol>
<GridCol span={{ base: 12, lg: 6 }}> <GridCol span={{ base: 12, lg: 6 }}>
<Card p="md" radius="md" withBorder bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }}> <Card
<Title order={4} mb="md" c={dark ? 'white' : 'darmasaba-navy'}> 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 Progres Kegiatan
</Title> </Title>
<ResponsiveContainer width="100%" height={200}> <ResponsiveContainer width="100%" height={200}>
@@ -278,16 +443,26 @@ const KinerjaDivisi = () => {
outerRadius={80} outerRadius={80}
fill="#8884d8" fill="#8884d8"
dataKey="value" dataKey="value"
label={({ name, percent }) => `${name}: ${percent ? (percent * 100).toFixed(0) : '0'}%`} label={({ name, percent }) =>
`${name}: ${percent ? (percent * 100).toFixed(0) : "0"}%`
}
> >
{activityProgressStats.map((entry, index) => ( {activityProgressStats.map((entry, index) => (
<Cell key={`cell-${index}`} fill={COLORS[index % COLORS.length]} /> <Cell
key={`cell-${index}`}
fill={COLORS[index % COLORS.length]}
/>
))} ))}
</Pie> </Pie>
<Tooltip <Tooltip
contentStyle={dark contentStyle={
? { backgroundColor: 'var(--mantine-color-dark-7)', borderColor: 'var(--mantine-color-dark-6)' } dark
: {}} ? {
backgroundColor: "var(--mantine-color-dark-7)",
borderColor: "var(--mantine-color-dark-6)",
}
: {}
}
/> />
</PieChart> </PieChart>
</ResponsiveContainer> </ResponsiveContainer>
@@ -296,26 +471,51 @@ const KinerjaDivisi = () => {
</Grid> </Grid>
{/* Diskusi Internal */} {/* Diskusi Internal */}
<Card p="md" radius="md" withBorder bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }}> <Card
<Title order={4} mb="md" c={dark ? 'white' : 'darmasaba-navy'}> 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 Diskusi Internal
</Title> </Title>
<Stack gap="sm"> <Stack gap="sm">
{discussions.map((discussion, index) => ( {discussions.map((discussion, index) => (
<Card key={index} p="md" radius="md" withBorder bg={dark ? "#263852ff" : "#F1F5F9"} style={{ borderColor: dark ? "#263852ff" : "#F1F5F9" }}> <Card
key={index}
p="md"
radius="md"
withBorder
bg={dark ? "#263852ff" : "#F1F5F9"}
style={{ borderColor: dark ? "#263852ff" : "#F1F5F9" }}
>
<Group justify="space-between"> <Group justify="space-between">
<Text c={dark ? 'white' : 'darmasaba-navy'} fw={500}>{discussion.title}</Text> <Text c={dark ? "white" : "darmasaba-navy"} fw={500}>
<Text size="sm" c="dimmed">{discussion.timestamp}</Text> {discussion.title}
</Text>
<Text size="sm" c="dimmed">
{discussion.timestamp}
</Text>
</Group> </Group>
<Text size="sm" c="dimmed">{discussion.sender}</Text> <Text size="sm" c="dimmed">
{discussion.sender}
</Text>
</Card> </Card>
))} ))}
</Stack> </Stack>
</Card> </Card>
{/* Agenda / Acara Hari Ini */} {/* Agenda / Acara Hari Ini */}
<Card p="md" radius="md" withBorder bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }}> <Card
<Title order={4} mb="md" c={dark ? 'white' : 'darmasaba-navy'}> 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 Agenda / Acara Hari Ini
</Title> </Title>
{todayAgenda.length > 0 ? ( {todayAgenda.length > 0 ? (
@@ -326,7 +526,9 @@ const KinerjaDivisi = () => {
<Text c="dimmed">{agenda.time}</Text> <Text c="dimmed">{agenda.time}</Text>
</Box> </Box>
<Divider orientation="vertical" mx="sm" /> <Divider orientation="vertical" mx="sm" />
<Text c={dark ? 'white' : 'darmasaba-navy'}>{agenda.event}</Text> <Text c={dark ? "white" : "darmasaba-navy"}>
{agenda.event}
</Text>
</Group> </Group>
))} ))}
</Stack> </Stack>
@@ -340,4 +542,4 @@ const KinerjaDivisi = () => {
); );
}; };
export default KinerjaDivisi; export default KinerjaDivisi;

View File

@@ -1,38 +1,54 @@
import type React from "react";
import { useState } from "react";
import { import {
ActionIcon,
Badge,
Box,
Button, Button,
Card, Card,
Divider,
Grid, Grid,
GridCol, GridCol,
Group, Group,
Text,
Title,
TextInput,
Textarea,
Select,
Table,
Badge,
Stack,
useMantineColorScheme,
List, List,
Divider, Select,
ActionIcon, Stack,
Box Table,
Text,
Textarea,
TextInput,
Title,
useMantineColorScheme,
} from "@mantine/core"; } from "@mantine/core";
import { IconMessage, IconAlertTriangle, IconClock, IconCheck, IconChevronRight } from "@tabler/icons-react"; import {
import { Line, LineChart, Bar, BarChart, CartesianGrid, XAxis, YAxis, Tooltip, ResponsiveContainer } from "recharts"; IconAlertTriangle,
IconCheck,
IconChevronRight,
IconClock,
IconMessage,
} from "@tabler/icons-react";
import type React from "react";
import { useState } from "react";
import {
Bar,
BarChart,
CartesianGrid,
Line,
LineChart,
ResponsiveContainer,
Tooltip,
XAxis,
YAxis,
} from "recharts";
const PengaduanLayananPublik = () => { const PengaduanLayananPublik = () => {
const { colorScheme } = useMantineColorScheme(); const { colorScheme } = useMantineColorScheme();
const dark = colorScheme === 'dark'; const dark = colorScheme === "dark";
// Summary data // Summary data
const summaryData = { const summaryData = {
total: 42, total: 42,
baru: 14, baru: 14,
diproses: 14, diproses: 14,
selesai: 14 selesai: 14,
}; };
// Tren pengaduan data // Tren pengaduan data
@@ -42,7 +58,7 @@ const PengaduanLayananPublik = () => {
{ bulan: "Mar", jumlah: 42 }, { bulan: "Mar", jumlah: 42 },
{ bulan: "Apr", jumlah: 38 }, { bulan: "Apr", jumlah: 38 },
{ bulan: "Mei", jumlah: 45 }, { bulan: "Mei", jumlah: 45 },
{ bulan: "Jun", jumlah: 42 } { bulan: "Jun", jumlah: 42 },
]; ];
// Surat terbanyak data // Surat terbanyak data
@@ -51,24 +67,65 @@ const PengaduanLayananPublik = () => {
{ jenis: "KK", jumlah: 18 }, { jenis: "KK", jumlah: 18 },
{ jenis: "Domisili", jumlah: 15 }, { jenis: "Domisili", jumlah: 15 },
{ jenis: "Usaha", jumlah: 12 }, { jenis: "Usaha", jumlah: 12 },
{ jenis: "Lainnya", jumlah: 8 } { jenis: "Lainnya", jumlah: 8 },
]; ];
// Pengajuan terbaru data // Pengajuan terbaru data
const pengajuanTerbaru = [ 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: "Budi Santoso",
{ nama: "Ahmad Fauzi", jenis: "Infrastruktur", waktu: "1 hari yang lalu", status: "selesai" }, jenis: "Ketertiban Umum",
{ nama: "Dewi Lestari", jenis: "Administrasi", waktu: "1 hari yang lalu", status: "baru" }, waktu: "2 jam yang lalu",
{ nama: "Joko Widodo", jenis: "Keamanan", waktu: "2 hari yang lalu", status: "diproses" } 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 // Ide inovatif data
const ideInovatif = [ const ideInovatif = [
{ nama: "Andi Prasetyo", judul: "Penerapan Smart Village", kategori: "Teknologi" }, {
{ nama: "Rina Kusuma", judul: "Program Ekowisata Desa", kategori: "Ekonomi" }, nama: "Andi Prasetyo",
{ nama: "Bambang Suryono", judul: "Peningkatan Sanitasi", kategori: "Kesehatan" }, judul: "Penerapan Smart Village",
{ nama: "Lina Marlina", judul: "Pusat Kreatif Anak Muda", kategori: "Pendidikan" } 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">( const [activeTab, setActiveTab] = useState<"complaints" | "services">(
@@ -229,10 +286,14 @@ const PengaduanLayananPublik = () => {
// Status badge color mapping // Status badge color mapping
const getStatusColor = (status: string) => { const getStatusColor = (status: string) => {
switch (status) { switch (status) {
case 'baru': return 'red'; case "baru":
case 'diproses': return 'yellow'; return "red";
case 'selesai': return 'green'; case "diproses":
default: return 'gray'; return "yellow";
case "selesai":
return "green";
default:
return "gray";
} }
}; };
@@ -243,7 +304,14 @@ const PengaduanLayananPublik = () => {
{/* Summary Cards */} {/* Summary Cards */}
<Grid gutter="md"> <Grid gutter="md">
<GridCol span={{ base: 12, md: 6, lg: 3 }}> <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%"> <Card
p="md"
radius="md"
withBorder
bg={dark ? "#141D34" : "white"}
style={{ borderColor: dark ? "#141D34" : "white" }}
h="100%"
>
<Group justify="space-between" align="center"> <Group justify="space-between" align="center">
<Stack gap={0}> <Stack gap={0}>
<Text size="sm" c={dark ? "dark.3" : "dimmed"}> <Text size="sm" c={dark ? "dark.3" : "dimmed"}>
@@ -266,7 +334,14 @@ const PengaduanLayananPublik = () => {
</GridCol> </GridCol>
<GridCol span={{ base: 12, md: 6, lg: 3 }}> <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%"> <Card
p="md"
radius="md"
withBorder
bg={dark ? "#141D34" : "white"}
style={{ borderColor: dark ? "#141D34" : "white" }}
h="100%"
>
<Group justify="space-between" align="center"> <Group justify="space-between" align="center">
<Stack gap={0}> <Stack gap={0}>
<Text size="sm" c={dark ? "dark.3" : "dimmed"}> <Text size="sm" c={dark ? "dark.3" : "dimmed"}>
@@ -276,12 +351,7 @@ const PengaduanLayananPublik = () => {
{summaryData.baru} {summaryData.baru}
</Text> </Text>
</Stack> </Stack>
<Badge <Badge variant="light" color="red" p={8} radius="md">
variant="light"
color="red"
p={8}
radius="md"
>
<IconAlertTriangle size={20} /> <IconAlertTriangle size={20} />
</Badge> </Badge>
</Group> </Group>
@@ -289,7 +359,14 @@ const PengaduanLayananPublik = () => {
</GridCol> </GridCol>
<GridCol span={{ base: 12, md: 6, lg: 3 }}> <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%"> <Card
p="md"
radius="md"
withBorder
bg={dark ? "#141D34" : "white"}
style={{ borderColor: dark ? "#141D34" : "white" }}
h="100%"
>
<Group justify="space-between" align="center"> <Group justify="space-between" align="center">
<Stack gap={0}> <Stack gap={0}>
<Text size="sm" c={dark ? "dark.3" : "dimmed"}> <Text size="sm" c={dark ? "dark.3" : "dimmed"}>
@@ -299,12 +376,7 @@ const PengaduanLayananPublik = () => {
{summaryData.diproses} {summaryData.diproses}
</Text> </Text>
</Stack> </Stack>
<Badge <Badge variant="light" color="yellow" p={8} radius="md">
variant="light"
color="yellow"
p={8}
radius="md"
>
<IconClock size={20} /> <IconClock size={20} />
</Badge> </Badge>
</Group> </Group>
@@ -312,7 +384,14 @@ const PengaduanLayananPublik = () => {
</GridCol> </GridCol>
<GridCol span={{ base: 12, md: 6, lg: 3 }}> <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%"> <Card
p="md"
radius="md"
withBorder
bg={dark ? "#141D34" : "white"}
style={{ borderColor: dark ? "#141D34" : "white" }}
h="100%"
>
<Group justify="space-between" align="center"> <Group justify="space-between" align="center">
<Stack gap={0}> <Stack gap={0}>
<Text size="sm" c={dark ? "dark.3" : "dimmed"}> <Text size="sm" c={dark ? "dark.3" : "dimmed"}>
@@ -322,12 +401,7 @@ const PengaduanLayananPublik = () => {
{summaryData.selesai} {summaryData.selesai}
</Text> </Text>
</Stack> </Stack>
<Badge <Badge variant="light" color="green" p={8} radius="md">
variant="light"
color="green"
p={8}
radius="md"
>
<IconCheck size={20} /> <IconCheck size={20} />
</Badge> </Badge>
</Group> </Group>
@@ -336,7 +410,13 @@ const PengaduanLayananPublik = () => {
</Grid> </Grid>
{/* Grafik Tren Pengaduan */} {/* Grafik Tren Pengaduan */}
<Card p="md" radius="md" withBorder bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }} > <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"}> <Title order={4} mb="md" c={dark ? "dark.0" : "black"}>
Grafik Tren Pengaduan Grafik Tren Pengaduan
</Title> </Title>
@@ -345,31 +425,58 @@ const PengaduanLayananPublik = () => {
<CartesianGrid <CartesianGrid
strokeDasharray="3 3" strokeDasharray="3 3"
vertical={false} vertical={false}
stroke={dark ? "var(--mantine-color-gray-7)" : "var(--mantine-color-gray-3)"} stroke={
dark
? "var(--mantine-color-gray-7)"
: "var(--mantine-color-gray-3)"
}
/> />
<XAxis <XAxis
dataKey="bulan" dataKey="bulan"
axisLine={false} axisLine={false}
tickLine={false} tickLine={false}
tick={{ fill: dark ? "var(--mantine-color-text)" : "var(--mantine-color-text)" }} tick={{
fill: dark
? "var(--mantine-color-text)"
: "var(--mantine-color-text)",
}}
/> />
<YAxis <YAxis
axisLine={false} axisLine={false}
tickLine={false} tickLine={false}
tick={{ fill: dark ? "var(--mantine-color-text)" : "var(--mantine-color-text)" }} tick={{
fill: dark
? "var(--mantine-color-text)"
: "var(--mantine-color-text)",
}}
/> />
<Tooltip <Tooltip
contentStyle={dark contentStyle={
? { backgroundColor: 'var(--mantine-color-dark-7)', borderColor: 'var(--mantine-color-dark-6)' } dark
: {}} ? {
backgroundColor: "var(--mantine-color-dark-7)",
borderColor: "var(--mantine-color-dark-6)",
}
: {}
}
/> />
<Line <Line
type="monotone" type="monotone"
dataKey="jumlah" dataKey="jumlah"
stroke={dark ? "var(--mantine-color-blue-6)" : "var(--mantine-color-blue-filled)"} stroke={
dark
? "var(--mantine-color-blue-6)"
: "var(--mantine-color-blue-filled)"
}
strokeWidth={2} strokeWidth={2}
dot={{ stroke: dark ? "var(--mantine-color-blue-6)" : "var(--mantine-color-blue-filled)", strokeWidth: 2, r: 4 }} dot={{
activeDot={{ r: 6, stroke: '#fff', strokeWidth: 2 }} stroke: dark
? "var(--mantine-color-blue-6)"
: "var(--mantine-color-blue-filled)",
strokeWidth: 2,
r: 4,
}}
activeDot={{ r: 6, stroke: "#fff", strokeWidth: 2 }}
/> />
</LineChart> </LineChart>
</ResponsiveContainer> </ResponsiveContainer>
@@ -379,7 +486,14 @@ const PengaduanLayananPublik = () => {
<Grid gutter="md"> <Grid gutter="md">
{/* Surat Terbanyak */} {/* Surat Terbanyak */}
<GridCol span={{ base: 12, lg: 4 }}> <GridCol span={{ base: 12, lg: 4 }}>
<Card p="md" radius="md" withBorder bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }} h="100%"> <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"}> <Title order={4} mb="md" c={dark ? "dark.0" : "black"}>
Surat Terbanyak Surat Terbanyak
</Title> </Title>
@@ -388,30 +502,51 @@ const PengaduanLayananPublik = () => {
<CartesianGrid <CartesianGrid
strokeDasharray="3 3" strokeDasharray="3 3"
horizontal={false} horizontal={false}
stroke={dark ? "var(--mantine-color-gray-7)" : "var(--mantine-color-gray-3)"} stroke={
dark
? "var(--mantine-color-gray-7)"
: "var(--mantine-color-gray-3)"
}
/> />
<XAxis <XAxis
dataKey="jumlah" dataKey="jumlah"
axisLine={false} axisLine={false}
tickLine={false} tickLine={false}
tick={{ fill: dark ? "var(--mantine-color-text)" : "var(--mantine-color-text)" }} tick={{
fill: dark
? "var(--mantine-color-text)"
: "var(--mantine-color-text)",
}}
/> />
<YAxis <YAxis
dataKey="jenis" dataKey="jenis"
type="category" type="category"
axisLine={false} axisLine={false}
tickLine={false} tickLine={false}
tick={{ fill: dark ? "var(--mantine-color-text)" : "var(--mantine-color-text)" }} tick={{
fill: dark
? "var(--mantine-color-text)"
: "var(--mantine-color-text)",
}}
width={80} width={80}
/> />
<Tooltip <Tooltip
contentStyle={dark contentStyle={
? { backgroundColor: 'var(--mantine-color-dark-7)', borderColor: 'var(--mantine-color-dark-6)' } dark
: {}} ? {
backgroundColor: "var(--mantine-color-dark-7)",
borderColor: "var(--mantine-color-dark-6)",
}
: {}
}
/> />
<Bar <Bar
dataKey="jumlah" dataKey="jumlah"
fill={dark ? "var(--mantine-color-blue-6)" : "var(--mantine-color-blue-filled)"} fill={
dark
? "var(--mantine-color-blue-6)"
: "var(--mantine-color-blue-filled)"
}
radius={[0, 4, 4, 0]} radius={[0, 4, 4, 0]}
/> />
</BarChart> </BarChart>
@@ -421,7 +556,14 @@ const PengaduanLayananPublik = () => {
{/* Pengajuan Terbaru */} {/* Pengajuan Terbaru */}
<GridCol span={{ base: 12, lg: 4 }}> <GridCol span={{ base: 12, lg: 4 }}>
<Card p="md" radius="md" withBorder bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }} h="100%"> <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"}> <Title order={4} mb="md" c={dark ? "dark.0" : "black"}>
Pengajuan Terbaru Pengajuan Terbaru
</Title> </Title>
@@ -429,14 +571,23 @@ const PengaduanLayananPublik = () => {
<Box key={index}> <Box key={index}>
<Group justify="space-between"> <Group justify="space-between">
<Stack gap={0}> <Stack gap={0}>
<Text fw={500} c={dark ? "dark.0" : "black"}>{item.nama}</Text> <Text fw={500} c={dark ? "dark.0" : "black"}>
<Text size="sm" c={dark ? "dark.3" : "dimmed"}>{item.jenis}</Text> {item.nama}
</Text>
<Text size="sm" c={dark ? "dark.3" : "dimmed"}>
{item.jenis}
</Text>
</Stack> </Stack>
<Stack gap={0} align="flex-end"> <Stack gap={0} align="flex-end">
<Badge color={getStatusColor(item.status)} variant="light"> <Badge
color={getStatusColor(item.status)}
variant="light"
>
{item.status} {item.status}
</Badge> </Badge>
<Text size="xs" c={dark ? "dark.4" : "dimmed"}>{item.waktu}</Text> <Text size="xs" c={dark ? "dark.4" : "dimmed"}>
{item.waktu}
</Text>
</Stack> </Stack>
</Group> </Group>
<Divider my="sm" /> <Divider my="sm" />
@@ -447,7 +598,14 @@ const PengaduanLayananPublik = () => {
{/* Ajuan Ide Inovatif */} {/* Ajuan Ide Inovatif */}
<GridCol span={{ base: 12, lg: 4 }}> <GridCol span={{ base: 12, lg: 4 }}>
<Card p="md" radius="md" withBorder bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }} h="100%"> <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"}> <Title order={4} mb="md" c={dark ? "dark.0" : "black"}>
Ajuan Ide Inovatif Ajuan Ide Inovatif
</Title> </Title>
@@ -455,8 +613,12 @@ const PengaduanLayananPublik = () => {
<Box key={index}> <Box key={index}>
<Group justify="space-between"> <Group justify="space-between">
<Stack gap={0}> <Stack gap={0}>
<Text fw={500} c={dark ? "dark.0" : "black"}>{item.judul}</Text> <Text fw={500} c={dark ? "dark.0" : "black"}>
<Text size="sm" c={dark ? "dark.3" : "dimmed"}>{item.nama}</Text> {item.judul}
</Text>
<Text size="sm" c={dark ? "dark.3" : "dimmed"}>
{item.nama}
</Text>
</Stack> </Stack>
<Group> <Group>
<Badge color="blue" variant="light"> <Badge color="blue" variant="light">
@@ -478,9 +640,18 @@ const PengaduanLayananPublik = () => {
<Grid gutter="md"> <Grid gutter="md">
{/* Complaint Submission Form */} {/* Complaint Submission Form */}
<GridCol span={{ base: 12, lg: 4 }}> <GridCol span={{ base: 12, lg: 4 }}>
<Card p="md" withBorder radius="md" h="100%" bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }}> <Card
p="md"
withBorder
radius="md"
h="100%"
bg={dark ? "#141D34" : "white"}
style={{ borderColor: dark ? "#141D34" : "white" }}
>
<Card.Section withBorder inheritPadding py="xs"> <Card.Section withBorder inheritPadding py="xs">
<Title order={3} py="xs">Ajukan Pengaduan</Title> <Title order={3} py="xs">
Ajukan Pengaduan
</Title>
</Card.Section> </Card.Section>
<Card.Section> <Card.Section>
<form onSubmit={handleSubmitComplaint}> <form onSubmit={handleSubmitComplaint}>
@@ -537,24 +708,39 @@ const PengaduanLayananPublik = () => {
{/* Complaints List */} {/* Complaints List */}
<GridCol span={{ base: 12, lg: 8 }}> <GridCol span={{ base: 12, lg: 8 }}>
<Card withBorder radius="md" bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }}> <Card
withBorder
radius="md"
bg={dark ? "#141D34" : "white"}
style={{ borderColor: dark ? "#141D34" : "white" }}
>
<Card.Section withBorder inheritPadding py="xs"> <Card.Section withBorder inheritPadding py="xs">
<Title order={3} py="xs">Daftar Pengaduan</Title> <Title order={3} py="xs">
Daftar Pengaduan
</Title>
</Card.Section> </Card.Section>
<Card.Section py="md" px="xs"> <Card.Section py="md" px="xs">
<Table withColumnBorders> <Table withColumnBorders>
<Table.Thead> <Table.Thead>
<Table.Tr> <Table.Tr>
<Table.Th><Text c={dark ? "white" : "dark.3" }>Judul</Text></Table.Th> <Table.Th>
<Table.Th><Text c={dark ? "white" : "dark.3" }>Kategori</Text></Table.Th> <Text c={dark ? "white" : "dark.3"}>Judul</Text>
<Table.Th><Text c={dark ? "white" : "dark.3" }>Status</Text></Table.Th> </Table.Th>
<Table.Th><Text c={dark ? "white" : "dark.3" }>Prioritas</Text></Table.Th> <Table.Th>
<Table.Th><Text c={dark ? "white" : "dark.3" }>Tanggal</Text></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.Tr>
</Table.Thead> </Table.Thead>
<Table.Tbody> <Table.Tbody>{complaintRows}</Table.Tbody>
{complaintRows}
</Table.Tbody>
</Table> </Table>
</Card.Section> </Card.Section>
</Card> </Card>
@@ -565,15 +751,19 @@ const PengaduanLayananPublik = () => {
<Stack gap="lg"> <Stack gap="lg">
<Card withBorder radius="md"> <Card withBorder radius="md">
<Card.Section withBorder inheritPadding py="xs"> <Card.Section withBorder inheritPadding py="xs">
<Title order={3} py="xs">Layanan Publik Tersedia</Title> <Title order={3} py="xs">
Layanan Publik Tersedia
</Title>
</Card.Section> </Card.Section>
<Card.Section pt="md"> <Card.Section pt="md">
<Grid gutter="md"> <Grid gutter="md">
{services.map((service) => ( {services.map((service) => (
<GridCol key={service.id} span={{ base: 12, md: 6, lg: 4 }}> <GridCol key={service.id} span={{ base: 12, md: 6, lg: 4 }}>
<Card withBorder radius="md" h="100%"> <Card withBorder radius="md" h="100%">
<Title order={4} mb="sm">{service.name}</Title> <Title order={4} mb="sm">
<Text size="sm" c={dark ? "white" : "dark.3" } mb="md"> {service.name}
</Title>
<Text size="sm" c={dark ? "white" : "dark.3"} mb="md">
{service.description} {service.description}
</Text> </Text>
<Group justify="space-between"> <Group justify="space-between">
@@ -589,11 +779,11 @@ const PengaduanLayananPublik = () => {
> >
{service.status} {service.status}
</Badge> </Badge>
<Text size="sm" c={dark ? "white" : "dark.3" }> <Text size="sm" c={dark ? "white" : "dark.3"}>
{service.category} {service.category}
</Text> </Text>
</Group> </Group>
<Text size="xs" c={dark ? "white" : "dark.3" } mt="sm"> <Text size="xs" c={dark ? "white" : "dark.3"} mt="sm">
Terakhir diperbarui: {service.lastUpdated} Terakhir diperbarui: {service.lastUpdated}
</Text> </Text>
</Card> </Card>
@@ -605,13 +795,17 @@ const PengaduanLayananPublik = () => {
<Card withBorder radius="md"> <Card withBorder radius="md">
<Card.Section withBorder inheritPadding py="xs"> <Card.Section withBorder inheritPadding py="xs">
<Title order={3} py="xs">Statistik Layanan</Title> <Title order={3} py="xs">
Statistik Layanan
</Title>
</Card.Section> </Card.Section>
<Card.Section pt="md"> <Card.Section pt="md">
<Grid gutter="md"> <Grid gutter="md">
<GridCol span={{ base: 12, md: 4 }}> <GridCol span={{ base: 12, md: 4 }}>
<Card p="md" bg={dark ? "dark.7" : "gray.0"} radius="md"> <Card p="md" bg={dark ? "dark.7" : "gray.0"} radius="md">
<Title order={4} mb="xs">Jumlah Layanan Tersedia</Title> <Title order={4} mb="xs">
Jumlah Layanan Tersedia
</Title>
<Text size="xl" fw={700} c="darmasaba-blue"> <Text size="xl" fw={700} c="darmasaba-blue">
12 12
</Text> </Text>
@@ -619,7 +813,9 @@ const PengaduanLayananPublik = () => {
</GridCol> </GridCol>
<GridCol span={{ base: 12, md: 4 }}> <GridCol span={{ base: 12, md: 4 }}>
<Card p="md" bg={dark ? "dark.7" : "gray.0"} radius="md"> <Card p="md" bg={dark ? "dark.7" : "gray.0"} radius="md">
<Title order={4} mb="xs">Layanan Terpopuler</Title> <Title order={4} mb="xs">
Layanan Terpopuler
</Title>
<Text size="xl" fw={700} c="darmasaba-success"> <Text size="xl" fw={700} c="darmasaba-success">
4 4
</Text> </Text>
@@ -627,7 +823,9 @@ const PengaduanLayananPublik = () => {
</GridCol> </GridCol>
<GridCol span={{ base: 12, md: 4 }}> <GridCol span={{ base: 12, md: 4 }}>
<Card p="md" bg={dark ? "dark.7" : "gray.0"} radius="md"> <Card p="md" bg={dark ? "dark.7" : "gray.0"} radius="md">
<Title order={4} mb="xs">Permintaan Baru</Title> <Title order={4} mb="xs">
Permintaan Baru
</Title>
<Text size="xl" fw={700} c="darmasaba-warning"> <Text size="xl" fw={700} c="darmasaba-warning">
23 23
</Text> </Text>
@@ -642,4 +840,4 @@ const PengaduanLayananPublik = () => {
); );
}; };
export default PengaduanLayananPublik; export default PengaduanLayananPublik;

View File

@@ -1,125 +1,190 @@
import { Card, Title, Text, Space, Button, Group, Alert, Table, ActionIcon, Modal, TextInput, Select, useMantineColorScheme } from '@mantine/core'; import {
import { IconInfoCircle, IconUserPlus, IconTrash, IconEdit, IconUser } from '@tabler/icons-react'; ActionIcon,
import { useState } from 'react'; Alert,
Button,
Card,
Group,
Modal,
Select,
Space,
Table,
Text,
TextInput,
Title,
useMantineColorScheme,
} from "@mantine/core";
import {
IconEdit,
IconInfoCircle,
IconTrash,
IconUser,
IconUserPlus,
} from "@tabler/icons-react";
import { useState } from "react";
const AksesDanTimSettings = () => { const AksesDanTimSettings = () => {
const [opened, setOpened] = useState(false); const [opened, setOpened] = useState(false);
const { colorScheme } = useMantineColorScheme(); const { colorScheme } = useMantineColorScheme();
const dark = colorScheme === 'dark'; const dark = colorScheme === "dark";
// Sample team members data // Sample team members data
const teamMembers = [ const teamMembers = [
{ id: 1, name: 'Admin Utama', email: 'admin@desa.go.id', role: 'Administrator', status: 'Aktif' }, {
{ id: 2, name: 'Operator Desa', email: 'operator@desa.go.id', role: 'Operator', status: 'Aktif' }, id: 1,
{ id: 3, name: 'Staff Keuangan', email: 'keuangan@desa.go.id', role: 'Keuangan', status: 'Aktif' }, name: "Admin Utama",
{ id: 4, name: 'Staff Umum', email: 'umum@desa.go.id', role: 'Umum', status: 'Nonaktif' }, email: "admin@desa.go.id",
]; role: "Administrator",
status: "Aktif",
},
{
id: 2,
name: "Operator Desa",
email: "operator@desa.go.id",
role: "Operator",
status: "Aktif",
},
{
id: 3,
name: "Staff Keuangan",
email: "keuangan@desa.go.id",
role: "Keuangan",
status: "Aktif",
},
{
id: 4,
name: "Staff Umum",
email: "umum@desa.go.id",
role: "Umum",
status: "Nonaktif",
},
];
const roles = [ const roles = [
{ value: 'administrator', label: 'Administrator' }, { value: "administrator", label: "Administrator" },
{ value: 'operator', label: 'Operator' }, { value: "operator", label: "Operator" },
{ value: 'keuangan', label: 'Keuangan' }, { value: "keuangan", label: "Keuangan" },
{ value: 'umum', label: 'Umum' }, { value: "umum", label: "Umum" },
{ value: 'keamanan', label: 'Keamanan' }, { value: "keamanan", label: "Keamanan" },
]; ];
return ( return (
<Card withBorder radius="md" p="xl" bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }}> <Card
<Modal withBorder
opened={opened} radius="md"
onClose={() => setOpened(false)} p="xl"
title="Tambah Anggota Tim" bg={dark ? "#141D34" : "white"}
size="lg" style={{ borderColor: dark ? "#141D34" : "white" }}
> >
<TextInput <Modal
label="Nama Lengkap" opened={opened}
placeholder="Masukkan nama lengkap anggota tim" onClose={() => setOpened(false)}
mb="md" title="Tambah Anggota Tim"
/> size="lg"
<TextInput >
label="Alamat Email" <TextInput
placeholder="Masukkan alamat email" label="Nama Lengkap"
mb="md" placeholder="Masukkan nama lengkap anggota tim"
/> mb="md"
<Select />
label="Peran" <TextInput
placeholder="Pilih peran anggota tim" label="Alamat Email"
data={roles} placeholder="Masukkan alamat email"
mb="md" mb="md"
/> />
<Group justify="flex-end" mt="xl"> <Select
<Button variant="outline" onClick={() => setOpened(false)}>Batal</Button> label="Peran"
<Button>Undang Anggota</Button> placeholder="Pilih peran anggota tim"
</Group> data={roles}
</Modal> mb="md"
/>
<Group justify="flex-end" mt="xl">
<Button variant="outline" onClick={() => setOpened(false)}>
Batal
</Button>
<Button>Undang Anggota</Button>
</Group>
</Modal>
<Title order={2} mb="lg">Akses & Tim</Title> <Title order={2} mb="lg">
<Text color="dimmed" mb="xl">Kelola akses dan anggota tim Anda</Text> Akses & Tim
</Title>
<Text color="dimmed" mb="xl">
Kelola akses dan anggota tim Anda
</Text>
<Space h="lg" /> <Space h="lg" />
<Group justify="space-between" mb="md"> <Group justify="space-between" mb="md">
<Title order={4}>Anggota Tim</Title> <Title order={4}>Anggota Tim</Title>
<Button leftSection={<IconUserPlus size={16} />} onClick={() => setOpened(true)}> <Button
Tambah Anggota leftSection={<IconUserPlus size={16} />}
</Button> onClick={() => setOpened(true)}
</Group> >
Tambah Anggota
</Button>
</Group>
<Table highlightOnHover> <Table highlightOnHover>
<Table.Thead> <Table.Thead>
<Table.Tr> <Table.Tr>
<Table.Th>Nama</Table.Th> <Table.Th>Nama</Table.Th>
<Table.Th>Email</Table.Th> <Table.Th>Email</Table.Th>
<Table.Th>Peran</Table.Th> <Table.Th>Peran</Table.Th>
<Table.Th>Status</Table.Th> <Table.Th>Status</Table.Th>
<Table.Th>Aksi</Table.Th> <Table.Th>Aksi</Table.Th>
</Table.Tr> </Table.Tr>
</Table.Thead> </Table.Thead>
<Table.Tbody> <Table.Tbody>
{teamMembers.map((member) => ( {teamMembers.map((member) => (
<Table.Tr key={member.id}> <Table.Tr key={member.id}>
<Table.Td> <Table.Td>
<Group gap="sm"> <Group gap="sm">
<IconUser size={20} /> <IconUser size={20} />
<Text>{member.name}</Text> <Text>{member.name}</Text>
</Group> </Group>
</Table.Td> </Table.Td>
<Table.Td>{member.email}</Table.Td> <Table.Td>{member.email}</Table.Td>
<Table.Td> <Table.Td>
<Text fw={500}>{member.role}</Text> <Text fw={500}>{member.role}</Text>
</Table.Td> </Table.Td>
<Table.Td> <Table.Td>
<Text c={member.status === 'Aktif' ? 'green' : 'red'} fw={500}> <Text c={member.status === "Aktif" ? "green" : "red"} fw={500}>
{member.status} {member.status}
</Text> </Text>
</Table.Td> </Table.Td>
<Table.Td> <Table.Td>
<Group> <Group>
<ActionIcon variant="subtle" color="blue"> <ActionIcon variant="subtle" color="blue">
<IconEdit size={16} /> <IconEdit size={16} />
</ActionIcon> </ActionIcon>
<ActionIcon variant="subtle" color="red"> <ActionIcon variant="subtle" color="red">
<IconTrash size={16} /> <IconTrash size={16} />
</ActionIcon> </ActionIcon>
</Group> </Group>
</Table.Td> </Table.Td>
</Table.Tr> </Table.Tr>
))} ))}
</Table.Tbody> </Table.Tbody>
</Table> </Table>
<Space h="xl" /> <Space h="xl" />
<Alert icon={<IconInfoCircle size={16} />} title="Informasi" color="blue" mb="md"> <Alert
Administrator memiliki akses penuh ke semua fitur. Peran lainnya memiliki akses terbatas sesuai kebutuhan. icon={<IconInfoCircle size={16} />}
</Alert> title="Informasi"
color="blue"
mb="md"
>
Administrator memiliki akses penuh ke semua fitur. Peran lainnya
memiliki akses terbatas sesuai kebutuhan.
</Alert>
<Group justify="flex-end" mt="xl"> <Group justify="flex-end" mt="xl">
<Button variant="outline">Batal</Button> <Button variant="outline">Batal</Button>
<Button>Simpan Perubahan</Button> <Button>Simpan Perubahan</Button>
</Group> </Group>
</Card> </Card>
); );
}; };
export default AksesDanTimSettings; export default AksesDanTimSettings;

View File

@@ -1,57 +1,90 @@
import { Card, Title, Text, Space, Button, Group, Alert, PasswordInput, Switch, useMantineColorScheme } from '@mantine/core'; import {
import { IconInfoCircle, IconLock } from '@tabler/icons-react'; Alert,
Button,
Card,
Group,
PasswordInput,
Space,
Switch,
Text,
Title,
useMantineColorScheme,
} from "@mantine/core";
import { IconInfoCircle, IconLock } from "@tabler/icons-react";
const KeamananSettings = () => { const KeamananSettings = () => {
const { colorScheme } = useMantineColorScheme(); const { colorScheme } = useMantineColorScheme();
const dark = colorScheme === 'dark'; const dark = colorScheme === "dark";
return ( return (
<Card withBorder radius="md" p="xl" bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }}> <Card
<Title order={2} mb="lg">Pengaturan Keamanan</Title> withBorder
<Text color="dimmed" mb="xl">Kelola keamanan akun Anda</Text> radius="md"
p="xl"
bg={dark ? "#141D34" : "white"}
style={{ borderColor: dark ? "#141D34" : "white" }}
>
<Title order={2} mb="lg">
Pengaturan Keamanan
</Title>
<Text color="dimmed" mb="xl">
Kelola keamanan akun Anda
</Text>
<Space h="lg" /> <Space h="lg" />
<PasswordInput <PasswordInput
label="Kata Sandi Saat Ini" label="Kata Sandi Saat Ini"
placeholder="Masukkan kata sandi saat ini" placeholder="Masukkan kata sandi saat ini"
mb="md" mb="md"
/> />
<PasswordInput <PasswordInput
label="Kata Sandi Baru" label="Kata Sandi Baru"
placeholder="Masukkan kata sandi baru" placeholder="Masukkan kata sandi baru"
mb="md" mb="md"
/> />
<PasswordInput <PasswordInput
label="Konfirmasi Kata Sandi Baru" label="Konfirmasi Kata Sandi Baru"
placeholder="Konfirmasi kata sandi baru" placeholder="Konfirmasi kata sandi baru"
mb="md" mb="md"
/> />
<Space h="md" /> <Space h="md" />
<Group mb="md"> <Group mb="md">
<Switch label="Verifikasi Dua Langkah" /> <Switch label="Verifikasi Dua Langkah" />
<Switch label="Login Otentikasi Aplikasi" /> <Switch label="Login Otentikasi Aplikasi" />
</Group> </Group>
<Space h="md" /> <Space h="md" />
<Alert icon={<IconLock size={16} />} title="Keamanan" color="orange" mb="md"> <Alert
Gunakan kata sandi yang kuat dan unik. Hindari menggunakan kata sandi yang sama di banyak layanan. icon={<IconLock size={16} />}
</Alert> title="Keamanan"
color="orange"
mb="md"
>
Gunakan kata sandi yang kuat dan unik. Hindari menggunakan kata sandi
yang sama di banyak layanan.
</Alert>
<Alert icon={<IconInfoCircle size={16} />} title="Informasi" color="blue" mb="md"> <Alert
Setelah mengganti kata sandi, Anda akan diminta logout dari semua perangkat. icon={<IconInfoCircle size={16} />}
</Alert> title="Informasi"
color="blue"
mb="md"
>
Setelah mengganti kata sandi, Anda akan diminta logout dari semua
perangkat.
</Alert>
<Group justify="flex-end" mt="xl"> <Group justify="flex-end" mt="xl">
<Button variant="outline">Batal</Button> <Button variant="outline">Batal</Button>
<Button>Perbarui Kata Sandi</Button> <Button>Perbarui Kata Sandi</Button>
</Group> </Group>
</Card> </Card>
); );
}; };
export default KeamananSettings; export default KeamananSettings;

View File

@@ -1,55 +1,86 @@
import { Card, Title, Text, Space, Switch, Group, Alert, Checkbox, Button, useMantineColorScheme } from '@mantine/core'; import {
import { IconInfoCircle } from '@tabler/icons-react'; Alert,
Button,
Card,
Checkbox,
Group,
Space,
Switch,
Text,
Title,
useMantineColorScheme,
} from "@mantine/core";
import { IconInfoCircle } from "@tabler/icons-react";
const NotifikasiSettings = () => { const NotifikasiSettings = () => {
const { colorScheme } = useMantineColorScheme(); const { colorScheme } = useMantineColorScheme();
const dark = colorScheme === 'dark'; const dark = colorScheme === "dark";
return ( return (
<Card withBorder radius="md" p="xl" bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }}> <Card
<Title order={2} mb="lg">Pengaturan Notifikasi</Title> withBorder
<Text color="dimmed" mb="xl">Kelola preferensi notifikasi Anda</Text> radius="md"
p="xl"
bg={dark ? "#141D34" : "white"}
style={{ borderColor: dark ? "#141D34" : "white" }}
>
<Title order={2} mb="lg">
Pengaturan Notifikasi
</Title>
<Text color="dimmed" mb="xl">
Kelola preferensi notifikasi Anda
</Text>
<Space h="lg" /> <Space h="lg" />
<Checkbox.Group defaultValue={['email', 'push']} mb="md"> <Checkbox.Group defaultValue={["email", "push"]} mb="md">
<Title order={4} mb="sm">Metode Notifikasi</Title> <Title order={4} mb="sm">
<Group> Metode Notifikasi
<Checkbox value="email" label="Email" /> </Title>
<Checkbox value="push" label="Notifikasi Push" /> <Group>
<Checkbox value="sms" label="SMS" /> <Checkbox value="email" label="Email" />
</Group> <Checkbox value="push" label="Notifikasi Push" />
</Checkbox.Group> <Checkbox value="sms" label="SMS" />
</Group>
</Checkbox.Group>
<Space h="md" /> <Space h="md" />
<Group mb="md"> <Group mb="md">
<Switch label="Notifikasi Email" defaultChecked /> <Switch label="Notifikasi Email" defaultChecked />
<Switch label="Notifikasi Push" defaultChecked /> <Switch label="Notifikasi Push" defaultChecked />
</Group> </Group>
<Space h="md" /> <Space h="md" />
<Title order={4} mb="sm">Jenis Notifikasi</Title> <Title order={4} mb="sm">
<Group align="start"> Jenis Notifikasi
<Switch label="Pengaduan Baru" defaultChecked /> </Title>
<Switch label="Update Status Pengaduan" defaultChecked /> <Group align="start">
<Switch label="Laporan Mingguan" /> <Switch label="Pengaduan Baru" defaultChecked />
<Switch label="Pemberitahuan Keamanan" defaultChecked /> <Switch label="Update Status Pengaduan" defaultChecked />
<Switch label="Aktivitas Akun" defaultChecked /> <Switch label="Laporan Mingguan" />
</Group> <Switch label="Pemberitahuan Keamanan" defaultChecked />
<Switch label="Aktivitas Akun" defaultChecked />
</Group>
<Space h="md" /> <Space h="md" />
<Alert icon={<IconInfoCircle size={16} />} title="Tip" color="blue" mb="md"> <Alert
Anda dapat menyesuaikan frekuensi notifikasi mingguan sesuai kebutuhan Anda. icon={<IconInfoCircle size={16} />}
</Alert> title="Tip"
color="blue"
mb="md"
>
Anda dapat menyesuaikan frekuensi notifikasi mingguan sesuai kebutuhan
Anda.
</Alert>
<Group justify="flex-end" mt="xl"> <Group justify="flex-end" mt="xl">
<Button variant="outline">Batal</Button> <Button variant="outline">Batal</Button>
<Button>Simpan Preferensi</Button> <Button>Simpan Preferensi</Button>
</Group> </Group>
</Card> </Card>
); );
}; };
export default NotifikasiSettings; export default NotifikasiSettings;

View File

@@ -1,58 +1,86 @@
import { Card, Title, Text, Space, TextInput, Select, Button, Group, Switch, Alert, useMantineColorScheme } from '@mantine/core'; import {
import { IconInfoCircle } from '@tabler/icons-react'; Alert,
Button,
Card,
Group,
Select,
Space,
Switch,
Text,
TextInput,
Title,
useMantineColorScheme,
} from "@mantine/core";
import { IconInfoCircle } from "@tabler/icons-react";
const UmumSettings = () => { const UmumSettings = () => {
const { colorScheme } = useMantineColorScheme(); const { colorScheme } = useMantineColorScheme();
const dark = colorScheme === 'dark'; const dark = colorScheme === "dark";
return ( return (
<Card withBorder radius="md" p="xl" bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }}> <Card
<Title order={2} mb="lg">Pengaturan Umum</Title> withBorder
<Text color="dimmed" mb="xl">Kelola pengaturan umum aplikasi Anda</Text> radius="md"
p="xl"
bg={dark ? "#141D34" : "white"}
style={{ borderColor: dark ? "#141D34" : "white" }}
>
<Title order={2} mb="lg">
Pengaturan Umum
</Title>
<Text color="dimmed" mb="xl">
Kelola pengaturan umum aplikasi Anda
</Text>
<Space h="lg" /> <Space h="lg" />
<TextInput <TextInput
label="Nama Aplikasi" label="Nama Aplikasi"
placeholder="Masukkan nama aplikasi" placeholder="Masukkan nama aplikasi"
defaultValue="Dashboard Desa Plus" defaultValue="Dashboard Desa Plus"
mb="md" mb="md"
/> />
<Select <Select
label="Bahasa Aplikasi" label="Bahasa Aplikasi"
data={[ data={[
{ value: 'id', label: 'Indonesia' }, { value: "id", label: "Indonesia" },
{ value: 'en', label: 'English' }, { value: "en", label: "English" },
]} ]}
defaultValue="id" defaultValue="id"
mb="md" mb="md"
/> />
<Select <Select
label="Zona Waktu" label="Zona Waktu"
data={[ data={[
{ value: 'Asia/Jakarta', label: 'Asia/Jakarta (GMT+7)' }, { value: "Asia/Jakarta", label: "Asia/Jakarta (GMT+7)" },
{ value: 'Asia/Makassar', label: 'Asia/Makassar (GMT+8)' }, { value: "Asia/Makassar", label: "Asia/Makassar (GMT+8)" },
{ value: 'Asia/Jayapura', label: 'Asia/Jayapura (GMT+9)' }, { value: "Asia/Jayapura", label: "Asia/Jayapura (GMT+9)" },
]} ]}
defaultValue="Asia/Jakarta" defaultValue="Asia/Jakarta"
mb="md" mb="md"
/> />
<Group mb="md"> <Group mb="md">
<Switch label="Notifikasi Email" defaultChecked /> <Switch label="Notifikasi Email" defaultChecked />
</Group> </Group>
<Alert icon={<IconInfoCircle size={16} />} title="Informasi" color="blue" mb="md"> <Alert
Beberapa pengaturan mungkin memerlukan restart aplikasi untuk diterapkan sepenuhnya. icon={<IconInfoCircle size={16} />}
</Alert> title="Informasi"
color="blue"
mb="md"
>
Beberapa pengaturan mungkin memerlukan restart aplikasi untuk diterapkan
sepenuhnya.
</Alert>
<Group justify="flex-end" mt="xl"> <Group justify="flex-end" mt="xl">
<Button variant="outline">Batal</Button> <Button variant="outline">Batal</Button>
<Button>Simpan Perubahan</Button> <Button>Simpan Perubahan</Button>
</Group> </Group>
</Card> </Card>
); );
}; };
export default UmumSettings; export default UmumSettings;

View File

@@ -1,16 +1,16 @@
import { useNavigate, useLocation } from "@tanstack/react-router";
import { Search, ChevronDown, ChevronUp } from "lucide-react";
import { import {
Stack,
Group,
Text,
Badge, Badge,
Box,
Collapse,
Group,
Input, Input,
NavLink as MantineNavLink, NavLink as MantineNavLink,
Box, Stack,
Text,
useMantineColorScheme, useMantineColorScheme,
Collapse,
} from "@mantine/core"; } from "@mantine/core";
import { useLocation, useNavigate } from "@tanstack/react-router";
import { ChevronDown, ChevronUp, Search } from "lucide-react";
import { useState } from "react"; import { useState } from "react";
interface SidebarProps { interface SidebarProps {
@@ -21,46 +21,49 @@ export function Sidebar({ className }: SidebarProps) {
const location = useLocation(); const location = useLocation();
const navigate = useNavigate(); const navigate = useNavigate();
const { colorScheme } = useMantineColorScheme(); const { colorScheme } = useMantineColorScheme();
const dark = colorScheme === 'dark'; const dark = colorScheme === "dark";
const isActiveBg = colorScheme === 'dark' ? "#182949" : "#E6F0FF"; const isActiveBg = colorScheme === "dark" ? "#182949" : "#E6F0FF";
const isActiveBorder = colorScheme === 'dark' ? "#00398D" : "#1F41AE"; const isActiveBorder = colorScheme === "dark" ? "#00398D" : "#1F41AE";
// 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: "/dashboard/pengaduan-layanan-publik" }, { name: "Pengaduan & Layanan Publik", path: "/pengaduan-layanan-publik" },
{ name: "Jenna Analytic", path: "/dashboard/jenna-analytic" }, { name: "Jenna Analytic", path: "/jenna-analytic" },
{ name: "Demografi & Kependudukan", path: "/dashboard/demografi-pekerjaan" }, { name: "Demografi & Kependudukan", path: "/demografi-pekerjaan" },
{ name: "Keuangan & Anggaran", path: "/dashboard/keuangan-anggaran" }, { name: "Keuangan & Anggaran", path: "/keuangan-anggaran" },
{ name: "Bumdes & UMKM Desa", path: "/dashboard/bumdes" }, { name: "Bumdes & UMKM Desa", path: "/bumdes" },
{ name: "Sosial", path: "/dashboard/sosial" }, { name: "Sosial", path: "/sosial" },
{ name: "Keamanan", path: "/dashboard/keamanan" }, { name: "Keamanan", path: "/keamanan" },
{ name: "Bantuan", path: "/dashboard/bantuan" }, { name: "Bantuan", path: "/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
const isSettingsActive = settingsItems.some(item => const isSettingsActive = settingsItems.some(
location.pathname === item.path (item) => location.pathname === item.path,
); );
return ( return (
<Box className={className}> <Box className={className}>
{/* Logo */} {/* Logo */}
<Box p="md" style={{ borderBottom: "1px solid var(--mantine-color-gray-3)" }}> <Box
p="md"
style={{ borderBottom: "1px solid var(--mantine-color-gray-3)" }}
>
<Group gap="xs"> <Group gap="xs">
<Badge <Badge
color="dark" color="dark"
@@ -112,7 +115,9 @@ export function Sidebar({ className }: SidebarProps) {
style={{ style={{
background: isActive ? isActiveBg : "transparent", background: isActive ? isActiveBg : "transparent",
fontWeight: isActive ? "bold" : "normal", fontWeight: isActive ? "bold" : "normal",
borderLeft: isActive ? `4px solid ${isActiveBorder}` : "4px solid transparent", borderLeft: isActive
? `4px solid ${isActiveBorder}`
: "4px solid transparent",
borderRadius: "8px", borderRadius: "8px",
transition: "all 200ms ease", transition: "all 200ms ease",
margin: "2px 0", margin: "2px 0",
@@ -121,8 +126,8 @@ export function Sidebar({ className }: SidebarProps) {
body: { body: {
"&:hover": { "&:hover": {
background: "#F1F5F9", background: "#F1F5F9",
} },
} },
}} }}
/> />
); );
@@ -132,7 +137,9 @@ export function Sidebar({ className }: SidebarProps) {
<Box> <Box>
<MantineNavLink <MantineNavLink
onClick={() => setSettingsOpen(!settingsOpen)} onClick={() => setSettingsOpen(!settingsOpen)}
rightSection={settingsOpen ? <ChevronUp size={16} /> : <ChevronDown size={16} />} rightSection={
settingsOpen ? <ChevronUp size={16} /> : <ChevronDown size={16} />
}
label="Pengaturan" label="Pengaturan"
active={isSettingsActive} active={isSettingsActive}
variant="subtle" variant="subtle"
@@ -140,7 +147,9 @@ export function Sidebar({ className }: SidebarProps) {
style={{ style={{
background: isSettingsActive ? isActiveBg : "transparent", background: isSettingsActive ? isActiveBg : "transparent",
fontWeight: isSettingsActive ? "bold" : "normal", fontWeight: isSettingsActive ? "bold" : "normal",
borderLeft: isSettingsActive ? `4px solid ${isActiveBorder}` : "4px solid transparent", borderLeft: isSettingsActive
? `4px solid ${isActiveBorder}`
: "4px solid transparent",
borderRadius: "8px", borderRadius: "8px",
transition: "all 200ms ease", transition: "all 200ms ease",
margin: "2px 0", margin: "2px 0",
@@ -149,12 +158,16 @@ export function Sidebar({ className }: SidebarProps) {
body: { body: {
"&:hover": { "&:hover": {
background: "#F1F5F9", background: "#F1F5F9",
} },
} },
}} }}
/> />
<Collapse in={settingsOpen}> <Collapse in={settingsOpen}>
<Stack gap={0} ml="lg" style={{ overflowY: 'auto', maxHeight: '200px' }}> <Stack
gap={0}
ml="lg"
style={{ overflowY: "auto", maxHeight: "200px" }}
>
{settingsItems.map((item, index) => { {settingsItems.map((item, index) => {
const isActive = location.pathname === item.path; const isActive = location.pathname === item.path;
return ( return (
@@ -168,7 +181,9 @@ export function Sidebar({ className }: SidebarProps) {
style={{ style={{
background: isActive ? isActiveBg : "transparent", background: isActive ? isActiveBg : "transparent",
fontWeight: isActive ? "bold" : "normal", fontWeight: isActive ? "bold" : "normal",
borderLeft: isActive ? `4px solid ${isActiveBorder}` : "4px solid transparent", borderLeft: isActive
? `4px solid ${isActiveBorder}`
: "4px solid transparent",
borderRadius: "8px", borderRadius: "8px",
transition: "all 200ms ease", transition: "all 200ms ease",
margin: "2px 0", margin: "2px 0",
@@ -177,8 +192,8 @@ export function Sidebar({ className }: SidebarProps) {
body: { body: {
"&:hover": { "&:hover": {
background: "#F1F5F9", background: "#F1F5F9",
} },
} },
}} }}
/> />
); );

View File

@@ -1,289 +1,465 @@
import { useState } from "react"; import {
import { Badge,
Card, Card,
Grid, Grid,
GridCol, GridCol,
Group, Group,
Text, List,
Title, Progress,
Progress, Stack,
Stack, Text,
useMantineColorScheme, ThemeIcon,
Badge, Title,
List, useMantineColorScheme,
ThemeIcon
} from "@mantine/core"; } from "@mantine/core";
import { import {
IconHeartbeat, IconAward,
IconBabyCarriage, IconBabyCarriage,
IconStethoscope, IconBook,
IconMedicalCross, IconCalendarEvent,
IconSchool, IconHeartbeat,
IconBook, IconMedicalCross,
IconCalendarEvent, IconSchool,
IconAward IconStethoscope,
} from "@tabler/icons-react"; } from "@tabler/icons-react";
import { useState } from "react";
const SosialPage = () => { const SosialPage = () => {
const { colorScheme } = useMantineColorScheme(); const { colorScheme } = useMantineColorScheme();
const dark = colorScheme === 'dark'; const dark = colorScheme === "dark";
// Sample data for health statistics
const healthStats = {
ibuHamil: 87,
balita: 342,
alertStunting: 12,
posyanduAktif: 8,
};
// Sample data for health progress // Sample data for health statistics
const healthProgress = [ const healthStats = {
{ label: "Imunisasi Lengkap", value: 92, color: "green" }, ibuHamil: 87,
{ label: "Pemeriksaan Rutin", value: 88, color: "blue" }, balita: 342,
{ label: "Gizi Baik", value: 86, color: "teal" }, alertStunting: 12,
{ label: "Target Stunting", value: 14, color: "red" }, posyanduAktif: 8,
]; };
// Sample data for posyandu schedule // Sample data for health progress
const posyanduSchedule = [ const healthProgress = [
{ nama: "Posyandu Mawar", tanggal: "Senin, 15 Feb 2026", jam: "08:00 - 11:00" }, { label: "Imunisasi Lengkap", value: 92, color: "green" },
{ nama: "Posyandu Melati", tanggal: "Selasa, 16 Feb 2026", jam: "08:00 - 11:00" }, { label: "Pemeriksaan Rutin", value: 88, color: "blue" },
{ nama: "Posyandu Dahlia", tanggal: "Rabu, 17 Feb 2026", jam: "08:00 - 11:00" }, { label: "Gizi Baik", value: 86, color: "teal" },
{ nama: "Posyandu Anggrek", tanggal: "Kamis, 18 Feb 2026", jam: "08:00 - 11:00" }, { label: "Target Stunting", value: 14, color: "red" },
]; ];
// Sample data for education stats // Sample data for posyandu schedule
const educationStats = { const posyanduSchedule = [
siswa: { {
tk: 125, nama: "Posyandu Mawar",
sd: 480, tanggal: "Senin, 15 Feb 2026",
smp: 210, jam: "08:00 - 11:00",
sma: 150, },
}, {
sekolah: { nama: "Posyandu Melati",
jumlah: 8, tanggal: "Selasa, 16 Feb 2026",
guru: 42, 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 scholarships // Sample data for education stats
const scholarshipData = { const educationStats = {
penerima: 45, siswa: {
dana: "Rp 1.200.000.000", tk: 125,
tahunAjaran: "2025/2026", sd: 480,
}; smp: 210,
sma: 150,
},
sekolah: {
jumlah: 8,
guru: 42,
},
};
// Sample data for cultural events // Sample data for scholarships
const culturalEvents = [ const scholarshipData = {
{ nama: "Hari Kesaktian Pancasila", tanggal: "1 Oktober 2025", lokasi: "Balai Desa" }, penerima: 45,
{ nama: "Festival Budaya Desa", tanggal: "20 Mei 2026", lokasi: "Lapangan Desa" }, dana: "Rp 1.200.000.000",
{ nama: "Perayaan HUT Desa", tanggal: "17 Agustus 2026", lokasi: "Balai Desa" }, tahunAjaran: "2025/2026",
]; };
return ( // Sample data for cultural events
<Stack gap="lg"> const culturalEvents = [
{/* Health Statistics Cards */} {
<Grid gutter="md"> nama: "Hari Kesaktian Pancasila",
<GridCol span={{ base: 12, sm: 6, md: 3 }}> tanggal: "1 Oktober 2025",
<Card p="md" radius="md" withBorder bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }}> lokasi: "Balai Desa",
<Group justify="space-between" align="center"> },
<Stack gap={0}> {
<Text size="sm" c={dark ? "dark.3" : "dimmed"}> nama: "Festival Budaya Desa",
Ibu Hamil Aktif tanggal: "20 Mei 2026",
</Text> lokasi: "Lapangan Desa",
<Text size="xl" fw={700} c={dark ? "dark.0" : "black"}> },
{healthStats.ibuHamil} {
</Text> nama: "Perayaan HUT Desa",
</Stack> tanggal: "17 Agustus 2026",
<ThemeIcon variant="light" color="darmasaba-blue" size="xl" radius="xl"> lokasi: "Balai Desa",
<IconHeartbeat size={24} /> },
</ThemeIcon> ];
</Group>
</Card>
</GridCol>
<GridCol span={{ base: 12, sm: 6, md: 3 }}> return (
<Card p="md" radius="md" withBorder bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }}> <Stack gap="lg">
<Group justify="space-between" align="center"> {/* Health Statistics Cards */}
<Stack gap={0}> <Grid gutter="md">
<Text size="sm" c={dark ? "dark.3" : "dimmed"}> <GridCol span={{ base: 12, sm: 6, md: 3 }}>
Balita Terdaftar <Card
</Text> p="md"
<Text size="xl" fw={700} c={dark ? "dark.0" : "black"}> radius="md"
{healthStats.balita} withBorder
</Text> bg={dark ? "#141D34" : "white"}
</Stack> style={{ borderColor: dark ? "#141D34" : "white" }}
<ThemeIcon variant="light" color="darmasaba-success" size="xl" radius="xl"> >
<IconBabyCarriage size={24} /> <Group justify="space-between" align="center">
</ThemeIcon> <Stack gap={0}>
</Group> <Text size="sm" c={dark ? "dark.3" : "dimmed"}>
</Card> Ibu Hamil Aktif
</GridCol> </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 }}> <GridCol span={{ base: 12, sm: 6, md: 3 }}>
<Card p="md" radius="md" withBorder bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }}> <Card
<Group justify="space-between" align="center"> p="md"
<Stack gap={0}> radius="md"
<Text size="sm" c={dark ? "dark.3" : "dimmed"}> withBorder
Alert Stunting bg={dark ? "#141D34" : "white"}
</Text> style={{ borderColor: dark ? "#141D34" : "white" }}
<Text size="xl" fw={700} c="red"> >
{healthStats.alertStunting} <Group justify="space-between" align="center">
</Text> <Stack gap={0}>
</Stack> <Text size="sm" c={dark ? "dark.3" : "dimmed"}>
<ThemeIcon variant="light" color="red" size="xl" radius="xl"> Balita Terdaftar
<IconStethoscope size={24} /> </Text>
</ThemeIcon> <Text size="xl" fw={700} c={dark ? "dark.0" : "black"}>
</Group> {healthStats.balita}
</Card> </Text>
</GridCol> </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 }}> <GridCol span={{ base: 12, sm: 6, md: 3 }}>
<Card p="md" radius="md" withBorder bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }}> <Card
<Group justify="space-between" align="center"> p="md"
<Stack gap={0}> radius="md"
<Text size="sm" c={dark ? "dark.3" : "dimmed"}> withBorder
Posyandu Aktif bg={dark ? "#141D34" : "white"}
</Text> style={{ borderColor: dark ? "#141D34" : "white" }}
<Text size="xl" fw={700} c={dark ? "dark.0" : "black"}> >
{healthStats.posyanduAktif} <Group justify="space-between" align="center">
</Text> <Stack gap={0}>
</Stack> <Text size="sm" c={dark ? "dark.3" : "dimmed"}>
<ThemeIcon variant="light" color="darmasaba-warning" size="xl" radius="xl"> Alert Stunting
<IconMedicalCross size={24} /> </Text>
</ThemeIcon> <Text size="xl" fw={700} c="red">
</Group> {healthStats.alertStunting}
</Card> </Text>
</GridCol> </Stack>
</Grid> <ThemeIcon variant="light" color="red" size="xl" radius="xl">
<IconStethoscope size={24} />
</ThemeIcon>
</Group>
</Card>
</GridCol>
{/* Health Progress Bars */} <GridCol span={{ base: 12, sm: 6, md: 3 }}>
<Card p="md" radius="md" withBorder bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }}> <Card
<Title order={3} mb="md" c={dark ? "dark.0" : "black"}>Statistik Kesehatan</Title> p="md"
<Stack gap="md"> radius="md"
{healthProgress.map((item, index) => ( withBorder
<div key={index}> bg={dark ? "#141D34" : "white"}
<Group justify="space-between" mb={5}> style={{ borderColor: dark ? "#141D34" : "white" }}
<Text size="sm" fw={500} c={dark ? "dark.0" : "black"}> >
{item.label} <Group justify="space-between" align="center">
</Text> <Stack gap={0}>
<Text size="sm" fw={600} c={dark ? "dark.0" : "black"}> <Text size="sm" c={dark ? "dark.3" : "dimmed"}>
{item.value}% Posyandu Aktif
</Text> </Text>
</Group> <Text size="xl" fw={700} c={dark ? "dark.0" : "black"}>
<Progress {healthStats.posyanduAktif}
value={item.value} </Text>
size="lg" </Stack>
radius="xl" <ThemeIcon
color={item.color} variant="light"
/> color="darmasaba-warning"
</div> size="xl"
))} radius="xl"
</Stack> >
</Card> <IconMedicalCross size={24} />
</ThemeIcon>
</Group>
</Card>
</GridCol>
</Grid>
<Grid gutter="md"> {/* Health Progress Bars */}
{/* Jadwal Posyandu */} <Card
<GridCol span={{ base: 12, lg: 6 }}> p="md"
<Card p="md" radius="md" withBorder bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }}> radius="md"
<Title order={3} mb="md" c={dark ? "dark.0" : "black"}>Jadwal Posyandu</Title> withBorder
<Stack gap="sm"> bg={dark ? "#141D34" : "white"}
{posyanduSchedule.map((item, index) => ( style={{ borderColor: dark ? "#141D34" : "white" }}
<Card key={index} p="md" radius="md" withBorder bg={dark ? "#263852ff" : "#F1F5F9"} style={{ borderColor: dark ? "#263852ff" : "#F1F5F9" }} h="100%"> >
<Group justify="space-between"> <Title order={3} mb="md" c={dark ? "dark.0" : "black"}>
<Stack gap={0}> Statistik Kesehatan
<Text fw={500} c={dark ? "dark.0" : "black"}>{item.nama}</Text> </Title>
<Text size="sm" c={dark ? "dark.0" : "black"}>{item.tanggal}</Text> <Stack gap="md">
</Stack> {healthProgress.map((item, index) => (
<Badge variant="light" color="darmasaba-blue"> <div key={index}>
{item.jam} <Group justify="space-between" mb={5}>
</Badge> <Text size="sm" fw={500} c={dark ? "dark.0" : "black"}>
</Group> {item.label}
</Card> </Text>
))} <Text size="sm" fw={600} c={dark ? "dark.0" : "black"}>
</Stack> {item.value}%
</Card> </Text>
</GridCol> </Group>
<Progress
value={item.value}
size="lg"
radius="xl"
color={item.color}
/>
</div>
))}
</Stack>
</Card>
{/* Pendidikan */} <Grid gutter="md">
<GridCol span={{ base: 12, lg: 6 }}> {/* Jadwal Posyandu */}
<Card p="md" radius="md" withBorder bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }} h="100%"> <GridCol span={{ base: 12, lg: 6 }}>
<Title order={3} mb="md" c={dark ? "dark.0" : "black"}>Pendidikan</Title> <Card
<Stack gap="md"> p="md"
<Group justify="space-between"> radius="md"
<Text fw={500} c={dark ? "dark.0" : "black"}>TK / PAUD</Text> withBorder
<Text fw={700} c={dark ? "dark.0" : "black"}>{educationStats.siswa.tk}</Text> bg={dark ? "#141D34" : "white"}
</Group> style={{ borderColor: dark ? "#141D34" : "white" }}
<Group justify="space-between"> >
<Text fw={500} c={dark ? "dark.0" : "black"}>SD</Text> <Title order={3} mb="md" c={dark ? "dark.0" : "black"}>
<Text fw={700} c={dark ? "dark.0" : "black"}>{educationStats.siswa.sd}</Text> Jadwal Posyandu
</Group> </Title>
<Group justify="space-between"> <Stack gap="sm">
<Text fw={500} c={dark ? "dark.0" : "black"}>SMP</Text> {posyanduSchedule.map((item, index) => (
<Text fw={700} c={dark ? "dark.0" : "black"}>{educationStats.siswa.smp}</Text> <Card
</Group> key={index}
<Group justify="space-between"> p="md"
<Text fw={500} c={dark ? "dark.0" : "black"}>SMA</Text> radius="md"
<Text fw={700} c={dark ? "dark.0" : "black"}>{educationStats.siswa.sma}</Text> withBorder
</Group> bg={dark ? "#263852ff" : "#F1F5F9"}
style={{ borderColor: dark ? "#263852ff" : "#F1F5F9" }}
<Card withBorder radius="md" p="md" mt="md" bg={dark ? "#263852ff" : "#F1F5F9"} style={{ borderColor: dark ? "#263852ff" : "#F1F5F9" }}> h="100%"
<Group justify="space-between"> >
<Text fw={500} c={dark ? "dark.0" : "black"}>Jumlah Lembaga Pendidikan</Text> <Group justify="space-between">
<Text fw={700} c={dark ? "dark.0" : "black"}>{educationStats.sekolah.jumlah}</Text> <Stack gap={0}>
</Group> <Text fw={500} c={dark ? "dark.0" : "black"}>
<Group justify="space-between" mt="sm"> {item.nama}
<Text fw={500} c={dark ? "dark.0" : "black"}>Jumlah Tenaga Pengajar</Text> </Text>
<Text fw={700} c={dark ? "dark.0" : "black"}>{educationStats.sekolah.guru}</Text> <Text size="sm" c={dark ? "dark.0" : "black"}>
</Group> {item.tanggal}
</Card> </Text>
</Stack> </Stack>
</Card> <Badge variant="light" color="darmasaba-blue">
</GridCol> {item.jam}
</Grid> </Badge>
</Group>
</Card>
))}
</Stack>
</Card>
</GridCol>
<Grid gutter="md"> {/* Pendidikan */}
{/* Beasiswa Desa */} <GridCol span={{ base: 12, lg: 6 }}>
<GridCol span={{ base: 12, lg: 6 }}> <Card
<Card p="md" radius="md" withBorder bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }} h="100%"> p="md"
<Group justify="space-between" align="center"> radius="md"
<Stack gap={0}> withBorder
<Text size="sm" c={dark ? "dark.3" : "dimmed"}>Beasiswa Desa</Text> bg={dark ? "#141D34" : "white"}
<Text size="xl" fw={700} c={dark ? "dark.0" : "black"}>Penerima: {scholarshipData.penerima}</Text> style={{ borderColor: dark ? "#141D34" : "white" }}
</Stack> h="100%"
<ThemeIcon variant="light" color="darmasaba-success" size="xl" radius="xl"> >
<IconAward size={24} /> <Title order={3} mb="md" c={dark ? "dark.0" : "black"}>
</ThemeIcon> Pendidikan
</Group> </Title>
<Text mt="md" c={dark ? "dark.0" : "black"}>Dana Tersalurkan: <Text span fw={700}>{scholarshipData.dana}</Text></Text> <Stack gap="md">
<Text mt="sm" c={dark ? "dark.3" : "dimmed"}>Tahun Ajaran: {scholarshipData.tahunAjaran}</Text> <Group justify="space-between">
</Card> <Text fw={500} c={dark ? "dark.0" : "black"}>
</GridCol> 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>
{/* Kalender Event Budaya */} <Card
<GridCol span={{ base: 12, lg: 6 }}> withBorder
<Card p="md" radius="md" withBorder bg={dark ? "#141D34" : "white"} style={{ borderColor: dark ? "#141D34" : "white" }} h="100%"> radius="md"
<Title order={3} mb="md" c={dark ? "dark.0" : "black"}>Kalender Event Budaya</Title> p="md"
<List spacing="sm"> mt="md"
{culturalEvents.map((event, index) => ( bg={dark ? "#263852ff" : "#F1F5F9"}
<List.Item key={index} icon={ style={{ borderColor: dark ? "#263852ff" : "#F1F5F9" }}
<ThemeIcon color="darmasaba-blue" size={24} radius="xl"> >
<IconCalendarEvent size={12} /> <Group justify="space-between">
</ThemeIcon> <Text fw={500} c={dark ? "dark.0" : "black"}>
}> Jumlah Lembaga Pendidikan
<Group justify="space-between"> </Text>
<Text fw={500} c={dark ? "dark.0" : "black"}>{event.nama}</Text> <Text fw={700} c={dark ? "dark.0" : "black"}>
<Text size="sm" c={dark ? "dark.3" : "dimmed"}>{event.lokasi}</Text> {educationStats.sekolah.jumlah}
</Group> </Text>
<Text size="sm" c={dark ? "dark.3" : "dimmed"}>{event.tanggal}</Text> </Group>
</List.Item> <Group justify="space-between" mt="sm">
))} <Text fw={500} c={dark ? "dark.0" : "black"}>
</List> Jumlah Tenaga Pengajar
</Card> </Text>
</GridCol> <Text fw={700} c={dark ? "dark.0" : "black"}>
</Grid> {educationStats.sekolah.guru}
</Stack> </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} />
</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
p="md"
radius="md"
withBorder
bg={dark ? "#141D34" : "white"}
style={{ borderColor: dark ? "#141D34" : "white" }}
h="100%"
>
<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; export default SosialPage;

View File

@@ -2,7 +2,6 @@ import {
Box, Box,
Card as MantineCard, Card as MantineCard,
type CardProps as MantineCardProps, type CardProps as MantineCardProps,
Title, Title,
} from "@mantine/core"; } from "@mantine/core";
import type React from "react"; import type React from "react";

View File

@@ -1,90 +1,86 @@
import { Card, useMantineTheme, useComputedColorScheme } from '@mantine/core'; import type { CardProps } from "@mantine/core";
import type { CardProps } from '@mantine/core'; import { Card, useComputedColorScheme, useMantineTheme } from "@mantine/core";
import type { ReactNode } from 'react'; import type { ReactNode } from "react";
interface HelpCardProps extends CardProps { interface HelpCardProps extends CardProps {
children: ReactNode; children: ReactNode;
icon?: ReactNode; icon?: ReactNode;
title?: string; title?: string;
minHeight?: string | number; // Allow specifying a minimum height minHeight?: string | number; // Allow specifying a minimum height
} }
export const HelpCard = ({ export const HelpCard = ({
children, children,
icon, icon,
title, title,
minHeight = 'auto', // Default to auto, but allow override minHeight = "auto", // Default to auto, but allow override
...props ...props
}: HelpCardProps) => { }: HelpCardProps) => {
const theme = useMantineTheme(); const theme = useMantineTheme();
const colorScheme = useComputedColorScheme('light'); const colorScheme = useComputedColorScheme("light");
const isDark = colorScheme === 'dark'; const isDark = colorScheme === "dark";
return ( return (
<Card <Card
shadow="sm" shadow="sm"
padding="xl" padding="xl"
radius="md" radius="md"
withBorder withBorder
style={{ style={{
backgroundColor: isDark ? theme.colors.dark[7] : theme.white, backgroundColor: isDark ? theme.colors.dark[7] : theme.white,
borderRadius: '16px', borderRadius: "16px",
transition: 'transform 0.2s ease, box-shadow 0.2s ease', transition: "transform 0.2s ease, box-shadow 0.2s ease",
border: `1px solid ${ border: `1px solid ${
isDark ? theme.colors.dark[4] : theme.colors.gray[3] isDark ? theme.colors.dark[4] : theme.colors.gray[3]
}`, }`,
minHeight, // Apply the minimum height minHeight, // Apply the minimum height
display: 'flex', display: "flex",
flexDirection: 'column', flexDirection: "column",
}} }}
{...props} {...props}
> >
{(icon || title) && ( {(icon || title) && (
<div <div
style={{ style={{
display: 'flex', display: "flex",
alignItems: 'center', alignItems: "center",
gap: '12px', gap: "12px",
marginBottom: '16px', marginBottom: "16px",
}} }}
> >
{icon && ( {icon && (
<div <div
style={{ style={{
backgroundColor: isDark backgroundColor: isDark
? theme.colors.blue[8] ? theme.colors.blue[8]
: theme.colors.blue[0], : theme.colors.blue[0],
borderRadius: '8px', borderRadius: "8px",
padding: '8px', padding: "8px",
display: 'flex', display: "flex",
alignItems: 'center', alignItems: "center",
justifyContent: 'center', justifyContent: "center",
}} }}
> >
{icon} {icon}
</div> </div>
)} )}
{title && ( {title && (
<h3 <h3
style={{ style={{
margin: 0, margin: 0,
fontSize: '16px', fontSize: "16px",
fontWeight: 600, fontWeight: 600,
color: isDark color: isDark ? theme.colors.dark[0] : theme.colors.dark[9],
? theme.colors.dark[0] }}
: theme.colors.dark[9], >
}} {title}
> </h3>
{title} )}
</h3> </div>
)} )}
</div>
)}
<div style={{ flex: 1 }}> <div style={{ flex: 1 }}>{children}</div>
{children} </Card>
</div> );
</Card>
);
}; };

View File

@@ -14,10 +14,9 @@ import { Inspector } from "react-dev-inspector";
import { createRoot } from "react-dom/client"; import { createRoot } from "react-dom/client";
import { routeTree } from "./routeTree.gen"; import { routeTree } from "./routeTree.gen";
import "./index.css"; import "./index.css";
import '@mantine/charts/styles.css'; import "@mantine/charts/styles.css";
import { IS_DEV, VITE_PUBLIC_URL } from "./utils/env"; import { IS_DEV, VITE_PUBLIC_URL } from "./utils/env";
// Create a new router instance // Create a new router instance
export const router = createRouter({ export const router = createRouter({
routeTree, routeTree,
@@ -102,8 +101,6 @@ const theme = createTheme({
primaryColor: "darmasaba-blue", primaryColor: "darmasaba-blue",
}); });
const InspectorWrapper = IS_DEV const InspectorWrapper = IS_DEV
? Inspector ? Inspector
: ({ children }: { children: React.ReactNode }) => <>{children}</>; : ({ children }: { children: React.ReactNode }) => <>{children}</>;

View File

@@ -1 +1 @@
@import "tailwindcss"; @import "tailwindcss";

View File

@@ -60,16 +60,39 @@ 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 === "/dashboard" || p.startsWith("/dashboard/"), 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: "/profile", 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);
} }

View File

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

View File

@@ -7,8 +7,20 @@ import { createRootRoute, Outlet } from "@tanstack/react-router";
export const Route = createRootRoute({ export const Route = createRootRoute({
component: RootComponent, component: RootComponent,
beforeLoad: protectedRouteMiddleware, beforeLoad: async ({ location }) => {
onEnter({ context }) { // Only apply auth middleware for routes that need it
// Public routes: /, /signin, /signup
const isPublicRoute =
location.pathname === "/" ||
location.pathname === "/signin" ||
location.pathname === "/signup";
if (isPublicRoute) {
return;
}
// Apply protected route middleware for all other routes
const context = await protectedRouteMiddleware({ location });
authStore.user = context?.user as any; authStore.user = context?.user as any;
authStore.session = context?.session as any; authStore.session = context?.session as any;
}, },

View File

@@ -154,7 +154,6 @@ function DashboardLayout() {
</Group> </Group>
<Group gap="md"> <Group gap="md">
<Menu <Menu
shadow="md" shadow="md"
width={200} width={200}

51
src/routes/bantuan.tsx Normal file
View 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
View 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>;
}

View File

@@ -1,7 +0,0 @@
import { createFileRoute } from '@tanstack/react-router'
import HelpPage from '@/components/help-page'
export const Route = createFileRoute('/dashboard/bantuan')({
component: HelpPage,
})

View File

@@ -1,7 +0,0 @@
import BumdesPage from '@/components/bumdes-page'
import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/dashboard/bumdes')({
component: BumdesPage,
})

View File

@@ -1,7 +0,0 @@
import { createFileRoute } from '@tanstack/react-router'
import DemografiPekerjaan from '../../components/demografi-pekerjaan'
export const Route = createFileRoute('/dashboard/demografi-pekerjaan')({
component: DemografiPekerjaan,
})

View File

@@ -1,5 +0,0 @@
import { createFileRoute } from "@tanstack/react-router";
import { DashboardContent } from "@/components/dashboard-content";
export const Route = createFileRoute("/dashboard/")({
component: DashboardContent,
});

View File

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

View File

@@ -1,7 +0,0 @@
import { createFileRoute } from '@tanstack/react-router'
import KeamananPage from '@/components/keamanan-page'
export const Route = createFileRoute('/dashboard/keamanan')({
component: KeamananPage,
})

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,7 +0,0 @@
import { createFileRoute } from '@tanstack/react-router'
import KeamananSettings from '@/components/pengaturan/keamanan'
export const Route = createFileRoute('/dashboard/pengaturan/keamanan')({
component: KeamananSettings,
})

View File

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

View File

@@ -1,9 +0,0 @@
import { createFileRoute, Outlet } from '@tanstack/react-router';
export const Route = createFileRoute('/dashboard/pengaturan')({
component: () => (
<div className="p-2">
<Outlet />
</div>
),
});

View File

@@ -1,7 +0,0 @@
import UmumSettings from '@/components/pengaturan/umum'
import { createFileRoute } from '@tanstack/react-router'
export const Route = createFileRoute('/dashboard/pengaturan/umum')({
component: UmumSettings,
})

View File

@@ -1,8 +0,0 @@
import { createFileRoute } from '@tanstack/react-router'
import SocialPage from '@/components/sosial-page'
export const Route = createFileRoute('/dashboard/sosial')({
component: SocialPage,
})

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

View File

@@ -1,788 +1,51 @@
import { import { AppShell, Burger, Group, useMantineColorScheme } from "@mantine/core";
ActionIcon, import { useDisclosure } from "@mantine/hooks";
Avatar, import { createFileRoute } from "@tanstack/react-router";
Box, import { DashboardContent } from "@/components/dashboard-content";
Button, import { Header } from "@/components/header";
Card, import { Sidebar } from "@/components/sidebar";
Container,
Grid,
Group,
Image,
Paper,
rem,
SimpleGrid,
Stack,
Text,
ThemeIcon,
Title,
Transition,
useMantineColorScheme,
} from "@mantine/core";
import {
IconApi,
IconBolt,
IconBrandGithub,
IconBrandLinkedin,
IconBrandTwitter,
IconChevronRight,
IconLock,
IconMoon,
IconRocket,
IconShield,
IconStack2,
IconSun,
} from "@tabler/icons-react";
import { createFileRoute, Link } from "@tanstack/react-router";
import { useEffect, useState } from "react";
export const Route = createFileRoute("/")({ export const Route = createFileRoute("/")({
component: HomePage, component: DashboardPage,
}); });
// Navigation items function DashboardPage() {
const NAV_ITEMS = [ const [opened, { toggle }] = useDisclosure();
{ label: "Home", link: "/" }, const { colorScheme } = useMantineColorScheme();
{ label: "Features", link: "#features" }, const headerBgColor = colorScheme === "dark" ? "#11192D" : "#19355E";
{ label: "Testimonials", link: "#testimonials" }, const navbarBgColor = colorScheme === "dark" ? "#11192D" : "white";
{ label: "Pricing", link: "/pricing" }, const mainBgColor = colorScheme === "dark" ? "#11192D" : "#edf3f8ff";
{ label: "Contact", link: "/contact" },
];
// Features data
const FEATURES = [
{
icon: IconBolt,
title: "Lightning Fast",
description: "Built on Bun runtime for exceptional performance and speed.",
},
{
icon: IconShield,
title: "Secure by Design",
description:
"Enterprise-grade authentication with Better Auth integration.",
},
{
icon: IconApi,
title: "RESTful API",
description:
"Full-featured API with Elysia.js for seamless backend operations.",
},
{
icon: IconStack2,
title: "Modern Stack",
description: "React 19, TanStack Router, and Mantine UI for the best DX.",
},
{
icon: IconLock,
title: "API Key Auth",
description: "Secure API key management for external integrations.",
},
{
icon: IconRocket,
title: "Production Ready",
description: "Type-safe, tested, and optimized for production deployment.",
},
];
// Testimonials data
const TESTIMONIALS = [
{
id: "testimonial-1",
name: "Alex Johnson",
role: "Lead Developer",
content:
"This template saved us weeks of setup time. The architecture is clean and well-thought-out.",
avatar:
"https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=200&q=80",
},
{
id: "testimonial-2",
name: "Sarah Williams",
role: "CTO",
content:
"The performance improvements we saw after switching to this stack were remarkable. Highly recommended!",
avatar:
"https://images.unsplash.com/photo-1494790108377-be9c29b29330?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=200&q=80",
},
{
id: "testimonial-3",
name: "Michael Chen",
role: "Product Manager",
content:
"The developer experience is top-notch. Everything is well-documented and easy to extend.",
avatar:
"https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=200&q=80",
},
];
function NavigationBar() {
const { colorScheme, toggleColorScheme } = useMantineColorScheme();
const [scrolled, setScrolled] = useState(false);
useEffect(() => {
const handleScroll = () => {
setScrolled(window.scrollY > 20);
};
window.addEventListener("scroll", handleScroll);
return () => window.removeEventListener("scroll", handleScroll);
}, []);
return ( return (
<Box <AppShell
h={70} header={{ height: 60 }}
px="md" navbar={{
style={{ width: 300,
borderBottom: "1px solid var(--mantine-color-gray-2)", breakpoint: "sm",
transition: "all 0.3s ease", collapsed: { mobile: !opened },
boxShadow: scrolled ? "0 2px 10px rgba(0,0,0,0.1)" : "none",
display: "flex",
alignItems: "center",
justifyContent: "space-between",
}} }}
padding="md"
> >
<Group h="100%" justify="space-between"> <AppShell.Header bg={headerBgColor}>
<Group> <Group h="100%" px="md">
<Link to="/" style={{ textDecoration: "none" }}> <Burger opened={opened} onClick={toggle} hiddenFrom="sm" size="sm" />
<Title order={3} c="blue"> <Header />
BunStack
</Title>
</Link>
<Group ml={50} visibleFrom="sm" gap="lg">
{NAV_ITEMS.map((item) => {
const isActive = window.location.pathname === item.link;
return (
<Box
key={item.label}
component={Link}
to={item.link}
style={{
textDecoration: "none",
fontSize: rem(16),
padding: `${rem(8)} ${rem(12)}`,
borderRadius: rem(6),
transition: "all 0.2s ease",
color: isActive
? "var(--mantine-color-blue-6)"
: "var(--mantine-color-dimmed)",
fontWeight: 500,
cursor: "pointer",
display: "block",
}}
className="nav-item"
>
{item.label}
</Box>
);
})}
</Group>
</Group> </Group>
</AppShell.Header>
<Group> <AppShell.Navbar
<ActionIcon p="md"
variant="default" bg={navbarBgColor}
onClick={() => toggleColorScheme()} style={{ display: "flex", flexDirection: "column" }}
size="lg"
>
{colorScheme === "dark" ? (
<IconSun size={18} />
) : (
<IconMoon size={18} />
)}
</ActionIcon>
<Button component={Link} to="/signin" variant="light" size="sm">
Sign In
</Button>
<Button component={Link} to="/signup" size="sm">
Get Started
</Button>
</Group>
</Group>
</Box>
);
}
function HeroSection() {
const [loaded, setLoaded] = useState(false);
const [imageLoaded, setImageLoaded] = useState(false);
useEffect(() => {
setLoaded(true);
}, []);
// Simulate delay for image transition
useEffect(() => {
const timer = setTimeout(() => {
setImageLoaded(true);
}, 200);
return () => clearTimeout(timer);
}, []);
return (
<Box
pt={rem(140)} // Adjusted padding for simpler header
pb={rem(60)}
>
<Container size="lg">
<Grid gutter={{ base: rem(40), md: rem(80) }} align="center">
<Grid.Col span={{ base: 12, md: 6 }}>
<Transition
mounted={loaded}
transition="slide-up"
duration={600}
timingFunction="ease"
>
{(styles) => (
<Stack gap="xl" style={styles}>
<Title
order={1}
style={{
fontSize: rem(48),
fontWeight: 900,
lineHeight: 1.2,
}}
>
Build Faster with{" "}
<Text span c="blue" inherit>
Bun Stack
</Text>
</Title>
<Text size="xl" c="dimmed">
A modern, full-stack React template powered by Bun,
Elysia.js, and TanStack Router. Ship your ideas faster than
ever.
</Text>
<Group gap="md">
<Button
component={Link}
to="/admin"
size="lg"
variant="filled"
rightSection={<IconRocket size="1.25rem" />}
>
Get Started
</Button>
<Button
component={Link}
to="/docs"
size="lg"
variant="outline"
>
Learn More
</Button>
</Group>
</Stack>
)}
</Transition>
</Grid.Col>
<Grid.Col span={{ base: 12, md: 6 }}>
<Transition
mounted={imageLoaded}
transition="slide-left"
duration={800}
timingFunction="ease"
>
{(styles) => (
<Paper shadow="xl" radius="lg" p="md" withBorder style={styles}>
<Image
src="https://images.unsplash.com/photo-1555066931-4365d14bab8c?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1170&q=80"
alt="Code editor showing Bun Stack code"
radius="md"
/>
</Paper>
)}
</Transition>
</Grid.Col>
</Grid>
</Container>
</Box>
);
}
function AnimatedFeatureCard({
feature,
index,
isVisible,
}: {
feature: (typeof FEATURES)[number];
index: number;
isVisible: boolean;
}) {
const [isDelayedVisible, setIsDelayedVisible] = useState(isVisible);
useEffect(() => {
if (isVisible) {
const timer = setTimeout(() => {
setIsDelayedVisible(true);
}, index * 100);
return () => clearTimeout(timer);
}
}, [isVisible, index]);
return (
<Transition
mounted={isDelayedVisible}
transition="slide-up"
duration={500}
timingFunction="ease"
>
{(styles) => (
<Card
className="feature-card"
padding="lg"
radius="md"
withBorder
shadow="sm"
style={styles}
>
<ThemeIcon variant="light" color="blue" size={60} radius="md">
<feature.icon size="1.75rem" />
</ThemeIcon>
<Stack gap={8} mt="md">
<Title order={4}>{feature.title}</Title>
<Text size="sm" c="dimmed" lh={1.5}>
{feature.description}
</Text>
</Stack>
</Card>
)}
</Transition>
);
}
function FeaturesSection() {
const [visibleFeatures, setVisibleFeatures] = useState(
Array(FEATURES.length).fill(false),
);
useEffect(() => {
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry, index) => {
if (entry.isIntersecting) {
setVisibleFeatures((prev) => {
const newVisible = [...prev];
newVisible[index] = true;
return newVisible;
});
}
});
},
{ threshold: 0.1 },
);
const elements = document.querySelectorAll(".feature-card");
elements.forEach((el) => {
observer.observe(el);
});
return () => observer.disconnect();
}, []);
return (
<Container size="lg" py={rem(80)}>
<Stack gap="xl" align="center" mb={rem(50)}>
<Transition
mounted={true}
transition="fade"
duration={600}
timingFunction="ease"
>
{(styles) => (
<div style={styles}>
<Title order={2} ta="center">
Everything You Need
</Title>
<Text c="dimmed" size="lg" ta="center" maw={600}>
A complete toolkit for building modern web applications with
best practices built-in.
</Text>
</div>
)}
</Transition>
</Stack>
<SimpleGrid cols={{ base: 1, sm: 2, lg: 3 }} spacing="lg">
{FEATURES.map((feature, index) => (
<AnimatedFeatureCard
key={feature.title}
feature={feature}
index={index}
isVisible={visibleFeatures[index]}
/>
))}
</SimpleGrid>
</Container>
);
}
function AnimatedTestimonialCard({
testimonial,
index,
isVisible,
}: {
testimonial: (typeof TESTIMONIALS)[number];
index: number;
isVisible: boolean;
}) {
const [isDelayedVisible, setIsDelayedVisible] = useState(isVisible);
useEffect(() => {
if (isVisible) {
const timer = setTimeout(() => {
setIsDelayedVisible(true);
}, index * 150);
return () => clearTimeout(timer);
}
}, [isVisible, index]);
return (
<Transition
mounted={isDelayedVisible}
transition="slide-up"
duration={500}
timingFunction="ease"
>
{(styles) => (
<Card
padding="lg"
radius="md"
withBorder
shadow="sm"
className="testimonial-card"
style={styles}
>
<Text c="dimmed" mb="md">
"{testimonial.content}"
</Text>
<Group>
<Avatar src={testimonial.avatar} size="md" radius="xl" />
<Stack gap={0}>
<Text fw={600}>{testimonial.name}</Text>
<Text size="sm" c="dimmed">
{testimonial.role}
</Text>
</Stack>
</Group>
</Card>
)}
</Transition>
);
}
function TestimonialsSection() {
const [visibleTestimonials, setVisibleTestimonials] = useState(
Array(TESTIMONIALS.length).fill(false),
);
useEffect(() => {
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry, index) => {
if (entry.isIntersecting) {
setVisibleTestimonials((prev) => {
const newVisible = [...prev];
newVisible[index] = true;
return newVisible;
});
}
});
},
{ threshold: 0.1 },
);
const elements = document.querySelectorAll(".testimonial-card");
elements.forEach((el) => {
observer.observe(el);
});
return () => observer.disconnect();
}, []);
return (
<Box py={rem(80)}>
<Container size="lg">
<Stack gap="xl" align="center" mb={rem(50)}>
<Transition
mounted={true}
transition="fade"
duration={600}
timingFunction="ease"
>
{(styles) => (
<div style={styles}>
<Title order={2} ta="center">
Loved by Developers
</Title>
<Text c="dimmed" size="lg" ta="center" maw={600}>
Join thousands of satisfied developers who have accelerated
their projects with Bun Stack.
</Text>
</div>
)}
</Transition>
</Stack>
<SimpleGrid cols={{ base: 1, sm: 2, lg: 3 }} spacing="lg">
{TESTIMONIALS.map((testimonial, index) => (
<AnimatedTestimonialCard
key={testimonial.id}
testimonial={testimonial}
index={index}
isVisible={visibleTestimonials[index]}
/>
))}
</SimpleGrid>
</Container>
</Box>
);
}
function CtaSection() {
const [loaded, setLoaded] = useState(false);
useEffect(() => {
setLoaded(true);
}, []);
return (
<Container size="lg" py={rem(80)}>
<Transition
mounted={loaded}
transition="slide-up"
duration={600}
timingFunction="ease"
> >
{(styles) => ( <div style={{ flex: 1, overflowY: "auto" }}>
<Paper <Sidebar />
radius="lg" </div>
p={rem(60)} </AppShell.Navbar>
bg="blue"
style={{ <AppShell.Main bg={mainBgColor}>
...styles, <DashboardContent />
background: </AppShell.Main>
"linear-gradient(135deg, var(--mantine-color-blue-6), var(--mantine-color-indigo-6))", </AppShell>
}}
>
<Stack align="center" gap="xl" ta="center">
<Title c="white" order={2}>
Ready to get started?
</Title>
<Text c="white" size="lg" maw={600}>
Join thousands of developers who are building faster and more
reliable applications with Bun Stack.
</Text>
<Group>
<Button
component={Link}
to="/signup"
size="lg"
variant="white"
color="dark"
rightSection={<IconChevronRight size="1.125rem" />}
>
Create Account
</Button>
<Button
component={Link}
to="/docs"
size="lg"
variant="outline"
color="white"
>
View Documentation
</Button>
</Group>
</Stack>
</Paper>
)}
</Transition>
</Container>
);
}
function Footer() {
const [loaded, setLoaded] = useState(false);
useEffect(() => {
const timer = setTimeout(() => {
setLoaded(true);
}, 300);
return () => clearTimeout(timer);
}, []);
return (
<Transition
mounted={loaded}
transition="slide-up"
duration={600}
timingFunction="ease"
>
{(styles) => (
<Box
py={rem(40)}
style={{
...styles,
borderTop: "1px solid var(--mantine-color-gray-2)",
}}
>
<Container size="lg">
<Grid gutter={{ base: rem(40), md: rem(80) }}>
<Grid.Col span={{ base: 12, md: 4 }}>
<Stack gap="md">
<Title order={3}>BunStack</Title>
<Text size="sm" c="dimmed">
The ultimate full-stack solution for modern web
applications.
</Text>
<Group>
<ActionIcon size="lg" variant="subtle" color="gray">
<IconBrandGithub size="1.25rem" />
</ActionIcon>
<ActionIcon size="lg" variant="subtle" color="gray">
<IconBrandTwitter size="1.25rem" />
</ActionIcon>
<ActionIcon size="lg" variant="subtle" color="gray">
<IconBrandLinkedin size="1.25rem" />
</ActionIcon>
</Group>
</Stack>
</Grid.Col>
<Grid.Col span={{ base: 12, md: 2 }}>
<Stack gap="xs">
<Title order={4}>Product</Title>
<Text
size="sm"
c="dimmed"
component={Link}
to="/features"
td="none"
>
Features
</Text>
<Text
size="sm"
c="dimmed"
component={Link}
to="/pricing"
td="none"
>
Pricing
</Text>
<Text
size="sm"
c="dimmed"
component={Link}
to="/docs"
td="none"
>
Documentation
</Text>
</Stack>
</Grid.Col>
<Grid.Col span={{ base: 12, md: 2 }}>
<Stack gap="xs">
<Title order={4}>Company</Title>
<Text
size="sm"
c="dimmed"
component={Link}
to="/about"
td="none"
>
About
</Text>
<Text
size="sm"
c="dimmed"
component={Link}
to="/blog"
td="none"
>
Blog
</Text>
<Text
size="sm"
c="dimmed"
component={Link}
to="/careers"
td="none"
>
Careers
</Text>
</Stack>
</Grid.Col>
<Grid.Col span={{ base: 12, md: 4 }}>
<Stack gap="xs">
<Title order={4}>Subscribe to our newsletter</Title>
<Text size="sm" c="dimmed">
Get the latest news and updates
</Text>
<Group>
<input
type="email"
placeholder="Your email"
style={{
padding: "8px 12px",
borderRadius: "4px",
border: "1px solid var(--mantine-color-gray-3)",
flex: 1,
}}
/>
<Button>Subscribe</Button>
</Group>
</Stack>
</Grid.Col>
</Grid>
<Box
pt={rem(40)}
style={{ borderTop: "1px solid var(--mantine-color-gray-2)" }}
>
<Group justify="space-between" align="center">
<Text size="sm" c="dimmed">
© 2024 Bun Stack. Built with Bun, Elysia, and React.
</Text>
<Group gap="lg">
<Text
component={Link}
to="/privacy"
size="sm"
c="dimmed"
style={{ textDecoration: "none" }}
>
Privacy Policy
</Text>
<Text
component={Link}
to="/terms"
size="sm"
c="dimmed"
style={{ textDecoration: "none" }}
>
Terms of Service
</Text>
</Group>
</Group>
</Box>
</Container>
</Box>
)}
</Transition>
);
}
function HomePage() {
return (
<Box>
<NavigationBar />
<HeroSection />
<FeaturesSection />
<TestimonialsSection />
<CtaSection />
<Footer />
</Box>
); );
} }

View 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
View 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>;
}

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

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

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

View 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>;
}

View 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>;
}

View 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>;
}

View File

@@ -1,19 +1,20 @@
import { AppShell, Burger, Group, useMantineColorScheme } from "@mantine/core";
import { useDisclosure } from "@mantine/hooks";
import { createFileRoute, Outlet } from "@tanstack/react-router"; import { createFileRoute, Outlet } from "@tanstack/react-router";
import { Header } from "@/components/header"; import { Header } from "@/components/header";
import { Sidebar } from "@/components/sidebar"; import { Sidebar } from "@/components/sidebar";
import { AppShell, Burger, Group, useMantineColorScheme } from "@mantine/core";
import { useDisclosure } from "@mantine/hooks";
export const Route = createFileRoute("/dashboard")({ export const Route = createFileRoute("/pengaturan")({
component: RouteComponent, component: PengaturanLayout,
}); });
function RouteComponent() { function PengaturanLayout() {
const [opened, { toggle }] = useDisclosure(); const [opened, { toggle }] = useDisclosure();
const { colorScheme } = useMantineColorScheme(); const { colorScheme } = useMantineColorScheme();
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";
return ( return (
<AppShell <AppShell
header={{ height: 60 }} header={{ height: 60 }}
@@ -31,14 +32,20 @@ function RouteComponent() {
</Group> </Group>
</AppShell.Header> </AppShell.Header>
<AppShell.Navbar p="md" bg={navbarBgColor} style={{ display: 'flex', flexDirection: 'column' }}> <AppShell.Navbar
<div style={{ flex: 1, overflowY: 'auto' }}> p="md"
bg={navbarBgColor}
style={{ display: "flex", flexDirection: "column" }}
>
<div style={{ flex: 1, overflowY: "auto" }}>
<Sidebar /> <Sidebar />
</div> </div>
</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>
); );

View File

@@ -0,0 +1,6 @@
import { createFileRoute } from "@tanstack/react-router";
import UmumSettings from "@/components/pengaturan/umum";
export const Route = createFileRoute("/pengaturan/umum")({
component: UmumSettings,
});

9
src/routes/sosial.tsx Normal file
View 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>;
}