MIF_E31230549/lib/bidan/dashboard_bidan.dart

435 lines
14 KiB
Dart

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<DashboardBidanPage> createState() => _DashboardBidanPageState();
}
class _DashboardBidanPageState extends State<DashboardBidanPage> {
int jumlahIbuHamil = 0;
int jumlahBalita = 0;
// Variabel penampung data grafik Ibu Hamil
double bumilKek = 0;
double bumilNormal = 0;
String namaUser = "";
final List<String> listBulan = [
'Jan',
'Feb',
'Mar',
'Apr',
'Mei',
'Jun',
'Jul',
'Agu',
'Sep',
'Okt',
'Nov',
'Des'
];
@override
void initState() {
super.initState();
_checkLogin();
getDashboard();
}
Future<void> _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<void> 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<String> 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;
}