672 lines
21 KiB
Dart
672 lines
21 KiB
Dart
import 'dart:async';
|
|
|
|
import 'package:flutter/foundation.dart';
|
|
import 'package:uuid/uuid.dart';
|
|
import 'package:firebase_database/firebase_database.dart';
|
|
import '../models/panen_model.dart';
|
|
import '../services/notification_service.dart';
|
|
|
|
class PanenProvider extends ChangeNotifier {
|
|
final FirebaseDatabase _database = FirebaseDatabase.instance;
|
|
static const bool _allowFlutterAutoCaptureWrite = false;
|
|
|
|
// Tracking snapshot untuk time-windowing logic
|
|
final Map<String, int> _snapshotPagiHariIni = {}; // kandangId -> nilaiPagi
|
|
final Map<String, int> _snapshotSoreHariIni = {}; // kandangId -> nilaiSore
|
|
|
|
final List<Panen> _panens = [];
|
|
final Set<String> _knownRealtimeRecordKeys = <String>{};
|
|
StreamSubscription<DatabaseEvent>? _recordsRealtimeSubscription;
|
|
bool _realtimeListenerStarted = false;
|
|
|
|
List<Panen> get panens => _panens;
|
|
|
|
bool _isKandang1(String kandangId) {
|
|
final normalized = kandangId.toLowerCase().replaceAll('_', '');
|
|
return normalized == 'kandang1';
|
|
}
|
|
|
|
bool _isKandang2(String kandangId) {
|
|
final normalized = kandangId.toLowerCase().replaceAll('_', '');
|
|
return normalized == 'kandang2';
|
|
}
|
|
|
|
String _jenisFromPanen(Panen panen) {
|
|
final jenis = (panen.jenisPanen ?? '').trim().toLowerCase();
|
|
if (jenis == 'pagi' || jenis == 'sore') return jenis;
|
|
|
|
// Parse jam untuk klasifikasi: < 12 adalah pagi, >= 12 adalah sore
|
|
final jam = panen.jam.trim();
|
|
final jamParts = jam.split(':');
|
|
if (jamParts.isNotEmpty) {
|
|
final hour = int.tryParse(jamParts[0]);
|
|
if (hour != null) {
|
|
return hour < 12 ? 'pagi' : 'sore';
|
|
}
|
|
}
|
|
|
|
return 'panen';
|
|
}
|
|
|
|
bool _isSameDate(DateTime a, DateTime b) {
|
|
return a.year == b.year && a.month == b.month && a.day == b.day;
|
|
}
|
|
|
|
Future<void> _notifySummaryForJenis(Panen latestPanen) async {
|
|
final jenis = _jenisFromPanen(latestPanen);
|
|
if (jenis != 'pagi' && jenis != 'sore') return;
|
|
|
|
int k1 = 0;
|
|
int k2 = 0;
|
|
for (final panen in _panens) {
|
|
if (!_isSameDate(panen.tanggalPanen, latestPanen.tanggalPanen)) continue;
|
|
if (_jenisFromPanen(panen) != jenis) continue;
|
|
|
|
if (_isKandang1(panen.kandangId)) {
|
|
k1 += panen.jumlahTelur;
|
|
} else if (_isKandang2(panen.kandangId)) {
|
|
k2 += panen.jumlahTelur;
|
|
}
|
|
}
|
|
|
|
final totals = <String, int>{
|
|
'kandang 1': k1,
|
|
'kandang 2': k2,
|
|
};
|
|
|
|
await NotificationService.instance.showPanenSummaryNotification(
|
|
jenisPanen: jenis,
|
|
kandangTotals: totals,
|
|
);
|
|
}
|
|
|
|
Future<void> startRealtimePanenNotifications() async {
|
|
if (_realtimeListenerStarted) return;
|
|
|
|
final ref = _database.ref('riwayat/records');
|
|
|
|
try {
|
|
final snapshot = await ref.get();
|
|
if (snapshot.exists && snapshot.value is Map) {
|
|
final data = Map<dynamic, dynamic>.from(snapshot.value as Map);
|
|
for (final entry in data.entries) {
|
|
_knownRealtimeRecordKeys.add(entry.key.toString());
|
|
}
|
|
}
|
|
} catch (e) {
|
|
if (kDebugMode) {
|
|
print('⚠️ Seed realtime panen keys gagal: $e');
|
|
}
|
|
}
|
|
|
|
_recordsRealtimeSubscription = ref.onChildAdded.listen((event) async {
|
|
try {
|
|
final key = event.snapshot.key;
|
|
if (key == null || _knownRealtimeRecordKeys.contains(key)) return;
|
|
_knownRealtimeRecordKeys.add(key);
|
|
|
|
if (!event.snapshot.exists || event.snapshot.value is! Map) return;
|
|
|
|
final panenData =
|
|
Map<dynamic, dynamic>.from(event.snapshot.value as Map);
|
|
final tanggalRaw = panenData['tanggal_panen'];
|
|
final tanggal = DateTime.tryParse('${tanggalRaw ?? ''}');
|
|
if (tanggal == null) return;
|
|
|
|
final newPanen = Panen(
|
|
id: panenData['id'] ?? '',
|
|
kandangId: panenData['kandang_id'] ?? '',
|
|
kandangNama: panenData['kandang_nama'] ?? '',
|
|
jumlahTelur: panenData['jumlah_telur'] ?? 0,
|
|
tanggalPanen: tanggal,
|
|
jam: panenData['jam'] ?? '',
|
|
catatan: panenData['catatan'] ?? '',
|
|
jenisPanen: panenData['jenis_panen'],
|
|
sensorSnapshot: panenData['sensor_snapshot'],
|
|
panenSebelumnya: panenData['panen_sebelumnya'],
|
|
);
|
|
|
|
final exists =
|
|
_panens.any((p) => p.id == newPanen.id && p.id.isNotEmpty);
|
|
if (!exists) {
|
|
_panens.add(newPanen);
|
|
_panens.sort((a, b) => b.tanggalPanen.compareTo(a.tanggalPanen));
|
|
notifyListeners();
|
|
}
|
|
|
|
await _notifySummaryForJenis(newPanen);
|
|
} catch (e) {
|
|
if (kDebugMode) {
|
|
print('⚠️ Realtime panen notification error: $e');
|
|
}
|
|
}
|
|
});
|
|
|
|
_realtimeListenerStarted = true;
|
|
|
|
if (kDebugMode) {
|
|
print('✅ Realtime panen notification listener started');
|
|
}
|
|
}
|
|
|
|
Future<void> stopRealtimePanenNotifications() async {
|
|
await _recordsRealtimeSubscription?.cancel();
|
|
_recordsRealtimeSubscription = null;
|
|
_realtimeListenerStarted = false;
|
|
_knownRealtimeRecordKeys.clear();
|
|
}
|
|
|
|
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();
|
|
}
|
|
|
|
Panen 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();
|
|
return newPanen;
|
|
}
|
|
|
|
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)
|
|
Panen 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();
|
|
return newPanen;
|
|
}
|
|
|
|
// ============================================
|
|
// 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<Panen?> 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 null;
|
|
}
|
|
|
|
// 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 now = DateTime.now();
|
|
final jamStr =
|
|
'${now.hour.toString().padLeft(2, '0')}:${now.minute.toString().padLeft(2, '0')}';
|
|
final newPanen = Panen(
|
|
id: uuid.v4(),
|
|
kandangId: kandangId,
|
|
kandangNama: kandangNama,
|
|
jumlahTelur: jumlahTelur,
|
|
tanggalPanen: now,
|
|
jam: jamStr,
|
|
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));
|
|
|
|
// Guard: auto-capture write hanya dari Railway worker.
|
|
if (_allowFlutterAutoCaptureWrite) {
|
|
await _savePanenToFirebase(newPanen);
|
|
}
|
|
|
|
if (kDebugMode) {
|
|
print('✅ Panen Pagi recorded untuk $kandangNama: $jumlahTelur telur');
|
|
}
|
|
|
|
notifyListeners();
|
|
return newPanen;
|
|
} catch (e) {
|
|
if (kDebugMode) {
|
|
print('❌ Error capturing panen pagi: $e');
|
|
}
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/// AUTO-CAPTURE DARI SCHEDULER
|
|
/// Panggil ini saat scheduler trigger jam 15:00
|
|
/// Dengan delta calculation: nilai_sore - nilai_pagi
|
|
Future<Panen?> 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 null;
|
|
}
|
|
|
|
// 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 now = DateTime.now();
|
|
final jamStr =
|
|
'${now.hour.toString().padLeft(2, '0')}:${now.minute.toString().padLeft(2, '0')}';
|
|
final newPanen = Panen(
|
|
id: uuid.v4(),
|
|
kandangId: kandangId,
|
|
kandangNama: kandangNama,
|
|
jumlahTelur: jumlahTelur,
|
|
tanggalPanen: now,
|
|
jam: jamStr,
|
|
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));
|
|
|
|
// Guard: auto-capture write hanya dari Railway worker.
|
|
if (_allowFlutterAutoCaptureWrite) {
|
|
await _savePanenToFirebase(newPanen);
|
|
}
|
|
|
|
if (kDebugMode) {
|
|
print(
|
|
'✅ Panen Sore recorded untuk $kandangNama: $jumlahTelur telur${nilaiPagi != null ? ' (delta: $sensorValue - $nilaiPagi)' : ''}',
|
|
);
|
|
}
|
|
|
|
notifyListeners();
|
|
return newPanen;
|
|
} catch (e) {
|
|
if (kDebugMode) {
|
|
print('❌ Error capturing panen sore: $e');
|
|
}
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/// Simpan panen ke Firebase
|
|
Future<void> _savePanenToFirebase(Panen panen) async {
|
|
try {
|
|
final ref = _database.ref('riwayat/records').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,
|
|
});
|
|
|
|
await _updateRiwayatSummary(panen);
|
|
|
|
if (kDebugMode) {
|
|
print('✅ Panen saved to Firebase: ${panen.id}');
|
|
}
|
|
} catch (e) {
|
|
if (kDebugMode) {
|
|
print('❌ Error saving panen to Firebase: $e');
|
|
}
|
|
}
|
|
}
|
|
|
|
Future<void> _updateRiwayatSummary(Panen panen) async {
|
|
final now = panen.tanggalPanen;
|
|
final dateKey =
|
|
'${now.year}-${now.month.toString().padLeft(2, '0')}-${now.day.toString().padLeft(2, '0')}';
|
|
final summaryRef = _database.ref('riwayat/summary');
|
|
|
|
await summaryRef.runTransaction((current) {
|
|
final data = Map<dynamic, dynamic>.from(
|
|
(current as Map?) ?? <dynamic, dynamic>{},
|
|
);
|
|
|
|
int telurHariIni = (data['telur_hari_ini'] ?? 0) as int;
|
|
int totalTelur = (data['total_telur'] ?? 0) as int;
|
|
int kandang1 = (data['kandang1_hari_ini'] ?? 0) as int;
|
|
int kandang2 = (data['kandang2_hari_ini'] ?? 0) as int;
|
|
final lastDate = '${data['last_reset_date'] ?? ''}';
|
|
|
|
if (lastDate != dateKey) {
|
|
telurHariIni = 0;
|
|
kandang1 = 0;
|
|
kandang2 = 0;
|
|
}
|
|
|
|
telurHariIni += panen.jumlahTelur;
|
|
totalTelur += panen.jumlahTelur;
|
|
|
|
final kandangIdLower = panen.kandangId.toLowerCase();
|
|
if (kandangIdLower == 'kandang1' || kandangIdLower == 'kandang_1') {
|
|
kandang1 += panen.jumlahTelur;
|
|
}
|
|
if (kandangIdLower == 'kandang2' || kandangIdLower == 'kandang_2') {
|
|
kandang2 += panen.jumlahTelur;
|
|
}
|
|
|
|
return Transaction.success({
|
|
...data,
|
|
'telur_hari_ini': telurHariIni,
|
|
'kandang1_hari_ini': kandang1,
|
|
'kandang2_hari_ini': kandang2,
|
|
'total_telur': totalTelur,
|
|
'last_reset_date': dateKey,
|
|
'updated_at': DateTime.now().toIso8601String(),
|
|
});
|
|
});
|
|
}
|
|
|
|
/// 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/records');
|
|
final snapshot = await ref.get();
|
|
|
|
if (snapshot.exists) {
|
|
final data = Map<dynamic, dynamic>.from(snapshot.value as Map);
|
|
_panens.clear();
|
|
|
|
data.forEach((key, value) {
|
|
if (value is! Map) return;
|
|
final panenData = Map<dynamic, dynamic>.from(value);
|
|
final tanggalRaw = panenData['tanggal_panen'];
|
|
final tanggal = DateTime.tryParse('${tanggalRaw ?? ''}');
|
|
if (tanggal == null) return;
|
|
|
|
final panen = Panen(
|
|
id: panenData['id'] ?? '',
|
|
kandangId: panenData['kandang_id'] ?? '',
|
|
kandangNama: panenData['kandang_nama'] ?? '',
|
|
jumlahTelur: panenData['jumlah_telur'] ?? 0,
|
|
tanggalPanen: tanggal,
|
|
jam: panenData['jam'] ?? '',
|
|
catatan: panenData['catatan'] ?? '',
|
|
jenisPanen: panenData['jenis_panen'],
|
|
sensorSnapshot: panenData['sensor_snapshot'],
|
|
panenSebelumnya: panenData['panen_sebelumnya'],
|
|
);
|
|
_panens.add(panen);
|
|
});
|
|
} else {
|
|
// Fallback legacy structure: /riwayat langsung berisi records + summary.
|
|
final legacySnapshot = await _database.ref('riwayat').get();
|
|
if (legacySnapshot.exists) {
|
|
final legacyData = Map<dynamic, dynamic>.from(
|
|
legacySnapshot.value as Map,
|
|
);
|
|
_panens.clear();
|
|
|
|
legacyData.forEach((key, value) {
|
|
if (value is! Map) return;
|
|
final panenData = Map<dynamic, dynamic>.from(value);
|
|
if (!panenData.containsKey('kandang_id')) return;
|
|
final tanggalRaw = panenData['tanggal_panen'];
|
|
final tanggal = DateTime.tryParse('${tanggalRaw ?? ''}');
|
|
if (tanggal == null) return;
|
|
|
|
_panens.add(
|
|
Panen(
|
|
id: panenData['id'] ?? '',
|
|
kandangId: panenData['kandang_id'] ?? '',
|
|
kandangNama: panenData['kandang_nama'] ?? '',
|
|
jumlahTelur: panenData['jumlah_telur'] ?? 0,
|
|
tanggalPanen: tanggal,
|
|
jam: panenData['jam'] ?? '',
|
|
catatan: panenData['catatan'] ?? '',
|
|
jenisPanen: panenData['jenis_panen'],
|
|
sensorSnapshot: panenData['sensor_snapshot'],
|
|
panenSebelumnya: panenData['panen_sebelumnya'],
|
|
),
|
|
);
|
|
});
|
|
}
|
|
|
|
_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');
|
|
}
|
|
}
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_recordsRealtimeSubscription?.cancel();
|
|
super.dispose();
|
|
}
|
|
}
|