import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; import 'package:fl_chart/fl_chart.dart'; import 'package:supabase_flutter/supabase_flutter.dart'; class HarvestAnalysisChart extends StatefulWidget { final String userId; final Map? scheduleData; final Map? harvestData; final bool isManualInput; const HarvestAnalysisChart({ super.key, required this.userId, this.scheduleData, this.harvestData, this.isManualInput = false, }); @override State createState() => _HarvestAnalysisChartState(); } class _HarvestAnalysisChartState extends State with SingleTickerProviderStateMixin { final supabase = Supabase.instance.client; final currency = NumberFormat.currency(locale: 'id_ID', symbol: 'Rp '); late TabController _tabController; bool _isLoading = true; // Data untuk grafik List> _dailyLogs = []; List> _costBreakdown = []; Map _financialSummary = {}; @override void initState() { super.initState(); _tabController = TabController(length: 4, vsync: this); _loadData(); } Future _loadData() async { if (!mounted) return; try { debugPrint('Loading data for harvest analysis...'); // Check if we need to use manual data if (widget.isManualInput || widget.scheduleData == null) { debugPrint('Using manual input data'); // Process manual data (sync operation) _processManualData(); } else { debugPrint('Using automatic analysis from daily logs'); // Fetch data with proper error handling await _fetchDailyLogs(); // Check if still mounted before processing if (!mounted) return; _processDailyLogs(); } } catch (e) { debugPrint('Error loading chart data: $e'); // Handle error state if (mounted) { // Set empty data structures to avoid null errors _dailyLogs = []; _costBreakdown = []; _financialSummary = { 'total_cost': 0, 'income': 0, 'profit': 0, 'profit_margin': 0, 'rc_ratio': 0, 'bc_ratio': 0, 'roi': 0, 'productivity': 0, 'status': 'N/A', }; } } finally { // Only update state if widget is still mounted if (mounted) { setState(() => _isLoading = false); } } } void _processManualData() { if (widget.harvestData == null) return; final data = widget.harvestData!; debugPrint('=== MANUAL INPUT CALCULATION ==='); debugPrint('- Income: ${data['income']}'); debugPrint('- Cost: ${data['cost']}'); debugPrint('- Profit: ${data['profit']}'); debugPrint('- Profit Margin: ${data['profit_margin']}'); debugPrint('- Quantity: ${data['quantity']} kilogram'); debugPrint('- Price per kg: ${data['price_per_kg']}'); debugPrint('- BEP Price: ${data['bep_price']}'); debugPrint('- BEP Production: ${data['bep_production']}'); debugPrint('- Production Cost per kg: ${data['production_cost_per_kg']}'); debugPrint('- Crop Name: ${data['crop_name']}'); debugPrint('- Area: ${data['area']} m²'); debugPrint('- Field: ${data['field_name']}'); debugPrint('- Plot: ${data['plot']}'); // Memproses data biaya dengan semua komponen yang tersedia _costBreakdown = [ { 'name': 'Bibit', 'cost': data['seed_cost'] ?? 0, 'color': Colors.green.shade700, }, { 'name': 'Pupuk', 'cost': data['fertilizer_cost'] ?? 0, 'color': Colors.blue.shade700, }, { 'name': 'Pestisida', 'cost': data['pesticide_cost'] ?? 0, 'color': Colors.red.shade700, }, { 'name': 'Tenaga Kerja', 'cost': data['labor_cost'] ?? 0, 'color': Colors.orange.shade700, }, { 'name': 'Irigasi', 'cost': data['irrigation_cost'] ?? 0, 'color': Colors.purple.shade700, }, { 'name': 'Persiapan Lahan', 'cost': data['land_preparation_cost'] ?? 0, 'color': Colors.brown.shade700, }, { 'name': 'Alat & Peralatan', 'cost': data['tools_equipment_cost'] ?? 0, 'color': Colors.grey.shade700, }, { 'name': 'Transportasi', 'cost': data['transportation_cost'] ?? 0, 'color': Colors.indigo.shade700, }, { 'name': 'Pasca Panen', 'cost': data['post_harvest_cost'] ?? 0, 'color': Colors.teal.shade700, }, { 'name': 'Lain-lain', 'cost': data['other_cost'] ?? 0, 'color': Colors.amber.shade700, }, ]; // Filter untuk menghapus komponen biaya yang nilainya 0 _costBreakdown = _costBreakdown.where((item) => (item['cost'] as double) > 0).toList(); // Membuat ringkasan keuangan dengan metrik standar pertanian Indonesia _financialSummary = { 'total_cost': data['cost'] ?? 0, 'direct_cost': data['direct_cost'] ?? 0, 'indirect_cost': data['indirect_cost'] ?? 0, 'income': data['income'] ?? 0, 'profit': data['profit'] ?? 0, 'profit_margin': data['profit_margin'] ?? 0, // % dari pendapatan 'rc_ratio': data['rc_ratio'] ?? 1.0, // Revenue/Cost ratio 'bc_ratio': data['bc_ratio'] ?? 0, // Benefit/Cost ratio 'bep_price': data['bep_price'] ?? 0, // BEP Harga 'bep_production': data['bep_production'] ?? 0, // BEP Produksi 'production_cost_per_kg': data['production_cost_per_kg'] ?? 0, // Biaya Pokok Produksi 'roi': data['roi'] ?? 0, // Return on Investment (%) 'productivity': data['productivity'] ?? 0, // Produktivitas (kilogram/ha) 'status': _determineStatus(data), 'quantity': data['quantity'] ?? 0, // Total panen (kilogram) 'area': data['area'] ?? 0, // Luas lahan (m²) 'price_per_kg': data['price_per_kg'] ?? 0, // Harga jual per kg 'weather_condition': data['weather_condition'] ?? 'Normal', 'irrigation_type': data['irrigation_type'] ?? 'Irigasi Teknis', 'soil_type': data['soil_type'] ?? 'Lempung', 'fertilizer_type': data['fertilizer_type'] ?? 'NPK', 'crop_name': data['crop_name'] ?? 'Tanaman', 'field_name': data['field_name'] ?? 'Lahan', 'plot': data['plot'] ?? 'Plot', 'start_date': data['start_date'], 'end_date': data['end_date'], }; debugPrint('=== FINANCIAL SUMMARY (MANUAL) ==='); debugPrint('Financial summary: $_financialSummary'); } // Menentukan status berdasarkan metrik pertanian yang lebih komprehensif String _determineStatus(Map data) { final rcRatio = data['rc_ratio'] ?? 0.0; final profitMargin = data['profit_margin'] ?? 0.0; final productivity = data['productivity'] ?? 0.0; final cropName = data['crop_name']?.toString().toLowerCase() ?? ''; // Mendapatkan target produktivitas berdasarkan jenis tanaman double targetProductivity = 0.0; if (cropName.contains('padi')) { targetProductivity = 5500; } else if (cropName.contains('jagung')) { targetProductivity = 5200; } else if (cropName.contains('kedelai')) { targetProductivity = 1500; } else if (cropName.contains('bawang')) { targetProductivity = 9500; } else if (cropName.contains('cabai') || cropName.contains('cabe')) { targetProductivity = 8000; } else if (cropName.contains('tomat')) { targetProductivity = 16000; } else if (cropName.contains('kentang')) { targetProductivity = 17000; } else if (cropName.contains('kopi')) { targetProductivity = 700; } else if (cropName.contains('kakao') || cropName.contains('coklat')) { targetProductivity = 800; } else if (cropName.contains('tebu')) { targetProductivity = 70000; } else if (cropName.contains('kelapa sawit') || cropName.contains('sawit')) { targetProductivity = 20000; } else { targetProductivity = 4000; } // Menghitung rasio produktivitas terhadap target final productivityRatio = productivity / targetProductivity; // Menggunakan standar Kementerian Pertanian untuk kelayakan usaha tani if (rcRatio >= 2.0) { return 'Sangat Layak'; } else if (rcRatio >= 1.5) { return 'Layak'; } else if (rcRatio >= 1.0) { return 'Cukup Layak'; } else { return 'Tidak Layak'; } } Future _fetchDailyLogs() async { if (widget.scheduleData == null || !mounted) return; final scheduleId = widget.scheduleData!['id']; try { debugPrint( 'Mencoba mengambil data dari daily_logs untuk schedule_id: $scheduleId', ); // Clear existing data before fetching _dailyLogs = []; // Menggunakan tabel daily_logs sebagai sumber data final res = await supabase .from('daily_logs') .select() .eq('schedule_id', scheduleId) .order('date', ascending: true); // Check if still mounted after async operation if (!mounted) return; debugPrint('Berhasil mengambil ${res.length} daily logs untuk analisis'); // Filter out entries with null or invalid cost - more efficiently _dailyLogs = List>.from(res) .where( (log) => log['cost'] != null && log['cost'] is num && log['cost'] >= 0, ) .toList(); // If we don't have valid logs, try the fallback table if (_dailyLogs.isEmpty && mounted) { debugPrint( 'Tidak ada daily logs yang valid, mencoba mencari di daily_records', ); try { final recordsRes = await supabase .from('daily_records') .select() .eq('schedule_id', scheduleId) .order('date', ascending: true); // Check if still mounted after this second async operation if (!mounted) return; debugPrint( 'Berhasil mengambil ${recordsRes.length} daily records untuk analisis', ); // Filter out entries with null or invalid cost _dailyLogs = List>.from(recordsRes) .where( (log) => log['cost'] != null && log['cost'] is num && log['cost'] >= 0, ) .toList(); } catch (e) { debugPrint('Tidak dapat mengambil data dari daily_records: $e'); // Ensure _dailyLogs is empty but initialized _dailyLogs = []; } } } catch (e) { debugPrint('Error fetching daily logs: $e'); // Ensure _dailyLogs is empty but initialized _dailyLogs = []; } } void _processDailyLogs() { if (_dailyLogs.isEmpty || widget.harvestData == null) { debugPrint('Tidak ada daily logs atau harvest data untuk diproses'); return; } // PERBAIKAN KONSISTENSI: Gunakan biaya dari harvestData yang sama dengan manual input // untuk memastikan konsistensi perhitungan antara summary dan chart final manualCost = widget.harvestData!['cost'] ?? 0; // Hitung total biaya dari daily logs untuk perbandingan double dailyLogsCost = 0; for (var log in _dailyLogs) { double cost = (log['cost'] ?? 0).toDouble(); dailyLogsCost += cost; } // Gunakan biaya dari manual input untuk konsistensi final totalCost = manualCost; final income = widget.harvestData!['income'] ?? 0; // Tetapi untuk debugging, kita cek apakah ada perbedaan perhitungan final harvestQuantity = widget.harvestData!['quantity'] ?? 0; final pricePerKg = widget.harvestData!['price_per_kg'] ?? 0; final calculatedIncome = harvestQuantity * 100 * pricePerKg; final profit = income - totalCost; final profitMargin = income > 0 ? (profit / income) * 100 : 0; debugPrint('=== DAILY LOGS CALCULATION (FIXED) ==='); debugPrint('Manual input cost (used): $totalCost'); debugPrint('Daily logs accumulated cost (reference): $dailyLogsCost'); debugPrint('Cost difference: ${dailyLogsCost - totalCost}'); debugPrint('Income dari harvestData: $income'); debugPrint('Calculated income (quantity × 100 × price): $calculatedIncome'); debugPrint('Income difference: ${income - calculatedIncome}'); debugPrint('Profit yang dihitung: $profit'); debugPrint('Profit margin yang dihitung: $profitMargin%'); // Kategori biaya default untuk analisis // PERBAIKAN: Gunakan breakdown biaya dari harvestData untuk konsistensi _costBreakdown = [ { 'name': 'Bibit', 'cost': widget.harvestData!['seed_cost'] ?? 0, 'color': Colors.green, }, { 'name': 'Pupuk', 'cost': widget.harvestData!['fertilizer_cost'] ?? 0, 'color': Colors.blue, }, { 'name': 'Pestisida', 'cost': widget.harvestData!['pesticide_cost'] ?? 0, 'color': Colors.red, }, { 'name': 'Tenaga Kerja', 'cost': widget.harvestData!['labor_cost'] ?? 0, 'color': Colors.orange, }, { 'name': 'Irigasi', 'cost': widget.harvestData!['irrigation_cost'] ?? 0, 'color': Colors.purple, }, ]; // Status panen berdasarkan rasio keuntungan yang baru String status; final productivity = widget.harvestData!['productivity'] ?? 0; if (productivity >= 5.0 && profitMargin >= 30) { status = 'Baik'; } else if (productivity >= 5.0 || profitMargin >= 30) { status = 'Cukup'; } else { status = 'Kurang'; } // Membuat ringkasan keuangan dengan data yang konsisten dengan manual input _financialSummary = { 'total_cost': totalCost, // Gunakan biaya dari harvestData untuk konsistensi 'income': income, // Gunakan income dari harvestData 'profit': profit, // Profit yang dihitung dengan biaya konsisten 'profit_margin': profitMargin, // Profit margin yang benar 'rc_ratio': totalCost > 0 ? (income / totalCost) : 0, 'bc_ratio': totalCost > 0 ? (profit / totalCost) : 0, 'roi': totalCost > 0 ? (profit / totalCost) * 100 : 0, 'productivity': productivity, 'status': status, 'quantity': widget.harvestData!['quantity'] ?? 0, 'area': widget.harvestData!['area'] ?? 0, 'price_per_kg': widget.harvestData!['price_per_kg'] ?? 0, }; debugPrint('=== FINANCIAL SUMMARY (DAILY LOGS - FIXED) ==='); debugPrint('Updated financial summary: $_financialSummary'); debugPrint( 'Profit should now match manual input calculation: ${_financialSummary['profit']}', ); } @override void dispose() { _tabController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { if (_isLoading) { return const Center(child: CircularProgressIndicator()); } if (widget.isManualInput && widget.harvestData == null) { return const Center( child: Text('Silakan selesaikan analisis panen terlebih dahulu'), ); } // Wrap in SafeArea to avoid keyboard overlap issues return SafeArea( child: Column( children: [ // Memory-optimized TabBar with less decoration TabBar( controller: _tabController, isScrollable: true, labelColor: Theme.of(context).primaryColor, unselectedLabelColor: Colors.grey, padding: const EdgeInsets.symmetric(horizontal: 8), labelPadding: const EdgeInsets.symmetric(horizontal: 16), onTap: (index) { // Dismiss keyboard when switching tabs FocusScope.of(context).unfocus(); }, tabs: const [ Tab( icon: Icon(Icons.analytics, size: 20), text: 'Ringkasan', height: 64, ), Tab( icon: Icon(Icons.pie_chart, size: 20), text: 'Komposisi Biaya', height: 64, ), Tab( icon: Icon(Icons.bar_chart, size: 20), text: 'Perbandingan Keuangan', height: 64, ), Tab( icon: Icon(Icons.show_chart, size: 20), text: 'Tren Pengeluaran', height: 64, ), ], ), const SizedBox(height: 16), // Use Expanded to avoid overflow issues with keyboard Expanded( child: TabBarView( controller: _tabController, // Avoid rebuilding tabs that are not visible physics: const NeverScrollableScrollPhysics(), children: [ _buildSummaryTab(), _buildCostBreakdownTab(), _buildFinancialComparisonTab(), _buildDailyExpensesTrendTab(), ], ), ), ], ), ); } Widget _buildSummaryTab() { return SingleChildScrollView( padding: const EdgeInsets.all(16.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Card( elevation: 4, color: Colors.white, child: Padding( padding: const EdgeInsets.all(16.0), child: Column( children: [ Text( 'Status Panen: ${_financialSummary['status'] ?? 'N/A'}', style: Theme.of(context).textTheme.titleLarge?.copyWith( color: _getStatusColor(_financialSummary['status']), fontWeight: FontWeight.bold, ), ), const SizedBox(height: 24), SizedBox( height: 150, child: Stack( alignment: Alignment.center, children: [ PieChart( PieChartData( startDegreeOffset: 180, sectionsSpace: 0, centerSpaceRadius: 80, sections: [ PieChartSectionData( value: (_financialSummary['profit_margin'] ?? 0.0) .clamp(0.0, 100.0) / 100.0, color: _getProfitRatioColor( (_financialSummary['profit_margin'] ?? 0.0) .toDouble(), ), radius: 20, showTitle: false, ), PieChartSectionData( value: 1.0 - (_financialSummary['profit_margin'] ?? 0.0) .clamp(0.0, 100.0) / 100.0, color: Colors.grey.shade200, radius: 20, showTitle: false, ), ], ), ), Column( mainAxisSize: MainAxisSize.min, children: [ Text( 'Keuntungan', style: TextStyle( fontSize: 14, color: Colors.grey.shade600, ), ), FittedBox( fit: BoxFit.scaleDown, child: Text( currency.format( _financialSummary['profit'] ?? 0, ), style: const TextStyle( fontSize: 16, fontWeight: FontWeight.bold, ), ), ), ], ), ], ), ), const SizedBox(height: 45), GridView.count( shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), crossAxisCount: 2, childAspectRatio: 2, mainAxisSpacing: 8, crossAxisSpacing: 8, children: [ _buildSummaryCard( 'Produktivitas', '${(_financialSummary['productivity'] ?? 0).toStringAsFixed(2)} kilogram/ha', Icons.agriculture, Colors.green, ), _buildSummaryCard( 'Keuntungan', '${(_financialSummary['profit_margin'] ?? 0).toStringAsFixed(2)}%', Icons.trending_up, Colors.blue, ), _buildSummaryCard( 'Total Biaya', currency.format(_financialSummary['total_cost'] ?? 0), Icons.money_off, Colors.red, ), _buildSummaryCard( 'Pendapatan', currency.format(_financialSummary['income'] ?? 0), Icons.attach_money, Colors.green, ), ], ), ], ), ), ), const SizedBox(height: 24), _buildSummaryAnalysis(), ], ), ); } Widget _buildSummaryAnalysis() { final profitMargin = (_financialSummary['profit_margin'] ?? 0.0).toDouble(); final productivity = (_financialSummary['productivity'] ?? 0.0).toDouble(); final cropName = _financialSummary['crop_name'] ?? 'Tanaman'; final rcRatio = (_financialSummary['rc_ratio'] ?? 0.0).toDouble(); final bcRatio = (_financialSummary['bc_ratio'] ?? 0.0).toDouble(); final roi = (_financialSummary['roi'] ?? 0.0).toDouble(); final weatherCondition = _financialSummary['weather_condition'] ?? 'Normal'; final irrigationType = _financialSummary['irrigation_type'] ?? 'Irigasi Teknis'; final soilType = _financialSummary['soil_type'] ?? 'Lempung'; final fertilizerType = _financialSummary['fertilizer_type'] ?? 'NPK'; String statusText; String recommendationText; String conditionText = ''; // Analisis profitabilitas berdasarkan R/C Ratio dan profit margin if (rcRatio >= 1.5 && profitMargin >= 30) { statusText = 'Usaha tani $cropName ini sangat layak dengan R/C Ratio ${rcRatio.toStringAsFixed(2)} dan margin keuntungan ${profitMargin.toStringAsFixed(2)}%.'; recommendationText = 'Pertahankan praktik pertanian yang sudah diterapkan dan pertimbangkan untuk memperluas area tanam atau meningkatkan produktivitas.'; } else if (rcRatio >= 1.0 && profitMargin >= 15) { statusText = 'Usaha tani $cropName ini layak dengan R/C Ratio ${rcRatio.toStringAsFixed(2)} dan margin keuntungan ${profitMargin.toStringAsFixed(2)}%.'; recommendationText = 'Ada ruang untuk peningkatan. Pertimbangkan untuk mengoptimalkan penggunaan input atau mencari pasar dengan harga jual yang lebih baik.'; } else if (rcRatio >= 1.0) { statusText = 'Usaha tani $cropName ini cukup layak dengan R/C Ratio ${rcRatio.toStringAsFixed(2)} namun margin keuntungan rendah (${profitMargin.toStringAsFixed(2)}%).'; recommendationText = 'Perlu evaluasi menyeluruh terhadap struktur biaya dan proses produksi untuk meningkatkan profitabilitas di masa mendatang.'; } else { statusText = 'Usaha tani $cropName ini tidak layak dengan R/C Ratio ${rcRatio.toStringAsFixed(2)} dan mengalami kerugian (margin ${profitMargin.toStringAsFixed(2)}%).'; recommendationText = 'Perlu tindakan segera untuk mengevaluasi faktor-faktor yang menyebabkan kerugian dan membuat perubahan signifikan pada siklus tanam berikutnya.'; } // Analisis produktivitas berdasarkan jenis tanaman String productivityText; double targetProductivity = _getTargetProductivity(cropName); if (productivity > targetProductivity * 1.2) { productivityText = 'Produktivitas lahan sangat tinggi (${productivity.toStringAsFixed(0)} kg/ha), jauh di atas rata-rata nasional untuk tanaman $cropName (${targetProductivity.toStringAsFixed(0)} kg/ha).'; } else if (productivity > targetProductivity * 0.8) { productivityText = 'Produktivitas lahan baik (${productivity.toStringAsFixed(0)} kg/ha), mendekati rata-rata nasional untuk tanaman $cropName (${targetProductivity.toStringAsFixed(0)} kg/ha).'; } else { productivityText = 'Produktivitas lahan kurang optimal (${productivity.toStringAsFixed(0)} kg/ha), di bawah rata-rata nasional untuk tanaman $cropName (${targetProductivity.toStringAsFixed(0)} kg/ha).'; } // Analisis kondisi tanam if (weatherCondition != 'Normal') { conditionText += 'Kondisi cuaca $weatherCondition dapat mempengaruhi hasil panen. '; } if (irrigationType.contains('Tadah Hujan')) { conditionText += 'Sistem irigasi tadah hujan meningkatkan risiko kegagalan saat kekeringan. '; } if (soilType.contains('Pasir')) { conditionText += 'Tanah berpasir memiliki retensi air dan nutrisi rendah. '; } else if (soilType.contains('Liat')) { conditionText += 'Tanah liat memiliki drainase yang kurang baik. '; } return Card( elevation: 3, color: Colors.white, child: Padding( padding: const EdgeInsets.all(16.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( 'Analisis Ringkasan Panen', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), ), const Divider(), const SizedBox(height: 8), Text(statusText), const SizedBox(height: 16), Text(productivityText), if (conditionText.isNotEmpty) ...[ const SizedBox(height: 16), Text(conditionText), ], const SizedBox(height: 16), const Text( 'Rekomendasi:', style: TextStyle(fontWeight: FontWeight.bold), ), const SizedBox(height: 8), Text(recommendationText), ], ), ), ); } Widget _buildSummaryCard( String title, String value, IconData icon, Color color, ) { return Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(8), border: Border.all(color: color.withOpacity(0.3)), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ Row( children: [ Icon(icon, color: color, size: 14), const SizedBox(width: 4), Expanded( child: Text( title, style: TextStyle( color: color, fontWeight: FontWeight.bold, fontSize: 11, ), maxLines: 1, overflow: TextOverflow.ellipsis, ), ), ], ), const Spacer(), FittedBox( fit: BoxFit.scaleDown, child: Text( value, style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 13), ), ), ], ), ); } Widget _buildCostBreakdownTab() { if (_costBreakdown.isEmpty) { return const Center(child: Text('Tidak ada data biaya yang tersedia')); } // Hitung total untuk persentase final totalCost = _costBreakdown.fold( 0, (sum, item) => sum + ((item['cost'] ?? 0).toDouble()), ); return SingleChildScrollView( padding: const EdgeInsets.all(16.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ AspectRatio( aspectRatio: 1.3, child: PieChart( PieChartData( sections: _buildPieChartSections(totalCost), centerSpaceRadius: 40, sectionsSpace: 2, pieTouchData: PieTouchData( touchCallback: (FlTouchEvent event, pieTouchResponse) { // Implement touch callback if needed }, ), ), ), ), const SizedBox(height: 24), const Text( 'Rincian Biaya:', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), ), const SizedBox(height: 8), ListView.builder( shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), itemCount: _costBreakdown.length, itemBuilder: (context, index) { final item = _costBreakdown[index]; final cost = (item['cost'] ?? 0).toDouble(); final percentage = totalCost > 0 ? cost / totalCost * 100 : 0.0; return ListTile( leading: Container( width: 16, height: 16, decoration: BoxDecoration( color: item['color'] as Color, shape: BoxShape.circle, ), ), title: Text(item['name'] as String), trailing: Text( '${currency.format(cost)} (${percentage.toStringAsFixed(1)}%)', style: const TextStyle(fontWeight: FontWeight.bold), ), ); }, ), const SizedBox(height: 24), _buildCostBreakdownAnalysis(totalCost), ], ), ); } Widget _buildCostBreakdownAnalysis(double totalCost) { // Mencari kategori dengan biaya tertinggi Map? highestCostCategory; for (var item in _costBreakdown) { if (highestCostCategory == null || (item['cost'] ?? 0) > (highestCostCategory['cost'] ?? 0)) { highestCostCategory = item; } } // Analisis struktur biaya String costAnalysis; String recommendation; String cropName = _financialSummary['crop_name'] ?? 'Tanaman'; if (highestCostCategory != null) { final highestCost = (highestCostCategory['cost'] ?? 0).toDouble(); final highestPercentage = totalCost > 0 ? (highestCost / totalCost * 100) : 0; if (highestPercentage > 40) { costAnalysis = 'Biaya ${highestCostCategory['name']} mendominasi struktur biaya produksi $cropName (${highestPercentage.toStringAsFixed(1)}% dari total biaya). Hal ini menciptakan ketergantungan tinggi pada komponen biaya ini.'; recommendation = 'Pertimbangkan cara untuk mengurangi ketergantungan pada biaya ${highestCostCategory['name']}, misalnya dengan mencari alternatif yang lebih ekonomis atau mengoptimalkan penggunaannya. Bandingkan dengan praktik petani sukses lainnya untuk tanaman $cropName.'; } else if (highestPercentage > 25) { costAnalysis = 'Biaya ${highestCostCategory['name']} merupakan komponen signifikan dalam struktur biaya $cropName (${highestPercentage.toStringAsFixed(1)}% dari total biaya). Struktur biaya cukup berimbang namun masih bisa dioptimalkan.'; recommendation = 'Evaluasi efisiensi penggunaan ${highestCostCategory['name']} untuk mengurangi biaya tanpa mengorbankan produktivitas. Pertimbangkan teknologi atau metode baru untuk mengoptimalkan penggunaan input ini.'; } else { costAnalysis = 'Struktur biaya untuk tanaman $cropName cukup berimbang dengan komponen terbesar ${highestCostCategory['name']} hanya menyumbang ${highestPercentage.toStringAsFixed(1)}% dari total biaya.'; recommendation = 'Pertahankan pendekatan seimbang dalam manajemen biaya, namun tetap periksa apakah ada komponen biaya yang dapat dikurangi. Dokumentasikan praktik manajemen biaya yang efektif ini untuk siklus tanam berikutnya.'; } // Tambahkan analisis berdasarkan jenis tanaman if (cropName.toLowerCase().contains('padi')) { if (highestCostCategory['name'] == 'Tenaga Kerja') { recommendation += ' Pertimbangkan mekanisasi untuk mengurangi biaya tenaga kerja yang tinggi pada budidaya padi.'; } else if (highestCostCategory['name'] == 'Pupuk') { recommendation += ' Pertimbangkan penggunaan pupuk organik atau teknik pemupukan berimbang untuk tanaman padi.'; } } else if (cropName.toLowerCase().contains('jagung')) { if (highestCostCategory['name'] == 'Bibit') { recommendation += ' Evaluasi penggunaan varietas jagung hibrida yang lebih produktif meskipun harga bibit lebih tinggi.'; } } } else { costAnalysis = 'Tidak ada data biaya yang cukup untuk analisis struktur biaya tanaman $cropName.'; recommendation = 'Catat komponen biaya dengan lebih detail untuk analisis lebih akurat di masa mendatang.'; } return Card( elevation: 3, color: Colors.white, child: Padding( padding: const EdgeInsets.all(16.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( 'Analisis Komposisi Biaya', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), ), const Divider(), const SizedBox(height: 8), Text(costAnalysis), const SizedBox(height: 16), const Text( 'Rekomendasi:', style: TextStyle(fontWeight: FontWeight.bold), ), const SizedBox(height: 8), Text(recommendation), ], ), ), ); } List _buildPieChartSections(double total) { return _costBreakdown.map((item) { final cost = (item['cost'] ?? 0).toDouble(); final percentage = total > 0 ? cost / total * 100 : 0.0; return PieChartSectionData( value: cost, color: item['color'] as Color, title: '${percentage.toStringAsFixed(1)}%', radius: 100, titleStyle: const TextStyle( color: Colors.white, fontWeight: FontWeight.bold, fontSize: 14, ), ); }).toList(); } Widget _buildFinancialComparisonTab() { return SingleChildScrollView( padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 16.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ AspectRatio( aspectRatio: 1.5, child: BarChart( BarChartData( alignment: BarChartAlignment.spaceAround, barTouchData: BarTouchData(enabled: false), titlesData: FlTitlesData( show: true, bottomTitles: AxisTitles( sideTitles: SideTitles( showTitles: true, getTitlesWidget: (value, meta) { String text = ''; switch (value.toInt()) { case 0: text = 'Biaya'; break; case 1: text = 'Pendapatan'; break; case 2: text = 'Keuntungan'; break; } return Padding( padding: const EdgeInsets.only(top: 8.0), child: Text(text), ); }, reservedSize: 30, ), ), leftTitles: AxisTitles( sideTitles: SideTitles( showTitles: true, reservedSize: 60, getTitlesWidget: (value, meta) { return Text( currency.format(value).replaceAll(',00', ''), style: const TextStyle(fontSize: 10), ); }, ), ), topTitles: AxisTitles( sideTitles: SideTitles(showTitles: false), ), rightTitles: AxisTitles( sideTitles: SideTitles(showTitles: false), ), ), gridData: FlGridData(show: true), borderData: FlBorderData(show: true), barGroups: [ BarChartGroupData( x: 0, barRods: [ BarChartRodData( toY: (_financialSummary['total_cost'] ?? 0).toDouble(), color: Colors.red, width: 30, borderRadius: const BorderRadius.only( topLeft: Radius.circular(4), topRight: Radius.circular(4), ), ), ], ), BarChartGroupData( x: 1, barRods: [ BarChartRodData( toY: (_financialSummary['income'] ?? 0).toDouble(), color: Colors.green, width: 30, borderRadius: const BorderRadius.only( topLeft: Radius.circular(4), topRight: Radius.circular(4), ), ), ], ), BarChartGroupData( x: 2, barRods: [ BarChartRodData( toY: (_financialSummary['profit'] ?? 0).toDouble(), color: Colors.blue, width: 30, borderRadius: const BorderRadius.only( topLeft: Radius.circular(4), topRight: Radius.circular(4), ), ), ], ), ], ), ), ), const SizedBox(height: 24), Card( child: Padding( padding: const EdgeInsets.all(16.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( 'Ringkasan Keuangan', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), ), const SizedBox(height: 16), _buildFinancialRow( 'Total Biaya:', currency.format(_financialSummary['total_cost'] ?? 0), ), _buildFinancialRow( 'Pendapatan Kotor:', currency.format(_financialSummary['income'] ?? 0), ), _buildFinancialRow( 'Keuntungan Bersih:', currency.format(_financialSummary['profit'] ?? 0), ), _buildFinancialRow( 'ROI:', '${(_financialSummary['roi'] ?? 0).toStringAsFixed(2)}%', ), ], ), ), ), const SizedBox(height: 24), _buildFinancialComparisonAnalysis(), ], ), ); } Widget _buildFinancialRow(String label, String value) { return Padding( padding: const EdgeInsets.symmetric(vertical: 4.0), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text(label), Text(value, style: const TextStyle(fontWeight: FontWeight.bold)), ], ), ); } Widget _buildFinancialComparisonAnalysis() { final totalCost = (_financialSummary['total_cost'] ?? 0.0).toDouble(); final income = (_financialSummary['income'] ?? 0.0).toDouble(); final profit = (_financialSummary['profit'] ?? 0.0).toDouble(); final profitMargin = (_financialSummary['profit_margin'] ?? 0.0).toDouble(); final rcRatio = (_financialSummary['rc_ratio'] ?? 0.0).toDouble(); final bcRatio = (_financialSummary['bc_ratio'] ?? 0.0).toDouble(); final roi = (_financialSummary['roi'] ?? 0.0).toDouble(); final cropName = _financialSummary['crop_name'] ?? 'Tanaman'; final bepPrice = _financialSummary['bep_price'] ?? 0.0; final pricePerKg = _financialSummary['price_per_kg'] ?? 0.0; final productivity = _financialSummary['productivity'] ?? 0.0; final targetProductivity = _getTargetProductivity(cropName); String profitabilityAnalysis; String ratioAnalysis; String marketAnalysis; String recommendation; // Analisis profitabilitas if (profit <= 0) { profitabilityAnalysis = 'Panen $cropName ini merugi sebesar ${currency.format(profit.abs())}. Total biaya produksi (${currency.format(totalCost)}) melebihi pendapatan (${currency.format(income)}).'; recommendation = 'Evaluasi seluruh proses produksi dan struktur biaya. Pertimbangkan untuk mencari pasar dengan harga jual lebih tinggi (saat ini ${currency.format(pricePerKg)}/kg) atau beralih ke varietas $cropName yang lebih produktif.'; } else if (profitMargin < 15) { profitabilityAnalysis = 'Panen $cropName ini menghasilkan keuntungan minimal sebesar ${currency.format(profit)} dengan margin profit hanya ${profitMargin.toStringAsFixed(2)}%.'; recommendation = 'Periksa komponen biaya yang mungkin terlalu tinggi dan cari cara untuk meningkatkan produktivitas atau efisiensi tanpa menambah biaya.'; } else if (profitMargin < 30) { profitabilityAnalysis = 'Panen $cropName ini cukup menguntungkan dengan keuntungan ${currency.format(profit)} dan margin profit ${profitMargin.toStringAsFixed(2)}%.'; recommendation = 'Pertahankan praktik yang baik dan cari peluang untuk meningkatkan skala produksi atau efisiensi lebih lanjut.'; } else { profitabilityAnalysis = 'Panen $cropName ini sangat menguntungkan dengan keuntungan ${currency.format(profit)} dan margin profit mencapai ${profitMargin.toStringAsFixed(2)}%.'; recommendation = 'Pertahankan praktik yang sudah sangat baik dan pertimbangkan untuk meningkatkan skala produksi untuk keuntungan yang lebih besar.'; } // Analisis R/C dan B/C Ratio (standar evaluasi pertanian Indonesia) if (rcRatio < 1.0) { ratioAnalysis = 'R/C Ratio sebesar ${rcRatio.toStringAsFixed(2)} menunjukkan usaha tani $cropName tidak layak secara ekonomi karena pendapatan lebih kecil dari biaya produksi.'; } else if (rcRatio >= 1.0 && rcRatio < 1.5) { ratioAnalysis = 'R/C Ratio sebesar ${rcRatio.toStringAsFixed(2)} menunjukkan usaha tani $cropName cukup layak secara ekonomi, namun masih berisiko jika terjadi kenaikan biaya produksi.'; } else { ratioAnalysis = 'R/C Ratio sebesar ${rcRatio.toStringAsFixed(2)} menunjukkan usaha tani $cropName sangat layak secara ekonomi karena pendapatan jauh lebih besar dari biaya produksi.'; } ratioAnalysis += ' B/C Ratio sebesar ${bcRatio.toStringAsFixed(2)} ${bcRatio < 0 ? 'menunjukkan kerugian.' : bcRatio < 1 ? 'menunjukkan keuntungan yang kurang optimal.' : 'menunjukkan perbandingan keuntungan terhadap biaya yang baik.'}'; ratioAnalysis += ' ROI sebesar ${roi.toStringAsFixed(2)}% ${roi < 15 ? 'tergolong rendah untuk usaha tani.' : roi < 30 ? 'tergolong cukup baik untuk usaha tani.' : 'tergolong sangat baik untuk usaha tani.'}'; // Analisis pasar if (pricePerKg > bepPrice * 1.5) { marketAnalysis = 'Harga pasar sangat menguntungkan dengan harga jual ${currency.format(pricePerKg)}/kg yang jauh melebihi BEP Harga ${currency.format(bepPrice)}/kg.'; } else if (pricePerKg > bepPrice * 1.2) { marketAnalysis = 'Harga pasar cukup menguntungkan dengan harga jual ${currency.format(pricePerKg)}/kg yang lebih tinggi dari BEP Harga ${currency.format(bepPrice)}/kg.'; } else if (pricePerKg > bepPrice) { marketAnalysis = 'Harga pasar memberikan keuntungan minimal dengan harga jual ${currency.format(pricePerKg)}/kg sedikit di atas BEP Harga ${currency.format(bepPrice)}/kg.'; } else { marketAnalysis = 'Harga pasar tidak menguntungkan dengan harga jual ${currency.format(pricePerKg)}/kg di bawah BEP Harga ${currency.format(bepPrice)}/kg.'; } // Tambahan analisis produktivitas if (productivity > targetProductivity * 1.2) { recommendation += ' Produktivitas sangat baik (${productivity.toStringAsFixed(0)} kg/ha), pertahankan praktik budidaya yang sudah diterapkan.'; } else if (productivity < targetProductivity * 0.8) { recommendation += ' Produktivitas masih di bawah rata-rata nasional, pertimbangkan untuk meningkatkan teknik budidaya dan pemeliharaan tanaman.'; } return Card( elevation: 3, color: Colors.white, child: Padding( padding: const EdgeInsets.all(16.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( 'Analisis Keuangan', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), ), const Divider(), const SizedBox(height: 8), Text(profitabilityAnalysis), const SizedBox(height: 16), Text(ratioAnalysis), const SizedBox(height: 16), Text(marketAnalysis), const SizedBox(height: 16), const Text( 'Rekomendasi:', style: TextStyle(fontWeight: FontWeight.bold), ), const SizedBox(height: 8), Text(recommendation), ], ), ), ); } Widget _buildDailyExpensesTrendTab() { if (_dailyLogs.isEmpty) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ const Icon(Icons.bar_chart_outlined, size: 64, color: Colors.grey), const SizedBox(height: 16), Text( widget.isManualInput ? 'Grafik tren belum tersedia' : 'Tidak ada catatan harian yang tersedia', textAlign: TextAlign.center, ), ], ), ); } // Menyiapkan data untuk line chart List spots = []; double maxY = 0; for (int i = 0; i < _dailyLogs.length; i++) { final cost = (_dailyLogs[i]['cost'] ?? 0).toDouble(); spots.add(FlSpot(i.toDouble(), cost)); if (cost > maxY) maxY = cost; } // Menghitung statistik dasar double totalSpent = 0; double maxSpent = 0; double avgSpent = 0; DateTime? maxSpentDate; for (var log in _dailyLogs) { final cost = (log['cost'] ?? 0).toDouble(); totalSpent += cost; if (cost > maxSpent) { maxSpent = cost; maxSpentDate = DateTime.parse(log['date']); } } if (_dailyLogs.isNotEmpty) { avgSpent = totalSpent / _dailyLogs.length; } return SingleChildScrollView( padding: const EdgeInsets.all(16.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ SizedBox( height: 300, child: LineChart( LineChartData( gridData: FlGridData(show: true), titlesData: FlTitlesData( bottomTitles: AxisTitles( sideTitles: SideTitles( showTitles: true, getTitlesWidget: (value, meta) { if (value.toInt() >= 0 && value.toInt() < _dailyLogs.length) { final date = DateTime.parse( _dailyLogs[value.toInt()]['date'], ); return Padding( padding: const EdgeInsets.only(top: 8.0), child: Text( DateFormat('dd/MM').format(date), style: const TextStyle(fontSize: 10), ), ); } return const SizedBox(); }, reservedSize: 30, ), ), leftTitles: AxisTitles( sideTitles: SideTitles( showTitles: true, getTitlesWidget: (value, meta) { return Text( currency.format(value).replaceAll(',00', ''), style: const TextStyle(fontSize: 10), ); }, reservedSize: 60, ), ), topTitles: AxisTitles( sideTitles: SideTitles(showTitles: false), ), rightTitles: AxisTitles( sideTitles: SideTitles(showTitles: false), ), ), borderData: FlBorderData(show: true), minY: 0, maxY: maxY * 1.2, lineBarsData: [ LineChartBarData( spots: spots, isCurved: true, color: Colors.blue, barWidth: 3, dotData: FlDotData(show: true), belowBarData: BarAreaData( show: true, color: Colors.blue.withOpacity(0.2), ), ), ], ), ), ), const SizedBox(height: 16), _buildExpenseSummary(totalSpent, maxSpent, avgSpent, maxSpentDate), const SizedBox(height: 16), const Text( 'Riwayat Pengeluaran Harian', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), ), const SizedBox(height: 8), ListView.builder( shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), itemCount: _dailyLogs.length, itemBuilder: (context, index) { final log = _dailyLogs[index]; final date = DateTime.parse(log['date']); final cost = (log['cost'] ?? 0).toDouble(); return Card( margin: const EdgeInsets.symmetric(vertical: 4.0), child: ListTile( leading: CircleAvatar( backgroundColor: Colors.blue.withOpacity(0.1), child: const Icon(Icons.receipt_long, color: Colors.blue), ), title: Text( DateFormat('dd MMMM yyyy').format(date), style: const TextStyle(fontWeight: FontWeight.bold), ), subtitle: Text( log['note'] ?? 'Tidak ada catatan', maxLines: 2, overflow: TextOverflow.ellipsis, ), trailing: Text( currency.format(cost), style: const TextStyle( fontWeight: FontWeight.bold, fontSize: 16, ), ), onTap: () { _showDailyLogDetail(log); }, ), ); }, ), const SizedBox(height: 16), _buildExpenseTrendAnalysis(), ], ), ); } Widget _buildExpenseSummary( double total, double max, double avg, DateTime? maxDate, ) { return Card( elevation: 3, color: Colors.white, child: Padding( padding: const EdgeInsets.all(16.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( 'Ringkasan Pengeluaran', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), ), const Divider(), const SizedBox(height: 8), GridView.count( shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), crossAxisCount: 2, childAspectRatio: 2, mainAxisSpacing: 8, crossAxisSpacing: 8, children: [ _buildExpenseStatCard( 'Total Pengeluaran', currency.format(total), Icons.account_balance_wallet, Colors.blue, ), _buildExpenseStatCard( 'Pengeluaran Rata-rata', currency.format(avg), Icons.trending_up, Colors.green, ), _buildExpenseStatCard( 'Pengeluaran Tertinggi', currency.format(max), Icons.arrow_upward, Colors.red, ), _buildExpenseStatCard( 'Tanggal Tertinggi', maxDate != null ? DateFormat('dd/MM').format(maxDate) : 'N/A', Icons.calendar_today, Colors.orange, ), ], ), ], ), ), ); } Widget _buildExpenseStatCard( String title, String value, IconData icon, Color color, ) { return Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(8), border: Border.all(color: color.withOpacity(0.3)), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ Row( children: [ Icon(icon, color: color, size: 14), const SizedBox(width: 4), Expanded( child: Text( title, style: TextStyle( color: color, fontWeight: FontWeight.bold, fontSize: 11, ), maxLines: 1, overflow: TextOverflow.ellipsis, ), ), ], ), const Spacer(), FittedBox( fit: BoxFit.scaleDown, child: Text( value, style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 13), ), ), ], ), ); } Widget _buildExpenseTrendAnalysis() { if (_dailyLogs.isEmpty || _dailyLogs.length < 2) { return const SizedBox.shrink(); } // Menghitung tren pengeluaran (naik/turun) List costs = _dailyLogs .map((r) => (r['cost'] ?? 0).toDouble()) .toList() .cast(); double firstHalfAvg = 0; double secondHalfAvg = 0; int midPoint = costs.length ~/ 2; for (int i = 0; i < midPoint; i++) { firstHalfAvg += costs[i]; } for (int i = midPoint; i < costs.length; i++) { secondHalfAvg += costs[i]; } firstHalfAvg = firstHalfAvg / midPoint; secondHalfAvg = secondHalfAvg / (costs.length - midPoint); String trendAnalysis; String recommendation; double trendPercentage = firstHalfAvg > 0 ? ((secondHalfAvg - firstHalfAvg) / firstHalfAvg) * 100 : 0; if (trendPercentage > 20) { trendAnalysis = 'Tren pengeluaran menunjukkan peningkatan signifikan sebesar ${trendPercentage.abs().toStringAsFixed(1)}% di paruh kedua periode tanam.'; recommendation = 'Investigasi penyebab peningkatan biaya signifikan di paruh kedua periode. Hal ini mungkin menunjukkan adanya masalah yang perlu diatasi seperti serangan hama atau kebutuhan perawatan tanaman yang meningkat.'; } else if (trendPercentage > 5) { trendAnalysis = 'Tren pengeluaran menunjukkan peningkatan moderat sebesar ${trendPercentage.abs().toStringAsFixed(1)}% di paruh kedua periode tanam.'; recommendation = 'Pantau dengan cermat pengeluaran pada fase-fases tertentu dan evaluasi apakah peningkatan biaya ini berkontribusi pada peningkatan hasil panen.'; } else if (trendPercentage > -5) { trendAnalysis = 'Tren pengeluaran relatif stabil sepanjang periode tanam dengan perubahan hanya ${trendPercentage.abs().toStringAsFixed(1)}%.'; recommendation = 'Pertahankan manajemen keuangan yang stabil dan terkendali seperti yang sudah dilakukan.'; } else if (trendPercentage > -20) { trendAnalysis = 'Tren pengeluaran menunjukkan penurunan moderat sebesar ${trendPercentage.abs().toStringAsFixed(1)}% di paruh kedua periode tanam.'; recommendation = 'Evaluasi apakah penurunan biaya ini merupakan hasil dari efisiensi atau mungkin karena pengurangan perawatan yang dapat mempengaruhi hasil panen.'; } else { trendAnalysis = 'Tren pengeluaran menunjukkan penurunan signifikan sebesar ${trendPercentage.abs().toStringAsFixed(1)}% di paruh kedua periode tanam.'; recommendation = 'Pastikan penurunan biaya yang signifikan ini tidak mempengaruhi kualitas dan kuantitas hasil panen. Verifikasi apakah ada faktor penting yang terlewatkan dalam proses budidaya.'; } return Card( elevation: 3, color: Colors.white, child: Padding( padding: const EdgeInsets.all(16.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( 'Analisis Tren Pengeluaran', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), ), const Divider(), const SizedBox(height: 8), Text(trendAnalysis), const SizedBox(height: 16), const Text( 'Rekomendasi:', style: TextStyle(fontWeight: FontWeight.bold), ), const SizedBox(height: 8), Text(recommendation), ], ), ), ); } void _showDailyLogDetail(Map log) { final date = DateTime.parse(log['date']); final formattedDate = DateFormat('dd MMMM yyyy').format(date); showModalBottomSheet( context: context, isScrollControlled: true, shape: const RoundedRectangleBorder( borderRadius: BorderRadius.vertical(top: Radius.circular(16)), ), builder: (context) { return Container( padding: const EdgeInsets.all(24), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Detail Pengeluaran: $formattedDate', style: const TextStyle( fontSize: 18, fontWeight: FontWeight.bold, ), ), const SizedBox(height: 16), _buildDetailRow('Jumlah:', currency.format(log['cost'] ?? 0)), if (log['note'] != null) _buildDetailRow('Catatan:', log['note']), if (log['image_url'] != null) ...[ const SizedBox(height: 16), const Text( 'Foto:', style: TextStyle(fontWeight: FontWeight.bold), ), const SizedBox(height: 8), ClipRRect( borderRadius: BorderRadius.circular(8), child: Image.network( log['image_url'], height: 200, fit: BoxFit.cover, errorBuilder: (_, __, ___) => const Icon(Icons.image_not_supported, size: 100), ), ), ], const SizedBox(height: 24), SizedBox( width: double.infinity, child: ElevatedButton( onPressed: () => Navigator.pop(context), style: ElevatedButton.styleFrom( padding: const EdgeInsets.symmetric(vertical: 12), ), child: const Text('Tutup'), ), ), ], ), ); }, ); } Widget _buildDetailRow(String label, String value) { return Padding( padding: const EdgeInsets.symmetric(vertical: 4.0), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ SizedBox( width: 100, child: Text( label, style: const TextStyle(fontWeight: FontWeight.bold), ), ), Expanded(child: Text(value)), ], ), ); } Color _getStatusColor(String? status) { switch (status) { case 'Baik': return Colors.green; case 'Cukup': return Colors.orange; case 'Kurang': return Colors.red; default: return Colors.grey; } } Color _getProfitRatioColor(double ratio) { if (ratio >= 30) { return Colors.green; } else if (ratio >= 15) { return Colors.orange; } else { return Colors.red; } } // Fungsi untuk mendapatkan target produktivitas berdasarkan jenis tanaman double _getTargetProductivity(String cropName) { String crop = cropName.toLowerCase(); if (crop.contains('padi')) { return 5500; // 5.5 ton/ha - Standar nasional } else if (crop.contains('jagung')) { return 5200; // 5.2 ton/ha - Standar nasional } else if (crop.contains('kedelai')) { return 1500; // 1.5 ton/ha - Standar nasional } else if (crop.contains('bawang')) { return 9500; // 9.5 ton/ha - Standar nasional } else if (crop.contains('cabai') || crop.contains('cabe')) { return 8000; // 8 ton/ha - Standar nasional } else if (crop.contains('tomat')) { return 16000; // 16 ton/ha - Standar nasional } else if (crop.contains('kentang')) { return 17000; // 17 ton/ha - Standar nasional } else if (crop.contains('kopi')) { return 700; // 0.7 ton/ha - Standar nasional } else if (crop.contains('kakao') || crop.contains('coklat')) { return 800; // 0.8 ton/ha - Standar nasional } else if (crop.contains('tebu')) { return 70000; // 70 ton/ha - Standar nasional } else if (crop.contains('kelapa sawit') || crop.contains('sawit')) { return 20000; // 20 ton/ha - Standar nasional } else { return 4000; // Default 4 ton/ha } } }