MIF_E31222656/lib/screens/panen/analisis_hasil_screen.dart

2949 lines
102 KiB
Dart

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<String, dynamic> harvestData;
final Map<String, dynamic>? scheduleData;
const HarvestResultScreen({
super.key,
required this.userId,
required this.harvestData,
this.scheduleData,
});
@override
State<HarvestResultScreen> createState() => _HarvestResultScreenState();
}
class _HarvestResultScreenState extends State<HarvestResultScreen> {
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<String, dynamic>? get _harvestData => widget.harvestData;
Map<String, dynamic>? 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<Color>(
_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<PieChartSectionData> _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<void> _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<Map<String, dynamic>>? 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<Map<String, dynamic>>.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<Uint8List?> _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<void> _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<void> _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<String, double> 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;
}
}