Compare commits

...

4 Commits

Author SHA1 Message Date
239771a714 fix(apbdes): improve UI components and styling
- Update Apbdes component with better conditional rendering
- Enhance grafikRealisasi with improved percentage display
- Refine color coding and feedback messages
- Optimize layout and spacing for better UX

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-03-03 14:15:27 +08:00
03451195c8 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>
2026-03-03 13:58:52 +08:00
597af7e716 fix(apbdes) landing page: fix APBDes component not displaying on darmasaba page
- Restore Apbdes component with full functionality (fetch data, year selector, tables, charts)
- Fix realisasiTable.tsx: add missing items variable
- Fix grafikRealisasi.tsx: dynamic year title instead of hardcoded 2026
- Add eslint-disable comments for TypeScript any types
- Remove unused imports in paguTable.tsx
- Integrate PaguTable, RealisasiTable, GrafikRealisasi into main Apbdes component
- Component now fetches data from Valtio state and displays 3 tables + charts

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-03-03 12:51:53 +08:00
0a8a026b94 fix(apbdes): integrate new APBDes API with admin UI
- Update API schema to support name, deskripsi, and jumlah fields
- Enhance state management with additional form fields
- Add input fields for name, description, and total amount in create/edit pages
- Display description and total amount in detail page
- Fix APBDes component order in landing page
- Update TypeScript types and Prisma schema integration

API Changes:
- POST /api/landingpage/apbdes/create: Added optional fields (name, deskripsi, jumlah)
- PUT /api/landingpage/apbdes/🆔 Added optional fields (name, deskripsi, jumlah)

Admin UI Changes:
- create/page.tsx: Add TextInput for name, deskripsi, and jumlah
- edit/page.tsx: Add TextInput for name, deskripsi, and jumlah; improve reset functionality
- [id]/page.tsx: Display deskripsi and jumlah if available
- page.tsx: Minor formatting fix
- _state/apbdes.ts: Update Zod schema and default form with new fields

Landing Page:
- Move Apbdes component to top of stack for better visibility

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
2026-03-03 10:56:30 +08:00
15 changed files with 436 additions and 46 deletions

View File

