import 'dart:convert'; import 'dart:io' as html; import 'package:dropdown_search/dropdown_search.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_quill/flutter_quill_internal.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:http/http.dart' as http; import 'package:intl/intl.dart'; import '../../../services/apiService.dart'; import '../../../utils/auth_utils.dart'; import '../../../widgets/custom_user_form.dart'; import '../../../config/apiUrl.dart'; import '../create_user/change_password.dart'; class PersonalDetails extends StatefulWidget { final GlobalKey personalDetailsKey; final Map controllers; final Map errorMessages; final bool isDesktop; final bool isViewMode; final bool apiselectedUser; final Function(List>)? onServiceIdsChanged; final ValueChanged? onGenderChanged; final ValueChanged? onCountryChanged; final ValueChanged? onRoleChanged; final void Function(bool)? onUserTypeChanged; final List>? initialSelectedServices; final String? selectedGender; final String? selectedCountry; final String? selectedRole; final String? userIdApi; // const PersonalDetails(this.isDesktop, this.isViewMode, {super.key},); const PersonalDetails({ Key? key, this.selectedGender, this.selectedCountry, this.selectedRole, this.onServiceIdsChanged, required this.personalDetailsKey, this.initialSelectedServices, required this.controllers, required this.errorMessages, required this.isDesktop, required this.isViewMode, required this.apiselectedUser, this.onGenderChanged, this.onCountryChanged, this.onRoleChanged, this.onUserTypeChanged, this.userIdApi, }) : super(key: key); @override PersonalDetailsState createState() => PersonalDetailsState(); } class PersonalDetailsState extends State { final ApiService apiService = ApiService(); late Map countryMap; // Mapping country_code -> country_name late List countryCodes; // List of country codes late List? apiCountryData; late List? apiRoleData; List? apiAllServices; Map? selectedOrg; List> selectedServiceIds = []; Map focusNodes = {}; Map focusStates = {}; String? get selectedGenderValue => selectedGender; String? get selectedCountryValue => selectedCountry; String? get selectedRoleValue => selectedRole; late bool isTravelAgent = false; String? selectedTab; 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", "Cname", "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; final Map controllers = {}; Map errorMessages2 = {}; @override void initState() { super.initState(); apiCountryData = null; apiRoleData = null; selectedGender = widget.selectedGender; selectedCountry = widget.selectedCountry; selectedRole = widget.selectedRole; fetchRoles(); fetchCountries(); selectedServiceIds = List>.from( widget.initialSelectedServices ?? [], ); print("selectedServiceIdsAPI - $selectedServiceIds"); for (var field in dataHeader) { focusNodes["${field}FocusNode"] = FocusNode(); focusStates["${field}Focused"] = false; } for (var key in focusNodes.keys) { _addFocusListener(focusNodes[key]!, (focus) { setState(() { focusStates[key.replaceFirst("FocusNode", "Focused")] = focus; }); }); } WidgetsBinding.instance.addPostFrameCallback((_) { loadAllServices(); getOrganizationData(); loadInitialData(); }); } @override void dispose() { for (var node in focusNodes.values) { node.dispose(); } super.dispose(); } void _addFocusListener(FocusNode node, Function(bool) updateState) { node.addListener(() { setState(() { updateState(node.hasFocus); }); }); } Future apiCheckDuplicate( String label, String field, String value, String? userId, ) async { try { // Basic validation: Check mobile number length if (field == "mobile_no" && value.length != 10) { _clearError(field); widget.errorMessages[field] = "$label is invalid"; print("Validation failed: $label is too short."); return; // Skip the API call if input is invalid } final response = await apiService.CheckDuplicate( context, label, field, value, userId, ); if (response.isNotEmpty) { _clearError(field); widget.errorMessages[field] = response['message'] ?? "$label Already Exists"; print("Duplicate found: ${response['message']}"); return; } else { _clearError(field); print("No duplicates found."); } } catch (e) { print("Error in checkDuplicate: $e"); } } void _clearError(String field) { setState(() { widget.errorMessages.remove(field); }); } 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 fetchRoles() async { try { final response = await apiService.fetchMasterDropdown(context); if (response is Map && response.containsKey("role")) { List roleList = response["role"]; // Extract the list if (!mounted) return; setState(() { final filteredRole = roleList.where((item) => item['dropdown_key'] == '5').toList(); apiRoleData = filteredRole; }); print("APIROLEData - $apiRoleData"); } else { print("Error: 'role' key not found or response is not a Map."); } } catch (e) { print('Error fetching role list: $e'); } } Future fetchCountries() async { try { List countries = await apiService.fetchCountryList(context); setState(() { apiCountryData = countries; }); } catch (e) { print('Error fetching country list: $e'); } } Future getOrganizationData() async { try { print("getUpdatedServices"); final result = await apiService.fetchOrganization(context); print("UUPdatedServices - $result"); setState(() { final servicesRaw = selectedOrg?['services_ids']; List services; if (servicesRaw is String) { try { services = jsonDecode(servicesRaw); } catch (e) { print('❌ Failed to decode services_ids: $e'); services = []; } } else if (servicesRaw is List) { services = servicesRaw; } else { services = []; } if (selectedServiceIds.isEmpty && services.isNotEmpty) { selectedServiceIds = services.map>((item) { final map = Map.from(item); return {"service_id": map['service_id'].toString()}; }).toList(); } // selectedServiceIds = services.map>((item) { // // force cast or copy to a regular map // final map = Map.from(item); // return { // "service_id": map['service_id'].toString(), // }; // }).toList(); }); // orgId = await getOrgId(); print("selectedOrg - $selectedOrg"); } catch (e) { print('Error fetching updatedServices list: $e'); } } @override Widget build(BuildContext context) { return Container( height: MediaQuery.of(context).size.height, decoration: BoxDecoration( border: Border( top: BorderSide( color: Colors.blueGrey.shade300, // Set your desired color width: 0.09, // Set border thickness ), ), ), child: SingleChildScrollView( scrollDirection: Axis.vertical, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ SizedBox(height: 10), _buildFirstRow(widget.isDesktop), SizedBox(height: 10), // _buildSecondRow(widget.isDesktop), // SizedBox(height: 10), _buildThirdRow(widget.isDesktop), SizedBox(height: 10), _buildForthRow(widget.isDesktop), SizedBox(height: 10), _buildServices(widget.isDesktop), ], ), ), ); } Future loadAllServices() async { try { final result = await apiService.fetchAllServices(context); setState(() { // apiAllServices = result; setState(() { apiAllServices = result ..sort((a, b) => (a['order'] ?? 0).compareTo(b['order'] ?? 0)); }); }); print("Fetched services: $apiAllServices"); } catch (e) { print('Error fetching role list: $e'); } } Widget _buildServices(bool isDesktop) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Services", style: GoogleFonts.poppins( fontSize: 14, fontWeight: FontWeight.w400, color: Colors.black, ), // style: TextStyle( // fontFamily: "Archivo", // fontSize: 14, // fontWeight: FontWeight.w600, // color: Color(0xFF212121)), ), SizedBox(height: 10), if (widget.errorMessages["agent_supported_service_ids"] != null) ...[ SizedBox(height: 5), // Space before error message Align( alignment: Alignment.center, child: Text( widget.errorMessages["agent_supported_service_ids"]!, style: GoogleFonts.poppins(color: Colors.red, fontSize: 12), ), ), ], Container( decoration: BoxDecoration( border: Border.all(color: Color(0xFFF4F4FB)), borderRadius: BorderRadius.circular(1), // color: bodyColor, // color: Color(0xFFF5F5F5), color: Colors.white, ), padding: EdgeInsets.only(left: 5, right: 5, top: 15, bottom: 5), child: isDesktop ? SingleChildScrollView( scrollDirection: Axis.vertical, child: SingleChildScrollView( scrollDirection: Axis.horizontal, child: Row(children: _buildOptions()), ), ) : Expanded( child: SingleChildScrollView( scrollDirection: Axis.horizontal, child: Row(children: _buildOptions()), ), ), ), ], ); } List _buildOptions() { if (apiAllServices == null) return []; return apiAllServices!.map((service) { return Padding( padding: const EdgeInsets.only(right: 20.0), child: _buildOption(service), ); }).toList(); } Widget _buildOption(Map service) { String name = service['name']; String iconUrl = service['icon']; // Can be empty string IconData fallbackIcon = _getLocalIconForService(name); String serviceId = service['service_id'].toString(); // bool isSelected = selectedServiceIds.contains(serviceId); bool isSelected = selectedServiceIds.any( (item) => item["service_id"] == serviceId, ); return GestureDetector( onTap: () { setState(() { String serviceId = service['service_id'].toString(); // Check if already selected int existingIndex = selectedServiceIds.indexWhere( (item) => item["service_id"] == serviceId, ); if (existingIndex != -1) { selectedServiceIds.removeAt(existingIndex); } else { selectedServiceIds.add({"service_id": serviceId}); } print("selectedServiceIdsUser --- $selectedServiceIds"); print( "selectedServiceIdsUser1 --- ${json.encode(selectedServiceIds)}", ); // Call the parent's callback widget.onServiceIdsChanged!(selectedServiceIds); // widget.onServiceIdsChanged!(selectedServiceIds); }); }, child: Row( children: [ iconUrl.isNotEmpty ? Image.network( iconUrl, width: 18, height: 18, errorBuilder: (context, error, stackTrace) { return Icon( fallbackIcon, size: 18, color: isSelected == name ? Color(0xFF114D8B) : Color(0xFF475569), ); }, ) : Icon( fallbackIcon, size: 18, color: isSelected == name ? Color(0xFF114D8B) : Color(0xFF475569), ), SizedBox(width: 5), SizedBox(width: 5), Text( name, style: GoogleFonts.poppins( fontSize: 12, color: isSelected == name ? Color(0xFF114D8B) : Color(0xFF475569), fontWeight: isSelected == name ? FontWeight.bold : FontWeight.w500, ), ), SizedBox(width: 5), // if (selectedListOption == title && widget.isViewMode == false) Container( height: 15, width: 15, decoration: BoxDecoration( shape: BoxShape.rectangle, border: Border.all( color: isSelected ? Colors.green : Colors.grey.shade400, width: isSelected ? 2 : 1, ), ), child: Icon( Icons.check, size: 10, color: isSelected ? Colors.green : Colors.white10, // color: Colors.grey, ), ), ], ), ); } IconData _getLocalIconForService(String name) { switch (name.toLowerCase()) { case 'flight': return Icons.flight_takeoff_outlined; case 'train': return Icons.train_outlined; case 'bus': return Icons.directions_bus; case 'taxi': return Icons.local_taxi_outlined; case 'accomodation': return Icons.local_hotel_outlined; case 'forex': return Icons.attach_money_outlined; case 'insurance': return Icons.list_alt_outlined; case 'visa': return Icons.badge_outlined; case 'miscellaneous': return Icons.card_giftcard_outlined; default: return Icons.circle_notifications; } } Widget _buildFirstRow(bool isDesktop) { return Container( color: Colors.white, child: widget.isDesktop ? Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ buildFirstNameField(), Spacer(), buildLastNameField(), Spacer(), // Space after Last Name buildCompanyNameField(), Spacer(), // buildGenderField(), // SizedBox(width: 15), // buildDobField(), buildMobileField(), Spacer(), buildAlternateMobileField(), ], ) : Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ buildFirstNameField(), SizedBox(height: 8), // Vertical space buildLastNameField(), SizedBox(height: 8), buildCompanyNameField(), SizedBox(height: 8), // buildGenderField(), // SizedBox(height: 8), // buildDobField(), buildMobileField(), SizedBox(height: 8), // Space after Last Name buildAlternateMobileField(), ], ), ); } // Widget _buildSecondRow(bool isDesktop) { // return Container( // color: Colors.white, // child: // widget.isDesktop // ? Row( // crossAxisAlignment: CrossAxisAlignment.start, // children: [ // // buildEmailField(), // // Spacer(), // // buildMobileField(), // // Spacer(), // Space after Last Name // // buildAlternateMobileField(), // ], // ) // : Column( // crossAxisAlignment: CrossAxisAlignment.start, // children: [ // // buildMobileField(), // // SizedBox(height: 8), // Vertical space // // buildAlternateMobileField(), // // SizedBox(height: 8), // // buildEmailField(), // ], // ), // ); // } Widget _buildThirdRow(bool isDesktop) { void _openPopup() { final emailValue = widget.controllers["email"]?.text ?? ""; final updaterUserId = widget.userIdApi ?? ""; showDialog( context: context, builder: (context) { return ChangePasswordDialogData( updaterEmail: emailValue, updaterUserId: updaterUserId, layoutColor: layoutColor, isDesktop: widget.isDesktop, ); }, ); } return Container( color: Colors.white, child: isDesktop ? !widget.apiselectedUser ? Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ if (!widget.apiselectedUser) ...[ buildEmailField(), // SizedBox( // width: MediaQuery.of(context).size.width * 0.014, // ), Spacer(), buildPassword(), // SizedBox( // width: MediaQuery.of(context).size.width * 0.015, // ), Spacer(), buildRole(), ], ], ) : Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ buildEmailField(), SizedBox(width: 10), // Spacer(), // Container( // width: // isDesktop // ? MediaQuery.of(context).size.width * 0.25 // : MediaQuery.of(context).size.width * 0.8, // child: Row( // mainAxisAlignment: MainAxisAlignment.end, // children: [ // TextButton( // onPressed: () => _openPopup(), // style: TextButton.styleFrom( // foregroundColor: Color(0xFF114D8B), // textStyle: GoogleFonts.poppins( // fontSize: 12, // fontWeight: FontWeight.w400, // ), // ), // child: Text("Change Password"), // ), // ], // ), // ), buildchangPswd(), // SizedBox(height: 1), Spacer(), buildRole(), ], ) : Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ if (!widget.apiselectedUser) ...[ buildEmailField(), SizedBox(height: 8), buildPassword(), SizedBox(height: 8), buildRole(), ] else ...[ Container( width: isDesktop ? MediaQuery.of(context).size.width * 0.25 : MediaQuery.of(context).size.width * 0.8, child: Row( mainAxisAlignment: MainAxisAlignment.end, children: [ TextButton( onPressed: () => _openPopup(), style: TextButton.styleFrom( foregroundColor: Color(0xFF114D8B), textStyle: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w400, ), ), child: Text("Change Password"), ), ], ), ), SizedBox(height: 1), buildRole(), ], ], ), ); } Widget _buildForthRow(bool isDesktop) { return Container( color: Colors.white, child: isDesktop ? Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ buildAddress(), Spacer(), // SizedBox(width: 15), buildCountryField(), Spacer(), // SizedBox(width: 15), buildPostalCodeField(), ], ) : Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ buildAddress(), SizedBox(height: 8), // buildCountryField(), SizedBox(height: 8), // Vertical space buildPostalCodeField(), ], ), ); } Widget buildFirstNameField() { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "First Name *", style: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w600, color: Color(0xFF575A74), ), ), SizedBox(height: 5), CustomTextFieldUserWrapper( isFocused: focusStates["FnameFocused"] ?? false, isDesktop: widget.isDesktop, width: widget.isDesktop ? MediaQuery.of(context).size.width * 0.12 : null, child: SizedBox( height: 40, child: TextField( style: GoogleFonts.poppins(fontSize: 12, color: Colors.black), focusNode: focusNodes["FnameFocusNode"], controller: widget.controllers["Fname"], enabled: !widget.isViewMode, onChanged: (value) { _clearError("first_name"); }, decoration: InputDecoration( labelText: "FirstName", labelStyle: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w400, color: Colors.grey, ), floatingLabelBehavior: FloatingLabelBehavior.never, border: InputBorder.none, contentPadding: EdgeInsets.symmetric(vertical: 16), ), ), ), ), if (widget.errorMessages["first_name"] != null) ...[ SizedBox(height: 5), // Space before error message Text( widget.errorMessages["first_name"]!, style: GoogleFonts.poppins(color: Colors.red, fontSize: 12), ), ], ], ); } Widget buildLastNameField() { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Last Name*", style: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w600, color: Color(0xFF575A74), ), ), SizedBox(height: 5), CustomTextFieldUserWrapper( isFocused: focusStates["LnameFocused"] ?? false, isDesktop: widget.isDesktop, width: widget.isDesktop ? MediaQuery.of(context).size.width * 0.12 : null, child: SizedBox( height: 40, child: TextField( style: TextStyle(fontSize: 12, color: Colors.black), focusNode: focusNodes["LnameFocusNode"], controller: widget.controllers["Lname"], enabled: !widget.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 (widget.errorMessages["last_name"] != null) ...[ SizedBox(height: 5), // Space before error message Text( widget.errorMessages["last_name"]!, style: GoogleFonts.poppins(color: Colors.red, fontSize: 12), ), ], ], ); } Widget buildCompanyNameField() { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Company Name", style: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w600, color: Color(0xFF575A74), ), ), SizedBox(height: 5), CustomTextFieldUserWrapper( isFocused: focusStates["CnameFocused"] ?? false, isDesktop: widget.isDesktop, child: SizedBox( height: 40, child: TextField( style: TextStyle(fontSize: 12, color: Colors.black), focusNode: focusNodes["CnameFocusNode"], controller: widget.controllers["Cname"], enabled: !widget.isViewMode, decoration: InputDecoration( labelText: "CompanyName", labelStyle: TextStyle(fontSize: 12, color: Colors.grey), floatingLabelBehavior: FloatingLabelBehavior.never, border: InputBorder.none, contentPadding: EdgeInsets.symmetric(vertical: 16), ), ), ), ), ], ); } Widget buildGenderField() { List genderOptions = ["Male", "Female"]; return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Gender", style: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w600, color: Color(0xFF575A74), ), ), SizedBox(height: 5), CustomTextFieldUserWrapper( width: widget.isDesktop ? MediaQuery.of(context).size.width * 0.12 : null, isFocused: false, isDesktop: widget.isDesktop, child: SizedBox( height: 40, child: DropdownSearch( selectedItem: selectedGender, enabled: !widget.isViewMode, popupProps: PopupProps.menu( showSearchBox: false, // Set true if you want search fit: FlexFit.loose, constraints: BoxConstraints(maxHeight: 200), itemBuilder: (context, item, isSelected) => Container( color: Colors.white, padding: EdgeInsets.symmetric( horizontal: 10, vertical: 6, ), child: Text( item, style: GoogleFonts.poppins(fontSize: 11.5), ), ), ), items: genderOptions, dropdownDecoratorProps: DropDownDecoratorProps( dropdownSearchDecoration: InputDecoration( border: InputBorder.none, contentPadding: EdgeInsets.symmetric(horizontal: 1), ), ), dropdownBuilder: (context, selectedItem) => Align( alignment: Alignment.centerLeft, child: Text( selectedItem ?? "Select Gender", style: GoogleFonts.poppins( fontSize: 12, color: Colors.black, ), ), ), onChanged: widget.isViewMode ? null : (String? newValue) { setState(() { selectedGender = newValue; print("selectedGender - $selectedGender"); }); widget.onGenderChanged?.call(newValue); }, ), ), ), ], ); } Widget buildDobField() { 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), lastDate: today, initialEntryMode: DatePickerEntryMode.calendarOnly, ); if (pickedDate != null && pickedDate != _selectedDateOfBirth) { setState(() { _selectedDateOfBirth = pickedDate; widget.controllers["dob"]?.text = DateFormat( 'dd-MM-yyyy', ).format(pickedDate); }); } } return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Date of Birth", style: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w600, color: Color(0xFF575A74), ), ), SizedBox(height: 5), CustomTextFieldUserWrapper( width: widget.isDesktop ? MediaQuery.of(context).size.width * 0.12 : null, isFocused: false, isDesktop: widget.isDesktop, child: SizedBox( height: 40, child: GestureDetector( onTap: widget.isViewMode ? null : () async { await _selectCheckDateOfBirth(context); if (widget.controllers["dob"]!.text.isNotEmpty) { setState(() { // errorMessages.remove("start_date"); }); } }, child: AbsorbPointer( child: TextField( controller: widget.controllers["dob"], readOnly: true, style: GoogleFonts.poppins(fontSize: 12, color: Colors.black), decoration: InputDecoration( labelText: "Select Date", labelStyle: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w400, color: Colors.grey, ), floatingLabelBehavior: FloatingLabelBehavior.never, border: InputBorder.none, contentPadding: EdgeInsets.symmetric(vertical: 16), suffixIcon: Icon( Icons.calendar_today, size: 16, color: Color(0xFF8B8FB2), ), ), ), ), ), ), ), ], ); } Widget buildchangPswd() { void _openPopup() { final emailValue = widget.controllers["email"]?.text ?? ""; final updaterUserId = widget.userIdApi ?? ""; showDialog( context: context, builder: (context) { return ChangePasswordDialogData( updaterEmail: emailValue, updaterUserId: updaterUserId, layoutColor: layoutColor, isDesktop: widget.isDesktop, ); }, ); } return Container( width: isDesktop ? MediaQuery.of(context).size.width * 0.25 : MediaQuery.of(context).size.width * 0.8, child: Column( // mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ SizedBox(height: 30), TextButton( onPressed: () => _openPopup(), style: TextButton.styleFrom( foregroundColor: Color(0xFF114D8B), textStyle: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w400, ), ), child: Text("Change Password"), ), ], ), ); } Widget buildEmailField() { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Email *", style: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w600, color: Color(0xFF575A74), ), ), SizedBox(height: 5), CustomTextFieldUserWrapper( isFocused: focusStates["emailFocused"] ?? false, isDesktop: widget.isDesktop, child: SizedBox( height: 40, child: TextField( focusNode: focusNodes["emailFocusNode"], style: GoogleFonts.poppins(fontSize: 12, color: Colors.black), controller: widget.controllers["email"], onChanged: (value) { _clearError("email"); apiCheckDuplicate("Email", "email", value, widget.userIdApi); }, enabled: !widget.isViewMode, decoration: InputDecoration( labelText: "Email", labelStyle: GoogleFonts.poppins( fontSize: 12, color: Colors.grey, ), floatingLabelBehavior: FloatingLabelBehavior.never, border: InputBorder.none, contentPadding: EdgeInsets.symmetric(vertical: 16), ), ), ), ), if (widget.errorMessages["email"] != null) ...[ SizedBox(height: 5), // Space before error message Text( widget.errorMessages["email"]!, style: GoogleFonts.poppins(color: Colors.red, fontSize: 12), ), ], ], ); } Widget buildMobileField() { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Mobile Number *", style: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w600, color: Color(0xFF575A74), ), ), SizedBox(height: 5), CustomTextFieldUserWrapper( isFocused: focusStates["mobileNumberFocused"] ?? false, isDesktop: widget.isDesktop, width: widget.isDesktop ? MediaQuery.of(context).size.width * 0.12 : null, child: SizedBox( height: 40, child: TextField( style: GoogleFonts.poppins(fontSize: 12, color: Colors.black), focusNode: focusNodes["mobileNumberFocusNode"], controller: widget.controllers["mobileNumber"], enabled: !widget.isViewMode, onChanged: (value) { _clearError("mobile_no"); apiCheckDuplicate( "Mobile Number", "mobile_no", value, widget.userIdApi, ); }, 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: GoogleFonts.poppins( fontSize: 12, color: Colors.grey, ), floatingLabelBehavior: FloatingLabelBehavior.never, border: InputBorder.none, contentPadding: EdgeInsets.symmetric(vertical: 16), ), ), ), ), if (widget.errorMessages["mobile_no"] != null) ...[ SizedBox(height: 5), // Space before error message Text( widget.errorMessages["mobile_no"]!, style: GoogleFonts.poppins(color: Colors.red, fontSize: 12), ), ], ], ); } Widget buildAlternateMobileField() { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Alternate Mobile Number", style: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w600, color: Color(0xFF575A74), ), ), SizedBox(height: 5), CustomTextFieldUserWrapper( // isFocused: false, isFocused: focusStates["alternateMobileFocused"] ?? false, isDesktop: widget.isDesktop, width: widget.isDesktop ? MediaQuery.of(context).size.width * 0.12 : null, child: SizedBox( height: 40, child: TextField( focusNode: focusNodes["alternateMobileFocusNode"], style: GoogleFonts.poppins(fontSize: 12, color: Colors.black), controller: widget.controllers["alternateMobile"], enabled: !widget.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: GoogleFonts.poppins( fontSize: 12, color: Colors.grey, ), floatingLabelBehavior: FloatingLabelBehavior.never, border: InputBorder.none, contentPadding: EdgeInsets.symmetric(vertical: 16), ), ), ), ), if (widget.errorMessages["alternate_mobile_no"] != null) ...[ SizedBox(height: 5), // Space before error message Text( widget.errorMessages["alternate_mobile_no"]!, style: GoogleFonts.poppins(color: Colors.red, fontSize: 12), ), ], ], ); } Widget buildCountryField() { 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, // // item['currency'] as String // }; countryMap = { for (var country in countryList) (country['country_code'] ?? ''): '${country['country_name'] ?? ''} (${country['country_code'] ?? ''})', }; // Extract only country codes for processing countryCodes = countryMap.keys.toList(); selectedCountry ??= null; return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Country", style: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w600, color: Color(0xFF575A74), ), ), SizedBox(height: 5), CustomTextFieldUserWrapper( isFocused: focusStates["countryFocusNode"] ?? false, padding: const EdgeInsets.symmetric(horizontal: 0), isDesktop: widget.isDesktop, child: SizedBox( height: 40, width: double.infinity, child: Focus( focusNode: focusNodes["countryFocusNode"], onFocusChange: (hasFocus) { setState(() { focusStates["countryFocused"] = hasFocus; }); }, child: GestureDetector( onTap: () { // Request focus when user taps focusNodes["countryFocusNode"]?.requestFocus(); }, child: DropdownSearch( selectedItem: countryMap[selectedCountry], enabled: !widget.isViewMode, popupProps: PopupProps.menu( showSearchBox: true, // Enables search functionality fit: FlexFit.loose, // Allows flexible height menuProps: const MenuProps(backgroundColor: Colors.white), itemBuilder: // (context, item, isSelected) => Container( // padding: EdgeInsets.symmetric( // horizontal: 10, // vertical: 6, // ), // child: Text( // item, // style: GoogleFonts.poppins(fontSize: 11.5), // ), // ), (context, item, isSelected) { print("contryItem - $item"); final match = RegExp( r'^(.*)\s\((.*)\)$', ).firstMatch(item); final countryName = match?.group(1) ?? ''; final countryCode = match?.group(2) ?? ''; return Padding( padding: const EdgeInsets.symmetric( horizontal: 10.0, vertical: 6.0, ), child: Padding( // padding: const EdgeInsets.all(3.0), padding: EdgeInsets.symmetric( horizontal: 2, vertical: 3, ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( countryName, style: GoogleFonts.poppins(fontSize: 11.5), ), Text( countryCode, style: GoogleFonts.poppins( fontSize: 11.5, color: Colors.grey, ), ), ], ), ), ); }, constraints: BoxConstraints(maxHeight: 180), searchFieldProps: TextFieldProps( decoration: InputDecoration( hintText: "Search Country...", contentPadding: EdgeInsets.symmetric( horizontal: 3, vertical: 3, ), ), ), ), items: countryMap.values.toList(), dropdownDecoratorProps: DropDownDecoratorProps( dropdownSearchDecoration: InputDecoration( // border: InputBorder.none, border: OutlineInputBorder( borderRadius: BorderRadius.circular(8), borderSide: BorderSide( color: (focusStates["countryFocused"] ?? false) ? (layoutColor ?? Colors.blue) : Colors.white, // width: 0.5, ), ), enabledBorder: OutlineInputBorder( borderSide: BorderSide( color: (focusStates["countryFocused"] ?? false) ? (layoutColor ?? Colors.blue) : Colors.white, // width: 0.5, // const Color(0xFFD6D5E6), ), ), focusedBorder: OutlineInputBorder( borderSide: BorderSide( color: (layoutColor ?? Colors.grey), width: 1, ), ), contentPadding: EdgeInsets.symmetric( horizontal: 10.0, vertical: 8.0, ), // contentPadding: EdgeInsets.symmetric(horizontal: 1), ), ), dropdownBuilder: (context, selectedItem) => Align( // Center-align selected item alignment: Alignment.centerLeft, child: Text( selectedItem ?? "Select Country", style: GoogleFonts.poppins( fontSize: 12, color: Colors.black, ), ), ), // onChanged: widget.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"); // // } // }); // widget.onCountryChanged?.call(newValue); // }, onChanged: widget.isViewMode ? null : (String? newValue) { if (newValue == null) return; final countryCode = countryMap.entries .firstWhere( (entry) => entry.value == newValue, ) .key; setState(() { selectedCountry = countryCode; }); widget.onCountryChanged?.call( countryCode, ); // ✅ send code not name }, ), ), ), ), ), ], ); } Widget buildPostalCodeField() { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Postal Code", style: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w600, color: Color(0xFF575A74), ), ), SizedBox(height: 5), CustomTextFieldUserWrapper( isFocused: focusStates["postalCodeFocused"] ?? false, isDesktop: widget.isDesktop, child: SizedBox( height: 40, child: TextField( focusNode: focusNodes["postalCodeFocusNode"], style: GoogleFonts.poppins(fontSize: 12, color: Colors.black), controller: widget.controllers["postalCode"], enabled: !widget.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: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w400, color: Colors.grey, ), floatingLabelBehavior: FloatingLabelBehavior.never, border: InputBorder.none, contentPadding: EdgeInsets.symmetric(vertical: 16), ), ), ), ), ], ); } Widget buildRole() { Map roleMap = { for (var item in apiRoleData ?? []) item['dropdown_key'] as String: item['dropdown_value'] as String, }; // List roleNames = roleMap.values.toList(); List roleNames = roleMap.values.take(5).toList(); return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Role", style: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w600, color: Color(0xFF575A74), ), ), SizedBox(height: 5), CustomTextFieldUserWrapper( isFocused: false, isDesktop: widget.isDesktop, child: SizedBox( height: 40, child: DropdownSearch( selectedItem: roleMap[selectedRole], enabled: false, //enabled: !widget.isViewMode, popupProps: PopupProps.menu( showSearchBox: true, fit: FlexFit.loose, constraints: BoxConstraints(maxHeight: 250), menuProps: const MenuProps(backgroundColor: Colors.white), itemBuilder: (context, item, isSelected) => Container( color: Colors.white, padding: EdgeInsets.symmetric( horizontal: 10, vertical: 6, ), child: Text( item, style: GoogleFonts.poppins(fontSize: 11.5), ), ), searchFieldProps: TextFieldProps( decoration: InputDecoration( hintText: "Search Role...", contentPadding: EdgeInsets.symmetric(horizontal: 10), ), ), ), items: roleNames, dropdownDecoratorProps: DropDownDecoratorProps( dropdownSearchDecoration: InputDecoration( border: InputBorder.none, contentPadding: EdgeInsets.symmetric(horizontal: 1), ), ), dropdownBuilder: (context, selectedItem) => Align( alignment: Alignment.centerLeft, child: Text( selectedItem ?? "Select Role", style: GoogleFonts.poppins( fontSize: 12, color: Colors.black, ), ), ), onChanged: widget.isViewMode ? null : (String? newValue) { widget.errorMessages.remove("role_id"); if (newValue == null) return; final selectedKey = roleMap.entries .firstWhere((entry) => entry.value == newValue) .key; setState(() { selectedRole = selectedKey; isTravelAgent = selectedKey == "5"; }); widget.onUserTypeChanged?.call(isTravelAgent); widget.onRoleChanged?.call(selectedKey); }, ), ), ), if (widget.errorMessages["role_id"] != null) ...[ SizedBox(height: 5), // Space before error message Text( widget.errorMessages["role_id"]!, style: GoogleFonts.poppins(color: Colors.red, fontSize: 12), ), ], ], ); } Widget buildAddress() { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Address", style: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w600, color: Color(0xFF575A74), ), ), SizedBox(height: 5), CustomTextFieldUserWrapper( isFocused: focusStates["addressFocused"] ?? false, isDesktop: widget.isDesktop, child: SizedBox( height: 80, child: TextField( focusNode: focusNodes["addressFocusNode"], style: GoogleFonts.poppins(fontSize: 12, color: Colors.black), controller: widget.controllers["address"], enabled: !widget.isViewMode, maxLines: 2, keyboardType: TextInputType.multiline, decoration: InputDecoration( labelText: "Address", labelStyle: GoogleFonts.poppins( fontSize: 12, color: Colors.grey, ), floatingLabelBehavior: FloatingLabelBehavior.never, border: InputBorder.none, // contentPadding: EdgeInsets.symmetric(vertical: 16), contentPadding: EdgeInsets.symmetric(vertical: 4), ), ), ), ), ], ); } Widget buildPassword() { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Password *", style: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w600, color: Color(0xFF575A74), ), ), SizedBox(height: 5), CustomTextFieldUserWrapper( isFocused: focusStates["passwordFocused"] ?? false, isDesktop: widget.isDesktop, child: SizedBox( height: 40, child: TextField( focusNode: focusNodes["passwordFocusNode"], style: GoogleFonts.poppins(fontSize: 12, color: Colors.black), controller: widget.controllers["password"], enabled: !widget.isViewMode, onChanged: (value) { _clearError("password"); }, decoration: InputDecoration( labelText: "Password", labelStyle: GoogleFonts.poppins( fontSize: 12, color: Colors.grey, ), floatingLabelBehavior: FloatingLabelBehavior.never, border: InputBorder.none, contentPadding: EdgeInsets.symmetric(vertical: 16), ), ), ), ), if (widget.errorMessages["password"] != null) ...[ SizedBox(height: 5), // Space before error message Text( widget.errorMessages["password"]!, style: GoogleFonts.poppins(color: Colors.red, fontSize: 12), ), ], ], ); } // apiselectedUser != null // ? SizedBox() }