import 'dart:async'; 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:frontend/utils/auth_utils.dart'; import 'package:go_router/go_router.dart'; import 'package:http/http.dart' as http; import 'package:http_parser/http_parser.dart' as http_parser; import 'package:intl/intl.dart'; import 'package:responsive_builder/responsive_builder.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:http_parser/http_parser.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 CreateUserForm1 extends StatefulWidget { @override _CreateUserFormState createState() => _CreateUserFormState(); } class _CreateUserFormState extends State { final ApiService apiService = ApiService(); late TabController _tabController; String? userId; String? orgId; String? token; // Map to store controllers dynamically final Map controllers = {}; bool isViewMode = false; bool isEditProfile = false; // 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? passportFileUrlFromApi; String? base64PDF; html.File? passportFile; 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" ]; Color? layoutColor; Color? bodyColor; 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_code": 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": passportFile, "date_of_issue": controllers["dateOfIssue"]?.text, "date_of_expiry": controllers["dateOfExpiry"]?.text, "created_by": userId, "is_active": "1", "org_id": orgId, // "passport_fileData": passportFile, }; return data; } Future updateData() async { // Ensure apiselectedUser is not null before printing if (apiselectedUser != null) { print("API Selected User Has Data - $widget.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"] ?? ""; if (apiselectedUser?["date_of_issue"] != null) { controllers["dateOfIssue"]?.text = apiselectedUser?["date_of_issue"] ?? ""; } if (apiselectedUser?["date_of_expiry"] != null) { controllers["dateOfExpiry"]?.text = apiselectedUser?["date_of_expiry"] ?? ""; } if (apiselectedUser?["country_code"] != null) { selectedCountry = apiselectedUser?["country_code"]?.toString() ?? ""; } if (apiselectedUser?["gender"] != null) { selectedGender = apiselectedUser?["gender"]?.toString().trim() ?? ""; } if (apiselectedUser?["user_type"] != null) { selectedUserType = apiselectedUser?["user_type"]?.toString().trim() ?? ""; } if (apiselectedUser?["role_id"] != null) { selectedRole = apiselectedUser?["role_id"]?.toString().trim() ?? ""; } // selectedDepartment = // apiselectedUser?["department_id"]?.toString().trim() ?? ""; if (apiselectedUser?["department_id"] != null) { selectedDepartment = apiselectedUser!["department_id"].toString(); } // print( // "selectedDepartment - $selectedDepartment - ${apiselectedUser?["department_id"]} "); if (apiselectedUser?["level_id"] != null) { selectedLevel = apiselectedUser?["level_id"]?.toString().trim() ?? ""; } if (apiselectedUser?["first_approver"] != null) { selectedFirstApprover = apiselectedUser?["first_approver"]?.toString() ?? ""; } if (apiselectedUser?["second_approver"] != null) { selectedSecondApprover = apiselectedUser?["second_approver"]?.toString() ?? ""; } if (apiselectedUser?["third_approver"] != null) { selectedThirdApprover = apiselectedUser?["third_approver"]?.toString() ?? ""; } print("Updated selectedGender: $selectedGender"); // Debugging // ✅ Load passport document from API String? apiDocPath = apiselectedUser?["passport_document"]; if (apiDocPath != null && apiDocPath.isNotEmpty) { passportFileUrlFromApi = apiDocPath; selectedFileNames = apiDocPath.split('/').last; // Extract filename from path passportFile = null; // No local file selected yet } else { passportFileUrlFromApi = null; selectedFileNames = null; passportFile = null; } }); } else { print("API Selected User Has Data - No data available yet"); } } @override void initState() { super.initState(); WidgetsFlutterBinding.ensureInitialized(); // Step 1: Set 'reloaded' flag before page unload html.window.onBeforeUnload.listen((event) { html.window.localStorage['reloaded'] = 'true'; }); // apiCountryData = extraData['apiCountryData']; // Extract apiCountryData // futureUsers = extraData['apiUserData']; // Extract futureUsers (Future>) apiCountryData = null; apiUserData = null; // apiselectedUser = null; apiCostData = null; apiRoleData = null; // Initialize controllers for each field WidgetsBinding.instance.addPostFrameCallback((_) async { final wasReloaded = html.window.localStorage['reloaded'] == 'true'; if (wasReloaded) { html.window.localStorage.remove('reloaded'); // Clear it context.go('/listUser'); // Navigate using go_router } 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; }); print("selectedUser: $apiselectedUser"); // Add another post-frame callback to check after setState // await Future.delayed(Duration( // milliseconds: 100)); // Optional delay to ensure UI has updated updateData(); } initializeData(); fetchCountries(); fetchDepartment(); fetchUsers(); fetchRoles(); loadInitialData(); }); for (var field in dataHeader) { controllers[field] = TextEditingController(); } } 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"] == "4").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}"); } } void loadInitialData() async { String? layoutString = await getLayoutColor(); String? bodyStringColor = await getBodyColor(); setState(() { layoutColor = layoutString != null ? Color(int.parse(layoutString)) : Colors.redAccent; bodyColor = bodyStringColor != null ? Color(int.parse(bodyStringColor)) : Colors.white; }); } 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() async { 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"); orgId = await getOrgId(); 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; // Ensure the file is a PDF if (!file.type.contains("pdf")) { print("Error: Not a PDF file"); 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; } setState(() { selectedFileNames = file.name; passportFile = file; passportFileUrlFromApi = null; }); print('PDF File selected: ${file.name}'); }); } Future createUserData(Map userData) async { final bool isUpdating = apiselectedUser != null && apiselectedUser!.isNotEmpty; final uri = Uri.parse( isUpdating ? '$apiUrl/api/users/update/${apiselectedUser?["user_id"]}' : '$apiUrl/api/users/create', ); if (token == null) { throw Exception('Token not found. Please log in.'); } // Use MultipartRequest (POST only) final request = http.MultipartRequest('POST', uri); request.headers['Authorization'] = 'Bearer $token'; // If updating, spoof the method Laravel-style if (isUpdating) { request.fields['_method'] = 'PUT'; request.fields['user_id'] = apiselectedUser!["user_id"].toString(); } // Add all non-null and non-empty user data fields userData.forEach((key, value) { if (value != null && value.toString().trim().isNotEmpty) { request.fields[key] = value.toString(); } }); // Attach file if selected if (passportFile != null) { try { final reader = html.FileReader(); reader.readAsArrayBuffer(passportFile!); await reader.onLoad.first; final data = reader.result as Uint8List; final multipartFile = http.MultipartFile.fromBytes( 'passport_document', data, filename: passportFile!.name, ); request.files.add(multipartFile); print("📎 File attached: ${passportFile!.name}"); } catch (e) { print("❌ Failed to read file: $e"); } } else { print("⚠️ No passport file selected."); } print("🚀 Sending request with fields: ${request.fields}"); try { final streamedResponse = await request.send(); final response = await http.Response.fromStream(streamedResponse); print("Response status: ${response.statusCode}"); print("Response body: ${response.body}"); if (response.statusCode == 200 || response.statusCode == 201) { print("✅ User submitted successfully!"); print("📨 Response: ${response.body}"); context.go('/listUser'); } else { print("❌ Submission failed. Status: ${response.statusCode}"); print("📨 Body: ${response.body}"); } } catch (e) { print("🔥 Error submitting user: $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( backgroundColor: Color(0xFFf5f5f5), // appBar: isDesktop ? null : const CustomAppBar(title: 'Create User '), // drawer: isDesktop ? null : CustomDrawer(isDesktop: false), appBar: CustomAppBar(isDesktop: isDesktop), drawer: CustomDrawer(isDesktop: false), body: Padding( padding: isDesktop ? EdgeInsets.symmetric( horizontal: MediaQuery.of(context).size.width * 0.1, // 30% of screen width as horizontal padding vertical: MediaQuery.of(context).size.height * 0, // 5% of screen height as vertical padding ) : EdgeInsets.all(0), child: Row( children: [ // if (isDesktop) CustomDrawer(isDesktop: true), // const Expanded(child: Center(child: Text("User Details Content"))), // Expanded( // child: _buildUserDetails(isDesktop), // ), Expanded(child: buildData(isDesktop, context)), ], ), ), ); }); } Widget buildData(bool isDesktop, context) { return Container( margin: isDesktop ? const EdgeInsets.only(top: 10.0, bottom: 10.0) : null, padding: const EdgeInsets.only(left: 10, right: 10, top: 8), decoration: BoxDecoration( color: isDesktop ? Colors.white : Color(0xFFFCFCFC), ), // margin: const EdgeInsets.only(left: 10.0, right: 15.0, top: 10.0, bottom: 10.0), // decoration: BoxDecoration( // // color: Colors.amber, // // color: bodyColor, // color: Color(0xFFE1F5FE), // border: Border.all( // color: Colors.white, // // color: Color(0xFFF7F7FB), // width: 3.5)), child: Column( children: [ Expanded( child: Container( // margin: isDesktop // ? EdgeInsets.all(10.0) // : EdgeInsets.only( // left: 20.0, bottom: 20.0, top: 10.0, right: 20.0), // padding: const EdgeInsets.all(10), height: isDesktop ? MediaQuery.of(context).size.height * 0.98 : MediaQuery.of(context).size.height, // decoration: BoxDecoration( // border: isDesktop // ? Border.all( // width: 2, // color: Colors.white, // // color: Color(0xFFF7F7FB), // ) // : null, // color: Colors.white, // // color: Color(0xFFF7F7FB), // // // color: Colors.amber, // ), // color: bodyColor, child: SingleChildScrollView( child: Padding( padding: EdgeInsets.all(0.0), child: _buildUserDetails(isDesktop), ), ), ), ), Container( color: Colors.white, child: Padding( padding: const EdgeInsets.all(8.0), child: isDesktop ? Row( mainAxisAlignment: MainAxisAlignment.end, children: _buildSubmit(isDesktop, layoutColor!), ) : Row( mainAxisAlignment: MainAxisAlignment.center, children: _buildSubmit(isDesktop, layoutColor!), )), ) ], ), ); } // 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 // ), // ), // ], // ), // ); // } Widget _buildUserDetails(bool isDesktop) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ 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), ), ], ), ), 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: 16, ), SizedBox( width: 10, ), Text( "Personal Details", style: TextStyle( fontSize: 14, 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, crossAxisAlignment: CrossAxisAlignment.start, children: [ _buildFirstRowLeftColumn(isDesktop), SizedBox(width: 20), _buildFirstRowRightColumn(isDesktop), ], ) : Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ _buildFirstRowLeftColumn(isDesktop), SizedBox(height: 1), _buildFirstRowRightColumn(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_2_outlined, color: Color(0xFF8B8FB2), size: 16, ), SizedBox( width: 10, ), Text( "Passport Details", style: TextStyle( fontSize: 14, 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: [ _buildSecondRowLeftColumn(isDesktop), SizedBox(width: 20), _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: 16, ), SizedBox( width: 10, ), Text( "Employee Organization Details", style: TextStyle( fontSize: 14, 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: [ _buildThirdRowLeftColumn(isDesktop), SizedBox(width: 20), _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: 16, ), SizedBox( width: 10, ), Text( "Approver", style: TextStyle( fontSize: 14, 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: [ _buildForthRowLeftColumn(isDesktop), SizedBox(width: 20), _buildForthRowRightColumn(isDesktop), SizedBox(width: 20), _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) { 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: 12, fontWeight: FontWeight.w200, color: Colors.black)), SizedBox(height: 5), CustomTextFieldUserWrapper( isFocused: false, isDesktop: isDesktop, child: SizedBox( height: 40, child: TextField( style: TextStyle(fontSize: 12, color: Colors.black), 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("Gender", style: TextStyle( fontSize: 12, fontWeight: FontWeight.w200, color: Colors.black)), SizedBox(height: 5), CustomTextFieldUserWrapper( isFocused: false, isDesktop: isDesktop, child: SizedBox( height: 40, child: DropdownButtonFormField( // value: selectedGender, value: isViewMode ? null : 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, color: Colors.black), items: [ DropdownMenuItem(value: "Male", child: Text("Male")), DropdownMenuItem( value: "Female", child: Text("Female")), ], hint: Text( selectedGender ?? "Select Gender", style: TextStyle(fontSize: 12, color: Colors.black), ), disabledHint: Text( selectedGender ?? "Select Gender", style: TextStyle(fontSize: 12, color: Colors.black), ), ), // 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("Mobile Number", style: TextStyle( fontSize: 12, fontWeight: FontWeight.w200, color: Colors.black)), SizedBox(height: 5), CustomTextFieldUserWrapper( isFocused: false, isDesktop: isDesktop, child: SizedBox( height: 40, child: TextField( style: TextStyle(fontSize: 12, color: Colors.black), 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("Email", style: TextStyle( fontSize: 12, fontWeight: FontWeight.w200, color: Colors.black)), SizedBox(height: 5), CustomTextFieldUserWrapper( isFocused: false, isDesktop: isDesktop, child: SizedBox( height: 40, child: TextField( style: TextStyle(fontSize: 12, color: Colors.black), 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), Row( children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text("Address", style: TextStyle( fontSize: 12, fontWeight: FontWeight.w200, color: Colors.black)), SizedBox(height: 5), CustomTextFieldUserWrapper( isFocused: false, isDesktop: isDesktop, child: SizedBox( height: 110, child: TextField( style: TextStyle(fontSize: 12, color: Colors.black), 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), ), ), ), ), ], ), ], ), ], ), // ), ); } Widget _buildFirstRowRightColumn(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: DateTime(1900), lastDate: DateTime(2100), ); if (pickedDate != null && pickedDate != _selectedDateOfBirth) { setState(() { _selectedDateOfBirth = pickedDate; controllers["dob"]?.text = DateFormat('yyyy-MM-dd').format(pickedDate); }); } } // ----------------- 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("Last Name", style: TextStyle( fontSize: 12, fontWeight: FontWeight.w200, color: Colors.black)), SizedBox(height: 5), CustomTextFieldUserWrapper( isFocused: false, isDesktop: isDesktop, child: SizedBox( height: 40, child: TextField( style: TextStyle(fontSize: 12, color: Colors.black), 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("Date of Birth", style: TextStyle( fontSize: 12, 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, color: Colors.black), 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("Alternate Mobile Number", style: TextStyle( fontSize: 12, fontWeight: FontWeight.w200, color: Colors.black)), SizedBox(height: 5), CustomTextFieldUserWrapper( isFocused: false, isDesktop: isDesktop, child: SizedBox( height: 40, child: TextField( style: TextStyle(fontSize: 12, color: Colors.black), 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), apiselectedUser != null ? SizedBox() : Row( children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text("Password", style: TextStyle( fontSize: 12, fontWeight: FontWeight.w200, color: Colors.black)), SizedBox(height: 5), CustomTextFieldUserWrapper( isFocused: false, isDesktop: isDesktop, child: SizedBox( height: 40, child: TextField( style: TextStyle(fontSize: 12, color: Colors.black), 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, ), SizedBox(height: 3), Row( children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text("Country", style: TextStyle( fontSize: 12, 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, color: Colors.black), ), ), 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"); // } }); }, ), ), ), ], ), ], ), SizedBox(height: 3), Row( children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text("Postal Code", style: TextStyle( fontSize: 12, fontWeight: FontWeight.w200, color: Colors.black)), SizedBox(height: 5), CustomTextFieldUserWrapper( isFocused: false, isDesktop: isDesktop, child: SizedBox( height: 40, child: TextField( style: TextStyle(fontSize: 12, color: Colors.black), 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), ), ), ), ), ], ), ], ), 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: 12, fontWeight: FontWeight.w200, color: Colors.black)), SizedBox(height: 5), CustomTextFieldUserWrapper( isFocused: false, isDesktop: isDesktop, child: SizedBox( height: 40, child: TextField( style: TextStyle(fontSize: 12, color: Colors.black), 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: 12, 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 (passportFile != null || passportFileUrlFromApi != 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('DOWNLOAD - $passportFile'); if (passportFile != null) { try { // ✅ Step 1: Create a Blob directly from the file final blob = html.Blob( [passportFile!], 'application/pdf'); // ✅ Step 2: Generate a download URL from the Blob final url = html.Url.createObjectUrlFromBlob(blob); // ✅ Step 3: Create an invisible anchor to trigger download final anchor = html.AnchorElement(href: url) ..setAttribute("download", selectedFileNames ?? "document.pdf") ..style.display = "none"; // ✅ Step 4: Add anchor to DOM and click it html.document.body!.append(anchor); anchor.click(); // ✅ Step 5: Clean up anchor.remove(); html.Url.revokeObjectUrl(url); print("Download triggered successfully!"); } catch (e) { print("Error during download: $e"); } } else if (passportFileUrlFromApi != null) { // Trigger file download from the server path final anchor = html.AnchorElement( href: passportFileUrlFromApi!) ..target = 'blank' ..download = selectedFileNames ?? "document.pdf" ..click(); } else { print("No file available 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: 12, fontWeight: FontWeight.w200, color: Colors.black)), SizedBox(height: 5), CustomTextFieldUserWrapper( isFocused: false, isDesktop: isDesktop, child: SizedBox( height: 40, child: TextField( style: TextStyle(fontSize: 12, color: Colors.black), 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: 12, 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, color: Colors.black), 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: 12, 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, color: Colors.black), 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: 12, fontWeight: FontWeight.w200, color: Colors.black)), SizedBox(height: 5), CustomTextFieldUserWrapper( isFocused: false, isDesktop: isDesktop, child: SizedBox( height: 40, child: TextField( style: TextStyle(fontSize: 12, color: Colors.black), 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: 12, // 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"); // // } // }); // }, // ), // ), // ), // ], // ), // ], // ), Row( children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text("Role ", style: TextStyle( fontSize: 12, 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: isViewMode ? null : selectedRole, style: TextStyle(fontSize: 12, color: Colors.black), 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"), disabledHint: Text( selectedRole ?? "Select Role", style: TextStyle(fontSize: 12, color: Colors.black), ), ), ), ), ], ), ], ), SizedBox(height: 3), 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("Group", style: TextStyle( fontSize: 12, 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, color: Colors.black), ), ), 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: 12, 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: isViewMode ? null : selectedDepartment, style: TextStyle(fontSize: 12, color: Colors.black), 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"), disabledHint: Text( selectedDepartment ?? "Select Department", style: TextStyle(fontSize: 12, color: Colors.black), ), ), ), ), ], ), ], ), ], ), // ), ); } //----------------------------------- 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: 12, 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: 12, 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: 12, 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, Color layoutColor) { return [ ElevatedButton( style: ElevatedButton.styleFrom( backgroundColor: Colors.white, foregroundColor: layoutColor, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), side: BorderSide(color: layoutColor, width: 2), ), padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12), ), onPressed: () { isEditProfile ? context.go('/listPlan') : context.go('/listUser'); }, child: isViewMode ? Text("Back") : Text("Cancel")), SizedBox( width: 20, ), if (!isViewMode) MouseRegion( cursor: isViewMode ? SystemMouseCursors.forbidden : SystemMouseCursors.click, child: ElevatedButton( style: ElevatedButton.styleFrom( backgroundColor: isViewMode ? layoutColor : layoutColor, // Keep original color foregroundColor: isViewMode ? Colors.white : Colors.white, // Keep original color disabledBackgroundColor: layoutColor, // Ensure color remains when disabled disabledForegroundColor: Colors.white, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), side: BorderSide(color: layoutColor, 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 } }