import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:fl_chart/fl_chart.dart'; import 'package:get/get.dart'; import 'package:incubator_app/controller/incubatorController.dart'; class Beranda extends StatefulWidget { Beranda({super.key}); @override State createState() => _BerandaState(); } class _BerandaState extends State { final IncubatorController incubatorController = Get.put(IncubatorController()); List masukSpots = []; List keluarSpots = []; List tanggalLabels = []; ValueNotifier motorStatusSwitch = ValueNotifier(false); void prosesData(List> data) { masukSpots.clear(); keluarSpots.clear(); tanggalLabels.clear(); final recentData = data.length <= 7 ? data : data.sublist(data.length - 7); for (int i = 0; i < recentData.length; i++) { final row = recentData[i]; final tgl = row['tanggal']; final masuk = row['masuk']; final keluar = row['keluar']; masukSpots.add(FlSpot(i.toDouble(), masuk.toDouble())); keluarSpots.add(FlSpot(i.toDouble(), keluar.toDouble())); tanggalLabels.add(tgl); } } double _getMaxY() { double maxMasuk = masukSpots.isEmpty ? 0 : masukSpots.map((e) => e.y).reduce((a, b) => a > b ? a : b); double maxKeluar = keluarSpots.isEmpty ? 0 : keluarSpots.map((e) => e.y).reduce((a, b) => a > b ? a : b); double max = maxMasuk > maxKeluar ? maxMasuk : maxKeluar; double adjusted = (max * 1.2).ceilToDouble(); return (adjusted / 20).ceil() * 20; } double _getIntervalY(double maxY) { if (maxY <= 20) return 5; if (maxY <= 50) return 10; if (maxY <= 100) return 20; if (maxY <= 200) return 40; return 50; } @override void initState() { super.initState(); incubatorController.loadChartData().then((_) { setState(() { prosesData(incubatorController.chartData); }); }); incubatorController.loadAllIncubators().then((_) { incubatorController.cekTelurKeluarHariIni(); }); incubatorController.fetchMotorStatus(); incubatorController.fetchPompaStatus(); } @override Widget build(BuildContext context) { double maxY = _getMaxY(); double intervalY = _getIntervalY(maxY); return Scaffold( body: SingleChildScrollView( scrollDirection: Axis.vertical, child: Center( child: Container( margin: const EdgeInsets.symmetric(horizontal: 15), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const SizedBox(height: 65), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( "Selamat Datang", style: TextStyle(fontWeight: FontWeight.bold, fontSize: 24), ), SizedBox(width: 8), Obx(() { int notifCount = incubatorController.telurKeluarHariIni.length; return Stack( children: [ IconButton( icon: const Icon(Icons.notifications, size: 32, color: Color(0xFF00A1FF)), onPressed: () { final telurHariIni = incubatorController.telurKeluarHariIni; if (telurHariIni.isEmpty) { showDialog( context: context, builder: (context) => AlertDialog( title: const Text("Notifikasi"), content: const Text( "Tidak ada telur keluar hari ini."), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(), child: const Text("Tutup"), ), ], ), ); } else { showDialog( context: context, builder: (context) => AlertDialog( title: const Text("Telur Keluar Hari Ini"), content: SizedBox( width: double.maxFinite, child: ListView.builder( shrinkWrap: true, itemCount: telurHariIni.length, itemBuilder: (context, index) { final item = telurHariIni[index]; return ListTile( leading: const Icon(Icons.egg, color: Colors.orangeAccent), title: Text("Kode: ${item['kode']}"), subtitle: Text( "Jumlah Telur: ${item['jumlah_telur']} telur\nJumlah Menetas: ${item['jumlah_menetas']} telur\nJumlah Belum Menetas: ${item['jumlah_belum_menetas']}"), ); }, ), ), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(), child: const Text("Tutup"), ), ], ), ); } }, ), if (notifCount > 0) Positioned( right: 4, top: 4, child: Container( padding: const EdgeInsets.all(5), decoration: const BoxDecoration( color: Colors.red, shape: BoxShape.circle, ), child: Text( '$notifCount', style: const TextStyle( color: Colors.white, fontSize: 10, ), ), ), ), ], ); }), ], ), const SizedBox(height: 40), Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Obx( () => InfoCard( title: "Suhu", icon: Icons.thermostat_outlined, value: "${incubatorController.suhu.value.toStringAsFixed(1)}℃"), ), const SizedBox(width: 15), Obx( () => InfoCard( title: "Kelembapan", icon: Icons.water_drop_outlined, value: "${incubatorController.kelembapan.value.toStringAsFixed(1)}%"), ), ], ), const SizedBox(height: 20), Obx(() => Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const Text( "Motor Status", style: TextStyle( fontSize: 18, fontWeight: FontWeight.w500), ), CupertinoSwitch( value: incubatorController.motorStatus.value, onChanged: (newValue) { incubatorController.updateMotorStatus(newValue); }, activeColor: Color(0xFF00A1FF), ), ], )), const SizedBox(height: 10), Obx(() => Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const Text( "Pompa Status", style: TextStyle( fontSize: 18, fontWeight: FontWeight.w500), ), CupertinoSwitch( value: incubatorController.pumpStatus.value, onChanged: (newValue) { incubatorController.updatePompaStatus(newValue); }, activeColor: Color(0xFF00A1FF), ), ], )), const SizedBox(height: 20), const Text( "History Telur Incubator", style: TextStyle(fontSize: 18, fontWeight: FontWeight.w400), ), const SizedBox(height: 20), if (masukSpots.isEmpty && keluarSpots.isEmpty) Center( heightFactor: 5, child: Text("Data Telur Tidak ada", style: TextStyle(fontSize: 18, color: Color(0xFF737373))), ) else SingleChildScrollView( scrollDirection: Axis.horizontal, child: SizedBox( width: MediaQuery.of(context).size.width * 0.9, child: AspectRatio( aspectRatio: 1.7, child: LineChart( LineChartData( gridData: FlGridData(show: true, drawVerticalLine: false), titlesData: FlTitlesData( show: true, rightTitles: AxisTitles( sideTitles: SideTitles(showTitles: false)), topTitles: AxisTitles( sideTitles: SideTitles(showTitles: false)), leftTitles: AxisTitles( sideTitles: SideTitles( showTitles: true, reservedSize: 40, interval: intervalY, getTitlesWidget: (value, _) => Text(value.toInt().toString()), ), ), bottomTitles: AxisTitles( sideTitles: SideTitles( showTitles: true, reservedSize: 32, interval: 1, getTitlesWidget: (value, _) { int index = value.toInt(); if (index >= 0 && index < tanggalLabels.length) { String tanggal = tanggalLabels[index]; return Text( tanggal.length >= 10 ? tanggal.substring(5, 10) : tanggal, style: const TextStyle(fontSize: 10), ); } return const SizedBox.shrink(); }, ), ), ), borderData: FlBorderData( show: true, border: Border.all(color: Colors.black, width: 1), ), minX: 0, maxX: (tanggalLabels.length - 1).toDouble(), minY: 0, maxY: maxY, lineBarsData: [ LineChartBarData( spots: masukSpots, isCurved: true, color: Colors.green, barWidth: 3, isStrokeCapRound: true, dotData: FlDotData(show: true), belowBarData: BarAreaData(show: false), ), LineChartBarData( spots: keluarSpots, isCurved: true, color: Colors.red, barWidth: 3, isStrokeCapRound: true, dotData: FlDotData(show: true), belowBarData: BarAreaData(show: false), ), ], ), ), ), ), ), // ==== Grafik Suhu & Kelembapan ==== const SizedBox(height: 20), const Text( "Grafik Suhu & Kelembapan", style: TextStyle(fontSize: 18, fontWeight: FontWeight.w400), ), const SizedBox(height: 20), Obx(() { if (incubatorController.suhuSpots.isEmpty || incubatorController.kelembapanSpots.isEmpty) { return Center( heightFactor: 3, child: Text("Belum ada data suhu & kelembapan.", style: TextStyle( fontSize: 16, color: Color(0xFF737373))), ); } return SingleChildScrollView( scrollDirection: Axis.horizontal, child: SizedBox( width: MediaQuery.of(context).size.width * 0.9, child: AspectRatio( aspectRatio: 1.7, child: LineChart( LineChartData( gridData: FlGridData(show: true, drawVerticalLine: false), titlesData: FlTitlesData( show: true, topTitles: AxisTitles( sideTitles: SideTitles(showTitles: false)), rightTitles: AxisTitles( sideTitles: SideTitles(showTitles: false)), bottomTitles: AxisTitles( sideTitles: SideTitles( showTitles: true, reservedSize: 28, interval: 1, getTitlesWidget: (value, _) { int index = value.toInt(); if (index >= 0 && index < incubatorController .waktuLabels.length) { return Text( incubatorController.waktuLabels[index], style: const TextStyle(fontSize: 10), ); } return const SizedBox.shrink(); }, ), ), leftTitles: AxisTitles( sideTitles: SideTitles( showTitles: true, reservedSize: 42, interval: 20, getTitlesWidget: (value, _) => Text(value.toInt().toString()), ), ), ), borderData: FlBorderData( show: true, border: Border.all(color: Colors.black, width: 1), ), minX: 0, maxX: (incubatorController.waktuLabels.length - 1) .toDouble(), minY: 0, maxY: 100, lineBarsData: [ LineChartBarData( spots: incubatorController.suhuSpots.length > 10 ? incubatorController.suhuSpots.sublist( incubatorController.suhuSpots.length - 10) : incubatorController.suhuSpots, isCurved: true, color: Colors.orange, barWidth: 4, isStrokeCapRound: true, dotData: FlDotData(show: false), belowBarData: BarAreaData(show: false), ), LineChartBarData( spots: incubatorController.kelembapanSpots.length > 10 ? incubatorController.kelembapanSpots .sublist(incubatorController .kelembapanSpots.length - 10) : incubatorController.kelembapanSpots, isCurved: true, color: Colors.blue, barWidth: 4, isStrokeCapRound: true, dotData: FlDotData(show: false), belowBarData: BarAreaData(show: false), ), ], ), ), ), ), ); }), ], ), ), ), ), ); } } class InfoCard extends StatelessWidget { final String title; final IconData icon; final String value; InfoCard({ super.key, required this.title, required this.icon, required this.value, }); @override Widget build(BuildContext context) { final screenWidth = MediaQuery.of(context).size.width; final screenHeight = MediaQuery.of(context).size.height; final cardWidth = screenWidth * 0.44; final cardHeight = screenHeight * 0.22; return Container( height: cardHeight, width: cardWidth, decoration: BoxDecoration( color: const Color(0xFF00A1FF), borderRadius: BorderRadius.circular(15), ), padding: const EdgeInsets.all(15), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Text( title, style: TextStyle( fontSize: 21, fontWeight: FontWeight.w600, color: Colors.white), ), SizedBox(height: 10), Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(icon, size: 48, color: Colors.white), Text( value, style: TextStyle( fontSize: 28, fontWeight: FontWeight.w500, color: Colors.white), ), ], ), ], ), ); } }