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