feat: implement Kependudukan menu with CRUD admin pages

- Add Distribusi Umur admin pages (list, create, edit)
- Add Data Banjar admin pages (list, create, edit)
- Add Migrasi Penduduk admin pages (list, create, edit)
- Update state management with full CRUD operations for all modules
- Add Kependudukan menu to admin sidebar (devBar, navBar, role1)
- Add public pages for Distribusi Umur with age range sorting
- Update Dinamika Penduduk to use real-time birth/death data
- Add Biome configuration for code linting
- Create API routes for all Kependudukan modules

Features:
- Pagination and search for all admin list pages
- Responsive design (table for desktop, cards for mobile)
- Delete confirmation modal
- Toast notifications for user feedback
- Zod validation for all forms
- Age range auto-sorting in public Distribusi Umur chart

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
This commit is contained in:
2026-04-09 17:10:29 +08:00
parent 34a37dc63b
commit 5e822f0b05
51 changed files with 5964 additions and 0 deletions

View File

@@ -0,0 +1,196 @@
'use client'
import colors from '@/con/colors';
import { Stack, Box, Paper, Text, Title, SimpleGrid, Skeleton, Group, Badge, Center, Image } from '@mantine/core';
import { IconUsers, IconHome, IconBasket, IconCoin, IconDatabaseOff } from '@tabler/icons-react';
import React from 'react';
import BackButton from '../../desa/layanan/_com/BackButto';
import { useProxy } from 'valtio/utils';
import kependudukanDashboard from '@/app/admin/(dashboard)/_state/kependudukan/dashboard';
import { useShallowEffect } from '@mantine/hooks';
function Page() {
const state = useProxy(kependudukanDashboard)
useShallowEffect(() => {
state.summary.load()
}, [])
const summary = state.summary.data?.summary;
if (state.summary.loading) {
return (
<Stack py={10}>
<Skeleton h={200} />
</Stack>
)
}
if (!summary) {
return (
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"lg"}>
<Box px={{ base: 'md', md: 100 }}>
<BackButton />
</Box>
<Box px={{ base: 'md', md: 100 }}>
<Title
order={1}
ta="center"
c={colors["blue-button"]}
fw="bold"
lh={1.2}
>
Dashboard Kependudukan
</Title>
<Text
ta="center"
fz={{ base: 'sm', md: 'md' }}
lh={1.5}
c="black"
>
Ringkasan data kependudukan Desa Darmasaba
</Text>
</Box>
<Box px={{ base: "md", md: 100 }}>
<Paper p="xl" withBorder>
<Center py="xl">
<Stack align="center" gap="md">
<IconDatabaseOff size={80} color={colors.grey['2']} />
<Text ta="center" fz="lg" fw={500} c="dimmed">
Data Belum Tersedia
</Text>
<Text ta="center" fz="sm" c="dimmed" maw={400}>
Data kependudukan untuk tahun ini belum diperbarui.
Silakan hubungi administrator desa untuk informasi lebih lanjut.
</Text>
</Stack>
</Center>
</Paper>
</Box>
</Stack>
)
}
const stats = [
{
title: 'Total Penduduk',
value: summary.totalPenduduk,
icon: IconUsers,
color: colors['blue-button'],
},
{
title: 'Kepala Keluarga',
value: summary.totalKK,
icon: IconHome,
color: '#6EDF9C',
},
{
title: 'Kelahiran Tahun Ini',
value: summary.totalKelahiran,
icon: IconBasket,
color: '#FF9F43',
},
{
title: 'Penduduk Miskin',
value: summary.totalKemiskinan,
icon: IconCoin,
color: '#EE5050',
},
];
return (
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"lg"}>
<Box px={{ base: 'md', md: 100 }}>
<BackButton />
</Box>
<Box px={{ base: 'md', md: 100 }}>
<Title
order={1}
ta="center"
c={colors["blue-button"]}
fw="bold"
lh={1.2}
>
Dashboard Kependudukan
</Title>
<Text
ta="center"
fz={{ base: 'sm', md: 'md' }}
lh={1.5}
c="black"
>
Ringkasan data kependudukan Desa Darmasaba
</Text>
</Box>
<Box px={{ base: "md", md: 100 }}>
<SimpleGrid cols={{ base: 1, sm: 2, md: 4 }} spacing="md">
{stats.map((stat) => {
const Icon = stat.icon;
return (
<Paper key={stat.title} p="xl" withBorder>
<Group justify="space-between">
<div>
<Text c="dimmed" fz="sm" fw={500}>
{stat.title}
</Text>
<Text fz={28} fw={700} mt={5}>
{stat.value.toLocaleString('id-ID')}
</Text>
</div>
<Badge
size="xl"
color={stat.color}
variant="light"
>
<Icon size={32} />
</Badge>
</Group>
</Paper>
);
})}
</SimpleGrid>
</Box>
{state.summary.data?.dinamika && (
<Box px={{ base: "md", md: 100 }}>
<Paper p="xl" withBorder>
<Title order={3} mb="md" c={colors["blue-button"]}>
Dinamika Penduduk
</Title>
<SimpleGrid cols={{ base: 2, md: 4 }} spacing="md">
<Paper p="md" bg="#6EDF9C">
<Text fz="sm" c="white">Kelahiran</Text>
<Text fz={24} fw={700} c="white">
{state.summary.data.dinamika.kelahiran}
</Text>
</Paper>
<Paper p="md" bg="#EE5050">
<Text fz="sm" c="white">Kematian</Text>
<Text fz={24} fw={700} c="white">
{state.summary.data.dinamika.kematian}
</Text>
</Paper>
<Paper p="md" bg="#5082EE">
<Text fz="sm" c="white">Pindah Masuk</Text>
<Text fz={24} fw={700} c="white">
{state.summary.data.dinamika.pindahMasuk}
</Text>
</Paper>
<Paper p="md" bg="#FF9F43">
<Text fz="sm" c="white">Pindah Keluar</Text>
<Text fz={24} fw={700} c="white">
{state.summary.data.dinamika.pindahKeluar}
</Text>
</Paper>
</SimpleGrid>
</Paper>
</Box>
)}
</Stack>
);
}
export default Page;

