MIF_E31221269/lib/screens/dashboard.dart

1409 lines
44 KiB
Dart

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<DashboardPage> 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<Color>(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>(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<List<Map<String, dynamic>>>(
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<dynamic> 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<dynamic> 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
];
}