import 'package:flutter/cupertino.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/widgets.dart'; import 'dart:io'; import 'package:http/http.dart' as http; import 'dart:convert'; import 'package:get/get.dart'; import 'package:qyuota/view/home/home_view.dart'; import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; import 'package:qyuota/config/colors.dart'; import 'package:qyuota/models/cuti_model.dart'; import 'package:qyuota/config/api_config.dart'; import 'package:qyuota/services/api_service.dart'; import 'package:file_picker/file_picker.dart'; import 'package:qyuota/services/auth_service.dart'; import 'dart:async'; import 'package:http_parser/http_parser.dart'; class Cutipage extends StatefulWidget { const Cutipage({super.key}); @override State createState() => _CutipageState(); } class _CutipageState extends State { final controllerName = TextEditingController(); final formController = TextEditingController(); final toController = TextEditingController(); final _keteranganController = TextEditingController(); PlatformFile? _selectedFile; String? _fileName; String dropValueCategories = "Pilih"; var categoriesList = [ "Pilih", "Cuti Tahunan", "Cuti Bulanan" ]; @override void dispose() { controllerName.dispose(); formController.dispose(); toController.dispose(); _keteranganController.dispose(); super.dispose(); } Future _pickFile() async { try { final result = await FilePicker.platform.pickFiles( type: FileType.custom, allowedExtensions: ['pdf'], ); if (result != null) { setState(() { _selectedFile = result.files.first; _fileName = _selectedFile?.name; }); } } catch (e) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Error picking file: ${e.toString()}')), ); } } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Colors.grey[100], appBar: AppBar( elevation: 0, backgroundColor: ConstColors.primaryColor, leading: IconButton( icon: const Icon(Icons.arrow_back_ios, color: Colors.white), onPressed: () => Navigator.of(context).pop(), ), title: const Text( "Pengajuan Cuti", style: TextStyle( fontSize: 20, fontWeight: FontWeight.bold, color: Colors.white, ), ), centerTitle: true, ), body: SingleChildScrollView( child: Column( children: [ Container( height: 75, decoration: BoxDecoration( color: ConstColors.primaryColor, borderRadius: const BorderRadius.only( bottomLeft: Radius.circular(30), bottomRight: Radius.circular(30), ), ), ), Transform.translate( offset: const Offset(0, -60), child: Container( margin: const EdgeInsets.symmetric(horizontal: 20), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(20), boxShadow: [ BoxShadow( color: Colors.grey.withOpacity(0.1), spreadRadius: 5, blurRadius: 10, ), ], ), child: Padding( padding: const EdgeInsets.all(20), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( "Form Pengajuan Cuti", style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, color: ConstColors.primaryColor, ), ), const SizedBox(height: 20), _buildTextField( controller: controllerName, label: "Nama Lengkap", hint: "Masukkan nama lengkap Anda", icon: Icons.person_outline, ), const SizedBox(height: 15), _buildTextField( controller: _keteranganController, maxLines: 3, label: "Keterangan", hint: "Berikan alasan cuti Anda", icon: Icons.description_outlined, ), const SizedBox(height: 15), _buildDropdown(), const SizedBox(height: 15), Row( children: [ Expanded( child: _buildDateField( controller: formController, label: "Tanggal Mulai", onTap: () => _selectDate(formController), ), ), const SizedBox(width: 15), Expanded( child: _buildDateField( controller: toController, label: "Tanggal Selesai", onTap: () => _selectDate(toController), ), ), ], ), const SizedBox(height: 15), _buildFilePicker(), const SizedBox(height: 30), SizedBox( width: double.infinity, height: 50, child: ElevatedButton( style: ElevatedButton.styleFrom( backgroundColor: ConstColors.skyColor, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), ), onPressed: _handleSubmit, child: const Text( "Ajukan Cuti", style: TextStyle( fontSize: 16, fontWeight: FontWeight.bold, color: Colors.white, ), ), ), ), ], ), ), ), ), ], ), ), ); } Future _handleSubmit() async { if (_selectedFile == null) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Mohon pilih file PDF')), ); return; } if (_keteranganController.text.isEmpty || controllerName.text.isEmpty || formController.text.isEmpty || toController.text.isEmpty || dropValueCategories == "Pilih") { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Mohon lengkapi semua field')), ); return; } showDialog( context: context, barrierDismissible: false, builder: (BuildContext context) { return const Center(child: CircularProgressIndicator()); }, ); try { final token = await AuthService().getToken(); if (token == null) { Navigator.pop(context); ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Error autentikasi')), ); return; } // Debug: Print token print('Token: $token'); // Convert dates to correct format final startDate = DateFormat('dd/MM/yyyy').parse(formController.text); final endDate = DateFormat('dd/MM/yyyy').parse(toController.text); final uri = Uri.parse('${ApiConfig.baseUrl}/api/mobile/cuti'); // Debug: Print URL print('Submitting to URL: $uri'); final request = http.MultipartRequest('POST', uri) ..headers.addAll({ 'Authorization': 'Bearer $token', 'Accept': 'application/json', 'Content-Type': 'multipart/form-data', }); // Add form fields request.fields.addAll({ 'nama': controllerName.text, 'keterangan': _keteranganController.text, 'jenis_cuti': dropValueCategories, 'tanggal_mulai': DateFormat('yyyy-MM-dd').format(startDate), 'tanggal_selesai': DateFormat('yyyy-MM-dd').format(endDate), }); // Debug: Print request fields print('Request fields: ${request.fields}'); // Add file to request if (_selectedFile != null && _selectedFile!.bytes != null) { final file = http.MultipartFile.fromBytes( 'file_pdf', _selectedFile!.bytes!, filename: _fileName ?? 'document.pdf', contentType: MediaType('application', 'pdf'), ); request.files.add(file); // Debug: Print file details print('File name: ${file.filename}'); print('File size: ${_selectedFile!.bytes!.length} bytes'); } final streamedResponse = await request.send().timeout( const Duration(seconds: 30), onTimeout: () { throw TimeoutException('Request timeout'); }, ); final response = await http.Response.fromStream(streamedResponse); // Debug: Print response print('Response status: ${response.statusCode}'); print('Response body: ${response.body}'); final responseData = json.decode(response.body); Navigator.pop(context); if (response.statusCode == 201 || response.statusCode == 200) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Pengajuan cuti berhasil disimpan'), backgroundColor: Colors.green, ), ); setState(() { controllerName.clear(); _keteranganController.clear(); formController.clear(); toController.clear(); dropValueCategories = "Pilih"; _selectedFile = null; _fileName = null; }); Get.off(() => const HomeView()); } else { throw Exception(responseData['message'] ?? 'Gagal mengajukan cuti'); } } catch (e) { Navigator.pop(context); String errorMessage = 'Gagal mengajukan cuti'; if (e is TimeoutException) { errorMessage = 'Koneksi timeout, silakan coba lagi'; } else if (e is SocketException) { errorMessage = 'Tidak dapat terhubung ke server'; } else { errorMessage = e.toString().replaceAll('Exception: ', ''); } // Debug: Print error print('Error submitting form: $errorMessage'); ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(errorMessage), backgroundColor: Colors.red, ), ); } } Widget _buildTextField({ TextEditingController? controller, String? label, String? hint, IconData? icon, int maxLines = 1, }) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Text( label ?? "", style: const TextStyle( fontSize: 14, fontWeight: FontWeight.w600, color: Colors.black87, ), ), if (icon != null) ...[ const SizedBox(width: 8), Icon(icon, size: 18, color: ConstColors.primaryColor), ], ], ), const SizedBox(height: 8), Container( decoration: BoxDecoration( color: Colors.grey[50], borderRadius: BorderRadius.circular(12), border: Border.all(color: Colors.grey[300]!), ), child: TextField( controller: controller, maxLines: maxLines, decoration: InputDecoration( hintText: hint, hintStyle: TextStyle(color: Colors.grey[400]), border: InputBorder.none, contentPadding: const EdgeInsets.all(16), alignLabelWithHint: true, ), ), ), ], ); } Widget _buildDropdown() { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( "Jenis Cuti", style: TextStyle( fontSize: 14, fontWeight: FontWeight.w600, color: Colors.black87, ), ), const SizedBox(height: 8), Container( decoration: BoxDecoration( color: Colors.grey[50], borderRadius: BorderRadius.circular(12), border: Border.all(color: Colors.grey[300]!), ), child: DropdownButtonFormField( value: dropValueCategories, decoration: const InputDecoration( prefixIcon: Icon(Icons.category_outlined, color: ConstColors.primaryColor), border: InputBorder.none, contentPadding: EdgeInsets.symmetric(horizontal: 16, vertical: 12), ), isExpanded: true, items: categoriesList.map((String value) { return DropdownMenuItem( value: value, child: Text(value), ); }).toList(), onChanged: (String? newValue) { if (newValue != null) { setState(() { dropValueCategories = newValue; }); } }, ), ), ], ); } Widget _buildDateField({ required TextEditingController controller, required String label, required VoidCallback onTap, }) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( label, style: const TextStyle( fontSize: 14, fontWeight: FontWeight.w600, color: Colors.black87, ), ), const SizedBox(height: 8), Container( decoration: BoxDecoration( color: Colors.grey[50], borderRadius: BorderRadius.circular(12), border: Border.all(color: Colors.grey[300]!), ), child: TextField( controller: controller, readOnly: true, onTap: onTap, decoration: const InputDecoration( prefixIcon: Icon(Icons.calendar_today, color: ConstColors.primaryColor), border: InputBorder.none, contentPadding: EdgeInsets.all(16), ), ), ), ], ); } Widget _buildFilePicker() { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( "File Penunjang (PDF)", style: TextStyle( fontSize: 14, fontWeight: FontWeight.w600, color: Colors.black87, ), ), const SizedBox(height: 8), Container( decoration: BoxDecoration( color: Colors.grey[50], borderRadius: BorderRadius.circular(12), border: Border.all(color: Colors.grey[300]!), ), child: Row( children: [ Expanded( child: Container( padding: const EdgeInsets.all(16), child: Row( children: [ Icon(Icons.picture_as_pdf, color: _selectedFile != null ? Colors.red : Colors.grey, ), const SizedBox(width: 8), Expanded( child: Text( _selectedFile != null ? _fileName! : 'Pilih file PDF', style: TextStyle( color: _selectedFile != null ? Colors.black87 : Colors.grey[600], fontSize: 14, ), overflow: TextOverflow.ellipsis, ), ), ], ), ), ), Container( decoration: BoxDecoration( border: Border( left: BorderSide(color: Colors.grey[300]!), ), ), child: IconButton( icon: const Icon(Icons.upload_file), color: ConstColors.primaryColor, onPressed: _pickFile, ), ), ], ), ), if (_selectedFile != null) ...[ const SizedBox(height: 8), Row( children: [ Icon(Icons.check_circle, color: Colors.green, size: 16, ), const SizedBox(width: 8), Text( 'File siap diupload (${(_selectedFile!.size / 1024).toStringAsFixed(2)} KB)', style: TextStyle( color: Colors.green[700], fontSize: 12, ), ), ], ), ], ], ); } Future _selectDate(TextEditingController controller) async { try { final DateTime? picked = await showDatePicker( context: context, initialDate: controller.text.isEmpty ? DateTime.now() : DateFormat('dd/MM/yyyy').parse(controller.text), firstDate: DateTime(2024), lastDate: DateTime(2025, 12, 31), builder: (context, child) { return Theme( data: Theme.of(context).copyWith( colorScheme: ColorScheme.light( primary: ConstColors.skyColor, onPrimary: Colors.white, onSurface: Colors.black, ), textButtonTheme: TextButtonThemeData( style: TextButton.styleFrom( foregroundColor: ConstColors.skyColor, ), ), ), child: child!, ); }, ); if (picked != null) { setState(() { controller.text = DateFormat('dd/MM/yyyy').format(picked); }); } } catch (e) { print('Error selecting date: $e'); ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Terjadi kesalahan saat memilih tanggal')), ); } } } String _convertDateFormat(String inputDate) { try { DateTime parsedDate = DateFormat('dd/MM/yyyy').parse(inputDate); return DateFormat('yyyy-MM-dd').format(parsedDate); } catch (e) { return inputDate; // Jika gagal, kembalikan input asli } } void showLoaderDialog(BuildContext context) { showDialog( barrierDismissible: false, context: context, builder: (BuildContext context) { return AlertDialog( content: Row( children: [ const CircularProgressIndicator( valueColor: AlwaysStoppedAnimation(ConstColors.primaryColor), ), Container( margin: const EdgeInsets.only(left: 20), child: const Text("Mohon tunggu..."), ), ], ), ); }, ); }