import 'dart:convert'; import 'dart:html' as html; import 'dart:typed_data'; // Import for Uint8List import 'package:bcrypt/bcrypt.dart'; import 'package:dropdown_search/dropdown_search.dart'; import 'package:file_picker/file_picker.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:go_router/go_router.dart'; import 'package:http/http.dart' as http; import 'package:intl/intl.dart'; import 'package:responsive_builder/responsive_builder.dart'; import 'package:shared_preferences/shared_preferences.dart'; import '../../../config/apiUrl.dart'; import '../../../routes/custom_appBar.dart'; import '../../../routes/custom_drawer.dart'; import '../../../services/apiService.dart'; import '../../../widgets/custom_text_field.dart'; import '../../../widgets/custom_text_forex.dart'; import '../../../widgets/custom_user_form.dart'; class CreateUserForm extends StatefulWidget{ @override _CreateUserFormState createState() => _CreateUserFormState(); } class _CreateUserFormState extends State{ final ApiService apiService = ApiService(); String? userId; String? token; // Map to store controllers dynamically final Map controllers = {}; bool isViewMode = false; bool isEditProfile = true; // late final List? apiCountryData ; late List? apiCountryData; late List? apiCostData; late List? apiRoleData; late List? apiUserData; Map? apiselectedUser; late List userList; late Future> futureUsers; List? resolvedUserData; late List userIdsApi; late Map userMap; Map errorMessages = {}; String? selectedCountry; String? selectedGender; String? selectedUserType; String? selectedRole; String? selectedLevel; String? selectedDepartment; String? selectedFirstApprover; String? selectedSecondApprover; String? selectedThirdApprover; String? selectedFileNames; Uint8List? passportDocumentBytes; String? base64PDF; List dataHeader = [ "Fname", "Lname", "email", "password", "dob","gender","mobileNumber", "alternateMobile", "address", "postalCode","country","passportNumber", "placeOfIssue", "passportDoc","userType","roleId","deptId", "levelId", "firstApproval", "secondApproval", "thirdApproval","employeeCode","dateOfIssue","dateOfExpiry", "changePassword" ]; Map get userDetials{ Map data = { "first_name" : controllers["Fname"]?.text, "last_name" : controllers["Lname"]?.text, "email" : controllers["email"]?.text, "password" : controllers["password"]?.text, "mobile_no" : controllers["mobileNumber"]?.text, "alternate_mobile_no": controllers["alternateMobile"]?.text, "date_of_birth": controllers["dob"]?.text, "address" : controllers["address"]?.text, "gender" : selectedGender, "postal_code" : controllers["postalCode"]?.text, "country" : selectedCountry, "employee_code": controllers["employeeCode"]?.text, "user_type" : selectedUserType, "role_id" : selectedRole, "department_id": selectedDepartment, "group_id" : selectedLevel, "first_approver": selectedFirstApprover, "second_approver": selectedSecondApprover, "third_approver": selectedThirdApprover, "passport_number": controllers["passportNumber"]?.text, "place_of_issue": controllers["placeOfIssue"]?.text, "passport_document": base64PDF, "date_of_issue": controllers["dateOfIssue"]?.text, "date_of_expiry": controllers["dateOfExpiry"]?.text, "created_by": userId, "is_active": "1", // "passport_fileData": base64PDF, }; return data; } void updateData() { // Ensure apiselectedUser is not null before printing if (apiselectedUser != null) { print("API Selected User Has Data - $apiselectedUser"); setState(() { // ✅ Wrap in setState to update the UI controllers["Fname"]?.text = apiselectedUser?["first_name"] ?? ""; controllers["Lname"]?.text = apiselectedUser?["last_name"] ?? ""; controllers["email"]?.text = apiselectedUser?["email"] ?? ""; controllers["password"]?.text = ""; // controllers["password"]?.text = apiselectedUser?["password"] ?? ""; controllers["mobileNumber"]?.text = apiselectedUser?["mobile_no"] ?? ""; controllers["alternateMobile"]?.text = apiselectedUser?["alternate_mobile_no"] ?? ""; controllers["dob"]?.text = apiselectedUser?["date_of_birth"] ?? ""; controllers["address"]?.text = apiselectedUser?["address"] ?? ""; controllers["postalCode"]?.text = apiselectedUser?["postal_code"] ?? ""; controllers["employeeCode"]?.text = apiselectedUser?["employee_code"] ?? ""; controllers["passportNumber"]?.text = apiselectedUser?["passport_number"] ?? ""; controllers["placeOfIssue"]?.text = apiselectedUser?["place_of_issue"] ?? ""; controllers["dateOfIssue"]?.text = apiselectedUser?["date_of_issue"] ?? ""; controllers["dateOfExpiry"]?.text = apiselectedUser?["date_of_expiry"] ?? ""; selectedCountry = apiselectedUser?["country_code"]?.toString() ?? ""; selectedGender = apiselectedUser?["gender"]?.toString().trim() ?? ""; base64PDF = apiselectedUser?["passport_document"]?.toString().trim() ?? ""; selectedUserType = apiselectedUser?["user_type"]?.toString().trim() ?? ""; selectedRole = apiselectedUser?["role_id"]?.toString().trim() ?? ""; selectedDepartment = apiselectedUser?["department_id"]?.toString().trim() ?? ""; selectedLevel = apiselectedUser?["level_id"]?.toString().trim() ?? ""; selectedFirstApprover = apiselectedUser?["first_approver"]?.toString() ?? ""; selectedSecondApprover = apiselectedUser?["second_approver"]?.toString() ?? ""; selectedThirdApprover = apiselectedUser?["third_approver"]?.toString() ?? ""; print("Updated selectedGender: $selectedGender"); // Debugging }); } else { print("API Selected User Has Data - No data available yet"); } } @override void initState() { super.initState(); WidgetsFlutterBinding.ensureInitialized(); // apiCountryData = extraData['apiCountryData']; // Extract apiCountryData // futureUsers = extraData['apiUserData']; // Extract futureUsers (Future>) apiCountryData = null; apiUserData = null; apiselectedUser = null; apiCostData = null; apiRoleData = null; // Delay accessing context until the widget is fully initialized // WidgetsBinding.instance.addPostFrameCallback((_) { // setState(() { // apiCountryData = (GoRouterState.of(context).extra as Map)['apiCountryData']; // apiUserData = (GoRouterState.of(context).extra as Map)['apiUserData']; // apiselectedUser = (GoRouterState.of(context).extra as Map)['selectedUser']; // // // userList = apiUserData ?? []; // userMap = { // for (var user in userList) // user['user_id'] as String: "${user['first_name']} ${user['last_name']}" // }; // // userIdsApi = userMap.keys.toList(); // // // }); // }); // Initialize controllers for each field WidgetsBinding.instance.addPostFrameCallback((_) async{ final extraData = GoRouterState.of(context).extra as Map?; if (extraData != null) { print("extraData: ${extraData['selectedUser']}"); setState(() { // apiCountryData = extraData['apiCountryData']; // apiUserData = extraData['apiUserData']; // // userList = apiUserData ?? []; // userMap = { // for (var user in userList) // user['user_id'].toString(): "${user['first_name']} ${user['last_name']}" // }; // userIdsApi = userMap.keys.toList(); // Handle selectedUser as a Map (not a List) apiselectedUser = extraData['selectedUser'] as Map?; // Cast it as a Map isViewMode = extraData['isViewMode'] ?? false; isEditProfile = extraData['isEditProfile'] ?? false; }); // Add another post-frame callback to check after setState await Future.delayed(Duration(milliseconds: 100)); // Optional delay to ensure UI has updated updateData(); } }); for(var field in dataHeader) { controllers[field] = TextEditingController(); } initializeData(); fetchCountries(); fetchDepartment(); fetchUsers(); fetchRoles(); } Future fetchCountries() async { try { List countries = await apiService.fetchCountryList(); setState(() { apiCountryData = countries; }); } catch (e) { print('Error fetching country list: $e'); } } Future fetchDepartment() async { try { List department = await apiService.fetchCostCenter(); setState(() { apiCostData = department; }); } catch (e) { print('Error fetching department list: $e'); } } Future fetchUsers() async { try { List users = await apiService.fetchUsers(); setState(() { // apiUserData = users; apiUserData = users.where((user) => user["role_id"] == "3").toList(); print("APIUSerDATa - $apiUserData"); userList = apiUserData ?? []; userMap = { for (var user in userList) user['user_id'].toString(): "${user['first_name']} ${user['last_name']}" }; userIdsApi = userMap.keys.toList(); }); } catch (e) { print('Error fetching country list: $e'); } } Future fetchRoles() async { try { final response = await apiService.fetchMasterDropdown(); if (response is Map && response.containsKey("role")) { List roleList = response["role"]; // Extract the list setState(() { apiRoleData = roleList; }); print("APIROLEData - $apiRoleData"); } else { print("Error: 'role' key not found or response is not a Map."); } } catch (e) { print('Error fetching role list: $e'); } } void printFormData() { for (var field in dataHeader) { print("$field: ${controllers[field]?.text}"); } } Future initializeData ()async{ token = await getToken(); userId = await getUserId(); if(token == null || userId == null){ print("Token or USerId missing"); print("Retrieved Token: $token"); print("Retrieved UserId: $userId"); return; } else{ setState(() { }); } } Future getToken() async { final prefs = await SharedPreferences.getInstance(); return prefs.getString('auth_token'); } // Future getUserId() async { // final prefs = await SharedPreferences.getInstance(); // return prefs.getString('userId'); // } Future getUserId() async { final prefs = await SharedPreferences.getInstance(); final userData = prefs.getString('user_data'); if (userData != null) { final decodedData = jsonDecode(userData); // Return the user_id from the decoded data return decodedData['user_id']?.toString(); } return null; } @override void dispose() { // Dispose all controllers to prevent memory leaks for (var controller in controllers.values) { controller.dispose(); } super.dispose(); } void handleSubmit(){ print("USR Detail Submit"); printFormData(); Map data = userDetials; if (!isValidData(data)) { print("USERDETAILS : $userDetials"); print("Validation Failed: Required fields are missing."); setState(() {}); return; // Stop execution if validation fails }else { print("USERDETAILS : $userDetials"); createUserData(userDetials); } } bool isValidData(Map data) { errorMessages.clear(); // Reset errors // Required fields that must not be empty List requiredFields = ["first_name", "last_name","email","mobile_no"]; if (apiselectedUser == null) { requiredFields.add("password"); } // Check validation for each field for (String field in requiredFields) { if (data[field] == null || data[field].toString().trim().isEmpty) { errorMessages[field] = "Required"; } } // Mobile number validation for both mobile_no and alternate_mobile_no if (data["mobile_no"] != null && data["mobile_no"].toString().isNotEmpty) { if (!RegExp(r"^\d{10}$").hasMatch(data["mobile_no"].toString())) { errorMessages["mobile_no"] = "Enter 10 digits"; // Invalid mobile number format } } if (data["alternate_mobile_no"] != null && data["alternate_mobile_no"].toString().isNotEmpty) { if (!RegExp(r"^\d{10}$").hasMatch(data["alternate_mobile_no"].toString())) { errorMessages["alternate_mobile_no"] = "Enter 10 digits"; // Invalid mobile number format } } // Email validation if (data["email"] != null && data["email"].toString().isNotEmpty) { if (!RegExp(r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$").hasMatch(data["email"].toString())) { errorMessages["email"] = "Invalid email format"; // Invalid email format } } return errorMessages.isEmpty; // Valid if there are no errors } void _clearError(String field) { if (mounted && errorMessages.containsKey(field)) { setState(() { errorMessages.remove(field); }); } } void pickPDFWeb() { html.FileUploadInputElement uploadInput = html.FileUploadInputElement(); uploadInput.accept = '.pdf'; uploadInput.click(); uploadInput.onChange.listen((e) { final file = uploadInput.files!.first; final reader = html.FileReader(); reader.readAsArrayBuffer(file); reader.onLoadEnd.listen((event) { print('File picked: ${file.name}'); print('File size: ${file.size} bytes'); // Ensure the file is a PDF if (!file.type.contains("pdf")) { print("Error: Not a PDF file"); return; } setState(() { selectedFileNames = file.name; // Store file name passportDocumentBytes = reader.result as Uint8List; // Store file data // 🔹 Convert to Base64 properly base64PDF = base64Encode(passportDocumentBytes!); print('Base64 Length: ${base64PDF!.length}'); print('Base64 (first 50 chars): ${base64PDF!.substring(0, 50)}'); // Ensure Base64 starts with "JVBERi0x" if (!base64PDF!.startsWith("JVBERi0x")) { print("Error: Base64 does not start with 'JVBERi0x'"); return; } // 🔹 File size check: Ensure it does not exceed 3MB int maxFileSize = 3 * 1024 * 1024; // 3MB in bytes if (file.size > maxFileSize) { print('Error: File size exceeds 3MB'); return; } }); }); }); } // void pickPDFWeb() { // html.FileUploadInputElement uploadInput = html.FileUploadInputElement(); // uploadInput.accept = '.pdf'; // uploadInput.click(); // // uploadInput.onChange.listen((e) { // final file = uploadInput.files!.first; // final reader = html.FileReader(); // // reader.readAsArrayBuffer(file); // reader.onLoadEnd.listen((event) { // print('File picked: ${file.name}'); // print('File size: ${file.size} bytes'); // // // // Update the state with the selected file name // setState(() { // selectedFileNames = file.name; // Store only one file name // passportDocumentBytes = reader.result as Uint8List; // Store file data // // // 🔹 Convert file to Base64 // base64PDF = base64Encode(passportDocumentBytes! as List); // // File size check: Ensure the file size does not exceed 3MB // int maxFileSize = 3 * 1024 * 1024; // 3MB in bytes // // if (file.size > maxFileSize) { // print('Error: File size exceeds 3MB'); // // You can show an error message here if necessary // // For example: // // showError('File size cannot exceed 3MB'); // return; // } // // print('File size: ${file.size} bytes'); // print('Base64 Data: $base64PDF'); // Debugging // }); // // // // }); // }); // } Future createUserData(Map userData) async { bool isUpdating = apiselectedUser != null && apiselectedUser!.isNotEmpty; final String apiUrldata = isUpdating? '$apiUrl/api/users/update/${apiselectedUser?["user_id"]}' :'$apiUrl/api/users/create'; if (token == null) { throw Exception('Token not found. Please log in.'); } // Add user_id only if updating if (isUpdating) { userData['user_id'] = apiselectedUser?["user_id"]; } try { final response = isUpdating ? await http.put( Uri.parse(apiUrldata), headers: { 'Authorization': 'Bearer $token', 'Content-Type': 'application/json', }, body: jsonEncode(userData), ) : await http.post( Uri.parse(apiUrldata), headers: { 'Authorization': 'Bearer $token', 'Content-Type': 'application/json', }, body: jsonEncode(userData), ); if (response.statusCode == 200 || response.statusCode == 201) { print("Plan submitted successfully!"); print("Response: ${response.body}"); context.go('/listUser'); } else { print("Failed to submit plan. Status: ${response.statusCode}"); print("Error: ${response.body}"); } } catch (e) { print(" Error submitting plan: $e"); } } void handleChangePassword(String enteredPassword) { var hashedPassword = apiselectedUser?["password"]; // Get stored bcrypt hash print("HAshPAssword - $hashedPassword "); print("enteredPassword - $enteredPassword "); if (hashedPassword != null && hashedPassword.isNotEmpty) { bool isMatch = BCrypt.checkpw(enteredPassword, hashedPassword); // Compare passwords setState(() { // ✅ Ensure UI updates if (isMatch) { errorMessages["password"] = "New password is not similar to old password"; print(" Password match!"); } else { print(" Password NOT match!"); errorMessages.remove("password"); // Clear error if password is different } }); } else { print("No stored password found."); } } @override Widget build(BuildContext context){ return ResponsiveBuilder(builder: (context, sizingInfo) { bool isDesktop = sizingInfo.deviceScreenType == DeviceScreenType.desktop; return Scaffold( appBar: isDesktop ? null : const CustomAppBar(title:'Create User '), drawer: isDesktop ? null : CustomDrawer(isDesktop: false), body: Container( color: Colors.white, child: Row( children: [ if(isDesktop) CustomDrawer(isDesktop: true), // const Expanded(child: Center(child: Text("User Details Content"))), Expanded(child: buildUserTable(isDesktop)), ], ), ), ); }); } Widget buildUserTable(bool isDesktop){ return Container( margin: const EdgeInsets.only(left: 10.0, right: 15.0, top: 10.0,bottom: 10.0), decoration: BoxDecoration( border: Border.all(color: Color(0xFFF7F7FB),width: 3.5) ), child: Column( children: [ Container( color: Color(0xFFF7F7FB), child: Padding( padding: const EdgeInsets.all(8.0), child: Row(children: [ Text(apiselectedUser != null ? "Profile": "New User", style: TextStyle(fontSize: 18,fontWeight: FontWeight.w200, color: Colors.black ),), if(isEditProfile) IconButton( icon: Icon(Icons.edit, color: Colors.blueAccent,size: 18,), onPressed:(){ setState(() { isViewMode = !isViewMode; }); } ) ], ), ), ), SizedBox(height: 15 ,), Expanded( child: SingleChildScrollView( scrollDirection: Axis.vertical, child: _buildUserDetails(isDesktop), // Ensure this returns a scrollable widget ), ), Container( color: Colors.white, child: Padding( padding: const EdgeInsets.all(18.0), child: isDesktop ? Row( mainAxisAlignment: MainAxisAlignment.end, children: _buildSubmit(isDesktop),) :Row( mainAxisAlignment: MainAxisAlignment.center, children: _buildSubmit(isDesktop),) ), ) ], ), ); } Widget _buildUserDetails(bool isDesktop){ return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Padding( padding: const EdgeInsets.all(8.0), child: Row( children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row(children: [ Icon(Icons.filter_1_outlined, color: Color(0xFF8B8FB2),size: 18,), SizedBox(width: 10,), Text("Personal Details", style: TextStyle(fontSize: 18,fontWeight: FontWeight.bold, // color: Colors.black, color: Color(0xFF8B8FB2) ), ) ],), SizedBox(height: 20,), Padding( padding: const EdgeInsets.only(left:16.0), // padding: const EdgeInsets.all(8.0), // color: Colors.grey, child: SizedBox( width: isDesktop? MediaQuery.of(context).size.width *0.7: null, // height: isDesktop? null :MediaQuery.of(context).size.height , child: isDesktop ? Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Expanded(child: _buildFirstRowLeftColumn(isDesktop)), SizedBox(width: 20), Expanded(child: _buildFirstRowRightColumn(isDesktop)), ], ) : Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ _buildFirstRowLeftColumn(isDesktop), SizedBox(height: 1), _buildFirstRowRightColumn(isDesktop), ], ), ), ), ],), ], ), ), // SizedBox( // child: Expanded( // child: Container( // margin: const EdgeInsets.only(left: 10.0, right: 15.0), // child: Divider( // color: Color(0xFF8B8FB2), // thickness: 0.5, // height: 50, // ), // ), // ), // ), SizedBox( width: double.infinity, // Ensures the divider takes full width child: Stack( alignment: Alignment.center, // Centers the icon children: [ Divider( color: Color(0xFF8B8FB2), thickness: 0.5, height: 50, ), Container( // padding: EdgeInsets.all(4), color: Colors.white, // Background to avoid overlapping child: Row( mainAxisSize: MainAxisSize.min, // Prevents row from taking full width children: [ Icon(Icons.keyboard_arrow_right_outlined, color: Colors.blue, size: 28), SizedBox(width: 1), Icon(Icons.keyboard_arrow_right_outlined, color: Colors.blue, size: 28), SizedBox(width: 1), Icon(Icons.keyboard_arrow_right_outlined, color: Colors.blue, size: 28), SizedBox(width: 1), ], ), ), ], ), ), Padding( padding: const EdgeInsets.all(8.0), child: Row( children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row(children: [ Icon(Icons.filter_2_outlined, color: Color(0xFF8B8FB2),size: 18,), SizedBox(width: 10,), Text("Passport Details", style: TextStyle(fontSize: 18,fontWeight: FontWeight.bold, // color: Colors.black, color: Color(0xFF8B8FB2) ), ) ],), SizedBox(height: 20,), Padding( padding: const EdgeInsets.only(left:16.0), child: SizedBox( width: isDesktop? MediaQuery.of(context).size.width *0.7: null, // height: isDesktop? null :MediaQuery.of(context).size.height , child: isDesktop ? Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded(child: _buildSecondRowLeftColumn(isDesktop)), SizedBox(width: 20), Expanded(child: _buildSecondRowRightColumn(isDesktop)), ], ) : Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ _buildSecondRowLeftColumn(isDesktop), SizedBox(height: 1), _buildSecondRowRightColumn(isDesktop), ], ), ), ), ],), ], ), ), SizedBox( width: double.infinity, // Ensures the divider takes full width child: Stack( alignment: Alignment.center, // Centers the icon children: [ Divider( color: Color(0xFF8B8FB2), thickness: 0.5, height: 50, ), Container( // padding: EdgeInsets.all(4), color: Colors.white, // Background to avoid overlapping child: Row( mainAxisSize: MainAxisSize.min, // Prevents row from taking full width children: [ Icon(Icons.keyboard_arrow_right_outlined, color: Colors.blue, size: 28), SizedBox(width: 1), Icon(Icons.keyboard_arrow_right_outlined, color: Colors.blue, size: 28), SizedBox(width: 1), Icon(Icons.keyboard_arrow_right_outlined, color: Colors.blue, size: 28), SizedBox(width: 1), ], ), ), ], ), ), Padding( padding: const EdgeInsets.all(8.0), child: Row( children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row(children: [ Icon(Icons.filter_3_outlined, color: Color(0xFF8B8FB2),size: 18,), SizedBox(width: 10,), Text("Employee Organization Details", style: TextStyle(fontSize: 18,fontWeight: FontWeight.bold, // color: Colors.black, color: Color(0xFF8B8FB2) ), ) ],), SizedBox(height: 20,), Padding( padding: const EdgeInsets.only(left:16.0), child: SizedBox( width: isDesktop? MediaQuery.of(context).size.width *0.7: null, // height: isDesktop? null :MediaQuery.of(context).size.height , child: isDesktop ? Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded(child: _buildThirdRowLeftColumn(isDesktop)), SizedBox(width: 20), Expanded(child: _buildThirdRowRightColumn(isDesktop)), ], ) : Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ _buildThirdRowLeftColumn(isDesktop), SizedBox(height: 1), _buildThirdRowRightColumn(isDesktop), ], ), ), ), ],), ], ), ), SizedBox( width: double.infinity, // Ensures the divider takes full width child: Stack( alignment: Alignment.center, // Centers the icon children: [ Divider( color: Color(0xFF8B8FB2), thickness: 0.5, height: 50, ), Container( // padding: EdgeInsets.all(4), color: Colors.white, // Background to avoid overlapping child: Row( mainAxisSize: MainAxisSize.min, // Prevents row from taking full width children: [ Icon(Icons.keyboard_arrow_right_outlined, color: Colors.blue, size: 28), SizedBox(width: 1), Icon(Icons.keyboard_arrow_right_outlined, color: Colors.blue, size: 28), SizedBox(width: 1), Icon(Icons.keyboard_arrow_right_outlined, color: Colors.blue, size: 28), SizedBox(width: 1), ], ), ), ], ), ), Padding( padding: const EdgeInsets.all(8.0), child: Row( children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row(children: [ Icon(Icons.filter_4_outlined, color: Color(0xFF8B8FB2),size: 18,), SizedBox(width: 10,), Text("Approver", style: TextStyle(fontSize: 18,fontWeight: FontWeight.bold, // color: Colors.black, color: Color(0xFF8B8FB2) ), ) ],), SizedBox(height: 20,), Padding( padding: const EdgeInsets.only(left:16.0), child: SizedBox( width: isDesktop? MediaQuery.of(context).size.width *0.7: null, // height: isDesktop? null :MediaQuery.of(context).size.height , child: isDesktop ? Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded(child: _buildForthRowLeftColumn(isDesktop)), SizedBox(width: 20), Expanded(child: _buildForthRowRightColumn(isDesktop)), SizedBox(width: 20), Expanded(child: _buildForthRowLastColumn(isDesktop)), ], ) : Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ _buildForthRowLeftColumn(isDesktop), SizedBox(height: 1), _buildForthRowRightColumn(isDesktop), SizedBox(height: 1), _buildForthRowLastColumn(isDesktop), ], ), ), ), ],), ], ), ), SizedBox( width: double.infinity, // Ensures the divider takes full width child: Stack( alignment: Alignment.center, // Centers the icon children: [ Divider( color: Color(0xFF8B8FB2), thickness: 0.5, height: 50, ), Container( // padding: EdgeInsets.all(4), color: Colors.white, // Background to avoid overlapping child: Row( mainAxisSize: MainAxisSize.min, // Prevents row from taking full width children: [ Icon(Icons.keyboard_arrow_right_outlined, color: Colors.blue, size: 28), SizedBox(width: 1), Icon(Icons.keyboard_arrow_right_outlined, color: Colors.blue, size: 28), SizedBox(width: 1), Icon(Icons.keyboard_arrow_right_outlined, color: Colors.blue, size: 28), SizedBox(width: 1), ], ), ), ], ), ), ], ); } //--------------------------------- Personal Details --------------------------- Widget _buildFirstRowLeftColumn(bool isDesktop){ DateTime? _selectedDateOfBirth; Future _selectCheckDateOfBirth(BuildContext context) async { DateTime now = DateTime.now(); DateTime today = DateTime(now.year, now.month, now.day); DateTime? pickedDate = await showDatePicker( context: context, initialDate: _selectedDateOfBirth != null && _selectedDateOfBirth!.isAfter(today) ? _selectedDateOfBirth! : today, firstDate: today, lastDate: DateTime(2100), ); if (pickedDate != null && pickedDate != _selectedDateOfBirth) { setState(() { _selectedDateOfBirth = pickedDate; controllers["dob"]?.text = DateFormat('yyyy-MM-dd').format(pickedDate); }); } } return Container( // color: Colors.yellow, color: Colors.white, child: Expanded( // Allow first column to take available space child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text("First Name", style: TextStyle(fontSize: 15, fontWeight: FontWeight.w200, color: Colors.black)), SizedBox(height: 5), CustomTextFieldUserWrapper( isFocused: false, isDesktop: isDesktop, child: SizedBox( height: 40, child: TextField( style: TextStyle(fontSize: 12), controller: controllers["Fname"], enabled: !isViewMode, onChanged: (value) {_clearError("first_name");}, decoration: InputDecoration( labelText: "FirstName", labelStyle: TextStyle(fontSize: 12, color: Colors.grey), floatingLabelBehavior: FloatingLabelBehavior.never, border: InputBorder.none, contentPadding: EdgeInsets.symmetric(vertical: 16), ), ), ), ), if (errorMessages["first_name"] != null) ...[ SizedBox(height: 5), // Space before error message Text( errorMessages["first_name"]!, style: TextStyle(color: Colors.red, fontSize: 12), ), ], ], ), ], ), SizedBox(height: 3), Row( children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text("Last Name", style: TextStyle(fontSize: 15, fontWeight: FontWeight.w200, color: Colors.black)), SizedBox(height: 5), CustomTextFieldUserWrapper( isFocused: false, isDesktop: isDesktop, child: SizedBox( height: 40, child: TextField( style: TextStyle(fontSize: 12), controller: controllers["Lname"], enabled: !isViewMode, onChanged: (value) {_clearError("last_name");}, decoration: InputDecoration( labelText: "LastName", labelStyle: TextStyle(fontSize: 12, color: Colors.grey), floatingLabelBehavior: FloatingLabelBehavior.never, border: InputBorder.none, contentPadding: EdgeInsets.symmetric(vertical: 16), ), ), ), ), if (errorMessages["last_name"] != null) ...[ SizedBox(height: 5), // Space before error message Text( errorMessages["last_name"]!, style: TextStyle(color: Colors.red, fontSize: 12), ), ], ], ), ], ), SizedBox(height: 3), Row( children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text("Gender", style: TextStyle(fontSize: 15, fontWeight: FontWeight.w200, color: Colors.black)), SizedBox(height: 5), CustomTextFieldUserWrapper( isFocused: false, isDesktop: isDesktop, child: SizedBox( height: 40, child: DropdownButtonFormField( value: selectedGender, onChanged: isViewMode ? null : (String? newValue) { setState(() { selectedGender = newValue; print("selectedGender - $selectedGender"); }); }, decoration: InputDecoration( border: InputBorder.none, enabled: !isViewMode, // Disables input when in view mode ), style: TextStyle(fontSize: 12), items: [ DropdownMenuItem(value: "Male", child: Text("Male")), DropdownMenuItem(value: "Female", child: Text("Female")), ], hint : Text( selectedGender ?? "Select Gender", style: TextStyle(fontSize: 12),), ), // child: DropdownSearch( // selectedItem: selectedGender, // // key: ValueKey(selectedGender), // popupProps: PopupProps.menu( // fit: FlexFit.loose, // Allows flexible height // constraints: BoxConstraints(maxHeight: 250), // // ), // items: ["Male", "Female", "Others"], // dropdownDecoratorProps: DropDownDecoratorProps( // dropdownSearchDecoration: InputDecoration( // border: InputBorder.none, // contentPadding: EdgeInsets.symmetric(horizontal: 1,), // ), // ), // dropdownBuilder: (context, selectedItem) => Align( // Center-align selected item // alignment: Alignment.centerLeft, // child: Text( // selectedItem ?? "Select", // style: TextStyle(fontSize: 12), // ), // ), // onChanged: isViewMode // ? null // : (String? newValue) { // setState(() { // // Find the country_code based on selected country_name // selectedGender = newValue; // print("selectedGender - $selectedGender"); // // if (selectedCountry!.isNotEmpty) { // // errorMessages.remove("country_code"); // // } // }); // }, // // // ), ), ), ], ), ], ), SizedBox(height: 3), Row( children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text("Date of Birth", style: TextStyle(fontSize: 15, fontWeight: FontWeight.w200, color: Colors.black)), SizedBox(height: 5), CustomTextFieldUserWrapper( isFocused: false, isDesktop: isDesktop, child: SizedBox( height: 40, child: GestureDetector( onTap: isViewMode ? null :()async{ await _selectCheckDateOfBirth(context); if (controllers["dob"]!.text.isNotEmpty) { setState(() { // errorMessages.remove("start_date"); }); } }, child: AbsorbPointer( child: TextField( controller: controllers["dob"], style: const TextStyle(fontSize: 12), decoration: const InputDecoration( labelText: "Select Date", labelStyle: TextStyle(fontSize: 12, color: Colors.grey), floatingLabelBehavior: FloatingLabelBehavior.never, border: InputBorder.none, contentPadding: EdgeInsets.symmetric(vertical: 16), suffixIcon: Icon(Icons.calendar_today, size: 16, color: Color(0xFF8B8FB2) ), ), ), ), ), ), ), ], ), ], ), SizedBox(height: 3), Row( children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text("Email", style: TextStyle(fontSize: 15, fontWeight: FontWeight.w200, color: Colors.black)), SizedBox(height: 5), CustomTextFieldUserWrapper( isFocused: false, isDesktop: isDesktop, child: SizedBox( height: 40, child: TextField( style: TextStyle(fontSize: 12), controller: controllers["email"], onChanged: (value) {_clearError("email");}, enabled: !isViewMode, decoration: InputDecoration( labelText: "Email", labelStyle: TextStyle(fontSize: 12, color: Colors.grey), floatingLabelBehavior: FloatingLabelBehavior.never, border: InputBorder.none, contentPadding: EdgeInsets.symmetric(vertical: 16), ), ), ), ), if (errorMessages["email"] != null) ...[ SizedBox(height: 5), // Space before error message Text( errorMessages["email"]!, style: TextStyle(color: Colors.red, fontSize: 12), ), ], ], ), ], ), SizedBox(height: 3), apiselectedUser != null? Row( children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text("Change Password", style: TextStyle(fontSize: 15, fontWeight: FontWeight.w200, color: Colors.black)), SizedBox(height: 5), ], ), ], ): Row( children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text("Password", style: TextStyle(fontSize: 15, fontWeight: FontWeight.w200, color: Colors.black)), SizedBox(height: 5), CustomTextFieldUserWrapper( isFocused: false, isDesktop: isDesktop, child: SizedBox( height: 40, child: TextField( style: TextStyle(fontSize: 12), controller: controllers["password"], enabled: !isViewMode, onChanged: (value) {_clearError("password");}, decoration: InputDecoration( labelText: "Password", labelStyle: TextStyle(fontSize: 12, color: Colors.grey), floatingLabelBehavior: FloatingLabelBehavior.never, border: InputBorder.none, contentPadding: EdgeInsets.symmetric(vertical: 16), ), ), ), ), if (errorMessages["password"] != null) ...[ SizedBox(height: 5), // Space before error message Text( errorMessages["password"]!, style: TextStyle(color: Colors.red, fontSize: 12), ), ], ], ), ], ), if(!isDesktop) SizedBox(height: 3,), ], ), ), ); } Widget _buildFirstRowRightColumn(bool isDesktop){ late Map countryMap; // Mapping country_code -> country_name late List countryCodes; // List of country codes List countryList = apiCountryData ?? []; countryList = apiCountryData ?? []; // Map country codes to country names countryMap = { for (var item in countryList) item['country_code'] as String: item['country_name'] as String }; // Extract only country codes for processing countryCodes = countryMap.keys.toList(); selectedCountry ??= null; return Container( // color: Colors.yellow, color: Colors.white, child: Expanded( // Allow second column to take available space child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text("Mobile Number", style: TextStyle(fontSize: 15, fontWeight: FontWeight.w200, color: Colors.black)), SizedBox(height: 5), CustomTextFieldUserWrapper( isFocused: false, isDesktop: isDesktop, child: SizedBox( height: 40, child: TextField( style: TextStyle(fontSize: 12), controller: controllers["mobileNumber"], enabled: !isViewMode, onChanged: (value) {_clearError("mobile_no");}, keyboardType: TextInputType.numberWithOptions(decimal: true), inputFormatters: [ FilteringTextInputFormatter.allow(RegExp(r'^\d*\.?\d*$')), // Allow only positive numbers with optional decimal ], decoration: InputDecoration( labelText: "Mobile Number", labelStyle: TextStyle(fontSize: 12, color: Colors.grey), floatingLabelBehavior: FloatingLabelBehavior.never, border: InputBorder.none, contentPadding: EdgeInsets.symmetric(vertical: 16), ), ), ), ), if (errorMessages["mobile_no"] != null) ...[ SizedBox(height: 5), // Space before error message Text( errorMessages["mobile_no"]!, style: TextStyle(color: Colors.red, fontSize: 12), ), ], ], ), ], ), SizedBox(height: 3), Row( children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text("Alternate Mobile Number", style: TextStyle(fontSize: 15, fontWeight: FontWeight.w200, color: Colors.black)), SizedBox(height: 5), CustomTextFieldUserWrapper( isFocused: false, isDesktop: isDesktop, child: SizedBox( height: 40, child: TextField( style: TextStyle(fontSize: 12), controller: controllers["alternateMobile"], enabled: !isViewMode, keyboardType: TextInputType.numberWithOptions(decimal: true), inputFormatters: [ FilteringTextInputFormatter.allow(RegExp(r'^\d*\.?\d*$')), // Allow only positive numbers with optional decimal ], decoration: InputDecoration( labelText: "Alternate Number", labelStyle: TextStyle(fontSize: 12, color: Colors.grey), floatingLabelBehavior: FloatingLabelBehavior.never, border: InputBorder.none, contentPadding: EdgeInsets.symmetric(vertical: 16), ), ), ), ), if (errorMessages["alternate_mobile_no"] != null) ...[ SizedBox(height: 5), // Space before error message Text( errorMessages["alternate_mobile_no"]!, style: TextStyle(color: Colors.red, fontSize: 12), ), ], ], ), ], ), SizedBox(height: 3), Row( children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text("Address", style: TextStyle(fontSize: 15, fontWeight: FontWeight.w200, color: Colors.black)), SizedBox(height: 5), CustomTextFieldUserWrapper( isFocused: false, isDesktop: isDesktop, child: SizedBox( height: 110, child: TextField( style: TextStyle(fontSize: 12), controller: controllers["address"], enabled: !isViewMode, maxLines: 4, keyboardType: TextInputType.multiline, decoration: InputDecoration( labelText: "Address", labelStyle: TextStyle(fontSize: 12, color: Colors.grey), floatingLabelBehavior: FloatingLabelBehavior.never, border: InputBorder.none, // contentPadding: EdgeInsets.symmetric(vertical: 16), contentPadding: EdgeInsets.symmetric(vertical: 4), ), ), ), ), ], ), ], ), SizedBox(height: 3), Row( children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text("Postal Code", style: TextStyle(fontSize: 15, fontWeight: FontWeight.w200, color: Colors.black)), SizedBox(height: 5), CustomTextFieldUserWrapper( isFocused: false, isDesktop: isDesktop, child: SizedBox( height: 40, child: TextField( style: TextStyle(fontSize: 12), controller: controllers["postalCode"], enabled: !isViewMode, keyboardType: TextInputType.numberWithOptions(decimal: true), inputFormatters: [ FilteringTextInputFormatter.allow(RegExp(r'^\d*\.?\d*$')), // Allow only positive numbers with optional decimal ], decoration: InputDecoration( labelText: "Postal Code", labelStyle: TextStyle(fontSize: 12, color: Colors.grey), floatingLabelBehavior: FloatingLabelBehavior.never, border: InputBorder.none, contentPadding: EdgeInsets.symmetric(vertical: 16), ), ), ), ), ], ), ], ), SizedBox(height: 3), Row( children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text("Country", style: TextStyle(fontSize: 15, fontWeight: FontWeight.w200, color: Colors.black)), SizedBox(height: 5), CustomTextFieldUserWrapper( isFocused: false, isDesktop: isDesktop, child: SizedBox( height: 40, child: DropdownSearch( selectedItem: countryMap[selectedCountry], enabled: !isViewMode, popupProps: PopupProps.menu( showSearchBox: true, // Enables search functionality fit: FlexFit.loose, // Allows flexible height constraints: BoxConstraints(maxHeight: 250), searchFieldProps: TextFieldProps( decoration: InputDecoration( hintText: "Search Country...", contentPadding: EdgeInsets.symmetric(horizontal: 10), ), ), ), items: countryMap.values.toList(), dropdownDecoratorProps: DropDownDecoratorProps( dropdownSearchDecoration: InputDecoration( border: InputBorder.none, contentPadding: EdgeInsets.symmetric(horizontal: 1,), ), ), dropdownBuilder: (context, selectedItem) => Align( // Center-align selected item alignment: Alignment.centerLeft, child: Text( selectedItem ?? "Select Country", style: TextStyle(fontSize: 12), ), ), onChanged: isViewMode ? null: (String? newValue) { setState(() { // Find the country_code based on selected country_name selectedCountry = countryMap.entries .firstWhere((entry) => entry.value == newValue) .key; // if (selectedCountry!.isNotEmpty) { // errorMessages.remove("country_code"); // } }); }, ), ), ), ], ), ], ), if(!isDesktop) SizedBox(height: 3,), ], ), ), ); } //--------------------------------- Passport Details ----------------------------- Widget _buildSecondRowLeftColumn(bool isDesktop){ return Container( color: Colors.white, child: Expanded( // Allow first column to take available space child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text("Passport Number", style: TextStyle(fontSize: 15, fontWeight: FontWeight.w200, color: Colors.black)), SizedBox(height: 5), CustomTextFieldUserWrapper( isFocused: false, isDesktop: isDesktop, child: SizedBox( height: 40, child: TextField( style: TextStyle(fontSize: 12), controller: controllers["passportNumber"], enabled: !isViewMode, decoration: InputDecoration( labelText: "Passport Number", labelStyle: TextStyle(fontSize: 12, color: Colors.grey), floatingLabelBehavior: FloatingLabelBehavior.never, border: InputBorder.none, contentPadding: EdgeInsets.symmetric(vertical: 16), ), ), ), ), ], ), ], ), SizedBox(height: 3,), Row( children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text("Passport Document", style: TextStyle(fontSize: 15, fontWeight: FontWeight.w200, color: Colors.black)), SizedBox(height: 5), CustomTextFieldUserWrapper( isFocused: false, isDesktop: isDesktop, child: SizedBox( height: 40, child: GestureDetector( onTap: pickPDFWeb, child: Container( padding: EdgeInsets.symmetric(horizontal: 10), alignment: Alignment.centerLeft, child: Text( selectedFileNames?? "Select Document", style: TextStyle(fontSize: 12, color: Colors.black), overflow: TextOverflow.ellipsis, // Prevents overflow issues ), ), ), ), ), SizedBox(height: 10), if(base64PDF != null) // Centers the text Container( color: Colors.white, width : isDesktop ? MediaQuery.of(context).size.width * 0.25 : MediaQuery.of(context).size.width * 0.8, child: Row( mainAxisAlignment: MainAxisAlignment.end, crossAxisAlignment: CrossAxisAlignment.end, children: [ GestureDetector(onTap: (){ print('DOWLOAS- $base64PDF '); if (base64PDF != null && base64PDF!.isNotEmpty) { try { // ✅ Step 1: Clean the Base64 string String cleanedBase64 = base64PDF! .replaceAll("\n", "") // Remove newlines .replaceAll("\r", "") // Remove carriage returns .replaceAll(" ", "") // Remove spaces .trim(); // Trim any whitespace // ✅ Step 2: Ensure valid Base64 length (multiple of 4) while (cleanedBase64.length % 4 != 0) { cleanedBase64 += "_"; // Add '=' padding } // ✅ Step 3: Decode the cleaned Base64 Uint8List bytes; try { bytes = base64Decode(cleanedBase64); } catch (e) { print("Base64 decoding failed: $e"); return; } // ✅ Step 4: Create a Blob for download final blob = html.Blob([bytes], 'application/pdf'); final url = html.Url.createObjectUrlFromBlob(blob); // ✅ Step 5: Trigger the file download final anchor = html.AnchorElement(href: url) ..setAttribute("download", selectedFileNames ?? "document.pdf") ..style.display = "none"; html.document.body!.append(anchor); anchor.click(); // ✅ Step 6: Clean up anchor.remove(); html.Url.revokeObjectUrl(url); print("Download successful!"); } catch (e) { print("Error downloading file: $e"); } } else { print("No file to download."); } } , child: Container( padding: const EdgeInsets.all(5), decoration: BoxDecoration( borderRadius: BorderRadius.circular(5), color: Colors.green.shade300, ), child: Row( mainAxisAlignment: MainAxisAlignment.end, crossAxisAlignment: CrossAxisAlignment.end, children: [ Text( "Download", style: TextStyle(fontSize: 12, color: Colors.white,fontWeight: FontWeight.bold), overflow: TextOverflow.ellipsis, textAlign: TextAlign.end, // Ensures text is centered within the Text widget ), SizedBox(width: 5), Icon(Icons.download ,size: 13, color: Colors.white,) ], ), ), ) ], ), ), // CustomTextFieldUserWrapper( // isFocused: false, // isDesktop: isDesktop, // child: SizedBox( // height: 40, // child: TextField( // style: TextStyle(fontSize: 12), // controller: controllers["passportDoc"], // decoration: InputDecoration( // labelText: "Select Document", // labelStyle: TextStyle(fontSize: 12, color: Colors.grey), // floatingLabelBehavior: FloatingLabelBehavior.never, // border: InputBorder.none, // contentPadding: EdgeInsets.symmetric(vertical: 16), // ), // ), // ), // ), ], ), ], ), if(!isDesktop) SizedBox(height: 3,), ], ), ), ); } Widget _buildSecondRowRightColumn(bool isDesktop){ DateTime? _selectedDateOfIssue; Future _selectCheckDateOfIssue(BuildContext context) async { DateTime now = DateTime.now(); DateTime today = DateTime(now.year, now.month, now.day); DateTime? pickedDate = await showDatePicker( context: context, initialDate: _selectedDateOfIssue != null && _selectedDateOfIssue!.isAfter(today) ? _selectedDateOfIssue! : today, firstDate: today, lastDate: DateTime(2100), ); if (pickedDate != null && pickedDate != _selectedDateOfIssue) { setState(() { _selectedDateOfIssue = pickedDate; controllers["dateOfIssue"]?.text = DateFormat('yyyy-MM-dd').format(pickedDate); }); } } DateTime? _selectedDateOfExpiry; Future _selectCheckDateOfExpiry(BuildContext context) async { DateTime now = DateTime.now(); DateTime today = DateTime(now.year, now.month, now.day); DateTime? pickedDate = await showDatePicker( context: context, initialDate: _selectedDateOfExpiry != null && _selectedDateOfExpiry!.isAfter(today) ? _selectedDateOfExpiry! : today, firstDate: today, lastDate: DateTime(2100), ); if (pickedDate != null && pickedDate != _selectedDateOfExpiry) { setState(() { _selectedDateOfExpiry = pickedDate; controllers["dateOfExpiry"]?.text = DateFormat('yyyy-MM-dd').format(pickedDate); }); } } return Container( color: Colors.white, child: Expanded( // Allow second column to take available space child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text("Place of Issue", style: TextStyle(fontSize: 15, fontWeight: FontWeight.w200, color: Colors.black)), SizedBox(height: 5), CustomTextFieldUserWrapper( isFocused: false, isDesktop: isDesktop, child: SizedBox( height: 40, child: TextField( style: TextStyle(fontSize: 12), controller: controllers["placeOfIssue"], enabled: !isViewMode, decoration: InputDecoration( labelText: "Place Of Issue", labelStyle: TextStyle(fontSize: 12, color: Colors.grey), floatingLabelBehavior: FloatingLabelBehavior.never, border: InputBorder.none, contentPadding: EdgeInsets.symmetric(vertical: 16), ), ), ), ), ], ), ], ), SizedBox(height: 3,), Row( children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text("Date of Issue", style: TextStyle(fontSize: 15, fontWeight: FontWeight.w200, color: Colors.black)), SizedBox(height: 5), CustomTextFieldUserWrapper( isFocused: false, isDesktop: isDesktop, child: SizedBox( height: 40, child: GestureDetector( onTap: isViewMode? null : ()async{ await _selectCheckDateOfIssue(context); if (controllers["dateOfIssue"]!.text.isNotEmpty) { setState(() { // errorMessages.remove("start_date"); }); } }, child: AbsorbPointer( child: TextField( controller: controllers["dateOfIssue"], style: const TextStyle(fontSize: 12), decoration: const InputDecoration( labelText: "Select Date", labelStyle: TextStyle(fontSize: 12, color: Colors.grey), floatingLabelBehavior: FloatingLabelBehavior.never, border: InputBorder.none, contentPadding: EdgeInsets.symmetric(vertical: 16), suffixIcon: Icon(Icons.calendar_today, size: 16, color: Color(0xFF8B8FB2) ), ), ), ), ), ), ), ], ), ], ), SizedBox(height: 3,), Row( children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text("Date of Expiry", style: TextStyle(fontSize: 15, fontWeight: FontWeight.w200, color: Colors.black)), SizedBox(height: 5), CustomTextFieldUserWrapper( isFocused: false, isDesktop: isDesktop, child: SizedBox( height: 40, child: GestureDetector( onTap: isViewMode? null : ()async{ await _selectCheckDateOfExpiry(context); if (controllers["dateOfExpiry"]!.text.isNotEmpty) { setState(() { // errorMessages.remove("start_date"); }); } }, child: AbsorbPointer( child: TextField( controller: controllers["dateOfExpiry"], style: const TextStyle(fontSize: 12), decoration: const InputDecoration( labelText: "Select Date", labelStyle: TextStyle(fontSize: 12, color: Colors.grey), floatingLabelBehavior: FloatingLabelBehavior.never, border: InputBorder.none, contentPadding: EdgeInsets.symmetric(vertical: 16), suffixIcon: Icon(Icons.calendar_today, size: 16, color: Color(0xFF8B8FB2) ), ), ), ), ), ), ), ], ), ], ), if(!isDesktop) SizedBox(height: 3,), ], ), ), ); } //----------------------------------- Employee Organization Details ------------------ Widget _buildThirdRowLeftColumn(bool isDesktop){ return Container( color: Colors.white, child: Expanded( // Allow first column to take available space child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text("Employee Code", style: TextStyle(fontSize: 15, fontWeight: FontWeight.w200, color: Colors.black)), SizedBox(height: 5), CustomTextFieldUserWrapper( isFocused: false, isDesktop: isDesktop, child: SizedBox( height: 40, child: TextField( style: TextStyle(fontSize: 12), controller: controllers["employeeCode"], enabled: !isViewMode, decoration: InputDecoration( labelText: "Employee Code", labelStyle: TextStyle(fontSize: 12, color: Colors.grey), floatingLabelBehavior: FloatingLabelBehavior.never, border: InputBorder.none, contentPadding: EdgeInsets.symmetric(vertical: 16), ), ), ), ), ], ), ], ), SizedBox(height: 3), Row( children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text("User Type", style: TextStyle(fontSize: 15, fontWeight: FontWeight.w200, color: Colors.black)), SizedBox(height: 5), CustomTextFieldUserWrapper( isFocused: false, isDesktop: isDesktop, child: SizedBox( height: 40, child: DropdownSearch( selectedItem: selectedUserType, enabled: !isViewMode, popupProps: PopupProps.menu( // showSearchBox: true, fit: FlexFit.loose, // Allows flexible height constraints: BoxConstraints(maxHeight: 250), ), items: ["Normal", "Privilege",], dropdownDecoratorProps: DropDownDecoratorProps( dropdownSearchDecoration: InputDecoration( border: InputBorder.none, contentPadding: EdgeInsets.symmetric(horizontal: 1,), ), ), dropdownBuilder: (context, selectedItem) => Align( // Center-align selected item alignment: Alignment.centerLeft, child: Text( selectedItem ?? "Select", style: TextStyle(fontSize: 12), ), ), onChanged: (String? newValue) { setState(() { // Find the country_code based on selected country_name selectedUserType = newValue; // print("selectedUserType - $selectedUserType"); // if (selectedCountry!.isNotEmpty) { // errorMessages.remove("country_code"); // } }); }, ), ), ), ], ), ], ), if(!isDesktop) SizedBox(height: 3), ], ), ), ); } Widget _buildThirdRowRightColumn(bool isDesktop){ return Container( color: Colors.white, child: Expanded( // Allow second column to take available space child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text("Role ", style: TextStyle(fontSize: 15, fontWeight: FontWeight.w200, color: Colors.black)), SizedBox(height: 5), CustomTextFieldUserWrapper( isFocused: false, // Dropdown doesn't use focus isDesktop: isDesktop, child: SizedBox( height: 45, // Set appropriate height child: DropdownButtonFormField( value: selectedRole, style: TextStyle(fontSize: 12), decoration: InputDecoration( border: InputBorder.none, contentPadding: EdgeInsets.symmetric(horizontal: 10), // Proper padding ), onChanged: isViewMode ? null : (newValue) { setState(() { selectedRole = newValue; }); }, items:apiRoleData?.map>((item){ return DropdownMenuItem( value: item['dropdown_key'], // ID as value child:Text(item['dropdown_value'] ?? "Select") ); }).toList(), hint: Text("Select"), ), ), ), ], ), ], ), SizedBox(height: 3), Row( children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text("Group", style: TextStyle(fontSize: 15, fontWeight: FontWeight.w200, color: Colors.black)), SizedBox(height: 5), CustomTextFieldUserWrapper( isFocused: false, isDesktop: isDesktop, child: SizedBox( height: 40, child: DropdownSearch( selectedItem: selectedLevel, enabled: !isViewMode, popupProps: PopupProps.menu( // showSearchBox: true, fit: FlexFit.loose, // Allows flexible height constraints: BoxConstraints(maxHeight: 250), ), items: ["Level 1", "Level 2", "Level 3"], dropdownDecoratorProps: DropDownDecoratorProps( dropdownSearchDecoration: InputDecoration( border: InputBorder.none, contentPadding: EdgeInsets.symmetric(horizontal: 1,), ), ), dropdownBuilder: (context, selectedItem) => Align( // Center-align selected item alignment: Alignment.centerLeft, child: Text( selectedItem ?? "Select", style: TextStyle(fontSize: 12), ), ), onChanged: (String? newValue) { setState(() { // Find the country_code based on selected country_name selectedLevel = newValue; // if (selectedCountry!.isNotEmpty) { // errorMessages.remove("country_code"); // } }); }, ), ), ), ], ), ], ), SizedBox(height: 3), Row( children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text("Department", style: TextStyle(fontSize: 15, fontWeight: FontWeight.w200, color: Colors.black)), SizedBox(height: 5), CustomTextFieldUserWrapper( isFocused: false, // Dropdown doesn't use focus isDesktop: isDesktop, child: SizedBox( height: 45, // Set appropriate height child: DropdownButtonFormField( value: selectedDepartment, style: TextStyle(fontSize: 12), decoration: InputDecoration( border: InputBorder.none, contentPadding: EdgeInsets.symmetric(horizontal: 10), // Proper padding ), onChanged: isViewMode ? null : (newValue) { setState(() { selectedDepartment = newValue; }); }, items:apiCostData?.map>((item){ return DropdownMenuItem( value: item['department_id'], // ID as value child: Text(item['name'] ?? "Unknown"), ); }).toList(), hint: Text("Select"), ), ), ), ], ), ], ), ], ), ), ); } //----------------------------------- Approver --------------------------------------- Widget _buildForthRowLeftColumn(bool isDesktop){ return Container( color: Colors.white, child: Expanded( // Allow first column to take available space child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text("First Approver", style: TextStyle(fontSize: 15, fontWeight: FontWeight.w200, color: Colors.black)), SizedBox(height: 5), CustomTextFieldUserWrapper( isFocused: false, isDesktop: isDesktop, width: isDesktop? MediaQuery.of(context).size.width * 0.2 : MediaQuery.of(context).size.width * 0.8, child: SizedBox( height: 40, child: apiUserData == null ? Center(child: Transform.scale(scale: 0.5, child: CircularProgressIndicator(),),) : DropdownSearch( selectedItem: userMap[selectedFirstApprover], enabled: !isViewMode, popupProps: PopupProps.menu( showSearchBox: true, fit: FlexFit.loose, // Allows flexible height constraints: BoxConstraints(maxHeight: 250), searchFieldProps: TextFieldProps( decoration: InputDecoration( hintText: "Search User...", contentPadding: EdgeInsets.symmetric(horizontal: 10), ), ), ), items: apiUserData!.map((user) { return "${user['first_name']} ${user['last_name']}"; }).toList(), dropdownDecoratorProps: DropDownDecoratorProps( dropdownSearchDecoration: InputDecoration( border: InputBorder.none, contentPadding: EdgeInsets.symmetric(horizontal: 1,), ), ), dropdownBuilder: (context, selectedItem) => Align( // Center-align selected item alignment: Alignment.centerLeft, child: Text( selectedItem ?? "Select", style: TextStyle(fontSize: 12), ), ), onChanged: (String? newValue) { setState(() { selectedFirstApprover = userMap.entries .firstWhere((entry) => entry.value == newValue) .key; // if (selectedCountry!.isNotEmpty) { // errorMessages.remove("country_code"); // } }); }, ), ), ), ], ), ], ), ], ), ), ); } Widget _buildForthRowRightColumn(bool isDesktop){ return Container( color: Colors.white, child: Expanded( // Allow second column to take available space child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text("Second Approver", style: TextStyle(fontSize: 15, fontWeight: FontWeight.w200, color: Colors.black)), SizedBox(height: 5), CustomTextFieldUserWrapper( isFocused: false, isDesktop: isDesktop, width: isDesktop? MediaQuery.of(context).size.width * 0.2 : MediaQuery.of(context).size.width * 0.8, child: SizedBox( height: 40, child: apiUserData == null ? Center(child: Transform.scale(scale: 0.5, child: CircularProgressIndicator(),),) : DropdownSearch( selectedItem: userMap[selectedSecondApprover], enabled: !isViewMode, popupProps: PopupProps.menu( showSearchBox: true, fit: FlexFit.loose, // Allows flexible height constraints: BoxConstraints(maxHeight: 250), searchFieldProps: TextFieldProps( decoration: InputDecoration( hintText: "Search User...", contentPadding: EdgeInsets.symmetric(horizontal: 10), ), ), ), items: apiUserData!.map((user) { return "${user['first_name']} ${user['last_name']}"; }).toList(), dropdownDecoratorProps: DropDownDecoratorProps( dropdownSearchDecoration: InputDecoration( border: InputBorder.none, contentPadding: EdgeInsets.symmetric(horizontal: 1,), ), ), dropdownBuilder: (context, selectedItem) => Align( // Center-align selected item alignment: Alignment.centerLeft, child: Text( selectedItem ?? "Select", style: TextStyle(fontSize: 12), ), ), onChanged: (String? newValue) { setState(() { selectedSecondApprover = userMap.entries .firstWhere((entry) => entry.value == newValue) .key; // if (selectedCountry!.isNotEmpty) { // errorMessages.remove("country_code"); // } }); }, ), ), ), ], ), ], ), ], ), ), ); } Widget _buildForthRowLastColumn(bool isDesktop){ return Container( color: Colors.white, child: Expanded( // Allow second column to take available space child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text("Third Approver", style: TextStyle(fontSize: 15, fontWeight: FontWeight.w200, color: Colors.black)), SizedBox(height: 5), CustomTextFieldUserWrapper( isFocused: false, isDesktop: isDesktop, width: isDesktop? MediaQuery.of(context).size.width * 0.2 : MediaQuery.of(context).size.width * 0.8, child: SizedBox( height: 40, child: apiUserData == null ? Center(child: Transform.scale(scale: 0.5, child: CircularProgressIndicator(),),) : DropdownSearch( selectedItem: userMap[selectedThirdApprover], enabled: !isViewMode, popupProps: PopupProps.menu( showSearchBox: true, fit: FlexFit.loose, // Allows flexible height constraints: BoxConstraints(maxHeight: 250), searchFieldProps: TextFieldProps( decoration: InputDecoration( hintText: "Search User...", contentPadding: EdgeInsets.symmetric(horizontal: 10), ), ), ), items: apiUserData!.map((user) { return "${user['first_name']} ${user['last_name']}"; }).toList(), dropdownDecoratorProps: DropDownDecoratorProps( dropdownSearchDecoration: InputDecoration( border: InputBorder.none, contentPadding: EdgeInsets.symmetric(horizontal: 1,), ), ), dropdownBuilder: (context, selectedItem) => Align( // Center-align selected item alignment: Alignment.centerLeft, child: Text( selectedItem ?? "Select", style: TextStyle(fontSize: 12), ), ), onChanged: (String? newValue) { setState(() { selectedThirdApprover = userMap.entries .firstWhere((entry) => entry.value == newValue) .key; // if (selectedCountry!.isNotEmpty) { // errorMessages.remove("country_code"); // } }); }, ), ), ), ], ), ], ), ], ), ), ); } //----------------------------------- Submit --------------------------------------- List _buildSubmit(isDesktop){ return[ ElevatedButton( style: ElevatedButton.styleFrom( backgroundColor: Colors.white, foregroundColor: Colors.blueAccent, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), side: BorderSide(color: Colors.blueAccent, width: 2), ), padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12), ), onPressed: (){ context.go('/listUser'); }, child: Text("Cancel") ), SizedBox(width: 20,), MouseRegion( cursor: isViewMode ? SystemMouseCursors.forbidden : SystemMouseCursors.click, child: ElevatedButton( style: ElevatedButton.styleFrom( backgroundColor: isViewMode ? Colors.blueAccent : Colors.blueAccent, // Keep original color foregroundColor: isViewMode ? Colors.white : Colors.white, // Keep original color disabledBackgroundColor: Colors.blueAccent, // Ensure color remains when disabled disabledForegroundColor: Colors.white, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), side: BorderSide(color: Colors.blueAccent, width: 2), ), padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12), ), onPressed: isViewMode ? null : handleSubmit, // Disable when in view mode child: Text("Submit"), ), ) ]; } } class DatabaseHelper { // Example: Singleton pattern to ensure only one instance of the class exists static final DatabaseHelper _instance = DatabaseHelper._internal(); // Private constructor to prevent external instantiation DatabaseHelper._internal(); // Public method to access the instance static DatabaseHelper get instance => _instance; // Add your database methods here Future fetchData() async { // Implement data fetching logic } }