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: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 '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; final dynamic focusNodes; // 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.focusNodes, 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 = []; String? get selectedGenderValue => selectedGender; String? get selectedCountryValue => selectedCountry; String? get selectedRoleValue => selectedRole; final FocusNode _fnameFocusNode = FocusNode(); final FocusNode _lnameFocusNode = FocusNode(); final FocusNode _emailFocusNode = FocusNode(); final FocusNode _passwordFocusNode = FocusNode(); final FocusNode _dobFocusNode = FocusNode(); final FocusNode _genderFocusNode = FocusNode(); final FocusNode _mobileNumberFocusNode = FocusNode(); final FocusNode _alternateMobileFocusNode = FocusNode(); final FocusNode _addressFocusNode = FocusNode(); final FocusNode _countryFocusNode = FocusNode(); final FocusNode _postalCodeFocusNode = FocusNode(); final FocusNode _roleIdFocusNode = FocusNode(); bool _fnameFocus = false; bool _lnameFocus = false; bool _emailFocus = false; bool _passwordFocus = false; bool _dobFocus = false; bool _genderFocus = false; bool _mobileNumberFocus = false; bool _alternateMobileFocus = false; bool _addressFocus = false; bool _countryFocus = false; bool _postalCodeFocus = false; bool _roleIdFocus = false; 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", "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 errorMessages = {}; @override void initState() { super.initState(); WidgetsBinding.instance.addPostFrameCallback((_) { loadInitialData(); }); apiCountryData = null; apiRoleData = null; selectedGender = widget.selectedGender; selectedCountry = widget.selectedCountry; selectedRole = widget.selectedRole; fetchRoles(); fetchCountries(); selectedServiceIds = List>.from( widget.initialSelectedServices ?? [], ); print("selectedServiceIdsAPI - $selectedServiceIds"); _addFocusListener(_fnameFocusNode, (focus) => _fnameFocus = focus); _addFocusListener(_lnameFocusNode, (focus) => _lnameFocus = focus); _addFocusListener(_emailFocusNode, (focus) => _emailFocus = focus); _addFocusListener(_passwordFocusNode, (focus) => _passwordFocus = focus); _addFocusListener(_dobFocusNode, (focus) => _dobFocus = focus); _addFocusListener(_genderFocusNode, (focus) => _genderFocus = focus); _addFocusListener( _mobileNumberFocusNode, (focus) => _mobileNumberFocus = focus, ); _addFocusListener( _alternateMobileFocusNode, (focus) => _alternateMobileFocus = focus, ); _addFocusListener(_addressFocusNode, (focus) => _addressFocus = focus); _addFocusListener(_countryFocusNode, (focus) => _countryFocus = focus); _addFocusListener( _postalCodeFocusNode, (focus) => _postalCodeFocus = focus, ); _addFocusListener(_roleIdFocusNode, (focus) => _roleIdFocus = focus); WidgetsBinding.instance.addPostFrameCallback((_) { loadAllServices(); getOrganizationData(); loadInitialData(); _fnameFocusNode.requestFocus(); }); } 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."); // widget.controllers["mobileNumber"]?.clear(); final controller = widget.controllers["mobileNumber"]; if (controller != null) { print("Before clear: ${controller.text}"); // controller.clear(); print("After clear: ${controller.text}"); } // Make sure this setState rebuilds the widget that owns the TextField! if (mounted) setState(() {}); // setState(() {}); return; // Skip the API call if input is invalid } final response = await apiService.CheckDuplicate( 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(); if (response is Map && response.containsKey("role")) { List roleList = response["role"]; // Extract the list if (!mounted) return; 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'); } } Future fetchCountries() async { try { List countries = await apiService.fetchCountryList(); setState(() { apiCountryData = countries; }); } catch (e) { print('Error fetching country list: $e'); } } Future getOrganizationData() async { try { print("getUpdatedServices"); final result = await apiService.fetchOrganization(); 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: 20), // _buildPassportDataRow1(widget.isDesktop), // 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), if (selectedRole == "5") _buildServices(widget.isDesktop), ], ), ), ); } Widget _buildPassportDataRow1(bool isDesktop) { return Container( color: Colors.white, child: Row( children: [ Text( "* Please fill name details as per in passport *", style: GoogleFonts.poppins( fontSize: 10, color: Colors.black87, fontStyle: FontStyle.italic, letterSpacing: 0.5, ), ), ], ), ); } Future loadAllServices() async { try { final result = await apiService.fetchAllServices(); 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), 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 ? Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, // mainAxisSize: MainAxisSize.min, 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, ), // style: TextStyle( // fontSize: 13, // color: isSelected == name ? Color(0xFF114D8B) : Color(0xFF475569), // fontFamily: "Archivo", // fontWeight: // isSelected == name ? FontWeight.bold : FontWeight.w500), // fontWeight: selectedListOption == title ? FontWeight.bold : FontWeight.normal,)), ), 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.bus_alert_outlined; 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 buildGenderField(), SizedBox(width: 15), buildDobField(), ], ) : Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ buildFirstNameField(), SizedBox(height: 8), // Vertical space buildLastNameField(), SizedBox(height: 8), buildGenderField(), SizedBox(height: 8), buildDobField(), ], ), ); } 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) ...[ buildPassword(), SizedBox( width: MediaQuery.of(context).size.width * 0.015, ), buildRole(), ] else ...[ // TextButton( // onPressed: () => _openPopup(), // style: TextButton.styleFrom( // foregroundColor: Colors.black, // textStyle: GoogleFonts.poppins( // fontSize: 12, // fontWeight: FontWeight.w400, // ), // ), // child: Text("Change Password"), // ), // // SizedBox(height: 8, width: 15), // buildRole(), ], ], ) : Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ 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(), ], ) : Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ if (!widget.apiselectedUser) ...[ 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: _fnameFocus, isDesktop: widget.isDesktop, child: SizedBox( height: 40, child: TextField( style: GoogleFonts.poppins(fontSize: 12, color: Colors.black), // focusNode: focusNodes["FnameFocusNode"], focusNode: _fnameFocusNode, // 👈 Use it here controller: widget.controllers["Fname"], autofocus: true, 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: _lnameFocus, isDesktop: widget.isDesktop, child: SizedBox( height: 40, child: TextField( style: TextStyle(fontSize: 12, color: Colors.black), focusNode: _lnameFocusNode, autofocus: true, 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 buildGenderFieldOld() { // 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: DropdownButtonFormField( // // value: selectedGender, // value: widget.isViewMode ? null : selectedGender, // onChanged: // widget.isViewMode // ? null // : (String? newValue) { // setState(() { // selectedGender = newValue; // print("selectedGender - $selectedGender"); // }); // // widget.onGenderChanged?.call(newValue); // }, // decoration: InputDecoration( // border: InputBorder.none, // enabled: !widget.isViewMode, // Disables input when in view mode // ), // style: GoogleFonts.poppins(fontSize: 12, color: Colors.black), // items: [ // DropdownMenuItem(value: "Male", child: Text("Male")), // DropdownMenuItem(value: "Female", child: Text("Female")), // ], // hint: Text( // selectedGender ?? "Select Gender", // style: GoogleFonts.poppins(fontSize: 12, color: Colors.black), // ), // disabledHint: Text( // selectedGender ?? "Select Gender", // style: GoogleFonts.poppins(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"); // // // } // // }); // // }, // // // // // // ), // ), // ), // ], // ); // } 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: _genderFocus, padding: const EdgeInsets.symmetric(horizontal: 0), isDesktop: widget.isDesktop, child: SizedBox( height: 40, child: Focus( focusNode: _genderFocusNode, onFocusChange: (hasFocus) { setState(() { _genderFocus = hasFocus; }); }, child: GestureDetector( onTap: () { _genderFocusNode?.requestFocus(); }, 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, border: OutlineInputBorder( borderRadius: BorderRadius.circular(5), borderSide: BorderSide( color: (_genderFocus) ? (layoutColor ?? Colors.blue) : Colors.white, width: 0.5, ), ), enabledBorder: OutlineInputBorder( borderSide: BorderSide( color: (_genderFocus) ? (layoutColor ?? Colors.blue) : Colors.white, // : const Color(0xFFD6D5E6), width: 0.5, // const Color(0xFFD6D5E6), ), ), focusedBorder: OutlineInputBorder( borderSide: BorderSide( color: (layoutColor ?? Colors.grey), width: 0.5, ), ), contentPadding: EdgeInsets.symmetric( horizontal: 10, // vertical: 1, ), // 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: _dobFocus, isDesktop: widget.isDesktop, child: SizedBox( height: 40, child: Focus( focusNode: _dobFocusNode, onKeyEvent: (FocusNode node, KeyEvent event) { // Only act on KeyDown and when Enter is pressed if (event is KeyDownEvent && (event.logicalKey == LogicalKeyboardKey.enter || event.logicalKey == LogicalKeyboardKey.numpadEnter)) { if (!widget.isViewMode) { _selectCheckDateOfBirth(context).then((_) { if (widget.controllers["dob"]!.text.isNotEmpty) { Future.delayed(Duration(milliseconds: 100), () { _dobFocusNode?.requestFocus(); }); } }); } return KeyEventResult.handled; // Stop default behavior } return KeyEventResult.ignored; // Allow normal handling }, child: GestureDetector( behavior: HitTestBehavior.opaque, onTapDown: (details) { if (!widget.isViewMode) { _selectCheckDateOfBirth(context).then((_) { if (widget.controllers["dob"]!.text.isNotEmpty) { Future.delayed(Duration(milliseconds: 100), () { _dobFocusNode?.requestFocus(); }); } }); } }, 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), ), ), ), ), ), ), // GestureDetector( // onTap: // widget.isViewMode // ? null // : () async { // await _selectCheckDateOfBirth(context); // if (widget.controllers["dob"]!.text.isNotEmpty) { // setState(() { // // errorMessages.remove("start_date"); // }); // // Delay re-focusing the DOB field slightly // Future.delayed(Duration(milliseconds: 100), () { // _dobFocusNode?.requestFocus(); // }); // } // }, // child: AbsorbPointer( // child: TextField( // controller: widget.controllers["dob"], // focusNode:_dobFocusNode, // 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 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: _emailFocus, isDesktop: widget.isDesktop, child: SizedBox( height: 40, child: TextField( style: GoogleFonts.poppins(fontSize: 12, color: Colors.black), focusNode: _emailFocusNode, autofocus: true, 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: _mobileNumberFocus, isDesktop: widget.isDesktop, child: SizedBox( height: 40, child: TextField( style: GoogleFonts.poppins(fontSize: 12, color: Colors.black), focusNode: _mobileNumberFocusNode, autofocus: true, 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) ...[ // Builder( // builder: (context) { // WidgetsBinding.instance.addPostFrameCallback((_) { // widget.controllers["mobileNumber"]?.clear(); // }); // return const SizedBox.shrink(); // }, // ), 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: _alternateMobileFocus, isDesktop: widget.isDesktop, child: SizedBox( height: 40, child: TextField( style: GoogleFonts.poppins(fontSize: 12, color: Colors.black), focusNode: _alternateMobileFocusNode, autofocus: true, 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, // }; 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: _countryFocus, padding: const EdgeInsets.symmetric(horizontal: 0), isDesktop: widget.isDesktop, child: SizedBox( height: 40, width: double.infinity, child: Focus( focusNode: _countryFocusNode, onFocusChange: (hasFocus) { setState(() { _countryFocus = hasFocus; }); }, child: GestureDetector( onTap: () { // Request focus when user taps _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), ), ), 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, // contentPadding: EdgeInsets.symmetric(horizontal: 1), border: OutlineInputBorder( borderRadius: BorderRadius.circular(8), borderSide: BorderSide( color: (_countryFocus) ? (layoutColor ?? Colors.blue) : Colors.white, width: 0.5, ), ), enabledBorder: OutlineInputBorder( borderSide: BorderSide( color: (_countryFocus) ? (layoutColor ?? Colors.blue) : Colors.white, width: 0.5, // const Color(0xFFD6D5E6), ), ), focusedBorder: OutlineInputBorder( borderSide: BorderSide( color: (layoutColor ?? Colors.grey), width: 0.5, ), ), contentPadding: EdgeInsets.symmetric( horizontal: 10.0, vertical: 8.0, ), ), ), 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: _postalCodeFocus, isDesktop: widget.isDesktop, child: SizedBox( height: 40, child: TextField( autofocus: true, style: GoogleFonts.poppins(fontSize: 12, color: Colors.black), focusNode: _postalCodeFocusNode, 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 buildRoleOld() { // 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, // Dropdown doesn't use focus // isDesktop: widget.isDesktop, // child: SizedBox( // height: 45, // Set appropriate height // child: DropdownButtonFormField( // // value: widget.isViewMode ? null : selectedRole, // value: selectedRole, // style: GoogleFonts.poppins(fontSize: 12, color: Colors.black), // decoration: InputDecoration( // border: InputBorder.none, // contentPadding: EdgeInsets.symmetric( // horizontal: 10, // ), // Proper padding // ), // onChanged: // widget.isViewMode // ? null // : (newValue) { // setState(() { // selectedRole = newValue; // isTravelAgent = selectedRole == "5"; // // Pass the result back to parent // widget.onUserTypeChanged?.call(isTravelAgent); // }); // widget.onRoleChanged?.call(newValue); // }, // items: // apiRoleData?.map>((item) { // return DropdownMenuItem( // value: item['dropdown_key'], // ID as value // child: Text(item['dropdown_value'] ?? "Select Role"), // ); // }).toList(), // hint: Text("Select Role"), // disabledHint: Text( // selectedRole ?? "Select Role", // style: GoogleFonts.poppins(fontSize: 12, color: Colors.black), // ), // ), // ), // ), // ], // ); // } Widget buildRole() { // Map dropdown_key (ID) -> dropdown_value (Name) // Map roleMap = { // for (var item in apiRoleData ?? []) // item['dropdown_key'] as String: item['dropdown_value'] as String, // }; Map roleMap = { for (var item in apiRoleData ?? []) if (item['dropdown_key'] != '5') item['dropdown_key'].toString(): item['dropdown_value'].toString(), }; List roleNames = roleMap.values.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, isFocused: _roleIdFocus, padding: const EdgeInsets.symmetric(horizontal: 0), isDesktop: widget.isDesktop, child: SizedBox( height: 40, child: Focus( focusNode: _roleIdFocusNode, onFocusChange: (hasFocus) { setState(() { _roleIdFocus = hasFocus; }); }, child: GestureDetector( onTap: () { // Request focus when user taps _roleIdFocusNode?.requestFocus(); }, child: DropdownSearch( selectedItem: selectedRole != null ? roleMap[selectedRole] : null, 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), border: OutlineInputBorder( borderRadius: BorderRadius.circular(5), borderSide: BorderSide( color: (_roleIdFocus) ? (layoutColor ?? Colors.blue) : Colors.white, width: 0.5, ), ), enabledBorder: OutlineInputBorder( borderSide: BorderSide( color: (_roleIdFocus) ? (layoutColor ?? Colors.blue) : Colors.white, // : const Color(0xFFD6D5E6), width: 0.5, // const Color(0xFFD6D5E6), ), ), focusedBorder: OutlineInputBorder( borderSide: BorderSide( color: (layoutColor ?? Colors.grey), width: 0.5, ), ), contentPadding: EdgeInsets.symmetric( horizontal: 8, vertical: 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: _addressFocus, isDesktop: widget.isDesktop, child: SizedBox( height: 80, child: TextField( autofocus: true, style: GoogleFonts.poppins(fontSize: 12, color: Colors.black), focusNode: _addressFocusNode, 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: _passwordFocus, isDesktop: widget.isDesktop, child: SizedBox( height: 40, child: TextField( style: GoogleFonts.poppins(fontSize: 12, color: Colors.black), focusNode: _passwordFocusNode, autofocus: true, 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() }