import 'package:flutter/material.dart'; import 'package:fl_chart/fl_chart.dart'; import 'package:intl/intl.dart'; import 'package:supabase_flutter/supabase_flutter.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; import 'package:flutter/rendering.dart'; import 'dart:ui' as ui; import 'dart:typed_data'; import 'package:tugas_akhir_supabase/utils/pdf_generator.dart'; import 'dart:io'; import 'package:tugas_akhir_supabase/screens/panen/analisis_chart_screen.dart'; import 'package:tugas_akhir_supabase/core/theme/app_colors.dart'; import 'package:google_fonts/google_fonts.dart'; class HarvestResultScreen extends StatefulWidget { final String userId; final Map harvestData; final Map? scheduleData; const HarvestResultScreen({ super.key, required this.userId, required this.harvestData, this.scheduleData, }); @override State createState() => _HarvestResultScreenState(); } class _HarvestResultScreenState extends State { final supabase = Supabase.instance.client; final currency = NumberFormat.currency(locale: 'id_ID', symbol: 'Rp '); // Tab index int _selectedTabIndex = 0; // Data fields from the previous analysis double? _produktivitasPerHektar; double? _totalBiayaProduksi; double? _pendapatanKotor; double? _keuntunganBersih; double? _rasioKeuntungan; String? _statusPanen; // Data from harvestData Map? get _harvestData => widget.harvestData; Map? get _selectedSchedule => widget.scheduleData; // GlobalKey for capturing chart view as image final GlobalKey _chartKey = GlobalKey(); @override void initState() { super.initState(); // Gunakan Future.microtask untuk menghindari setState selama build Future.microtask(() => _loadData()); } void _loadData() { try { final data = widget.harvestData; setState(() { _produktivitasPerHektar = data['productivity']; _totalBiayaProduksi = data['cost']; _pendapatanKotor = data['income']; _keuntunganBersih = data['profit']; _rasioKeuntungan = data['profit_margin']?.toDouble(); _statusPanen = data['status']; }); // Debug untuk memastikan data konsisten debugPrint('=== HASIL SCREEN DATA VALIDATION ==='); debugPrint('Cost: $_totalBiayaProduksi'); debugPrint('Income: $_pendapatanKotor'); debugPrint('Profit: $_keuntunganBersih'); debugPrint('Profit Margin: $_rasioKeuntungan%'); debugPrint('Status: $_statusPanen'); } catch (e) { debugPrint('Error loading harvest data: $e'); // Handle error gracefully setState(() { _produktivitasPerHektar = 0; _totalBiayaProduksi = 0; _pendapatanKotor = 0; _keuntunganBersih = 0; _rasioKeuntungan = 0; _statusPanen = 'Tidak diketahui'; }); } } @override Widget build(BuildContext context) { return GestureDetector( // Hapus unfocus otomatis yang menyebabkan masalah keyboard // onTap: () => FocusScope.of(context).unfocus(), child: Scaffold( body: SafeArea(child: _buildBody()), floatingActionButton: FloatingActionButton( onPressed: () { // Hapus unfocus yang mungkin menyebabkan masalah keyboard // FocusScope.of(context).unfocus(); // Small delay to ensure UI is responsive Future.delayed(const Duration(milliseconds: 50), () { if (mounted) { _exportToPdf(); } }); }, backgroundColor: Colors.green.shade700, tooltip: 'Ekspor PDF', child: const Icon(Icons.picture_as_pdf), ), ), ); } Widget _buildBody() { return DefaultTabController( length: 4, child: Scaffold( appBar: AppBar( title: const Text('Analisis Panen'), backgroundColor: AppColors.primary, foregroundColor: Colors.white, elevation: 0, actions: [ IconButton( icon: const Icon(Icons.refresh), tooltip: 'Refresh Data', onPressed: () { setState(() { // Reset data _produktivitasPerHektar = null; _totalBiayaProduksi = null; _pendapatanKotor = null; _keuntunganBersih = null; _rasioKeuntungan = null; _statusPanen = null; }); // Reload data _loadData(); ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Data berhasil diperbarui')), ); }, ), IconButton( icon: const Icon(Icons.help_outline), onPressed: () { // Hapus unfocus yang mungkin menyebabkan masalah keyboard // FocusScope.of(context).unfocus(); // Add small delay to ensure UI responsiveness Future.delayed(const Duration(milliseconds: 50), () { if (mounted) { showDialog( context: context, builder: (context) => AlertDialog( title: const Text('Tentang Analisis Panen'), content: const Text( 'Analisis panen mengukur produktivitas, efisiensi biaya, dan profitabilitas tanaman Anda berdasarkan indikator kelayakan usaha tani standar Indonesia.', ), actions: [ TextButton( onPressed: () => Navigator.pop(context), child: const Text('Tutup'), ), ], ), ); } }); }, ), ], bottom: TabBar( indicatorColor: Colors.white, labelColor: Colors.white, unselectedLabelColor: Colors.white60, isScrollable: true, onTap: (index) { setState(() { _selectedTabIndex = index; }); }, tabs: const [ Tab(icon: Icon(Icons.analytics, size: 20), text: 'Ringkasan'), Tab(icon: Icon(Icons.pie_chart, size: 20), text: 'Grafik'), Tab(icon: Icon(Icons.assessment, size: 20), text: 'Detail'), Tab( icon: Icon(Icons.agriculture, size: 20), text: 'Kelayakan Usaha Tani', ), ], ), ), body: Column( children: [ // Status header - More compact and modern Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16), decoration: BoxDecoration( color: Colors.white, boxShadow: [ BoxShadow( color: Colors.grey.withOpacity(0.1), spreadRadius: 1, blurRadius: 3, offset: const Offset(0, 1), ), ], ), child: Row( children: [ Container( padding: const EdgeInsets.all(10), decoration: BoxDecoration( color: _getStatusColor(_statusPanen), shape: BoxShape.circle, boxShadow: [ BoxShadow( color: _getStatusColor(_statusPanen).withOpacity(0.3), spreadRadius: 1, blurRadius: 4, offset: const Offset(0, 2), ), ], ), child: Icon( _getStatusIcon(_statusPanen), color: Colors.white, size: 24, ), ), const SizedBox(width: 16), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Container( padding: const EdgeInsets.symmetric( horizontal: 10, vertical: 4, ), decoration: BoxDecoration( color: _getStatusColor( _statusPanen, ).withOpacity(0.1), borderRadius: BorderRadius.circular(12), border: Border.all( color: _getStatusColor(_statusPanen), width: 1, ), ), child: Text( _statusPanen ?? 'Tidak diketahui', style: GoogleFonts.poppins( fontSize: 14, fontWeight: FontWeight.bold, color: _getStatusColor(_statusPanen), ), ), ), ], ), const SizedBox(height: 6), Text( _getStatusDescription(_statusPanen), style: GoogleFonts.poppins( color: Colors.grey.shade800, fontSize: 12, height: 1.3, ), ), ], ), ), ], ), ), // TabBarView wrapped in Expanded to avoid overflow Expanded( child: TabBarView( children: [ _buildSummaryTab(), _buildChartTab(), _buildDetailTab(), _buildIndonesianFarmAnalysisTab(), ], ), ), ], ), ), ); } Widget _buildSummaryTab() { // Extract crop information from scheduleData or harvestData final String cropName = _harvestData?['crop_name'] ?? (_selectedSchedule != null ? _selectedSchedule!['crop_name'] : 'Tanaman'); final String fieldName = _harvestData?['field_name'] ?? 'Lahan'; final String plotName = (_harvestData?['plot'] ?? 'Plot').toString(); // Format dates if available String periodInfo = ''; if (_harvestData?['start_date'] != null && _harvestData?['end_date'] != null) { try { final startDate = DateTime.parse(_harvestData!['start_date']); final endDate = DateTime.parse(_harvestData!['end_date']); periodInfo = '${DateFormat('dd/MM/yyyy').format(startDate)} - ${DateFormat('dd/MM/yyyy').format(endDate)}'; } catch (e) { periodInfo = ''; } } else if (_selectedSchedule?['start_date'] != null && _selectedSchedule?['end_date'] != null) { try { final startDate = DateTime.parse(_selectedSchedule!['start_date']); final endDate = DateTime.parse(_selectedSchedule!['end_date']); periodInfo = '${DateFormat('dd/MM/yyyy').format(startDate)} - ${DateFormat('dd/MM/yyyy').format(endDate)}'; } catch (e) { periodInfo = ''; } } return ListView( padding: const EdgeInsets.all(16.0), children: [ // Crop and field info card Card( elevation: 4, // Tingkatkan elevation shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), // Perbesar radius ), child: Padding( padding: const EdgeInsets.all(20.0), // Perbesar padding child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Icon( Icons.eco, color: Colors.green.shade700, ), // Tambahkan icon const SizedBox(width: 8), Text( 'Informasi Tanaman', style: GoogleFonts.poppins( fontSize: 18, fontWeight: FontWeight.bold, color: Colors.green.shade700, ), ), ], ), const SizedBox(height: 16), // Perbesar spacing _buildInfoRow('Tanaman', cropName, Icons.eco, Colors.green), const SizedBox(height: 10), _buildInfoRow( 'Lahan', '$fieldName • $plotName', Icons.landscape, Colors.brown, ), if (periodInfo.isNotEmpty) ...[ const SizedBox(height: 10), _buildInfoRow( 'Periode Tanam', periodInfo, Icons.calendar_today, Colors.blue, ), ], ], ), ), ), const SizedBox(height: 24), // Perbesar spacing antar card // Productivity card Card( elevation: 4, // Tingkatkan elevation shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), // Perbesar radius ), child: Padding( padding: const EdgeInsets.all(20.0), // Perbesar padding child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Icon( Icons.trending_up, color: Colors.orange.shade700, ), // Tambahkan icon const SizedBox(width: 8), Text( 'Produktivitas', style: GoogleFonts.poppins( fontSize: 18, fontWeight: FontWeight.bold, color: Colors.orange.shade700, ), ), ], ), const SizedBox(height: 16), // Perbesar spacing Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( _harvestData?['quantity'] != null ? '${NumberFormat("#,###.##", "id_ID").format(_harvestData!['quantity'])} kg' : 'N/A', style: GoogleFonts.poppins( fontSize: 20, fontWeight: FontWeight.bold, color: Colors.green, ), ), Text( 'Total Panen', style: GoogleFonts.poppins( fontSize: 12, color: Colors.grey, ), ), ], ), Column( crossAxisAlignment: CrossAxisAlignment.end, children: [ Text( _produktivitasPerHektar != null ? '${NumberFormat("#,###.##", "id_ID").format(_produktivitasPerHektar)} kg/ha' : 'N/A', style: GoogleFonts.poppins( fontSize: 20, fontWeight: FontWeight.bold, color: Colors.orange, ), ), Text( 'Produktivitas', style: GoogleFonts.poppins( fontSize: 12, color: Colors.grey, ), ), ], ), ], ), const SizedBox(height: 16), LinearProgressIndicator( value: _getProductivityRating(), backgroundColor: Colors.grey.shade200, valueColor: AlwaysStoppedAnimation( _getProductivityColor(), ), minHeight: 8, // Perbesar tinggi progress bar ), const SizedBox(height: 10), Text( _getProductivityMessage(), style: GoogleFonts.poppins( fontSize: 12, color: Colors.grey.shade700, fontStyle: FontStyle.italic, ), ), ], ), ), ), const SizedBox(height: 24), // Perbesar spacing antar card // Financial summary card Card( elevation: 4, // Tingkatkan elevation shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), // Perbesar radius ), child: Padding( padding: const EdgeInsets.all(20.0), // Perbesar padding child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Icon( Icons.account_balance_wallet, color: Colors.blue.shade700, ), // Tambahkan icon const SizedBox(width: 8), Text( 'Ringkasan Keuangan', style: GoogleFonts.poppins( fontSize: 18, fontWeight: FontWeight.bold, color: Colors.blue.shade700, ), ), ], ), const SizedBox(height: 20), // Perbesar spacing _buildFinancialRow( 'Biaya Produksi', _totalBiayaProduksi ?? 0, Colors.red.shade700, ), const SizedBox(height: 16), _buildFinancialRow( 'Pendapatan', _pendapatanKotor ?? 0, Colors.blue.shade700, ), const SizedBox(height: 16), _buildFinancialRow( 'Keuntungan', _keuntunganBersih ?? 0, _keuntunganBersih != null && _keuntunganBersih! >= 0 ? Colors.green.shade700 : Colors.red.shade700, ), const Divider(height: 32, thickness: 1), // Tambahkan divider Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( 'Margin Keuntungan', style: GoogleFonts.poppins(fontWeight: FontWeight.w500), ), Container( padding: const EdgeInsets.symmetric( horizontal: 12, vertical: 6, ), decoration: BoxDecoration( color: _getProfitMarginColor().withOpacity(0.1), borderRadius: BorderRadius.circular(16), border: Border.all( color: _getProfitMarginColor(), width: 1, ), ), child: Text( _rasioKeuntungan != null ? '${NumberFormat("#,###.##", "id_ID").format(_rasioKeuntungan)}%' : 'N/A', style: GoogleFonts.poppins( fontWeight: FontWeight.bold, fontSize: 16, color: _getProfitMarginColor(), ), ), ), ], ), ], ), ), ), const SizedBox(height: 24), // Perbesar spacing antar card // Feasibility card Card( elevation: 4, // Tingkatkan elevation shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), // Perbesar radius ), child: Padding( padding: const EdgeInsets.all(20.0), // Perbesar padding child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Icon( Icons.verified_user, color: Colors.green.shade700, ), // Tambahkan icon const SizedBox(width: 8), Text( 'Kelayakan Usaha Tani', style: GoogleFonts.poppins( fontSize: 18, fontWeight: FontWeight.bold, color: Colors.green.shade700, ), ), ], ), const SizedBox(height: 20), // Perbesar spacing _buildRatioRow( 'R/C Ratio', _getRcRatio(), 'Rasio pendapatan terhadap biaya', ), const SizedBox(height: 16), // Perbesar spacing _buildRatioRow( 'B/C Ratio', _getBcRatio(), 'Rasio keuntungan terhadap biaya', ), const SizedBox(height: 16), // Perbesar spacing _buildRatioRow( 'ROI', _harvestData?['roi'] ?? 0, 'Return on Investment (%)', isPercentage: true, ), ], ), ), ), const SizedBox(height: 24), // Perbesar spacing antar card // Chart button Builder( builder: (tabContext) => ElevatedButton.icon( onPressed: () { setState(() { _selectedTabIndex = 1; // Switch to chart tab DefaultTabController.of(tabContext).animateTo(1); }); }, icon: const Icon(Icons.bar_chart), label: Text( 'Lihat Grafik Analisis', style: GoogleFonts.poppins(fontWeight: FontWeight.w600), ), style: ElevatedButton.styleFrom( backgroundColor: AppColors.primary, foregroundColor: Colors.white, padding: const EdgeInsets.symmetric( vertical: 14, ), // Perbesar padding shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), // Perbesar radius ), ), ), ), ], ); } Widget _buildInfoRow(String label, String value, IconData icon, Color color) { return Row( children: [ Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( color: color.withOpacity(0.1), borderRadius: BorderRadius.circular(8), ), child: Icon(icon, color: color, size: 16), ), const SizedBox(width: 12), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( value, style: GoogleFonts.poppins( fontWeight: FontWeight.w500, fontSize: 14, ), ), Text( label, style: GoogleFonts.poppins( color: Colors.grey.shade600, fontSize: 12, ), ), ], ), ], ); } Widget _buildChartTab() { debugPrint('=== DEBUG CHART TAB ==='); debugPrint('isManualInput: \\${_selectedSchedule == null}'); debugPrint('harvestData: \\${_harvestData}'); return RepaintBoundary( key: _chartKey, child: HarvestAnalysisChart( userId: widget.userId, scheduleData: _selectedSchedule, harvestData: _harvestData, isManualInput: false, // pastikan false agar data harvestData dipakai ), ); } Widget _buildDetailTab() { return ListView( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16), children: [ // Cost breakdown card Card( elevation: 4, // Tingkatkan elevation color: Colors.white, margin: const EdgeInsets.only(bottom: 20), // Perbesar margin shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), // Perbesar radius ), child: Padding( padding: const EdgeInsets.all(20), // Perbesar padding child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Icon(Icons.money_off, color: Colors.red.shade700, size: 20), const SizedBox(width: 8), Text( 'Rincian Biaya Produksi', style: GoogleFonts.poppins( fontSize: 18, fontWeight: FontWeight.bold, color: Colors.red.shade700, ), ), ], ), const SizedBox(height: 16), // Pie chart and legend in a row SizedBox( height: 180, // Perbesar height child: Row( children: [ // Pie chart Expanded( flex: 3, child: PieChart( PieChartData( sectionsSpace: 2, centerSpaceRadius: 30, // Perbesar radius sections: _getCostPieSections(), ), ), ), // Legend Expanded( flex: 2, child: Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.start, children: [ _buildLegendItem('Bibit', Colors.green.shade800), const SizedBox(height: 6), _buildLegendItem('Pupuk', Colors.brown.shade600), const SizedBox(height: 6), _buildLegendItem( 'Pestisida', Colors.purple.shade700, ), const SizedBox(height: 6), _buildLegendItem( 'Tenaga Kerja', Colors.blue.shade700, ), const SizedBox(height: 6), _buildLegendItem('Irigasi', Colors.cyan.shade700), ], ), ), ], ), ), const Divider(height: 32, thickness: 1), // Tambahkan divider // Cost items in a more compact list _buildCostItem( 'Bibit', _harvestData?['seed_cost'] ?? 0, Colors.green.shade800, ), const SizedBox(height: 8), _buildCostItem( 'Pupuk', _harvestData?['fertilizer_cost'] ?? 0, Colors.brown.shade600, ), const SizedBox(height: 8), _buildCostItem( 'Pestisida', _harvestData?['pesticide_cost'] ?? 0, Colors.purple.shade700, ), const SizedBox(height: 8), _buildCostItem( 'Tenaga Kerja', _harvestData?['labor_cost'] ?? 0, Colors.blue.shade700, ), const SizedBox(height: 8), _buildCostItem( 'Irigasi', _harvestData?['irrigation_cost'] ?? 0, Colors.cyan.shade700, ), const Divider(height: 32, thickness: 1), // Tambahkan divider // Total cost Container( padding: const EdgeInsets.symmetric( vertical: 10, horizontal: 12, ), decoration: BoxDecoration( color: Colors.grey.shade100, borderRadius: BorderRadius.circular(12), ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( 'Total Biaya', style: GoogleFonts.poppins( fontWeight: FontWeight.bold, fontSize: 16, ), ), Text( currency.format(_totalBiayaProduksi ?? 0), style: GoogleFonts.poppins( fontWeight: FontWeight.bold, fontSize: 16, color: Colors.red.shade700, ), ), ], ), ), ], ), ), ), // Financial ratios card Card( elevation: 4, // Tingkatkan elevation color: Colors.white, margin: const EdgeInsets.only(bottom: 20), // Perbesar margin shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), // Perbesar radius ), child: Padding( padding: const EdgeInsets.all(20), // Perbesar padding child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Icon(Icons.analytics, color: AppColors.primary, size: 20), const SizedBox(width: 8), Text( 'Analisis Kelayakan Usaha Tani', style: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.bold, color: AppColors.primary, ), ), ], ), const SizedBox(height: 16), // Rasio-rasio keuangan _buildRatioItem( 'R/C Ratio', _getRcRatio(), '', 1.0, 1.5, _getRcRatioColor(_getRcRatio()), ), const SizedBox(height: 16), _buildRatioItem( 'B/C Ratio', _getBcRatio(), '', 0.0, 1.0, _getBcRatioColor(_getBcRatio()), ), const SizedBox(height: 16), _buildRatioItem( 'Profit Margin', _getProfitMargin(), '%', 0.0, 15.0, _getProfitMarginColor(_getProfitMargin()), ), const SizedBox(height: 20), // Penjelasan Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: Colors.blue.shade50, borderRadius: BorderRadius.circular(12), border: Border.all(color: Colors.blue.shade200), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Icon( Icons.info_outline, color: Colors.blue.shade800, size: 16, ), const SizedBox(width: 6), Text( 'Keterangan:', style: GoogleFonts.poppins( fontWeight: FontWeight.bold, fontSize: 14, color: Colors.blue.shade800, ), ), ], ), const SizedBox(height: 8), Text( '• R/C Ratio > 1: Usaha tani layak secara ekonomi\n' '• B/C Ratio > 0: Usaha tani menguntungkan\n' '• Profit Margin: Persentase keuntungan dari pendapatan', style: GoogleFonts.poppins( fontSize: 12, color: Colors.blue.shade900, height: 1.5, ), ), ], ), ), ], ), ), ), // Productivity analysis card Card( elevation: 2, color: Colors.white, margin: const EdgeInsets.only(bottom: 12), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), child: Padding( padding: const EdgeInsets.all(12), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Icon(Icons.insights, color: AppColors.primary, size: 16), const SizedBox(width: 6), const Text( 'Analisis Produktivitas', style: TextStyle( fontSize: 14, fontWeight: FontWeight.bold, ), ), ], ), const SizedBox(height: 10), // Row 1: Luas Lahan & Total Panen Row( children: [ Expanded( child: _buildSimpleInfoItem( 'Plot', '${widget.scheduleData?['plot'] ?? "Tidak diketahui"}', ), ), const SizedBox(width: 8), Expanded( child: _buildSimpleInfoItem( 'Total Panen', '${_harvestData?['quantity']?.toString() ?? "0"} kilogram', ), ), ], ), const SizedBox(height: 6), // Row 2: Produktivitas & Harga Jual Row( children: [ Expanded( child: _buildSimpleInfoItem( 'Produktivitas', '${_produktivitasPerHektar?.toStringAsFixed(2) ?? "0"} kilogram/ha', isHighlighted: true, ), ), const SizedBox(width: 8), Expanded( child: _buildSimpleInfoItem( 'Harga Jual', '${currency.format((_harvestData?['income'] ?? 0) / ((_harvestData?['quantity'] ?? 1) * 100))}/kg', ), ), ], ), const SizedBox(height: 6), // Row 3: Pendapatan & Keuntungan Row( children: [ Expanded( child: _buildSimpleInfoItem( 'Pendapatan', currency.format(_pendapatanKotor ?? 0), ), ), const SizedBox(width: 8), Expanded( child: _buildSimpleInfoItem( 'Keuntungan', currency.format(_keuntunganBersih ?? 0), isHighlighted: true, ), ), ], ), const SizedBox(height: 6), // Benchmark visualization Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Benchmark Panen', style: TextStyle( fontWeight: FontWeight.w500, color: Colors.grey.shade800, fontSize: 13, ), ), const SizedBox(height: 8), _buildBenchmarkItem( 'Produktivitas', _produktivitasPerHektar ?? 0, 3000.0, 'kilogram/ha', 5000.0, ), const SizedBox(height: 10), _buildBenchmarkItem( 'R/C Ratio', _getRcRatio(), 1.0, '', 1.5, ), ], ), ], ), ), ), // Financial comparison analysis _buildFinancialComparisonAnalysis(), ], ); } Widget _buildSimpleInfoItem( String label, String value, { bool isHighlighted = false, Color? valueColor, }) { return Container( width: double.infinity, decoration: BoxDecoration( color: isHighlighted ? AppColors.lightGreen.withOpacity(0.2) : Colors.grey.shade50, borderRadius: BorderRadius.circular(10), border: Border.all( color: isHighlighted ? AppColors.primary.withOpacity(0.3) : Colors.grey.shade200, width: 1, ), boxShadow: isHighlighted ? [ BoxShadow( color: AppColors.primary.withOpacity(0.1), blurRadius: 4, offset: const Offset(0, 2), ), ] : null, ), padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ Text( label, style: GoogleFonts.poppins( fontSize: 11, color: Colors.grey.shade700, fontWeight: FontWeight.w500, ), maxLines: 1, overflow: TextOverflow.ellipsis, ), const SizedBox(height: 4), FittedBox( fit: BoxFit.scaleDown, alignment: Alignment.centerLeft, child: Text( value, style: GoogleFonts.poppins( fontWeight: FontWeight.bold, color: valueColor ?? (isHighlighted ? AppColors.primary : Colors.grey.shade800), fontSize: 14, ), maxLines: 1, ), ), ], ), ); } Widget _buildMetricCard( String title, String value, IconData icon, Color color, ) { return Card( elevation: 2, color: Colors.white, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), child: Padding( padding: const EdgeInsets.all(10), child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Row( children: [ Icon(icon, color: color, size: 16), const SizedBox(width: 4), Expanded( child: Text( title, style: TextStyle( color: color, fontWeight: FontWeight.w500, fontSize: 12, ), overflow: TextOverflow.ellipsis, ), ), ], ), FittedBox( fit: BoxFit.scaleDown, alignment: Alignment.centerLeft, child: Text( value, style: const TextStyle( fontSize: 14, fontWeight: FontWeight.bold, ), ), ), ], ), ), ); } Widget _buildCostItem(String title, double value, Color iconColor) { return Container( padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 10), decoration: BoxDecoration( color: iconColor.withOpacity(0.05), borderRadius: BorderRadius.circular(8), ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Row( children: [ Container( width: 10, height: 10, decoration: BoxDecoration( color: iconColor, borderRadius: BorderRadius.circular(3), ), ), const SizedBox(width: 8), Text( title, style: GoogleFonts.poppins( fontSize: 13, fontWeight: FontWeight.w500, ), ), ], ), Text( currency.format(value), style: GoogleFonts.poppins( fontSize: 13, fontWeight: FontWeight.bold, color: iconColor, ), ), ], ), ); } Widget _buildLegendItem(String title, Color color) { return Row( children: [ Container( width: 10, height: 10, decoration: BoxDecoration( color: color, borderRadius: BorderRadius.circular(3), ), ), const SizedBox(width: 6), Expanded( child: Text( title, style: GoogleFonts.poppins( fontSize: 11, color: Colors.grey.shade800, ), overflow: TextOverflow.ellipsis, ), ), ], ); } Widget _buildBenchmarkItem( String label, double value, double benchmark, String unit, double excellent, ) { final double percentage = value / excellent * 100; Color progressColor; if (value >= excellent) { progressColor = AppColors.primary; } else if (value >= benchmark) { progressColor = Colors.orange.shade600; } else { progressColor = Colors.red.shade600; } return Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: progressColor.withOpacity(0.05), borderRadius: BorderRadius.circular(10), border: Border.all(color: progressColor.withOpacity(0.2), width: 1), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( label, style: GoogleFonts.poppins( fontSize: 14, fontWeight: FontWeight.w500, color: Colors.grey.shade800, ), ), Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), decoration: BoxDecoration( color: progressColor.withOpacity(0.1), borderRadius: BorderRadius.circular(8), border: Border.all(color: progressColor, width: 1), ), child: Text( unit == 'ton/ha' ? '${value.toStringAsFixed(2)} kilogram/ha' : '${value.toStringAsFixed(2)} $unit', style: GoogleFonts.poppins( fontWeight: FontWeight.bold, color: progressColor, fontSize: 12, ), ), ), ], ), const SizedBox(height: 12), Stack( children: [ Container( height: 8, decoration: BoxDecoration( color: Colors.grey.shade200, borderRadius: BorderRadius.circular(4), ), ), Container( height: 8, width: ((MediaQuery.of(context).size.width - 100) * percentage / 100) .clamp(0.0, double.infinity), decoration: BoxDecoration( color: progressColor, borderRadius: BorderRadius.circular(4), ), ), ], ), const SizedBox(height: 8), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( 'Min: 0', style: GoogleFonts.poppins( fontSize: 10, color: Colors.grey.shade600, ), ), Text( 'Target: $benchmark', style: GoogleFonts.poppins( fontSize: 10, color: Colors.orange.shade600, ), ), Text( 'Optimal: $excellent', style: GoogleFonts.poppins( fontSize: 10, color: Colors.green.shade600, ), ), ], ), ], ), ); } List _getCostPieSections() { final total = _totalBiayaProduksi ?? 1; // avoid division by zero final seedCost = _harvestData?['seed_cost'] ?? 0; final fertilizerCost = _harvestData?['fertilizer_cost'] ?? 0; final pesticideCost = _harvestData?['pesticide_cost'] ?? 0; final laborCost = _harvestData?['labor_cost'] ?? 0; final irrigationCost = _harvestData?['irrigation_cost'] ?? 0; return [ if (seedCost > 0) PieChartSectionData( value: seedCost, title: '${((seedCost / total) * 100).toStringAsFixed(0)}%', color: AppColors.primary, radius: 45, titleStyle: const TextStyle( color: Colors.white, fontWeight: FontWeight.bold, fontSize: 10, ), ), if (fertilizerCost > 0) PieChartSectionData( value: fertilizerCost, title: '${((fertilizerCost / total) * 100).toStringAsFixed(0)}%', color: Colors.brown.shade600, radius: 45, titleStyle: const TextStyle( color: Colors.white, fontWeight: FontWeight.bold, fontSize: 10, ), ), if (pesticideCost > 0) PieChartSectionData( value: pesticideCost, title: '${((pesticideCost / total) * 100).toStringAsFixed(0)}%', color: Colors.purple.shade700, radius: 45, titleStyle: const TextStyle( color: Colors.white, fontWeight: FontWeight.bold, fontSize: 10, ), ), if (laborCost > 0) PieChartSectionData( value: laborCost, title: '${((laborCost / total) * 100).toStringAsFixed(0)}%', color: Colors.blue.shade700, radius: 45, titleStyle: const TextStyle( color: Colors.white, fontWeight: FontWeight.bold, fontSize: 10, ), ), if (irrigationCost > 0) PieChartSectionData( value: irrigationCost, title: '${((irrigationCost / total) * 100).toStringAsFixed(0)}%', color: Colors.cyan.shade700, radius: 45, titleStyle: const TextStyle( color: Colors.white, fontWeight: FontWeight.bold, fontSize: 10, ), ), ]; } IconData _getStatusIcon(String? status) { switch (status) { case 'Sangat Layak': return Icons.verified; case 'Layak': return Icons.check_circle; case 'Cukup Layak': return Icons.thumbs_up_down; case 'Tidak Layak': return Icons.warning; default: return Icons.help_outline; } } Color _getStatusColor(String? status) { switch (status) { case 'Sangat Layak': return Colors.green.shade800; case 'Layak': return Colors.green.shade600; case 'Cukup Layak': return Colors.orange.shade600; case 'Tidak Layak': return Colors.red.shade600; default: return Colors.grey; } } String _getStatusDescription(String? status) { final rcRatio = _getRcRatio(); final bcRatio = _getBcRatio(); switch (status) { case 'Sangat Layak': return 'R/C Ratio ${rcRatio.toStringAsFixed(2)} - Usaha tani sangat layak dengan keuntungan optimal. Pendapatan jauh lebih tinggi dari biaya produksi.'; case 'Layak': return 'R/C Ratio ${rcRatio.toStringAsFixed(2)} - Usaha tani layak secara ekonomi. Pendapatan cukup untuk menutupi biaya dan memberikan keuntungan yang baik.'; case 'Cukup Layak': return 'R/C Ratio ${rcRatio.toStringAsFixed(2)} - Usaha tani cukup layak, namun perlu optimasi biaya dan peningkatan produktivitas.'; case 'Tidak Layak': return 'R/C Ratio ${rcRatio.toStringAsFixed(2)} - Usaha tani tidak layak secara ekonomi. Pendapatan tidak cukup untuk menutupi biaya produksi.'; default: return ''; } } String _getRecommendation(String? status) { // Ambil R/C Ratio untuk analisis lebih spesifik final rcRatio = _getRcRatio(); final bcRatio = _getBcRatio(); final produktivitas = _produktivitasPerHektar ?? 0; switch (status) { case 'Sangat Layak': if (produktivitas >= 6000) { return 'Usaha tani sangat layak (R/C Ratio ${rcRatio.toStringAsFixed(2)}) dengan produktivitas tinggi. Pertahankan praktik budidaya yang sudah diterapkan dan pertimbangkan untuk perluasan skala usaha.'; } else { return 'Usaha tani sangat layak (R/C Ratio ${rcRatio.toStringAsFixed(2)}). Tingkatkan produktivitas dengan teknologi budidaya yang lebih baik untuk memaksimalkan keuntungan.'; } case 'Layak': if (produktivitas >= 5000) { return 'Usaha tani layak (R/C Ratio ${rcRatio.toStringAsFixed(2)}) dengan produktivitas baik. Optimalkan penggunaan input produksi untuk meningkatkan efisiensi dan profitabilitas.'; } else { return 'Usaha tani layak (R/C Ratio ${rcRatio.toStringAsFixed(2)}). Tingkatkan produktivitas melalui penerapan teknologi budidaya yang lebih baik dan manajemen usaha tani yang lebih efisien.'; } case 'Cukup Layak': return 'Usaha tani cukup layak (R/C Ratio ${rcRatio.toStringAsFixed(2)}) namun berisiko jika terjadi kenaikan biaya atau penurunan harga. Efisiensi penggunaan input produksi perlu ditingkatkan dan struktur biaya perlu dievaluasi.'; case 'Tidak Layak': return 'Usaha tani tidak layak secara ekonomi (R/C Ratio ${rcRatio.toStringAsFixed(2)} < 1). Evaluasi kembali seluruh komponen usaha tani termasuk pemilihan komoditas, teknologi budidaya, dan strategi pemasaran. Pertimbangkan alternatif usaha tani yang lebih menguntungkan.'; default: return 'Belum dapat memberikan rekomendasi spesifik.'; } } // Fungsi untuk mengekspor data ke PDF Future _exportToPdf() async { try { // Show loading indicator showDialog( context: context, barrierDismissible: false, builder: (context) => const Center(child: CircularProgressIndicator()), ); // Capture chart view as image if available Uint8List? chartImageBytes; if (_selectedTabIndex == 1) { // Switch to chart tab if not already on it setState(() { _selectedTabIndex = 1; }); // Wait for the UI to update await Future.delayed(const Duration(milliseconds: 300)); // Try to capture the chart try { chartImageBytes = await _captureChartAsImage(); } catch (e) { debugPrint('Failed to capture chart image: $e'); } } // Get daily logs data for more comprehensive report List>? dailyLogs; if (widget.scheduleData != null) { try { final scheduleId = widget.scheduleData!['id']; final res = await supabase .from('daily_logs') .select() .eq('schedule_id', scheduleId) .order('date', ascending: true); if (res.isNotEmpty) { dailyLogs = List>.from(res); } } catch (e) { debugPrint('Error fetching daily logs for PDF: $e'); } } // Generate PDF using the HarvestPdfGenerator final pdfGenerator = HarvestPdfGenerator(); final pdfFile = await pdfGenerator.generatePdf( title: 'Laporan Analisis Panen', harvestData: _harvestData ?? {}, scheduleData: widget.scheduleData, dailyLogs: dailyLogs, chartImageBytes: chartImageBytes, ); // Close loading dialog if (!context.mounted) return; Navigator.pop(context); // Show success dialog with options showDialog( context: context, builder: (context) => AlertDialog( title: const Text('PDF Berhasil Dibuat'), content: SingleChildScrollView( child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( 'Laporan PDF analisis panen telah berhasil dibuat.', ), const SizedBox(height: 8), Text( 'Lokasi: ${pdfFile.path}', style: const TextStyle( fontSize: 12, fontStyle: FontStyle.italic, ), ), ], ), ), actions: [ TextButton( onPressed: () { Navigator.pop(context); }, child: const Text('Tutup'), ), TextButton( onPressed: () { Navigator.pop(context); _sharePdf(pdfFile); }, child: const Text('Bagikan'), ), TextButton( onPressed: () { Navigator.pop(context); _openPdf(pdfFile); }, child: const Text('Buka'), ), ], ), ); } catch (e) { // Close loading dialog if open if (context.mounted) { Navigator.pop(context); } // Show error message ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('Gagal membuat PDF: ${e.toString()}'), backgroundColor: Colors.red, ), ); } } // Function to capture chart as image Future _captureChartAsImage() async { try { // Find the RenderRepaintBoundary object associated with the key RenderRepaintBoundary? boundary = _chartKey.currentContext?.findRenderObject() as RenderRepaintBoundary?; if (boundary == null) { debugPrint('Could not find chart boundary'); return null; } // Capture the image ui.Image image = await boundary.toImage(pixelRatio: 3.0); ByteData? byteData = await image.toByteData( format: ui.ImageByteFormat.png, ); if (byteData == null) { debugPrint('Failed to convert image to bytes'); return null; } return byteData.buffer.asUint8List(); } catch (e) { debugPrint('Error capturing chart image: $e'); return null; } } // Helper function to open the PDF Future _openPdf(File file) async { try { final pdfGenerator = HarvestPdfGenerator(); await pdfGenerator.openPdf(file); } catch (e) { if (!context.mounted) return; // If opening fails, show dialog with options showDialog( context: context, builder: (context) => AlertDialog( title: const Text('Gagal Membuka PDF'), content: const Text( 'Tidak dapat membuka file PDF secara langsung. ' 'Silakan bagikan file untuk dibuka dengan aplikasi lain.', ), actions: [ TextButton( onPressed: () => Navigator.pop(context), child: const Text('Tutup'), ), TextButton( onPressed: () { Navigator.pop(context); _sharePdf(file); }, child: const Text('Bagikan'), ), ], ), ); } } // Helper function to share the PDF Future _sharePdf(File file) async { try { final pdfGenerator = HarvestPdfGenerator(); await pdfGenerator.sharePdf(file); } catch (e) { if (!context.mounted) return; ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('Gagal membagikan PDF: ${e.toString()}'), backgroundColor: Colors.red, ), ); } } // Helper method untuk mendapatkan R/C Ratio double _getRcRatio() { final cost = _totalBiayaProduksi ?? 1.0; final income = _pendapatanKotor ?? 0.0; return cost > 0 ? income / cost : 0.0; } // Helper method untuk mendapatkan B/C Ratio double _getBcRatio() { final cost = _totalBiayaProduksi ?? 1.0; final profit = _keuntunganBersih ?? 0.0; return cost > 0 ? profit / cost : 0.0; } // Helper method untuk mendapatkan Profit Margin double _getProfitMargin() { final income = _pendapatanKotor ?? 1.0; final profit = _keuntunganBersih ?? 0.0; return income > 0 ? (profit / income) * 100 : 0.0; } // Fungsi untuk menentukan warna margin keuntungan Color _getProfitMarginColor([double? value]) { if (value != null) { // Gunakan nilai yang diberikan if (value >= 40) { return Colors.green.shade800; } else if (value >= 25) { return Colors.green; } else if (value >= 15) { return Colors.orange; } else if (value >= 0) { return Colors.orange.shade300; } else { return Colors.red; } } else { // Gunakan nilai dari _rasioKeuntungan if (_rasioKeuntungan == null) return Colors.grey; if (_rasioKeuntungan! >= 40) { return Colors.green.shade800; } else if (_rasioKeuntungan! >= 25) { return Colors.green; } else if (_rasioKeuntungan! >= 15) { return Colors.orange; } else if (_rasioKeuntungan! >= 0) { return Colors.orange.shade300; } else { return Colors.red; } } } // Widget untuk menampilkan item ratio Widget _buildRatioItem( String label, double value, String unit, double minThreshold, double goodThreshold, Color valueColor, ) { return Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( color: Colors.grey.shade50, borderRadius: BorderRadius.circular(6), border: Border.all(color: valueColor.withOpacity(0.3)), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( label, style: TextStyle(fontSize: 13, fontWeight: FontWeight.w500), ), Text( '${value.toStringAsFixed(2)}${unit.isNotEmpty ? ' $unit' : ''}', style: TextStyle( fontSize: 13, fontWeight: FontWeight.bold, color: valueColor, ), ), ], ), const SizedBox(height: 6), Stack( children: [ Container( height: 4, decoration: BoxDecoration( color: Colors.grey.shade200, borderRadius: BorderRadius.circular(2), ), ), Container( height: 4, width: value <= 0 ? 0 : (value > goodThreshold * 2 ? 1.0 : value / (goodThreshold * 2)) * MediaQuery.of(context).size.width * 0.7, decoration: BoxDecoration( color: valueColor, borderRadius: BorderRadius.circular(2), ), ), ], ), const SizedBox(height: 4), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( minThreshold.toStringAsFixed(1), style: TextStyle(fontSize: 10, color: Colors.grey.shade600), ), Text( goodThreshold.toStringAsFixed(1), style: TextStyle(fontSize: 10, color: Colors.grey.shade600), ), ], ), ], ), ); } Widget _buildFinancialComparisonAnalysis() { final totalCost = (_totalBiayaProduksi ?? 0.0).toDouble(); final income = (_pendapatanKotor ?? 0.0).toDouble(); final profit = (_keuntunganBersih ?? 0.0).toDouble(); final profitMargin = (_rasioKeuntungan ?? 0.0).toDouble(); final rcRatio = _getRcRatio(); final bcRatio = _getBcRatio(); final roi = _harvestData?['roi'] ?? 0.0; // Tambahan indikator kelayakan usaha tani Indonesia final bepPrice = _harvestData?['bep_price'] ?? 0.0; final bepProduction = _harvestData?['bep_production'] ?? 0.0; final productionCostPerKg = _harvestData?['production_cost_per_kg'] ?? 0.0; final quantity = _harvestData?['quantity'] ?? 0.0; final pricePerKg = _harvestData?['price_per_kg'] ?? 0.0; final area = _harvestData?['area'] ?? 0.0; final productivity = _harvestData?['productivity'] ?? 0.0; // Informasi tanaman final cropName = _harvestData?['crop_name'] ?? 'Tanaman'; final weatherCondition = _harvestData?['weather_condition'] ?? 'Normal'; final irrigationType = _harvestData?['irrigation_type'] ?? 'Irigasi Teknis'; final soilType = _harvestData?['soil_type'] ?? 'Lempung'; final fertilizerType = _harvestData?['fertilizer_type'] ?? 'NPK'; String profitabilityAnalysis; String ratioAnalysis; String bepAnalysis; String recommendation; String conditionAnalysis; // 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)}). Margin keuntungan ${profitMargin.toStringAsFixed(2)}%.'; recommendation = 'Evaluasi ulang 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)}%. Produktivitas ${productivity.toStringAsFixed(0)} kg/ha masih bisa ditingkatkan.'; recommendation = 'Periksa komponen biaya yang mungkin terlalu tinggi (terutama ${_getHighestCostComponent()}) 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)}%. Produktivitas ${productivity.toStringAsFixed(0)} kg/ha cukup baik.'; recommendation = 'Pertahankan praktik yang baik dan cari peluang untuk meningkatkan skala produksi atau efisiensi lebih lanjut. Pertimbangkan untuk mengurangi komponen biaya ${_getHighestCostComponent()}.'; } else { profitabilityAnalysis = 'Panen $cropName ini sangat menguntungkan dengan keuntungan ${currency.format(profit)} dan margin profit mencapai ${profitMargin.toStringAsFixed(2)}%. Produktivitas ${productivity.toStringAsFixed(0)} kg/ha sangat baik.'; recommendation = 'Pertahankan praktik yang sudah sangat baik dan pertimbangkan untuk meningkatkan skala produksi untuk keuntungan yang lebih besar. Dokumentasikan praktik-praktik terbaik untuk siklus tanam berikutnya.'; } // 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 atau penurunan harga jual.'; } 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 BEP (Break Even Point) - Standar Kementerian Pertanian bepAnalysis = 'BEP Harga: ${currency.format(bepPrice)}/kg - '; if (pricePerKg > bepPrice) { final margin = ((pricePerKg - bepPrice) / bepPrice * 100).toStringAsFixed( 1, ); bepAnalysis += 'Harga jual (${currency.format(pricePerKg)}/kg) lebih tinggi ${margin}% dari BEP Harga, menunjukkan usaha tani menguntungkan. '; } else { final gap = ((bepPrice - pricePerKg) / bepPrice * 100).toStringAsFixed(1); bepAnalysis += 'Harga jual (${currency.format(pricePerKg)}/kg) lebih rendah ${gap}% dari BEP Harga, menunjukkan usaha tani merugi. '; } bepAnalysis += 'BEP Produksi: ${bepProduction.toStringAsFixed(2)} kg - '; if (quantity > bepProduction) { final margin = ((quantity - bepProduction) / bepProduction * 100) .toStringAsFixed(1); bepAnalysis += 'Produksi (${quantity.toStringAsFixed(2)} kg) lebih tinggi ${margin}% dari BEP Produksi, menunjukkan usaha tani menguntungkan.'; } else { final gap = ((bepProduction - quantity) / bepProduction * 100) .toStringAsFixed(1); bepAnalysis += 'Produksi (${quantity.toStringAsFixed(2)} kg) lebih rendah ${gap}% dari BEP Produksi, menunjukkan usaha tani merugi.'; } // Analisis kondisi tanam conditionAnalysis = 'Tanaman $cropName ditanam pada kondisi cuaca $weatherCondition dengan sistem $irrigationType pada tanah $soilType menggunakan pupuk $fertilizerType. '; // Analisis pengaruh kondisi terhadap hasil if (weatherCondition != 'Normal') { conditionAnalysis += 'Kondisi cuaca $weatherCondition dapat mempengaruhi produktivitas. '; } if (irrigationType.contains('Tadah Hujan')) { conditionAnalysis += 'Sistem irigasi tadah hujan meningkatkan risiko kegagalan panen saat kekeringan. '; } if (soilType.contains('Pasir')) { conditionAnalysis += 'Tanah berpasir memiliki retensi air dan nutrisi yang rendah, perlu penambahan bahan organik. '; } else if (soilType.contains('Liat')) { conditionAnalysis += 'Tanah liat memiliki drainase yang buruk, perlu perhatian pada sistem irigasi. '; } return Card( elevation: 4, // Tingkatkan elevation color: Colors.white, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), // Perbesar radius ), child: Padding( padding: const EdgeInsets.all(20.0), // Perbesar padding child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Icon(Icons.assessment, color: AppColors.primary, size: 20), const SizedBox(width: 8), Text( 'Analisis Kelayakan Usaha Tani', style: GoogleFonts.poppins( fontSize: 14, fontWeight: FontWeight.bold, color: AppColors.primary, ), ), ], ), const Divider(height: 32, thickness: 1), const SizedBox(height: 8), // Profitability Analysis Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: profit > 0 ? Colors.green.shade50 : Colors.red.shade50, borderRadius: BorderRadius.circular(12), border: Border.all( color: profit > 0 ? Colors.green.shade200 : Colors.red.shade200, ), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Icon( profit > 0 ? Icons.trending_up : Icons.trending_down, color: profit > 0 ? Colors.green.shade700 : Colors.red.shade700, size: 16, ), const SizedBox(width: 6), Text( 'Analisis Profitabilitas', style: GoogleFonts.poppins( fontWeight: FontWeight.bold, fontSize: 14, color: profit > 0 ? Colors.green.shade700 : Colors.red.shade700, ), ), ], ), const SizedBox(height: 8), Text( profitabilityAnalysis, style: GoogleFonts.poppins(fontSize: 13, height: 1.5), ), ], ), ), const SizedBox(height: 16), // Ratio Analysis Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: rcRatio >= 1.0 ? Colors.blue.shade50 : Colors.orange.shade50, borderRadius: BorderRadius.circular(12), border: Border.all( color: rcRatio >= 1.0 ? Colors.blue.shade200 : Colors.orange.shade200, ), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Icon( Icons.pie_chart, color: rcRatio >= 1.0 ? Colors.blue.shade700 : Colors.orange.shade700, size: 16, ), const SizedBox(width: 6), Text( 'Analisis Rasio Keuangan', style: GoogleFonts.poppins( fontWeight: FontWeight.bold, fontSize: 14, color: rcRatio >= 1.0 ? Colors.blue.shade700 : Colors.orange.shade700, ), ), ], ), const SizedBox(height: 8), Text( ratioAnalysis, style: GoogleFonts.poppins(fontSize: 13, height: 1.5), ), ], ), ), const SizedBox(height: 16), // BEP Analysis Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: (pricePerKg > bepPrice || quantity > bepProduction) ? Colors.green.shade50 : Colors.red.shade50, borderRadius: BorderRadius.circular(12), border: Border.all( color: (pricePerKg > bepPrice || quantity > bepProduction) ? Colors.green.shade200 : Colors.red.shade200, ), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Icon( Icons.balance, color: (pricePerKg > bepPrice || quantity > bepProduction) ? Colors.green.shade700 : Colors.red.shade700, size: 16, ), const SizedBox(width: 6), Text( 'Analisis Break Even Point', style: GoogleFonts.poppins( fontWeight: FontWeight.bold, fontSize: 14, color: (pricePerKg > bepPrice || quantity > bepProduction) ? Colors.green.shade700 : Colors.red.shade700, ), ), ], ), const SizedBox(height: 8), Text( bepAnalysis, style: GoogleFonts.poppins(fontSize: 13, height: 1.5), ), ], ), ), const SizedBox(height: 16), // Condition Analysis Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: Colors.grey.shade50, borderRadius: BorderRadius.circular(12), border: Border.all(color: Colors.grey.shade300), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Icon(Icons.eco, color: Colors.green.shade700, size: 16), const SizedBox(width: 6), Text( 'Analisis Kondisi Tanam', style: GoogleFonts.poppins( fontWeight: FontWeight.bold, fontSize: 14, color: Colors.green.shade700, ), ), ], ), const SizedBox(height: 8), Text( conditionAnalysis, style: GoogleFonts.poppins(fontSize: 13, height: 1.5), ), ], ), ), const SizedBox(height: 16), // Production Cost Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: Colors.blue.shade50, borderRadius: BorderRadius.circular(12), border: Border.all(color: Colors.blue.shade200), ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Row( children: [ Icon( Icons.calculate, color: Colors.blue.shade700, size: 16, ), const SizedBox(width: 6), Text( 'Biaya Pokok Produksi:', style: GoogleFonts.poppins( fontWeight: FontWeight.bold, fontSize: 10, color: Colors.blue.shade700, ), ), ], ), Text( '${currency.format(productionCostPerKg)}/kg', style: GoogleFonts.poppins( fontWeight: FontWeight.bold, fontSize: 14, color: Colors.blue.shade900, ), ), ], ), ), const SizedBox(height: 20), // Recommendation Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: Colors.amber.shade50, borderRadius: BorderRadius.circular(12), border: Border.all(color: Colors.amber.shade200), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Icon( Icons.lightbulb, color: Colors.amber.shade800, size: 16, ), const SizedBox(width: 6), Text( 'Rekomendasi:', style: GoogleFonts.poppins( fontWeight: FontWeight.bold, fontSize: 14, color: Colors.amber.shade800, ), ), ], ), const SizedBox(height: 8), Text( recommendation, style: GoogleFonts.poppins(fontSize: 13, height: 1.5), ), ], ), ), ], ), ), ); } // Fungsi untuk mendapatkan komponen biaya tertinggi String _getHighestCostComponent() { if (_harvestData == null) return 'biaya produksi'; double seedCost = _harvestData?['seed_cost'] ?? 0; double fertilizerCost = _harvestData?['fertilizer_cost'] ?? 0; double pesticideCost = _harvestData?['pesticide_cost'] ?? 0; double laborCost = _harvestData?['labor_cost'] ?? 0; double irrigationCost = _harvestData?['irrigation_cost'] ?? 0; double landPreparationCost = _harvestData?['land_preparation_cost'] ?? 0; double toolsEquipmentCost = _harvestData?['tools_equipment_cost'] ?? 0; double transportationCost = _harvestData?['transportation_cost'] ?? 0; double postHarvestCost = _harvestData?['post_harvest_cost'] ?? 0; double otherCost = _harvestData?['other_cost'] ?? 0; Map costs = { 'bibit': seedCost, 'pupuk': fertilizerCost, 'pestisida': pesticideCost, 'tenaga kerja': laborCost, 'irigasi': irrigationCost, 'persiapan lahan': landPreparationCost, 'alat dan peralatan': toolsEquipmentCost, 'transportasi': transportationCost, 'pasca panen': postHarvestCost, 'lain-lain': otherCost, }; String highestComponent = 'biaya produksi'; double highestValue = 0; costs.forEach((component, value) { if (value > highestValue) { highestValue = value; highestComponent = component; } }); return highestComponent; } // Fungsi untuk menentukan rating produktivitas double _getProductivityRating() { if (_produktivitasPerHektar == null) return 0.0; // Standar produktivitas berdasarkan jenis tanaman String cropName = _harvestData?['crop_name']?.toString().toLowerCase() ?? ''; double targetProductivity = 0.0; // Menggunakan standar produktivitas nasional berdasarkan jenis tanaman if (cropName.contains('padi')) { targetProductivity = 5500; // 5.5 ton/ha - Standar nasional } else if (cropName.contains('jagung')) { targetProductivity = 5200; // 5.2 ton/ha - Standar nasional } else if (cropName.contains('kedelai')) { targetProductivity = 1500; // 1.5 ton/ha - Standar nasional } else if (cropName.contains('bawang')) { targetProductivity = 9500; // 9.5 ton/ha - Standar nasional } else if (cropName.contains('cabai') || cropName.contains('cabe')) { targetProductivity = 8000; // 8 ton/ha - Standar nasional } else if (cropName.contains('tomat')) { targetProductivity = 16000; // 16 ton/ha - Standar nasional } else if (cropName.contains('kentang')) { targetProductivity = 17000; // 17 ton/ha - Standar nasional } else if (cropName.contains('kopi')) { targetProductivity = 700; // 0.7 ton/ha - Standar nasional } else if (cropName.contains('kakao') || cropName.contains('coklat')) { targetProductivity = 800; // 0.8 ton/ha - Standar nasional } else if (cropName.contains('tebu')) { targetProductivity = 70000; // 70 ton/ha - Standar nasional } else if (cropName.contains('kelapa sawit') || cropName.contains('sawit')) { targetProductivity = 20000; // 20 ton/ha - Standar nasional } else { targetProductivity = 4000; // Default 4 ton/ha } return (_produktivitasPerHektar! / targetProductivity).clamp(0.0, 1.0); } // Fungsi untuk menentukan warna produktivitas Color _getProductivityColor() { double rating = _getProductivityRating(); if (rating >= 0.9) { return Colors.green.shade800; } else if (rating >= 0.7) { return Colors.green; } else if (rating >= 0.5) { return Colors.orange; } else { return Colors.red; } } // Fungsi untuk menentukan pesan produktivitas String _getProductivityMessage() { double rating = _getProductivityRating(); String cropName = _harvestData?['crop_name']?.toString().toLowerCase() ?? ''; double productivity = _produktivitasPerHektar ?? 0; // Mendapatkan standar produktivitas untuk 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; } if (rating >= 0.9) { return 'Produktivitas sangat baik (${productivity.toStringAsFixed(0)} kg/ha), melebihi target rata-rata nasional (${targetProductivity.toStringAsFixed(0)} kg/ha)'; } else if (rating >= 0.7) { return 'Produktivitas baik (${productivity.toStringAsFixed(0)} kg/ha), mencapai target rata-rata nasional (${targetProductivity.toStringAsFixed(0)} kg/ha)'; } else if (rating >= 0.5) { return 'Produktivitas cukup (${productivity.toStringAsFixed(0)} kg/ha), mendekati target rata-rata nasional (${targetProductivity.toStringAsFixed(0)} kg/ha)'; } else { return 'Produktivitas di bawah target (${productivity.toStringAsFixed(0)} kg/ha), jauh dari rata-rata nasional (${targetProductivity.toStringAsFixed(0)} kg/ha)'; } } // Fungsi untuk menampilkan baris informasi keuangan Widget _buildFinancialRow(String label, double value, Color color) { return Container( padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 12), decoration: BoxDecoration( color: color.withOpacity(0.05), borderRadius: BorderRadius.circular(12), border: Border.all(color: color.withOpacity(0.2), width: 1), ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( label, style: GoogleFonts.poppins( fontWeight: FontWeight.w500, fontSize: 14, color: Colors.grey.shade800, ), ), Text( currency.format(value), style: GoogleFonts.poppins( fontWeight: FontWeight.bold, fontSize: 16, color: color, ), ), ], ), ); } // Fungsi untuk menampilkan baris rasio Widget _buildRatioRow( String label, double value, String description, { bool isPercentage = false, }) { final formattedValue = isPercentage ? '${NumberFormat("#,###.##", "id_ID").format(value)}%' : NumberFormat('#,###.##', 'id_ID').format(value); // Tentukan warna berdasarkan nilai dan jenis rasio Color badgeColor; if (label == 'R/C Ratio') { if (value >= 1.5) badgeColor = Colors.green.shade700; else if (value >= 1.0) badgeColor = Colors.orange.shade700; else badgeColor = Colors.red.shade700; } else if (label == 'B/C Ratio') { if (value >= 0.5) badgeColor = Colors.green.shade700; else if (value >= 0.0) badgeColor = Colors.orange.shade700; else badgeColor = Colors.red.shade700; } else { // ROI if (value >= 30) badgeColor = Colors.green.shade700; else if (value >= 15) badgeColor = Colors.orange.shade700; else badgeColor = Colors.red.shade700; } return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( label, style: GoogleFonts.poppins( fontWeight: FontWeight.w500, fontSize: 14, ), ), Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), decoration: BoxDecoration( color: badgeColor.withOpacity(0.1), borderRadius: BorderRadius.circular(16), border: Border.all(color: badgeColor, width: 1), ), child: Text( formattedValue, style: GoogleFonts.poppins( fontWeight: FontWeight.bold, color: badgeColor, ), ), ), ], ), if (description.isNotEmpty) Text( description, style: GoogleFonts.poppins( fontSize: 12, color: Colors.grey.shade600, ), ), ], ); } Widget _buildIndonesianFarmAnalysisTab() { // Ambil nilai-nilai dari harvestData final bepPrice = _harvestData?['bep_price'] ?? 0.0; final bepProduction = _harvestData?['bep_production'] ?? 0.0; final productionCostPerKg = _harvestData?['production_cost_per_kg'] ?? 0.0; final quantity = _harvestData?['quantity'] ?? 0.0; final pricePerKg = _harvestData?['price_per_kg'] ?? 0.0; final rcRatio = _getRcRatio(); final bcRatio = _getBcRatio(); final roi = _harvestData?['roi'] ?? 0.0; return ListView( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16), children: [ // Informasi Standar Kementerian Pertanian Card( elevation: 4, // Tingkatkan elevation color: Colors.white, margin: const EdgeInsets.only(bottom: 20), // Perbesar margin shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), // Perbesar radius ), child: Padding( padding: const EdgeInsets.all(20), // Perbesar padding child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Icon(Icons.analytics, color: AppColors.primary, size: 20), const SizedBox(width: 8), Text( 'Indikator Kelayakan Usaha Tani', style: GoogleFonts.poppins( fontSize: 14, fontWeight: FontWeight.bold, color: AppColors.primary, ), ), ], ), const SizedBox(height: 20), // RC Ratio _buildRatioItem( 'R/C Ratio', rcRatio, '', 1.0, 1.5, _getRcRatioColor(rcRatio), ), const SizedBox(height: 16), // BC Ratio _buildRatioItem( 'B/C Ratio', bcRatio, '', 0.0, 1.0, _getBcRatioColor(bcRatio), ), const SizedBox(height: 16), // ROI _buildRatioItem( 'ROI', roi.toDouble(), '%', 0.0, 15.0, _getProfitMarginColor(roi.toDouble()), ), const SizedBox(height: 16), // BEP Harga _buildBreakEvenPointItem( 'BEP Harga', bepPrice, pricePerKg, 'Rp/kg', false, ), const SizedBox(height: 16), // BEP Produksi _buildBreakEvenPointItem( 'BEP Produksi', bepProduction, quantity, 'kg', true, ), const SizedBox(height: 16), // Biaya Pokok Produksi Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: Colors.grey.shade50, borderRadius: BorderRadius.circular(12), border: Border.all(color: Colors.grey.shade300), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( 'Biaya Pokok Produksi', style: GoogleFonts.poppins( fontWeight: FontWeight.w600, fontSize: 16, color: Colors.grey.shade800, ), ), Text( currency.format(productionCostPerKg), style: GoogleFonts.poppins( fontWeight: FontWeight.bold, fontSize: 16, color: Colors.red.shade700, ), ), ], ), const SizedBox(height: 8), Text( 'Biaya yang dikeluarkan untuk menghasilkan 1 kg produk', style: GoogleFonts.poppins( fontSize: 12, color: Colors.grey.shade600, ), ), ], ), ), const SizedBox(height: 20), // Penjelasan Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: Colors.blue.shade50, borderRadius: BorderRadius.circular(12), border: Border.all(color: Colors.blue.shade200), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Icon( Icons.info_outline, color: Colors.blue.shade800, size: 16, ), const SizedBox(width: 6), Text( 'Kriteria Kelayakan Usaha Tani:', style: GoogleFonts.poppins( fontWeight: FontWeight.bold, fontSize: 12, color: Colors.blue.shade800, ), ), ], ), const SizedBox(height: 8), Text( '• R/C Ratio > 1: Usaha tani layak secara ekonomi\n' '• B/C Ratio > 0: Usaha tani menguntungkan\n' '• BEP Harga < Harga Jual: Usaha tani menguntungkan\n' '• BEP Produksi < Produksi Aktual: Usaha tani menguntungkan', style: GoogleFonts.poppins( fontSize: 12, color: Colors.blue.shade900, height: 1.5, ), ), ], ), ), ], ), ), ), // Analisis Kelayakan Usaha Tani _buildFinancialComparisonAnalysis(), ], ); } Widget _buildBreakEvenPointItem( String label, double bepValue, double actualValue, String unit, bool higherIsBetter, ) { final bool isProfit = higherIsBetter ? actualValue > bepValue : actualValue < bepValue; final Color valueColor = isProfit ? Colors.green.shade700 : Colors.red.shade700; final String comparison = higherIsBetter ? actualValue > bepValue ? 'Menguntungkan' : 'Tidak Menguntungkan' : actualValue < bepValue ? 'Menguntungkan' : 'Tidak Menguntungkan'; final double percentage = bepValue > 0 ? (actualValue / bepValue * 100).clamp(0, 200) : 0; return Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: valueColor.withOpacity(0.05), borderRadius: BorderRadius.circular(12), border: Border.all(color: valueColor.withOpacity(0.2), width: 1), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Row( children: [ Icon( isProfit ? Icons.check_circle : Icons.warning, color: valueColor, size: 16, ), const SizedBox(width: 6), Text( label, style: GoogleFonts.poppins( fontWeight: FontWeight.w600, fontSize: 12, color: Colors.grey.shade800, ), ), ], ), Container( padding: const EdgeInsets.symmetric( horizontal: 10, vertical: 4, ), decoration: BoxDecoration( color: valueColor.withOpacity(0.1), borderRadius: BorderRadius.circular(8), border: Border.all(color: valueColor, width: 1), ), child: Text( comparison, style: GoogleFonts.poppins( color: valueColor, fontSize: 11, fontWeight: FontWeight.bold, ), ), ), ], ), const SizedBox(height: 16), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( 'BEP: ${currency.format(bepValue)}$unit', style: GoogleFonts.poppins( fontSize: 9, fontWeight: FontWeight.w500, ), ), Text( 'Aktual: ${currency.format(actualValue)}$unit', style: GoogleFonts.poppins( fontSize: 8, fontWeight: FontWeight.bold, color: valueColor, ), ), ], ), const SizedBox(height: 12), // Progress bar Stack( children: [ // Background Container( height: 8, decoration: BoxDecoration( color: Colors.grey.shade200, borderRadius: BorderRadius.circular(4), ), ), // Value Container( height: 8, width: MediaQuery.of(context).size.width * 0.65 * percentage / 100, decoration: BoxDecoration( color: valueColor, borderRadius: BorderRadius.circular(4), ), ), // BEP Marker Positioned( left: MediaQuery.of(context).size.width * 0.65 * 100 / 100, child: Container( width: 2, height: 16, color: Colors.grey.shade800, ), ), ], ), const SizedBox(height: 10), Text( higherIsBetter ? 'Produksi di atas BEP menunjukkan usaha tani menguntungkan' : 'Harga jual di atas BEP menunjukkan usaha tani menguntungkan', style: GoogleFonts.poppins( fontSize: 11, color: Colors.grey.shade600, fontStyle: FontStyle.italic, ), ), ], ), ); } // Warna untuk R/C Ratio Color _getRcRatioColor(double value) { if (value >= 1.5) return Colors.green.shade600; if (value >= 1.0) return Colors.orange.shade600; return Colors.red.shade600; } // Warna untuk B/C Ratio Color _getBcRatioColor(double value) { if (value >= 1.0) return Colors.green.shade600; if (value >= 0.0) return Colors.orange.shade600; return Colors.red.shade600; } }