From 8fc52574583f62358916e85756487f2192ea268d Mon Sep 17 00:00:00 2001 From: oxel Date: Sat, 18 Apr 2026 11:12:38 +0700 Subject: [PATCH] add fix bug --- .../kelola-data/CetakInstanModal.tsx | 58 ++++++++++++------- .../kelola-data/[id]/CetakPDFButton.tsx | 33 ++++++++++- .../kelola-data/[id]/HasilStuntingTable.tsx | 12 +++- .../kelola-data/[id]/PerkembanganChart.tsx | 46 ++++++++++++++- app/dashboard/kelola-data/[id]/page.tsx | 2 +- .../perkembangan/ExportPDFButton.tsx | 33 ++++++++++- .../perkembangan/GrowthChart.tsx | 53 ++++++++++++++++- .../perkembangan/StuntingTable.tsx | 12 +++- app/user-dashboard/perkembangan/page.tsx | 2 +- 9 files changed, 214 insertions(+), 37 deletions(-) diff --git a/app/dashboard/kelola-data/CetakInstanModal.tsx b/app/dashboard/kelola-data/CetakInstanModal.tsx index 49c7a3a..13253cf 100644 --- a/app/dashboard/kelola-data/CetakInstanModal.tsx +++ b/app/dashboard/kelola-data/CetakInstanModal.tsx @@ -16,6 +16,7 @@ interface HasilItem { id_balita: number tinggi_badan: number | null berat_badan: number | null + z_score: number | null status_stunting: boolean | null pesan_ai: string | null tanggal_upload: string | null @@ -72,7 +73,12 @@ function build5MonthData(allData: HasilItem[], rowDate: Date) { return id.getFullYear() === slot.year && id.getMonth() + 1 === slot.month && id <= rowDate }) const label = new Date(slot.year, slot.month - 1, 1).toLocaleDateString('id-ID', { month: 'short', year: '2-digit' }) - return { label, tinggi: match?.tinggi_badan ?? null, berat: match?.berat_badan ?? null } + return { + label, + tinggi: match?.tinggi_badan ?? null, + berat: match?.berat_badan ?? null, + zscore: match?.z_score ?? null, + } }) } @@ -185,7 +191,7 @@ export function CetakInstanModal() { // --- Update template and wait for render --- setActivePrintData({ pengguna: b, row: rowForMonth, allHasil: balitaHasil }) // Give React and Recharts some time to finish rendering the hidden template - await new Promise(r => setTimeout(r, 600)) // 600ms buffer for Recharts animations/stable DOM + await new Promise(r => setTimeout(r, 1000)) // 1s buffer for stable DOM & Recharts if (!templateRef.current) continue @@ -197,14 +203,21 @@ export function CetakInstanModal() { logging: false, }) - const imgData = canvas.toDataURL('image/png') + const imgData = canvas.toDataURL('image/jpeg', 0.95) + + // Safety check: ensure imgData is a valid Data URI + if (!imgData || !imgData.startsWith('data:image/')) { + console.error('Invalid image data generated for', b.nama_anak) + continue + } + const pdf = new jsPDF('p', 'mm', 'a4') const pageW = pdf.internal.pageSize.getWidth() const pageH = pdf.internal.pageSize.getHeight() const imgH = (canvas.height * pageW) / canvas.width if (imgH <= pageH) { - pdf.addImage(imgData, 'PNG', 0, 0, pageW, imgH) + pdf.addImage(imgData, 'JPEG', 0, 0, pageW, imgH) } else { let yPos = 0 const sliceH = canvas.width * (pageH / pageW) @@ -215,7 +228,7 @@ export function CetakInstanModal() { const ctx = sliceCanvas.getContext('2d')! ctx.drawImage(canvas, 0, -yPos) if (yPos > 0) pdf.addPage() - pdf.addImage(sliceCanvas.toDataURL('image/png'), 'PNG', 0, 0, pageW, pageH) + pdf.addImage(sliceCanvas.toDataURL('image/jpeg', 0.95), 'JPEG', 0, 0, pageW, pageH) yPos += sliceH } } @@ -242,7 +255,7 @@ export function CetakInstanModal() { clearInterval(timer) setStep('done') - await showSwal.success('Selesai!', `Berhasil mencetak ${progress.total} file PDF.`) + await showSwal.success('Selesai!', `Berhasil mencetak ${targets.length} file PDF.`) handleClose() } catch (err: any) { clearInterval(timer) @@ -410,7 +423,7 @@ export function CetakInstanModal() { {/* ─── HIDDEN PDF TEMPLATE (Rich HTML) ─── */} {activePrintData && ( -
+
📏 Tinggi Badan (cm)
- - - - - - - +
@@ -480,20 +487,26 @@ export function CetakInstanModal() {
⚖️ Berat Badan (kg)
- - - - - - - +
+ {/* Z-Score PDF Chart */} +
+
📈 Z-Score (SD)
+ + + + + + + + +
{/* Table */} @@ -502,7 +515,7 @@ export function CetakInstanModal() { - {['Tinggi', 'Berat', 'Status', 'Posyandu', 'Tgl Upload'].map(h => ( + {['Tinggi', 'Berat', 'Z-Score', 'Status', 'Posyandu', 'Tgl Upload'].map(h => ( ))} @@ -511,6 +524,7 @@ export function CetakInstanModal() { +
{h}
{activePrintData.row.tinggi_badan} cm {activePrintData.row.berat_badan} kg{activePrintData.row.z_score} SD {isStunting ? 'Stunting' : 'Normal'} diff --git a/app/dashboard/kelola-data/[id]/CetakPDFButton.tsx b/app/dashboard/kelola-data/[id]/CetakPDFButton.tsx index 434b73f..b54621c 100644 --- a/app/dashboard/kelola-data/[id]/CetakPDFButton.tsx +++ b/app/dashboard/kelola-data/[id]/CetakPDFButton.tsx @@ -13,6 +13,7 @@ interface HasilItem { id: number tinggi_badan: number | null berat_badan: number | null + z_score: number | null status_stunting: boolean | null pesan_ai: string | null tanggal_upload: string | null @@ -68,6 +69,7 @@ function build5MonthData(allData: HasilItem[], rowDate: Date) { label, tinggi: match?.tinggi_badan ?? null, berat: match?.berat_badan ?? null, + zscore: match?.z_score ?? null, } }) } @@ -210,6 +212,7 @@ export function CetakPDFButton({ row, allData, pengguna }: Props) { @@ -231,11 +234,34 @@ export function CetakPDFButton({ row, allData, pengguna }: Props) { + {/* Z-Score PDF Chart */} +
+
📈 Z-Score (SD)
+ + + + + + + + + + + + + + +
{/* ── Data Pemeriksaan ── */} @@ -246,15 +272,16 @@ export function CetakPDFButton({ row, allData, pengguna }: Props) { - {['Tinggi Badan', 'Berat Badan', 'Status Stunting', 'Posyandu', 'Tgl Pemeriksaan'].map(h => ( + {['Tinggi', 'Berat', 'Z-Score', 'Status Stunting', 'Posyandu', 'Tgl Pemeriksaan'].map(h => ( ))} - - + + +
{h}
{row.tinggi_badan ?? '-'} {row.tinggi_badan ? 'cm' : ''}{row.berat_badan ?? '-'} {row.berat_badan ? 'kg' : ''}{row.tinggi_badan ?? '-'} cm{row.berat_badan ?? '-'} kg{row.z_score ?? '-'} SD {/* Header */} -
+
# Tinggi Berat + Z-Score Status Pesan AI Posyandu @@ -112,7 +114,7 @@ export function HasilStuntingTable({ data, pengguna }: Props) { return (
{/* Index */}
@@ -131,6 +133,12 @@ export function HasilStuntingTable({ data, pengguna }: Props) { {row.berat_badan && kg}
+ {/* Z-Score */} +
+ {row.z_score ?? '-'} + {row.z_score !== null && SD} +
+ {/* Status Stunting */}
{row.status_stunting === null ? ( diff --git a/app/dashboard/kelola-data/[id]/PerkembanganChart.tsx b/app/dashboard/kelola-data/[id]/PerkembanganChart.tsx index bbd9d52..5c4d814 100644 --- a/app/dashboard/kelola-data/[id]/PerkembanganChart.tsx +++ b/app/dashboard/kelola-data/[id]/PerkembanganChart.tsx @@ -6,11 +6,12 @@ import { XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, } from 'recharts' -import { Ruler, Weight, ChevronDown } from 'lucide-react' +import { Ruler, Weight, ChevronDown, Activity } from 'lucide-react' interface HasilItem { tinggi_badan: number | null berat_badan: number | null + z_score: number | null tanggal_upload: string | null } @@ -66,6 +67,7 @@ export function PerkembanganChart({ data }: Props) { _date: new Date(d.tanggal_upload!).getTime(), tinggi: d.tinggi_badan, berat: d.berat_badan, + zscore: d.z_score, })) .sort((a, b) => a._date - b._date) }, [data, selectedYear]) @@ -189,6 +191,48 @@ export function PerkembanganChart({ data }: Props) { )}
+ {/* Z-Score Chart */} +
+
+
+ +
+
+

Z-Score (SD)

+

Standar Deviasi Pertumbuhan

+
+
+ {!hasData ? ( +
+ Tidak ada data untuk tahun {selectedYear} +
+ ) : ( + + + + + + + + + + + + } /> + + + + )} +
+
) diff --git a/app/dashboard/kelola-data/[id]/page.tsx b/app/dashboard/kelola-data/[id]/page.tsx index 0e9424c..3b62225 100644 --- a/app/dashboard/kelola-data/[id]/page.tsx +++ b/app/dashboard/kelola-data/[id]/page.tsx @@ -63,7 +63,7 @@ export default async function DetailPenggunaKelolaPage({ params }: Props) { // Fetch hasil pengukuran stunting milik balita ini const { data: hasilData } = await supabase .from('hasil_stunting_balita') - .select('id, tinggi_badan, berat_badan, status_stunting, pesan_ai, tanggal_upload, nama_posyandu') + .select('id, tinggi_badan, berat_badan, z_score, status_stunting, pesan_ai, tanggal_upload, nama_posyandu') .eq('id_balita', pengguna.id) .order('tanggal_upload', { ascending: false }) diff --git a/app/user-dashboard/perkembangan/ExportPDFButton.tsx b/app/user-dashboard/perkembangan/ExportPDFButton.tsx index 3627b5d..c346dcb 100644 --- a/app/user-dashboard/perkembangan/ExportPDFButton.tsx +++ b/app/user-dashboard/perkembangan/ExportPDFButton.tsx @@ -13,6 +13,7 @@ interface HasilItem { id: number tinggi_badan: number | null berat_badan: number | null + z_score: number | null status_stunting: boolean | null pesan_ai: string | null tanggal_upload: string | null @@ -67,6 +68,7 @@ function build5MonthData(allData: HasilItem[], rowDate: Date) { label, tinggi: match?.tinggi_badan ?? null, berat: match?.berat_badan ?? null, + zscore: match?.z_score ?? null, } }) } @@ -208,6 +210,7 @@ export function ExportPDFButton({ row, allData, pengguna }: Props) { @@ -229,11 +232,34 @@ export function ExportPDFButton({ row, allData, pengguna }: Props) {
+ {/* Z-Score PDF Chart */} +
+
📈 Z-Score (SD)
+ + + + + + + + + + + + + + +
{/* ── Data Pemeriksaan ── */} @@ -244,15 +270,16 @@ export function ExportPDFButton({ row, allData, pengguna }: Props) { - {['Tinggi Badan', 'Berat Badan', 'Status Stunting', 'Posyandu', 'Tgl Pemeriksaan'].map(h => ( + {['Tinggi', 'Berat', 'Z-Score', 'Status Stunting', 'Posyandu', 'Tgl Pemeriksaan'].map(h => ( ))} - - + + +
{h}
{row.tinggi_badan ?? '-'} {row.tinggi_badan ? 'cm' : ''}{row.berat_badan ?? '-'} {row.berat_badan ? 'kg' : ''}{row.tinggi_badan ?? '-'} cm{row.berat_badan ?? '-'} kg{row.z_score ?? '-'} SD a._date - b._date) }, [data, selectedYear]) @@ -89,7 +91,7 @@ export function GrowthChart({ data }: Props) { Grafik Pertumbuhan Anak

- Statistik Tinggi & Berat {selectedYear} + Statistik Tinggi, Berat & Z-Score {selectedYear}

@@ -156,6 +158,7 @@ export function GrowthChart({ data }: Props) { )} + {/* Weight Chart */}
@@ -200,6 +203,52 @@ export function GrowthChart({ data }: Props) { )}
+ + {/* Z-Score Chart */} +
+
+
+
+ +
+
+

Z-Score (SD)

+

Standar Deviasi Pertumbuhan

+
+
+
+ + {!hasData ? ( +
+ +

Data belum tersedia

+
+ ) : ( + + + + + + + + + + + + } /> + + + + )} +
) diff --git a/app/user-dashboard/perkembangan/StuntingTable.tsx b/app/user-dashboard/perkembangan/StuntingTable.tsx index 43d48a0..ade1f8d 100644 --- a/app/user-dashboard/perkembangan/StuntingTable.tsx +++ b/app/user-dashboard/perkembangan/StuntingTable.tsx @@ -8,6 +8,7 @@ interface HasilStunting { id: number tinggi_badan: number | null berat_badan: number | null + z_score: number | null status_stunting: boolean | null pesan_ai: string | null tanggal_upload: string | null @@ -88,9 +89,10 @@ export function StuntingTable({ data, pengguna }: Props) {
{/* Header */} -
+
Tinggi Berat + Z-Score Status Pesan / Rekomendasi Posyandu @@ -111,7 +113,7 @@ export function StuntingTable({ data, pengguna }: Props) { return (
{/* Tinggi Badan */}
@@ -125,6 +127,12 @@ export function StuntingTable({ data, pengguna }: Props) { {row.berat_badan && kg}
+ {/* Z-Score */} +
+ {row.z_score ?? '-'} + {row.z_score !== null && SD} +
+ {/* Status Stunting */}
{row.status_stunting === null ? ( diff --git a/app/user-dashboard/perkembangan/page.tsx b/app/user-dashboard/perkembangan/page.tsx index 727e1a8..08319cf 100644 --- a/app/user-dashboard/perkembangan/page.tsx +++ b/app/user-dashboard/perkembangan/page.tsx @@ -62,7 +62,7 @@ export default async function UserPerkembanganPage() { // Fetch measurement history const { data: hasilData } = await supabase .from('hasil_stunting_balita') - .select('id, tinggi_badan, berat_badan, status_stunting, pesan_ai, tanggal_upload, nama_posyandu') + .select('id, tinggi_badan, berat_badan, z_score, status_stunting, pesan_ai, tanggal_upload, nama_posyandu') .eq('id_balita', pengguna.id) .order('tanggal_upload', { ascending: false })