import 'dart:convert'; import 'dart:io'; import 'package:absen/config/config.dart'; import 'package:dotted_border/dotted_border.dart'; import 'package:file_picker/file_picker.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; import 'package:image_picker/image_picker.dart'; class IzinScreen extends StatefulWidget { final String token; const IzinScreen({Key? key, required this.token}) : super(key: key); @override State createState() => _IzinScreenState(); } class _IzinScreenState extends State { final _formKey = GlobalKey(); DateTime? _startDate; DateTime? _endDate; String? _category; final TextEditingController _reasonController = TextEditingController(); // Photo state File? _photo; Uint8List? _webPhotoBytes; String? _webPhotoName; bool _isLoading = false; final List _categories = [ 'Cuti Tahunan', 'Izin Sakit', 'Izin Setengah Hari', 'Dinas di Luar', ]; // Pick start/end dates Future _pickDate(bool isStart) async { final picked = await showDatePicker( context: context, initialDate: DateTime.now(), firstDate: DateTime.now(), lastDate: DateTime.now().add(const Duration(days: 365)), ); if (picked != null) { setState(() { if (isStart) _startDate = picked; else _endDate = picked; }); } } // Pick proof photo Future _pickPhoto() async { try { if (kIsWeb) { final result = await FilePicker.platform.pickFiles( type: FileType.image, ); if (result != null && result.files.single.bytes != null) { setState(() { _webPhotoBytes = result.files.single.bytes; _webPhotoName = result.files.single.name; }); } } else { final picker = ImagePicker(); final picked = await picker.pickImage( source: ImageSource.gallery, imageQuality: 70, ); if (picked != null) { setState(() => _photo = File(picked.path)); } } } catch (e) { ScaffoldMessenger.of( context, ).showSnackBar(SnackBar(content: Text('Gagal mengambil foto: $e'))); } } // Submit permission including location & photo Future _submitIzin() async { if (_startDate == null || _endDate == null || _category == null) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Mohon lengkapi semua field')), ); return; } setState(() => _isLoading = true); final uri = Uri.parse('${AppConfig.baseUrl}/api/employee/permission'); final req = http.MultipartRequest('POST', uri) ..headers['Authorization'] = 'Bearer ${widget.token}' ..fields['start_date'] = _startDate!.toIso8601String().split('T')[0] ..fields['end_date'] = _endDate!.toIso8601String().split('T')[0] ..fields['category'] = _category! ..fields['reason'] = _reasonController.text; if (kIsWeb && _webPhotoBytes != null && _webPhotoName != null) { req.files.add( http.MultipartFile.fromBytes( 'proof_photo', _webPhotoBytes!, filename: _webPhotoName, ), ); } else if (_photo != null) { req.files.add( await http.MultipartFile.fromPath('proof_photo', _photo!.path), ); } try { final streamed = await req.send(); final resp = await http.Response.fromStream(streamed); final data = jsonDecode(resp.body); ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text(data['message'] ?? 'Pengajuan izin gagal!')), ); if (resp.statusCode == 200) Navigator.pop(context); } catch (_) { ScaffoldMessenger.of( context, ).showSnackBar(const SnackBar(content: Text('Terjadi kesalahan.'))); } finally { if (mounted) setState(() => _isLoading = false); } } // Whether a photo is selected bool _isPhotoReady() { return kIsWeb ? _webPhotoBytes != null && _webPhotoName != null : _photo != null; } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Form Izin'), backgroundColor: const Color(0xFF4F8DFD), elevation: 0, ), backgroundColor: const Color(0xFFF7F9FB), body: SingleChildScrollView( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 24), child: Center( child: Container( width: 430, padding: const EdgeInsets.all(20), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(18), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.06), blurRadius: 16, offset: const Offset(0, 4), ), ], ), child: Form( key: _formKey, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Start date const Text( 'Tanggal Mulai', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 15), ), const SizedBox(height: 8), InkWell( onTap: () => _pickDate(true), child: Container( padding: const EdgeInsets.symmetric( vertical: 14, horizontal: 16, ), decoration: BoxDecoration( color: const Color(0xFFF7F9FB), border: Border.all(color: const Color(0xFFE3EAF2)), borderRadius: BorderRadius.circular(10), ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( _startDate == null ? 'yyyy-mm-dd' : _startDate!.toIso8601String().split('T')[0], style: TextStyle( color: _startDate == null ? Colors.grey : Colors.black, fontSize: 15, ), ), const Icon( Icons.calendar_today, color: Color(0xFF4F8DFD), ), ], ), ), ), const SizedBox(height: 18), // End date const Text( 'Tanggal Akhir', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 15), ), const SizedBox(height: 8), InkWell( onTap: () => _pickDate(false), child: Container( padding: const EdgeInsets.symmetric( vertical: 14, horizontal: 16, ), decoration: BoxDecoration( color: const Color(0xFFF7F9FB), border: Border.all(color: const Color(0xFFE3EAF2)), borderRadius: BorderRadius.circular(10), ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( _endDate == null ? 'yyyy-mm-dd' : _endDate!.toIso8601String().split('T')[0], style: TextStyle( color: _endDate == null ? Colors.grey : Colors.black, fontSize: 15, ), ), const Icon( Icons.calendar_today, color: Color(0xFF4F8DFD), ), ], ), ), ), const SizedBox(height: 18), // Category const Text( 'Kategori', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 15), ), const SizedBox(height: 8), DropdownButtonFormField( value: _category, decoration: InputDecoration( filled: true, fillColor: const Color(0xFFF7F9FB), border: OutlineInputBorder( borderRadius: BorderRadius.circular(10), borderSide: const BorderSide(color: Color(0xFFE3EAF2)), ), contentPadding: const EdgeInsets.symmetric( vertical: 14, horizontal: 16, ), ), hint: const Text('Pilih kategori'), items: _categories .map( (value) => DropdownMenuItem( value: value, child: Text(value), ), ) .toList(), onChanged: (v) => setState(() => _category = v), validator: (v) => v == null ? 'Pilih kategori' : null, ), const SizedBox(height: 18), // Reason const Text( 'Alasan', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 15), ), const SizedBox(height: 8), TextFormField( controller: _reasonController, decoration: InputDecoration( filled: true, fillColor: const Color(0xFFF7F9FB), border: OutlineInputBorder( borderRadius: BorderRadius.circular(10), borderSide: const BorderSide(color: Color(0xFFE3EAF2)), ), contentPadding: const EdgeInsets.symmetric( vertical: 14, horizontal: 16, ), ), maxLines: 3, validator: (v) => v == null || v.isEmpty ? 'Alasan tidak boleh kosong' : null, ), const SizedBox(height: 18), // Photo proof const Text( 'Foto Bukti', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 15), ), const SizedBox(height: 8), GestureDetector( onTap: _pickPhoto, child: DottedBorder( color: const Color(0xFFB5C9F7), borderType: BorderType.RRect, radius: const Radius.circular(12), dashPattern: const [6, 3], strokeWidth: 1.5, child: Container( width: double.infinity, padding: const EdgeInsets.symmetric(vertical: 24), decoration: BoxDecoration( color: const Color(0xFFF7F9FB), borderRadius: BorderRadius.circular(12), ), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ const Icon( Icons.camera_alt, size: 40, color: Color(0xFFB5C9F7), ), const SizedBox(height: 8), const Text( 'Unggah foto bukti', style: TextStyle( color: Color(0xFFB5C9F7), fontWeight: FontWeight.w600, ), ), const SizedBox(height: 4), Text( _isPhotoReady() ? 'File terpilih' : 'Pilih File', style: const TextStyle( color: Color(0xFF4F8DFD), fontWeight: FontWeight.w500, ), ), if (_isPhotoReady()) Padding( padding: const EdgeInsets.only(top: 12.0), child: kIsWeb ? Image.memory( _webPhotoBytes!, height: 80, ) : Image.file(_photo!, height: 80), ), ], ), ), ), ), const SizedBox(height: 28), // Submit button SizedBox( width: double.infinity, height: 48, child: ElevatedButton( style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFF4F8DFD), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), elevation: 0, ), onPressed: _isLoading ? null : _submitIzin, child: _isLoading ? const CircularProgressIndicator( color: Colors.white, ) : const Text( 'Kirim', style: TextStyle( fontSize: 16, fontWeight: FontWeight.bold, ), ), ), ), ], ), ), ), ), ), ); } }