2053 lines
70 KiB
Dart
2053 lines
70 KiB
Dart
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<PersonalDetailsState> personalDetailsKey;
|
|
final Map<String, TextEditingController> controllers;
|
|
final Map<String, String> errorMessages;
|
|
final bool isDesktop;
|
|
final bool isViewMode;
|
|
final bool apiselectedUser;
|
|
final bool isEditProfile;
|
|
|
|
final Function(List<Map<String, dynamic>>)? onServiceIdsChanged;
|
|
|
|
final ValueChanged<String?>? onGenderChanged;
|
|
final ValueChanged<String?>? onCountryChanged;
|
|
final ValueChanged<String?>? onRoleChanged;
|
|
final void Function(bool)? onUserTypeChanged;
|
|
final List<Map<String, dynamic>>? 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,
|
|
required this.isEditProfile,
|
|
}) : super(key: key);
|
|
|
|
@override
|
|
PersonalDetailsState createState() => PersonalDetailsState();
|
|
}
|
|
|
|
class PersonalDetailsState extends State<PersonalDetails> {
|
|
final ApiService apiService = ApiService();
|
|
|
|
late Map<String, String> countryMap; // Mapping country_code -> country_name
|
|
late List<String> countryCodes; // List of country codes
|
|
late List<dynamic>? apiCountryData;
|
|
late List<dynamic>? apiRoleData;
|
|
List<dynamic>? apiAllServices;
|
|
Map<String, dynamic>? selectedOrg;
|
|
List<Map<String, dynamic>> 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<String> 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<String, TextEditingController> controllers = {};
|
|
Map<String, String> 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<Map<String, dynamic>>.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<void> 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(
|
|
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<void> fetchRoles() async {
|
|
try {
|
|
final response = await apiService.fetchMasterDropdown(context);
|
|
|
|
if (response is Map<String, dynamic> && response.containsKey("role")) {
|
|
List<dynamic> 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<void> fetchCountries() async {
|
|
try {
|
|
List<dynamic> countries = await apiService.fetchCountryList(context);
|
|
setState(() {
|
|
apiCountryData = countries;
|
|
});
|
|
} catch (e) {
|
|
print('Error fetching country list: $e');
|
|
}
|
|
}
|
|
|
|
Future<void> getOrganizationData() async {
|
|
try {
|
|
print("getUpdatedServices");
|
|
|
|
final result = await apiService.fetchOrganization(context);
|
|
print("UUPdatedServices - $result");
|
|
setState(() {
|
|
final servicesRaw = selectedOrg?['services_ids'];
|
|
|
|
List<dynamic> 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<Map<String, dynamic>>((item) {
|
|
final map = Map<String, dynamic>.from(item);
|
|
return {"service_id": map['service_id'].toString()};
|
|
}).toList();
|
|
}
|
|
|
|
// selectedServiceIds = services.map<Map<String, dynamic>>((item) {
|
|
// // force cast or copy to a regular map
|
|
// final map = Map<String, dynamic>.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<void> 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),
|
|
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<Widget> _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<String, dynamic> 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.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
|
|
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"],
|
|
inputFormatters: [
|
|
FilteringTextInputFormatter.allow(RegExp(r'[a-zA-Z0-9 _-]')),
|
|
],
|
|
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(
|
|
inputFormatters: [
|
|
FilteringTextInputFormatter.allow(RegExp(r'[a-zA-Z0-9_-]')),
|
|
],
|
|
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<String>(
|
|
// // 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<String>(
|
|
// // 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<String> 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<String>(
|
|
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<void> _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<String, String> countryMap; // Mapping country_code -> country_name
|
|
late List<String> countryCodes; // List of country codes
|
|
List<dynamic> 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<String>(
|
|
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<String>(
|
|
// // 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<DropdownMenuItem<String>>((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<String, String> roleMap = {
|
|
// for (var item in apiRoleData ?? [])
|
|
// item['dropdown_key'] as String: item['dropdown_value'] as String,
|
|
// };
|
|
Map<String, String> roleMap = {
|
|
for (var item in apiRoleData ?? [])
|
|
if (item['dropdown_key'] != '5')
|
|
item['dropdown_key'].toString(): item['dropdown_value'].toString(),
|
|
};
|
|
|
|
List<String> 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<String>(
|
|
selectedItem:
|
|
selectedRole != null ? roleMap[selectedRole] : null,
|
|
enabled: !widget.isEditProfile,
|
|
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"],
|
|
inputFormatters: [
|
|
FilteringTextInputFormatter.allow(
|
|
RegExp(r"[a-zA-Z0-9\s,.\-/#']"),
|
|
),
|
|
],
|
|
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()
|
|
}
|