1554 lines
57 KiB
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 m²');
|
|
} 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 m²');
|
|
} 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 m²');
|
|
}
|
|
} 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} m²');
|
|
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']} m²';
|
|
} 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']} m²';
|
|
}
|
|
|
|
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),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|