import 'dart:io'; import 'package:flutter/material.dart'; import 'package:image_picker/image_picker.dart'; import 'package:supabase_flutter/supabase_flutter.dart'; import 'package:uuid/uuid.dart'; class AddDailyLogDialog extends StatefulWidget { final String scheduleId; final DateTime date; const AddDailyLogDialog({ super.key, required this.scheduleId, required this.date, }); @override State createState() => _AddDailyLogDialogState(); } class _AddDailyLogDialogState extends State { final _formKey = GlobalKey(); final _noteController = TextEditingController(); final _costController = TextEditingController(); final _noteFocus = FocusNode(); final _costFocus = FocusNode(); final _scrollController = ScrollController(); File? _imageFile; bool _isUploading = false; @override void initState() { super.initState(); // Hapus semua kode yang mungkin mengganggu keyboard } @override void dispose() { _noteController.dispose(); _costController.dispose(); _noteFocus.dispose(); _costFocus.dispose(); _scrollController.dispose(); super.dispose(); } Future _pickImage() async { final picker = ImagePicker(); final picked = await picker.pickImage(source: ImageSource.camera); if (picked != null) { setState(() => _imageFile = File(picked.path)); } } Future _uploadImage(String id) async { if (_imageFile == null) return null; final fileExt = _imageFile!.path.split('.').last; final path = 'daily_logs/$id.$fileExt'; final storage = Supabase.instance.client.storage; try { await storage .from('images') .upload( path, _imageFile!, fileOptions: const FileOptions(upsert: true), ); } catch (e) { debugPrint('Upload error: $e'); return null; } final url = storage.from('images').getPublicUrl(path); return url; } Future _submit() async { if (!_formKey.currentState!.validate()) return; setState(() => _isUploading = true); final id = const Uuid().v4(); final imageUrl = await _uploadImage(id); if (_imageFile != null && imageUrl == null) { setState(() => _isUploading = false); ScaffoldMessenger.of( context, ).showSnackBar(const SnackBar(content: Text('Gagal mengunggah gambar'))); return; } try { final selectedDate = DateTime.utc( widget.date.year, widget.date.month, widget.date.day, DateTime.now().hour, DateTime.now().minute, ); await Supabase.instance.client.from('daily_logs').insert({ 'id': id, 'schedule_id': widget.scheduleId, 'date': selectedDate.toIso8601String(), 'note': _noteController.text, 'cost': double.tryParse(_costController.text), 'image_url': imageUrl, }); if (mounted) { Navigator.pop(context, true); } } catch (e) { debugPrint('Insert error: $e'); if (mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Gagal menyimpan log harian')), ); } } } String _formatDate(DateTime date) { const months = [ 'Januari', 'Februari', 'Maret', 'April', 'Mei', 'Juni', 'Juli', 'Agustus', 'September', 'Oktober', 'November', 'Desember', ]; return '${date.day} ${months[date.month - 1]} ${date.year}'; } @override Widget build(BuildContext context) { return Material( color: Colors.transparent, child: Container( decoration: const BoxDecoration( color: Colors.white, borderRadius: BorderRadius.vertical(top: Radius.circular(20)), ), child: Column( mainAxisSize: MainAxisSize.min, children: [ // Header dengan judul dan tanggal Container( padding: const EdgeInsets.fromLTRB(20, 20, 20, 10), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Judul const Text( 'Catatan Harian', style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold), ), const SizedBox(height: 8), // Tanggal Text( 'Tanggal: ${_formatDate(widget.date)}', style: const TextStyle(fontSize: 16, color: Colors.black87), ), ], ), ), // Konten form yang dapat di-scroll Flexible( child: SingleChildScrollView( padding: EdgeInsets.only( bottom: MediaQuery.of(context).viewInsets.bottom + 80, // Meningkatkan padding bottom saat keyboard muncul left: 20, right: 20, top: 10, ), controller: _scrollController, child: Form( key: _formKey, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Label Catatan const Text( 'Catatan', style: TextStyle( fontSize: 16, fontWeight: FontWeight.w500, ), ), const SizedBox(height: 8), // Field Catatan TextFormField( controller: _noteController, focusNode: _noteFocus, maxLines: 3, textInputAction: TextInputAction.next, onFieldSubmitted: (_) => FocusScope.of(context).requestFocus(_costFocus), decoration: InputDecoration( hintText: 'Catatan', border: OutlineInputBorder( borderRadius: BorderRadius.circular(8), ), contentPadding: const EdgeInsets.symmetric( horizontal: 16, vertical: 16, ), ), ), const SizedBox(height: 16), // Label Biaya const Text( 'Biaya (Rp)', style: TextStyle( fontSize: 16, fontWeight: FontWeight.w500, ), ), const SizedBox(height: 8), // Field Biaya TextFormField( controller: _costController, focusNode: _costFocus, keyboardType: TextInputType.number, textInputAction: TextInputAction.done, decoration: InputDecoration( hintText: 'Biaya (Rp)', border: OutlineInputBorder( borderRadius: BorderRadius.circular(8), ), contentPadding: const EdgeInsets.symmetric( horizontal: 16, vertical: 16, ), ), validator: (value) { if (value == null || value.isEmpty) { return 'Biaya tidak boleh kosong'; } if (double.tryParse(value) == null) { return 'Masukkan angka yang valid'; } return null; }, ), const SizedBox(height: 20), // Tombol Ambil Foto Center( child: OutlinedButton.icon( onPressed: _pickImage, icon: const Icon( Icons.camera_alt, color: Color(0xFF056839), ), label: const Text( 'Ambil Foto', style: TextStyle(color: Color(0xFF056839)), ), style: OutlinedButton.styleFrom( side: const BorderSide(color: Color(0xFF056839)), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(20), ), padding: const EdgeInsets.symmetric( horizontal: 24, vertical: 12, ), ), ), ), // Preview gambar jika ada if (_imageFile != null) ...[ const SizedBox(height: 16), Center( child: ClipRRect( borderRadius: BorderRadius.circular(8), child: Image.file( _imageFile!, width: 150, height: 150, fit: BoxFit.cover, ), ), ), ], const SizedBox(height: 24), // Tombol Aksi Row( mainAxisAlignment: MainAxisAlignment.end, children: [ TextButton( onPressed: () => Navigator.pop(context), child: const Text( 'Batal', style: TextStyle( color: Colors.black54, fontSize: 16, ), ), ), const SizedBox(width: 16), ElevatedButton( onPressed: _isUploading ? null : _submit, style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFF056839), foregroundColor: Colors.white, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(20), ), padding: const EdgeInsets.symmetric( horizontal: 32, vertical: 12, ), ), child: _isUploading ? const SizedBox( height: 20, width: 20, child: CircularProgressIndicator( color: Colors.white, strokeWidth: 2, ), ) : const Text( 'Simpan', style: TextStyle(fontSize: 16), ), ), ], ), ], ), ), ), ), ], ), ), ); } } Future _showAddDailyLogDialog( BuildContext context, String scheduleId, DateTime date, ) { // Gunakan showModalBottomSheet standar return showModalBottomSheet( context: context, isScrollControlled: true, backgroundColor: Colors.transparent, useSafeArea: true, enableDrag: true, isDismissible: true, builder: (BuildContext context) { return AddDailyLogDialog(scheduleId: scheduleId, date: date); }, ); }