MIF_E31222656/lib/screens/panen/analisis_input_screen.dart

1554 lines
57 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:supabase_flutter/supabase_flutter.dart';
import 'package:tugas_akhir_supabase/screens/panen/analisis_hasil_screen.dart';
import 'dart:math' as math;
class AnalisisInputScreen extends StatefulWidget {
final String userId;
final Map<String, dynamic>? scheduleData;
const AnalisisInputScreen({
super.key,
required this.userId,
this.scheduleData,
});
@override
_AnalisisInputScreenState createState() => _AnalisisInputScreenState();
}
class _AnalisisInputScreenState extends State<AnalisisInputScreen> {
final _formKey = GlobalKey<FormState>();
bool _isLoading = false;
// Form controllers
final _areaController = TextEditingController();
final _quantityController = TextEditingController();
final _seedCostController = TextEditingController();
final _fertilizerCostController = TextEditingController();
final _pesticideCostController = TextEditingController();
final _laborCostController = TextEditingController();
final _irrigationCostController = TextEditingController();
final _pricePerKgController = TextEditingController();
// Tambahan variabel sesuai pedoman pertanian Indonesia
final _landPreparationCostController =
TextEditingController(); // Biaya persiapan lahan
final _toolsEquipmentCostController =
TextEditingController(); // Biaya alat dan peralatan
final _transportationCostController =
TextEditingController(); // Biaya transportasi
final _postHarvestCostController =
TextEditingController(); // Biaya pasca panen
final _otherCostController = TextEditingController(); // Biaya lain-lain
// Dropdown controllers
String _selectedWeatherCondition = 'Normal';
String _selectedIrrigationType = 'Irigasi Teknis';
String _selectedSoilType = 'Lempung';
String _selectedFertilizerType = 'NPK';
// Lists untuk dropdown
final List<String> _weatherConditionOptions = [
'Normal',
'Kekeringan',
'Banjir',
'Curah Hujan Tinggi',
];
final List<String> _irrigationTypeOptions = [
'Irigasi Teknis',
'Irigasi Setengah Teknis',
'Irigasi Sederhana',
'Tadah Hujan',
];
final List<String> _soilTypeOptions = [
'Lempung',
'Pasir',
'Liat',
'Lempung Berpasir',
'Liat Berpasir',
];
final List<String> _fertilizerTypeOptions = [
'NPK',
'Urea',
'TSP/SP-36',
'KCL',
'Organik',
'Campuran',
];
// Selected schedule
String? _selectedScheduleId;
Map<String, dynamic>? _selectedSchedule;
List<Map<String, dynamic>> _schedules = [];
Map<String, Map<String, dynamic>> _fieldsData = {}; // Cache untuk data lahan
bool _isManualMode = false;
@override
void initState() {
super.initState();
debugPrint('AnalisisInputScreen initState with userId: ${widget.userId}');
debugPrint('Schedule data provided: ${widget.scheduleData}');
_fetchSchedules();
// Set default values if schedule data is provided
if (widget.scheduleData != null) {
_selectedScheduleId = widget.scheduleData!['id'];
_isManualMode = false;
debugPrint(
'Setting selected schedule ID from props: $_selectedScheduleId',
);
} else {
_isManualMode = true;
debugPrint('No schedule data provided, using manual mode');
_setDefaultValues();
}
}
void _setDefaultValues() {
// For manual mode, we can set either empty fields or default values
if (_isManualMode) {
// Untuk mode manual, isi dengan nilai default yang realistis
// seperti yang diminta dosen agar esensi "otomatis" tetap terjaga
// Nilai default untuk luas lahan - 1000 m² (10 are)
_areaController.text = '1000';
// Nilai default untuk hasil panen - 500 kg (asumsi produktivitas rata-rata)
_quantityController.text = '500';
// Nilai default untuk biaya produksi langsung
_seedCostController.text = '300000';
_fertilizerCostController.text = '450000';
_pesticideCostController.text = '250000';
_irrigationCostController.text = '200000';
// Nilai default untuk biaya produksi tidak langsung
_laborCostController.text = '800000';
_landPreparationCostController.text = '300000';
_toolsEquipmentCostController.text = '200000';
_transportationCostController.text = '150000';
_postHarvestCostController.text = '100000';
_otherCostController.text = '50000';
// Default harga jual per kg (rata-rata harga gabah)
_pricePerKgController.text = '4500';
// Reset dropdown ke default
_selectedWeatherCondition = 'Normal';
_selectedIrrigationType = 'Irigasi Teknis';
_selectedSoilType = 'Lempung';
_selectedFertilizerType = 'NPK';
}
}
@override
void dispose() {
_areaController.dispose();
_quantityController.dispose();
_seedCostController.dispose();
_fertilizerCostController.dispose();
_pesticideCostController.dispose();
_laborCostController.dispose();
_irrigationCostController.dispose();
_pricePerKgController.dispose();
// Dispose controller tambahan
_landPreparationCostController.dispose();
_toolsEquipmentCostController.dispose();
_transportationCostController.dispose();
_postHarvestCostController.dispose();
_otherCostController.dispose();
super.dispose();
}
Future<void> _fetchSchedules() async {
if (widget.userId.isEmpty) return;
setState(() => _isLoading = true);
try {
debugPrint('Fetching schedules for user: ${widget.userId}');
// Fetch crop schedules with more complete data
final response = await Supabase.instance.client
.from('crop_schedules')
.select('''
id, crop_name, field_id, plot, start_date, end_date,
seed_cost, fertilizer_cost, pesticide_cost, irrigation_cost,
expected_yield, land_preparation_cost, tools_equipment_cost,
transportation_cost, post_harvest_cost, other_cost,
weather_condition, irrigation_type, soil_type, fertilizer_type,
labor_cost, area_size, status, created_at, user_id
''')
.eq('user_id', widget.userId)
.order('created_at', ascending: false);
debugPrint(
'Fetched ${response.length} schedules for user ${widget.userId}',
);
// Preload fields data for better performance
await _preloadFieldsData();
if (mounted) {
setState(() {
_schedules = List<Map<String, dynamic>>.from(response);
debugPrint('Schedules loaded: ${_schedules.length}');
// Jika ada jadwal yang diberikan melalui widget.scheduleData, pilih itu
if (widget.scheduleData != null &&
widget.scheduleData!['id'] != null) {
_selectedScheduleId = widget.scheduleData!['id'];
_isManualMode = false;
debugPrint('Selected schedule from props: $_selectedScheduleId');
}
// Jika tidak ada jadwal yang dipilih tapi ada jadwal tersedia, pilih yang pertama
else if (_schedules.isNotEmpty && _selectedScheduleId == null) {
_selectedScheduleId = _schedules.first['id'];
_isManualMode = false;
debugPrint('Selected first schedule: $_selectedScheduleId');
} else if (_isManualMode) {
_setDefaultValues();
}
});
// Call update methods outside setState to avoid issues
if (widget.scheduleData != null && widget.scheduleData!['id'] != null) {
await _updateFormFieldsFromSelectedSchedule();
} else if (_schedules.isNotEmpty &&
_selectedScheduleId != null &&
!_isManualMode) {
await _updateFormFieldsFromSelectedSchedule();
}
}
} catch (e) {
debugPrint('Error fetching schedules: $e');
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Error mengambil data jadwal: ${e.toString()}'),
backgroundColor: Colors.red,
),
);
}
} finally {
if (mounted) {
setState(() => _isLoading = false);
}
}
}
// Preload fields data untuk mempercepat akses
Future<void> _preloadFieldsData() async {
try {
final fieldsResponse = await Supabase.instance.client
.from('fields')
.select('id, name, area_size, area_unit, region, location')
.eq('user_id', widget.userId);
_fieldsData = {};
for (var field in fieldsResponse) {
_fieldsData[field['id']] = Map<String, dynamic>.from(field);
}
debugPrint('Preloaded ${_fieldsData.length} fields data');
} catch (e) {
debugPrint('Error preloading fields data: $e');
}
}
Future<void> _updateFormFieldsFromSelectedSchedule() async {
if (_isManualMode || _selectedScheduleId == null || _schedules.isEmpty) {
_setDefaultValues();
return;
}
try {
// Find the selected schedule from the schedules list
_selectedSchedule = _schedules.firstWhere(
(schedule) => schedule['id'] == _selectedScheduleId,
orElse: () => {},
);
if (_selectedSchedule == null || _selectedSchedule!.isEmpty) {
debugPrint('Selected schedule not found in schedules list');
_setDefaultValues();
return;
}
debugPrint(
'Updating form fields from selected schedule: $_selectedSchedule',
);
// Update form fields with data from the selected schedule
_seedCostController.text =
(_selectedSchedule!['seed_cost'] ?? 0).toString();
_fertilizerCostController.text =
(_selectedSchedule!['fertilizer_cost'] ?? 0).toString();
_pesticideCostController.text =
(_selectedSchedule!['pesticide_cost'] ?? 0).toString();
_irrigationCostController.text =
(_selectedSchedule!['irrigation_cost'] ?? 0).toString();
// Update form fields untuk kolom baru
_landPreparationCostController.text =
(_selectedSchedule!['land_preparation_cost'] ?? 0).toString();
_toolsEquipmentCostController.text =
(_selectedSchedule!['tools_equipment_cost'] ?? 0).toString();
_transportationCostController.text =
(_selectedSchedule!['transportation_cost'] ?? 0).toString();
_postHarvestCostController.text =
(_selectedSchedule!['post_harvest_cost'] ?? 0).toString();
_otherCostController.text =
(_selectedSchedule!['other_cost'] ?? 0).toString();
// Update dropdown values jika tersedia
_selectedWeatherCondition =
_selectedSchedule!['weather_condition'] ?? 'Normal';
_selectedIrrigationType =
_selectedSchedule!['irrigation_type'] ?? 'Irigasi Teknis';
_selectedSoilType = _selectedSchedule!['soil_type'] ?? 'Lempung';
_selectedFertilizerType = _selectedSchedule!['fertilizer_type'] ?? 'NPK';
// Mengisi semua field secara otomatis untuk mempertahankan esensi "otomatis"
// Mengambil data plot dari jadwal
String plotName = _selectedSchedule!['plot']?.toString() ?? '';
double plotArea = 0;
// Cek apakah ada area_size yang sudah disimpan di jadwal
if (_selectedSchedule!.containsKey('area_size') &&
_selectedSchedule!['area_size'] != null) {
// Gunakan area yang sudah ada di jadwal
try {
plotArea = double.parse(_selectedSchedule!['area_size'].toString());
debugPrint('Using area_size directly from schedule: $plotArea');
} catch (e) {
debugPrint('Error parsing area_size from schedule: $e');
}
}
// Jika tidak ada area di jadwal, coba ambil dari field_id menggunakan cache
else if (_selectedSchedule!.containsKey('field_id') &&
_selectedSchedule!['field_id'] != null) {
final fieldId = _selectedSchedule!['field_id'];
// Cek apakah data field sudah ada di cache
if (_fieldsData.containsKey(fieldId)) {
final fieldData = _fieldsData[fieldId]!;
if (fieldData.containsKey('area_size') &&
fieldData['area_size'] != null) {
try {
plotArea = double.parse(fieldData['area_size'].toString());
debugPrint('Retrieved area from fields cache: $plotArea');
} catch (e) {
debugPrint('Error parsing area_size from fields cache: $e');
}
}
} else {
// Jika tidak ada di cache, ambil dari database
try {
final fieldResponse =
await Supabase.instance.client
.from('fields')
.select('area_size, area_unit')
.eq('id', fieldId)
.single();
if (fieldResponse.containsKey('area_size') &&
fieldResponse['area_size'] != null) {
plotArea = double.parse(fieldResponse['area_size'].toString());
debugPrint('Retrieved area_size from fields table: $plotArea');
}
} catch (e) {
debugPrint('Error fetching field area_size: $e');
}
}
}
// Mengisi luas lahan dari data yang ditemukan atau default
_areaController.text = plotArea > 0 ? plotArea.toString() : '1000';
// Mengisi jumlah produksi dari expected_yield atau estimasi
double expectedYield = 0;
if (_selectedSchedule!.containsKey('expected_yield') &&
_selectedSchedule!['expected_yield'] != null) {
try {
expectedYield = double.parse(
_selectedSchedule!['expected_yield'].toString(),
);
} catch (e) {
debugPrint('Error parsing expected_yield: $e');
}
}
if (expectedYield > 0) {
_quantityController.text = expectedYield.toString();
} else {
// Estimasi berdasarkan jenis tanaman dan luas
String cropName =
_selectedSchedule!['crop_name']?.toString().toLowerCase() ?? '';
double area = double.tryParse(_areaController.text) ?? 1000;
double estimatedYield = 0;
// Estimasi hasil panen berdasarkan jenis tanaman (kg/ha)
if (cropName.contains('padi')) {
estimatedYield = area * 5.5 / 10000; // Rata-rata 5.5 ton/ha
} else if (cropName.contains('jagung')) {
estimatedYield = area * 5.2 / 10000; // Rata-rata 5.2 ton/ha
} else if (cropName.contains('kedelai')) {
estimatedYield = area * 1.5 / 10000; // Rata-rata 1.5 ton/ha
} else if (cropName.contains('bawang')) {
estimatedYield = area * 9.5 / 10000; // Rata-rata 9.5 ton/ha
} else {
estimatedYield = area * 4.0 / 10000; // Default 4 ton/ha
}
_quantityController.text = estimatedYield.toStringAsFixed(0);
}
// Mengisi biaya tenaga kerja dari data atau estimasi
double laborCost = 0;
if (_selectedSchedule!.containsKey('labor_cost') &&
_selectedSchedule!['labor_cost'] != null) {
try {
laborCost = double.parse(_selectedSchedule!['labor_cost'].toString());
} catch (e) {
debugPrint('Error parsing labor_cost: $e');
}
}
_laborCostController.text =
laborCost > 0 ? laborCost.toString() : '300000';
// Mengisi harga jual per kg berdasarkan jenis tanaman
String cropName =
_selectedSchedule!['crop_name']?.toString().toLowerCase() ?? '';
double pricePerKg = 0;
// Harga pasar rata-rata (Rp/kg) berdasarkan jenis tanaman
if (cropName.contains('padi')) {
pricePerKg = 4500; // Harga GKP per kg
} else if (cropName.contains('jagung')) {
pricePerKg = 4200; // Harga jagung pipil per kg
} else if (cropName.contains('kedelai')) {
pricePerKg = 9000; // Harga kedelai per kg
} else if (cropName.contains('bawang')) {
pricePerKg = 25000; // Harga bawang merah per kg
} else if (cropName.contains('kopi')) {
pricePerKg = 35000; // Harga kopi per kg
} else {
pricePerKg = 5000; // Default harga per kg
}
_pricePerKgController.text = pricePerKg.toString();
debugPrint('Auto-filled all fields for schedule: $_selectedScheduleId');
debugPrint('Area: ${_areaController.text}');
debugPrint('Quantity: ${_quantityController.text} kg');
debugPrint('Price per kg: ${_pricePerKgController.text}');
debugPrint('Seed cost: ${_seedCostController.text}');
debugPrint('Fertilizer cost: ${_fertilizerCostController.text}');
debugPrint('Pesticide cost: ${_pesticideCostController.text}');
debugPrint('Irrigation cost: ${_irrigationCostController.text}');
debugPrint('Labor cost: ${_laborCostController.text}');
} catch (e) {
debugPrint('Error updating form fields from selected schedule: $e');
_setDefaultValues();
}
}
Future<void> _analyzeHarvest() async {
if (!_formKey.currentState!.validate()) return;
setState(() => _isLoading = true);
try {
// Parse input values
final double area = double.tryParse(_areaController.text) ?? 0;
final double quantity = double.tryParse(_quantityController.text) ?? 0;
final double seedCost = double.tryParse(_seedCostController.text) ?? 0;
final double fertilizerCost =
double.tryParse(_fertilizerCostController.text) ?? 0;
final double pesticideCost =
double.tryParse(_pesticideCostController.text) ?? 0;
final double laborCost = double.tryParse(_laborCostController.text) ?? 0;
final double irrigationCost =
double.tryParse(_irrigationCostController.text) ?? 0;
final double pricePerKg =
double.tryParse(_pricePerKgController.text) ?? 0;
// Parse biaya tambahan
final double landPreparationCost =
double.tryParse(_landPreparationCostController.text) ?? 0;
final double toolsEquipmentCost =
double.tryParse(_toolsEquipmentCostController.text) ?? 0;
final double transportationCost =
double.tryParse(_transportationCostController.text) ?? 0;
final double postHarvestCost =
double.tryParse(_postHarvestCostController.text) ?? 0;
final double otherCost = double.tryParse(_otherCostController.text) ?? 0;
// Berikan waktu untuk UI update
await Future.delayed(const Duration(milliseconds: 100));
// Calculate productivity (kilogram/ha)
final double productivityPerHa = area > 0 ? (quantity / area) * 10000 : 0;
// Calculate total cost (termasuk biaya tambahan)
final double directCost =
seedCost +
fertilizerCost +
pesticideCost +
irrigationCost; // Biaya langsung
final double indirectCost =
laborCost +
landPreparationCost +
toolsEquipmentCost +
transportationCost +
postHarvestCost +
otherCost; // Biaya tidak langsung
final double totalCost = directCost + indirectCost;
// Calculate income (quantity in kilogram)
final double income = quantity * pricePerKg;
// Calculate profit
final double profit = income - totalCost;
// Calculate profit margin
final double profitMargin = income > 0 ? (profit / income) * 100 : 0;
// Calculate R/C ratio (Revenue Cost Ratio) - Standar analisis usaha tani Indonesia
final double rcRatio = totalCost > 0 ? income / totalCost : 0;
// Calculate B/C ratio (Benefit Cost Ratio) - Standar analisis usaha tani Indonesia
final double bcRatio = totalCost > 0 ? profit / totalCost : 0;
// Calculate BEP Price (Break Even Point harga) - Standar analisis usaha tani Indonesia
final double bepPrice = quantity > 0 ? totalCost / quantity : 0;
// Calculate BEP Production (Break Even Point produksi) - Standar analisis usaha tani Indonesia
final double bepProduction = pricePerKg > 0 ? totalCost / pricePerKg : 0;
// Calculate ROI (Return on Investment)
final double roi = totalCost > 0 ? (profit / totalCost) * 100 : 0;
// Calculate production cost per kg - Biaya pokok produksi per kg
final double productionCostPerKg =
quantity > 0 ? totalCost / quantity : 0;
// Determine status based on productivity and profit margin
// Menggunakan standar Kementan dan Kemenristekdikti untuk usahatani
String status;
if (rcRatio >= 2.0) {
status = 'Sangat Layak';
} else if (rcRatio >= 1.5) {
status = 'Layak';
} else if (rcRatio >= 1.0) {
status = 'Cukup Layak';
} else {
status = 'Tidak Layak';
}
// Prepare harvest data
final Map<String, dynamic> harvestData = {
'user_id': widget.userId,
'schedule_id': _selectedScheduleId,
'area': area,
'quantity': quantity,
'productivity': productivityPerHa,
'seed_cost': seedCost,
'fertilizer_cost': fertilizerCost,
'pesticide_cost': pesticideCost,
'labor_cost': laborCost,
'irrigation_cost': irrigationCost,
'land_preparation_cost': landPreparationCost,
'tools_equipment_cost': toolsEquipmentCost,
'transportation_cost': transportationCost,
'post_harvest_cost': postHarvestCost,
'other_cost': otherCost,
'direct_cost': directCost,
'indirect_cost': indirectCost,
'cost': totalCost,
'total_cost': totalCost,
'price_per_kg': pricePerKg,
'income': income,
'profit': profit,
'profit_margin': profitMargin,
'rc_ratio': rcRatio,
'bc_ratio': bcRatio,
'bep_price': bepPrice,
'bep_production': bepProduction,
'roi': roi,
'production_cost_per_kg': productionCostPerKg,
'status': status,
'weather_condition': _selectedWeatherCondition,
'irrigation_type': _selectedIrrigationType,
'soil_type': _selectedSoilType,
'fertilizer_type': _selectedFertilizerType,
'harvest_date': DateTime.now().toIso8601String(),
};
// Tambahkan informasi jadwal tanam jika ada
if (!_isManualMode && _selectedSchedule != null) {
// Tambahkan informasi penting dari jadwal tanam
harvestData['crop_name'] = _selectedSchedule!['crop_name'];
harvestData['field_id'] = _selectedSchedule!['field_id'];
harvestData['plot'] = _selectedSchedule!['plot'];
harvestData['start_date'] = _selectedSchedule!['start_date'];
harvestData['end_date'] = _selectedSchedule!['end_date'];
// Tambahkan informasi lahan jika tersedia
if (_selectedSchedule!['field_id'] != null &&
_fieldsData.containsKey(_selectedSchedule!['field_id'])) {
final fieldData = _fieldsData[_selectedSchedule!['field_id']]!;
harvestData['field_name'] = fieldData['name'];
harvestData['field_location'] = fieldData['location'];
harvestData['field_region'] = fieldData['region'];
}
}
// Simpan hasil analisis ke database jika pengguna tidak dalam mode manual
if (!_isManualMode && _selectedScheduleId != null) {
try {
// Simpan hasil analisis ke tabel harvest_analysis
final analysisResponse =
await Supabase.instance.client
.from('harvest_analysis')
.insert({
'user_id': widget.userId,
'schedule_id': _selectedScheduleId,
'area': area,
'quantity': quantity,
'productivity': productivityPerHa,
'total_cost': totalCost,
'income': income,
'profit': profit,
'profit_margin': profitMargin,
'rc_ratio': rcRatio,
'bc_ratio': bcRatio,
'status': status,
'created_at': DateTime.now().toIso8601String(),
})
.select()
.single();
debugPrint(
'Hasil analisis berhasil disimpan: ${analysisResponse['id']}',
);
harvestData['analysis_id'] = analysisResponse['id'];
} catch (e) {
debugPrint('Error menyimpan hasil analisis: $e');
}
}
// Berikan waktu untuk UI update sebelum navigasi
await Future.delayed(const Duration(milliseconds: 100));
// Navigate to result screen
if (!mounted) return;
debugPrint('=== HARVEST DATA YANG DIKIRIM KE HASIL ANALISIS ===');
harvestData.forEach((k, v) => debugPrint('$k: $v'));
Navigator.push(
context,
MaterialPageRoute(
builder:
(context) => HarvestResultScreen(
userId: widget.userId,
harvestData: harvestData,
scheduleData: _selectedSchedule,
),
),
);
} catch (e) {
debugPrint('Error analyzing harvest: $e');
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
'Error: ${e.toString().substring(0, math.min(e.toString().length, 100))}',
),
backgroundColor: Colors.red,
),
);
} finally {
if (mounted) {
setState(() => _isLoading = false);
}
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Analisis Hasil Panen'),
backgroundColor: const Color(0xFF056839),
foregroundColor: Colors.white,
actions: [
IconButton(
icon: const Icon(Icons.refresh),
tooltip: 'Refresh Data',
onPressed: () {
setState(() => _isLoading = true);
_fetchSchedules().then((_) {
setState(() => _isLoading = false);
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Data jadwal berhasil diperbarui'),
),
);
});
},
),
],
),
body:
_isLoading
? const Center(child: CircularProgressIndicator())
: _buildForm(),
);
}
Widget _buildForm() {
return Form(
key: _formKey,
child: ListView(
padding: const EdgeInsets.all(16.0),
children: [
// Data Tanaman section
_buildSectionTitle('Data Tanaman'),
const SizedBox(height: 16),
// Jadwal Tanam dropdown
_buildScheduleDropdown(),
const SizedBox(height: 16),
// Luas Lahan field
_buildTextField(
controller: _areaController,
label: 'Luas Lahan (m²)',
icon: Icons.landscape,
keyboardType: TextInputType.number,
validator: (value) {
if (value == null || value.isEmpty) {
return 'Masukkan luas lahan';
}
return null;
},
),
const SizedBox(height: 16),
// Total Panen field
_buildTextField(
controller: _quantityController,
label: 'Total Panen (kilogram)',
icon: Icons.shopping_basket,
keyboardType: TextInputType.number,
validator: (value) {
if (value == null || value.isEmpty) {
return 'Masukkan total panen';
}
return null;
},
),
const SizedBox(height: 24),
// Informasi Kondisi Tanam
_buildSectionTitle('Informasi Kondisi Tanam'),
const SizedBox(height: 16),
// Dropdown Kondisi Cuaca
_buildDropdown(
label: 'Kondisi Cuaca',
icon: Icons.wb_sunny,
value: _selectedWeatherCondition,
items:
_weatherConditionOptions.map((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
onChanged: (String? newValue) {
if (newValue != null) {
setState(() {
_selectedWeatherCondition = newValue;
});
}
},
),
const SizedBox(height: 16),
// Dropdown Jenis Irigasi
_buildDropdown(
label: 'Jenis Irigasi',
icon: Icons.water,
value: _selectedIrrigationType,
items:
_irrigationTypeOptions.map((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
onChanged: (String? newValue) {
if (newValue != null) {
setState(() {
_selectedIrrigationType = newValue;
});
}
},
),
const SizedBox(height: 16),
// Dropdown Jenis Tanah
_buildDropdown(
label: 'Jenis Tanah',
icon: Icons.grass,
value: _selectedSoilType,
items:
_soilTypeOptions.map((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
onChanged: (String? newValue) {
if (newValue != null) {
setState(() {
_selectedSoilType = newValue;
});
}
},
),
const SizedBox(height: 16),
// Dropdown Jenis Pupuk Utama
_buildDropdown(
label: 'Jenis Pupuk Utama',
icon: Icons.eco,
value: _selectedFertilizerType,
items:
_fertilizerTypeOptions.map((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
onChanged: (String? newValue) {
if (newValue != null) {
setState(() {
_selectedFertilizerType = newValue;
});
}
},
),
const SizedBox(height: 24),
// Biaya Produksi section
_buildSectionTitle('Biaya Produksi Langsung'),
const SizedBox(height: 16),
// Biaya Bibit field
_buildTextField(
controller: _seedCostController,
label: 'Biaya Bibit (Rp)',
icon: Icons.spa,
keyboardType: TextInputType.number,
prefixText: 'Rp ',
validator: (value) {
if (value == null || value.isEmpty) {
return 'Masukkan biaya bibit';
}
return null;
},
),
const SizedBox(height: 16),
// Biaya Pupuk field
_buildTextField(
controller: _fertilizerCostController,
label: 'Biaya Pupuk (Rp)',
icon: Icons.eco,
keyboardType: TextInputType.number,
prefixText: 'Rp ',
validator: (value) {
if (value == null || value.isEmpty) {
return 'Masukkan biaya pupuk';
}
return null;
},
),
const SizedBox(height: 16),
// Biaya Pestisida field
_buildTextField(
controller: _pesticideCostController,
label: 'Biaya Pestisida (Rp)',
icon: Icons.bug_report,
keyboardType: TextInputType.number,
prefixText: 'Rp ',
validator: (value) {
if (value == null || value.isEmpty) {
return 'Masukkan biaya pestisida';
}
return null;
},
),
const SizedBox(height: 16),
// Biaya Irigasi field
_buildTextField(
controller: _irrigationCostController,
label: 'Biaya Irigasi (Rp)',
icon: Icons.water_drop,
keyboardType: TextInputType.number,
prefixText: 'Rp ',
validator: (value) {
if (value == null || value.isEmpty) {
return 'Masukkan biaya irigasi';
}
return null;
},
),
const SizedBox(height: 24),
// Biaya Tidak Langsung section
_buildSectionTitle('Biaya Produksi Tidak Langsung'),
const SizedBox(height: 16),
// Biaya Tenaga Kerja field
_buildTextField(
controller: _laborCostController,
label: 'Biaya Tenaga Kerja (Rp)',
icon: Icons.people,
keyboardType: TextInputType.number,
prefixText: 'Rp ',
validator: (value) {
if (value == null || value.isEmpty) {
return 'Masukkan biaya tenaga kerja';
}
return null;
},
),
const SizedBox(height: 16),
// Biaya Persiapan Lahan
_buildTextField(
controller: _landPreparationCostController,
label: 'Biaya Persiapan Lahan (Rp)',
icon: Icons.agriculture,
keyboardType: TextInputType.number,
prefixText: 'Rp ',
),
const SizedBox(height: 16),
// Biaya Alat dan Peralatan
_buildTextField(
controller: _toolsEquipmentCostController,
label: 'Biaya Alat & Peralatan (Rp)',
icon: Icons.build,
keyboardType: TextInputType.number,
prefixText: 'Rp ',
),
const SizedBox(height: 16),
// Biaya Transportasi
_buildTextField(
controller: _transportationCostController,
label: 'Biaya Transportasi (Rp)',
icon: Icons.local_shipping,
keyboardType: TextInputType.number,
prefixText: 'Rp ',
),
const SizedBox(height: 16),
// Biaya Pasca Panen
_buildTextField(
controller: _postHarvestCostController,
label: 'Biaya Pasca Panen (Rp)',
icon: Icons.inventory_2,
keyboardType: TextInputType.number,
prefixText: 'Rp ',
),
const SizedBox(height: 16),
// Biaya Lain-lain
_buildTextField(
controller: _otherCostController,
label: 'Biaya Lain-lain (Rp)',
icon: Icons.more_horiz,
keyboardType: TextInputType.number,
prefixText: 'Rp ',
),
const SizedBox(height: 24),
// Harga Jual section
_buildSectionTitle('Harga Jual'),
const SizedBox(height: 16),
// Harga Jual per Kg field
_buildTextField(
controller: _pricePerKgController,
label: 'Harga Jual per Kg (Rp)',
icon: Icons.attach_money,
keyboardType: TextInputType.number,
prefixText: 'Rp ',
validator: (value) {
if (value == null || value.isEmpty) {
return 'Masukkan harga jual per kg';
}
return null;
},
),
const SizedBox(height: 32),
// Analyze button
SizedBox(
height: 50,
child: ElevatedButton(
onPressed: _isLoading ? null : _analyzeHarvest,
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF056839),
foregroundColor: Colors.white,
textStyle: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
child:
_isLoading
? const CircularProgressIndicator(color: Colors.white)
: const Text('ANALISIS HASIL PANEN'),
),
),
const SizedBox(height: 24),
],
),
);
}
Widget _buildSectionTitle(String title) {
return Text(
title,
style: GoogleFonts.poppins(
fontSize: 20,
fontWeight: FontWeight.w600,
color: const Color(0xFF056839),
),
);
}
Widget _buildTextField({
required TextEditingController controller,
required String label,
required IconData icon,
TextInputType keyboardType = TextInputType.text,
String? Function(String?)? validator,
String? prefixText,
}) {
return TextFormField(
controller: controller,
keyboardType: keyboardType,
validator: validator,
decoration: InputDecoration(
labelText: label,
prefixIcon: Icon(icon, color: const Color(0xFF056839)),
prefixText: prefixText,
border: OutlineInputBorder(borderRadius: BorderRadius.circular(12)),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: const BorderSide(color: Color(0xFF056839), width: 2),
),
),
inputFormatters:
keyboardType == TextInputType.number
? [FilteringTextInputFormatter.digitsOnly]
: null,
);
}
Widget _buildScheduleDropdown() {
return InkWell(
onTap: () => _showScheduleSelector(),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16),
decoration: BoxDecoration(
border: Border.all(color: Colors.grey.shade400),
borderRadius: BorderRadius.circular(12),
),
child: Row(
children: [
Icon(Icons.calendar_today, color: const Color(0xFF056839)),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Pilih Jadwal Tanam',
style: TextStyle(fontSize: 12, color: Colors.grey[600]),
),
const SizedBox(height: 4),
Text(
_getSelectedScheduleText(),
style: const TextStyle(fontSize: 16),
),
],
),
),
const Icon(Icons.arrow_drop_down),
],
),
),
);
}
String _getSelectedScheduleText() {
if (_isManualMode) {
return 'Manual';
}
if (_selectedScheduleId != null) {
try {
final selectedSchedule = _schedules.firstWhere(
(s) => s['id'] == _selectedScheduleId,
orElse: () => {'crop_name': 'Jadwal tidak ditemukan'},
);
return selectedSchedule['crop_name'] ?? 'Jadwal tidak ditemukan';
} catch (e) {
debugPrint('Error finding selected schedule: $e');
return 'Jadwal tidak ditemukan';
}
}
return 'Pilih Jadwal Tanam';
}
void _showScheduleSelector() {
try {
showModalBottomSheet(
context: context,
isScrollControlled: true,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(16)),
),
builder: (context) {
return Container(
height: MediaQuery.of(context).size.height * 0.7,
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'Pilih Jadwal Tanam',
style: GoogleFonts.poppins(
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
IconButton(
icon: const Icon(Icons.close),
onPressed: () => Navigator.pop(context),
),
],
),
const SizedBox(height: 8),
Text(
'Jadwal yang tersedia: ${_schedules.length}',
style: GoogleFonts.poppins(
fontSize: 14,
color: Colors.grey[600],
),
),
const SizedBox(height: 16),
Expanded(
child:
_schedules.isEmpty
? Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.calendar_today,
size: 64,
color: Colors.grey[400],
),
const SizedBox(height: 16),
Text(
'Belum ada jadwal tanam',
style: GoogleFonts.poppins(
fontSize: 16,
fontWeight: FontWeight.w500,
color: Colors.grey[600],
),
),
const SizedBox(height: 8),
Text(
'Gunakan mode manual untuk saat ini',
style: GoogleFonts.poppins(
fontSize: 14,
color: Colors.grey[500],
),
),
],
),
)
: ListView.builder(
itemCount:
_schedules.length + 1, // +1 for Manual option
itemBuilder: (context, index) {
if (index == 0) {
// Manual option
return Card(
elevation: _isManualMode ? 2 : 0,
color:
_isManualMode
? const Color(0xFFE8F5E9)
: null,
margin: const EdgeInsets.only(bottom: 8),
child: ListTile(
leading: Container(
width: 40,
height: 40,
decoration: BoxDecoration(
color: const Color(
0xFF056839,
).withOpacity(0.1),
borderRadius: BorderRadius.circular(8),
),
child: const Icon(
Icons.add,
color: Color(0xFF056839),
),
),
title: const Text('Input Manual'),
subtitle: const Text(
'Masukkan data secara manual',
),
trailing:
_isManualMode
? const Icon(
Icons.check_circle,
color: Color(0xFF056839),
)
: null,
onTap: () {
try {
setState(() {
_selectedScheduleId = null;
_selectedSchedule = null;
_isManualMode = true;
});
Navigator.pop(context);
// Use setState again to ensure UI updates properly
setState(() {
_setDefaultValues();
});
ScaffoldMessenger.of(
context,
).showSnackBar(
const SnackBar(
content: Text(
'Mode manual dipilih. Semua field diisi dengan nilai default.',
),
duration: Duration(seconds: 2),
),
);
debugPrint('Selected manual mode');
} catch (e) {
debugPrint(
'Error selecting manual mode: $e',
);
}
},
),
);
} else {
// Schedule options
final schedule = _schedules[index - 1];
final isSelected =
!_isManualMode &&
_selectedScheduleId == schedule['id'];
// Format dates if available
String dateInfo = '';
if (schedule['start_date'] != null &&
schedule['end_date'] != null) {
try {
final startDate = DateTime.parse(
schedule['start_date'],
);
final endDate = DateTime.parse(
schedule['end_date'],
);
dateInfo =
'${startDate.day}/${startDate.month}/${startDate.year} - ${endDate.day}/${endDate.month}/${endDate.year}';
} catch (e) {
dateInfo = 'Tanggal tidak valid';
debugPrint('Error parsing dates: $e');
}
}
// Ambil informasi lahan jika tersedia
String fieldInfo = '';
if (schedule['field_id'] != null &&
_fieldsData.containsKey(
schedule['field_id'],
)) {
final fieldData =
_fieldsData[schedule['field_id']]!;
fieldInfo = fieldData['name'] ?? '';
}
// Ambil informasi luas lahan
String areaInfo = '';
if (schedule['area_size'] != null &&
schedule['area_size'] > 0) {
areaInfo = '${schedule['area_size']}';
} else if (schedule['field_id'] != null &&
_fieldsData.containsKey(
schedule['field_id'],
) &&
_fieldsData[schedule['field_id']]!['area_size'] !=
null) {
areaInfo =
'${_fieldsData[schedule['field_id']]!['area_size']}';
}
return Card(
elevation: isSelected ? 2 : 0,
color:
isSelected
? const Color(0xFFE8F5E9)
: null,
margin: const EdgeInsets.only(bottom: 8),
child: ListTile(
leading: Container(
width: 40,
height: 40,
decoration: BoxDecoration(
color: const Color(
0xFF056839,
).withOpacity(0.1),
borderRadius: BorderRadius.circular(8),
),
child: const Icon(
Icons.eco,
color: Color(0xFF056839),
),
),
title: Text(
schedule['crop_name'] ?? 'Tanaman',
),
subtitle: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
if (fieldInfo.isNotEmpty)
Text(
'Lahan: $fieldInfo',
style: const TextStyle(
fontSize: 12,
),
),
Text(
'Plot: ${schedule['plot'] ?? '-'}${areaInfo.isNotEmpty ? '$areaInfo' : ''}',
style: const TextStyle(fontSize: 12),
),
if (dateInfo.isNotEmpty)
Text(
'Periode: $dateInfo',
style: const TextStyle(
fontSize: 12,
),
),
],
),
isThreeLine: true,
trailing:
isSelected
? const Icon(
Icons.check_circle,
color: Color(0xFF056839),
)
: null,
onTap: () async {
try {
setState(() {
_selectedScheduleId = schedule['id'];
_selectedSchedule = schedule;
_isManualMode = false;
});
Navigator.pop(context);
// Show loading indicator
ScaffoldMessenger.of(
context,
).showSnackBar(
const SnackBar(
content: Text(
'Mengisi data otomatis...',
),
duration: Duration(seconds: 1),
),
);
// Update fields with await since it's async now
await _updateFormFieldsFromSelectedSchedule();
// Show success message after fields are updated
if (mounted) {
ScaffoldMessenger.of(
context,
).showSnackBar(
const SnackBar(
content: Text(
'Data jadwal berhasil diisi otomatis',
),
duration: Duration(seconds: 2),
),
);
}
debugPrint(
'Selected schedule: ${schedule['id']} - ${schedule['crop_name']}',
);
} catch (e) {
debugPrint(
'Error selecting schedule: $e',
);
if (mounted) {
ScaffoldMessenger.of(
context,
).showSnackBar(
SnackBar(
content: Text(
'Error: ${e.toString()}',
),
backgroundColor: Colors.red,
),
);
}
}
},
),
);
}
},
),
),
],
),
);
},
);
} catch (e) {
debugPrint('Error showing schedule selector: $e');
// Fallback to simple dialog if bottom sheet fails
showDialog(
context: context,
builder:
(context) => AlertDialog(
title: const Text('Pilih Jadwal Tanam'),
content: const Text(
'Terjadi kesalahan saat menampilkan jadwal. Silakan coba lagi nanti.',
),
actions: [
TextButton(
onPressed: () {
Navigator.pop(context);
setState(() {
_isManualMode = true;
_selectedScheduleId = null;
_selectedSchedule = null;
_setDefaultValues();
});
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text(
'Mode manual dipilih. Semua field diisi dengan nilai default.',
),
duration: Duration(seconds: 2),
),
);
},
child: const Text('Gunakan Mode Manual'),
),
],
),
);
}
}
// Dropdown builder helper
Widget _buildDropdown({
required String label,
required IconData icon,
required String value,
required List<DropdownMenuItem<String>> items,
required void Function(String?) onChanged,
}) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
decoration: BoxDecoration(
border: Border.all(color: Colors.grey.shade400),
borderRadius: BorderRadius.circular(12),
),
child: Row(
children: [
Icon(icon, color: const Color(0xFF056839)),
const SizedBox(width: 12),
Expanded(
child: DropdownButtonHideUnderline(
child: DropdownButton<String>(
value: value,
icon: const Icon(Icons.arrow_drop_down),
isExpanded: true,
style: const TextStyle(color: Colors.black, fontSize: 16),
onChanged: onChanged,
items: items,
hint: Text(label),
),
),
),
],
),
);
}
}