711 lines
28 KiB
Dart
711 lines
28 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:uuid/uuid.dart';
|
|
import 'package:supabase_flutter/supabase_flutter.dart';
|
|
import 'package:tugas_akhir_supabase/core/theme/app_colors.dart';
|
|
import 'package:tugas_akhir_supabase/screens/calendar/field_model.dart'; // Pastikan path ini benar
|
|
import 'package:tugas_akhir_supabase/screens/calendar/add_field_bottom_sheet.dart';
|
|
|
|
// final supabase = Supabase.instance.client; // Sebaiknya akses via Supabase.instance.client di dalam method
|
|
|
|
class FieldManagementScreen extends StatefulWidget {
|
|
const FieldManagementScreen({super.key});
|
|
|
|
@override
|
|
State<FieldManagementScreen> createState() => _FieldManagementScreenState();
|
|
}
|
|
|
|
class _FieldManagementScreenState extends State<FieldManagementScreen> {
|
|
final _formKey = GlobalKey<FormState>();
|
|
final _nameController = TextEditingController();
|
|
int _plotCount = 1; // Default plot count
|
|
List<Field> _fields = [];
|
|
Field? _editingField;
|
|
bool _isLoadingFields = true;
|
|
bool _isSaving = false;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_loadFields();
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
// Hapus kode yang mungkin mengganggu keyboard
|
|
_nameController.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
Future<void> _loadFields() async {
|
|
if (!mounted) return;
|
|
setState(() {
|
|
_isLoadingFields = true;
|
|
});
|
|
try {
|
|
final userId = Supabase.instance.client.auth.currentUser?.id;
|
|
if (userId == null) {
|
|
throw Exception('User not authenticated');
|
|
}
|
|
final result = await Supabase.instance.client
|
|
.from('fields')
|
|
.select()
|
|
.eq('user_id', userId)
|
|
.order('created_at', ascending: false);
|
|
|
|
final fields = (result as List).map((f) => Field.fromMap(f)).toList();
|
|
if (mounted) {
|
|
setState(() {
|
|
_fields = fields;
|
|
});
|
|
}
|
|
} catch (e) {
|
|
debugPrint('Error loading fields: $e');
|
|
if (mounted) {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
SnackBar(
|
|
content: Text('Gagal memuat data lahan. Silakan coba lagi.'),
|
|
backgroundColor: Colors.red,
|
|
),
|
|
);
|
|
}
|
|
} finally {
|
|
if (mounted) {
|
|
setState(() {
|
|
_isLoadingFields = false;
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
Future<void> _saveField() async {
|
|
if (!_formKey.currentState!.validate()) return;
|
|
|
|
// Dismiss keyboard immediately when saving
|
|
FocusScope.of(context).unfocus();
|
|
|
|
final userId = Supabase.instance.client.auth.currentUser?.id;
|
|
if (userId == null) {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
const SnackBar(
|
|
content: Text('User tidak ditemukan, silakan login ulang.'),
|
|
backgroundColor: Colors.red,
|
|
),
|
|
);
|
|
return;
|
|
}
|
|
|
|
if (mounted) setState(() => _isSaving = true);
|
|
|
|
try {
|
|
if (_editingField == null) {
|
|
// Create
|
|
await Supabase.instance.client.from('fields').insert({
|
|
'user_id': userId,
|
|
'name': _nameController.text,
|
|
'plot_count': _plotCount,
|
|
});
|
|
if (mounted) {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
const SnackBar(
|
|
content: Text('Lahan berhasil ditambahkan'),
|
|
backgroundColor: Colors.green,
|
|
),
|
|
);
|
|
}
|
|
} else {
|
|
// Update
|
|
await Supabase.instance.client
|
|
.from('fields')
|
|
.update({'name': _nameController.text, 'plot_count': _plotCount})
|
|
.eq('id', _editingField!.id);
|
|
if (mounted) {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
const SnackBar(
|
|
content: Text('Lahan berhasil diperbarui'),
|
|
backgroundColor: Colors.green,
|
|
),
|
|
);
|
|
}
|
|
}
|
|
_resetForm();
|
|
await _loadFields();
|
|
} catch (e) {
|
|
if (mounted) {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
SnackBar(
|
|
content: Text('Gagal menyimpan lahan. Silakan coba lagi.'),
|
|
backgroundColor: Colors.red,
|
|
),
|
|
);
|
|
}
|
|
} finally {
|
|
if (mounted) setState(() => _isSaving = false);
|
|
}
|
|
}
|
|
|
|
Future<void> _deleteField(Field field) async {
|
|
// Dismiss keyboard when showing a dialog
|
|
FocusScope.of(context).unfocus();
|
|
|
|
final confirm = await showDialog<bool>(
|
|
context: context,
|
|
builder:
|
|
(_) => AlertDialog(
|
|
title: const Text('Hapus Lahan'),
|
|
content: Text(
|
|
'''Yakin ingin menghapus lahan "${field.name}"?
|
|
|
|
Semua jadwal tanam yang terkait dengan lahan ini akan tetap ada namun mungkin kehilangan referensi lahan. Anda mungkin perlu menangani ini secara manual atau membuat logika pembersihan data terkait.''',
|
|
),
|
|
actions: [
|
|
TextButton(
|
|
onPressed: () => Navigator.pop(context, false),
|
|
child: const Text('Batal'),
|
|
),
|
|
ElevatedButton(
|
|
onPressed: () => Navigator.pop(context, true),
|
|
style: ElevatedButton.styleFrom(backgroundColor: Colors.red),
|
|
child: const Text('Hapus'),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
|
|
if (confirm == true) {
|
|
if (mounted)
|
|
setState(
|
|
() => _isSaving = true,
|
|
); // Bisa gunakan _isLoadingFields atau state lain
|
|
try {
|
|
await Supabase.instance.client
|
|
.from('fields')
|
|
.delete()
|
|
.eq('id', field.id);
|
|
if (mounted) {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
const SnackBar(
|
|
content: Text('Lahan berhasil dihapus'),
|
|
backgroundColor: Colors.green,
|
|
),
|
|
);
|
|
}
|
|
await _loadFields(); // Muat ulang daftar lahan
|
|
} catch (e) {
|
|
if (mounted) {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
SnackBar(
|
|
content: Text('Gagal menghapus lahan. Silakan coba lagi.'),
|
|
backgroundColor: Colors.red,
|
|
),
|
|
);
|
|
}
|
|
} finally {
|
|
if (mounted) setState(() => _isSaving = false);
|
|
}
|
|
}
|
|
}
|
|
|
|
void _resetForm() {
|
|
// Dismiss keyboard when resetting the form
|
|
FocusScope.of(context).unfocus();
|
|
|
|
_formKey.currentState?.reset(); // Ini akan mereset state validator
|
|
_nameController.clear();
|
|
_plotCount = 1; // Reset ke nilai default
|
|
_editingField = null;
|
|
if (mounted) {
|
|
setState(
|
|
() {},
|
|
); // Update UI untuk membersihkan form dan kembali ke mode "Tambah"
|
|
}
|
|
}
|
|
|
|
void _startEdit(Field field) {
|
|
// Dismiss keyboard when starting an edit
|
|
FocusScope.of(context).unfocus();
|
|
|
|
_nameController.text = field.name;
|
|
_plotCount = field.plotCount;
|
|
_editingField = field;
|
|
if (mounted) {
|
|
setState(
|
|
() {},
|
|
); // Update UI untuk mengisi form dengan data lahan yang diedit
|
|
}
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final isEditing = _editingField != null;
|
|
|
|
return WillPopScope(
|
|
onWillPop: () async {
|
|
// Hapus unfocus yang menyebabkan masalah keyboard
|
|
// FocusScope.of(context).unfocus();
|
|
return true;
|
|
},
|
|
child: GestureDetector(
|
|
// Hapus unfocus yang menyebabkan masalah keyboard
|
|
// onTap: () => FocusScope.of(context).unfocus(),
|
|
child: Scaffold(
|
|
resizeToAvoidBottomInset:
|
|
true, // Memastikan UI tidak tertutup keyboard
|
|
appBar: AppBar(
|
|
title: Text(
|
|
'Manajemen Lahan',
|
|
style: TextStyle(fontWeight: FontWeight.w600),
|
|
),
|
|
backgroundColor: AppColors.primary,
|
|
foregroundColor: Colors.white,
|
|
elevation: 0,
|
|
actions: [
|
|
IconButton(
|
|
icon: const Icon(Icons.add_circle_outline),
|
|
tooltip: 'Tambah Lahan Baru',
|
|
onPressed: () {
|
|
// Use bottom sheet instead of dialog for adding fields
|
|
showAddFieldBottomSheet(
|
|
context: context,
|
|
onFieldAdded: () {
|
|
_loadFields();
|
|
},
|
|
);
|
|
},
|
|
),
|
|
IconButton(
|
|
icon: const Icon(Icons.refresh),
|
|
tooltip: 'Refresh Data',
|
|
onPressed: () {
|
|
_loadFields();
|
|
},
|
|
),
|
|
],
|
|
),
|
|
body: SingleChildScrollView(
|
|
child: Column(
|
|
children: [
|
|
// Form Section
|
|
Container(
|
|
padding: const EdgeInsets.fromLTRB(20, 16, 20, 24),
|
|
decoration: BoxDecoration(
|
|
gradient: LinearGradient(
|
|
begin: Alignment.topCenter,
|
|
end: Alignment.bottomCenter,
|
|
colors: [AppColors.primary, AppColors.secondary],
|
|
),
|
|
borderRadius: const BorderRadius.only(
|
|
bottomLeft: Radius.circular(20),
|
|
bottomRight: Radius.circular(20),
|
|
),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: Colors.black.withOpacity(0.1),
|
|
blurRadius: 8,
|
|
offset: const Offset(0, 3),
|
|
),
|
|
],
|
|
),
|
|
child: Form(
|
|
key: _formKey,
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
mainAxisSize:
|
|
MainAxisSize
|
|
.min, // Agar Column tidak mengambil semua tinggi
|
|
children: [
|
|
Text(
|
|
isEditing ? 'Edit Detail Lahan' : 'Tambah Lahan Baru',
|
|
style: const TextStyle(
|
|
color: Colors.white,
|
|
fontSize: 18, // Ukuran font disesuaikan
|
|
fontWeight: FontWeight.bold,
|
|
),
|
|
),
|
|
const SizedBox(height: 16), // Spasi antar elemen
|
|
TextFormField(
|
|
controller: _nameController,
|
|
decoration: InputDecoration(
|
|
labelText: 'Nama Lahan',
|
|
labelStyle: TextStyle(
|
|
color: Colors.white.withOpacity(0.9),
|
|
),
|
|
hintText: 'Contoh: Lahan Cabai',
|
|
hintStyle: TextStyle(
|
|
color: Colors.white.withOpacity(0.7),
|
|
),
|
|
filled: true,
|
|
fillColor: Colors.white.withOpacity(0.2),
|
|
border: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(12),
|
|
borderSide: BorderSide.none,
|
|
),
|
|
contentPadding: const EdgeInsets.symmetric(
|
|
horizontal: 16,
|
|
vertical: 14,
|
|
),
|
|
prefixIcon: Icon(
|
|
Icons.agriculture,
|
|
color: Colors.white.withOpacity(0.9),
|
|
size: 20,
|
|
),
|
|
),
|
|
style: const TextStyle(
|
|
color: Colors.white,
|
|
fontSize: 15,
|
|
),
|
|
validator:
|
|
(val) =>
|
|
val == null || val.isEmpty
|
|
? 'Nama lahan wajib diisi'
|
|
: null,
|
|
),
|
|
const SizedBox(height: 16),
|
|
Container(
|
|
// Kontainer untuk baris jumlah plot
|
|
padding: const EdgeInsets.symmetric(
|
|
horizontal: 12,
|
|
vertical: 4,
|
|
),
|
|
decoration: BoxDecoration(
|
|
color: Colors.white.withOpacity(0.2),
|
|
borderRadius: BorderRadius.circular(12),
|
|
),
|
|
child: Row(
|
|
children: [
|
|
Icon(
|
|
Icons.grid_view_rounded,
|
|
color: Colors.white.withOpacity(0.9),
|
|
size: 20,
|
|
),
|
|
const SizedBox(width: 12),
|
|
const Text(
|
|
'Jumlah Plot:',
|
|
style: TextStyle(
|
|
color: Colors.white,
|
|
fontSize: 15,
|
|
),
|
|
),
|
|
const Spacer(), // Untuk mendorong dropdown ke kanan
|
|
DropdownButton<int>(
|
|
value: _plotCount,
|
|
dropdownColor:
|
|
AppColors.primary, // Warna dropdown
|
|
icon: const Icon(
|
|
Icons.arrow_drop_down,
|
|
color: Colors.white,
|
|
),
|
|
underline:
|
|
const SizedBox(), // Hilangkan garis bawah default
|
|
style: const TextStyle(
|
|
color: Colors.white,
|
|
fontSize: 15, // Ukuran font disesuaikan
|
|
fontWeight: FontWeight.w500,
|
|
),
|
|
items:
|
|
List.generate(20, (i) => i + 1)
|
|
.map(
|
|
(n) => DropdownMenuItem(
|
|
value: n,
|
|
child: Text(n.toString()),
|
|
),
|
|
)
|
|
.toList(),
|
|
onChanged:
|
|
_isSaving
|
|
? null
|
|
: (val) =>
|
|
setState(() => _plotCount = val!),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
const SizedBox(height: 20),
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.end,
|
|
children: [
|
|
if (isEditing)
|
|
TextButton.icon(
|
|
onPressed: _isSaving ? null : _resetForm,
|
|
icon: const Icon(
|
|
Icons.cancel_outlined,
|
|
size: 18,
|
|
), // Icon disesuaikan
|
|
label: const Text('Batal Edit'),
|
|
style: TextButton.styleFrom(
|
|
foregroundColor: Colors.white,
|
|
backgroundColor: Colors.black.withOpacity(
|
|
0.25,
|
|
),
|
|
padding: const EdgeInsets.symmetric(
|
|
horizontal: 16,
|
|
vertical: 10,
|
|
),
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(10),
|
|
),
|
|
),
|
|
),
|
|
if (isEditing) const SizedBox(width: 10),
|
|
ElevatedButton.icon(
|
|
onPressed: _isSaving ? null : _saveField,
|
|
icon:
|
|
_isSaving
|
|
? SizedBox(
|
|
width: 18,
|
|
height: 18,
|
|
child: CircularProgressIndicator(
|
|
strokeWidth: 2,
|
|
color: AppColors.primary,
|
|
),
|
|
)
|
|
: Icon(
|
|
isEditing
|
|
? Icons.save_alt_outlined
|
|
: Icons.add_circle_outline_rounded,
|
|
size: 18,
|
|
), // Icon disesuaikan
|
|
label: Text(
|
|
isEditing ? 'Simpan Perubahan' : 'Tambah Lahan',
|
|
style: TextStyle(fontSize: 14),
|
|
),
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: Colors.white,
|
|
foregroundColor: AppColors.primary,
|
|
padding: const EdgeInsets.symmetric(
|
|
horizontal: 16,
|
|
vertical: 10,
|
|
),
|
|
elevation: 2,
|
|
shadowColor: Colors.black.withOpacity(0.3),
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(10),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
|
|
// List Title Section
|
|
Padding(
|
|
padding: const EdgeInsets.fromLTRB(
|
|
20,
|
|
20,
|
|
20,
|
|
10,
|
|
), // Padding disesuaikan
|
|
child: Row(
|
|
children: [
|
|
Icon(
|
|
Icons.list_alt_rounded,
|
|
color: AppColors.primary,
|
|
size: 22, // Ukuran icon disesuaikan
|
|
),
|
|
const SizedBox(width: 8),
|
|
Text(
|
|
'Daftar Lahan Tersimpan',
|
|
style: TextStyle(
|
|
fontWeight: FontWeight.bold,
|
|
fontSize: 17, // Ukuran font disesuaikan
|
|
color: AppColors.primary,
|
|
),
|
|
),
|
|
const SizedBox(width: 6),
|
|
Text(
|
|
'(${_fields.length})',
|
|
style: TextStyle(
|
|
color: Colors.grey.shade600,
|
|
fontSize: 15, // Ukuran font disesuaikan
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
|
|
// List Section
|
|
_isLoadingFields
|
|
? Center(
|
|
child: CircularProgressIndicator(
|
|
color: AppColors.primary,
|
|
),
|
|
)
|
|
: _fields.isEmpty
|
|
? Center(
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Icon(
|
|
Icons.landscape_outlined, // Icon disesuaikan
|
|
size: 60, // Ukuran icon disesuaikan
|
|
color: Colors.grey.shade400,
|
|
),
|
|
const SizedBox(height: 16),
|
|
Text(
|
|
'Belum ada lahan tersimpan',
|
|
style: TextStyle(
|
|
color: Colors.grey.shade600,
|
|
fontSize: 16,
|
|
),
|
|
),
|
|
const SizedBox(height: 8),
|
|
Text(
|
|
'Tambahkan lahan baru pada form di atas.',
|
|
style: TextStyle(
|
|
color: Colors.grey.shade500,
|
|
fontSize: 14,
|
|
),
|
|
textAlign: TextAlign.center,
|
|
),
|
|
],
|
|
),
|
|
)
|
|
: ListView.builder(
|
|
shrinkWrap: true,
|
|
physics: const NeverScrollableScrollPhysics(),
|
|
itemCount: _fields.length,
|
|
padding: const EdgeInsets.symmetric(
|
|
horizontal: 16,
|
|
vertical: 8,
|
|
),
|
|
itemBuilder: (context, index) {
|
|
final field = _fields[index];
|
|
return Card(
|
|
elevation: 1.5, // Elevasi disesuaikan
|
|
margin: const EdgeInsets.only(bottom: 12),
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(
|
|
10,
|
|
), // Border radius disesuaikan
|
|
side: BorderSide(
|
|
color:
|
|
Colors
|
|
.grey
|
|
.shade300, // Warna border disesuaikan
|
|
width: 0.8,
|
|
),
|
|
),
|
|
child: ListTile(
|
|
// Padding diatur oleh ListTile
|
|
contentPadding: const EdgeInsets.symmetric(
|
|
horizontal: 16,
|
|
vertical: 10,
|
|
),
|
|
leading: Container(
|
|
// Icon Lahan
|
|
width: 42,
|
|
height: 42,
|
|
decoration: BoxDecoration(
|
|
color: Colors.green.shade50,
|
|
borderRadius: BorderRadius.circular(8),
|
|
),
|
|
child: Icon(
|
|
Icons.eco_outlined, // Icon disesuaikan
|
|
color:
|
|
Colors
|
|
.green
|
|
.shade700, // Warna icon disesuaikan
|
|
size: 22,
|
|
),
|
|
),
|
|
title: Text(
|
|
field.name,
|
|
style: const TextStyle(
|
|
fontWeight: FontWeight.w600,
|
|
fontSize: 15.5, // Ukuran font disesuaikan
|
|
),
|
|
),
|
|
subtitle: Padding(
|
|
padding: const EdgeInsets.only(
|
|
top: 5.0,
|
|
), // Padding disesuaikan
|
|
child: Text(
|
|
'${field.plotCount} Plot',
|
|
style: TextStyle(
|
|
color:
|
|
Colors
|
|
.grey
|
|
.shade700, // Warna teks disesuaikan
|
|
fontSize: 13, // Ukuran font disesuaikan
|
|
),
|
|
),
|
|
),
|
|
trailing: Row(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
IconButton(
|
|
icon: Icon(
|
|
Icons
|
|
.edit_note_outlined, // Icon disesuaikan
|
|
color:
|
|
Colors
|
|
.orange
|
|
.shade700, // Warna icon disesuaikan
|
|
size: 22, // Ukuran icon disesuaikan
|
|
),
|
|
tooltip: 'Edit Lahan',
|
|
style: IconButton.styleFrom(
|
|
backgroundColor: Colors.orange.withOpacity(
|
|
0.1,
|
|
), // Background disesuaikan
|
|
padding: const EdgeInsets.all(8),
|
|
),
|
|
onPressed:
|
|
_isSaving
|
|
? null
|
|
: () => _startEdit(field),
|
|
),
|
|
const SizedBox(width: 6), // Spasi antar tombol
|
|
IconButton(
|
|
icon: Icon(
|
|
Icons
|
|
.delete_outline_rounded, // Icon disesuaikan
|
|
color:
|
|
Colors
|
|
.red
|
|
.shade600, // Warna icon disesuaikan
|
|
size: 22, // Ukuran icon disesuaikan
|
|
),
|
|
tooltip: 'Hapus Lahan',
|
|
style: IconButton.styleFrom(
|
|
backgroundColor: Colors.red.withOpacity(
|
|
0.1,
|
|
), // Background disesuaikan
|
|
padding: const EdgeInsets.all(8),
|
|
),
|
|
onPressed:
|
|
_isSaving
|
|
? null
|
|
: () => _deleteField(field),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
},
|
|
),
|
|
],
|
|
),
|
|
),
|
|
floatingActionButton: FloatingActionButton(
|
|
backgroundColor: AppColors.primary,
|
|
foregroundColor: Colors.white,
|
|
onPressed: () {
|
|
showAddFieldBottomSheet(
|
|
context: context,
|
|
onFieldAdded: () {
|
|
_loadFields();
|
|
},
|
|
);
|
|
},
|
|
tooltip: 'Tambah Lahan',
|
|
child: const Icon(Icons.add),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|