BUGFIX_USER_MNGMT

This commit is contained in:
venbaittech 2025-07-18 18:21:11 +05:30
parent 186a07d126
commit 80db60c39c
4 changed files with 340 additions and 112 deletions

View File

@ -509,7 +509,8 @@ class _ApprovalListState extends State<ApprovalList> {
Future<void> _showDetails(id) async {
_dialogShown = true;
final String apiUrldata = '$apiUrl/api/plans/get_plan_approval_status?plan_id=$id';
final String apiUrldata =
'$apiUrl/api/plans/get_plan_approval_status?plan_id=$id';
final String? token = await getToken();
String label = "";
List<Map<String, dynamic>> approverData = [];
@ -530,7 +531,9 @@ class _ApprovalListState extends State<ApprovalList> {
if (response.statusCode == 200) {
final data = json.decode(response.body);
label = data['data']['model_heading'] ?? "";
approverData = List<Map<String, dynamic>>.from(data['data']['approver_data'] ?? []);
approverData = List<Map<String, dynamic>>.from(
data['data']['approver_data'] ?? [],
);
} else {
print('Failed to load');
label = "Error - Failed to load data";
@ -543,76 +546,104 @@ class _ApprovalListState extends State<ApprovalList> {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text(
label,
style: GoogleFonts.poppins(
fontSize: 18,
fontWeight: FontWeight.w600,
color: Colors.black,
),
overflow: TextOverflow.ellipsis,
),
content: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
if (approverData.isNotEmpty)
SizedBox(
height: 250,
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: SingleChildScrollView(
scrollDirection: Axis.vertical,
child: DataTable(
columns: [
// DataColumn(label: Text('S.No', style: GoogleFonts.poppins(fontSize: 14, fontWeight: FontWeight.w600))),
// DataColumn(label: Text('Approver ID', style: GoogleFonts.poppins(fontSize: 14, fontWeight: FontWeight.w600))),
DataColumn(label: Text('Status', style: GoogleFonts.poppins(fontSize: 14, fontWeight: FontWeight.w600))),
],
rows: approverData.asMap().entries.map((entry) {
final index = entry.key + 1;
final item = entry.value;
final approverIdKey = item.keys.firstWhere((k) => k.endsWith('_id'), orElse: () => '');
final statusKey = item.keys.firstWhere((k) => k.endsWith('_status'), orElse: () => '');
final approverId = item[approverIdKey]?.toString() ?? '-';
final statusText = item[statusKey]?.toString() ?? 'Unknown';
return DataRow(
cells: [
// DataCell(Text('$index')),
// DataCell(SizedBox(width: 120, child: Text(approverId, overflow: TextOverflow.ellipsis))),
DataCell(SizedBox(child: Text(statusText, overflow: TextOverflow.ellipsis))),
builder:
(context) => AlertDialog(
title: Text(
label,
style: GoogleFonts.poppins(
fontSize: 18,
fontWeight: FontWeight.w600,
color: Colors.black,
),
overflow: TextOverflow.ellipsis,
),
content: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
if (approverData.isNotEmpty)
SizedBox(
height: 250,
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: SingleChildScrollView(
scrollDirection: Axis.vertical,
child: DataTable(
columns: [
// DataColumn(label: Text('S.No', style: GoogleFonts.poppins(fontSize: 14, fontWeight: FontWeight.w600))),
// DataColumn(label: Text('Approver ID', style: GoogleFonts.poppins(fontSize: 14, fontWeight: FontWeight.w600))),
DataColumn(
label: Text(
'Status',
style: GoogleFonts.poppins(
fontSize: 14,
fontWeight: FontWeight.w600,
),
),
),
],
);
}).toList(),
rows:
approverData.asMap().entries.map((entry) {
final index = entry.key + 1;
final item = entry.value;
final approverIdKey = item.keys.firstWhere(
(k) => k.endsWith('_id'),
orElse: () => '',
);
final statusKey = item.keys.firstWhere(
(k) => k.endsWith('_status'),
orElse: () => '',
);
final approverId =
item[approverIdKey]?.toString() ?? '-';
final statusText =
item[statusKey]?.toString() ?? 'Unknown';
return DataRow(
cells: [
// DataCell(Text('$index')),
// DataCell(SizedBox(width: 120, child: Text(approverId, overflow: TextOverflow.ellipsis))),
DataCell(
SizedBox(
child: Text(
statusText,
overflow: TextOverflow.ellipsis,
),
),
),
],
);
}).toList(),
),
),
),
)
else
Text(
"-- no data. --",
style: GoogleFonts.poppins(
fontSize: 14,
fontWeight: FontWeight.w300,
),
),
),
)
else
Text("-- no data. --", style: GoogleFonts.poppins(fontSize: 14, fontWeight: FontWeight.w300)),
],
),
),
actions: [
TextButton(
onPressed: () {
Navigator.pop(context);
_dialogShown = false;
},
child: Text("Close"),
),
],
),
),
actions: [
TextButton(
onPressed: () {
Navigator.pop(context);
_dialogShown = false;
},
child: Text("Close"),
),
],
),
);
}
Widget build(BuildContext context) {
return ResponsiveBuilder(
builder: (context, sizingInfo) {
@ -688,7 +719,10 @@ class _ApprovalListState extends State<ApprovalList> {
return Container(
margin: isDesktop ? EdgeInsets.all(10.0) : null,
padding: const EdgeInsets.only(top: 15, bottom: 15, left: 20, right: 20),
padding:
isDesktop
? const EdgeInsets.only(top: 15, bottom: 15, left: 20, right: 20)
: EdgeInsets.all(3.0),
height:
isDesktop
? MediaQuery.of(context).size.height * 0.98
@ -788,10 +822,10 @@ class _ApprovalListState extends State<ApprovalList> {
isDesktop
? SizedBox.shrink()
: Row(
mainAxisAlignment: MainAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
width: MediaQuery.of(context).size.width * 0.8,
width: MediaQuery.of(context).size.width * 0.82,
height: 35,
child: TextField(
controller: searchController,
@ -858,7 +892,8 @@ class _ApprovalListState extends State<ApprovalList> {
// fontWeight: FontWeight.bold,
// color: Colors.redAccent)),
SizedBox(height: adjHgt / 4),
Text("No Data Found",
Text(
"No Data Found",
textAlign: TextAlign.center,
style: GoogleFonts.poppins(
fontSize: 20,
@ -1138,14 +1173,20 @@ class _ApprovalListState extends State<ApprovalList> {
height: 25,
alignment: Alignment.center,
decoration: BoxDecoration(
color: getStatusColor(plan.statusValue),
borderRadius: BorderRadius.circular(8),
color: getStatusColor(
plan.statusValue,
),
borderRadius: BorderRadius.circular(
8,
),
),
child: Text(
plan.statusValue,
textAlign: TextAlign.center,
style: TextStyle(
color: getStatusTextColor(plan.statusValue),
color: getStatusTextColor(
plan.statusValue,
),
fontSize: 12,
fontFamily: "Inter",
fontWeight: FontWeight.w400,
@ -1412,8 +1453,12 @@ class _ApprovalListState extends State<ApprovalList> {
vertical: 4,
),
decoration: BoxDecoration(
color: getStatusColor(plan.statusValue),
borderRadius: BorderRadius.circular(8),
color: getStatusColor(
plan.statusValue,
),
borderRadius: BorderRadius.circular(
8,
),
),
child: Text(
plan.statusValue,

View File

@ -31,7 +31,10 @@ class _ColorThemePickerWidgetState extends State<ColorThemePickerWidget> {
Color(0xFF448AFF), // BlueAccent
Color(0xFF027E87), // Blue Shade
Color(0xFF12B24B), // Green
Color(0xFF2ECC71), // Emerald Green
Color(0xFFe30f01), // Emerald Green
// Color(0xFFef3030), // Emerald Green
Color(0xFFf26157), // Emerald Green
// Color(0xFF2ECC71), // Emerald Green
Color(0xFFFF9800), // Orange
Color(0xFFE67E22), // Orange 2
Color(0xFFBF4C0D), // Orange dark orange 3

View File

@ -61,10 +61,10 @@ class _CreateUserFormDetialsState extends State<CreateUserFormDetials> {
bool isCheckingToken = false;
bool isDisable = false;
bool isValid = false;
final FocusNode submitButtonFocusNode = FocusNode();
String? token;
late bool isapiselectedUser = false;
@ -180,7 +180,7 @@ class _CreateUserFormDetialsState extends State<CreateUserFormDetials> {
"first_approver": selectedFirstApprover,
"second_approver": selectedSecondApprover,
"third_approver": selectedThirdApprover,
"exceptional_approver" : selectedExceptionalApprover,
"exceptional_approver": selectedExceptionalApprover,
"delegated_to_user_id": selectedSubstituteApprover,
// "passport_number": controllers["passportNumber"]?.text,
// "place_of_issue": controllers["placeOfIssue"]?.text,
@ -784,9 +784,10 @@ class _CreateUserFormDetialsState extends State<CreateUserFormDetials> {
final currentTab = tabKeys[currentIndex];
if (currentTab == "personal") {
isValid = isValidData(userDetials);
// isValid = isValidData(userDetials);
isValid = await isValidData(userDetials);
} else if (currentTab == "office") {
isValid = isValidDataTwo(userDetials);
isValid = await isValidDataTwo(userDetials);
} else {
isValid = true; // Travel tab might not need validation at this point
}
@ -827,7 +828,7 @@ class _CreateUserFormDetialsState extends State<CreateUserFormDetials> {
Map<String, dynamic> data = userDetials;
print("userDetials ====> $userDetials");
if (!isValidData(data)) {
if (!await isValidData(data)) {
print("USERDETAILS : $userDetials");
print("Validation Failed: Required fields are missing.");
setState(() {});
@ -942,7 +943,7 @@ class _CreateUserFormDetialsState extends State<CreateUserFormDetials> {
// Map<String, dynamic> data = userDetials;
if (!isValidData(data) && isValidDataTwo(data)) {
if (!await isValidData(data) && !await isValidDataTwo(data)) {
print("USERDETAILS : $userDetials");
print("Validation Failed: Required fields are missing.");
setState(() {});
@ -958,7 +959,72 @@ class _CreateUserFormDetialsState extends State<CreateUserFormDetials> {
if (mounted) Navigator.of(context, rootNavigator: true).pop();
}
bool isValidData(Map<String, dynamic> data) {
// Future<bool> apiCheckDuplicate(
// String label,
// String field,
// String value,
// String? userId,
// ) async
// {
// try {
// // Basic validation: Check mobile number length
// if (field == "mobile_no" && value.length != 10) {
// return true; // Skip the API call if input is invalid
// }
//
// final response = await apiService.CheckDuplicate(
// label,
// field,
// value,
// userId,
// );
// if (response.isNotEmpty) {
// // _clearError(field);
// errorMessages[field] = response['message'] ?? "$label Already Exists";
// print("Duplicate found: ${response['message']}");
//
// setState(() {});
// return true;
// } else {
// _clearError(field);
// print("No duplicates found.");
//
// return false;
// }
// } catch (e) {
// print("Error in checkDuplicate: $e");
// return false;
// }
// }
Future<bool> apiCheckDuplicate(
String label,
String field,
String value,
String? userId,
) {
return apiService.CheckDuplicate(label, field, value, userId)
.then((response) {
if (response.isNotEmpty) {
errorMessages[field] =
response['message'] ?? "$label Already Exists";
print("Duplicate found: ${response['message']}");
// setState(() {});
print("Duplicate found: true");
return true;
} else {
_clearError(field);
print("No duplicates found.");
return false;
}
})
.catchError((e) {
print("Error in checkDuplicate: $e");
return false;
});
}
Future<bool> isValidData(Map<String, dynamic> data) async {
errorMessages.clear(); // Reset errors
// Required fields that must not be empty
@ -987,7 +1053,43 @@ class _CreateUserFormDetialsState extends State<CreateUserFormDetials> {
if (!RegExp(r"^\d{10}$").hasMatch(data["mobile_no"].toString())) {
errorMessages["mobile_no"] =
"Enter 10 digits"; // Invalid mobile number format
} else {
bool isDuplicate = await apiCheckDuplicate(
"Mobile Number",
"mobile_no",
data["mobile_no"]!,
userIdApi,
);
if (isDuplicate) {
print("REsduplica1 - $isDuplicate");
errorMessages["mobile_no"] = "Mobile Number already exist";
// errorMessages is already updated inside apiCheckDuplicate
// setState(() {});
}
}
// else {
// apiCheckDuplicate(
// "Mobile Number",
// "mobile_no",
// data["mobile_no"]!,
// userIdApi,
// ).then((isDuplicate) {
// print("REsduplica - $isDuplicate");
//
// if (isDuplicate) {
// print("REsduplica1 - $isDuplicate");
// errorMessages["mobile_no"] = "Mobile Number Already Exist";
// // errorMessages is already updated inside apiCheckDuplicate
// // setState(() {});
// } else {
// // Optional: do something if no duplicate found
// }
// });
//
// // errorMessages["mobile_no"] = "Mobile Number already exist";
// }
}
if (data["alternate_mobile_no"] != null &&
@ -1006,12 +1108,43 @@ class _CreateUserFormDetialsState extends State<CreateUserFormDetials> {
r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$",
).hasMatch(data["email"].toString())) {
errorMessages["email"] = "Invalid email format"; // Invalid email format
} else {
bool isDuplicate = await apiCheckDuplicate(
"Email",
"email",
data["email"]!,
userIdApi,
);
if (isDuplicate) {
errorMessages["email"] = "Email already exist";
}
}
// else {
// apiCheckDuplicate("Email", "email", data["email"]!, userIdApi).then((
// isDuplicate,
// ) {
// print("REsduplica - $isDuplicate");
//
// if (isDuplicate) {
// print("REsduplica1 - $isDuplicate");
// errorMessages["email"] = "Email Already Exist";
// // errorMessages is already updated inside apiCheckDuplicate
// // setState(() {});
// }
// });
//
// // errorMessages["mobile_no"] = "Mobile Number already exist";
// }
}
print("ISEMPTY");
return errorMessages.isEmpty; // Valid if there are no errors
}
bool isValidDataTwo(Map<String, dynamic> data) {
Future<bool> isValidDataTwo(Map<String, dynamic> data) async {
errorMessages.clear(); // Reset errors
// Required fields that must not be empty
@ -1021,6 +1154,20 @@ class _CreateUserFormDetialsState extends State<CreateUserFormDetials> {
for (String field in requiredFields) {
if (data[field] == null || data[field].toString().trim().isEmpty) {
errorMessages[field] = "Required";
} else {
bool isDuplicate = await apiCheckDuplicate(
"Employee Code",
"employee_code",
data["employee_code"]!,
userIdApi,
);
if (isDuplicate) {
print("REsduplica1 - $isDuplicate");
errorMessages["employee_code"] = "Employee Code already exist";
// errorMessages is already updated inside apiCheckDuplicate
// setState(() {});
}
}
}
@ -1364,26 +1511,27 @@ class _CreateUserFormDetialsState extends State<CreateUserFormDetials> {
children: [
if (apiselectedUser != null)
isEditProfile
? Text( "Profile",
style: GoogleFonts.poppins(
fontSize: isDesktop ? 15 : 12,
fontWeight: FontWeight.w600,
color: Colors.black,
),
)
? Text(
"Profile",
style: GoogleFonts.poppins(
fontSize: isDesktop ? 15 : 12,
fontWeight: FontWeight.w600,
color: Colors.black,
),
)
: BreadcrumbNavigation(
isDesktop: isDesktop,
breadcrumbItems: [
BreadcrumbItem(
title: 'Users',
tooltip: 'Go To Users',
onTap: (context) {
context.go("/listUser");
},
),
BreadcrumbItem(title: 'Edit User'),
],
)
isDesktop: isDesktop,
breadcrumbItems: [
BreadcrumbItem(
title: 'Users',
tooltip: 'Go To Users',
onTap: (context) {
context.go("/listUser");
},
),
BreadcrumbItem(title: 'Edit User'),
],
)
else
BreadcrumbNavigation(
isDesktop: isDesktop,
@ -1584,28 +1732,37 @@ class _CreateUserFormDetialsState extends State<CreateUserFormDetials> {
return GestureDetector(
onTap: () {
setState(() {
setState(() async {
bool isValid = false;
final currentTab = selectedTab;
if (currentTab == "personal") {
isValid = isValidData(userDetials);
// isValid = isValidData(userDetials);
isValid = await isValidData(userDetials);
if (isValid) {
selectedTab = entry.key;
setState(() {
selectedTab = entry.key;
});
}
} else if (currentTab == "office" &&
targetTab == "personal") {
selectedTab = entry.key;
} else if (currentTab == "office") {
isValid = isValidDataTwo(userDetials);
if (isValid) {
setState(() {
selectedTab = entry.key;
});
} else if (currentTab == "office") {
isValid = await isValidDataTwo(userDetials);
if (isValid) {
setState(() {
selectedTab = entry.key;
});
}
} else {
isValid =
true; // Travel tab might not need validation at this point
selectedTab = entry.key;
setState(() {
selectedTab = entry.key;
});
}
});
},
@ -1759,12 +1916,15 @@ class _CreateUserFormDetialsState extends State<CreateUserFormDetials> {
isViewMode
? SystemMouseCursors.forbidden
: SystemMouseCursors.click,
child: Focus( // 👈 Wrap with Focus here
child: Focus(
// 👈 Wrap with Focus here
focusNode: submitButtonFocusNode,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor:
isViewMode ? layoutColor : layoutColor, // Keep original color
isViewMode
? layoutColor
: layoutColor, // Keep original color
foregroundColor:
isViewMode
? Colors.white

View File

@ -229,6 +229,18 @@ class PersonalDetailsState extends State<PersonalDetails> {
_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
}
@ -241,7 +253,7 @@ class PersonalDetailsState extends State<PersonalDetails> {
if (response.isNotEmpty) {
// _clearError(field);
widget.errorMessages[field] =
response['message'] ?? "$label Already Exists";
response['message'] ?? "$label already exists";
print("Duplicate found: ${response['message']}");
return;
} else {
@ -1410,6 +1422,14 @@ class PersonalDetailsState extends State<PersonalDetails> {
),
),
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"]!,