merged code

This commit is contained in:
venba-Inspriron-3558 2025-07-19 09:45:47 +05:30
commit 058875d571
4 changed files with 334 additions and 375 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,
@ -1399,271 +1440,6 @@ class _ApprovalListState extends State<ApprovalList> {
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Status and Employee Code
// Row(
// mainAxisAlignment:
// MainAxisAlignment.spaceBetween,
// children: [
// GestureDetector(
// onTap: () => _showDetails(plan.planId),
// child: Container(
// padding: EdgeInsets.symmetric(
// horizontal: 8,
// vertical: 4,
// ),
// decoration: BoxDecoration(
// color: getStatusColor(plan.statusValue),
// borderRadius: BorderRadius.circular(8),
// ),
// child: Text(
// plan.statusValue,
// style: TextStyle(
// color: getStatusTextColor(
// plan.statusValue,
// ),
// fontSize: 10,
// fontWeight: FontWeight.w600,
// ),
// ),
// ),
// ),
// PopupMenuButton<int>(
// color: Colors.white,
// padding: EdgeInsets.zero,
// offset: Offset(0, 30),
// icon: Icon(
// Icons.more_vert,
// color: Color(0xFF475569),
// size: 14,
// ),
// itemBuilder:
// (context) => [
// CustomPopupMenuEntry(
// child: Container(
// padding: EdgeInsets.symmetric(
// horizontal: 8,
// vertical: 8,
// ),
// child: Row(
// mainAxisSize:
// MainAxisSize.min,
// mainAxisAlignment:
// MainAxisAlignment.center,
// children: [
// IconButton(
// icon: Icon(
// Icons.remove_red_eye,
// color: Color(
// 0xFF475569,
// ),
// size: 18,
// ),
// tooltip:
// 'View The Trip Details',
// onPressed: () {
// Navigator.pop(
// context,
// ); // Close popup manually
// viewPlanforApprover(
// plan.planId,
// plan.approverId,
// plan.delegaterId,
// isViewMode: true,
// isApprover: true,
// );
// },
// ),
// if (plan.statusValue ==
// "Pending Approval" ||
// plan.statusValue ==
// "Partially Approved")
// IconButton(
// icon: Image.asset(
// 'assets/images/IconsImg/edit.png',
// width: 20,
// height: 15,
// ),
// tooltip:
// 'Edit The Trip Details',
// onPressed: () {
// Navigator.pop(
// context,
// );
// print(
// "Approver Edit3 : ${plan.approver_status}",
// );
//
// ApiService.viewPlanForApprover(
// context,
// plan.planId,
// plan.approverId,
// plan.delegaterId,
// plan.approver_status,
// isViewMode: false,
// isApprover: true,
// );
// },
// ),
// IconButton(
// icon: Icon(
// Icons.cancel_rounded,
// size: 18,
// ),
// tooltip:
// 'Cancellation The Trip Details',
// onPressed: () {
// Navigator.pop(context);
// deletePlan(plan.planId);
// },
// ),
// IconButton(
// icon: Icon(
// Icons.download,
// color: Color(
// 0xFF114D8B,
// ),
// size: 18,
// ),
// tooltip:
// 'Download The PDF',
// onPressed: () {
// Navigator.pop(context);
// apiService
// .getPdfDownload(
// plan.planId,
// );
// },
// ),
// IconButton(
// icon: const Icon(
// Icons.comment,
// color: Color(
// 0xFF475569,
// ),
// size: 11,
// ),
// tooltip: 'Trip Comments',
// onPressed: () {
// showDialog(
// context: context,
// builder:
// (
// context,
// ) => CommentModalList(
// // planId: plan.planId,
// planId:
// plan.planId
// .toString(),
// layoutColorForUser:
// layoutColor!,
// role:
// "Approver",
// ),
// );
// },
// ),
// ],
// ),
// ),
// ),
// ],
// ),
// ],
// ),
// SizedBox(height: 2),
// // Trip Id and Trip Name
// Row(
// children: [
// Column(
// crossAxisAlignment:
// CrossAxisAlignment.start,
// children: [
// Text(
// ' ${plan.tripTitle}',
// style: TextStyle(
// fontSize: 12,
// fontFamily: "Inter",
// fontWeight: FontWeight.bold,
// ),
// ),
// ],
// ),
// ],
// ),
// SizedBox(height: 2),
// // Type and Created On
// Row(
// mainAxisAlignment:
// MainAxisAlignment.spaceBetween,
// children: [
// Column(
// crossAxisAlignment:
// CrossAxisAlignment.start,
// children: [
// Text(
// ' ${plan.tripType}',
// style: TextStyle(
// fontSize: 9,
// color: Colors.black87,
// fontFamily: "Inter",
// ),
// ),
// ],
// ),
// Column(
// crossAxisAlignment:
// CrossAxisAlignment.start,
// children: [
// Text(
// '${_formatDate(plan.createdOn)}',
// style: TextStyle(
// fontSize: 9,
// color: Colors.black87,
// fontFamily: "Inter",
// ),
// ),
// ],
// ),
// ],
// ),
// SizedBox(height: 3),
// // Name
// Row(
// children: [
// Column(
// crossAxisAlignment:
// CrossAxisAlignment.start,
// children: [
// Text(
// plan.userName.isNotEmpty
// ? '${plan.userName}'
// : '${plan.travellerName}',
// style: TextStyle(
// fontSize: 9,
// fontFamily: "Inter",
// color: Colors.black87,
// ),
// ),
// ],
// ),
// Spacer(),
// Column(
// crossAxisAlignment:
// CrossAxisAlignment.start,
// children: [
// Text(
// plan.employeeCode ?? 'No Data',
// style: TextStyle(
// fontSize: 9,
// fontFamily: "Inter",
// color: Colors.black87,
// ),
// ),
// ],
// ),
// ],
// ),
// Actions
Expanded(
child: Container(
// color: Colors.yellow.shade50,

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"]!,