Fix QC Kak Inno Admin, Fix QC Keano UI User, Fix QC Pak jun tabel apbdes
This commit is contained in:
@@ -1,9 +1,10 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
'use client'
|
||||
import PendapatanAsliDesa from '@/app/admin/(dashboard)/_state/ekonomi/PADesa'
|
||||
import apbdes from '@/app/admin/(dashboard)/_state/landing-page/apbdes'
|
||||
import colors from '@/con/colors'
|
||||
import { ActionIcon, BackgroundImage, Box, Center, Container, Group, Loader, SimpleGrid, Stack, Text, Title } from '@mantine/core'
|
||||
import { ActionIcon, BackgroundImage, Box, Center, Container, Group, Loader, Paper, Progress, SimpleGrid, Stack, Table, Text, Title } from '@mantine/core'
|
||||
import { IconDownload } from '@tabler/icons-react'
|
||||
import { Link } from 'next-view-transitions'
|
||||
import { useEffect, useState } from 'react'
|
||||
@@ -12,13 +13,14 @@ import BackButton from '../../(pages)/desa/layanan/_com/BackButto'
|
||||
|
||||
function Page() {
|
||||
const state = useProxy(apbdes)
|
||||
const paDesaState = useProxy(PendapatanAsliDesa.ApbDesa)
|
||||
const [loading, setLoading] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
const loadData = async () => {
|
||||
try {
|
||||
setLoading(true)
|
||||
await state.findMany.load()
|
||||
await paDesaState.findMany.load()
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
} finally {
|
||||
@@ -28,7 +30,7 @@ function Page() {
|
||||
loadData()
|
||||
}, [])
|
||||
|
||||
const data = state.findMany.data || []
|
||||
const dataAPBDes = state.findMany.data || []
|
||||
|
||||
return (
|
||||
<Stack pos="relative" bg={colors.Bg} py="xl" gap={32}>
|
||||
@@ -52,7 +54,7 @@ function Page() {
|
||||
<Text fz="lg" c="dimmed">Sedang memuat data APBDes...</Text>
|
||||
</Stack>
|
||||
</Center>
|
||||
) : data.length === 0 ? (
|
||||
) : dataAPBDes.length === 0 ? (
|
||||
<Center mih={200}>
|
||||
<Stack align="center" gap="xs">
|
||||
<Text fz="xl" fw={600} c="dimmed">Belum ada data APBDes tersedia</Text>
|
||||
@@ -61,7 +63,7 @@ function Page() {
|
||||
</Center>
|
||||
) : (
|
||||
<SimpleGrid px={{ base: 'md', md: 100 }} cols={{ base: 1, sm: 2, md: 3 }} spacing="xl">
|
||||
{data.map((v: any, k: number) => (
|
||||
{dataAPBDes.map((v: any, k: number) => (
|
||||
<BackgroundImage key={k} src={v.image?.link || ''} h={360} radius="xl" pos="relative">
|
||||
<Box pos="absolute" inset={0} bg="rgba(0,0,0,0.45)" style={{ borderRadius: 27 }} />
|
||||
<Stack justify="space-between" h="100%" p="lg" pos="relative">
|
||||
@@ -82,7 +84,7 @@ function Page() {
|
||||
bg={colors['blue-button']}
|
||||
variant="filled"
|
||||
>
|
||||
<IconDownload size={20} color="white" />
|
||||
<IconDownload size={20} color="white" />
|
||||
</ActionIcon>
|
||||
</Group>
|
||||
</Stack>
|
||||
@@ -90,8 +92,200 @@ function Page() {
|
||||
))}
|
||||
</SimpleGrid>
|
||||
)}
|
||||
<DetailAPBDesaTable />
|
||||
<APBDesaProgress />
|
||||
</Stack>
|
||||
)
|
||||
}
|
||||
|
||||
function DetailAPBDesaTable() {
|
||||
// 🔹 Dummy data
|
||||
const data = {
|
||||
tahun: 2024,
|
||||
pendapatan: [
|
||||
{ id: 1, nama: 'Pendapatan Asli Desa', anggaran: 32000000, realisasi: 6500000 },
|
||||
{ id: 2, nama: 'Dana Desa', anggaran: 125000000, realisasi: 120000000 },
|
||||
{ id: 3, nama: 'Bagi Hasil Pajak dan Retribusi', anggaran: 10000000, realisasi: 9000000 },
|
||||
],
|
||||
belanja: [
|
||||
{ id: 1, nama: 'Belanja Pegawai', anggaran: 80000000, realisasi: 75000000 },
|
||||
{ id: 2, nama: 'Belanja Barang & Jasa', anggaran: 50000000, realisasi: 42000000 },
|
||||
],
|
||||
pembiayaan: [
|
||||
{ id: 1, nama: 'Penerimaan Pembiayaan', anggaran: 15000000, realisasi: 15000000 },
|
||||
{ id: 2, nama: 'Pengeluaran Pembiayaan', anggaran: 10000000, realisasi: 8000000 },
|
||||
],
|
||||
};
|
||||
|
||||
const formatRupiah = (value: number) =>
|
||||
new Intl.NumberFormat('id-ID', {
|
||||
minimumFractionDigits: 2,
|
||||
style: 'decimal',
|
||||
}).format(value);
|
||||
|
||||
// 🔹 Helper buat render satu kategori (Pendapatan, Belanja, Pembiayaan)
|
||||
const renderSection = (title: string, items: any[]) => {
|
||||
const totalAnggaran = items.reduce((sum, i) => sum + Number(i.anggaran || 0), 0);
|
||||
const totalRealisasi = items.reduce((sum, i) => sum + Number(i.realisasi || 0), 0);
|
||||
const totalSelisih = totalAnggaran - totalRealisasi;
|
||||
const totalPersen = totalAnggaran
|
||||
? (totalRealisasi / totalAnggaran) * 100
|
||||
: 0;
|
||||
|
||||
return (
|
||||
<Paper withBorder radius="md" shadow="xs" p="md">
|
||||
<Title order={5} mb="sm" c={colors['blue-button']}>
|
||||
{title.toUpperCase()}
|
||||
</Title>
|
||||
<Table withColumnBorders highlightOnHover>
|
||||
<Table.Thead bg={colors['blue-button']}>
|
||||
<Table.Tr>
|
||||
<Table.Th c="white">Uraian</Table.Th>
|
||||
<Table.Th c="white" ta="right">
|
||||
Anggaran (Rp)
|
||||
</Table.Th>
|
||||
<Table.Th c="white" ta="right">
|
||||
Realisasi (Rp)
|
||||
</Table.Th>
|
||||
<Table.Th c="white" ta="right">
|
||||
Lebih/(Kurang) (Rp)
|
||||
</Table.Th>
|
||||
<Table.Th c="white" ta="center">
|
||||
Persentase (%)
|
||||
</Table.Th>
|
||||
</Table.Tr>
|
||||
</Table.Thead>
|
||||
<Table.Tbody>
|
||||
{items.map((item, index) => {
|
||||
const selisih = Number(item.anggaran) - Number(item.realisasi);
|
||||
const persen = item.anggaran
|
||||
? (item.realisasi / item.anggaran) * 100
|
||||
: 0;
|
||||
return (
|
||||
<Table.Tr key={item.id || index}>
|
||||
<Table.Td>
|
||||
<strong>{`${index + 1}. ${item.nama}`}</strong>
|
||||
</Table.Td>
|
||||
<Table.Td ta="right">{formatRupiah(item.anggaran)}</Table.Td>
|
||||
<Table.Td ta="right">{formatRupiah(item.realisasi)}</Table.Td>
|
||||
<Table.Td ta="right">{formatRupiah(selisih)}</Table.Td>
|
||||
<Table.Td ta="center">{persen.toFixed(2)}</Table.Td>
|
||||
</Table.Tr>
|
||||
);
|
||||
})}
|
||||
</Table.Tbody>
|
||||
<Table.Tfoot bg="#f1f5fb">
|
||||
<Table.Tr>
|
||||
<Table.Th>Total {title}</Table.Th>
|
||||
<Table.Th ta="right">{formatRupiah(totalAnggaran)}</Table.Th>
|
||||
<Table.Th ta="right">{formatRupiah(totalRealisasi)}</Table.Th>
|
||||
<Table.Th ta="right">{formatRupiah(totalSelisih)}</Table.Th>
|
||||
<Table.Th ta="center">{totalPersen.toFixed(2)}</Table.Th>
|
||||
</Table.Tr>
|
||||
</Table.Tfoot>
|
||||
</Table>
|
||||
</Paper>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Box py="md" px={{ base: 'md', md: 100 }}>
|
||||
<Stack gap="xl">
|
||||
<Title order={4} c={colors['blue-button']}>
|
||||
APB Desa Tahun {data.tahun}
|
||||
</Title>
|
||||
|
||||
{renderSection('Pendapatan', data.pendapatan)}
|
||||
{renderSection('Belanja', data.belanja)}
|
||||
{renderSection('Pembiayaan', data.pembiayaan)}
|
||||
</Stack>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
function APBDesaProgress() {
|
||||
const data = {
|
||||
tahun: 2024,
|
||||
pendapatan: [
|
||||
{ id: 1, nama: 'Pendapatan Asli Desa', anggaran: 32000000, realisasi: 6500000 },
|
||||
{ id: 2, nama: 'Dana Desa', anggaran: 125000000, realisasi: 120000000 },
|
||||
{ id: 3, nama: 'Bagi Hasil Pajak dan Retribusi', anggaran: 10000000, realisasi: 9000000 },
|
||||
],
|
||||
belanja: [
|
||||
{ id: 1, nama: 'Belanja Pegawai', anggaran: 80000000, realisasi: 75000000 },
|
||||
{ id: 2, nama: 'Belanja Barang & Jasa', anggaran: 50000000, realisasi: 42000000 },
|
||||
],
|
||||
pembiayaan: [
|
||||
{ id: 1, nama: 'Penerimaan Pembiayaan', anggaran: 15000000, realisasi: 15000000 },
|
||||
{ id: 2, nama: 'Pengeluaran Pembiayaan', anggaran: 10000000, realisasi: 8000000 },
|
||||
],
|
||||
};
|
||||
|
||||
const formatRupiah = (value: number) =>
|
||||
new Intl.NumberFormat('id-ID', {
|
||||
style: 'currency',
|
||||
currency: 'IDR',
|
||||
minimumFractionDigits: 2,
|
||||
}).format(value);
|
||||
|
||||
const calcProgress = (items: any[]) => {
|
||||
const anggaran = items.reduce((sum, i) => sum + i.anggaran, 0);
|
||||
const realisasi = items.reduce((sum, i) => sum + i.realisasi, 0);
|
||||
const persen = anggaran ? (realisasi / anggaran) * 100 : 0;
|
||||
return { anggaran, realisasi, persen };
|
||||
};
|
||||
|
||||
const pendapatan = calcProgress(data.pendapatan);
|
||||
const belanja = calcProgress(data.belanja);
|
||||
const pembiayaan = calcProgress(data.pembiayaan);
|
||||
|
||||
const renderProgress = (label: string, dataset: any) => (
|
||||
<Box>
|
||||
<Text fw={600}>{label}</Text>
|
||||
<Text fw={700} mb="xs">
|
||||
{formatRupiah(dataset.realisasi)} | {formatRupiah(dataset.anggaran)}
|
||||
</Text>
|
||||
<Progress
|
||||
value={dataset.persen}
|
||||
size="xl"
|
||||
radius="xl"
|
||||
striped={false}
|
||||
styles={{
|
||||
root: { backgroundColor: '#d7e3f1' },
|
||||
section: {
|
||||
backgroundColor: colors['blue-button'],
|
||||
position: 'relative',
|
||||
'&::after': {
|
||||
content: `'${dataset.persen.toFixed(2)}%'`,
|
||||
position: 'absolute',
|
||||
right: 10,
|
||||
top: '50%',
|
||||
transform: 'translateY(-50%)',
|
||||
color: 'white',
|
||||
fontWeight: 700,
|
||||
fontSize: '0.8rem',
|
||||
}
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
|
||||
return (
|
||||
<Paper mx={{ base: 'md', md: 100 }} p="xl" radius="md" shadow="sm" withBorder bg={colors['white-1']}>
|
||||
<Stack gap="lg">
|
||||
<Title order={4} c={colors['blue-button']}>
|
||||
Grafik APB Desa Tahun {data.tahun}
|
||||
</Title>
|
||||
|
||||
{renderProgress('Pendapatan Desa', pendapatan)}
|
||||
{renderProgress('Belanja Desa', belanja)}
|
||||
{renderProgress('Pembiayaan Desa', pembiayaan)}
|
||||
</Stack>
|
||||
</Paper>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
export default Page
|
||||
|
||||
Reference in New Issue
Block a user