View File

@@ -0,0 +1,147 @@
'use client'
import colors from '@/con/colors';
import { Stack, Box, Paper, Text, Title, Skeleton, Table, Center } from '@mantine/core';
import { IconDatabaseOff } from '@tabler/icons-react';
import React from 'react';
import BackButton from '../../desa/layanan/_com/BackButto';
import { useProxy } from 'valtio/utils';
import dataBanjar from '@/app/admin/(dashboard)/_state/kependudukan/data-banjar';
import { useShallowEffect } from '@mantine/hooks';
function Page() {
const state = useProxy(dataBanjar)
useShallowEffect(() => {
state.findMany.load()
}, [])
const data = state.findMany.data || [];
if (state.findMany.loading) {
return (
<Stack py={10}>
<Skeleton h={500} />
</Stack>
)
}
if (!data || data.length === 0) {
return (
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"lg"}>
<Box px={{ base: 'md', md: 100 }}>
<BackButton />
</Box>
<Box px={{ base: 'md', md: 100 }}>
<Title
order={1}
ta="center"
c={colors["blue-button"]}
fw="bold"
lh={1.2}
>
Data per Banjar
</Title>
<Text
ta="center"
fz={{ base: 'sm', md: 'md' }}
lh={1.5}
c="black"
>
Statistik kependudukan per banjar di Desa Darmasaba
</Text>
</Box>
<Box px={{ base: "md", md: 100 }}>
<Paper p="xl" withBorder>
<Center py="xl">
<Stack align="center" gap="md">
<IconDatabaseOff size={80} color={colors.grey['2']} />
<Text ta="center" fz="lg" fw={500} c="dimmed">
Data Belum Tersedia
</Text>
<Text ta="center" fz="sm" c="dimmed" maw={400}>
Data kependudukan per banjar untuk tahun ini belum diperbarui.
Silakan hubungi administrator desa untuk informasi lebih lanjut.
</Text>
</Stack>
</Center>
</Paper>
</Box>
</Stack>
)
}
const rows = data.map((item) => (
<Table.Tr key={item.id}>
<Table.Td>{item.nama}</Table.Td>
<Table.Td ta="right">{item.penduduk.toLocaleString('id-ID')}</Table.Td>
<Table.Td ta="right">{item.kk.toLocaleString('id-ID')}</Table.Td>
<Table.Td ta="right">{item.miskin.toLocaleString('id-ID')}</Table.Td>
</Table.Tr>
));
const totalPenduduk = data.reduce((sum, item) => sum + item.penduduk, 0);
const totalKK = data.reduce((sum, item) => sum + item.kk, 0);
const totalMiskin = data.reduce((sum, item) => sum + item.miskin, 0);
return (
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"lg"}>
<Box px={{ base: 'md', md: 100 }}>
<BackButton />
</Box>
<Box px={{ base: 'md', md: 100 }}>
<Title
order={1}
ta="center"
c={colors["blue-button"]}
fw="bold"
lh={1.2}
>
Data per Banjar
</Title>
<Text
ta="center"
fz={{ base: 'sm', md: 'md' }}
lh={1.5}
c="black"
>
Statistik kependudukan per banjar di Desa Darmasaba
</Text>
</Box>
<Box px={{ base: "md", md: 100 }}>
<Paper p="xl" withBorder>
<Text fw={700} fz="lg" mb="md" c="black">
Data Kependudukan per Banjar
</Text>
<Table.ScrollContainer minWidth={500}>
<Table highlightOnHover>
<Table.Thead>
<Table.Tr>
<Table.Th>Banjar</Table.Th>
<Table.Th ta="right">Penduduk</Table.Th>
<Table.Th ta="right">Kepala Keluarga</Table.Th>
<Table.Th ta="right">Penduduk Miskin</Table.Th>
</Table.Tr>
</Table.Thead>
<Table.Tbody>
{rows}
<Table.Tr fw={700} bg={colors.grey[1]}>
<Table.Td>Total</Table.Td>
<Table.Td ta="right">{totalPenduduk.toLocaleString('id-ID')}</Table.Td>
<Table.Td ta="right">{totalKK.toLocaleString('id-ID')}</Table.Td>
<Table.Td ta="right">{totalMiskin.toLocaleString('id-ID')}</Table.Td>
</Table.Tr>
</Table.Tbody>
</Table>
</Table.ScrollContainer>
</Paper>
</Box>
</Stack>
);
}
export default Page;

