From ea70f6f6e98c631debf1db0b8d639d1e6ccc3fb0 Mon Sep 17 00:00:00 2001 From: nico Date: Thu, 7 May 2026 11:34:59 +0800 Subject: [PATCH] feat(beasiswa): dynamic stats penerima & dana tersalurkan dari API + formatDana helper - Ganti static dataBeasiswa dengan data live dari ringkasanBeasiswaState.findStats - Tambah formatDana() untuk konversi angka ke Rb/Jt/M/T - Tambah tasks ke tasks-sample.csv Co-Authored-By: Claude Sonnet 4.6 --- .../(pages)/pendidikan/beasiswa-desa/page.tsx | 26 +++++++++++++++---- tasks-sample.csv | 6 +++++ 2 files changed, 27 insertions(+), 5 deletions(-) create mode 100644 tasks-sample.csv diff --git a/src/app/darmasaba/(pages)/pendidikan/beasiswa-desa/page.tsx b/src/app/darmasaba/(pages)/pendidikan/beasiswa-desa/page.tsx index de174975..f18f5c82 100644 --- a/src/app/darmasaba/(pages)/pendidikan/beasiswa-desa/page.tsx +++ b/src/app/darmasaba/(pages)/pendidikan/beasiswa-desa/page.tsx @@ -1,5 +1,6 @@ 'use client' import beasiswaDesaState from '@/app/admin/(dashboard)/_state/pendidikan/beasiswa-desa'; +import ringkasanBeasiswaState from '@/app/admin/(dashboard)/_state/pendidikan/ringkasan-beasiswa'; import colors from '@/con/colors'; import { Box, Button, Center, Divider, Group, Image, Modal, Pagination, Paper, Select, SimpleGrid, Skeleton, Stack, Stepper, Text, TextInput, Title } from '@mantine/core'; import { useDisclosure, useShallowEffect } from '@mantine/hooks'; @@ -9,15 +10,20 @@ import { useState } from 'react'; import { useProxy } from 'valtio/utils'; import BackButton from '../../desa/layanan/_com/BackButto'; -const dataBeasiswa = [ - { id: 1, nama: 'Penerima Beasiswa', jumlah: '250+', icon: IconUsers }, - { id: 2, nama: 'Peluang Kelulusan', jumlah: '90%', icon: IconSchool }, - { id: 3, nama: 'Dana Tersalurkan', jumlah: '1.5M', icon: IconCoin }, -]; +function formatDana(value: string | number): string { + const num = typeof value === 'string' ? parseFloat(value.replace(/[^0-9.-]/g, '')) : value; + if (isNaN(num)) return String(value); + if (num >= 1_000_000_000_000) return `${(num / 1_000_000_000_000).toFixed(num % 1_000_000_000_000 === 0 ? 0 : 1)}T`; + if (num >= 1_000_000_000) return `${(num / 1_000_000_000).toFixed(num % 1_000_000_000 === 0 ? 0 : 1)}M`; + if (num >= 1_000_000) return `${(num / 1_000_000).toFixed(num % 1_000_000 === 0 ? 0 : 1)}Jt`; + if (num >= 1_000) return `${(num / 1_000).toFixed(num % 1_000 === 0 ? 0 : 1)}Rb`; + return String(num); +} function Page() { const beasiswaDesa = useProxy(beasiswaDesaState.beasiswaPendaftar) const ungggulanDesa = useProxy(beasiswaDesaState.keunggulanProgram) + const statsBeasiswa = useProxy(ringkasanBeasiswaState.findStats) const [opened, { open, close }] = useDisclosure(false); const router = useTransitionRouter() const resetForm = () => { @@ -61,6 +67,16 @@ function Page() { load(page, 3, ""); }, [page]) + useShallowEffect(() => { + statsBeasiswa.load(); + }, []) + + const dataBeasiswa = [ + { id: 1, nama: 'Penerima Beasiswa', jumlah: statsBeasiswa.data?.jumlahPenerima?.toString() ?? '-', icon: IconUsers }, + { id: 2, nama: 'Peluang Kelulusan', jumlah: '90%', icon: IconSchool }, + { id: 3, nama: 'Dana Tersalurkan', jumlah: statsBeasiswa.data?.danaTersalurkan ? formatDana(statsBeasiswa.data.danaTersalurkan) : '-', icon: IconCoin }, + ]; + const handleSubmit = async () => { await beasiswaDesa.create.create(); resetForm(); diff --git a/tasks-sample.csv b/tasks-sample.csv new file mode 100644 index 00000000..aeeed7ff --- /dev/null +++ b/tasks-sample.csv @@ -0,0 +1,6 @@ +"title","description","kind","priority","startsAt","dueAt","estimateHours","assigneeEmail","tagNames" +"[Beasiswa] Import ringkasanBeasiswaState ke halaman publik beasiswa-desa","Import ringkasanBeasiswaState dari src/app/admin/(dashboard)/_state/pendidikan/ringkasan-beasiswa.ts ke src/app/darmasaba/(pages)/pendidikan/beasiswa-desa/page.tsx untuk mengakses data stats beasiswa secara dinamis.","TASK","MEDIUM","","","0.5","nicoarya20@gmail.com","" +"[Beasiswa] Tambah useProxy + useShallowEffect fetch stats beasiswa","Tambah useProxy(ringkasanBeasiswaState.findStats) dan useShallowEffect untuk memanggil statsBeasiswa.load() saat komponen mount di halaman publik beasiswa-desa/page.tsx.","TASK","MEDIUM","","","0.5","nicoarya20@gmail.com","" +"[Beasiswa] Ubah dataBeasiswa dari static ke dynamic (penerima & dana tersalurkan)","Pindahkan dataBeasiswa dari static array di luar komponen menjadi computed array di dalam Page(). Field 'Penerima Beasiswa' diambil dari statsBeasiswa.data.jumlahPenerima dan 'Dana Tersalurkan' dari statsBeasiswa.data.danaTersalurkan. Fallback '-' bila data belum tersedia.","TASK","MEDIUM","","","0.5","nicoarya20@gmail.com","" +"[Beasiswa] Buat fungsi formatDana untuk konversi angka ke format pendek Indonesia","Buat helper function formatDana(value: string | number) di halaman beasiswa-desa/page.tsx. Konversi: >= 1T → XT, >= 1M (miliar) → XM, >= 1Jt → XJt, >= 1Rb → XRb. Desimal 1 angka hanya muncul jika ada sisa (misal 1.5Jt), bilangan bulat tanpa desimal (misal 2Jt).","TASK","MEDIUM","","","0.5","nicoarya20@gmail.com","" +"[Beasiswa] Terapkan formatDana ke field Dana Tersalurkan di dataBeasiswa","Terapkan formatDana(statsBeasiswa.data.danaTersalurkan) pada entry Dana Tersalurkan di array dataBeasiswa agar nilai dari API otomatis diformat ke representasi singkat (Rb/Jt/M/T).","TASK","MEDIUM","","","0.5","nicoarya20@gmail.com","" \ No newline at end of file