2949 lines
102 KiB
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;
|
|
}
|
|
}
|