import 'dart:convert'; import 'dart:math'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:http/http.dart' as http; import '../layout/main_layout.dart'; import 'bidan_drawer.dart'; import '../pages/login_page.dart'; import '../bidan/data_gizi_balita.dart'; import '../bidan/laporan.dart'; const String baseUrl = "http://ta.myhost.id/E31230549/mposyandu_api"; class DashboardBidanPage extends StatefulWidget { const DashboardBidanPage({super.key}); @override State createState() => _DashboardBidanPageState(); } class _DashboardBidanPageState extends State { int jumlahIbuHamil = 0; int jumlahBalita = 0; // Variabel penampung data grafik Ibu Hamil double bumilKek = 0; double bumilNormal = 0; String namaUser = ""; final List listBulan = [ 'Jan', 'Feb', 'Mar', 'Apr', 'Mei', 'Jun', 'Jul', 'Agu', 'Sep', 'Okt', 'Nov', 'Des' ]; @override void initState() { super.initState(); _checkLogin(); getDashboard(); } Future _checkLogin() async { final prefs = await SharedPreferences.getInstance(); final isLogin = prefs.getBool('isLogin') ?? false; if (!isLogin) { if (!mounted) return; Navigator.pushAndRemoveUntil( context, MaterialPageRoute(builder: (_) => const LoginPage(fromGuard: true)), (route) => false, ); } else { setState(() { namaUser = prefs.getString('nama') ?? "Bidan"; }); } } Future getDashboard() async { try { final response = await http.get(Uri.parse("$baseUrl/dashboard_bidan.php")); final data = jsonDecode(response.body); if (data["success"] == true) { setState(() { jumlahIbuHamil = int.parse(data["ibu_hamil"].toString()); jumlahBalita = int.parse(data["balita"].toString()); // Menangkap data kondisi pemeriksaan LILA terbaru dari JSON bumilKek = double.parse((data["bumil_kek"] ?? 0).toString()); bumilNormal = double.parse((data["bumil_normal"] ?? 0).toString()); }); } } catch (e) { debugPrint("ERROR DASHBOARD: $e"); } } // Fungsi untuk mendeteksi klik pada diagram batang void _handleChartTap(TapUpDetails details, Size size) { const double paddingLeft = 30; const double paddingBottom = 30; final double chartWidth = size.width - paddingLeft; final double chartHeight = size.height - paddingBottom; double maxData = max(10, max(bumilNormal, bumilKek) + 5); double groupWidth = chartWidth / listBulan.length; double barWidth = groupWidth * 0.3; int currentMonthIndex = DateTime.now().month - 1; // Hitung posisi koordinat X pusat untuk bulan saat ini double xCenter = paddingLeft + (currentMonthIndex * groupWidth) + (groupWidth / 2); // Hitung area Rect untuk Batang Normal double barHeightNormal = (bumilNormal / maxData) * chartHeight; Rect rectNormal = Rect.fromLTWH(xCenter - barWidth, chartHeight - barHeightNormal, barWidth, barHeightNormal); // Hitung area Rect untuk Batang KEK double barHeightKek = (bumilKek / maxData) * chartHeight; Rect rectKek = Rect.fromLTWH( xCenter, chartHeight - barHeightKek, barWidth, barHeightKek); // Ambil posisi lokal ketukan jari user Offset tapPosition = details.localPosition; // Cek apakah ketukan berada di dalam salah satu area batang if (rectNormal.contains(tapPosition)) { _showDetailDialog("Bumil Normal", bumilNormal.round()); } else if (rectKek.contains(tapPosition)) { _showDetailDialog("Berisiko KEK", bumilKek.round()); } } // Fungsi memunculkan pop-up angka pasti void _showDetailDialog(String kategori, int jumlah) { showDialog( context: context, builder: (context) => AlertDialog( shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), title: Text(kategori, style: GoogleFonts.poppins(fontWeight: FontWeight.bold)), content: Text( "Jumlah saat ini: $jumlah Ibu Hamil", style: GoogleFonts.poppins(fontSize: 15), ), actions: [ TextButton( onPressed: () => Navigator.pop(context), child: const Text("OK", style: TextStyle(fontWeight: FontWeight.bold, color: Colors.blue)), ), ], ), ); } @override Widget build(BuildContext context) { return PopScope( canPop: false, onPopInvokedWithResult: (didPop, result) async { if (didPop) return; await SystemNavigator.pop(); }, child: Theme( data: Theme.of(context).copyWith( textTheme: GoogleFonts.poppinsTextTheme(Theme.of(context).textTheme), dividerColor: Colors.transparent, ), child: MainLayout( title: "", drawer: const BidanDrawer(), body: _buildBody(), ), ), ); } Widget _buildBody() { return SingleChildScrollView( child: Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ RichText( text: TextSpan( children: [ TextSpan( text: 'Selamat Datang Bidan $namaUser\n', style: GoogleFonts.poppins( fontSize: 20, fontWeight: FontWeight.w700, color: Colors.blue, ), ), TextSpan( text: 'Melayani dengan sepenuh hati untuk generasi tetap sehat ', style: GoogleFonts.poppins(fontSize: 13, color: Colors.black), ), const WidgetSpan( alignment: PlaceholderAlignment.middle, child: Icon(Icons.favorite, color: Colors.blue, size: 18), ), ], ), ), const SizedBox(height: 15), Center( child: Image.asset( 'assets/images/logoo.webp', width: 300, height: 180, fit: BoxFit.contain, ), ), const SizedBox(height: 25), Row( children: [ _infoBox("$jumlahIbuHamil", "Ibu Hamil Aktif", Colors.pink), const SizedBox(width: 10), _infoBox("$jumlahBalita", "Balita Terdaftar", Colors.amber), ], ), const SizedBox(height: 20), Row( children: [ _actionButton('Lihat Data Gizi', () { Navigator.push( context, MaterialPageRoute( builder: (context) => const DataGiziBalitaPage())); }), const SizedBox(width: 10), _actionButton('Lihat Laporan', () { Navigator.push( context, MaterialPageRoute( builder: (context) => const DataLaporanPage())); }), ], ), const SizedBox(height: 25), Text( 'Statistik Risiko KEK Ibu Hamil Bulan Ini', style: GoogleFonts.poppins( fontWeight: FontWeight.bold, fontSize: 16), ), const SizedBox(height: 15), Container( padding: const EdgeInsets.fromLTRB(10, 25, 15, 10), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(12), boxShadow: [ BoxShadow( color: Colors.grey.withOpacity(0.1), spreadRadius: 2, blurRadius: 10, ), ], ), child: Column( children: [ // Menggunakan LayoutBuilder agar size CustomPaint sinkron dengan pendeteksi klik LayoutBuilder( builder: (context, constraints) { final size = Size(constraints.maxWidth, 250); return GestureDetector( onTapUp: (details) => _handleChartTap(details, size), child: SizedBox( height: size.height, width: size.width, child: CustomPaint( painter: BarChartPainter( normal: bumilNormal, kek: bumilKek, months: listBulan, currentMonthIndex: DateTime.now().month - 1, ), ), ), ); }, ), const SizedBox(height: 20), Row( mainAxisAlignment: MainAxisAlignment.center, children: [ _legendItem(Colors.teal, "Bumil Normal"), const SizedBox(width: 25), _legendItem(Colors.pink.shade400, "Berisiko KEK"), ], ), ], ), ), const SizedBox(height: 20), ], ), ), ); } Widget _actionButton(String title, VoidCallback onPressed) { return Expanded( child: ElevatedButton( style: ElevatedButton.styleFrom( backgroundColor: Colors.blue, padding: const EdgeInsets.symmetric(vertical: 14), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), ), onPressed: onPressed, child: Text(title, style: const TextStyle(color: Colors.white)), ), ); } Widget _legendItem(Color color, String text) { return Row( children: [ Container( width: 14, height: 14, decoration: BoxDecoration(color: color, shape: BoxShape.circle), ), const SizedBox(width: 8), Text(text, style: const TextStyle(fontSize: 13, fontWeight: FontWeight.w500)), ], ); } Widget _infoBox(String value, String label, Color color) { return Expanded( child: Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: color, borderRadius: BorderRadius.circular(12)), child: Column( children: [ Text(value, style: const TextStyle( color: Colors.white, fontSize: 24, fontWeight: FontWeight.bold)), const SizedBox(height: 5), Text(label, textAlign: TextAlign.center, style: const TextStyle(color: Colors.white, fontSize: 12)), ], ), ), ); } } class BarChartPainter extends CustomPainter { final double normal; final double kek; final List months; final int currentMonthIndex; BarChartPainter({ required this.normal, required this.kek, required this.months, required this.currentMonthIndex, }); @override void paint(Canvas canvas, Size size) { final paintGrid = Paint() ..color = Colors.grey.shade300 ..strokeWidth = 1; const double paddingLeft = 30; const double paddingBottom = 30; final double chartWidth = size.width - paddingLeft; final double chartHeight = size.height - paddingBottom; double maxData = max(10, max(normal, kek) + 5); int segments = 5; for (int i = 0; i <= segments; i++) { double y = chartHeight - (i * chartHeight / segments); canvas.drawLine(Offset(paddingLeft, y), Offset(size.width, y), paintGrid); _drawText(canvas, Offset(5, y - 7), (maxData / segments * i).round().toString(), 10, Colors.grey); } double groupWidth = chartWidth / months.length; double barWidth = groupWidth * 0.3; for (int i = 0; i < months.length; i++) { double xCenter = paddingLeft + (i * groupWidth) + (groupWidth / 2); _drawText(canvas, Offset(xCenter - 10, chartHeight + 10), months[i], 9, Colors.black); if (i == currentMonthIndex) { double barHeightNormal = (normal / maxData) * chartHeight; canvas.drawRect( Rect.fromLTWH(xCenter - barWidth, chartHeight - barHeightNormal, barWidth, barHeightNormal), Paint()..color = Colors.teal, ); double barHeightKek = (kek / maxData) * chartHeight; canvas.drawRect( Rect.fromLTWH( xCenter, chartHeight - barHeightKek, barWidth, barHeightKek), Paint()..color = Colors.pink.shade400, ); } } } void _drawText( Canvas canvas, Offset offset, String text, double fontSize, Color color) { final textPainter = TextPainter( text: TextSpan( text: text, style: TextStyle( color: color, fontSize: fontSize, fontWeight: FontWeight.bold)), textDirection: TextDirection.ltr, ); textPainter.layout(); textPainter.paint(canvas, offset); } @override bool shouldRepaint(covariant CustomPainter oldDelegate) => true; }