TKK_E32231105/lib/providers/panen_provider.dart

499 lines
15 KiB
Dart

import 'package:flutter/foundation.dart';
import 'package:uuid/uuid.dart';
import 'package:firebase_database/firebase_database.dart';
import '../models/panen_model.dart';
class PanenProvider extends ChangeNotifier {
final FirebaseDatabase _database = FirebaseDatabase.instance;
// Tracking snapshot untuk time-windowing logic
Map<String, int> _snapshotPagiHariIni = {}; // kandangId -> nilaiPagi
Map<String, int> _snapshotSoreHariIni = {}; // kandangId -> nilaiSore
final List<Panen> _panens = [
// Tanggal 7 March 2026 - Kandang 1
Panen(
id: 'panen_1',
kandangId: 'kandang_1',
kandangNama: 'Kandang 1',
jumlahTelur: 45,
tanggalPanen: DateTime(2026, 3, 7, 9, 0), // 7 March 2026, 09:00
jam: '09:00',
catatan: 'Panen pagi - Kondisi baik',
),
Panen(
id: 'panen_2',
kandangId: 'kandang_1',
kandangNama: 'Kandang 1',
jumlahTelur: 42,
tanggalPanen: DateTime(2026, 3, 7, 15, 0), // 7 March 2026, 15:00
jam: '15:00',
catatan: 'Panen sore - Ada 3 telur pecah',
),
// Tanggal 7 March 2026 - Kandang 2
Panen(
id: 'panen_3',
kandangId: 'kandang_2',
kandangNama: 'Kandang 2',
jumlahTelur: 68,
tanggalPanen: DateTime(2026, 3, 7, 9, 0), // 7 March 2026, 09:00
jam: '09:00',
catatan: 'Panen pagi - Produksi meningkat',
),
Panen(
id: 'panen_4',
kandangId: 'kandang_2',
kandangNama: 'Kandang 2',
jumlahTelur: 71,
tanggalPanen: DateTime(2026, 3, 7, 15, 0), // 7 March 2026, 15:00
jam: '15:00',
catatan: 'Panen sore - Normal',
),
// Tanggal 8 March 2026 - Kandang 1
Panen(
id: 'panen_5',
kandangId: 'kandang_1',
kandangNama: 'Kandang 1',
jumlahTelur: 48,
tanggalPanen: DateTime(2026, 3, 8, 9, 0), // 8 March 2026, 09:00
jam: '09:00',
catatan: 'Panen pagi - Produksi stabil',
),
Panen(
id: 'panen_6',
kandangId: 'kandang_1',
kandangNama: 'Kandang 1',
jumlahTelur: 44,
tanggalPanen: DateTime(2026, 3, 8, 15, 0), // 8 March 2026, 15:00
jam: '15:00',
catatan: 'Panen sore - Normal',
),
// Tanggal 8 March 2026 - Kandang 2
Panen(
id: 'panen_7',
kandangId: 'kandang_2',
kandangNama: 'Kandang 2',
jumlahTelur: 65,
tanggalPanen: DateTime(2026, 3, 8, 9, 0), // 8 March 2026, 09:00
jam: '09:00',
catatan: 'Panen pagi - Kondisi baik',
),
Panen(
id: 'panen_8',
kandangId: 'kandang_2',
kandangNama: 'Kandang 2',
jumlahTelur: 69,
tanggalPanen: DateTime(2026, 3, 8, 15, 0), // 8 March 2026, 15:00
jam: '15:00',
catatan: 'Panen sore - Normal',
),
];
List<Panen> get panens => _panens;
List<Panen> getPanenByKandang(String kandangId) {
return _panens.where((p) => p.kandangId == kandangId).toList();
}
List<Panen> getPanenByDate(DateTime date) {
return _panens.where((p) {
return p.tanggalPanen.year == date.year &&
p.tanggalPanen.month == date.month &&
p.tanggalPanen.day == date.day;
}).toList();
}
List<Panen> getPanenByDateRange(DateTime startDate, DateTime endDate) {
return _panens.where((p) {
return p.tanggalPanen.isAfter(startDate) &&
p.tanggalPanen.isBefore(endDate.add(const Duration(days: 1)));
}).toList();
}
void addPanen(
String kandangId,
String kandangNama,
int jumlahTelur,
DateTime tanggalPanen,
String jam,
String catatan,
) {
const uuid = Uuid();
final newPanen = Panen(
id: uuid.v4(),
kandangId: kandangId,
kandangNama: kandangNama,
jumlahTelur: jumlahTelur,
tanggalPanen: tanggalPanen,
jam: jam,
catatan: catatan,
);
_panens.add(newPanen);
_panens.sort((a, b) => b.tanggalPanen.compareTo(a.tanggalPanen));
notifyListeners();
}
void deletePanen(String id) {
_panens.removeWhere((p) => p.id == id);
notifyListeners();
}
int getTotalPanenHari(DateTime date, String kandangId) {
return _panens.where((p) {
return p.kandangId == kandangId &&
p.tanggalPanen.year == date.year &&
p.tanggalPanen.month == date.month &&
p.tanggalPanen.day == date.day;
}).fold(0, (sum, p) => sum + p.jumlahTelur);
}
/// Get total panen per hari untuk semua kandang
int getTotalHarianSemuaKandang(DateTime date) {
return _panens.where((p) {
return p.tanggalPanen.year == date.year &&
p.tanggalPanen.month == date.month &&
p.tanggalPanen.day == date.day;
}).fold(0, (sum, p) => sum + p.jumlahTelur);
}
/// Get last harvest info untuk kandang
/// Berguna untuk dashboard "Status Panen Terakhir"
Panen? getLastHarvestStatus(String kandangId) {
final panenKandang =
_panens.where((p) => p.kandangId == kandangId).toList();
if (panenKandang.isEmpty) return null;
panenKandang.sort((a, b) => b.tanggalPanen.compareTo(a.tanggalPanen));
return panenKandang.first;
}
/// Get panen history per kandang grouped by date untuk riwayat
Map<DateTime, List<Panen>> getPanenHistoryGroupedByDate(String kandangId) {
final map = <DateTime, List<Panen>>{};
final panenKandang =
_panens.where((p) => p.kandangId == kandangId).toList();
for (final panen in panenKandang) {
final dateKey = DateTime(
panen.tanggalPanen.year,
panen.tanggalPanen.month,
panen.tanggalPanen.day,
);
map.putIfAbsent(dateKey, () => []).add(panen);
}
return map;
}
/// Add panen dari sensor snapshot dengan tracking jenis panen (pagi/sore)
void addPanenFromSensor(
String kandangId,
String kandangNama,
int sensorValue,
String jenisPanen,
String jam, {
int? panenSebelumnya,
}) {
const uuid = Uuid();
// Jika panen sore dan ada panen pagi, hitung delta
int jumlahTelur = sensorValue;
if (jenisPanen == 'sore' && panenSebelumnya != null) {
jumlahTelur = sensorValue - panenSebelumnya;
}
final newPanen = Panen(
id: uuid.v4(),
kandangId: kandangId,
kandangNama: kandangNama,
jumlahTelur: jumlahTelur,
tanggalPanen: DateTime.now(),
jam: jam,
catatan: 'Panen $jenisPanen - Auto capture dari sensor',
jenisPanen: jenisPanen,
panenSebelumnya: panenSebelumnya,
sensorSnapshot: sensorValue,
);
_panens.add(newPanen);
_panens.sort((a, b) => b.tanggalPanen.compareTo(a.tanggalPanen));
notifyListeners();
}
// ============================================
// TIME-WINDOWING & SNAPSHOT TRACKING
// ============================================
/// Get nilai panen pagi hari ini untuk delta calculation sore
int? getPanenPagiTodayValue(String kandangId) {
return _snapshotPagiHariIni[kandangId];
}
/// Get nilai panen sore hari ini
int? getPanenSoreTodayValue(String kandangId) {
return _snapshotSoreHariIni[kandangId];
}
/// Cek apakah panen pagi sudah di-record hari ini
bool isPanenPagiRecordedToday(String kandangId) {
return _snapshotPagiHariIni.containsKey(kandangId);
}
/// Cek apakah panen sore sudah di-record hari ini
bool isPanenSoreRecordedToday(String kandangId) {
return _snapshotSoreHariIni.containsKey(kandangId);
}
/// Reset snapshot harian (call setelah panen sore selesai atau tengah malam)
void resetDailySnapshots() {
_snapshotPagiHariIni.clear();
_snapshotSoreHariIni.clear();
if (kDebugMode) {
print('✅ Daily snapshots reset');
}
notifyListeners();
}
/// AUTO-CAPTURE DARI SCHEDULER
/// Panggil ini saat scheduler trigger jam 09:00
Future<void> captureScheduledPanenPagi(
String kandangId,
String kandangNama,
int sensorValue,
) async {
try {
// Jika sudah di-record pagi ini, skip
if (isPanenPagiRecordedToday(kandangId)) {
if (kDebugMode) {
print('⚠️ Panen pagi untuk $kandangId sudah di-record hari ini');
}
return;
}
// Time-Window Pagi: Ambil nilai sensor sebagai total telur pagi
// (Karena ini akumulasi dari jam 15:01 kemarin hingga 09:00 hari ini)
int jumlahTelur = sensorValue;
// Simpan ke snapshot untuk reference delta sore nanti
_snapshotPagiHariIni[kandangId] = sensorValue;
const uuid = Uuid();
final newPanen = Panen(
id: uuid.v4(),
kandangId: kandangId,
kandangNama: kandangNama,
jumlahTelur: jumlahTelur,
tanggalPanen: DateTime.now(),
jam: '09:00',
catatan: 'Auto-capture PAGI dari scheduler',
jenisPanen: 'pagi',
sensorSnapshot: sensorValue,
panenSebelumnya: null, // Pagi tidak butuh panenSebelumnya
);
_panens.add(newPanen);
_panens.sort((a, b) => b.tanggalPanen.compareTo(a.tanggalPanen));
// Simpan ke Firebase
await _savePanenToFirebase(newPanen);
if (kDebugMode) {
print('✅ Panen Pagi recorded untuk $kandangNama: $jumlahTelur telur');
}
notifyListeners();
} catch (e) {
if (kDebugMode) {
print('❌ Error capturing panen pagi: $e');
}
}
}
/// AUTO-CAPTURE DARI SCHEDULER
/// Panggil ini saat scheduler trigger jam 15:00
/// Dengan delta calculation: nilai_sore - nilai_pagi
Future<void> captureScheduledPanenSore(
String kandangId,
String kandangNama,
int sensorValue,
) async {
try {
// Jika sudah di-record sore ini, skip
if (isPanenSoreRecordedToday(kandangId)) {
if (kDebugMode) {
print('⚠️ Panen sore untuk $kandangId sudah di-record hari ini');
}
return;
}
// Ambil nilai pagi hari ini
int? nilaiPagi = getPanenPagiTodayValue(kandangId);
// Time-Window Sore: Hitung delta dari nilai pagi
int jumlahTelur = sensorValue;
if (nilaiPagi != null) {
jumlahTelur = sensorValue - nilaiPagi;
} else {
if (kDebugMode) {
print(
'⚠️ Nilai pagi tidak ditemukan untuk $kandangId, gunakan nilai sensor langsung',
);
}
}
// Simpan ke snapshot
_snapshotSoreHariIni[kandangId] = sensorValue;
const uuid = Uuid();
final newPanen = Panen(
id: uuid.v4(),
kandangId: kandangId,
kandangNama: kandangNama,
jumlahTelur: jumlahTelur,
tanggalPanen: DateTime.now(),
jam: '15:00',
catatan:
'Auto-capture SORE dari scheduler${nilaiPagi != null ? ' (delta: $sensorValue - $nilaiPagi)' : ''}',
jenisPanen: 'sore',
sensorSnapshot: sensorValue,
panenSebelumnya: nilaiPagi,
);
_panens.add(newPanen);
_panens.sort((a, b) => b.tanggalPanen.compareTo(a.tanggalPanen));
// Simpan ke Firebase
await _savePanenToFirebase(newPanen);
if (kDebugMode) {
print(
'✅ Panen Sore recorded untuk $kandangNama: $jumlahTelur telur${nilaiPagi != null ? ' (delta: $sensorValue - $nilaiPagi)' : ''}',
);
}
notifyListeners();
} catch (e) {
if (kDebugMode) {
print('❌ Error capturing panen sore: $e');
}
}
}
/// Simpan panen ke Firebase
Future<void> _savePanenToFirebase(Panen panen) async {
try {
final ref = _database.ref('riwayat').push();
await ref.set({
'id': panen.id,
'kandang_id': panen.kandangId,
'kandang_nama': panen.kandangNama,
'jumlah_telur': panen.jumlahTelur,
'tanggal_panen': panen.tanggalPanen.toIso8601String(),
'jam': panen.jam,
'jenis_panen': panen.jenisPanen,
'sensor_snapshot': panen.sensorSnapshot,
'panen_sebelumnya': panen.panenSebelumnya,
'catatan': panen.catatan,
});
if (kDebugMode) {
print('✅ Panen saved to Firebase: ${panen.id}');
}
} catch (e) {
if (kDebugMode) {
print('❌ Error saving panen to Firebase: $e');
}
}
}
/// Load snapshots dari Firebase ketika app start (untuk sync state)
Future<void> loadTodaySnapshots() async {
try {
final today = DateTime.now();
final todayKey =
'${today.year}-${today.month.toString().padLeft(2, '0')}-${today.day.toString().padLeft(2, '0')}';
final ref = _database.ref('panen_snapshot/$todayKey');
final snapshot = await ref.get();
if (snapshot.exists) {
final data = Map<dynamic, dynamic>.from(snapshot.value as Map);
// Load snapshot pagi
if (data.containsKey('pagi')) {
final pagiData = Map<dynamic, dynamic>.from(data['pagi'] as Map);
pagiData.forEach((kandangId, value) {
if (kandangId != 'timestamp') {
_snapshotPagiHariIni[kandangId] = value as int;
}
});
}
// Load snapshot sore
if (data.containsKey('sore')) {
final soreData = Map<dynamic, dynamic>.from(data['sore'] as Map);
soreData.forEach((kandangId, value) {
if (kandangId != 'timestamp' &&
!kandangId.toString().startsWith('delta_')) {
_snapshotSoreHariIni[kandangId] = value as int;
}
});
}
if (kDebugMode) {
print('✅ Today snapshots loaded from Firebase');
print(' Pagi: $_snapshotPagiHariIni');
print(' Sore: $_snapshotSoreHariIni');
}
}
notifyListeners();
} catch (e) {
if (kDebugMode) {
print('⚠️ Error loading today snapshots: $e');
}
}
}
/// Restore historical panen dari Firebase saat app start
Future<void> restorePanenHistoryFromFirebase() async {
try {
final ref = _database.ref('riwayat');
final snapshot = await ref.get();
if (snapshot.exists) {
final data = Map<dynamic, dynamic>.from(snapshot.value as Map);
_panens.clear();
data.forEach((key, value) {
final panenData = Map<dynamic, dynamic>.from(value as Map);
final panen = Panen(
id: panenData['id'] ?? '',
kandangId: panenData['kandang_id'] ?? '',
kandangNama: panenData['kandang_nama'] ?? '',
jumlahTelur: panenData['jumlah_telur'] ?? 0,
tanggalPanen: DateTime.parse(panenData['tanggal_panen'] ?? ''),
jam: panenData['jam'] ?? '',
catatan: panenData['catatan'] ?? '',
jenisPanen: panenData['jenis_panen'],
sensorSnapshot: panenData['sensor_snapshot'],
panenSebelumnya: panenData['panen_sebelumnya'],
);
_panens.add(panen);
});
_panens.sort((a, b) => b.tanggalPanen.compareTo(a.tanggalPanen));
if (kDebugMode) {
print('✅ Restored ${_panens.length} panen records from Firebase');
}
}
notifyListeners();
} catch (e) {
if (kDebugMode) {
print('⚠️ Error restoring panen history: $e');
}
}
}
}