ts-tat/lib/Screens/userManagement/create_traveller_agent/personalDetailsTravelAgent.dart
2025-10-08 11:37:26 +05:30

1790 lines
58 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:flutter_quill/flutter_quill_internal.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:http/http.dart' as http;
import 'package:intl/intl.dart';
import '../../../services/apiService.dart';
import '../../../utils/auth_utils.dart';
import '../../../widgets/custom_user_form.dart';
import '../../../config/apiUrl.dart';
import '../create_user/change_password.dart';
class PersonalDetails extends StatefulWidget {
final GlobalKey<PersonalDetailsState> personalDetailsKey;
final Map<String, TextEditingController> controllers;
final Map<String, String> errorMessages;
final bool isDesktop;
final bool isViewMode;
final bool apiselectedUser;
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;
// const PersonalDetails(this.isDesktop, this.isViewMode, {super.key},);
const PersonalDetails({
Key? key,
this.selectedGender,
this.selectedCountry,
this.selectedRole,
this.onServiceIdsChanged,
required this.personalDetailsKey,
this.initialSelectedServices,
required this.controllers,
required this.errorMessages,
required this.isDesktop,
required this.isViewMode,
required this.apiselectedUser,
this.onGenderChanged,
this.onCountryChanged,
this.onRoleChanged,
this.onUserTypeChanged,
this.userIdApi,
}) : super(key: key);
@override
PersonalDetailsState createState() => PersonalDetailsState();
}
class PersonalDetailsState extends State<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 = [];
Map<String, FocusNode> focusNodes = {};
Map<String, bool> focusStates = {};
String? get selectedGenderValue => selectedGender;
String? get selectedCountryValue => selectedCountry;
String? get selectedRoleValue => selectedRole;
late bool isTravelAgent = false;
String? selectedTab;
String? selectedCountry;
String? selectedGender;
String? selectedUserType;
String? selectedRole;
String? selectedLevel;
String? selectedDepartment;
String? selectedFirstApprover;
String? selectedSecondApprover;
String? selectedThirdApprover;
String? selectedFileNames;
Uint8List? passportDocumentBytes;
String? passportFileUrlFromApi;
String? base64PDF;
html.File? passportFile;
List<String> dataHeader = [
"Fname",
"Lname",
"Cname",
"email",
"password",
"dob",
"gender",
"mobileNumber",
"alternateMobile",
"address",
"postalCode",
"country",
"passportNumber",
"placeOfIssue",
"passportDoc",
"userType",
"roleId",
"deptId",
"levelId",
"firstApproval",
"secondApproval",
"thirdApproval",
"employeeCode",
"dateOfIssue",
"dateOfExpiry",
"changePassword",
];
Color? layoutColor;
Color? bodyColor;
final Map<String, TextEditingController> controllers = {};
Map<String, String> errorMessages2 = {};
@override
void initState() {
super.initState();
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");
for (var field in dataHeader) {
focusNodes["${field}FocusNode"] = FocusNode();
focusStates["${field}Focused"] = false;
}
for (var key in focusNodes.keys) {
_addFocusListener(focusNodes[key]!, (focus) {
setState(() {
focusStates[key.replaceFirst("FocusNode", "Focused")] = focus;
});
});
}
WidgetsBinding.instance.addPostFrameCallback((_) {
loadAllServices();
getOrganizationData();
loadInitialData();
});
}
@override
void dispose() {
for (var node in focusNodes.values) {
node.dispose();
}
super.dispose();
}
void _addFocusListener(FocusNode node, Function(bool) updateState) {
node.addListener(() {
setState(() {
updateState(node.hasFocus);
});
});
}
Future<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.");
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(() {
final filteredRole =
roleList.where((item) => item['dropdown_key'] == '5').toList();
apiRoleData = filteredRole;
});
print("APIROLEData - $apiRoleData");
} else {
print("Error: 'role' key not found or response is not a Map.");
}
} catch (e) {
print('Error fetching role list: $e');
}
}
Future<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: 10),
_buildFirstRow(widget.isDesktop),
SizedBox(height: 10),
// _buildSecondRow(widget.isDesktop),
// SizedBox(height: 10),
_buildThirdRow(widget.isDesktop),
SizedBox(height: 10),
_buildForthRow(widget.isDesktop),
SizedBox(height: 10),
_buildServices(widget.isDesktop),
],
),
),
);
}
Future<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),
if (widget.errorMessages["agent_supported_service_ids"] != null) ...[
SizedBox(height: 5), // Space before error message
Align(
alignment: Alignment.center,
child: Text(
widget.errorMessages["agent_supported_service_ids"]!,
style: GoogleFonts.poppins(color: Colors.red, fontSize: 12),
),
),
],
Container(
decoration: BoxDecoration(
border: Border.all(color: Color(0xFFF4F4FB)),
borderRadius: BorderRadius.circular(1),
// color: bodyColor,
// color: Color(0xFFF5F5F5),
color: Colors.white,
),
padding: EdgeInsets.only(left: 5, right: 5, top: 15, bottom: 5),
child:
isDesktop
? SingleChildScrollView(
scrollDirection: Axis.vertical,
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(children: _buildOptions()),
),
)
: Expanded(
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(children: _buildOptions()),
),
),
),
],
);
}
List<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,
),
),
SizedBox(width: 5),
// if (selectedListOption == title && widget.isViewMode == false)
Container(
height: 15,
width: 15,
decoration: BoxDecoration(
shape: BoxShape.rectangle,
border: Border.all(
color: isSelected ? Colors.green : Colors.grey.shade400,
width: isSelected ? 2 : 1,
),
),
child: Icon(
Icons.check,
size: 10,
color: isSelected ? Colors.green : Colors.white10,
// color: Colors.grey,
),
),
],
),
);
}
IconData _getLocalIconForService(String name) {
switch (name.toLowerCase()) {
case 'flight':
return Icons.flight_takeoff_outlined;
case 'train':
return Icons.train_outlined;
case 'bus':
return Icons.directions_bus;
case 'taxi':
return Icons.local_taxi_outlined;
case 'accomodation':
return Icons.local_hotel_outlined;
case 'forex':
return Icons.attach_money_outlined;
case 'insurance':
return Icons.list_alt_outlined;
case 'visa':
return Icons.badge_outlined;
case 'miscellaneous':
return Icons.card_giftcard_outlined;
default:
return Icons.circle_notifications;
}
}
Widget _buildFirstRow(bool isDesktop) {
return Container(
color: Colors.white,
child:
widget.isDesktop
? Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
buildFirstNameField(),
Spacer(),
buildLastNameField(),
Spacer(), // Space after Last Name
buildCompanyNameField(),
Spacer(),
// buildGenderField(),
// SizedBox(width: 15),
// buildDobField(),
buildMobileField(),
Spacer(),
buildAlternateMobileField(),
],
)
: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
buildFirstNameField(),
SizedBox(height: 8), // Vertical space
buildLastNameField(),
SizedBox(height: 8),
buildCompanyNameField(),
SizedBox(height: 8),
// buildGenderField(),
// SizedBox(height: 8),
// buildDobField(),
buildMobileField(),
SizedBox(height: 8), // Space after Last Name
buildAlternateMobileField(),
],
),
);
}
// Widget _buildSecondRow(bool isDesktop) {
// return Container(
// color: Colors.white,
// child:
// widget.isDesktop
// ? Row(
// crossAxisAlignment: CrossAxisAlignment.start,
// children: [
// // buildEmailField(),
// // Spacer(),
// // buildMobileField(),
// // Spacer(), // Space after Last Name
// // buildAlternateMobileField(),
// ],
// )
// : Column(
// crossAxisAlignment: CrossAxisAlignment.start,
// children: [
// // buildMobileField(),
// // SizedBox(height: 8), // Vertical space
// // buildAlternateMobileField(),
// // SizedBox(height: 8),
// // buildEmailField(),
// ],
// ),
// );
// }
Widget _buildThirdRow(bool isDesktop) {
void _openPopup() {
final emailValue = widget.controllers["email"]?.text ?? "";
final updaterUserId = widget.userIdApi ?? "";
showDialog(
context: context,
builder: (context) {
return ChangePasswordDialogData(
updaterEmail: emailValue,
updaterUserId: updaterUserId,
layoutColor: layoutColor,
isDesktop: widget.isDesktop,
);
},
);
}
return Container(
color: Colors.white,
child:
isDesktop
? !widget.apiselectedUser
? Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (!widget.apiselectedUser) ...[
buildEmailField(),
// SizedBox(
// width: MediaQuery.of(context).size.width * 0.014,
// ),
Spacer(),
buildPassword(),
// SizedBox(
// width: MediaQuery.of(context).size.width * 0.015,
// ),
Spacer(),
buildRole(),
],
],
)
: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
buildEmailField(),
SizedBox(width: 10),
// Spacer(),
// Container(
// width:
// isDesktop
// ? MediaQuery.of(context).size.width * 0.25
// : MediaQuery.of(context).size.width * 0.8,
// child: Row(
// mainAxisAlignment: MainAxisAlignment.end,
// children: [
// TextButton(
// onPressed: () => _openPopup(),
// style: TextButton.styleFrom(
// foregroundColor: Color(0xFF114D8B),
// textStyle: GoogleFonts.poppins(
// fontSize: 12,
// fontWeight: FontWeight.w400,
// ),
// ),
// child: Text("Change Password"),
// ),
// ],
// ),
// ),
buildchangPswd(),
// SizedBox(height: 1),
Spacer(),
buildRole(),
],
)
: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (!widget.apiselectedUser) ...[
buildEmailField(),
SizedBox(height: 8),
buildPassword(),
SizedBox(height: 8),
buildRole(),
] else ...[
Container(
width:
isDesktop
? MediaQuery.of(context).size.width * 0.25
: MediaQuery.of(context).size.width * 0.8,
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
TextButton(
onPressed: () => _openPopup(),
style: TextButton.styleFrom(
foregroundColor: Color(0xFF114D8B),
textStyle: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w400,
),
),
child: Text("Change Password"),
),
],
),
),
SizedBox(height: 1),
buildRole(),
],
],
),
);
}
Widget _buildForthRow(bool isDesktop) {
return Container(
color: Colors.white,
child:
isDesktop
? Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
buildAddress(),
Spacer(),
// SizedBox(width: 15),
buildCountryField(),
Spacer(),
// SizedBox(width: 15),
buildPostalCodeField(),
],
)
: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
buildAddress(),
SizedBox(height: 8), //
buildCountryField(),
SizedBox(height: 8), // Vertical space
buildPostalCodeField(),
],
),
);
}
Widget buildFirstNameField() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"First Name *",
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74),
),
),
SizedBox(height: 5),
CustomTextFieldUserWrapper(
isFocused: focusStates["FnameFocused"] ?? false,
isDesktop: widget.isDesktop,
width:
widget.isDesktop
? MediaQuery.of(context).size.width * 0.12
: null,
child: SizedBox(
height: 40,
child: TextField(
style: GoogleFonts.poppins(fontSize: 12, color: Colors.black),
focusNode: focusNodes["FnameFocusNode"],
controller: widget.controllers["Fname"],
enabled: !widget.isViewMode,
onChanged: (value) {
_clearError("first_name");
},
decoration: InputDecoration(
labelText: "FirstName",
labelStyle: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w400,
color: Colors.grey,
),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16),
),
),
),
),
if (widget.errorMessages["first_name"] != null) ...[
SizedBox(height: 5), // Space before error message
Text(
widget.errorMessages["first_name"]!,
style: GoogleFonts.poppins(color: Colors.red, fontSize: 12),
),
],
],
);
}
Widget buildLastNameField() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Last Name*",
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74),
),
),
SizedBox(height: 5),
CustomTextFieldUserWrapper(
isFocused: focusStates["LnameFocused"] ?? false,
isDesktop: widget.isDesktop,
width:
widget.isDesktop
? MediaQuery.of(context).size.width * 0.12
: null,
child: SizedBox(
height: 40,
child: TextField(
style: TextStyle(fontSize: 12, color: Colors.black),
focusNode: focusNodes["LnameFocusNode"],
controller: widget.controllers["Lname"],
enabled: !widget.isViewMode,
onChanged: (value) {
_clearError("last_name");
},
decoration: InputDecoration(
labelText: "LastName",
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16),
),
),
),
),
if (widget.errorMessages["last_name"] != null) ...[
SizedBox(height: 5), // Space before error message
Text(
widget.errorMessages["last_name"]!,
style: GoogleFonts.poppins(color: Colors.red, fontSize: 12),
),
],
],
);
}
Widget buildCompanyNameField() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Company Name",
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74),
),
),
SizedBox(height: 5),
CustomTextFieldUserWrapper(
isFocused: focusStates["CnameFocused"] ?? false,
isDesktop: widget.isDesktop,
child: SizedBox(
height: 40,
child: TextField(
style: TextStyle(fontSize: 12, color: Colors.black),
focusNode: focusNodes["CnameFocusNode"],
controller: widget.controllers["Cname"],
enabled: !widget.isViewMode,
decoration: InputDecoration(
labelText: "CompanyName",
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16),
),
),
),
),
],
);
}
Widget buildGenderField() {
List<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: false,
isDesktop: widget.isDesktop,
child: SizedBox(
height: 40,
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,
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: false,
isDesktop: widget.isDesktop,
child: SizedBox(
height: 40,
child: GestureDetector(
onTap:
widget.isViewMode
? null
: () async {
await _selectCheckDateOfBirth(context);
if (widget.controllers["dob"]!.text.isNotEmpty) {
setState(() {
// errorMessages.remove("start_date");
});
}
},
child: AbsorbPointer(
child: TextField(
controller: widget.controllers["dob"],
readOnly: true,
style: GoogleFonts.poppins(fontSize: 12, color: Colors.black),
decoration: InputDecoration(
labelText: "Select Date",
labelStyle: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w400,
color: Colors.grey,
),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16),
suffixIcon: Icon(
Icons.calendar_today,
size: 16,
color: Color(0xFF8B8FB2),
),
),
),
),
),
),
),
],
);
}
Widget buildchangPswd() {
void _openPopup() {
final emailValue = widget.controllers["email"]?.text ?? "";
final updaterUserId = widget.userIdApi ?? "";
showDialog(
context: context,
builder: (context) {
return ChangePasswordDialogData(
updaterEmail: emailValue,
updaterUserId: updaterUserId,
layoutColor: layoutColor,
isDesktop: widget.isDesktop,
);
},
);
}
return Container(
width:
isDesktop
? MediaQuery.of(context).size.width * 0.25
: MediaQuery.of(context).size.width * 0.8,
child: Column(
// mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(height: 30),
TextButton(
onPressed: () => _openPopup(),
style: TextButton.styleFrom(
foregroundColor: Color(0xFF114D8B),
textStyle: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w400,
),
),
child: Text("Change Password"),
),
],
),
);
}
Widget buildEmailField() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Email *",
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74),
),
),
SizedBox(height: 5),
CustomTextFieldUserWrapper(
isFocused: focusStates["emailFocused"] ?? false,
isDesktop: widget.isDesktop,
child: SizedBox(
height: 40,
child: TextField(
focusNode: focusNodes["emailFocusNode"],
style: GoogleFonts.poppins(fontSize: 12, color: Colors.black),
controller: widget.controllers["email"],
onChanged: (value) {
_clearError("email");
apiCheckDuplicate("Email", "email", value, widget.userIdApi);
},
enabled: !widget.isViewMode,
decoration: InputDecoration(
labelText: "Email",
labelStyle: GoogleFonts.poppins(
fontSize: 12,
color: Colors.grey,
),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16),
),
),
),
),
if (widget.errorMessages["email"] != null) ...[
SizedBox(height: 5), // Space before error message
Text(
widget.errorMessages["email"]!,
style: GoogleFonts.poppins(color: Colors.red, fontSize: 12),
),
],
],
);
}
Widget buildMobileField() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Mobile Number *",
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74),
),
),
SizedBox(height: 5),
CustomTextFieldUserWrapper(
isFocused: focusStates["mobileNumberFocused"] ?? false,
isDesktop: widget.isDesktop,
width:
widget.isDesktop
? MediaQuery.of(context).size.width * 0.12
: null,
child: SizedBox(
height: 40,
child: TextField(
style: GoogleFonts.poppins(fontSize: 12, color: Colors.black),
focusNode: focusNodes["mobileNumberFocusNode"],
controller: widget.controllers["mobileNumber"],
enabled: !widget.isViewMode,
onChanged: (value) {
_clearError("mobile_no");
apiCheckDuplicate(
"Mobile Number",
"mobile_no",
value,
widget.userIdApi,
);
},
keyboardType: TextInputType.numberWithOptions(decimal: true),
inputFormatters: [
FilteringTextInputFormatter.allow(
RegExp(r'^\d*\.?\d*$'),
), // Allow only positive numbers with optional decimal
],
decoration: InputDecoration(
labelText: "Mobile Number",
labelStyle: GoogleFonts.poppins(
fontSize: 12,
color: Colors.grey,
),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16),
),
),
),
),
if (widget.errorMessages["mobile_no"] != null) ...[
SizedBox(height: 5), // Space before error message
Text(
widget.errorMessages["mobile_no"]!,
style: GoogleFonts.poppins(color: Colors.red, fontSize: 12),
),
],
],
);
}
Widget buildAlternateMobileField() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Alternate Mobile Number",
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74),
),
),
SizedBox(height: 5),
CustomTextFieldUserWrapper(
// isFocused: false,
isFocused: focusStates["alternateMobileFocused"] ?? false,
isDesktop: widget.isDesktop,
width:
widget.isDesktop
? MediaQuery.of(context).size.width * 0.12
: null,
child: SizedBox(
height: 40,
child: TextField(
focusNode: focusNodes["alternateMobileFocusNode"],
style: GoogleFonts.poppins(fontSize: 12, color: Colors.black),
controller: widget.controllers["alternateMobile"],
enabled: !widget.isViewMode,
keyboardType: TextInputType.numberWithOptions(decimal: true),
inputFormatters: [
FilteringTextInputFormatter.allow(
RegExp(r'^\d*\.?\d*$'),
), // Allow only positive numbers with optional decimal
],
decoration: InputDecoration(
labelText: "Alternate Number",
labelStyle: GoogleFonts.poppins(
fontSize: 12,
color: Colors.grey,
),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16),
),
),
),
),
if (widget.errorMessages["alternate_mobile_no"] != null) ...[
SizedBox(height: 5), // Space before error message
Text(
widget.errorMessages["alternate_mobile_no"]!,
style: GoogleFonts.poppins(color: Colors.red, fontSize: 12),
),
],
],
);
}
Widget buildCountryField() {
late Map<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,
// // item['currency'] as String
// };
countryMap = {
for (var country in countryList)
(country['country_code'] ?? ''):
'${country['country_name'] ?? ''} (${country['country_code'] ?? ''})',
};
// Extract only country codes for processing
countryCodes = countryMap.keys.toList();
selectedCountry ??= null;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Country",
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74),
),
),
SizedBox(height: 5),
CustomTextFieldUserWrapper(
isFocused: focusStates["countryFocusNode"] ?? false,
padding: const EdgeInsets.symmetric(horizontal: 0),
isDesktop: widget.isDesktop,
child: SizedBox(
height: 40,
width: double.infinity,
child: Focus(
focusNode: focusNodes["countryFocusNode"],
onFocusChange: (hasFocus) {
setState(() {
focusStates["countryFocused"] = hasFocus;
});
},
child: GestureDetector(
onTap: () {
// Request focus when user taps
focusNodes["countryFocusNode"]?.requestFocus();
},
child: DropdownSearch<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),
// ),
// ),
(context, item, isSelected) {
print("contryItem - $item");
final match = RegExp(
r'^(.*)\s\((.*)\)$',
).firstMatch(item);
final countryName = match?.group(1) ?? '';
final countryCode = match?.group(2) ?? '';
return Padding(
padding: const EdgeInsets.symmetric(
horizontal: 10.0,
vertical: 6.0,
),
child: Padding(
// padding: const EdgeInsets.all(3.0),
padding: EdgeInsets.symmetric(
horizontal: 2,
vertical: 3,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
countryName,
style: GoogleFonts.poppins(fontSize: 11.5),
),
Text(
countryCode,
style: GoogleFonts.poppins(
fontSize: 11.5,
color: Colors.grey,
),
),
],
),
),
);
},
constraints: BoxConstraints(maxHeight: 180),
searchFieldProps: TextFieldProps(
decoration: InputDecoration(
hintText: "Search Country...",
contentPadding: EdgeInsets.symmetric(
horizontal: 3,
vertical: 3,
),
),
),
),
items: countryMap.values.toList(),
dropdownDecoratorProps: DropDownDecoratorProps(
dropdownSearchDecoration: InputDecoration(
// border: InputBorder.none,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide(
color:
(focusStates["countryFocused"] ?? false)
? (layoutColor ?? Colors.blue)
: Colors.white,
// width: 0.5,
),
),
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(
color:
(focusStates["countryFocused"] ?? false)
? (layoutColor ?? Colors.blue)
: Colors.white,
// width: 0.5,
// const Color(0xFFD6D5E6),
),
),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(
color: (layoutColor ?? Colors.grey),
width: 1,
),
),
contentPadding: EdgeInsets.symmetric(
horizontal: 10.0,
vertical: 8.0,
),
// contentPadding: EdgeInsets.symmetric(horizontal: 1),
),
),
dropdownBuilder:
(context, selectedItem) => Align(
// Center-align selected item
alignment: Alignment.centerLeft,
child: Text(
selectedItem ?? "Select Country",
style: GoogleFonts.poppins(
fontSize: 12,
color: Colors.black,
),
),
),
// onChanged: widget.isViewMode
// ? null
// : (String? newValue) {
// setState(() {
// // Find the country_code based on selected country_name
// selectedCountry = countryMap.entries
// .firstWhere((entry) => entry.value == newValue)
// .key;
//
// // if (selectedCountry!.isNotEmpty) {
// // errorMessages.remove("country_code");
// // }
// });
// widget.onCountryChanged?.call(newValue);
// },
onChanged:
widget.isViewMode
? null
: (String? newValue) {
if (newValue == null) return;
final countryCode =
countryMap.entries
.firstWhere(
(entry) => entry.value == newValue,
)
.key;
setState(() {
selectedCountry = countryCode;
});
widget.onCountryChanged?.call(
countryCode,
); // ✅ send code not name
},
),
),
),
),
),
],
);
}
Widget buildPostalCodeField() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Postal Code",
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74),
),
),
SizedBox(height: 5),
CustomTextFieldUserWrapper(
isFocused: focusStates["postalCodeFocused"] ?? false,
isDesktop: widget.isDesktop,
child: SizedBox(
height: 40,
child: TextField(
focusNode: focusNodes["postalCodeFocusNode"],
style: GoogleFonts.poppins(fontSize: 12, color: Colors.black),
controller: widget.controllers["postalCode"],
enabled: !widget.isViewMode,
keyboardType: TextInputType.numberWithOptions(decimal: true),
inputFormatters: [
FilteringTextInputFormatter.allow(
RegExp(r'^\d*\.?\d*$'),
), // Allow only positive numbers with optional decimal
],
decoration: InputDecoration(
labelText: "Postal Code",
labelStyle: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w400,
color: Colors.grey,
),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16),
),
),
),
),
],
);
}
Widget buildRole() {
Map<String, String> roleMap = {
for (var item in apiRoleData ?? [])
item['dropdown_key'] as String: item['dropdown_value'] as String,
};
// List<String> roleNames = roleMap.values.toList();
List<String> roleNames = roleMap.values.take(5).toList();
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Role",
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74),
),
),
SizedBox(height: 5),
CustomTextFieldUserWrapper(
isFocused: false,
isDesktop: widget.isDesktop,
child: SizedBox(
height: 40,
child: DropdownSearch<String>(
selectedItem: roleMap[selectedRole],
enabled: false, //enabled: !widget.isViewMode,
popupProps: PopupProps.menu(
showSearchBox: true,
fit: FlexFit.loose,
constraints: BoxConstraints(maxHeight: 250),
menuProps: const MenuProps(backgroundColor: Colors.white),
itemBuilder:
(context, item, isSelected) => Container(
color: Colors.white,
padding: EdgeInsets.symmetric(
horizontal: 10,
vertical: 6,
),
child: Text(
item,
style: GoogleFonts.poppins(fontSize: 11.5),
),
),
searchFieldProps: TextFieldProps(
decoration: InputDecoration(
hintText: "Search Role...",
contentPadding: EdgeInsets.symmetric(horizontal: 10),
),
),
),
items: roleNames,
dropdownDecoratorProps: DropDownDecoratorProps(
dropdownSearchDecoration: InputDecoration(
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(horizontal: 1),
),
),
dropdownBuilder:
(context, selectedItem) => Align(
alignment: Alignment.centerLeft,
child: Text(
selectedItem ?? "Select Role",
style: GoogleFonts.poppins(
fontSize: 12,
color: Colors.black,
),
),
),
onChanged:
widget.isViewMode
? null
: (String? newValue) {
widget.errorMessages.remove("role_id");
if (newValue == null) return;
final selectedKey =
roleMap.entries
.firstWhere((entry) => entry.value == newValue)
.key;
setState(() {
selectedRole = selectedKey;
isTravelAgent = selectedKey == "5";
});
widget.onUserTypeChanged?.call(isTravelAgent);
widget.onRoleChanged?.call(selectedKey);
},
),
),
),
if (widget.errorMessages["role_id"] != null) ...[
SizedBox(height: 5), // Space before error message
Text(
widget.errorMessages["role_id"]!,
style: GoogleFonts.poppins(color: Colors.red, fontSize: 12),
),
],
],
);
}
Widget buildAddress() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Address",
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74),
),
),
SizedBox(height: 5),
CustomTextFieldUserWrapper(
isFocused: focusStates["addressFocused"] ?? false,
isDesktop: widget.isDesktop,
child: SizedBox(
height: 80,
child: TextField(
focusNode: focusNodes["addressFocusNode"],
style: GoogleFonts.poppins(fontSize: 12, color: Colors.black),
controller: widget.controllers["address"],
enabled: !widget.isViewMode,
maxLines: 2,
keyboardType: TextInputType.multiline,
decoration: InputDecoration(
labelText: "Address",
labelStyle: GoogleFonts.poppins(
fontSize: 12,
color: Colors.grey,
),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
// contentPadding: EdgeInsets.symmetric(vertical: 16),
contentPadding: EdgeInsets.symmetric(vertical: 4),
),
),
),
),
],
);
}
Widget buildPassword() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Password *",
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74),
),
),
SizedBox(height: 5),
CustomTextFieldUserWrapper(
isFocused: focusStates["passwordFocused"] ?? false,
isDesktop: widget.isDesktop,
child: SizedBox(
height: 40,
child: TextField(
focusNode: focusNodes["passwordFocusNode"],
style: GoogleFonts.poppins(fontSize: 12, color: Colors.black),
controller: widget.controllers["password"],
enabled: !widget.isViewMode,
onChanged: (value) {
_clearError("password");
},
decoration: InputDecoration(
labelText: "Password",
labelStyle: GoogleFonts.poppins(
fontSize: 12,
color: Colors.grey,
),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16),
),
),
),
),
if (widget.errorMessages["password"] != null) ...[
SizedBox(height: 5), // Space before error message
Text(
widget.errorMessages["password"]!,
style: GoogleFonts.poppins(color: Colors.red, fontSize: 12),
),
],
],
);
}
// apiselectedUser != null
// ? SizedBox()
}