View File

@@ -0,0 +1,189 @@
'use client'
import colors from '@/con/colors';
import { Stack, Box, Paper, Text, Title, Skeleton, SimpleGrid, Center } from '@mantine/core';
import { IconDatabaseOff } from '@tabler/icons-react';
import React from 'react';
import BackButton from '../../desa/layanan/_com/BackButto';
import { useProxy } from 'valtio/utils';
import kependudukanDashboard from '@/app/admin/(dashboard)/_state/kependudukan/dashboard';
import persentaseKelahiranKematian from '@/app/admin/(dashboard)/_state/kesehatan/data_kesehatan_warga/persentaseKelahiran';
import { useShallowEffect } from '@mantine/hooks';
function Page() {
const stateKependudukan = useProxy(kependudukanDashboard);
const statePersentaseKelahiranKematian = useProxy(persentaseKelahiranKematian);
// Load migration data from kependudukan dashboard
useShallowEffect(() => {
stateKependudukan.summary.load();
}, []);
// Load birth and death data
useShallowEffect(() => {
statePersentaseKelahiranKematian.kelahiran.findMany.load();
statePersentaseKelahiranKematian.kematian.findMany.load();
}, []);
const dinamika = stateKependudukan.summary.data?.dinamika;
// Calculate birth and death counts from detailed data
const kelahiranCount = statePersentaseKelahiranKematian.kelahiran.findMany.data?.length || 0;
const kematianCount = statePersentaseKelahiranKematian.kematian.findMany.data?.length || 0;
const isLoading = stateKependudukan.summary.loading ||
statePersentaseKelahiranKematian.kelahiran.findMany.loading ||
statePersentaseKelahiranKematian.kematian.findMany.loading;
if (isLoading) {
return (
<Stack py={10}>
<Skeleton h={500} />
</Stack>
)
}
if (!dinamika || (kelahiranCount === 0 && kematianCount === 0 && (!dinamika.pindahMasuk || dinamika.pindahMasuk === 0))) {
return (
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"lg"}>
<Box px={{ base: 'md', md: 100 }}>
<BackButton />
</Box>
<Box px={{ base: 'md', md: 100 }}>
<Title
order={1}
ta="center"
c={colors["blue-button"]}
fw="bold"
lh={1.2}
>
Dinamika Penduduk
</Title>
<Text
ta="center"
fz={{ base: 'sm', md: 'md' }}
lh={1.5}
c="black"
>
Statistik kelahiran, kematian, dan migrasi penduduk
</Text>
</Box>
<Box px={{ base: "md", md: 100 }}>
<Paper p="xl" withBorder>
<Center py="xl">
<Stack align="center" gap="md">
<IconDatabaseOff size={80} color={colors.grey['2']} />
<Text ta="center" fz="lg" fw={500} c="dimmed">
Data Belum Tersedia
</Text>
<Text ta="center" fz="sm" c="dimmed" maw={400}>
Data dinamika penduduk untuk tahun ini belum diperbarui.
Silakan hubungi administrator desa untuk informasi lebih lanjut.
</Text>
</Stack>
</Center>
</Paper>
</Box>
</Stack>
)
}
return (
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"lg"}>
<Box px={{ base: 'md', md: 100 }}>
<BackButton />
</Box>
<Box px={{ base: 'md', md: 100 }}>
<Title
order={1}
ta="center"
c={colors["blue-button"]}
fw="bold"
lh={1.2}
>
Dinamika Penduduk
</Title>
<Text
ta="center"
fz={{ base: 'sm', md: 'md' }}
lh={1.5}
c="black"
>
Statistik kelahiran, kematian, dan migrasi penduduk
</Text>
</Box>
<Box px={{ base: "md", md: 100 }}>
<Paper p="xl" withBorder>
<Title order={3} mb="md" c={colors["blue-button"]}>
Statistik Dinamika Penduduk
</Title>
<SimpleGrid cols={{ base: 2, md: 4 }} spacing="md">
<Paper p="lg" bg="#6EDF9C">
<Text fz="sm" c="white" fw={500}>
Kelahiran
</Text>
<Text fz={36} fw={700} c="white" mt={10}>
{kelahiranCount}
</Text>
</Paper>
<Paper p="lg" bg="#EE5050">
<Text fz="sm" c="white" fw={500}>
Kematian
</Text>
<Text fz={36} fw={700} c="white" mt={10}>
{kematianCount}
</Text>
</Paper>
<Paper p="lg" bg="#5082EE">
<Text fz="sm" c="white" fw={500}>
Pindah Masuk
</Text>
<Text fz={36} fw={700} c="white" mt={10}>
{dinamika?.pindahMasuk || 0}
</Text>
</Paper>
<Paper p="lg" bg="#FF9F43">
<Text fz="sm" c="white" fw={500}>
Pindah Keluar
</Text>
<Text fz={36} fw={700} c="white" mt={10}>
{dinamika?.pindahKeluar || 0}
</Text>
</Paper>
</SimpleGrid>
<Box mt="xl" pt="xl" style={{ borderTop: `1px solid ${colors.grey['2']}` }}>
<Text fz="md" c="dimmed" fw={500} mb="md">
Ringkasan:
</Text>
<SimpleGrid cols={{ base: 1, sm: 2 }} spacing="md">
<Paper p="md" bg={colors.grey[1]}>
<Text fz="sm" c="dimmed">Pertumbuhan Alami</Text>
<Text fz={24} fw={700} c={kelahiranCount - kematianCount >= 0 ? '#6EDF9C' : '#EE5050'}>
{kelahiranCount - kematianCount > 0 ? '+' : ''}{kelahiranCount - kematianCount}
</Text>
<Text fz="xs" c="dimmed">(Kelahiran - Kematian)</Text>
</Paper>
<Paper p="md" bg={colors.grey[1]}>
<Text fz="sm" c="dimmed">Migrasi Bersih</Text>
<Text fz={24} fw={700} c={(dinamika?.pindahMasuk || 0) - (dinamika?.pindahKeluar || 0) >= 0 ? colors['blue-button'] : '#FF9F43'}>
{(dinamika?.pindahMasuk || 0) - (dinamika?.pindahKeluar || 0) > 0 ? '+' : ''}{(dinamika?.pindahMasuk || 0) - (dinamika?.pindahKeluar || 0)}
</Text>
<Text fz="xs" c="dimmed">(Pindah Masuk - Pindah Keluar)</Text>
</Paper>
</SimpleGrid>
</Box>
</Paper>
</Box>
</Stack>
);
}
export default Page;

