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? scheduleData; const AnalisisInputScreen({ super.key, required this.userId, this.scheduleData, }); @override _AnalisisInputScreenState createState() => _AnalisisInputScreenState(); } class _AnalisisInputScreenState extends State { final _formKey = GlobalKey(); 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(); // Selected schedule String? _selectedScheduleId; Map? _selectedSchedule; List> _schedules = []; 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) { // Clear all fields first _areaController.text = ''; _quantityController.text = ''; // Either set defaults or clear fields based on whether we want empty forms for manual // For true manual input with empty forms, uncomment the lines below: _seedCostController.text = ''; _fertilizerCostController.text = ''; _pesticideCostController.text = ''; _laborCostController.text = ''; _irrigationCostController.text = ''; _pricePerKgController.text = ''; // Or use default values if preferred (comment these out if using empty fields above) // _seedCostController.text = '30000'; // _fertilizerCostController.text = '60000'; // _pesticideCostController.text = '50000'; // _laborCostController.text = '300000'; // _irrigationCostController.text = '40000'; // _pricePerKgController.text = '4550'; } } @override void dispose() { _areaController.dispose(); _quantityController.dispose(); _seedCostController.dispose(); _fertilizerCostController.dispose(); _pesticideCostController.dispose(); _laborCostController.dispose(); _irrigationCostController.dispose(); _pricePerKgController.dispose(); super.dispose(); } Future _fetchSchedules() async { if (widget.userId.isEmpty) return; try { debugPrint('Fetching schedules for user: ${widget.userId}'); 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', ) .eq('user_id', widget.userId) .order('created_at', ascending: false); debugPrint('Fetched schedules response: $response'); if (mounted) { setState(() { _schedules = List>.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'); _updateFormFieldsFromSelectedSchedule(); } // 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'); _updateFormFieldsFromSelectedSchedule(); } else if (_isManualMode) { _setDefaultValues(); } }); } } catch (e) { debugPrint('Error fetching schedules: $e'); } } void _updateFormFieldsFromSelectedSchedule() { 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(); // Clear fields that should be filled by the user _areaController.text = ''; _quantityController.text = ''; _laborCostController.text = '300000'; // Default value _pricePerKgController.text = '4550'; // Default value } catch (e) { debugPrint('Error updating form fields from selected schedule: $e'); _setDefaultValues(); } } Future _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; // Gunakan compute untuk memindahkan kalkulasi berat ke isolate terpisah // Ini mencegah UI freeze dan main isolate paused await Future.delayed( const Duration(milliseconds: 100), ); // Berikan waktu untuk UI update // Calculate productivity (kilogram/ha) final double productivityPerHa = area > 0 ? (quantity / area) * 10000 : 0; // Calculate total cost final double totalCost = seedCost + fertilizerCost + pesticideCost + laborCost + irrigationCost; // 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 final double rcRatio = totalCost > 0 ? income / totalCost : 0; // Calculate B/C ratio final double bcRatio = totalCost > 0 ? profit / totalCost : 0; // Calculate ROI final double roi = totalCost > 0 ? (profit / totalCost) * 100 : 0; // Determine status based on productivity and profit margin String status; if (productivityPerHa >= 5000.0 && profitMargin >= 30) { status = 'Baik'; } else if (productivityPerHa >= 5000.0 || profitMargin >= 30) { status = 'Cukup'; } else { status = 'Kurang'; } // Prepare harvest data final Map 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, 'cost': totalCost, 'price_per_kg': pricePerKg, 'income': income, 'profit': profit, 'profit_margin': profitMargin, 'rc_ratio': rcRatio, 'bc_ratio': bcRatio, 'roi': roi, 'status': status, 'harvest_date': DateTime.now().toIso8601String(), }; // Berikan waktu untuk UI update sebelum navigasi await Future.delayed(const Duration(milliseconds: 100)); // Navigate to result screen if (!mounted) return; Navigator.push( context, MaterialPageRoute( builder: (context) => HarvestResultScreen( userId: widget.userId, harvestData: harvestData, scheduleData: widget.scheduleData, ), ), ); } 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), // Biaya Produksi section _buildSectionTitle('Biaya Produksi'), 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 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 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), // 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 dikosongkan.', ), 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'); } } 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: Text( dateInfo.isNotEmpty ? 'Plot: ${schedule['plot'] ?? '-'} • $dateInfo' : 'Plot: ${schedule['plot'] ?? '-'}', style: const TextStyle(fontSize: 12), ), trailing: isSelected ? const Icon( Icons.check_circle, color: Color(0xFF056839), ) : null, onTap: () { try { setState(() { _selectedScheduleId = schedule['id']; _selectedSchedule = schedule; _isManualMode = false; }); Navigator.pop(context); // Use setState again to ensure UI updates properly setState(() { _updateFormFieldsFromSelectedSchedule(); }); debugPrint( 'Selected schedule: ${schedule['id']} - ${schedule['crop_name']}', ); } catch (e) { debugPrint( 'Error selecting schedule: $e', ); } }, ), ); } }, ), ), ], ), ); }, ); } 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 dikosongkan.', ), duration: Duration(seconds: 2), ), ); }, child: const Text('Gunakan Mode Manual'), ), ], ), ); } } }