import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:fl_chart/fl_chart.dart'; import 'package:http/http.dart' as http; import 'package:shared_preferences/shared_preferences.dart'; // Sesuaikan path import ini dengan project Anda import '../../layout/main_layout.dart'; import '../ibu_drawer.dart'; // Import Dashboard Ibu agar navigasi PopScope berfungsi import '../dashboard_ibu.dart'; class GrafikAnakIMTPage extends StatefulWidget { const GrafikAnakIMTPage({super.key}); @override State createState() => _GrafikAnakIMTPageState(); } class _GrafikAnakIMTPageState extends State { List anakList = []; bool isLoading = true; @override void initState() { super.initState(); getData(); } Future getData() async { SharedPreferences prefs = await SharedPreferences.getInstance(); String userId = prefs.getString("id_user") ?? ""; try { var response = await http.post( Uri.parse( "http://ta.myhost.id/E31230549/mposyandu_api/grafik_balita/get_grafik_imt.php"), body: {"user_id": userId}, ); var jsonData = json.decode(response.body); if (jsonData["success"]) { setState(() { anakList = jsonData["data"]; isLoading = false; }); } } catch (e) { if (mounted) setState(() => isLoading = false); debugPrint("Error: $e"); } } int hitungUmurBulan(String tglLahir, String tglPeriksa) { DateTime lahir = DateTime.parse(tglLahir); DateTime periksa = DateTime.parse(tglPeriksa); int months = (periksa.year - lahir.year) * 12 + periksa.month - lahir.month; return months < 0 ? 0 : months; } String formatUsiaDetail(String tglLahir, String tglPeriksa) { DateTime birth = DateTime.parse(tglLahir); DateTime now = DateTime.parse(tglPeriksa); int years = now.year - birth.year; int months = now.month - birth.month; int days = now.day - birth.day; if (days < 0) { days += DateTime(now.year, now.month, 0).day; months--; } if (months < 0) { years--; months += 12; } return "$years Thn $months Bln $days Hari"; } @override Widget build(BuildContext context) { return PopScope( canPop: false, onPopInvokedWithResult: (didPop, result) async { if (didPop) return; // ✅ FIX FINAL: selalu kembali ke Dashboard Ibu Navigator.pushAndRemoveUntil( context, MaterialPageRoute( builder: (_) => const DashboardIbuPage(), ), (route) => false, ); }, child: MainLayout( title: "", drawer: const IbuDrawer(), body: Padding( padding: const EdgeInsets.all(10), child: isLoading ? const Center(child: CircularProgressIndicator()) : anakList.isEmpty ? const Center(child: Text("Tidak ada data IMT.")) : ListView.builder( itemCount: anakList.length, itemBuilder: (context, index) { var anak = anakList[index]; List spots = []; List> riwayatDetails = []; double lastIMT = 0; double lastBB = 0; double lastTB = 0; int lastBulan = 0; String lastTgl = ""; for (var p in anak["riwayat_imt"]) { double imtVal = double.tryParse(p["imt"].toString()) ?? 0.0; if (imtVal > 0) { int bulan = hitungUmurBulan(anak["tanggal_lahir"], p["tanggal_pemeriksaan"]); String usiaStr = formatUsiaDetail( anak["tanggal_lahir"], p["tanggal_pemeriksaan"]); spots.add(FlSpot(bulan.toDouble(), imtVal)); riwayatDetails.add({ "x": bulan.toDouble(), "y": imtVal, "bb": p["bb"], "tb": p["tb"], "usia_lengkap": usiaStr, }); lastIMT = imtVal; lastBB = p["bb"]; lastTB = p["tb"]; lastBulan = bulan; lastTgl = p["tanggal_pemeriksaan"]; } } return GrafikCardKIA_IMT( nama: anak["nama"], jk: anak["jenis_kelamin"], umurBulan: lastBulan, imt: lastIMT, bb: lastBB, tb: lastTB, detailUsiaTerakhir: lastTgl.isNotEmpty ? formatUsiaDetail(anak["tanggal_lahir"], lastTgl) : "-", historySpots: spots, historyDetails: riwayatDetails, ); }, ), ), ), ); } } class GrafikCardKIA_IMT extends StatelessWidget { final String nama, jk, detailUsiaTerakhir; final int umurBulan; final double imt, bb, tb; final List historySpots; final List> historyDetails; const GrafikCardKIA_IMT({ super.key, required this.nama, required this.jk, required this.umurBulan, required this.imt, required this.bb, required this.tb, required this.detailUsiaTerakhir, required this.historySpots, required this.historyDetails, }); double getSDValueAtAge(List spots, int targetUmur) { if (spots.isEmpty) return 0; for (int i = 0; i < spots.length - 1; i++) { if (targetUmur >= spots[i].x && targetUmur <= spots[i + 1].x) { double ratio = (targetUmur - spots[i].x) / (spots[i + 1].x - spots[i].x); return spots[i].y + ratio * (spots[i + 1].y - spots[i].y); } } return spots.last.y; } Map analisisStatusIMT( bool isLaki, int umur, double nilaiIMT) { if (umur > 60 || nilaiIMT <= 0) return {"status": "Data tidak valid", "warna": Colors.grey}; double sd3 = getSDValueAtAge(_getSDRefIMT_Gabungan(isLaki, 3), umur); double sd2 = getSDValueAtAge(_getSDRefIMT_Gabungan(isLaki, 2), umur); double sd1 = getSDValueAtAge(_getSDRefIMT_Gabungan(isLaki, 1), umur); double sd0 = getSDValueAtAge(_getSDRefIMT_Gabungan(isLaki, 0), umur); double sdM2 = getSDValueAtAge(_getSDRefIMT_Gabungan(isLaki, -2), umur); double sdM3 = getSDValueAtAge(_getSDRefIMT_Gabungan(isLaki, -3), umur); if (nilaiIMT < sdM3) return {"status": "Gizi Buruk (Sangat Kurus)", "warna": Colors.black}; if (nilaiIMT < sdM2) return {"status": "Gizi Kurang (Kurus)", "warna": Colors.red}; if (nilaiIMT > sd3) return {"status": "Obesitas", "warna": Colors.purple}; if (nilaiIMT > sd2) return {"status": "Berat Badan Lebih", "warna": Colors.orange.shade900}; if (nilaiIMT > sd1) return {"status": "Risiko Berat Badan Lebih", "warna": Colors.orange}; if (nilaiIMT >= sd0) return {"status": "Normal (Di atas Median)", "warna": Colors.green}; return { "status": "Normal (Di bawah Median)", "warna": Colors.green.shade700 }; } @override Widget build(BuildContext context) { final bool isLaki = jk == "L"; final Color accentColor = isLaki ? Colors.blue.shade700 : const Color(0xFFE91E63); final analisis = analisisStatusIMT(isLaki, umurBulan, imt); return Card( elevation: 4, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15)), margin: const EdgeInsets.only(bottom: 25), child: Column( children: [ Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), decoration: BoxDecoration( color: accentColor, borderRadius: const BorderRadius.vertical(top: Radius.circular(15))), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text(nama, style: GoogleFonts.poppins( color: Colors.white, fontWeight: FontWeight.bold, fontSize: 15)), Text(isLaki ? "Laki-laki" : "Perempuan", style: GoogleFonts.poppins(color: Colors.white, fontSize: 11)), ], ), ), Padding( padding: const EdgeInsets.fromLTRB(10, 25, 25, 10), child: SizedBox( width: double.infinity, height: 250, child: LineChart( LineChartData( lineTouchData: LineTouchData( touchTooltipData: LineTouchTooltipData( getTooltipColor: (touchedSpot) => Colors.blueGrey.withOpacity(0.9), getTooltipItems: (List touchedSpots) { return touchedSpots.map((spot) { var detail = historyDetails.firstWhere( (e) => e["x"] == spot.x && e["y"] == spot.y, orElse: () => {}); return LineTooltipItem( "IMT: ${spot.y.toStringAsFixed(1)}\nBB: ${detail["bb"]}kg, TB: ${detail["tb"]}cm\n${detail["usia_lengkap"]}", GoogleFonts.poppins( color: Colors.white, fontSize: 9, fontWeight: FontWeight.bold), ); }).toList(); }, ), ), minX: 0, maxX: 60, minY: 10, maxY: 22, titlesData: FlTitlesData( rightTitles: AxisTitles( sideTitles: SideTitles( showTitles: true, reservedSize: 30, getTitlesWidget: (val, _) => Text( val.toInt().toString(), style: GoogleFonts.poppins( fontSize: 9, color: Colors.black54)))), leftTitles: AxisTitles( axisNameWidget: Text("IMT", style: GoogleFonts.poppins( fontSize: 10, fontWeight: FontWeight.bold)), sideTitles: SideTitles( showTitles: true, reservedSize: 30, getTitlesWidget: (val, _) => Text( val.toInt().toString(), style: GoogleFonts.poppins( fontSize: 9, color: Colors.black54)))), topTitles: const AxisTitles( sideTitles: SideTitles(showTitles: false)), bottomTitles: AxisTitles( sideTitles: SideTitles( showTitles: true, interval: 12, getTitlesWidget: (val, _) { if (val == 0) return Text("Lahir", style: GoogleFonts.poppins(fontSize: 9)); return Text("${(val / 12).toInt()} Thn", style: GoogleFonts.poppins(fontSize: 9)); }), ), ), gridData: FlGridData( show: true, horizontalInterval: 1, verticalInterval: 6), borderData: FlBorderData( show: true, border: Border.all(color: Colors.black12)), lineBarsData: [ _sdLine(_getSDRefIMT_Gabungan(isLaki, 3), Colors.black), _sdLine(_getSDRefIMT_Gabungan(isLaki, 2), Colors.red), _sdLine(_getSDRefIMT_Gabungan(isLaki, 1), Colors.orange), _sdLine(_getSDRefIMT_Gabungan(isLaki, 0), Colors.green), _sdLine(_getSDRefIMT_Gabungan(isLaki, -2), Colors.red), _sdLine(_getSDRefIMT_Gabungan(isLaki, -3), Colors.black), LineChartBarData( spots: historySpots, isCurved: true, color: accentColor, barWidth: 3, dotData: FlDotData( show: true, getDotPainter: (spot, p, bar, i) => FlDotCirclePainter( radius: 4, color: Colors.white, strokeWidth: 2, strokeColor: accentColor)), ), ], ), ), ), ), Padding( padding: const EdgeInsets.all(16), child: Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: Colors.grey.shade50, borderRadius: BorderRadius.circular(10), border: Border.all(color: Colors.grey.shade200)), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text("Keterangan Grafik:", style: GoogleFonts.poppins( fontWeight: FontWeight.bold, fontSize: 12)), const SizedBox(height: 8), _legendItem(Colors.green, "Hijau", "Median (Ideal)"), _legendItem( Colors.orange, "Orange", "Ambang Normal Atas (+1 SD)"), _legendItem(Colors.red, "Merah", "Batas Kurus / Gemuk"), _legendItem(Colors.black, "Hitam", "Kritis"), const Divider(height: 20), Row( children: [ Container( width: 12, height: 12, decoration: BoxDecoration( color: analisis["warna"], shape: BoxShape.circle)), const SizedBox(width: 8), Expanded( child: Text("Status: ${analisis["status"]}", style: GoogleFonts.poppins( fontSize: 11, fontWeight: FontWeight.bold, color: analisis["warna"]))), ], ), const SizedBox(height: 5), Text( "Terakhir: ${imt.toStringAsFixed(1)} kg/m² | Usia: $detailUsiaTerakhir", style: GoogleFonts.poppins( fontSize: 11, fontWeight: FontWeight.w600, color: accentColor)), ], ), ), ) ], ), ); } Widget _legendItem(Color color, String label, String desc) { return Padding( padding: const EdgeInsets.only(bottom: 4), child: Row(children: [ Container(width: 10, height: 1.5, color: color), const SizedBox(width: 8), Text("$label: $desc", style: GoogleFonts.poppins(fontSize: 10)) ])); } LineChartBarData _sdLine(List spots, Color color) => LineChartBarData( spots: spots, color: color.withOpacity(0.4), barWidth: 1.2, dotData: const FlDotData(show: false), isCurved: true); List _getSDRefIMT_Gabungan(bool isLaki, int type) { if (isLaki) { if (type == 3) return const [ FlSpot(0, 16.1), FlSpot(6, 19.3), FlSpot(12, 19.9), FlSpot(60, 19.6) ]; if (type == 2) return const [ FlSpot(0, 15.3), FlSpot(6, 18.2), FlSpot(12, 18.9), FlSpot(60, 18.8) ]; if (type == 1) return const [ FlSpot(0, 14.4), FlSpot(6, 17.2), FlSpot(12, 18.0), FlSpot(60, 18.0) ]; if (type == 0) return const [ FlSpot(0, 13.4), FlSpot(6, 16.3), FlSpot(12, 17.1), FlSpot(60, 17.0) ]; if (type == -2) return const [ FlSpot(0, 11.5), FlSpot(6, 14.3), FlSpot(12, 15.1), FlSpot(60, 15.1) ]; if (type == -3) return const [ FlSpot(0, 10.7), FlSpot(6, 13.2), FlSpot(12, 14.1), FlSpot(60, 14.1) ]; } else { if (type == 3) return const [ FlSpot(0, 16.0), FlSpot(6, 19.1), FlSpot(12, 19.7), FlSpot(60, 21.0) ]; if (type == 2) return const [ FlSpot(0, 15.1), FlSpot(6, 17.9), FlSpot(12, 18.6), FlSpot(60, 19.9) ]; if (type == 1) return const [ FlSpot(0, 14.2), FlSpot(6, 16.9), FlSpot(12, 17.6), FlSpot(60, 18.9) ]; if (type == 0) return const [ FlSpot(0, 13.2), FlSpot(6, 15.8), FlSpot(12, 16.5), FlSpot(60, 17.8) ]; if (type == -2) return const [ FlSpot(0, 11.2), FlSpot(6, 13.8), FlSpot(12, 14.4), FlSpot(60, 15.6) ]; if (type == -3) return const [ FlSpot(0, 10.4), FlSpot(6, 12.8), FlSpot(12, 13.3), FlSpot(60, 14.6) ]; } return []; } }