View File

@@ -0,0 +1,170 @@
'use client'
import colors from '@/con/colors';
import { Stack, Box, Paper, Text, Title, Skeleton, Flex, ColorSwatch, Center } from '@mantine/core';
import { IconDatabaseOff } from '@tabler/icons-react';
import React from 'react';
import BackButton from '../../desa/layanan/_com/BackButto';
import { useProxy } from 'valtio/utils';
import distribusiAgama from '@/app/admin/(dashboard)/_state/kependudukan/distribusi-agama';
import { useShallowEffect } from '@mantine/hooks';
import { PieChart } from '@mantine/charts';
function Page() {
const state = useProxy(distribusiAgama)
useShallowEffect(() => {
state.findMany.load()
}, [])
const data = state.findMany.data || [];
if (state.findMany.loading) {
return (
<Stack py={10}>
<Skeleton h={500} />
</Stack>
)
}
if (!data || data.length === 0) {
return (
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"lg"}>
<Box px={{ base: 'md', md: 100 }}>
<BackButton />
</Box>
<Box px={{ base: 'md', md: 100 }}>
<Title
order={1}
ta="center"
c={colors["blue-button"]}
fw="bold"
lh={1.2}
>
Distribusi Agama
</Title>
<Text
ta="center"
fz={{ base: 'sm', md: 'md' }}
lh={1.5}
c="black"
>
Komposisi agama penduduk Desa Darmasaba
</Text>
</Box>
<Box px={{ base: "md", md: 100 }}>
<Paper p="xl" withBorder>
<Center py="xl">
<Stack align="center" gap="md">
<IconDatabaseOff size={80} color={colors.grey['2']} />
<Text ta="center" fz="lg" fw={500} c="dimmed">
Data Belum Tersedia
</Text>
<Text ta="center" fz="sm" c="dimmed" maw={400}>
Data distribusi agama untuk tahun ini belum diperbarui.
Silakan hubungi administrator desa untuk informasi lebih lanjut.
</Text>
</Stack>
</Center>
</Paper>
</Box>
</Stack>
)
}
const chartData = data.map(item => ({
name: item.agama,
value: item.jumlah,
color: getColorForAgama(item.agama),
}));
const total = data.reduce((sum, item) => sum + item.jumlah, 0);
return (
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"lg"}>
<Box px={{ base: 'md', md: 100 }}>
<BackButton />
</Box>
<Box px={{ base: 'md', md: 100 }}>
<Title
order={1}
ta="center"
c={colors["blue-button"]}
fw="bold"
lh={1.2}
>
Distribusi Agama
</Title>
<Text
ta="center"
fz={{ base: 'sm', md: 'md' }}
lh={1.5}
c="black"
>
Komposisi agama penduduk Desa Darmasaba
</Text>
</Box>
<Box px={{ base: "md", md: 100 }}>
<Paper p="xl" withBorder>
<Text fw={700} fz="lg" mb="md" c="black">
Statistik Distribusi Agama
</Text>
<Flex direction={{ base: 'column', md: 'row' }} gap="xl" align="center">
<Box style={{ flex: 1 }}>
<PieChart
data={chartData}
size={300}
labelsPosition="inside"
withLabels
withLabelsLine
/>
</Box>
<Stack style={{ flex: 1 }} gap="md">
{data.map((item) => (
<Flex key={item.id} align="center" gap="sm">
<ColorSwatch color={getColorForAgama(item.agama)} size={20} />
<Text fz="sm" c="black" style={{ flex: 1 }}>
{item.agama}
</Text>
<Text fz="sm" fw={700} c="black">
{item.jumlah.toLocaleString('id-ID')}
</Text>
<Text fz="xs" c="dimmed">
({((item.jumlah / total) * 100).toFixed(1)}%)
</Text>
</Flex>
))}
<Box mt="md" pt="md" style={{ borderTop: `1px solid ${colors.grey['2']}` }}>
<Flex justify="space-between" align="center">
<Text fw={700} c="black">Total</Text>
<Text fw={700} c="black">{total.toLocaleString('id-ID')}</Text>
</Flex>
</Box>
</Stack>
</Flex>
</Paper>
</Box>
</Stack>
);
}
function getColorForAgama(agama: string): string {
const colors: Record<string, string> = {
'HINDU': '#FF9F43',
'ISLAM': '#6EDF9C',
'KRISTEN': '#5082EE',
'KRISTEN_PROTESTAN': '#5082EE',
'KRISTEN_KATOLIK': '#4263D1',
'BUDDHA': '#FFD43B',
'KONGHUCU': '#EE5050',
'LAINNYA': '#868E96',
};
return colors[agama] || '#868E96';
}
export default Page;

