909 lines
32 KiB
Dart
909 lines
32 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();
|
|
|
|
// Selected schedule
|
|
String? _selectedScheduleId;
|
|
Map<String, dynamic>? _selectedSchedule;
|
|
List<Map<String, dynamic>> _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<void> _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<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');
|
|
_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<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;
|
|
|
|
// 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<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,
|
|
'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'),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|
|
}
|