# 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 panens = panenProvider.getPanenByDateRange( DateTime(2026, 3, 4), DateTime(2026, 3, 6), ); ``` **Step 3: Fungsi `getPanenByDateRange()` filter data** ```dart List 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((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` | `Consumer` | | **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?