feat(apbdes) grafik: add detailed percentage comparison
- Display percentage value prominently next to each category title - Add formatted currency (Rupiah) for better readability - Color-coded progress bars based on achievement level: * Teal: ≥100% (target tercapai) * Blue: ≥80% (baik) * Yellow: ≥60% (cukup) * Red: <60% (perlu perhatian) - Add contextual feedback messages based on percentage: * ✓ Achievement message for 100% * ⚡ Positive message for 80-99% * ⚠️ Warning messages for <80% - Add TOTAL KESELURUHAN summary section at the top - Add emoji icons for better visual distinction (💰 💸 📊) - Animated progress bars for <100% achievement Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
This commit is contained in:
@@ -132,11 +132,11 @@ function Apbdes() {
|
|||||||
|
|
||||||
{/* Tabel & Grafik - Hanya tampilkan jika ada data */}
|
{/* Tabel & Grafik - Hanya tampilkan jika ada data */}
|
||||||
{currentApbdes && currentApbdes.items?.length > 0 ? (
|
{currentApbdes && currentApbdes.items?.length > 0 ? (
|
||||||
<Stack gap="xl">
|
<SimpleGrid cols={{ base: 1, sm: 3 }}>
|
||||||
<PaguTable apbdesData={currentApbdes} />
|
<PaguTable apbdesData={currentApbdes} />
|
||||||
<RealisasiTable apbdesData={currentApbdes} />
|
<RealisasiTable apbdesData={currentApbdes} />
|
||||||
<GrafikRealisasi apbdesData={currentApbdes} />
|
<GrafikRealisasi apbdesData={currentApbdes} />
|
||||||
</Stack>
|
</SimpleGrid>
|
||||||
) : currentApbdes ? (
|
) : currentApbdes ? (
|
||||||
<Box px={{ base: 'md', md: 100 }} py="md">
|
<Box px={{ base: 'md', md: 100 }} py="md">
|
||||||
<Text fz="sm" c="dimmed" ta="center" lh={1.5}>
|
<Text fz="sm" c="dimmed" ta="center" lh={1.5}>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
import { Paper, Title, Progress, Stack, Text } from '@mantine/core';
|
import { Paper, Title, Progress, Stack, Text, Group, Box } from '@mantine/core';
|
||||||
|
|
||||||
function Summary({ title, data }: any) {
|
function Summary({ title, data }: any) {
|
||||||
if (!data || data.length === 0) return null;
|
if (!data || data.length === 0) return null;
|
||||||
@@ -10,15 +10,70 @@ function Summary({ title, data }: any) {
|
|||||||
const persen =
|
const persen =
|
||||||
totalAnggaran > 0 ? (totalRealisasi / totalAnggaran) * 100 : 0;
|
totalAnggaran > 0 ? (totalRealisasi / totalAnggaran) * 100 : 0;
|
||||||
|
|
||||||
|
// Format angka ke dalam format Rupiah
|
||||||
|
const formatRupiah = (angka: number) => {
|
||||||
|
return new Intl.NumberFormat('id-ID', {
|
||||||
|
style: 'currency',
|
||||||
|
currency: 'IDR',
|
||||||
|
minimumFractionDigits: 0,
|
||||||
|
maximumFractionDigits: 0,
|
||||||
|
}).format(angka);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Tentukan warna berdasarkan persentase
|
||||||
|
const getProgressColor = (persen: number) => {
|
||||||
|
if (persen >= 100) return 'teal';
|
||||||
|
if (persen >= 80) return 'blue';
|
||||||
|
if (persen >= 60) return 'yellow';
|
||||||
|
return 'red';
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack>
|
<Box>
|
||||||
<Text fw={600}>{title}</Text>
|
<Group justify="space-between" mb="xs">
|
||||||
<Text>
|
<Text fw={600} fz="md">{title}</Text>
|
||||||
Rp {totalRealisasi.toLocaleString('id-ID')} /
|
<Text fw={700} fz="lg" c={getProgressColor(persen)}>
|
||||||
Rp {totalAnggaran.toLocaleString('id-ID')}
|
{persen.toFixed(2)}%
|
||||||
</Text>
|
</Text>
|
||||||
<Progress value={persen} size="lg" radius="xl" />
|
</Group>
|
||||||
</Stack>
|
|
||||||
|
<Text fz="sm" c="dimmed" mb="xs">
|
||||||
|
Realisasi: {formatRupiah(totalRealisasi)} / Anggaran: {formatRupiah(totalAnggaran)}
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<Progress
|
||||||
|
value={persen}
|
||||||
|
size="xl"
|
||||||
|
radius="xl"
|
||||||
|
color={getProgressColor(persen)}
|
||||||
|
striped={persen < 100}
|
||||||
|
animated={persen < 100}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{persen >= 100 && (
|
||||||
|
<Text fz="xs" c="teal" mt="xs" fw={500}>
|
||||||
|
✓ Realisasi mencapai 100% dari anggaran
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{persen < 100 && persen >= 80 && (
|
||||||
|
<Text fz="xs" c="blue" mt="xs" fw={500}>
|
||||||
|
⚡ Realisasi baik, mendekati target
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{persen < 80 && persen >= 60 && (
|
||||||
|
<Text fz="xs" c="yellow" mt="xs" fw={500}>
|
||||||
|
⚠️ Realisasi cukup, perlu ditingkatkan
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{persen < 60 && (
|
||||||
|
<Text fz="xs" c="red" mt="xs" fw={500}>
|
||||||
|
⚠️ Realisasi rendah, perlu perhatian khusus
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -30,16 +85,49 @@ export default function GrafikRealisasi({ apbdesData }: any) {
|
|||||||
const belanja = items.filter((i: any) => i.tipe === 'belanja');
|
const belanja = items.filter((i: any) => i.tipe === 'belanja');
|
||||||
const pembiayaan = items.filter((i: any) => i.tipe === 'pembiayaan');
|
const pembiayaan = items.filter((i: any) => i.tipe === 'pembiayaan');
|
||||||
|
|
||||||
|
// Hitung total keseluruhan
|
||||||
|
const totalAnggaranSemua = items.reduce((s: number, i: any) => s + i.anggaran, 0);
|
||||||
|
const totalRealisasiSemua = items.reduce((s: number, i: any) => s + i.realisasi, 0);
|
||||||
|
const persenSemua = totalAnggaranSemua > 0 ? (totalRealisasiSemua / totalAnggaranSemua) * 100 : 0;
|
||||||
|
|
||||||
|
const formatRupiah = (angka: number) => {
|
||||||
|
return new Intl.NumberFormat('id-ID', {
|
||||||
|
style: 'currency',
|
||||||
|
currency: 'IDR',
|
||||||
|
minimumFractionDigits: 0,
|
||||||
|
maximumFractionDigits: 0,
|
||||||
|
}).format(angka);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Paper withBorder p="md" radius="md">
|
<Paper withBorder p="md" radius="md">
|
||||||
<Title order={5} mb="md">
|
<Title order={5} mb="md">
|
||||||
GRAFIK REALISASI APBDes {tahun}
|
GRAFIK REALISASI APBDes {tahun}
|
||||||
</Title>
|
</Title>
|
||||||
|
|
||||||
<Stack>
|
{/* Summary Total Keseluruhan */}
|
||||||
<Summary title="Pendapatan" data={pendapatan} />
|
<Box mb="lg" p="md" bg="gray.0">
|
||||||
<Summary title="Belanja" data={belanja} />
|
<Group justify="space-between" mb="xs">
|
||||||
<Summary title="Pembiayaan" data={pembiayaan} />
|
<Text fw={700} fz="lg">TOTAL KESELURUHAN</Text>
|
||||||
|
<Text fw={700} fz="xl" c={persenSemua >= 100 ? 'teal' : persenSemua >= 80 ? 'blue' : 'red'}>
|
||||||
|
{persenSemua.toFixed(2)}%
|
||||||
|
</Text>
|
||||||
|
</Group>
|
||||||
|
<Text fz="sm" c="dimmed" mb="xs">
|
||||||
|
{formatRupiah(totalRealisasiSemua)} / {formatRupiah(totalAnggaranSemua)}
|
||||||
|
</Text>
|
||||||
|
<Progress
|
||||||
|
value={persenSemua}
|
||||||
|
size="lg"
|
||||||
|
radius="xl"
|
||||||
|
color={persenSemua >= 100 ? 'teal' : persenSemua >= 80 ? 'blue' : 'red'}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Stack gap="lg">
|
||||||
|
<Summary title="💰 Pendapatan" data={pendapatan} />
|
||||||
|
<Summary title="💸 Belanja" data={belanja} />
|
||||||
|
<Summary title="📊 Pembiayaan" data={pembiayaan} />
|
||||||
</Stack>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user