MIF_E31222656/lib/screens/calendar/field_management_screen.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),
),
),
),
);
}
}