893 lines
35 KiB
Dart
893 lines
35 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:firebase_database/firebase_database.dart';
|
|
import 'package:intl/intl.dart'; // Untuk format tanggal
|
|
import 'package:jago/services/db_helper.dart'; // Pastikan sesuai dengan nama proyek
|
|
import 'package:excel/excel.dart' as excel;
|
|
import 'dart:io';
|
|
import 'dart:async'; // Tambahkan import untuk StreamSubscription
|
|
|
|
class HistoryPage extends StatefulWidget {
|
|
@override
|
|
_HistoryPageState createState() => _HistoryPageState();
|
|
}
|
|
|
|
class _HistoryPageState extends State<HistoryPage> {
|
|
List<Map<String, dynamic>> historyData = [];
|
|
List<Map<String, dynamic>> filteredHistory = [];
|
|
late DatabaseHelper dbHelper;
|
|
DateTime? selectedDate;
|
|
late DatabaseReference ageRef;
|
|
int ageInWeeks = 2; // Default age
|
|
int selectedAge = 0; // 0 untuk umur saat ini, 1-4 untuk umur spesifik
|
|
|
|
// Untuk filter tipe log
|
|
String selectedLogType = 'Semua';
|
|
List<String> logTypes = ['Semua', 'Relay', 'Kontrol', 'Status', 'Sensor', 'Sistem'];
|
|
|
|
// Tambahkan variabel untuk row pages
|
|
int _rowsPerPage = 10;
|
|
List<int> _rowsPerPageOptions = [5, 10, 20, 50, 100];
|
|
int _currentPage = 0;
|
|
|
|
// Daftar untuk menyimpan semua subscription
|
|
List<StreamSubscription> _subscriptions = [];
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
dbHelper = DatabaseHelper();
|
|
ageRef = FirebaseDatabase.instance.ref("chicken_age");
|
|
_loadHistoryFromDB();
|
|
_fetchAgeFromFirebase();
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
// Membersihkan semua subscription saat widget di-dispose
|
|
for (var subscription in _subscriptions) {
|
|
subscription.cancel();
|
|
}
|
|
_subscriptions.clear();
|
|
super.dispose();
|
|
}
|
|
|
|
Future<void> _loadHistoryFromDB() async {
|
|
if (!mounted) return; // Cek apakah widget masih mounted sebelum melakukan operasi
|
|
|
|
List<Map<String, dynamic>> dbData = await dbHelper.getHistory();
|
|
if (mounted) { // Cek lagi setelah operasi async
|
|
setState(() {
|
|
historyData = dbData; // Gunakan semua data tanpa filter
|
|
_applyFilter();
|
|
});
|
|
}
|
|
}
|
|
|
|
Future<void> _refreshHistory() async {
|
|
await _loadHistoryFromDB();
|
|
}
|
|
|
|
void _applyFilter() {
|
|
setState(() {
|
|
// Filter berdasarkan tanggal jika dipilih
|
|
List<Map<String, dynamic>> dateFiltered;
|
|
if (selectedDate == null) {
|
|
dateFiltered = List.from(historyData);
|
|
} else {
|
|
String selectedDateString = DateFormat('yyyy-MM-dd').format(selectedDate!);
|
|
dateFiltered = historyData.where((data) {
|
|
DateTime dateTime = DateTime.parse(data['timestamp']);
|
|
return DateFormat('yyyy-MM-dd').format(dateTime) == selectedDateString;
|
|
}).toList();
|
|
}
|
|
|
|
// Filter berdasarkan tipe log jika bukan 'Semua'
|
|
List<Map<String, dynamic>> typeFiltered;
|
|
if (selectedLogType == 'Semua') {
|
|
typeFiltered = dateFiltered;
|
|
} else {
|
|
typeFiltered = dateFiltered.where((data) {
|
|
String eventText = data['event'] ?? '';
|
|
|
|
// Ekstrak tipe dari teks event
|
|
if (selectedLogType == 'Relay') {
|
|
return eventText.contains('[RELAY]');
|
|
} else if (selectedLogType == 'Kontrol') {
|
|
return eventText.contains('[CONTROL]');
|
|
} else if (selectedLogType == 'Status') {
|
|
return eventText.contains('[STATUS]');
|
|
} else if (selectedLogType == 'Sensor') {
|
|
return eventText.contains('[SENSOR]');
|
|
} else if (selectedLogType == 'Sistem') {
|
|
return eventText.contains('[SYSTEM]');
|
|
}
|
|
|
|
return false;
|
|
}).toList();
|
|
}
|
|
|
|
// Filter berdasarkan umur ayam yang dipilih
|
|
List<Map<String, dynamic>> ageFiltered = typeFiltered.where((data) {
|
|
String eventText = data['event'] ?? '';
|
|
|
|
// Cek apakah event memiliki tag umur
|
|
if (eventText.contains("[Umur:")) {
|
|
// Ekstrak umur dari log
|
|
RegExp regExp = RegExp(r"\[Umur: (\d+) minggu\]");
|
|
Match? match = regExp.firstMatch(eventText);
|
|
|
|
if (match != null && match.groupCount >= 1) {
|
|
int logAge = int.parse(match.group(1)!);
|
|
|
|
// Jika selectedAge adalah 0, gunakan umur ayam saat ini
|
|
// Jika tidak, gunakan umur yang dipilih
|
|
if (selectedAge == 0) {
|
|
return logAge == ageInWeeks;
|
|
} else {
|
|
return logAge == selectedAge;
|
|
}
|
|
}
|
|
return false;
|
|
} else {
|
|
// Untuk log lama yang tidak memiliki tag umur, tampilkan di umur 2 minggu
|
|
if (selectedAge == 0) {
|
|
// Jika memilih "umur saat ini", tampilkan data lama jika umur saat ini adalah 2
|
|
return ageInWeeks == 2;
|
|
} else {
|
|
// Jika memilih umur spesifik, tampilkan data lama hanya jika memilih umur 2
|
|
return selectedAge == 2;
|
|
}
|
|
}
|
|
}).toList();
|
|
|
|
// Hapus data duplikat berdasarkan konten event
|
|
final Map<String, Map<String, dynamic>> uniqueEvents = {};
|
|
|
|
// Urutkan berdasarkan timestamp terbaru
|
|
ageFiltered.sort((a, b) =>
|
|
DateTime.parse(b['timestamp']).compareTo(DateTime.parse(a['timestamp']))
|
|
);
|
|
|
|
// Ambil entri unik berdasarkan konten event
|
|
for (var item in ageFiltered) {
|
|
String eventText = item['event'] ?? '';
|
|
// Hapus timestamp dari pertimbangan keunikan jika ada
|
|
String uniqueKey = eventText.replaceAll(RegExp(r'\d{2}:\d{2}:\d{2}'), '').trim();
|
|
|
|
// Simpan hanya entri pertama (yang paling baru) untuk setiap event unik
|
|
if (!uniqueEvents.containsKey(uniqueKey)) {
|
|
uniqueEvents[uniqueKey] = item;
|
|
}
|
|
}
|
|
|
|
// Konversi kembali ke list dan urutkan berdasarkan timestamp terbaru
|
|
filteredHistory = uniqueEvents.values.toList();
|
|
filteredHistory.sort((a, b) =>
|
|
DateTime.parse(b['timestamp']).compareTo(DateTime.parse(a['timestamp']))
|
|
);
|
|
|
|
// Reset halaman saat filter berubah
|
|
_currentPage = 0;
|
|
});
|
|
}
|
|
|
|
// Tambahkan method untuk paginasi
|
|
List<Map<String, dynamic>> _getPaginatedData() {
|
|
if (filteredHistory.isEmpty) return [];
|
|
|
|
int startIndex = _currentPage * _rowsPerPage;
|
|
int endIndex = startIndex + _rowsPerPage;
|
|
|
|
if (startIndex >= filteredHistory.length) {
|
|
_currentPage = 0;
|
|
startIndex = 0;
|
|
endIndex = _rowsPerPage;
|
|
}
|
|
|
|
if (endIndex > filteredHistory.length) {
|
|
endIndex = filteredHistory.length;
|
|
}
|
|
|
|
return filteredHistory.sublist(startIndex, endIndex);
|
|
}
|
|
|
|
Future<void> _pickDate(BuildContext context) async {
|
|
DateTime? pickedDate = await showDatePicker(
|
|
context: context,
|
|
initialDate: selectedDate ?? DateTime.now(),
|
|
firstDate: DateTime(2024),
|
|
lastDate: DateTime(2030),
|
|
);
|
|
|
|
if (pickedDate != null) {
|
|
setState(() {
|
|
selectedDate = pickedDate;
|
|
_applyFilter();
|
|
});
|
|
}
|
|
}
|
|
|
|
void _fetchAgeFromFirebase() {
|
|
final subscription = ageRef.onValue.listen((event) {
|
|
final data = event.snapshot.value;
|
|
if (data != null && mounted) {
|
|
int newAge = int.parse(data.toString());
|
|
if (newAge != ageInWeeks) {
|
|
print("🐔 Umur ayam berubah dari $ageInWeeks ke $newAge");
|
|
setState(() {
|
|
ageInWeeks = newAge;
|
|
});
|
|
// Muat ulang history saat umur berubah
|
|
_loadHistoryFromDB();
|
|
}
|
|
}
|
|
});
|
|
|
|
// Tambahkan subscription ke daftar
|
|
_subscriptions.add(subscription);
|
|
}
|
|
|
|
void _downloadHistoryAsExcel() async {
|
|
// Terapkan filter duplikat pada historyData
|
|
final Map<String, Map<String, dynamic>> uniqueEvents = {};
|
|
|
|
// Buat salinan historyData dan urutkan berdasarkan timestamp terbaru
|
|
List<Map<String, dynamic>> sortedData = List.from(historyData);
|
|
sortedData.sort((a, b) =>
|
|
DateTime.parse(b['timestamp']).compareTo(DateTime.parse(a['timestamp']))
|
|
);
|
|
|
|
// Filter berdasarkan umur ayam yang dipilih
|
|
List<Map<String, dynamic>> ageFilteredData = sortedData.where((data) {
|
|
String eventText = data['event'] ?? '';
|
|
|
|
if (eventText.contains("[Umur:")) {
|
|
RegExp regExp = RegExp(r"\[Umur: (\d+) minggu\]");
|
|
Match? match = regExp.firstMatch(eventText);
|
|
|
|
if (match != null && match.groupCount >= 1) {
|
|
int logAge = int.parse(match.group(1)!);
|
|
|
|
// Jika selectedAge adalah 0, gunakan umur ayam saat ini
|
|
// Jika tidak, gunakan umur yang dipilih
|
|
if (selectedAge == 0) {
|
|
return logAge == ageInWeeks;
|
|
} else {
|
|
return logAge == selectedAge;
|
|
}
|
|
}
|
|
return false;
|
|
} else {
|
|
// Untuk log lama yang tidak memiliki tag umur, tampilkan di umur 2 minggu
|
|
if (selectedAge == 0) {
|
|
// Jika memilih "umur saat ini", tampilkan data lama jika umur saat ini adalah 2
|
|
return ageInWeeks == 2;
|
|
} else {
|
|
// Jika memilih umur spesifik, tampilkan data lama hanya jika memilih umur 2
|
|
return selectedAge == 2;
|
|
}
|
|
}
|
|
}).toList();
|
|
|
|
// Ambil entri unik berdasarkan konten event
|
|
for (var item in ageFilteredData) {
|
|
String eventText = item['event'] ?? '';
|
|
// Hapus timestamp dari pertimbangan keunikan jika ada
|
|
String uniqueKey = eventText.replaceAll(RegExp(r'\d{2}:\d{2}:\d{2}'), '').trim();
|
|
|
|
// Simpan hanya entri pertama (yang paling baru) untuk setiap event unik
|
|
if (!uniqueEvents.containsKey(uniqueKey)) {
|
|
uniqueEvents[uniqueKey] = item;
|
|
}
|
|
}
|
|
|
|
// Konversi kembali ke list dan urutkan berdasarkan timestamp
|
|
List<Map<String, dynamic>> uniqueData = uniqueEvents.values.toList();
|
|
uniqueData.sort((a, b) =>
|
|
DateTime.parse(b['timestamp']).compareTo(DateTime.parse(a['timestamp']))
|
|
);
|
|
|
|
var excelFile = excel.Excel.createExcel();
|
|
var sheet = excelFile['Sheet1'];
|
|
sheet.appendRow(['Timestamp', 'Tipe', 'Event', 'Lampu', 'Kipas', 'Umur Ayam']);
|
|
for (var data in uniqueData) {
|
|
String lampuStatus = data['lampu'] == 1 ? 'Hidup' : 'Mati';
|
|
String kipasStatus = data['kipas'] == 1 ? 'Hidup' : 'Mati';
|
|
String eventText = data['event'] ?? '';
|
|
|
|
// Ekstrak tipe dari teks event
|
|
String type = 'Umum';
|
|
if (eventText.contains('[RELAY]')) {
|
|
type = 'Relay';
|
|
} else if (eventText.contains('[CONTROL]')) {
|
|
type = 'Kontrol';
|
|
} else if (eventText.contains('[STATUS]')) {
|
|
type = 'Status';
|
|
} else if (eventText.contains('[SENSOR]')) {
|
|
type = 'Sensor';
|
|
} else if (eventText.contains('[SYSTEM]')) {
|
|
type = 'Sistem';
|
|
}
|
|
|
|
// Hapus tag tipe dari teks event untuk tampilan Excel
|
|
eventText = eventText.replaceAll(RegExp(r'\[(RELAY|CONTROL|STATUS|SENSOR|SYSTEM)\]\s*'), '');
|
|
|
|
// Ekstrak umur dari teks event jika ada
|
|
int eventAge = 2; // Default umur untuk data lama tanpa tag
|
|
if (eventText.contains("[Umur:")) {
|
|
RegExp regExp = RegExp(r"\[Umur: (\d+) minggu\]");
|
|
Match? match = regExp.firstMatch(eventText);
|
|
|
|
if (match != null && match.groupCount >= 1) {
|
|
eventAge = int.parse(match.group(1)!);
|
|
}
|
|
|
|
// Hapus tag umur dari tampilan
|
|
eventText = eventText.replaceAll(regExp, '').trim();
|
|
}
|
|
|
|
sheet.appendRow([
|
|
data['timestamp'],
|
|
type,
|
|
eventText,
|
|
lampuStatus,
|
|
kipasStatus,
|
|
eventAge
|
|
]);
|
|
}
|
|
|
|
// Tentukan nama file berdasarkan mode filter umur
|
|
int exportAge = selectedAge == 0 ? ageInWeeks : selectedAge;
|
|
|
|
final directory = Directory('/storage/emulated/0/Documents/Data History');
|
|
if (!(await directory.exists())) {
|
|
await directory.create(recursive: true);
|
|
}
|
|
String filePath = '${directory.path}/History_${DateFormat('yyyyMMdd').format(DateTime.now())}_Umur${exportAge}minggu.xlsx';
|
|
File(filePath).writeAsBytesSync(excelFile.encode()!);
|
|
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('Data history berhasil diunduh sebagai Excel di $filePath')));
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
// Dapatkan data yang sudah dipaginasi
|
|
final paginatedData = _getPaginatedData();
|
|
final int totalPages = (filteredHistory.length / _rowsPerPage).ceil();
|
|
|
|
// Tentukan judul AppBar berdasarkan mode filter umur
|
|
String appBarTitle = selectedAge == 0
|
|
? 'Riwayat Aktivitas (Umur: $ageInWeeks minggu)'
|
|
: 'Riwayat Aktivitas (Umur: $selectedAge minggu)';
|
|
|
|
return Scaffold(
|
|
appBar: AppBar(
|
|
backgroundColor: Color(0xFFA82429),
|
|
elevation: 0,
|
|
leading: IconButton(
|
|
icon: Icon(Icons.arrow_back, color: Colors.white),
|
|
onPressed: () => Navigator.pop(context),
|
|
),
|
|
title: Text(
|
|
appBarTitle,
|
|
style: TextStyle(
|
|
color: Colors.white,
|
|
fontWeight: FontWeight.bold,
|
|
)
|
|
),
|
|
),
|
|
body: Column(
|
|
children: [
|
|
// Header section with curved background
|
|
Container(
|
|
padding: EdgeInsets.fromLTRB(16, 0, 16, 20),
|
|
decoration: BoxDecoration(
|
|
color: Color(0xFFA82429),
|
|
borderRadius: BorderRadius.only(
|
|
bottomLeft: Radius.circular(30),
|
|
bottomRight: Radius.circular(30),
|
|
),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: Colors.grey.withOpacity(0.5),
|
|
spreadRadius: 2,
|
|
blurRadius: 5,
|
|
offset: Offset(0, 3),
|
|
),
|
|
],
|
|
),
|
|
child: Column(
|
|
children: [
|
|
// Tanggal Filter
|
|
Row(
|
|
children: [
|
|
Expanded(
|
|
child: Container(
|
|
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(10),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: Colors.black.withOpacity(0.1),
|
|
blurRadius: 4,
|
|
offset: Offset(0, 2),
|
|
),
|
|
],
|
|
),
|
|
child: Row(
|
|
children: [
|
|
Icon(
|
|
Icons.calendar_today,
|
|
color: Color(0xFFA82429),
|
|
size: 20,
|
|
),
|
|
SizedBox(width: 8),
|
|
Expanded(
|
|
child: Text(
|
|
selectedDate == null
|
|
? 'Semua Tanggal'
|
|
: DateFormat('dd/MM/yyyy').format(selectedDate!),
|
|
style: TextStyle(
|
|
fontSize: 14,
|
|
fontWeight: FontWeight.bold,
|
|
color: Colors.black87,
|
|
),
|
|
),
|
|
),
|
|
TextButton(
|
|
onPressed: () => _pickDate(context),
|
|
child: Text(
|
|
'Pilih',
|
|
style: TextStyle(
|
|
color: Color(0xFFA82429),
|
|
fontWeight: FontWeight.bold,
|
|
),
|
|
),
|
|
style: TextButton.styleFrom(
|
|
minimumSize: Size(0, 0),
|
|
padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
SizedBox(height: 12),
|
|
// Filter umur ayam dan tipe log
|
|
Row(
|
|
children: [
|
|
Expanded(
|
|
child: Container(
|
|
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(10),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: Colors.black.withOpacity(0.1),
|
|
blurRadius: 4,
|
|
offset: Offset(0, 2),
|
|
),
|
|
],
|
|
),
|
|
child: DropdownButtonHideUnderline(
|
|
child: DropdownButton<int>(
|
|
value: selectedAge,
|
|
isExpanded: true,
|
|
icon: Icon(Icons.filter_list, color: Color(0xFFA82429)),
|
|
items: [
|
|
DropdownMenuItem<int>(
|
|
value: 0,
|
|
child: Text('Umur Saat Ini (${ageInWeeks} minggu)'),
|
|
),
|
|
DropdownMenuItem<int>(
|
|
value: 1,
|
|
child: Text('Umur 1 minggu'),
|
|
),
|
|
DropdownMenuItem<int>(
|
|
value: 2,
|
|
child: Text('Umur 2 minggu'),
|
|
),
|
|
DropdownMenuItem<int>(
|
|
value: 3,
|
|
child: Text('Umur 3 minggu'),
|
|
),
|
|
DropdownMenuItem<int>(
|
|
value: 4,
|
|
child: Text('Umur 4 minggu'),
|
|
),
|
|
],
|
|
onChanged: (int? newValue) {
|
|
if (newValue != null) {
|
|
setState(() {
|
|
selectedAge = newValue;
|
|
_applyFilter();
|
|
});
|
|
}
|
|
},
|
|
),
|
|
),
|
|
),
|
|
),
|
|
SizedBox(width: 10),
|
|
Container(
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(10),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: Colors.black.withOpacity(0.1),
|
|
blurRadius: 4,
|
|
offset: Offset(0, 2),
|
|
),
|
|
],
|
|
),
|
|
child: IconButton(
|
|
icon: Icon(Icons.file_download, color: Color(0xFFA82429)),
|
|
onPressed: _downloadHistoryAsExcel,
|
|
tooltip: 'Unduh Data Excel',
|
|
),
|
|
),
|
|
],
|
|
),
|
|
SizedBox(height: 12),
|
|
// Filter tipe log
|
|
Container(
|
|
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
|
decoration: BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.circular(10),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: Colors.black.withOpacity(0.1),
|
|
blurRadius: 4,
|
|
offset: Offset(0, 2),
|
|
),
|
|
],
|
|
),
|
|
child: DropdownButtonHideUnderline(
|
|
child: DropdownButton<String>(
|
|
value: selectedLogType,
|
|
isExpanded: true,
|
|
icon: Icon(Icons.category, color: Color(0xFFA82429)),
|
|
items: logTypes.map((String type) {
|
|
IconData typeIcon;
|
|
switch (type) {
|
|
case 'Relay':
|
|
typeIcon = Icons.power;
|
|
break;
|
|
case 'Kontrol':
|
|
typeIcon = Icons.settings;
|
|
break;
|
|
case 'Status':
|
|
typeIcon = Icons.info;
|
|
break;
|
|
case 'Sensor':
|
|
typeIcon = Icons.sensors;
|
|
break;
|
|
case 'Sistem':
|
|
typeIcon = Icons.computer;
|
|
break;
|
|
default:
|
|
typeIcon = Icons.list;
|
|
}
|
|
|
|
return DropdownMenuItem<String>(
|
|
value: type,
|
|
child: Row(
|
|
children: [
|
|
Icon(typeIcon, size: 18, color: Color(0xFFA82429)),
|
|
SizedBox(width: 8),
|
|
Text(type),
|
|
],
|
|
),
|
|
);
|
|
}).toList(),
|
|
onChanged: (String? newValue) {
|
|
if (newValue != null) {
|
|
setState(() {
|
|
selectedLogType = newValue;
|
|
_applyFilter();
|
|
});
|
|
}
|
|
},
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
|
|
// Paginasi controls
|
|
Padding(
|
|
padding: EdgeInsets.fromLTRB(16, 16, 16, 8),
|
|
child: Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
Row(
|
|
children: [
|
|
Text(
|
|
'Baris per halaman: ',
|
|
style: TextStyle(fontSize: 14, color: Colors.grey[600]),
|
|
),
|
|
Container(
|
|
padding: EdgeInsets.symmetric(horizontal: 8, vertical: 0),
|
|
decoration: BoxDecoration(
|
|
border: Border.all(color: Colors.grey.shade300),
|
|
borderRadius: BorderRadius.circular(4),
|
|
),
|
|
child: DropdownButtonHideUnderline(
|
|
child: DropdownButton<int>(
|
|
value: _rowsPerPage,
|
|
isDense: true,
|
|
items: _rowsPerPageOptions.map((int value) {
|
|
return DropdownMenuItem<int>(
|
|
value: value,
|
|
child: Text(value.toString()),
|
|
);
|
|
}).toList(),
|
|
onChanged: (newValue) {
|
|
if (newValue != null) {
|
|
setState(() {
|
|
_rowsPerPage = newValue;
|
|
_currentPage = 0; // Reset ke halaman pertama
|
|
});
|
|
}
|
|
},
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
if (totalPages > 0)
|
|
Row(
|
|
children: [
|
|
IconButton(
|
|
icon: Icon(Icons.arrow_back_ios, size: 16),
|
|
color: _currentPage > 0 ? Color(0xFFA82429) : Colors.grey,
|
|
onPressed: _currentPage > 0
|
|
? () {
|
|
setState(() {
|
|
_currentPage--;
|
|
});
|
|
}
|
|
: null,
|
|
),
|
|
Container(
|
|
padding: EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
|
decoration: BoxDecoration(
|
|
color: Color(0xFFA82429),
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
child: Text(
|
|
'${_currentPage + 1} / $totalPages',
|
|
style: TextStyle(
|
|
color: Colors.white,
|
|
fontWeight: FontWeight.bold,
|
|
),
|
|
),
|
|
),
|
|
IconButton(
|
|
icon: Icon(Icons.arrow_forward_ios, size: 16),
|
|
color: _currentPage < totalPages - 1 ? Color(0xFFA82429) : Colors.grey,
|
|
onPressed: _currentPage < totalPages - 1
|
|
? () {
|
|
setState(() {
|
|
_currentPage++;
|
|
});
|
|
}
|
|
: null,
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
|
|
// List of history items
|
|
Expanded(
|
|
child: RefreshIndicator(
|
|
onRefresh: _refreshHistory,
|
|
color: Color(0xFFA82429),
|
|
child: paginatedData.isEmpty
|
|
? Center(
|
|
child: Column(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
Icon(
|
|
Icons.history_toggle_off,
|
|
size: 64,
|
|
color: Colors.grey,
|
|
),
|
|
SizedBox(height: 16),
|
|
Text(
|
|
selectedAge == 0
|
|
? 'Tidak ada riwayat untuk umur ayam $ageInWeeks minggu'
|
|
: 'Tidak ada riwayat untuk umur ayam $selectedAge minggu',
|
|
style: TextStyle(
|
|
color: Colors.grey[600],
|
|
fontSize: 16,
|
|
),
|
|
textAlign: TextAlign.center,
|
|
),
|
|
],
|
|
),
|
|
)
|
|
: ListView.builder(
|
|
padding: EdgeInsets.symmetric(horizontal: 16),
|
|
itemCount: paginatedData.length,
|
|
itemBuilder: (context, index) {
|
|
final data = paginatedData[index];
|
|
final DateTime dateTime = DateTime.parse(data['timestamp']);
|
|
String eventText = data['event'] ?? 'Data tidak tersedia';
|
|
|
|
// Ekstrak tipe dari teks event
|
|
Color cardColor;
|
|
IconData iconData;
|
|
|
|
if (eventText.contains('[RELAY]')) {
|
|
eventText = eventText.replaceAll('[RELAY] ', '');
|
|
cardColor = Colors.blue;
|
|
iconData = Icons.power;
|
|
} else if (eventText.contains('[CONTROL]')) {
|
|
eventText = eventText.replaceAll('[CONTROL] ', '');
|
|
cardColor = Colors.green;
|
|
iconData = Icons.settings;
|
|
} else if (eventText.contains('[STATUS]')) {
|
|
eventText = eventText.replaceAll('[STATUS] ', '');
|
|
cardColor = Colors.orange;
|
|
iconData = Icons.info;
|
|
} else if (eventText.contains('[SENSOR]')) {
|
|
eventText = eventText.replaceAll('[SENSOR] ', '');
|
|
cardColor = Colors.purple;
|
|
iconData = Icons.sensors;
|
|
} else if (eventText.contains('[SYSTEM]')) {
|
|
eventText = eventText.replaceAll('[SYSTEM] ', '');
|
|
cardColor = Colors.red;
|
|
iconData = Icons.computer;
|
|
} else {
|
|
cardColor = Colors.blueGrey;
|
|
iconData = Icons.history;
|
|
}
|
|
|
|
// Ekstrak umur dari teks event jika ada
|
|
int eventAge = 2; // Default umur untuk data lama tanpa tag
|
|
if (eventText.contains("[Umur:")) {
|
|
RegExp regExp = RegExp(r"\[Umur: (\d+) minggu\]");
|
|
Match? match = regExp.firstMatch(eventText);
|
|
|
|
if (match != null && match.groupCount >= 1) {
|
|
eventAge = int.parse(match.group(1)!);
|
|
}
|
|
|
|
// Hapus tag umur dari tampilan
|
|
eventText = eventText.replaceAll(regExp, '').trim();
|
|
}
|
|
|
|
return Card(
|
|
elevation: 3,
|
|
margin: EdgeInsets.only(bottom: 12),
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(16),
|
|
),
|
|
child: ClipRRect(
|
|
borderRadius: BorderRadius.circular(16),
|
|
child: Column(
|
|
children: [
|
|
// Colored header
|
|
Container(
|
|
color: cardColor,
|
|
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 12),
|
|
child: Row(
|
|
children: [
|
|
Icon(
|
|
iconData,
|
|
color: Colors.white,
|
|
size: 20,
|
|
),
|
|
SizedBox(width: 8),
|
|
Expanded(
|
|
child: Text(
|
|
DateFormat('dd/MM/yyyy HH:mm').format(dateTime),
|
|
style: TextStyle(
|
|
color: Colors.white,
|
|
fontWeight: FontWeight.bold,
|
|
),
|
|
),
|
|
),
|
|
Container(
|
|
padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
|
decoration: BoxDecoration(
|
|
color: Colors.white.withOpacity(0.3),
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
child: Text(
|
|
'Umur: $eventAge minggu',
|
|
style: TextStyle(
|
|
fontSize: 12,
|
|
fontWeight: FontWeight.bold,
|
|
color: Colors.white,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
// Status perangkat
|
|
Padding(
|
|
padding: EdgeInsets.all(16),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
eventText,
|
|
style: TextStyle(
|
|
fontSize: 16,
|
|
color: Colors.black87,
|
|
),
|
|
),
|
|
SizedBox(height: 8),
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
_buildDeviceStatus(
|
|
'Lampu',
|
|
data['lampu'] == 1,
|
|
Icons.lightbulb,
|
|
Colors.amber
|
|
),
|
|
_buildDeviceStatus(
|
|
'Kipas',
|
|
data['kipas'] == 1,
|
|
Icons.propane,
|
|
Colors.blue
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildDeviceStatus(String title, bool isActive, IconData icon, Color activeColor) {
|
|
return Container(
|
|
padding: EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
|
decoration: BoxDecoration(
|
|
color: isActive ? activeColor.withOpacity(0.2) : Colors.grey.withOpacity(0.2),
|
|
borderRadius: BorderRadius.circular(20),
|
|
),
|
|
child: Row(
|
|
children: [
|
|
Icon(
|
|
icon,
|
|
size: 16,
|
|
color: isActive ? activeColor : Colors.grey,
|
|
),
|
|
SizedBox(width: 4),
|
|
Text(
|
|
'$title: ${isActive ? "Aktif" : "Nonaktif"}',
|
|
style: TextStyle(
|
|
fontSize: 12,
|
|
fontWeight: FontWeight.bold,
|
|
color: isActive ? activeColor : Colors.grey,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|