521 lines
20 KiB
Markdown
521 lines
20 KiB
Markdown
# Penjelasan Detail: Dashboard & Riwayat
|
|
|
|
## 1. FUNGSI STATUS PANEN TERAKHIR (Dashboard Page)
|
|
|
|
### 📍 Apa itu "Status Panen Terakhir"?
|
|
Bagian ini menampilkan **panen terakhir untuk SETIAP kandang** secara individual. Bukan ringkasan, tapi detail per kandang.
|
|
|
|
### 🔄 Alur Kerja
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────┐
|
|
│ STATUS PANEN TERAKHIR - Dashboard │
|
|
│ │
|
|
│ Kandang 1: ← Loop untuk setiap kandang │
|
|
│ ├─ Panen Terakhir: pagi (09:00) │
|
|
│ ├─ Jumlah: 45 telur │
|
|
│ ├─ Jam: 09:00 │
|
|
│ └─ Tanggal: 06-03-2026 │
|
|
│ │
|
|
│ Kandang 2: ← Kandang berikutnya │
|
|
│ ├─ Panen Terakhir: sore (15:00) │
|
|
│ ├─ Jumlah: 71 telur │
|
|
│ ├─ Jam: 15:00 │
|
|
│ └─ Tanggal: 06-03-2026 │
|
|
│ │
|
|
│ Kandang 3: │
|
|
│ └─ Belum ada data panen ⚠️ │
|
|
└─────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
### 🔍 Cara Kerjanya (Step-by-Step)
|
|
|
|
**Step 1: Loop setiap kandang**
|
|
```dart
|
|
ListView.builder(
|
|
itemCount: kandangProvider.kandangs.length, // 2 kandang
|
|
itemBuilder: (context, idx) {
|
|
final kandang = kandangProvider.kandangs[idx]; // Kandang 1, Kandang 2
|
|
```
|
|
|
|
**Step 2: Ambil panen terakhir untuk kandang itu**
|
|
```dart
|
|
final lastHarvest = panenProvider.getLastHarvestStatus(kandang.id);
|
|
// Panggil method yang mencari panen paling baru untuk kandang ini
|
|
```
|
|
|
|
**Step 3: Fungsi `getLastHarvestStatus()` di PanenProvider**
|
|
```dart
|
|
Panen? getLastHarvestStatus(String kandangId) {
|
|
// 1. Cari semua panen untuk kandang ini
|
|
final panenKandang = _panens
|
|
.where((p) => p.kandangId == kandangId)
|
|
.toList();
|
|
|
|
// 2. Jika tidak ada, return null (tampilkan "Belum ada data")
|
|
if (panenKandang.isEmpty) return null;
|
|
|
|
// 3. Urutkan dari terbaru ke terlama
|
|
panenKandang.sort((a, b) => b.tanggalPanen.compareTo(a.tanggalPanen));
|
|
|
|
// 4. Return yang paling atas (paling baru)
|
|
return panenKandang.first;
|
|
}
|
|
```
|
|
|
|
**Step 4: Tampilkan di UI**
|
|
```dart
|
|
if (lastHarvest == null) {
|
|
// Tampilkan pesan: "Belum ada data panen"
|
|
} else {
|
|
// Tampilkan kartu dengan:
|
|
// - Kandang: ${lastHarvest.kandangNama}
|
|
// - Jenis: ${lastHarvest.jenisPanen} (pagi/sore)
|
|
// - Jumlah: ${lastHarvest.jumlahTelur} telur
|
|
// - Jam: ${lastHarvest.jam}
|
|
// - Tanggal: ${lastHarvest.tanggalPanen}
|
|
}
|
|
```
|
|
|
|
### 📊 Contoh Data Aktual
|
|
|
|
Misal di Firebase sudah ada data:
|
|
```
|
|
_panens = [
|
|
// 2 hari lalu
|
|
Panen(kandangId: 'kandang_1', jumlahTelur: 45, jam: '09:00',
|
|
tanggalPanen: 04-03-2026), // pagi
|
|
Panen(kandangId: 'kandang_1', jumlahTelur: 42, jam: '15:00',
|
|
tanggalPanen: 04-03-2026), // sore
|
|
|
|
// Kemarin
|
|
Panen(kandangId: 'kandang_1', jumlahTelur: 48, jam: '09:00',
|
|
tanggalPanen: 05-03-2026), // pagi
|
|
Panen(kandangId: 'kandang_1', jumlahTelur: 43, jam: '15:00',
|
|
tanggalPanen: 05-03-2026), // sore
|
|
|
|
// Hari ini
|
|
Panen(kandangId: 'kandang_2', jumlahTelur: 68, jam: '09:00',
|
|
tanggalPanen: 06-03-2026), // pagi
|
|
Panen(kandangId: 'kandang_2', jumlahTelur: 71, jam: '15:00',
|
|
tanggalPanen: 06-03-2026), // sore
|
|
]
|
|
```
|
|
|
|
**Saat membuka Dashboard hari 06-03-2026 pukul 14:30:**
|
|
|
|
```
|
|
Kandang 1:
|
|
getLastHarvestStatus('kandang_1')
|
|
→ Cari: semua panen dengan kandangId='kandang_1'
|
|
→ Dapat: [45 (04th, 09:00), 42 (04th, 15:00),
|
|
48 (05th, 09:00), 43 (05th, 15:00)]
|
|
→ Urutkan desc: [43 (05th, 15:00), 48 (05th, 09:00), ...]
|
|
→ Return: 43 telur, sore, 15:00, 05-03-2026
|
|
|
|
TAMPILAN:
|
|
┌─────────────────────────┐
|
|
│ Kandang 1 │
|
|
│ Status: SORE (15:00) │
|
|
│ 43 telur │
|
|
│ 05-03-2026 pukul 15:00 │
|
|
└─────────────────────────┘
|
|
|
|
Kandang 2:
|
|
getLastHarvestStatus('kandang_2')
|
|
→ Cari: semua panen dengan kandangId='kandang_2'
|
|
→ Dapat: [68 (06th, 09:00), 71 (06th, 15:00)]
|
|
→ Urutkan desc: [71 (06th, 15:00), 68 (06th, 09:00)]
|
|
→ Return: 71 telur, sore, 15:00, 06-03-2026
|
|
|
|
TAMPILAN:
|
|
┌─────────────────────────┐
|
|
│ Kandang 2 │
|
|
│ Status: SORE (15:00) │
|
|
│ 71 telur │
|
|
│ 06-03-2026 pukul 15:00 │
|
|
└─────────────────────────┘
|
|
```
|
|
|
|
### ✅ Keuntungan Desain Ini
|
|
- ✅ User tahu kapan terakhir kali panen dilakukan
|
|
- ✅ Tahu berapa jumlah telur terakhir
|
|
- ✅ Tahu jenis panen (pagi/sore) dan jamnya
|
|
- ✅ Jika belum panen hari ini, tetap menunjukkan panen terakhir kemarin
|
|
- ✅ Per kandang, jadi transparan untuk setiap enclosure
|
|
|
|
---
|
|
|
|
## 2. LOGIKA RIWAYAT KESELURUHAN
|
|
|
|
### 📋 Apa itu Riwayat?
|
|
Sebuah halaman yang menampilkan **SEMUA data panen dalam rentang tanggal tertentu** dengan berbagai cara pengelompokan.
|
|
|
|
### 🔄 Alur Lengkap (Capture → Storage → Tampil)
|
|
|
|
```
|
|
┌──────────────────────────────────────────────────────────────┐
|
|
│ TIMELINE PANEN SEHARI-HARI │
|
|
│ │
|
|
│ 09:00 PAGI: Ambil telur (Panen Pagi) │
|
|
│ └─→ Data masuk: {kandang_1, 45, '09:00', pagi} │
|
|
│ └─→ Sensor: infra1 = 45 │
|
|
│ └─→ Simpan ke _panens (PanenProvider) │
|
|
│ │
|
|
│ 15:00 SORE: Ambil telur (Panen Sore) │
|
|
│ └─→ Sensor: infra1 = 100 (naik lagi!) │
|
|
│ └─→ Data masuk: {kandang_1, 100, '15:00', sore, prev: 45} │
|
|
│ └─→ HITUNG DELTA: 100 - 45 = 55 telur sore │
|
|
│ └─→ Simpan ke _panens (PanenProvider) │
|
|
│ │
|
|
│ SAMA SEPERTI KEMARIN... │
|
|
│ 09:00 PAGI: 48 telur (reset/mulai dari 0) │
|
|
│ 15:00 SORE: Sensor 98 - Prev 48 = 50 telur │
|
|
│ │
|
|
│ DAN HARI BERIKUTNYA (HARI BARU)... │
|
|
│ Tanggal berubah ke besoknya, cycle berulang │
|
|
└──────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
### 📊 Database Structure di Memory (_panens List)
|
|
|
|
```
|
|
_panens = [
|
|
// HARI 1: 04-03-2026
|
|
Panen {
|
|
id: 'panen_1',
|
|
kandangId: 'kandang_1',
|
|
kandangNama: 'Kandang 1',
|
|
jumlahTelur: 45,
|
|
tanggalPanen: 04-03-2026, ← KEY untuk filtering
|
|
jam: '09:00',
|
|
jenisPanen: 'pagi', ← Ada 2 per hari
|
|
sensorSnapshot: 45, ← Nilai sensor saat ambil
|
|
panenSebelumnya: null, ← Pagi tidak perlu
|
|
catatan: 'Kondisi baik',
|
|
},
|
|
|
|
Panen {
|
|
id: 'panen_2',
|
|
kandangId: 'kandang_1',
|
|
kandangNama: 'Kandang 1',
|
|
jumlahTelur: 55,
|
|
tanggalPanen: 04-03-2026, ← SAMA, tapi berbeda jam
|
|
jam: '15:00',
|
|
jenisPanen: 'sore',
|
|
sensorSnapshot: 100, ← Sensor mencapai 100
|
|
panenSebelumnya: 45, ← Nilai panen sebelumnya
|
|
catatan: 'Hitung: 100 - 45 = 55',
|
|
},
|
|
|
|
// HARI 2: 05-03-2026
|
|
Panen {
|
|
id: 'panen_3',
|
|
kandangId: 'kandang_1',
|
|
kandangNama: 'Kandang 1',
|
|
jumlahTelur: 48,
|
|
tanggalPanen: 05-03-2026, ← BEDA HARI
|
|
jam: '09:00',
|
|
jenisPanen: 'pagi',
|
|
sensorSnapshot: 48,
|
|
panenSebelumnya: null,
|
|
catatan: 'Sensor di-reset ke 0',
|
|
},
|
|
|
|
Panen {
|
|
id: 'panen_4',
|
|
kandangId: 'kandang_1',
|
|
kandangNama: 'Kandang 1',
|
|
jumlahTelur: 50,
|
|
tanggalPanen: 05-03-2026, ← SAMA HARI
|
|
jam: '15:00',
|
|
jenisPanen: 'sore',
|
|
sensorSnapshot: 98,
|
|
panenSebelumnya: 48,
|
|
catatan: 'Hitung: 98 - 48 = 50',
|
|
},
|
|
|
|
// ... dst untuk kandang_2, dst untuk hari lain
|
|
]
|
|
```
|
|
|
|
### 🎯 User Membuka Riwayat & Memilih Tanggal
|
|
|
|
```
|
|
┌─────────────────────────────────────┐
|
|
│ Riwayat Panen │
|
|
│ │
|
|
│ Filter Tanggal: │
|
|
│ [📅 04-03-2026] sd [📅 06-03-2026]│
|
|
│ │
|
|
│ [CARI] ← Klik ini │
|
|
└─────────────────────────────────────┘
|
|
```
|
|
|
|
### 🔍 Step-by-Step Proses Filtering
|
|
|
|
**Step 1: User pilih tanggal**
|
|
```dart
|
|
_selectedStartDate = 04-03-2026
|
|
_selectedEndDate = 06-03-2026
|
|
```
|
|
|
|
**Step 2: Panggil method filtering**
|
|
```dart
|
|
List<Panen> panens = panenProvider.getPanenByDateRange(
|
|
DateTime(2026, 3, 4),
|
|
DateTime(2026, 3, 6),
|
|
);
|
|
```
|
|
|
|
**Step 3: Fungsi `getPanenByDateRange()` filter data**
|
|
```dart
|
|
List<Panen> getPanenByDateRange(DateTime startDate, DateTime endDate) {
|
|
return _panens.where((p) {
|
|
// Cek apakah tanggal panen ANTARA start dan end (inclusive)
|
|
return p.tanggalPanen.isAfter(startDate) &&
|
|
p.tanggalPanen.isBefore(endDate.add(const Duration(days: 1)));
|
|
}).toList();
|
|
}
|
|
|
|
// Hasil: [panen_1, panen_2, panen_3, panen_4]
|
|
// (Semua 4 panen antara 04-03 hingga 06-03)
|
|
```
|
|
|
|
**Step 4: Hitung Total Produksi**
|
|
```dart
|
|
int total = panens.isEmpty ? 0 :
|
|
panens.map<int>((p) => p.jumlahTelur).reduce((a, b) => a + b);
|
|
|
|
// total = 45 + 55 + 48 + 50 = 198 telur
|
|
// Ditampilkan di card atas berwarna orange gradient
|
|
```
|
|
|
|
### 📊 UI Riwayat - Apa yang Ditampilkan
|
|
|
|
```
|
|
┌──────────────────────────────────────────────┐
|
|
│ RIWAYAT PANEN (04-03 sd 06-03) │
|
|
├──────────────────────────────────────────────┤
|
|
│ │
|
|
│ ┌────────────────────────────────────────┐ │
|
|
│ │ TOTAL PRODUKSI │ │
|
|
│ │ 198 🥚 │ │
|
|
│ │ 4 pencatatan panen │ │
|
|
│ └────────────────────────────────────────┘ │
|
|
│ │
|
|
│ ┌────────────────────────────────────────┐ │
|
|
│ │ 04-03-2026 │ │
|
|
│ ├────────────────────────────────────────┤ │
|
|
│ │ • Pagi (09:00): Kandang 1 - 45 telur │ │
|
|
│ │ • Sore (15:00): Kandang 1 - 55 telur │ │
|
|
│ │ TOTAL HARI: 100 telur │ │
|
|
│ └────────────────────────────────────────┘ │
|
|
│ │
|
|
│ ┌────────────────────────────────────────┐ │
|
|
│ │ 05-03-2026 │ │
|
|
│ ├────────────────────────────────────────┤ │
|
|
│ │ • Pagi (09:00): Kandang 1 - 48 telur │ │
|
|
│ │ • Sore (15:00): Kandang 1 - 50 telur │ │
|
|
│ │ TOTAL HARI: 98 telur │ │
|
|
│ └────────────────────────────────────────┘ │
|
|
│ │
|
|
│ ┌────────────────────────────────────────┐ │
|
|
│ │ 06-03-2026 │ │
|
|
│ ├────────────────────────────────────────┤ │
|
|
│ │ • Pagi (09:00): Kandang 2 - 68 telur │ │
|
|
│ │ • Sore (15:00): Kandang 2 - 71 telur │ │
|
|
│ │ TOTAL HARI: 139 telur │ │
|
|
│ └────────────────────────────────────────┘ │
|
|
│ │
|
|
└──────────────────────────────────────────────┘
|
|
```
|
|
|
|
---
|
|
|
|
## 3. SKENARIO RESET / HARI BARU
|
|
|
|
### 🔄 Apa yang Terjadi saat Reset Sensor?
|
|
|
|
**Scenario 1: Sensor RESET ke 0 di pagi hari**
|
|
```
|
|
HARI 1 (04-03):
|
|
├─09:00 Pagi: Sensor 45 → capture → Panen 45 telur
|
|
│ (jenisPanen: 'pagi', panenSebelumnya: null)
|
|
│
|
|
├─15:00 Sore: Sensor 100 → capture
|
|
│ (jenisPanen: 'sore', panenSebelumnya: 45)
|
|
│ DELTA: 100 - 45 = 55 telur ✓
|
|
│
|
|
└─23:59:59: Sensor dicek ulang = 100
|
|
|
|
HARI 2 (05-03):
|
|
├─09:00 Pagi: [RESET SENSOR ke 0 oleh user]
|
|
│ Kemudian ayam bertelur → Sensor 48
|
|
│ → capture → Panen 48 telur
|
|
│ (jenisPanen: 'pagi', panenSebelumnya: null)
|
|
│
|
|
├─15:00 Sore: Sensor 98 → capture
|
|
│ (jenisPanen: 'sore', panenSebelumnya: 48)
|
|
│ DELTA: 98 - 48 = 50 telur ✓
|
|
│
|
|
└─23:59:59: Sensor = 98
|
|
|
|
HARI 3 (06-03): ... (cycle berulang)
|
|
```
|
|
|
|
**Scenario 2: Sensor TIDAK RESET (Masih 100 dari kemarin)**
|
|
```
|
|
HARI 2 PAGI (05-03):
|
|
├─ Sensor menunjukkan 100 (belum di-reset)
|
|
├─ Tapi HARI BARU, jadi capture sebagai 'pagi'
|
|
├─ Masalah: jenisPanen='pagi' tapi sensorSnapshot=100?
|
|
│
|
|
└─ Solusi: Pengguna harus reset terlebih dahulu
|
|
atau gunakan logika khusus untuk tangani ini
|
|
```
|
|
|
|
### 📅 Data Tersimpan Permanen di PanenProvider
|
|
|
|
```
|
|
Setiap kali ada panen (pagi atau sore), data disimpan ke _panens:
|
|
|
|
_panens.add(Panen(...)) // Simpan ke memory
|
|
notifyListeners() // Update UI
|
|
|
|
Data PERSISTEN di sini sampai:
|
|
1. App di-close & semua hilang (jika tidak save ke Firebase) ⚠️
|
|
2. Atau di-backup/sinkronisasi ke Firebase
|
|
3. Atau dihapus manual via deletePanen()
|
|
```
|
|
|
|
### ⚠️ PENTING: Persistence / Penyimpanan Jangka Panjang
|
|
|
|
Saat ini, `_panens` adalah in-memory list. Artinya:
|
|
- ✅ Tersimpan SEPANJANG app berjalan
|
|
- ✅ Riwayat tetap tampil selama app tidak ditutup
|
|
- ❌ Hilang saat app ditutup jika tidak save ke Firebase!
|
|
|
|
Untuk persistence long-term, perlu:
|
|
1. **Option A**: Save ke SharedPreferences (lokal) → JsonEncode/Decode
|
|
2. **Option B**: Save ke Firebase Realtime Database → Auto-sync
|
|
3. **Option C**: Save ke SQLite/Hive → Local DB
|
|
|
|
**Current Flow yang Perlu:**
|
|
```
|
|
User capture panen → addPanen() → _panens (memory)
|
|
↓
|
|
Seharusnya ke Firebase
|
|
↓
|
|
Save untuk jangka panjang
|
|
```
|
|
|
|
---
|
|
|
|
## 4. DIAGRAM LENGKAP: DATA FLOW
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────┐
|
|
│ ALUR DATA LENGKAP │
|
|
└─────────────────────────────────────────────────────────────┘
|
|
|
|
CAPTURE PANEN:
|
|
┌──────────────┐
|
|
│ Jam 09:00 │
|
|
│ Panen Pagi │
|
|
└──────┬───────┘
|
|
│
|
|
▼
|
|
┌─────────────────────────────────┐
|
|
│ KandangProvider │
|
|
│ infra1Value = 45 (sensor) │
|
|
│ captureSensorSnapshot() │
|
|
└──────┬──────────────────────────┘
|
|
│ Get sensor value
|
|
▼
|
|
┌─────────────────────────────────┐
|
|
│ PanenProvider │
|
|
│ addPanenFromSensor( │
|
|
│ kandangId: 'kandang_1' │
|
|
│ sensorValue: 45 │
|
|
│ jenisPanen: 'pagi' │
|
|
│ jam: '09:00' │
|
|
│ panenSebelumnya: null │
|
|
│ ) │
|
|
└──────┬──────────────────────────┘
|
|
│ Add to _panens
|
|
▼
|
|
┌─────────────────────────────────┐
|
|
│ _panens List (Memory) │
|
|
│ [ │
|
|
│ Panen(45, 'pagi', 09:00), │
|
|
│ ... (other panens) │
|
|
│ ] │
|
|
└──────┬──────────────────────────┘
|
|
│
|
|
├────► DashboardPage (Status Panen Terakhir)
|
|
│ getLastHarvestStatus('kandang_1')
|
|
│ → Tampikan 45 telur, 09:00, pagi
|
|
│
|
|
└────► RiwayatPage (History)
|
|
getPanenByDateRange(start, end)
|
|
→ Filter & tampilkan semua panen
|
|
```
|
|
|
|
---
|
|
|
|
## 5. CHEAT SHEET - Capture vs Display
|
|
|
|
| Aspek | **Capture (Saat Panen)** | **Display (Dashboard)** | **Display (Riwayat)** |
|
|
|-------|--------------------------|------------------------|----------------------|
|
|
| **Method yang Digunakan** | `addPanenFromSensor()` | `getLastHarvestStatus()` | `getPanenByDateRange()` |
|
|
| **Data Source** | KandangProvider (sensor) + Input | PanenProvider (last record) | PanenProvider (filtered) |
|
|
| **Proses** | Sensor value → Add to _panens | Get paling baru → Sort desc | Filter by date range → Sum |
|
|
| **Hasil** | Tersimpan di _panens | 1 record per kandang | Multiple records, grouped |
|
|
| **Update UI** | `notifyListeners()` | `Consumer<PanenProvider>` | `Consumer<PanenProvider>` |
|
|
| **Contoh** | `45, 55 (delta), 48, 50` | `50 (terakhir)` | `45+55+48+50=198` |
|
|
|
|
---
|
|
|
|
## 6. TROUBLESHOOTING COMMON ISSUES
|
|
|
|
### ❓ Q: "Kenapa riwayat kosong?"
|
|
**A:** Periksa:
|
|
1. Apakah sudah ada data di `_panens`?
|
|
2. Apakah tanggal filter benar? (cek format DateTime)
|
|
3. Apakah `getPanenByDateRange()` correctly filtering?
|
|
|
|
### ❓ Q: "Status Panen Terakhir malah menunjukkan data lama?"
|
|
**A:** Periksa:
|
|
1. Apakah `getLastHarvestStatus()` benar-benar mengurutkan desc?
|
|
2. Apakah `sort()` sudah dipanggil?
|
|
3. Apakah `return first` mengambil yang paling atas?
|
|
|
|
### ❓ Q: "Delta calculation salah (09:00 dapat 55, seharusnya 45)"
|
|
**A:** Periksa:
|
|
1. Apakah `panenSebelumnya` diisi dengan nilai pagi?
|
|
2. Apakah kondisi `if (jenisPanen == 'sore' && panenSebelumnya != null)` terpenuhi?
|
|
3. Apakah rumus `sensorValue - panenSebelumnya` benar?
|
|
|
|
### ❓ Q: "Data hilang saat app ditutup"
|
|
**A:** Normal! Data di `_panens` adalah in-memory. Solusi:
|
|
1. Implementasikan save ke Firebase
|
|
2. Atau gunakan SharedPreferences/SQLite untuk local persistence
|
|
3. Atau render/download sebelum tutup app
|
|
|
|
---
|
|
|
|
## 7. RINGKASAN SINGKAT
|
|
|
|
| Komponen | Fungsi |
|
|
|----------|--------|
|
|
| **Status Panen Terakhir** | Tampil panen terakhir SETIAP kandang di dashboard |
|
|
| **Riwayat** | Tampil SEMUA panen dalam rentang tanggal |
|
|
| **PanenProvider** | Toko data (store, retrieve, filter) |
|
|
| **KandangProvider** | Sumber sensor real-time |
|
|
| **Capture** | Sensor → Add to _panens (memory) |
|
|
| **Display** | _panens → Filter/Sort → UI |
|
|
| **Persistence** | ⚠️ Belum ada (hanya memory) |
|
|
|
|
Apa ada yang masih belum jelas?
|