import 'package:flutter/material.dart'; import 'package:jago/screens/age_control.dart'; import 'package:jago/screens/angkakematian.dart'; import 'package:jago/services/db_helper.dart'; import 'notification.dart'; import 'history.dart'; import 'settings.dart'; import 'package:firebase_database/firebase_database.dart'; import 'package:percent_indicator/circular_percent_indicator.dart'; import 'package:intl/intl.dart'; import 'package:percent_indicator/percent_indicator.dart'; import 'dart:async'; class DashboardPage extends StatefulWidget { @override _DashboardPageState createState() => _DashboardPageState(); } class _DashboardPageState extends State with SingleTickerProviderStateMixin { int _selectedIndex = 0; late TabController _tabController; // Firebase reference final databaseReference = FirebaseDatabase.instance.ref(); // Control variables bool isManual = false; bool isLampOn = false; bool isFanOn = false; bool autoModeFan = true; bool autoModeLight = true; double temperature = 0; double humidity = 0; int ageInWeeks = 1; // Timer untuk refresh otomatis Timer? _refreshTimer; // Variabel untuk menyimpan waktu pembaruan terakhir DateTime _lastUpdateTime = DateTime.now(); void _updateLastRefreshTime() { setState(() { _lastUpdateTime = DateTime.now(); }); } @override void initState() { super.initState(); _tabController = TabController(length: 3, vsync: this); _initializeFirebaseData(); _setupFirebaseListeners(); // Menambahkan timer untuk refresh otomatis setiap 10 detik _refreshTimer = Timer.periodic(Duration(seconds: 10), (timer) { if (mounted) { setState(() { // Memperbarui UI untuk menampilkan data terbaru _updateLastRefreshTime(); print("Refresh otomatis data: ${_lastUpdateTime}"); }); } }); } @override void dispose() { // Batalkan timer saat widget dihapus _refreshTimer?.cancel(); _tabController.dispose(); super.dispose(); } // Mengambil data dari Firebase saat aplikasi dimulai void _initializeFirebaseData() { try { // Ambil status mode fan (auto/manual) databaseReference.child('control/fan/auto').onValue.listen((event) { if (event.snapshot.exists) { setState(() { // Menggunakan as bool untuk memastikan tipe data autoModeFan = (event.snapshot.value as dynamic) ?? true; }); } }, onError: (error) { print('Error getting fan auto mode: $error'); }); // Ambil status mode light (auto/manual) databaseReference.child('control/light/auto').onValue.listen((event) { if (event.snapshot.exists) { setState(() { // Menggunakan as bool untuk memastikan tipe data autoModeLight = (event.snapshot.value as dynamic) ?? true; // Update isManual berdasarkan kedua mode isManual = !(autoModeFan && autoModeLight); }); } }, onError: (error) { print('Error getting light auto mode: $error'); }); // Ambil status fan databaseReference.child('relay/Kipas').onValue.listen((event) { if (event.snapshot.exists) { setState(() { // Menggunakan as bool untuk memastikan tipe data isFanOn = (event.snapshot.value as dynamic) ?? false; }); } }, onError: (error) { print('Error getting fan status: $error'); }); // Ambil status lampu databaseReference.child('relay/Lampu').onValue.listen((event) { if (event.snapshot.exists) { setState(() { // Menggunakan as bool untuk memastikan tipe data isLampOn = (event.snapshot.value as dynamic) ?? false; }); } }, onError: (error) { print('Error getting lamp status: $error'); }); } catch (e) { print('Error in _initializeFirebaseData: $e'); } } void _setupFirebaseListeners() { try { // Listen to temperature changes databaseReference.child("sensor/temperature").onValue.listen((event) { if (event.snapshot.value != null) { setState(() { temperature = double.tryParse(event.snapshot.value.toString()) ?? 0.0; }); } }, onError: (error) { print('Error getting temperature: $error'); }); // Listen to humidity changes databaseReference.child("sensor/Humidity").onValue.listen((event) { if (event.snapshot.value != null) { setState(() { humidity = double.tryParse(event.snapshot.value.toString()) ?? 0.0; }); } }, onError: (error) { print('Error getting humidity: $error'); }); // Listen to fan status databaseReference.child("relay/Kipas").onValue.listen((event) { if (event.snapshot.value != null) { setState(() { isFanOn = event.snapshot.value as bool; }); } }, onError: (error) { print('Error getting fan status: $error'); }); // Listen to light status databaseReference.child("relay/Lampu").onValue.listen((event) { if (event.snapshot.value != null) { setState(() { isLampOn = event.snapshot.value as bool; }); } }, onError: (error) { print('Error getting lamp status: $error'); }); // Listen to chicken age databaseReference.child("chicken_age").onValue.listen((event) { if (event.snapshot.value != null) { setState(() { ageInWeeks = int.tryParse(event.snapshot.value.toString()) ?? 1; }); } }, onError: (error) { print('Error getting chicken age: $error'); }); } catch (e) { print('Error in _setupFirebaseListeners: $e'); } } // Toggle antara mode manual dan otomatis void _toggleMode() { setState(() { isManual = !isManual; // Update Firebase dengan nilai baru databaseReference.child('control/fan/auto').set(!isManual); databaseReference.child('control/light/auto').set(!isManual); // Jika beralih ke mode manual, atur status awal perangkat if (isManual) { databaseReference.child('control/fan/status').set(isFanOn); databaseReference.child('control/light/status').set(isLampOn); } }); } // Toggle status lampu (hanya berfungsi dalam mode manual) void _toggleLamp(bool newValue) { if (isManual) { setState(() { isLampOn = newValue; }); // Update status lampu di Firebase databaseReference.child('control/light/status').set(isLampOn); } } // Toggle status kipas (hanya berfungsi dalam mode manual) void _toggleFan(bool newValue) { if (isManual) { setState(() { isFanOn = newValue; }); // Update status kipas di Firebase databaseReference.child('control/fan/status').set(isFanOn); } } void _onItemTapped(int index) { setState(() { _selectedIndex = index; }); switch (index) { case 0: Navigator.push( context, MaterialPageRoute(builder: (context) => HistoryPage()), ); break; case 1: Navigator.push( context, MaterialPageRoute(builder: (context) => AngkaKematianPage()), ); break; } } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( elevation: 0, title: Text( 'JAGO', style: TextStyle( color: Colors.white, fontWeight: FontWeight.bold, fontSize: 24, letterSpacing: 1.5, ), ), backgroundColor: const Color(0xFFA82429), actions: [ IconButton( icon: Icon(Icons.notifications, color: Colors.white), onPressed: () { Navigator.push( context, MaterialPageRoute(builder: (context) => NotificationPage()), ); }, ), IconButton( icon: Icon(Icons.settings, color: Colors.white), onPressed: () { Navigator.push( context, MaterialPageRoute(builder: (context) => SettingsPage()), ); }, ), IconButton( icon: Icon(Icons.access_time, color: Colors.white), onPressed: () { Navigator.push( context, MaterialPageRoute(builder: (context) => AgeControlPage()), ); }, ), ], bottom: TabBar( controller: _tabController, indicatorColor: Colors.white, onTap: (index) { setState(() { _tabController.index = index; }); }, tabs: [ Tab(text: "Dashboard"), Tab(text: "Kontrol"), Tab(text: "Data"), ], ), ), body: PageStorage( bucket: PageStorageBucket(), child: TabBarView( controller: _tabController, physics: ClampingScrollPhysics(), children: [ // Menggunakan key untuk memastikan state dipertahankan KeyedSubtree( key: PageStorageKey('dashboard_tab'), child: _buildDashboardTab(), ), KeyedSubtree( key: PageStorageKey('control_tab'), child: _buildControlTab(), ), KeyedSubtree( key: PageStorageKey('data_tab'), child: _buildDataTab(), ), ], ), ), bottomNavigationBar: BottomNavigationBar( backgroundColor: const Color(0xFFA82429), selectedItemColor: Colors.white, unselectedItemColor: Colors.white70, currentIndex: _selectedIndex, onTap: _onItemTapped, items: [ BottomNavigationBarItem(icon: Icon(Icons.history), label: 'Riwayat'), BottomNavigationBarItem( icon: Icon(Icons.catching_pokemon), label: 'Angka Kematian'), ], ), ); } Widget _buildDashboardTab() { if (!mounted) return Container(); return Material( child: RefreshIndicator( onRefresh: () async { if (mounted) { setState(() {}); } return Future.value(); }, child: ListView( physics: const AlwaysScrollableScrollPhysics(), children: [ // Header section with device status _buildDeviceStatusHeader(), // Summary cards _buildSummaryCards(), ], ), ), ); } Widget _buildDeviceStatusHeader() { return Container( padding: EdgeInsets.all(16), decoration: BoxDecoration( color: Color(0xFFA82429), borderRadius: BorderRadius.only( bottomLeft: Radius.circular(30), bottomRight: Radius.circular(30), ), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Status Perangkat", style: TextStyle( color: Colors.white, fontSize: 18, fontWeight: FontWeight.bold, ), ), SizedBox(height: 16), Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ _buildDeviceStatusItem( "Lampu", isLampOn ? "Aktif" : "Nonaktif", isLampOn ? Icons.lightbulb : Icons.lightbulb_outline, isLampOn ? Colors.yellow : Colors.white70, ), _buildDeviceStatusItem( "Kipas", isFanOn ? "Aktif" : "Nonaktif", isFanOn ? Icons.propane : Icons.propane_outlined, isFanOn ? Colors.blue : Colors.white70, imageAsset: isFanOn ? 'assets/Kipas_on.png' : 'assets/Kipas.png', ), _buildDeviceStatusItem( "Mode", isManual ? "Manual" : "Otomatis", isManual ? Icons.pan_tool : Icons.auto_mode, Colors.white, ), ], ), SizedBox(height: 16), GestureDetector( onTap: _toggleMode, child: Container( padding: EdgeInsets.symmetric(vertical: 10, horizontal: 16), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(20), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon(isManual ? Icons.toggle_on : Icons.toggle_off, color: Color(0xFFA82429), size: 24), SizedBox(width: 8), Text( isManual ? "Mode Manual" : "Mode Otomatis", style: TextStyle( color: Color(0xFFA82429), fontWeight: FontWeight.bold, ), ), ], ), ), ), ], ), ); } Widget _buildDeviceStatusItem(String title, String status, IconData? icon, Color iconColor, {String? imageAsset}) { return Column( children: [ imageAsset != null ? ColorFiltered( colorFilter: ColorFilter.mode( iconColor, // Menggunakan warna yang sama dengan icon BlendMode.srcATop, ), child: Image.asset( imageAsset, width: 30, height: 30, errorBuilder: (context, error, stackTrace) { print("Error loading image: $error"); return Icon( icon ?? Icons.error, color: iconColor, size: 30, ); }, ), ) : Icon( icon!, color: iconColor, size: 30, ), SizedBox(height: 8), Text( title, style: TextStyle( color: Colors.white, fontWeight: FontWeight.bold, ), ), SizedBox(height: 4), Text( status, style: TextStyle( color: Colors.white70, fontSize: 12, ), ), ], ); } Widget _buildSummaryCards() { return Padding( padding: EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Halaman Utama", style: TextStyle( fontSize: 20, fontWeight: FontWeight.bold, color: Colors.black87, ), ), SizedBox(height: 16), Row( children: [ Expanded( child: _buildSensorCard( "Suhu", "${temperature.toStringAsFixed(1)}°C", Icons.thermostat, Color(0xFFF8D7DA), Color(0xFFA82429), temperature / 50, // Assuming max temp is 50°C ), ), SizedBox(width: 16), Expanded( child: _buildSensorCard( "Kelembapan", "${humidity.toStringAsFixed(1)}%", Icons.water_drop, Color(0xFFD1ECF1), Colors.blue, humidity / 100, ), ), ], ), SizedBox(height: 16), Row( children: [ Expanded( child: _buildInfoCard( "Umur Ayam", "$ageInWeeks minggu", Icons.calendar_today, Color(0xFFE2F0D9), Colors.green, ), ), SizedBox(width: 16), Expanded( child: _buildInfoCard( "Mode", isManual ? "Manual" : "Otomatis", isManual ? Icons.pan_tool : Icons.auto_mode, Color(0xFFFFF3CD), Colors.orange, ), ), ], ), ], ), ); } Widget _buildSensorCard(String title, String value, IconData icon, Color bgColor, Color valueColor, double percent) { return Card( elevation: 5, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(15), ), child: Container( padding: EdgeInsets.all(15), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(15), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( title, style: TextStyle( fontSize: 16, fontWeight: FontWeight.bold, color: Colors.black87, ), ), Container( padding: EdgeInsets.all(8), decoration: BoxDecoration( color: bgColor, shape: BoxShape.circle, ), child: Icon( icon, color: valueColor, size: 18, ), ), ], ), SizedBox(height: 20), CircularPercentIndicator( radius: 45.0, lineWidth: 10.0, percent: percent.clamp(0.0, 1.0), center: Text( value, style: TextStyle( fontSize: 16, fontWeight: FontWeight.bold, color: valueColor, ), ), progressColor: valueColor, backgroundColor: bgColor, circularStrokeCap: CircularStrokeCap.round, animation: true, animationDuration: 1000, ), ], ), ), ); } Widget _buildInfoCard(String title, String value, IconData icon, Color bgColor, Color valueColor) { return Card( elevation: 5, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(15), ), child: Container( padding: EdgeInsets.all(15), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(15), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( title, style: TextStyle( fontSize: 16, fontWeight: FontWeight.bold, color: Colors.black87, ), ), Container( padding: EdgeInsets.all(8), decoration: BoxDecoration( color: bgColor, shape: BoxShape.circle, ), child: Icon( icon, color: valueColor, size: 18, ), ), ], ), SizedBox(height: 15), Text( value, style: TextStyle( fontSize: 22, fontWeight: FontWeight.bold, color: valueColor, ), ), SizedBox(height: 10), LinearProgressIndicator( value: title == "Umur Ayam" ? ageInWeeks / 4 : 1.0, backgroundColor: bgColor, valueColor: AlwaysStoppedAnimation(valueColor), ), ], ), ), ); } Widget _buildControlTab() { if (!mounted) return Container(); return Material( child: ListView( padding: EdgeInsets.all(16), physics: const BouncingScrollPhysics(), children: [ // Control header Container( padding: EdgeInsets.all(16), decoration: BoxDecoration( gradient: LinearGradient( colors: [Color(0xFFA82429), Color(0xFFD64045)], begin: Alignment.topLeft, end: Alignment.bottomRight, ), borderRadius: BorderRadius.circular(16), boxShadow: [ BoxShadow( color: Colors.grey.withOpacity(0.3), spreadRadius: 2, blurRadius: 5, offset: Offset(0, 3), ), ], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Kontrol Perangkat", style: TextStyle( color: Colors.white, fontSize: 20, fontWeight: FontWeight.bold, ), ), SizedBox(height: 16), Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Text( "Mode: ", style: TextStyle( color: Colors.white, fontSize: 16, fontWeight: FontWeight.bold, ), ), SizedBox(width: 10), GestureDetector( onTap: _toggleMode, child: Container( padding: EdgeInsets.symmetric(vertical: 8, horizontal: 16), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(20), boxShadow: [ BoxShadow( color: Colors.black26, blurRadius: 4, offset: Offset(0, 2), ), ], ), child: Row( children: [ Icon( isManual ? Icons.pan_tool : Icons.auto_mode, color: Color(0xFFA82429), ), SizedBox(width: 8), Text( isManual ? "Manual" : "Otomatis", style: TextStyle( color: Color(0xFFA82429), fontWeight: FontWeight.bold, ), ), ], ), ), ), ], ), ], ), ), SizedBox(height: 24), // Device controls Text( "Status Perangkat", style: TextStyle( color: Colors.black87, fontSize: 18, fontWeight: FontWeight.bold, ), ), SizedBox(height: 16), // Lampu card _buildDeviceControlCard( "Lampu", "Digunakan untuk mengatur kelembapan kandang", Icons.lightbulb, isLampOn, isManual ? (val) => _toggleLamp(val) : null, Colors.amber, ), SizedBox(height: 16), // Kipas card _buildDeviceControlCard( "Kipas", "Digunakan untuk mengatur suhu kandang", Icons.propane, isFanOn, isManual ? (val) => _toggleFan(val) : null, Colors.blue, imageAsset: isFanOn ? 'assets/Kipas_on.png' : 'assets/Kipas.png', ), SizedBox(height: 30), // User guide section Container( padding: EdgeInsets.all(16), decoration: BoxDecoration( color: Color(0xFFF8F9FA), borderRadius: BorderRadius.circular(16), border: Border.all(color: Colors.grey.shade300), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Icon(Icons.info_outline, color: Color(0xFFA82429)), SizedBox(width: 8), Text( "Catatan", style: TextStyle( color: Color(0xFFA82429), fontSize: 16, fontWeight: FontWeight.bold, ), ), ], ), SizedBox(height: 8), Text( "• Mode Otomatis: Perangkat akan bekerja secara otomatis sesuai dengan data sensor.", style: TextStyle(color: Colors.black87), ), SizedBox(height: 4), Text( "• Mode Manual: Anda dapat mengontrol perangkat secara manual melalui tombol switch.", style: TextStyle(color: Colors.black87), ), ], ), ), ], ), ); } Widget _buildDeviceControlCard( String title, String description, IconData? icon, bool isActive, Function(bool)? onChanged, Color iconColor, {String? imageAsset} ) { return Card( elevation: 3, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), ), child: Padding( padding: EdgeInsets.all(16), child: Row( children: [ Container( padding: EdgeInsets.all(12), decoration: BoxDecoration( color: isActive ? iconColor.withOpacity(0.2) : Colors.grey.shade200, borderRadius: BorderRadius.circular(12), ), child: imageAsset != null ? ColorFiltered( colorFilter: ColorFilter.mode( isActive ? iconColor : Colors.grey, BlendMode.srcATop, ), child: Image.asset( imageAsset, width: 32, height: 32, errorBuilder: (context, error, stackTrace) { print("Error loading image: $error"); return Icon( icon ?? Icons.error, color: isActive ? iconColor : Colors.grey, size: 32, ); }, ), ) : Icon( icon!, color: isActive ? iconColor : Colors.grey, size: 32, ), ), SizedBox(width: 16), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( title, style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, ), ), SizedBox(height: 4), Text( description, style: TextStyle( fontSize: 12, color: Colors.grey[600], ), ), ], ), ), Switch( value: isActive, onChanged: onChanged, activeColor: iconColor, activeTrackColor: iconColor.withOpacity(0.5), inactiveThumbColor: Colors.grey, inactiveTrackColor: Colors.grey.shade300, ), ], ), ), ); } Widget _buildDataTab() { if (!mounted) return Container(); return Material( child: RefreshIndicator( onRefresh: () async { if (mounted) { setState(() { // Memperbarui data secara manual _updateLastRefreshTime(); print("Melakukan refresh manual pada tab Data"); }); } // Tunggu sebentar untuk efek refresh yang lebih terlihat await Future.delayed(Duration(milliseconds: 800)); return Future.value(); }, child: ListView( physics: AlwaysScrollableScrollPhysics(), children: [ Padding( padding: EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ _buildLastUpdateInfo(), SizedBox(height: 16), _buildSimpleSensorInfoCards(), SizedBox(height: 24), _buildSensorDataTable(), SizedBox(height: 24), _buildMortalityReportSection(), SizedBox(height: 50), ], ), ), ], ), ), ); } Widget _buildLastUpdateInfo() { final formatter = DateFormat('HH:mm:ss'); final updateTimeString = formatter.format(_lastUpdateTime); return Container( padding: EdgeInsets.symmetric(vertical: 8, horizontal: 16), decoration: BoxDecoration( color: Colors.grey[100], borderRadius: BorderRadius.circular(20), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon(Icons.update, size: 16, color: Colors.grey[700]), SizedBox(width: 8), Text( "Terakhir diperbarui: $updateTimeString", style: TextStyle( fontSize: 12, color: Colors.grey[700], ), ), ], ), ); } Widget _buildSimpleSensorInfoCards() { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Grafik Sensor", style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, ), ), SizedBox(height: 8), Text( "Tarik ke bawah untuk menyegarkan data", style: TextStyle( fontSize: 12, color: Colors.grey[600], ), ), SizedBox(height: 16), Row( children: [ Expanded( child: _buildSensorValueCard( title: "Suhu", value: "$temperature °C", icon: Icons.thermostat, color: Color(0xFFA82429), bgColor: Color(0xFFF8D7DA), percent: temperature / 50, // Asumsi suhu maksimal 50°C ), ), SizedBox(width: 16), Expanded( child: _buildSensorValueCard( title: "Kelembapan", value: "$humidity %", icon: Icons.water_drop, color: Colors.blue, bgColor: Color(0xFFD1ECF1), percent: humidity / 100, ), ), ], ), ], ); } Widget _buildSensorValueCard({ required String title, required String value, required IconData icon, required Color color, required Color bgColor, required double percent, }) { return Card( elevation: 4, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), ), child: Padding( padding: EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( title, style: TextStyle( fontSize: 16, fontWeight: FontWeight.bold, ), ), Icon( icon, color: color, size: 24, ), ], ), SizedBox(height: 16), Text( value, style: TextStyle( fontSize: 24, fontWeight: FontWeight.bold, color: color, ), ), SizedBox(height: 16), LinearProgressIndicator( value: percent.clamp(0.0, 1.0), backgroundColor: bgColor, valueColor: AlwaysStoppedAnimation(color), minHeight: 8, borderRadius: BorderRadius.circular(4), ), ], ), ), ); } Widget _buildSensorDataTable() { if (!mounted) return Container(); return Card( elevation: 4, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), ), child: Padding( padding: EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ Text( "Data Sensor", style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, ), ), SizedBox(height: 16), Container( height: 250, decoration: BoxDecoration( border: Border.all(color: Colors.grey.shade200), borderRadius: BorderRadius.circular(8), ), child: SingleChildScrollView( child: DataTable( headingRowColor: MaterialStateProperty.all( Color(0xFFA82429).withOpacity(0.1), ), dataRowColor: MaterialStateProperty.all(Colors.white), columnSpacing: 24, headingTextStyle: TextStyle( color: Color(0xFFA82429), fontWeight: FontWeight.bold, ), columns: [ DataColumn(label: Text('Sensor')), DataColumn(label: Text('Nilai')), DataColumn(label: Text('Status')), DataColumn(label: Text('Waktu')), ], rows: [ _buildSensorTableRow( name: 'Suhu', value: temperature, sensorType: 'temperature', ), _buildSensorTableRow( name: 'Kelembapan', value: humidity, sensorType: 'humidity', ), ], ), ), ), ], ), ), ); } DataRow _buildSensorTableRow({ required String name, required double value, required String sensorType, }) { bool isNormal = true; // Tentukan apakah nilai sensor dalam kisaran normal if (sensorType == 'temperature') { int ageIndex = (ageInWeeks-1).clamp(0, tempRanges.length-1); isNormal = value >= tempRanges[ageIndex]['min'] && value <= tempRanges[ageIndex]['max']; } else if (sensorType == 'humidity') { int ageIndex = (ageInWeeks-1).clamp(0, humidityRanges.length-1); isNormal = value >= humidityRanges[ageIndex]['min'] && value <= humidityRanges[ageIndex]['max']; } return DataRow( cells: [ DataCell(Text(name)), DataCell(Text('${value.toStringAsFixed(1)}')), DataCell( Container( padding: EdgeInsets.symmetric( horizontal: 8, vertical: 4, ), decoration: BoxDecoration( color: isNormal ? Colors.green.withOpacity(0.2) : Colors.red.withOpacity(0.2), borderRadius: BorderRadius.circular(20), ), child: Text( isNormal ? 'Normal' : 'Perhatian', style: TextStyle( color: isNormal ? Colors.green : Colors.red, fontSize: 12, fontWeight: FontWeight.bold, ), ), ), ), DataCell( Text( DateFormat('HH:mm:ss').format(DateTime.now()), ), ), ], ); } Widget _buildMortalityReportSection() { return Card( elevation: 4, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), ), child: Padding( padding: EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( "Angka Kematian", style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, ), ), TextButton( onPressed: () { Navigator.push( context, MaterialPageRoute( builder: (context) => AngkaKematianPage(), ), ); }, child: Text( "Lihat Semua", style: TextStyle( color: Color(0xFFA82429), ), ), ), ], ), SizedBox(height: 16), FutureBuilder>>( future: DatabaseHelper().getAngkaKematian(), builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { return Center( child: Container( height: 100, child: CircularProgressIndicator(), ), ); } else if (snapshot.hasError) { return Text('Error: ${snapshot.error}'); } else if (!snapshot.hasData || snapshot.data!.isEmpty) { return Center( child: Padding( padding: EdgeInsets.all(20), child: Column( children: [ Icon( Icons.info_outline, size: 48, color: Colors.grey, ), SizedBox(height: 16), Text( 'Tidak ada data angka kematian.', style: TextStyle(color: Colors.grey), ), ], ), ), ); } // Get only the last 5 entries final data = snapshot.data!.take(5).toList(); int total = data.fold(0, (sum, item) => sum + (item['angka'] as int)); return Column( children: [ Container( decoration: BoxDecoration( borderRadius: BorderRadius.circular(10), border: Border.all(color: Colors.grey.shade300), ), child: DataTable( columnSpacing: 16, headingRowColor: MaterialStateProperty.all( Color(0xFFA82429).withOpacity(0.1), ), dataRowColor: MaterialStateProperty.all(Colors.white), headingTextStyle: TextStyle( color: Color(0xFFA82429), fontWeight: FontWeight.bold, ), columns: [ DataColumn(label: Text('Tanggal')), DataColumn(label: Text('Kloter')), DataColumn(label: Text('Angka')), ], rows: data.map((entry) => DataRow( cells: [ DataCell(Text(entry['tanggal'].toString())), DataCell(Text(entry['kloter'].toString())), DataCell(Text(entry['angka'].toString())), ], )).toList(), ), ), SizedBox(height: 16), Container( padding: EdgeInsets.all(12), decoration: BoxDecoration( color: Color(0xFFA82429).withOpacity(0.1), borderRadius: BorderRadius.circular(8), ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( 'Total Kematian:', style: TextStyle( fontWeight: FontWeight.bold, ), ), Text( '$total ekor', style: TextStyle( fontWeight: FontWeight.bold, color: Color(0xFFA82429), ), ), ], ), ), ], ); }, ), ], ), ), ); } // Range structs for sensor data validation final List tempRanges = [ {'min': 33.0, 'max': 35.0, 'target': 34.0}, // Minggu 1 {'min': 30.0, 'max': 33.0, 'target': 31.5}, // Minggu 2 {'min': 28.0, 'max': 30.0, 'target': 29.0}, // Minggu 3 {'min': 25.0, 'max': 28.0, 'target': 26.5} // Minggu 4 ]; final List humidityRanges = [ {'min': 60.0, 'max': 70.0, 'target': 65.0}, // Minggu 1 {'min': 60.0, 'max': 65.0, 'target': 62.5}, // Minggu 2 {'min': 60.0, 'max': 65.0, 'target': 62.5}, // Minggu 3 {'min': 55.0, 'max': 60.0, 'target': 57.5} // Minggu 4 ]; }