View File

@@ -0,0 +1,150 @@
'use client'
import colors from '@/con/colors';
import { Stack, Box, Paper, Text, Title, Skeleton, Center } from '@mantine/core';
import { IconDatabaseOff } from '@tabler/icons-react';
import React from 'react';
import BackButton from '../../desa/layanan/_com/BackButto';
import { useProxy } from 'valtio/utils';
import distribusiUmur from '@/app/admin/(dashboard)/_state/kependudukan/distribusi-umur';
import { useShallowEffect } from '@mantine/hooks';
import { BarChart } from '@mantine/charts';
function Page() {
const state = useProxy(distribusiUmur)
useShallowEffect(() => {
state.findMany.load()
}, [])
const data = state.findMany.data || [];
if (state.findMany.loading) {
return (
<Stack py={10}>
<Skeleton h={500} />
</Stack>
)
}
if (!data || data.length === 0) {
return (
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"lg"}>
<Box px={{ base: 'md', md: 100 }}>
<BackButton />
</Box>
<Box px={{ base: 'md', md: 100 }}>
<Title
order={1}
ta="center"
c={colors["blue-button"]}
fw="bold"
lh={1.2}
>
Distribusi Umur
</Title>
<Text
ta="center"
fz={{ base: 'sm', md: 'md' }}
lh={1.5}
c="black"
>
Komposisi penduduk berdasarkan kelompok umur
</Text>
</Box>
<Box px={{ base: "md", md: 100 }}>
<Paper p="xl" withBorder>
<Center py="xl">
<Stack align="center" gap="md">
<IconDatabaseOff size={80} color={colors.grey['2']} />
<Text ta="center" fz="lg" fw={500} c="dimmed">
Data Belum Tersedia
</Text>
<Text ta="center" fz="sm" c="dimmed" maw={400}>
Data distribusi umur untuk tahun ini belum diperbarui.
Silakan hubungi administrator desa untuk informasi lebih lanjut.
</Text>
</Stack>
</Center>
</Paper>
</Box>
</Stack>
)
}
// Sort data by age range (extract the first number from rentangUmur)
const sortedData = [...data].sort((a, b) => {
const extractMinAge = (range: string) => {
const match = range.match(/^(\d+)/);
return match ? parseInt(match[1], 10) : 999;
};
return extractMinAge(a.rentangUmur) - extractMinAge(b.rentangUmur);
});
const chartData = sortedData.map(item => ({
umur: item.rentangUmur,
jumlah: item.jumlah,
}));
return (
<Stack pos={"relative"} bg={colors.Bg} py={"xl"} gap={"lg"}>
<Box px={{ base: 'md', md: 100 }}>
<BackButton />
</Box>
<Box px={{ base: 'md', md: 100 }}>
<Title
order={1}
ta="center"
c={colors["blue-button"]}
fw="bold"
lh={1.2}
>
Distribusi Umur
</Title>
<Text
ta="center"
fz={{ base: 'sm', md: 'md' }}
lh={1.5}
c="black"
>
Komposisi penduduk berdasarkan kelompok umur
</Text>
</Box>
<Box px={{ base: "md", md: 100 }}>
<Paper p="xl" withBorder>
<Text fw={700} fz="lg" mb="md" c="black">
Statistik Distribusi Umur
</Text>
<BarChart
h={400}
data={chartData}
dataKey="umur"
series={[{ name: 'jumlah', color: colors['blue-button'] }]}
tickLine="y"
yAxisProps={{
width: 80,
}}
xAxisProps={{
angle: -45,
textAnchor: 'end',
height: 100,
interval: 0,
style: {
fontSize: '12px',
}
}}
gridAxis="y"
withXAxis
withYAxis
/>
</Paper>
</Box>
</Stack>
);
}
export default Page;