Compare commits

...

4 Commits

Author SHA1 Message Date
7c8012d277 Fix Lint-1 2026-03-25 15:07:10 +08:00
687ce11a81 Refactor New Ui Semua Pengaturan 2026-03-25 14:38:37 +08:00
1ba4643e23 Refactor New Ui Pengaturan Umum 2026-03-25 11:56:22 +08:00
113dd7ba6f Refactor New Ui Sosial, Keamanan, & Bantuan 2026-03-25 11:10:50 +08:00
22 changed files with 1094 additions and 955 deletions

View File

@@ -25,7 +25,7 @@ export function StatCard({
trend,
trendValue,
icon,
iconColor = "darmasaba-blue",
iconColor = "#1E3A5F",
}: StatCardProps) {
const { colorScheme } = useMantineColorScheme();
const dark = colorScheme === "dark";
@@ -77,6 +77,7 @@ export function StatCard({
size="xl"
radius="xl"
color={dark ? "gray" : iconColor}
bg={dark ? "gray" : iconColor}
>
{icon}
</ThemeIcon>

View File

@@ -181,7 +181,7 @@ const HelpPage = () => {
<HelpCard
style={{ borderColor: dark ? "#141D34" : "white" }}
bg={dark ? "#141D34" : "white"}
icon={<IconBook size={24} />}
icon={<IconBook size={24} color="white" />}
title="Panduan Memulai"
h="100%"
>
@@ -211,7 +211,7 @@ const HelpPage = () => {
<HelpCard
style={{ borderColor: dark ? "#141D34" : "white" }}
bg={dark ? "#141D34" : "white"}
icon={<IconVideo size={24} />}
icon={<IconVideo size={24} color="white" />}
title="Video Tutorial"
h="100%"
>
@@ -241,7 +241,7 @@ const HelpPage = () => {
<HelpCard
style={{ borderColor: dark ? "#141D34" : "white" }}
bg={dark ? "#141D34" : "white"}
icon={<IconHelpCircle size={24} />}
icon={<IconHelpCircle size={24} color="white" />}
title="FAQ"
h="100%"
>
@@ -273,7 +273,7 @@ const HelpPage = () => {
<HelpCard
style={{ borderColor: dark ? "#141D34" : "white" }}
bg={dark ? "#141D34" : "white"}
icon={<IconHeadphones size={24} />}
icon={<IconHeadphones size={24} color="white" />}
title="Hubungi Support"
h="100%"
>
@@ -308,7 +308,7 @@ const HelpPage = () => {
<HelpCard
style={{ borderColor: dark ? "#141D34" : "white" }}
bg={dark ? "#141D34" : "white"}
icon={<IconFileText size={24} />}
icon={<IconFileText size={24} color="white" />}
title="Dokumentasi"
h="100%"
>
@@ -340,7 +340,7 @@ const HelpPage = () => {
<HelpCard
style={{ borderColor: dark ? "#141D34" : "white" }}
bg={dark ? "#141D34" : "white"}
icon={<IconMessage size={24} />}
icon={<IconMessage size={24} color="white" />}
title="Jenna - Virtual Assistant"
h="100%"
>

View File

@@ -5,22 +5,18 @@ import {
Grid,
GridCol,
Group,
List,
Stack,
Text,
ThemeIcon,
Title,
useMantineColorScheme,
useMantineColorScheme
} from "@mantine/core";
import {
IconAlertTriangle,
IconCamera,
IconClock,
IconEye,
IconMapPin,
IconShieldLock,
IconMapPin
} from "@tabler/icons-react";
import { useState } from "react";
const KeamananPage = () => {
const { colorScheme } = useMantineColorScheme();
@@ -125,10 +121,53 @@ const KeamananPage = () => {
</Title>
</Group>
{/* KPI Cards */}
<Grid gutter="md">
{kpiData.map((kpi, index) => (
<GridCol key={index} span={{ base: 12, sm: 6, md: 6 }}>
{/* Peta Keamanan CCTV */}
<GridCol span={{ base: 12, lg: 6 }}>
<Stack gap={"xs"}>
{/* KPI Cards */}
<Grid gutter="md">
{kpiData.map((kpi, index) => (
<GridCol key={index} span={{ base: 12, sm: 6, md: 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"}>
{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>
<Card
p="md"
radius="md"
@@ -137,119 +176,81 @@ const KeamananPage = () => {
style={{ borderColor: dark ? "#141D34" : "white" }}
h="100%"
>
<Group justify="space-between" align="center">
<Stack gap={0}>
<Text size="sm" c={dark ? "dark.3" : "dimmed"}>
{kpi.subtitle}
</Text>
<Group gap="xs" align="center">
<Text size="xl" fw={700} c={dark ? "dark.0" : "black"}>
{kpi.value}
</Text>
<Text size="sm" c={dark ? "dark.3" : "dimmed"}>
{kpi.title}
</Text>
</Group>
</Stack>
<ThemeIcon
variant="light"
color={kpi.color}
size="xl"
radius="xl"
>
{kpi.icon}
</ThemeIcon>
</Group>
</Card>
</GridCol>
))}
</Grid>
<Grid gutter="md">
{/* Peta Keamanan CCTV */}
<GridCol span={{ base: 12, lg: 6 }}>
<Card
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"}>
Peta Keamanan CCTV
</Title>
<Text size="sm" c={dark ? "dark.3" : "dimmed"} mb="md">
Titik Lokasi CCTV
</Text>
{/* Placeholder for map */}
<Box
style={{
backgroundColor: dark ? "#2d3748" : "#e2e8f0",
borderRadius: "8px",
height: "400px",
display: "flex",
alignItems: "center",
justifyContent: "center",
}}
>
<Stack align="center">
<IconMapPin
size={48}
stroke={1.5}
color={dark ? "#94a3b8" : "#64748b"}
/>
<Text c={dark ? "dark.3" : "dimmed"}>Peta Lokasi CCTV</Text>
<Text size="sm" c={dark ? "dark.3" : "dimmed"} ta="center">
Integrasi dengan Google Maps atau Mapbox akan ditampilkan di
sini
</Text>
</Stack>
</Box>
{/* CCTV Locations List */}
<Stack mt="md" gap="sm">
<Title order={4} c={dark ? "dark.0" : "black"}>
Daftar CCTV
<Title order={3} mb="md" c={dark ? "dark.0" : "black"}>
Peta Keamanan 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 size="sm" c={dark ? "dark.3" : "dimmed"} mb="md">
Titik Lokasi CCTV
</Text>
{/* Placeholder for map */}
<Box
style={{
backgroundColor: dark ? "#2d3748" : "#e2e8f0",
borderRadius: "8px",
height: "400px",
display: "flex",
alignItems: "center",
justifyContent: "center",
}}
>
<Stack align="center">
<IconMapPin
size={48}
stroke={1.5}
color={dark ? "#94a3b8" : "#64748b"}
/>
<Text c={dark ? "dark.3" : "dimmed"}>Peta Lokasi CCTV</Text>
<Text size="sm" c={dark ? "dark.3" : "dimmed"} ta="center">
Integrasi dengan Google Maps atau Mapbox akan ditampilkan di
sini
</Text>
</Stack>
</Box>
{/* CCTV Locations List */}
<Stack mt="md" gap="sm">
<Title order={4} c={dark ? "dark.0" : "black"}>
Daftar CCTV
</Title>
{cctvLocations.map((cctv, index) => (
<Card
key={index}
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>
<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>
</Card>
))}
</Stack>
</Card>
</Stack>
</GridCol>
{/* Daftar Laporan Keamanan */}
@@ -262,10 +263,6 @@ const KeamananPage = () => {
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

View File

@@ -1,189 +1,78 @@
import {
ActionIcon,
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";
import { Box, Button, Group, Stack, Switch, Text, Title } from "@mantine/core";
const AksesDanTimSettings = () => {
const [opened, setOpened] = useState(false);
const { colorScheme } = useMantineColorScheme();
const dark = colorScheme === "dark";
// Sample team members data
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: 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 = [
{ value: "administrator", label: "Administrator" },
{ value: "operator", label: "Operator" },
{ value: "keuangan", label: "Keuangan" },
{ value: "umum", label: "Umum" },
{ value: "keamanan", label: "Keamanan" },
];
return (
<Card
withBorder
radius="md"
p="xl"
bg={dark ? "#141D34" : "white"}
style={{ borderColor: dark ? "#141D34" : "white" }}
>
<Modal
opened={opened}
onClose={() => setOpened(false)}
title="Tambah Anggota Tim"
size="lg"
>
<TextInput
label="Nama Lengkap"
placeholder="Masukkan nama lengkap anggota tim"
mb="md"
/>
<TextInput
label="Alamat Email"
placeholder="Masukkan alamat email"
mb="md"
/>
<Select
label="Peran"
placeholder="Pilih peran anggota tim"
data={roles}
mb="md"
/>
<Group justify="flex-end" mt="xl">
<Button variant="outline" onClick={() => setOpened(false)}>
Batal
<Stack pr={"50%"} gap={"xl"}>
<Box>
<Stack gap={"xs"}>
<Title order={2}>Manajemen Tim</Title>
<Button bg={"#1E3A5F"} radius={"md"} c={"white"} fullWidth>
Undangan Anggota Baru
</Button>
<Button>Undang Anggota</Button>
</Group>
</Modal>
<Title order={2} mb="lg">
Akses & Tim
</Title>
<Text color="dimmed" mb="xl">
Kelola akses dan anggota tim Anda
</Text>
<Space h="lg" />
<Group justify="space-between" mb="md">
<Title order={4}>Anggota Tim</Title>
<Button
leftSection={<IconUserPlus size={16} />}
onClick={() => setOpened(true)}
>
Tambah Anggota
</Button>
</Group>
<Table highlightOnHover>
<Table.Thead>
<Table.Tr>
<Table.Th>Nama</Table.Th>
<Table.Th>Email</Table.Th>
<Table.Th>Peran</Table.Th>
<Table.Th>Status</Table.Th>
<Table.Th>Aksi</Table.Th>
</Table.Tr>
</Table.Thead>
<Table.Tbody>
{teamMembers.map((member) => (
<Table.Tr key={member.id}>
<Table.Td>
<Group gap="sm">
<IconUser size={20} />
<Text>{member.name}</Text>
</Group>
</Table.Td>
<Table.Td>{member.email}</Table.Td>
<Table.Td>
<Text fw={500}>{member.role}</Text>
</Table.Td>
<Table.Td>
<Text c={member.status === "Aktif" ? "green" : "red"} fw={500}>
{member.status}
</Text>
</Table.Td>
<Table.Td>
<Group>
<ActionIcon variant="subtle" color="blue">
<IconEdit size={16} />
</ActionIcon>
<ActionIcon variant="subtle" color="red">
<IconTrash size={16} />
</ActionIcon>
</Group>
</Table.Td>
</Table.Tr>
))}
</Table.Tbody>
</Table>
<Space h="xl" />
<Alert
icon={<IconInfoCircle size={16} />}
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">
<Button bg={"#1E3A5F"} radius={"md"} c={"white"} fullWidth>
Kelola Role & Permission
</Button>
<Group justify="space-between">
<Text fw={"bold"} fz={"sm"}>
Daftar Anggota Teraktif
</Text>
<Text fw={"bold"} fz={"sm"}>
12 Anggota
</Text>
</Group>
</Stack>
</Box>
<Box>
<Stack gap={"xs"}>
<Title order={2}>Hak Akses</Title>
<Group justify="space-between">
<Text fw={"bold"} fz={"sm"}>
Administrator
</Text>
<Text fw={"bold"} fz={"sm"}>
2 Orang
</Text>
</Group>
<Group justify="space-between">
<Text fw={"bold"} fz={"sm"}>
Editor
</Text>
<Text fw={"bold"} fz={"sm"}>
5 Orang
</Text>
</Group>
<Group justify="space-between">
<Text fw={"bold"} fz={"sm"}>
Viewer
</Text>
<Text fw={"bold"} fz={"sm"}>
5 Orang
</Text>
</Group>
</Stack>
</Box>
<Box>
<Stack gap={"xs"}>
<Title order={2}>Kolaborasi</Title>
<Group mb="md" justify="space-between">
<Text fw={"bold"} fz={"sm"}>
Izin Export Data
</Text>
<Switch defaultChecked />
</Group>
<Group mb="md" justify="space-between">
<Text fw={"bold"} fz={"sm"}>
Require Approval Untuk Perubahan
</Text>
<Switch defaultChecked />
</Group>
</Stack>
</Box>
<Group justify="flex-start" mt="xl">
<Button variant="outline">Batal</Button>
<Button>Simpan Perubahan</Button>
</Group>
</Card>
</Stack>
);
};

View File

@@ -1,89 +1,64 @@
import {
Alert,
Button,
Card,
Group,
PasswordInput,
Space,
Switch,
Text,
Title,
useMantineColorScheme,
} from "@mantine/core";
import { IconInfoCircle, IconLock } from "@tabler/icons-react";
import { Box, Button, Group, Stack, Switch, Text, Title } from "@mantine/core";
const KeamananSettings = () => {
const { colorScheme } = useMantineColorScheme();
const dark = colorScheme === "dark";
return (
<Card
withBorder
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" />
<PasswordInput
label="Kata Sandi Saat Ini"
placeholder="Masukkan kata sandi saat ini"
mb="md"
/>
<PasswordInput
label="Kata Sandi Baru"
placeholder="Masukkan kata sandi baru"
mb="md"
/>
<PasswordInput
label="Konfirmasi Kata Sandi Baru"
placeholder="Konfirmasi kata sandi baru"
mb="md"
/>
<Space h="md" />
<Group mb="md">
<Switch label="Verifikasi Dua Langkah" />
<Switch label="Login Otentikasi Aplikasi" />
</Group>
<Space h="md" />
<Alert
icon={<IconLock size={16} />}
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"
>
Setelah mengganti kata sandi, Anda akan diminta logout dari semua
perangkat.
</Alert>
<Group justify="flex-end" mt="xl">
<Stack pr={"50%"} gap={"xl"}>
<Box>
<Stack gap={"xs"}>
<Title order={2}>Autentikasi</Title>
<Group mb="md" justify="space-between">
<Text fw={"bold"} fz={"sm"}>
Two-Factor Authentication
</Text>
<Switch defaultChecked />
</Group>
<Group mb="md" justify="space-between">
<Text fw={"bold"} fz={"sm"}>
Biometrik Login
</Text>
<Switch defaultChecked />
</Group>
<Group mb="md" justify="space-between">
<Text fw={"bold"} fz={"sm"}>
IP Whitelist
</Text>
<Switch defaultChecked />
</Group>
</Stack>
</Box>
<Box>
<Stack gap={"xs"}>
<Title order={2}>Password</Title>
<Button bg={"#1E3A5F"} radius={"md"} c={"white"} fullWidth>
Ubah Password
</Button>
<Button bg={"#1E3A5F"} radius={"md"} c={"white"} fullWidth>
Riwayat Login
</Button>
<Button bg={"#1E3A5F"} radius={"md"} c={"white"} fullWidth>
Perangkat Terdaftar
</Button>
</Stack>
</Box>
<Box>
<Stack gap={"xs"}>
<Title order={2}>Audit & Log</Title>
<Group mb="md" justify="space-between">
<Text fw={"bold"} fz={"sm"}>
Log Aktivitas
</Text>
<Switch defaultChecked />
</Group>
<Button bg={"#1E3A5F"} radius={"md"} c={"white"} fullWidth>
Download Log
</Button>
</Stack>
</Box>
<Group justify="flex-start" mt="xl">
<Button variant="outline">Batal</Button>
<Button>Perbarui Kata Sandi</Button>
<Button>Simpan Perubahan</Button>
</Group>
</Card>
</Stack>
);
};

View File

@@ -1,10 +1,14 @@
import {
Alert,
Box,
Button,
Card,
Checkbox,
Grid,
GridCol,
Group,
Space,
Stack,
Switch,
Text,
Title,
@@ -16,70 +20,101 @@ const NotifikasiSettings = () => {
const { colorScheme } = useMantineColorScheme();
const dark = colorScheme === "dark";
return (
<Card
withBorder
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" />
<Checkbox.Group defaultValue={["email", "push"]} mb="md">
<Title order={4} mb="sm">
Metode Notifikasi
</Title>
<Group>
<Checkbox value="email" label="Email" />
<Checkbox value="push" label="Notifikasi Push" />
<Checkbox value="sms" label="SMS" />
</Group>
</Checkbox.Group>
<Space h="md" />
<Group mb="md">
<Switch label="Notifikasi Email" defaultChecked />
<Switch label="Notifikasi Push" defaultChecked />
</Group>
<Space h="md" />
<Title order={4} mb="sm">
Jenis Notifikasi
</Title>
<Group align="start">
<Switch label="Pengaduan Baru" defaultChecked />
<Switch label="Update Status Pengaduan" defaultChecked />
<Switch label="Laporan Mingguan" />
<Switch label="Pemberitahuan Keamanan" defaultChecked />
<Switch label="Aktivitas Akun" defaultChecked />
</Group>
<Space h="md" />
<Alert
icon={<IconInfoCircle size={16} />}
title="Tip"
color="blue"
mb="md"
>
Anda dapat menyesuaikan frekuensi notifikasi mingguan sesuai kebutuhan
Anda.
</Alert>
<Group justify="flex-end" mt="xl">
<Stack pr={"20%"} gap={"xs"}>
<Grid gutter={{ base: 5, xs: "md", md: "xl", xl: 50 }}>
<GridCol span={6}>
<Stack gap={"xs"}>
<Title order={3} mb="sm">
Metode Notifikasi
</Title>
<Group mb="md" justify="space-between">
<Text fw={"bold"} fz={"sm"}>
Laporan Harian
</Text>
<Switch defaultChecked />
</Group>
<Group mb="md" justify="space-between">
<Text fw={"bold"} fz={"sm"}>
Alert Sistem
</Text>
<Switch defaultChecked />
</Group>
<Group mb="md" justify="space-between">
<Text fw={"bold"} fz={"sm"}>
Update Keamanan
</Text>
<Switch defaultChecked />
</Group>
<Group mb="md" justify="space-between">
<Text fw={"bold"} fz={"sm"}>
Newsletter Bulanan
</Text>
<Switch defaultChecked />
</Group>
</Stack>
</GridCol>
<GridCol span={6}>
<Stack gap={"xs"}>
<Title order={3} mb="sm">
Preferensi Alert
</Title>
<Group mb="md" justify="space-between">
<Text fw={"bold"} fz={"sm"}>
Treshold Memori
</Text>
<Switch defaultChecked />
</Group>
<Group mb="md" justify="space-between">
<Text fw={"bold"} fz={"sm"}>
Treshold CPU
</Text>
<Switch defaultChecked />
</Group>
<Group mb="md" justify="space-between">
<Text fw={"bold"} fz={"sm"}>
Treshold Disk
</Text>
<Switch defaultChecked />
</Group>
</Stack>
</GridCol>
<GridCol span={6}>
<Stack gap={"xs"}>
<Title order={3} mb="sm">
Notifikasi Push
</Title>
<Group mb="md" justify="space-between">
<Text fw={"bold"} fz={"sm"}>
Alert Kritis
</Text>
<Switch defaultChecked />
</Group>
<Group mb="md" justify="space-between">
<Text fw={"bold"} fz={"sm"}>
Aktivitas Tim
</Text>
<Switch defaultChecked />
</Group>
<Group mb="md" justify="space-between">
<Text fw={"bold"} fz={"sm"}>
Komentar & Mention
</Text>
<Switch defaultChecked />
</Group>
<Group mb="md" justify="space-between">
<Text fw={"bold"} fz={"sm"}>
Bunyi Notifikasi
</Text>
<Switch defaultChecked />
</Group>
</Stack>
</GridCol>
</Grid>
<Group justify="flex-start" mt="xl">
<Button variant="outline">Batal</Button>
<Button>Simpan Preferensi</Button>
</Group>
</Card>
</Stack>
);
};

View File

@@ -1,44 +1,12 @@
import {
Alert,
Button,
Card,
Group,
Select,
Space,
Switch,
Text,
TextInput,
Title,
useMantineColorScheme,
} from "@mantine/core";
import { IconInfoCircle } from "@tabler/icons-react";
import { Box, Button, Group, Select, Switch, Text, Title } from "@mantine/core";
import { DateInput } from "@mantine/dates";
const UmumSettings = () => {
const { colorScheme } = useMantineColorScheme();
const dark = colorScheme === "dark";
return (
<Card
withBorder
radius="md"
p="xl"
bg={dark ? "#141D34" : "white"}
style={{ borderColor: dark ? "#141D34" : "white" }}
>
<Box pr={"50%"}>
<Title order={2} mb="lg">
Pengaturan Umum
Preferensi Tampilan
</Title>
<Text color="dimmed" mb="xl">
Kelola pengaturan umum aplikasi Anda
</Text>
<Space h="lg" />
<TextInput
label="Nama Aplikasi"
placeholder="Masukkan nama aplikasi"
defaultValue="Dashboard Desa Plus"
mb="md"
/>
<Select
label="Bahasa Aplikasi"
@@ -61,25 +29,53 @@ const UmumSettings = () => {
mb="md"
/>
<Group mb="md">
<Switch label="Notifikasi Email" defaultChecked />
<DateInput label="Format Tanggal" mb={"xl"} />
<Title order={2} mb="lg">
Dashboard
</Title>
<Group mb="md" justify="space-between">
<Text fw={"bold"} fz={"sm"}>
Refresh Otomatis
</Text>
<Switch defaultChecked />
</Group>
<Alert
icon={<IconInfoCircle size={16} />}
title="Informasi"
color="blue"
mb="md"
>
Beberapa pengaturan mungkin memerlukan restart aplikasi untuk diterapkan
sepenuhnya.
</Alert>
<Group mb="md" justify="space-between">
<Text fw={"bold"} fz={"sm"}>
Interval Refresh
</Text>
<Select
data={[
{ value: "1", label: "30d" },
{ value: "2", label: "60d" },
{ value: "3", label: "90d" },
]}
defaultValue="1"
w={90}
/>
</Group>
<Group mb="md" justify="space-between">
<Text fw={"bold"} fz={"sm"}>
Tampilkan Grid
</Text>
<Switch defaultChecked />
</Group>
<Group mb="md" justify="space-between">
<Text fw={"bold"} fz={"sm"}>
Animasi Transisi
</Text>
<Switch defaultChecked />
</Group>
<Group justify="flex-end" mt="xl">
<Button variant="outline">Batal</Button>
<Button>Simpan Perubahan</Button>
</Group>
</Card>
</Box>
);
};

View File

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

View File

@@ -0,0 +1,76 @@
import {
Card,
Group,
Stack,
Text,
ThemeIcon,
Title,
useMantineColorScheme,
} from "@mantine/core";
import { IconAward } from "@tabler/icons-react";
interface ScholarshipData {
penerima: number;
dana: string;
tahunAjaran: string;
}
interface BeasiswaProps {
data?: ScholarshipData;
}
export const Beasiswa = ({ data }: BeasiswaProps) => {
const { colorScheme } = useMantineColorScheme();
const dark = colorScheme === "dark";
const defaultData: ScholarshipData = {
penerima: 45,
dana: "Rp 1.200.000.000",
tahunAjaran: "2025/2026",
};
const displayData = data || defaultData;
return (
<Card
p="md"
radius="xl"
withBorder
shadow="sm"
bg={dark ? "#141D34" : "white"}
style={{ borderColor: dark ? "#141D34" : "#e5e7eb" }}
h={"100%"}
>
<Group justify="space-between" align="center">
<Stack gap={2}>
<Text size="sm" c={dark ? "dark.3" : "dimmed"} fw={500}>
Beasiswa Desa
</Text>
<Text size="xl" fw={700} c={dark ? "dark.0" : "#1e3a5f"}>
Penerima: {displayData.penerima}
</Text>
</Stack>
<ThemeIcon
variant="light"
color="darmasaba-success"
size="xl"
radius="xl"
>
<IconAward size={24} />
</ThemeIcon>
</Group>
<Stack gap="xs" mt="md">
<Group justify="space-between">
<Text c={dark ? "dark.3" : "dimmed"}>Dana Tersalurkan:</Text>
<Text fw={700} c={dark ? "dark.0" : "#1e3a5f"}>
{displayData.dana}
</Text>
</Group>
<Group justify="space-between">
<Text c={dark ? "dark.3" : "dimmed"}>Tahun Ajaran:</Text>
<Text c={dark ? "dark.0" : "#1e3a5f"}>{displayData.tahunAjaran}</Text>
</Group>
</Stack>
</Card>
);
};

View File

@@ -0,0 +1,101 @@
import {
Card,
Group,
Stack,
Text,
ThemeIcon,
Title,
useMantineColorScheme,
} from "@mantine/core";
import { IconCalendarEvent } from "@tabler/icons-react";
interface EventItem {
id: string;
nama: string;
tanggal: string;
lokasi: string;
}
interface EventCalendarProps {
data?: EventItem[];
}
export const EventCalendar = ({ data }: EventCalendarProps) => {
const { colorScheme } = useMantineColorScheme();
const dark = colorScheme === "dark";
const defaultData: EventItem[] = [
{
id: "1",
nama: "Hari Kesaktian Pancasila",
tanggal: "1 Oktober 2025",
lokasi: "Balai Desa",
},
{
id: "2",
nama: "Festival Budaya Desa",
tanggal: "20 Mei 2026",
lokasi: "Lapangan Desa",
},
{
id: "3",
nama: "Perayaan HUT Desa",
tanggal: "17 Agustus 2026",
lokasi: "Balai Desa",
},
];
const displayData = data || defaultData;
return (
<Card
p="md"
radius="xl"
withBorder
shadow="sm"
bg={dark ? "#141D34" : "white"}
style={{ borderColor: dark ? "#141D34" : "#e5e7eb" }}
>
<Title order={3} mb="md" c={dark ? "dark.0" : "#1e3a5f"}>
Kalender Event Budaya
</Title>
<Stack gap="sm">
{displayData.map((event) => (
<Card
key={event.id}
p="md"
radius="md"
withBorder
bg={dark ? "#263852ff" : "#F1F5F9"}
style={{ borderColor: dark ? "#263852ff" : "#F1F5F9" }}
hoverable
>
<Group justify="space-between" mb="xs">
<Group gap="sm" align="center">
<ThemeIcon
color="darmasaba-blue"
size="md"
radius="xl"
variant="light"
>
<IconCalendarEvent size={16} />
</ThemeIcon>
<Text fw={600} c={dark ? "dark.0" : "#1e3a5f"}>
{event.nama}
</Text>
</Group>
<Text size="sm" c={dark ? "dark.3" : "dimmed"} fw={500}>
{event.lokasi}
</Text>
</Group>
<Group pl={36}>
<Text size="sm" c={dark ? "dark.4" : "gray.6"}>
{event.tanggal}
</Text>
</Group>
</Card>
))}
</Stack>
</Card>
);
};

View File

@@ -0,0 +1,73 @@
import {
Card,
Group,
Progress,
Stack,
Text,
Title,
useMantineColorScheme,
} from "@mantine/core";
interface HealthProgressItem {
label: string;
value: number;
color: string;
}
interface HealthStatsProps {
data?: HealthProgressItem[];
}
export const HealthStats = ({ data }: HealthStatsProps) => {
const { colorScheme } = useMantineColorScheme();
const dark = colorScheme === "dark";
const defaultData: HealthProgressItem[] = [
{ label: "Imunisasi Lengkap", value: 92, color: "green" },
{ label: "Pemeriksaan Rutin", value: 88, color: "blue" },
{ label: "Gizi Baik", value: 86, color: "teal" },
{ label: "Target Stunting", value: 14, color: "red" },
];
const displayData = data || defaultData;
return (
<Card
p="md"
radius="xl"
withBorder
shadow="sm"
bg={dark ? "#141D34" : "white"}
style={{ borderColor: dark ? "#141D34" : "#e5e7eb" }}
h={"100%"}
>
<Title order={3} mb="md" c={dark ? "dark.0" : "#1e3a5f"}>
Statistik Kesehatan
</Title>
<Stack gap="md">
{displayData.map((item, index) => (
<div key={index}>
<Group justify="space-between" mb={5}>
<Text size="sm" fw={500} c={dark ? "dark.0" : "#1e3a5f"}>
{item.label}
</Text>
<Text
size="sm"
fw={600}
c={item.color === "red" ? "red" : dark ? "dark.0" : "#1e3a5f"}
>
{item.value}%
</Text>
</Group>
<Progress
value={item.value}
size="lg"
radius="xl"
color={item.color}
/>
</div>
))}
</Stack>
</Card>
);
};

View File

@@ -0,0 +1,120 @@
import {
Card,
Group,
Stack,
Text,
Title,
useMantineColorScheme,
} from "@mantine/core";
interface EducationData {
siswa: {
tk: number;
sd: number;
smp: number;
sma: number;
};
sekolah: {
jumlah: number;
guru: number;
};
}
interface PendidikanProps {
data?: EducationData;
}
export const Pendidikan = ({ data }: PendidikanProps) => {
const { colorScheme } = useMantineColorScheme();
const dark = colorScheme === "dark";
const defaultData: EducationData = {
siswa: {
tk: 125,
sd: 480,
smp: 210,
sma: 150,
},
sekolah: {
jumlah: 8,
guru: 42,
},
};
const displayData = data || defaultData;
return (
<Card
p="md"
radius="xl"
withBorder
shadow="sm"
bg={dark ? "#141D34" : "white"}
style={{ borderColor: dark ? "#141D34" : "#e5e7eb" }}
>
<Title order={3} mb="md" c={dark ? "dark.0" : "#1e3a5f"}>
Pendidikan
</Title>
<Stack gap="md">
<Group justify="space-between">
<Text fw={500} c={dark ? "dark.0" : "#1e3a5f"}>
TK / PAUD
</Text>
<Text fw={700} c={dark ? "dark.0" : "#1e3a5f"}>
{displayData.siswa.tk}
</Text>
</Group>
<Group justify="space-between">
<Text fw={500} c={dark ? "dark.0" : "#1e3a5f"}>
SD
</Text>
<Text fw={700} c={dark ? "dark.0" : "#1e3a5f"}>
{displayData.siswa.sd}
</Text>
</Group>
<Group justify="space-between">
<Text fw={500} c={dark ? "dark.0" : "#1e3a5f"}>
SMP
</Text>
<Text fw={700} c={dark ? "dark.0" : "#1e3a5f"}>
{displayData.siswa.smp}
</Text>
</Group>
<Group justify="space-between">
<Text fw={500} c={dark ? "dark.0" : "#1e3a5f"}>
SMA
</Text>
<Text fw={700} c={dark ? "dark.0" : "#1e3a5f"}>
{displayData.siswa.sma}
</Text>
</Group>
<Card
withBorder
radius="md"
p="md"
mt="md"
bg={dark ? "#263852ff" : "#F1F5F9"}
style={{ borderColor: dark ? "#263852ff" : "#F1F5F9" }}
>
<Group justify="space-between">
<Text fw={500} c={dark ? "dark.0" : "#1e3a5f"}>
Jumlah Lembaga Pendidikan
</Text>
<Text fw={700} c={dark ? "dark.0" : "#1e3a5f"}>
{displayData.sekolah.jumlah}
</Text>
</Group>
<Group justify="space-between" mt="sm">
<Text fw={500} c={dark ? "dark.0" : "#1e3a5f"}>
Jumlah Tenaga Pengajar
</Text>
<Text fw={700} c={dark ? "dark.0" : "#1e3a5f"}>
{displayData.sekolah.guru}
</Text>
</Group>
</Card>
</Stack>
</Card>
);
};

View File

@@ -0,0 +1,96 @@
import {
Badge,
Card,
Group,
Stack,
Text,
Title,
useMantineColorScheme,
} from "@mantine/core";
interface PosyanduItem {
id: string;
nama: string;
tanggal: string;
jam: string;
}
interface PosyanduScheduleProps {
data?: PosyanduItem[];
}
export const PosyanduSchedule = ({ data }: PosyanduScheduleProps) => {
const { colorScheme } = useMantineColorScheme();
const dark = colorScheme === "dark";
const defaultData: PosyanduItem[] = [
{
id: "1",
nama: "Posyandu Mawar",
tanggal: "Senin, 15 Feb 2026",
jam: "08:00 - 11:00",
},
{
id: "2",
nama: "Posyandu Melati",
tanggal: "Selasa, 16 Feb 2026",
jam: "08:00 - 11:00",
},
{
id: "3",
nama: "Posyandu Dahlia",
tanggal: "Rabu, 17 Feb 2026",
jam: "08:00 - 11:00",
},
{
id: "4",
nama: "Posyandu Anggrek",
tanggal: "Kamis, 18 Feb 2026",
jam: "08:00 - 11:00",
},
];
const displayData = data || defaultData;
return (
<Card
p="md"
radius="xl"
withBorder
shadow="sm"
bg={dark ? "#141D34" : "white"}
style={{ borderColor: dark ? "#141D34" : "#e5e7eb" }}
>
<Title order={3} mb="md" c={dark ? "dark.0" : "#1e3a5f"}>
Jadwal Posyandu
</Title>
<Stack gap="sm">
{displayData.map((item) => (
<Card
key={item.id}
p="md"
radius="md"
withBorder
bg={dark ? "#263852ff" : "#F1F5F9"}
style={{ borderColor: dark ? "#263852ff" : "#F1F5F9" }}
hoverable
>
<Group justify="space-between">
<Stack gap={0}>
<Text fw={600} c={dark ? "dark.0" : "#1e3a5f"}>
{item.nama}
</Text>
<Text size="sm" c={dark ? "dark.3" : "dimmed"}>
{item.tanggal}
</Text>
</Stack>
<Badge variant="light" color="darmasaba-blue" size="md">
{item.jam}
</Badge>
</Group>
</Card>
))}
</Stack>
</Card>
);
};

View File

@@ -0,0 +1,140 @@
import {
Card,
Grid,
GridCol,
Group,
Stack,
Text,
ThemeIcon,
useMantineColorScheme,
} from "@mantine/core";
import {
IconBabyCarriage,
IconHeartbeat,
IconMedicalCross,
IconStethoscope,
} from "@tabler/icons-react";
interface SummaryCardProps {
title: string;
value: number;
subtitle?: string;
icon: React.ReactNode;
color: string;
highlight?: boolean;
backgroundColor: string;
}
const SummaryCard = ({
title,
value,
subtitle,
icon,
color,
highlight = false,
backgroundColor,
}: SummaryCardProps) => {
const { colorScheme } = useMantineColorScheme();
const dark = colorScheme === "dark";
return (
<Card
p="md"
radius="xl"
withBorder
shadow="sm"
bg={dark ? "#141D34" : "white"}
style={{ borderColor: dark ? "#141D34" : "#e5e7eb" }}
>
<Group justify="space-between" align="center">
<Stack gap={2}>
<Text size="sm" c={dark ? "dark.3" : "dimmed"} fw={500}>
{title}
</Text>
<Text
size="xl"
fw={700}
c={highlight ? "red" : dark ? "dark.0" : "#1e3a5f"}
>
{value}
</Text>
{subtitle && (
<Text size="xs" c={dark ? "dark.4" : "gray.6"}>
{subtitle}
</Text>
)}
</Stack>
<ThemeIcon bg={backgroundColor} color={color} size="xl" radius="xl">
{icon}
</ThemeIcon>
</Group>
</Card>
);
};
interface HealthSummaryData {
ibuHamil: number;
balita: number;
alertStunting: number;
posyanduAktif: number;
}
interface SummaryCardsProps {
data?: HealthSummaryData;
}
export const SummaryCards = ({ data }: SummaryCardsProps) => {
const defaultData: HealthSummaryData = {
ibuHamil: 87,
balita: 342,
alertStunting: 12,
posyanduAktif: 8,
};
const displayData = data || defaultData;
return (
<Grid gutter="md">
<GridCol span={{ base: 12, sm: 6, lg: 3 }}>
<SummaryCard
title="Ibu Hamil Aktif"
value={displayData.ibuHamil}
subtitle="Aktif"
icon={<IconHeartbeat size={20} />}
color="white"
backgroundColor="#1E3A5F"
/>
</GridCol>
<GridCol span={{ base: 12, sm: 6, lg: 3 }}>
<SummaryCard
title="Balita Terdaftar"
value={displayData.balita}
subtitle="Terdaftar"
icon={<IconBabyCarriage size={20} />}
color="white"
backgroundColor="#1E3A5F"
/>
</GridCol>
<GridCol span={{ base: 12, sm: 6, lg: 3 }}>
<SummaryCard
title="Alert Stunting"
value={displayData.alertStunting}
subtitle="Perhatian"
icon={<IconStethoscope size={20} />}
color="white"
backgroundColor="#1E3A5F"
/>
</GridCol>
<GridCol span={{ base: 12, sm: 6, lg: 3 }}>
<SummaryCard
title="Posyandu Aktif"
value={displayData.posyanduAktif}
subtitle="Aktif"
icon={<IconMedicalCross size={20} />}
color="white"
backgroundColor="#1E3A5F"
/>
</GridCol>
</Grid>
);
};

View File

@@ -51,9 +51,7 @@ export const HelpCard = ({
{icon && (
<div
style={{
backgroundColor: isDark
? theme.colors.blue[8]
: theme.colors.blue[0],
backgroundColor: isDark ? "#263852ff" : "#1E3A5F",
borderRadius: "8px",
padding: "8px",
display: "flex",

View File

@@ -6,13 +6,13 @@ import {
Group,
Stack,
Text,
useMantineColorScheme
useMantineColorScheme,
} from "@mantine/core";
import {
IconCategory,
IconCurrencyDollar,
IconTrendingUp,
IconUsers
IconUsers,
} from "@tabler/icons-react";
interface KpiCardProps {
@@ -24,7 +24,14 @@ interface KpiCardProps {
backgroundColor: string;
}
const KpiCard = ({ title, value, subtitle, icon, color, backgroundColor }: KpiCardProps) => {
const KpiCard = ({
title,
value,
subtitle,
icon,
color,
backgroundColor,
}: KpiCardProps) => {
const { colorScheme } = useMantineColorScheme();
const dark = colorScheme === "dark";
@@ -108,7 +115,7 @@ export const SummaryCards = ({ data }: SummaryCardsProps) => {
subtitle: "Beroperasi",
icon: <IconCurrencyDollar size={25} />,
color: "white",
backgroundColor: "#1E3A5F"
backgroundColor: "#1E3A5F",
},
{
title: "UMKM Terdaftar",
@@ -116,7 +123,7 @@ export const SummaryCards = ({ data }: SummaryCardsProps) => {
subtitle: "Total registrasi",
icon: <IconUsers size={25} />,
color: "white",
backgroundColor: "#1E3A5F"
backgroundColor: "#1E3A5F",
},
{
title: "Omzet",
@@ -124,7 +131,7 @@ export const SummaryCards = ({ data }: SummaryCardsProps) => {
subtitle: "Omzet BUMDes per bulan",
icon: <IconTrendingUp size={25} />,
color: "white",
backgroundColor: "#1E3A5F"
backgroundColor: "#1E3A5F",
},
{
title: "UMKM Terbanyak",
@@ -132,7 +139,7 @@ export const SummaryCards = ({ data }: SummaryCardsProps) => {
subtitle: `Kategori ${displayData.kategoriTerbanyak.name}`,
icon: <IconTrendingUp size={25} />,
color: "white",
backgroundColor: "#1E3A5F"
backgroundColor: "#1E3A5F",
},
];

View File

@@ -1,16 +1,26 @@
import { useDisclosure } from "@mantine/hooks";
import { useState } from "react";
export function useSidebarFullscreen() {
const [opened, { toggle: toggleMobile }] = useDisclosure();
const [sidebarCollapsed, setSidebarCollapsed] = useDisclosure(false);
const [clickCount, setClickCount] = useState(0);
const toggleSidebar = () => {
setSidebarCollapsed.toggle();
setClickCount(0);
};
const handleMainClick = () => {
if (!sidebarCollapsed) {
toggleSidebar();
const newCount = clickCount + 1;
setClickCount(newCount);
if (newCount === 2) {
toggleSidebar();
} else {
setTimeout(() => setClickCount(0), 300);
}
}
};

View File

@@ -1,6 +1,7 @@
import { AppShell, Burger, Group, useMantineColorScheme } from "@mantine/core";
import { useDisclosure } from "@mantine/hooks";
import { createFileRoute } from "@tanstack/react-router";
import { useState } from "react";
import { DashboardContent } from "@/components/dashboard-content";
import { Header } from "@/components/header";
import { Sidebar } from "@/components/sidebar";
@@ -12,6 +13,7 @@ export const Route = createFileRoute("/")({
function DashboardPage() {
const [opened, { toggle }] = useDisclosure();
const [sidebarCollapsed, setSidebarCollapsed] = useDisclosure(false);
const [clickCount, setClickCount] = useState(0);
const { colorScheme } = useMantineColorScheme();
const headerBgColor = colorScheme === "dark" ? "#11192D" : "#19355E";
const navbarBgColor = colorScheme === "dark" ? "#11192D" : "white";
@@ -19,7 +21,15 @@ function DashboardPage() {
const handleMainClick = () => {
if (!sidebarCollapsed) {
setSidebarCollapsed.toggle();
const newCount = clickCount + 1;
setClickCount(newCount);
if (newCount === 2) {
setSidebarCollapsed.toggle();
setClickCount(0);
} else {
setTimeout(() => setClickCount(0), 300);
}
}
};

View File

@@ -1,9 +1,10 @@
import { createFileRoute } from "@tanstack/react-router";
import AksesDanTimSettings from "@/components/pengaturan/akses-dan-tim";
export const Route = createFileRoute("/pengaturan/akses-dan-tim")({
component: RouteComponent,
});
function RouteComponent() {
return <div>Hello "/pengaturan/akses-dan-tim"!</div>;
return <AksesDanTimSettings />;
}

View File

@@ -1,9 +1,10 @@
import { createFileRoute } from "@tanstack/react-router";
import KeamananSettings from "@/components/pengaturan/keamanan";
export const Route = createFileRoute("/pengaturan/keamanan")({
component: RouteComponent,
});
function RouteComponent() {
return <div>Hello "/pengaturan/keamanan"!</div>;
return <KeamananSettings />;
}

View File

@@ -1,9 +1,10 @@
import { createFileRoute } from "@tanstack/react-router";
import NotifikasiSettings from "@/components/pengaturan/notifikasi";
export const Route = createFileRoute("/pengaturan/notifikasi")({
component: RouteComponent,
});
function RouteComponent() {
return <div>Hello "/pengaturan/notifikasi"!</div>;
return <NotifikasiSettings />;
}

30
src/store/sosial.ts Normal file
View File

@@ -0,0 +1,30 @@
import { proxy } from "valtio";
type SelectedYear = string;
interface SosialState {
selectedYear: SelectedYear;
filters: {
dusun: string | null;
kategori: string | null;
};
}
export const sosialStore = proxy<SosialState>({
selectedYear: new Date().getFullYear().toString(),
filters: {
dusun: null,
kategori: null,
},
});
export const setYear = (year: SelectedYear) => {
sosialStore.selectedYear = year;
};
export const setFilter = (
key: keyof SosialState["filters"],
value: string | null,
) => {
sosialStore.filters[key] = value;
};