'use client'
import { useState, useMemo } from 'react'
import {
BarChart,
Bar,
XAxis,
YAxis,
CartesianGrid,
Tooltip,
Legend,
ResponsiveContainer,
LineChart,
Line,
Area,
AreaChart,
} from 'recharts'
import { TrendingUp, TrendingDown, Minus, BarChart2, Activity, Percent } from 'lucide-react'
interface RawData {
status_stunting: boolean
tanggal_upload: string
nama_posyandu: string
}
interface Props {
data: RawData[]
availableYears: number[]
}
const MONTHS = ['Jan', 'Feb', 'Mar', 'Apr', 'Mei', 'Jun', 'Jul', 'Ags', 'Sep', 'Okt', 'Nov', 'Des']
const CustomTooltip = ({ active, payload, label }: any) => {
if (active && payload && payload.length) {
const stunting = payload.find((p: any) => p.dataKey === 'stunting')?.value ?? 0
const normal = payload.find((p: any) => p.dataKey === 'normal')?.value ?? 0
const total = stunting + normal
const pct = total > 0 ? ((stunting / total) * 100).toFixed(1) : '0.0'
return (
{label}
Stunting
{stunting}
Normal
{normal}
Prevalensi
{pct}%
)
}
return null
}
export function StuntingChart({ data, availableYears }: Props) {
const [selectedYear, setSelectedYear] = useState(availableYears[availableYears.length - 1] ?? 2026)
const [chartType, setChartType] = useState<'bar' | 'area'>('bar')
const [selectedPosyandu, setSelectedPosyandu] = useState('all')
const posyanduList = useMemo(() => {
return Array.from(new Set(data.map(d => d.nama_posyandu).filter(Boolean))).sort()
}, [data])
const filteredData = useMemo(() => {
if (selectedPosyandu === 'all') return data
return data.filter(d => d.nama_posyandu === selectedPosyandu)
}, [data, selectedPosyandu])
const chartData = useMemo(() => {
return MONTHS.map((month, idx) => {
const monthNum = idx + 1
const filtered = filteredData.filter(d => {
if (!d.tanggal_upload) return false
const date = new Date(d.tanggal_upload)
return date.getFullYear() === selectedYear && date.getMonth() + 1 === monthNum
})
const stunting = filtered.filter(d => d.status_stunting === true).length
const normal = filtered.filter(d => d.status_stunting === false).length
const total = stunting + normal
const prevalensi = total > 0 ? parseFloat(((stunting / total) * 100).toFixed(1)) : 0
return { month, stunting, normal, total, prevalensi }
})
}, [filteredData, selectedYear])
const totalStunting = chartData.reduce((s, d) => s + d.stunting, 0)
const totalNormal = chartData.reduce((s, d) => s + d.normal, 0)
const totalAll = totalStunting + totalNormal
const prevalensiTotal = totalAll > 0 ? ((totalStunting / totalAll) * 100).toFixed(1) : '0.0'
const prevYearData = filteredData.filter(d => {
if (!d.tanggal_upload) return false
return new Date(d.tanggal_upload).getFullYear() === selectedYear - 1
})
const prevStunting = prevYearData.filter(d => d.status_stunting === true).length
const prevAll = prevYearData.length
const hasPrevData = prevAll > 0
const prevPrevalensi = hasPrevData ? (prevStunting / prevAll) * 100 : null
const currPrevalensiNum = totalAll > 0 ? (totalStunting / totalAll) * 100 : null
const trend = hasPrevData && currPrevalensiNum !== null
? currPrevalensiNum > prevPrevalensi! ? 'up' : currPrevalensiNum < prevPrevalensi! ? 'down' : 'stable'
: null
return (
{selectedPosyandu === 'all' ? 'Data Tren Stunting Wilayah' : `Tren Stunting — ${selectedPosyandu}`}
Pantau statistik prevalensi stunting untuk memahami kondisi kesehatan anak di daerah Anda.
Periode:
Posyandu:
Total Pemeriksaan
{totalAll}
data balita
Stunting
{totalStunting}
kasus
Normal
{totalNormal}
anak
Prevalensi
{prevalensiTotal}%
{trend !== null && (
{trend === 'up' && <>Naik vs {selectedYear - 1}>}
{trend === 'down' && <>Turun vs {selectedYear - 1}>}
{trend === 'stable' && <>Stabil>}
)}
Statistik Pertumbuhan Anak — {selectedYear}
Perbandingan Balita Stunting & Normal
{totalAll === 0 ? (
Data belum tersedia untuk tahun {selectedYear}
) : (
{chartType === 'bar' ? (
} cursor={{ fill: '#f8fafc' }} />
) : (
} />
)}
)}
Ambang Batas Keamanan WHO: 20%
{totalAll === 0 ? (
Belum ada data prevalensi
) : (
{
if (active && payload && payload.length) {
const val = payload[0].value as number
return (
{label} {selectedYear}
= 20 ? 'bg-red-500' : 'bg-emerald-500'}`} />
{val}%
{val >= 20 ? '🛑 Tinggi' : '✅ Aman'}
)
}
return null
}}
/>
(
= 20 ? '#ef4444' : '#10b981'}
stroke="white"
strokeWidth={2}
/>
)}
activeDot={{ r: 8, stroke: '#f59e0b', strokeWidth: 2 }}
/>
)}
)
}