@@ -19,6 +19,9 @@ const ApbdesItemSchema = z.object({
const ApbdesFormSchema = z.object({
tahun: z.number().int().min(2000, "Tahun tidak valid"),
name: z.string().optional(),
deskripsi: z.string().optional(),
jumlah: z.string().optional(),
imageId: z.string().min(1, "Gambar wajib diunggah"),
fileId: z.string().min(1, "File wajib diunggah"),
items: z.array(ApbdesItemSchema).min(1, "Minimal ada 1 item"),
@@ -27,6 +30,9 @@ const ApbdesFormSchema = z.object({
// --- Default Form ---
const defaultApbdesForm = {
tahun: new Date().getFullYear(),
name: "",
deskripsi: "",
jumlah: "",
imageId: "",
fileId: "",
items: [] as z.infer<typeof ApbdesItemSchema>[],
@@ -244,6 +250,9 @@ const apbdes = proxy({
this.id = data.id;
this.form = {
tahun: data.tahun || new Date().getFullYear(),
name: data.name || "",
deskripsi: data.deskripsi || "",
jumlah: data.jumlah || "",
imageId: data.imageId || "",
fileId: data.fileId || "",
items: (data.items || []).map((item: any) => ({

View File

@@ -79,6 +79,9 @@ function EditAPBDes() {
// Simpan data original untuk reset form
const [originalData, setOriginalData] = useState({
tahun: 0,
name: '',
deskripsi: '',
jumlah: '',
imageId: '',
fileId: '',
imageUrl: '',
@@ -103,6 +106,9 @@ function EditAPBDes() {
// Simpan data original untuk reset
setOriginalData({
tahun: data.tahun || new Date().getFullYear(),
name: data.name || '',
deskripsi: data.deskripsi || '',
jumlah: data.jumlah || '',
imageId: data.imageId || '',
fileId: data.fileId || '',
imageUrl: data.image?.link || '',
@@ -112,6 +118,9 @@ function EditAPBDes() {
// Set form dengan data lama (termasuk imageId dan fileId)
apbdesState.edit.form = {
tahun: data.tahun || new Date().getFullYear(),
name: data.name || '',
deskripsi: data.deskripsi || '',
jumlah: data.jumlah || '',
imageId: data.imageId || '',
fileId: data.fileId || '',
items: (data.items || []).map((item: any) => ({
@@ -238,9 +247,12 @@ function EditAPBDes() {
};
const handleReset = () => {
// Reset ke data original (tahun, imageId, fileId)
// Reset ke data original (tahun, name, deskripsi, jumlah, imageId, fileId)
apbdesState.edit.form = {
tahun: originalData.tahun,
name: originalData.name,
deskripsi: originalData.deskripsi,
jumlah: originalData.jumlah,
imageId: originalData.imageId,
fileId: originalData.fileId,
items: [...apbdesState.edit.form.items], // keep existing items
@@ -249,11 +261,11 @@ function EditAPBDes() {
// Reset preview ke data original
setPreviewImage(originalData.imageUrl || null);
setPreviewDoc(originalData.fileUrl || null);
// Reset file uploads
setImageFile(null);
setDocFile(null);
// Reset new item form
setNewItem({
kode: '',
@@ -263,7 +275,7 @@ function EditAPBDes() {
level: 1,
tipe: 'pendapatan',
});
toast.info('Form dikembalikan ke data awal');
};
@@ -288,6 +300,33 @@ function EditAPBDes() {
>
<Stack gap="md">
{/* Header Form */}
<TextInput
label="Nama APBDes"
placeholder="Contoh: APBDes Tahun 2025"
value={apbdesState.edit.form.name}
onChange={(e) =>
(apbdesState.edit.form.name = e.target.value)
}
description="Opsional - akan diisi otomatis jika kosong"
/>
<TextInput
label="Deskripsi"
placeholder="Deskripsi APBDes (opsional)"
value={apbdesState.edit.form.deskripsi}
onChange={(e) =>
(apbdesState.edit.form.deskripsi = e.target.value)
}
description="Opsional"
/>
<TextInput
label="Jumlah Total"
placeholder="Contoh: Rp 1.000.000.000"
value={apbdesState.edit.form.jumlah}
onChange={(e) =>
(apbdesState.edit.form.jumlah = e.target.value)
}
description="Opsional - total keseluruhan anggaran"
/>
<NumberInput
label="Tahun"
value={apbdesState.edit.form.tahun || new Date().getFullYear()}

View File

@@ -94,7 +94,7 @@ function DetailAPBDes() {
<Box>
<Text fz="lg" fw="bold">Nama APBDes</Text>
<Text fz="md" c="dimmed">
{data.name || '-'}
{data.name || `APBDes Tahun ${data.tahun}`}
</Text>
</Box>
@@ -105,6 +105,24 @@ function DetailAPBDes() {
</Text>
</Box>
{data.deskripsi && (
<Box>
<Text fz="lg" fw="bold">Deskripsi</Text>
<Text fz="md" c="dimmed">
{data.deskripsi}
</Text>
</Box>
)}
{data.jumlah && (
<Box>
<Text fz="lg" fw="bold">Jumlah Total</Text>
<Text fz="md" c="dimmed">
{data.jumlah}
</Text>
</Box>
)}
<Box>
<Text fz="lg" fw="bold">Gambar</Text>
{data.image?.link ? (

View File

@@ -117,9 +117,9 @@ function CreateAPBDes() {
toast.success("Berhasil menambahkan APBDes");
resetForm();
router.push("/admin/landing-page/apbdes");
} catch (error) {
} catch (error: any) {
console.error("Gagal submit:", error);
toast.error("Gagal menyimpan data");
toast.error(error?.message || "Gagal menyimpan data");
} finally {
setIsSubmitting(false);
}
@@ -334,6 +334,27 @@ function CreateAPBDes() {
</Stack>
{/* Form Header */}
<TextInput
label="Nama APBDes"
placeholder="Contoh: APBDes Tahun 2025"
value={stateAPBDes.create.form.name}
onChange={(e) => (stateAPBDes.create.form.name = e.target.value)}
description="Opsional - akan diisi otomatis jika kosong"
/>
<TextInput
label="Deskripsi"
placeholder="Deskripsi APBDes (opsional)"
value={stateAPBDes.create.form.deskripsi}
onChange={(e) => (stateAPBDes.create.form.deskripsi = e.target.value)}
description="Opsional"
/>
<TextInput
label="Jumlah Total"
placeholder="Contoh: Rp 1.000.000.000"
value={stateAPBDes.create.form.jumlah}
onChange={(e) => (stateAPBDes.create.form.jumlah = e.target.value)}
description="Opsional - total keseluruhan anggaran"
/>
<NumberInput
label="Tahun"
value={stateAPBDes.create.form.tahun || new Date().getFullYear()}

View File

@@ -45,7 +45,7 @@ function APBDes() {
function ListAPBDes({ search }: { search: string }) {
const listState = useProxy(apbdes);
const router = useRouter();
const [debouncedSearch] = useDebouncedValue(search, 1000);
const [debouncedSearch] = useDebouncedValue(search, 1000);
const { data, page, totalPages, loading, load } = listState.findMany;

View File

@@ -17,6 +17,9 @@ type APBDesItemInput = {
type FormCreate = {
tahun: number;
name?: string;
deskripsi?: string;
jumlah?: string;
imageId: string;
fileId: string;
items: APBDesItemInput[];
@@ -48,7 +51,9 @@ export default async function apbdesCreate(context: Context) {
const apbdes = await prisma.aPBDes.create({
data: {
tahun: body.tahun,
name: `APBDes Tahun ${body.tahun}`,
name: body.name || `APBDes Tahun ${body.tahun}`,
deskripsi: body.deskripsi,
jumlah: body.jumlah,
imageId: body.imageId,
fileId: body.fileId,
},

View File

@@ -47,7 +47,13 @@ export default async function apbdesFindMany(context: Context) {
include: {
image: true,
file: true,
items: true,
items: {
where: { isActive: true },
orderBy: { kode: "asc" },
include: {
parent: true,
},
},
},
}),
prisma.aPBDes.count({ where }),

View File

@@ -48,11 +48,14 @@ export default async function apbdesFindUnique(context: Context) {
include: {
items: {
where: { isActive: true },
orderBy: { kode: 'asc' }
orderBy: { kode: 'asc' },
include: {
parent: true, // Include parent item for hierarchy
},
},
image: true,
file: true
}
file: true,
},
});
if (!result || !result.isActive) {

View File

@@ -33,6 +33,9 @@ const APBDes = new Elysia({
.post("/create", apbdesCreate, {
body: t.Object({
tahun: t.Number(),
name: t.Optional(t.String()),
deskripsi: t.Optional(t.String()),
jumlah: t.Optional(t.String()),
imageId: t.String(),
fileId: t.String(),
items: t.Array(ApbdesItemSchema),
@@ -44,6 +47,9 @@ const APBDes = new Elysia({
params: t.Object({ id: t.String() }),
body: t.Object({
tahun: t.Number(),
name: t.Optional(t.String()),
deskripsi: t.Optional(t.String()),
jumlah: t.Optional(t.String()),
imageId: t.String(),
fileId: t.String(),
items: t.Array(ApbdesItemSchema),

View File

@@ -15,6 +15,9 @@ type APBDesItemInput = {
type FormUpdateBody = {
tahun: number;
name?: string;
deskripsi?: string;
jumlah?: string;
imageId: string;
fileId: string;
items: APBDesItemInput[];
@@ -79,6 +82,9 @@ export default async function apbdesUpdate(context: Context) {
where: { id },
data: {
tahun: body.tahun,
name: body.name || `APBDes Tahun ${body.tahun}`,
deskripsi: body.deskripsi,
jumlah: body.jumlah,
imageId: body.imageId,
fileId: body.fileId,
},

View File

@@ -2,12 +2,9 @@
/* eslint-disable react-hooks/exhaustive-deps */
'use client'
import apbdes from '@/app/admin/(dashboard)/_state/landing-page/apbdes'
import APBDesProgress from '@/app/darmasaba/(tambahan)/apbdes/lib/apbDesaProgress'
import { transformAPBDesData } from '@/app/darmasaba/(tambahan)/apbdes/lib/types'
import colors from '@/con/colors'
import {
ActionIcon,
BackgroundImage,
Box,
Button,
Center,
@@ -23,6 +20,9 @@ import { IconDownload } from '@tabler/icons-react'
import Link from 'next/link'
import { useEffect, useState } from 'react'
import { useProxy } from 'valtio/utils'
import PaguTable from './lib/paguTable'
import RealisasiTable from './lib/realisasiTable'
import GrafikRealisasi from './lib/grafikRealisasi'
function Apbdes() {
const state = useProxy(apbdes)
@@ -51,18 +51,17 @@ function Apbdes() {
const dataAPBDes = state.findMany.data || []
const years = Array.from(
new Set(
dataAPBDes
.map((item: any) => item?.tahun)
.filter((tahun): tahun is number => typeof tahun === 'number')
new Set(
dataAPBDes
.map((item: any) => item?.tahun)
.filter((tahun): tahun is number => typeof tahun === 'number')
)
)
)
.sort((a, b) => b - a)
.map(year => ({
value: year.toString(),
label: `Tahun ${year}`,
}))
.sort((a, b) => b - a)
.map(year => ({
value: year.toString(),
label: `Tahun ${year}`,
}))
useEffect(() => {
if (years.length > 0 && !selectedYear) {
@@ -71,7 +70,7 @@ function Apbdes() {
}, [years, selectedYear])
const currentApbdes = dataAPBDes.length > 0
? transformAPBDesData(dataAPBDes.find(item => item?.tahun?.toString() === selectedYear) || dataAPBDes[0])
? dataAPBDes.find((item: any) => item?.tahun?.toString() === selectedYear) || dataAPBDes[0]
: null
const data = (state.findMany.data || []).slice(0, 3)
@@ -131,18 +130,24 @@ function Apbdes() {
/>
</Box>
{/* Progress */}
{currentApbdes ? (
<APBDesProgress apbdesData={currentApbdes} />
) : (
{/* Tabel & Grafik - Hanya tampilkan jika ada data */}
{currentApbdes && currentApbdes.items?.length > 0 ? (
<Box px={{ base: 'md', md: 100 }}>
<SimpleGrid cols={{ base: 1, sm: 3 }}>
<PaguTable apbdesData={currentApbdes} />
<RealisasiTable apbdesData={currentApbdes} />
<GrafikRealisasi apbdesData={currentApbdes} />
</SimpleGrid>
</Box>
) : currentApbdes ? (
<Box px={{ base: 'md', md: 100 }} py="md">
<Text fz="sm" c="dimmed" ta="center" lh={1.5}>
Tidak ada data APBDes untuk tahun yang dipilih.
Tidak ada data item untuk tahun yang dipilih.
</Text>
</Box>
)}
) : null}
{/* GRID */}
{/* GRID - Card Preview */}
{loading ? (
<Center mx={{ base: 'md', md: 100 }} mih={200} pb="xl">
<Loader size="lg" color="blue" />
@@ -165,14 +170,18 @@ function Apbdes() {
spacing="lg"
pb="xl"
>
{data.map((v, k) => (
<BackgroundImage
{data.map((v: any, k: number) => (
<Box
key={k}
src={v.image?.link || ''}
h={360}
radius="xl"
pos="relative"
style={{ overflow: 'hidden' }}
style={{
backgroundImage: `url(${v.image?.link || ''})`,
backgroundSize: 'cover',
backgroundPosition: 'center',
borderRadius: 16,
height: 360,
overflow: 'hidden',
}}
>
<Box pos="absolute" inset={0} bg="rgba(0,0,0,0.45)" style={{ borderRadius: 16 }} />
@@ -185,7 +194,7 @@ function Apbdes() {
lh={1.35}
lineClamp={2}
>
{v.name}
{v.name || `APBDes Tahun ${v.tahun}`}
</Text>
<Text
@@ -196,7 +205,7 @@ function Apbdes() {
lh={1}
style={{ textShadow: '0 2px 8px rgba(0,0,0,0.6)' }}
>
{v.jumlah}
{v.jumlah || '-'}
</Text>
<Center>
@@ -212,7 +221,7 @@ function Apbdes() {
</ActionIcon>
</Center>
</Stack>
</BackgroundImage>
</Box>
))}
</SimpleGrid>
)}
@@ -220,4 +229,4 @@ function Apbdes() {
)
}
export default Apbdes
export default Apbdes

View File

@@ -0,0 +1,134 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { Paper, Title, Progress, Stack, Text, Group, Box } from '@mantine/core';
function Summary({ title, data }: any) {
if (!data || data.length === 0) return null;
const totalAnggaran = data.reduce((s: number, i: any) => s + i.anggaran, 0);
const totalRealisasi = data.reduce((s: number, i: any) => s + i.realisasi, 0);
const persen =
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 (
<Box>
<Group justify="space-between" mb="xs">
<Text fw={600} fz="md">{title}</Text>
<Text fw={700} fz="lg" c={getProgressColor(persen)}>
{persen.toFixed(2)}%
</Text>
</Group>
<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>
);
}
export default function GrafikRealisasi({ apbdesData }: any) {
const items = apbdesData.items || [];
const tahun = apbdesData.tahun || new Date().getFullYear();
const pendapatan = items.filter((i: any) => i.tipe === 'pendapatan');
const belanja = items.filter((i: any) => i.tipe === 'belanja');
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 (
<Paper withBorder p="md" radius="md">
<Title order={5} mb="md">
GRAFIK REALISASI APBDes {tahun}
</Title>
<Stack gap="lg">
<Summary title="💰 Pendapatan" data={pendapatan} />
<Summary title="💸 Belanja" data={belanja} />
<Summary title="📊 Pembiayaan" data={pembiayaan} />
</Stack>
{/* Summary Total Keseluruhan */}
<Box mb="lg" p="md" bg="gray.0">
<Group justify="space-between" mb="xs">
<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>
</Paper>
);
}

View File

@@ -0,0 +1,60 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { Paper, Table, Title } from '@mantine/core';
function Section({ title, data }: any) {
if (!data || data.length === 0) return null;
return (
<>
<Table.Tr>
<Table.Td colSpan={2}>
<strong>{title}</strong>
</Table.Td>
</Table.Tr>
{data.map((item: any) => (
<Table.Tr key={item.id}>
<Table.Td>
{item.kode} - {item.uraian}
</Table.Td>
<Table.Td ta="right">
Rp {item.anggaran.toLocaleString('id-ID')}
</Table.Td>
</Table.Tr>
))}
</>
);
}
export default function PaguTable({ apbdesData }: any) {
const items = apbdesData.items || [];
const title =
apbdesData.tahun
? `PAGU APBDes Tahun ${apbdesData.tahun}`
: 'PAGU APBDes';
const pendapatan = items.filter((i: any) => i.tipe === 'pendapatan');
const belanja = items.filter((i: any) => i.tipe === 'belanja');
const pembiayaan = items.filter((i: any) => i.tipe === 'pembiayaan');
return (
<Paper withBorder p="md" radius="md">
<Title order={5} mb="md">{title}</Title>
<Table>
<Table.Thead>
<Table.Tr>
<Table.Th>Uraian</Table.Th>
<Table.Th ta="right">Anggaran (Rp)</Table.Th>
</Table.Tr>
</Table.Thead>
<Table.Tbody>
<Section title="1) PENDAPATAN" data={pendapatan} />
<Section title="2) BELANJA" data={belanja} />
<Section title="3) PEMBIAYAAN" data={pembiayaan} />
</Table.Tbody>
</Table>
</Paper>
);
}

View File

@@ -0,0 +1,74 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { Paper, Table, Title, Badge } from '@mantine/core';
function Section({ title, data }: any) {
if (!data || data.length === 0) return null;
return (
<>
<Table.Tr>
<Table.Td colSpan={3}>
<strong>{title}</strong>
</Table.Td>
</Table.Tr>
{data.map((item: any) => (
<Table.Tr key={item.id}>
<Table.Td>
{item.kode} - {item.uraian}
</Table.Td>
<Table.Td ta="right">
Rp {item.realisasi.toLocaleString('id-ID')}
</Table.Td>
<Table.Td ta="center">
<Badge
color={
item.persentase >= 100
? 'teal'
: item.persentase >= 60
? 'yellow'
: 'red'
}
>
{item.persentase.toFixed(2)}%
</Badge>
</Table.Td>
</Table.Tr>
))}
</>
);
}
export default function RealisasiTable({ apbdesData }: any) {
const items = apbdesData.items || [];
const title =
apbdesData.tahun
? `REALISASI APBDes Tahun ${apbdesData.tahun}`
: 'REALISASI APBDes';
const pendapatan = items.filter((i: any) => i.tipe === 'pendapatan');
const belanja = items.filter((i: any) => i.tipe === 'belanja');
const pembiayaan = items.filter((i: any) => i.tipe === 'pembiayaan');
return (
<Paper withBorder p="md" radius="md">
<Title order={5} mb="md">{title}</Title>
<Table>
<Table.Thead>
<Table.Tr>
<Table.Th>Uraian</Table.Th>
<Table.Th ta="right">Realisasi (Rp)</Table.Th>
<Table.Th ta="center">%</Table.Th>
</Table.Tr>
</Table.Thead>
<Table.Tbody>
<Section title="1) PENDAPATAN" data={pendapatan} />
<Section title="2) BELANJA" data={belanja} />
<Section title="3) PEMBIAYAAN" data={pembiayaan} />
</Table.Tbody>
</Table>
</Paper>
);
}

View File

@@ -150,13 +150,13 @@ export default function Page() {
<Box id="page-root">
<Stack bg={colors.grey[1]} gap={0}>
<LandingPage />
<Apbdes />
<Penghargaan />
<Layanan />
<Potensi />
<DesaAntiKorupsi />
<Kepuasan />
<SDGS />
<Apbdes />
<Prestasi />
<ScrollToTopButton />
<NewsReaderLanding />