2153 lines
83 KiB
Dart
2153 lines
83 KiB
Dart
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:intl/intl.dart';
|
|
|
|
import '../../../services/apiService.dart';
|
|
import '../../../utils/auth_utils.dart';
|
|
import '../../../widgets/custom_user_form.dart';
|
|
|
|
class OfficeDetails extends StatefulWidget {
|
|
final Map<String, TextEditingController> controllers;
|
|
final Map<String, String> errorMessages;
|
|
final bool isDesktop;
|
|
final bool isViewMode;
|
|
final String? userIdApi;
|
|
|
|
final ValueChanged<String?>? onLevelChanged;
|
|
final ValueChanged<String?>? onDepartmentChanged;
|
|
final ValueChanged<String?>? onFirstApproverChanged;
|
|
final ValueChanged<String?>? onSecondApproverChanged;
|
|
final ValueChanged<String?>? onThirdApproverChanged;
|
|
final ValueChanged<String?>? onFirstSubsApproverChanged;
|
|
final ValueChanged<String?>? onExceptionalApproverChanged;
|
|
|
|
final String? selectedLevel;
|
|
final String? selectedDepartment;
|
|
final String? selectedFirstApprover;
|
|
final String? selectedSecondApprover;
|
|
final String? selectedThirdApprover;
|
|
final String? selectedSubstituteApprover;
|
|
final String? selectedExceptionalApprover;
|
|
|
|
const OfficeDetails({
|
|
Key? key,
|
|
this.selectedLevel,
|
|
this.selectedDepartment,
|
|
this.selectedFirstApprover,
|
|
this.selectedSecondApprover,
|
|
this.selectedThirdApprover,
|
|
required this.controllers,
|
|
required this.errorMessages,
|
|
required this.isDesktop,
|
|
required this.isViewMode,
|
|
this.onLevelChanged,
|
|
this.onDepartmentChanged,
|
|
this.onFirstApproverChanged,
|
|
this.onSecondApproverChanged,
|
|
this.onThirdApproverChanged,
|
|
this.selectedSubstituteApprover,
|
|
this.selectedExceptionalApprover,
|
|
this.onExceptionalApproverChanged,
|
|
this.onFirstSubsApproverChanged,
|
|
this.userIdApi,
|
|
}) : super(key: key);
|
|
|
|
@override
|
|
OfficeDetailsState createState() => OfficeDetailsState();
|
|
}
|
|
|
|
class OfficeDetailsState extends State<OfficeDetails> {
|
|
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;
|
|
|
|
bool isResetTrue = false;
|
|
|
|
late List<dynamic>? apiCostData;
|
|
late List<dynamic>? apiRoleData;
|
|
late List<dynamic>? apiUserData;
|
|
late List<dynamic>? apiAllGroups;
|
|
Map<String, dynamic>? apiselectedUser;
|
|
|
|
late List<dynamic> userList;
|
|
late List<dynamic> groupList;
|
|
late Future<List<dynamic>> futureUsers;
|
|
|
|
List<dynamic>? resolvedUserData;
|
|
// String? userIdsApi;
|
|
late List<String> userIdsApi;
|
|
late List<String> groupIdsApi;
|
|
late Map<String, String> userMap;
|
|
late Map<String, String> groupMap;
|
|
|
|
String? selectedTab;
|
|
|
|
String? selectedCountry;
|
|
String? selectedGender;
|
|
String? selectedUserType;
|
|
String? selectedRole;
|
|
String? selectedLevel;
|
|
String? selectedDepartment;
|
|
String? selectedFirstApprover;
|
|
String? selectedSecondApprover;
|
|
String? selectedThirdApprover;
|
|
String? selectedSubstituteApprover;
|
|
String? selectedExceptionalApprover;
|
|
|
|
String? selectedFileNames;
|
|
Uint8List? passportDocumentBytes;
|
|
String? passportFileUrlFromApi;
|
|
String? base64PDF;
|
|
DateTime? _selectedCheckOutDate;
|
|
DateTime? _selectedEndDate;
|
|
html.File? passportFile;
|
|
|
|
final FocusNode _employeeCodeFocusNode = FocusNode();
|
|
final FocusNode _departmentFocusNode = FocusNode();
|
|
final FocusNode _groupFocusNode = FocusNode();
|
|
final FocusNode _firstApprovalFocusNode = FocusNode();
|
|
final FocusNode _secondApprovalFocusNode = FocusNode();
|
|
final FocusNode _thirdApprovalFocusNode = FocusNode();
|
|
final FocusNode _exceptionalApprovalFocusNode = FocusNode();
|
|
final FocusNode _delegateFocusNode = FocusNode();
|
|
final FocusNode _startDateFocusNode = FocusNode();
|
|
final FocusNode _endDateFocusNode = FocusNode();
|
|
|
|
bool _employeeCodeFocus = false;
|
|
bool _departmentFocus = false;
|
|
bool _groupFocus = false;
|
|
bool _firstApprovalFocus = false;
|
|
bool _secondApprovalFocus = false;
|
|
bool _thirdApprovalFocus = false;
|
|
bool _exceptionalApprovalFocus = false;
|
|
bool _delegateFocus = false;
|
|
bool _startDateFocus = false;
|
|
bool _endDateFocus = false;
|
|
|
|
List<String> dataHeader = [
|
|
"Fname",
|
|
"Lname",
|
|
"email",
|
|
"password",
|
|
"dob",
|
|
"gender",
|
|
"mobileNumber",
|
|
"alternateMobile",
|
|
"address",
|
|
"postalCode",
|
|
"country",
|
|
"passportNumber",
|
|
"placeOfIssue",
|
|
"passportDoc",
|
|
"userType",
|
|
"roleId",
|
|
"deptId",
|
|
"levelId",
|
|
"firstApproval",
|
|
"secondApproval",
|
|
"thirdApproval",
|
|
"exceptional_approver",
|
|
"employeeCode",
|
|
"dateOfIssue",
|
|
"dateOfExpiry",
|
|
"changePassword",
|
|
];
|
|
|
|
// Color? layoutColor;
|
|
Color layoutColor = Colors.grey;
|
|
Color? bodyColor;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
loadInitialData();
|
|
_employeeCodeFocusNode.requestFocus();
|
|
});
|
|
apiCountryData = null;
|
|
apiUserData = null;
|
|
apiCostData = null;
|
|
apiRoleData = null;
|
|
apiAllGroups = null;
|
|
|
|
apiCountryData = null;
|
|
|
|
selectedLevel = widget.selectedLevel;
|
|
selectedDepartment = widget.selectedDepartment;
|
|
selectedFirstApprover = widget.selectedFirstApprover;
|
|
selectedSecondApprover = widget.selectedSecondApprover;
|
|
selectedThirdApprover = widget.selectedThirdApprover;
|
|
selectedSubstituteApprover = widget.selectedSubstituteApprover;
|
|
selectedExceptionalApprover = widget.selectedExceptionalApprover;
|
|
|
|
fetchDepartment();
|
|
fetchUsers();
|
|
fetchFindGroup();
|
|
|
|
|
|
_addFocusListener(_employeeCodeFocusNode, (focus) => _employeeCodeFocus = focus);
|
|
_addFocusListener(_departmentFocusNode, (focus) => _departmentFocus = focus);
|
|
_addFocusListener(_groupFocusNode, (focus) => _groupFocus = focus);
|
|
_addFocusListener(_firstApprovalFocusNode, (focus) => _firstApprovalFocus = focus);
|
|
_addFocusListener(_secondApprovalFocusNode, (focus) => _secondApprovalFocus = focus);
|
|
_addFocusListener(_thirdApprovalFocusNode, (focus) => _thirdApprovalFocus = focus);
|
|
_addFocusListener(_exceptionalApprovalFocusNode, (focus) => _exceptionalApprovalFocus = focus);
|
|
_addFocusListener(_delegateFocusNode, (focus) => _delegateFocus = focus);
|
|
_addFocusListener(_startDateFocusNode, (focus) => _startDateFocus = focus);
|
|
_addFocusListener(_endDateFocusNode, (focus) => _endDateFocus = focus);
|
|
|
|
}
|
|
|
|
void _addFocusListener(FocusNode node, Function(bool) updateState) {
|
|
node.addListener(() {
|
|
setState(() {
|
|
updateState(node.hasFocus);
|
|
});
|
|
});
|
|
}
|
|
|
|
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> apiCheckDuplicate(
|
|
String label,
|
|
String field,
|
|
String value,
|
|
String? userId,
|
|
) async {
|
|
try {
|
|
// Basic validation: Check mobile number length
|
|
|
|
final response = await apiService.CheckDuplicate(
|
|
label,
|
|
field,
|
|
value,
|
|
userId ?? "",
|
|
);
|
|
if (response.isNotEmpty) {
|
|
_clearError(field);
|
|
widget.errorMessages[field] =
|
|
response['message'] ?? "$label Already Exists";
|
|
print("Duplicate found: ${response['message']}");
|
|
return;
|
|
} else {
|
|
_clearError(field);
|
|
print("No duplicates found.");
|
|
}
|
|
} catch (e) {
|
|
print("Error in checkDuplicate: $e");
|
|
}
|
|
}
|
|
|
|
void _clearError(String field) {
|
|
setState(() {
|
|
widget.errorMessages.remove(field);
|
|
});
|
|
}
|
|
|
|
// void handleReset() {
|
|
// setState(() {
|
|
// selectedSubstituteApprover = "";
|
|
// widget.controllers["delegationEndDate"]?.text = "";
|
|
// widget.controllers["delegationStartDate"]?.text = "";
|
|
// });
|
|
// }
|
|
|
|
void handleReset() {
|
|
setState(() {
|
|
selectedSubstituteApprover = null;
|
|
widget.onFirstSubsApproverChanged?.call(null); // Notify parent if needed
|
|
|
|
// Clear date selections and fields
|
|
_selectedCheckOutDate = null;
|
|
_selectedEndDate = null;
|
|
|
|
widget.controllers["delegationStartDate"]?.text = "";
|
|
widget.controllers["delegationEndDate"]?.text = "";
|
|
|
|
// selectedSubstituteApprover = null;
|
|
// widget.onFirstSubsApproverChanged?.call(null);
|
|
// widget.controllers["delegationStartDate"]?.clear();
|
|
// widget.controllers["delegationEndDate"]?.clear();
|
|
//
|
|
// print("Start Date: ${widget.controllers["delegationStartDate"]?.text}");
|
|
// print("End Date: ${widget.controllers["delegationEndDate"]?.text}");
|
|
});
|
|
}
|
|
|
|
Future<void> fetchUsers() async {
|
|
try {
|
|
print("Test Users");
|
|
List<dynamic> users = await apiService.fetchUsers();
|
|
setState(() {
|
|
// apiUserData = users;
|
|
|
|
print("Total users fetched from API: ${users.length}");
|
|
// user["role_id"] != "5" - Travel Agent
|
|
apiUserData = users.where((user) => user["role_id"] != "5").toList();
|
|
print("Total users fetched from API1: ${apiUserData?.length}");
|
|
print("APIUSerDATa - $apiUserData");
|
|
|
|
userList = apiUserData ?? [];
|
|
userMap = {
|
|
for (var user in userList)
|
|
user['user_id'].toString():
|
|
"${user['first_name']} ${user['last_name']}",
|
|
};
|
|
userIdsApi = userMap.keys.toList();
|
|
});
|
|
} catch (e) {
|
|
print('Error fetching country list: $e');
|
|
}
|
|
}
|
|
|
|
Future<void> fetchDepartment() async {
|
|
try {
|
|
print("Test department");
|
|
List<dynamic> department = await apiService.fetchDepartmentCostCenter();
|
|
setState(() {
|
|
apiCostData = department;
|
|
});
|
|
print("Test department Completeed- $apiCostData");
|
|
} catch (e) {
|
|
print('Error fetching department list: $e');
|
|
}
|
|
}
|
|
|
|
Future<void> fetchFindGroup() async {
|
|
try {
|
|
print("Test Groups");
|
|
final result = await apiService.fetchAllGroup();
|
|
setState(() {
|
|
apiAllGroups = result;
|
|
|
|
groupList = apiAllGroups ?? [];
|
|
// groupMap = {
|
|
// for (var group in groupList)
|
|
// group['group_id'].toString(): "${group['name']}"
|
|
// };
|
|
|
|
groupMap = {
|
|
for (var group in groupList)
|
|
group['group_id'].toString(): group['name'].toString().trim(),
|
|
};
|
|
|
|
userIdsApi = groupMap.keys.toList();
|
|
});
|
|
print("Fetched Groupss: $apiAllGroups");
|
|
} catch (e) {
|
|
print('Error fetching role list: $e');
|
|
}
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Container(
|
|
decoration: BoxDecoration(
|
|
border: Border(
|
|
top: BorderSide(
|
|
color: Colors.blueGrey.shade300, // Set your desired color
|
|
width: 0.09, // Set border thickness
|
|
),
|
|
),
|
|
),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
SizedBox(height: 20),
|
|
_buildFirstRow(widget.isDesktop),
|
|
if (widget.isDesktop) SizedBox(height: 10),
|
|
_buildSecondRow(widget.isDesktop),
|
|
if (widget.isDesktop) SizedBox(height: 10),
|
|
_buildThirdRow(widget.isDesktop),
|
|
if (widget.isDesktop) SizedBox(height: 10),
|
|
Divider(thickness: 0.2, color: Colors.blueGrey.shade100),
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
Text(
|
|
"Delegation",
|
|
style: GoogleFonts.poppins(
|
|
fontSize: 12,
|
|
fontWeight: FontWeight.w500,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
SizedBox(height: 15),
|
|
_buildFourthRow(widget.isDesktop),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildFirstRow(bool isDesktop) {
|
|
return Container(
|
|
color: Colors.white,
|
|
child:
|
|
widget.isDesktop
|
|
? Row(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
buildEmpCodeField(),
|
|
Spacer(),
|
|
buildDepartmentField(),
|
|
Spacer(), // Space after Last Name
|
|
buildGroupField(),
|
|
],
|
|
)
|
|
: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
buildEmpCodeField(),
|
|
SizedBox(height: 8), // Vertical space
|
|
buildDepartmentField(),
|
|
SizedBox(height: 8),
|
|
buildGroupField(),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildSecondRow(bool isDesktop) {
|
|
return Container(
|
|
color: Colors.white,
|
|
child:
|
|
widget.isDesktop
|
|
? Row(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
buildApprover1(isDesktop),
|
|
Spacer(),
|
|
buildApprover2(isDesktop),
|
|
Spacer(), // Space after Last Name
|
|
buildApprover3(isDesktop),
|
|
],
|
|
)
|
|
: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
buildApprover1(isDesktop),
|
|
SizedBox(height: 8), // Vertical space
|
|
buildApprover2(isDesktop),
|
|
SizedBox(height: 8),
|
|
buildApprover3(isDesktop),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildThirdRow(bool isDesktop) {
|
|
return Container(
|
|
color: Colors.white,
|
|
child:
|
|
widget.isDesktop
|
|
? Row(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
buildExceptionApprover(isDesktop),
|
|
Spacer(),
|
|
],
|
|
)
|
|
: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
buildExceptionApprover(isDesktop),
|
|
SizedBox(height: 8), // Vertical space
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildFourthRow(bool isDesktop) {
|
|
return Container(
|
|
color: Colors.white,
|
|
child:
|
|
widget.isDesktop
|
|
? Row(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
buildApproverSubstitute1(isDesktop),
|
|
Spacer(),
|
|
buildDelegationStartDateField(isDesktop),
|
|
Spacer(), // Space after Last Name
|
|
buildDelegationEndDateField(isDesktop),
|
|
SizedBox(width: 16),
|
|
buildReset(isDesktop),
|
|
],
|
|
)
|
|
: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
buildApproverSubstitute1(isDesktop),
|
|
SizedBox(height: 8), // Vertical space
|
|
buildDelegationStartDateField(isDesktop),
|
|
SizedBox(height: 8),
|
|
buildDelegationEndDateField(isDesktop),
|
|
buildReset(isDesktop),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget buildEmpCodeField() {
|
|
return Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
"Employee Code *",
|
|
style: GoogleFonts.poppins(
|
|
fontSize: 12,
|
|
fontWeight: FontWeight.w600,
|
|
color: Color(0xFF575A74),
|
|
),
|
|
),
|
|
SizedBox(height: 5),
|
|
CustomTextFieldUserWrapper(
|
|
isFocused: _employeeCodeFocus,
|
|
isDesktop: widget.isDesktop,
|
|
child: SizedBox(
|
|
height: 40,
|
|
child: TextField(
|
|
style: GoogleFonts.poppins(fontSize: 12, color: Colors.black),
|
|
focusNode: _employeeCodeFocusNode,
|
|
controller: widget.controllers["employeeCode"],
|
|
enabled: !widget.isViewMode,
|
|
onChanged: (value) {
|
|
_clearError("employee_code");
|
|
|
|
final updaterUserId = widget.userIdApi ?? "";
|
|
apiCheckDuplicate(
|
|
"Employee Code",
|
|
"employee_code",
|
|
value,
|
|
updaterUserId,
|
|
);
|
|
},
|
|
decoration: InputDecoration(
|
|
labelText: "Employee Code",
|
|
labelStyle: GoogleFonts.poppins(
|
|
fontSize: 12,
|
|
color: Colors.grey,
|
|
),
|
|
floatingLabelBehavior: FloatingLabelBehavior.never,
|
|
border: InputBorder.none,
|
|
contentPadding: EdgeInsets.symmetric(vertical: 16),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
if (widget.errorMessages["employee_code"] != null) ...[
|
|
SizedBox(height: 5), // Space before error message
|
|
Text(
|
|
widget.errorMessages["employee_code"]!,
|
|
style: GoogleFonts.poppins(color: Colors.red, fontSize: 12),
|
|
),
|
|
],
|
|
],
|
|
);
|
|
}
|
|
|
|
// Widget buildDepartmentFieldOld() {
|
|
// return Column(
|
|
// crossAxisAlignment: CrossAxisAlignment.start,
|
|
// children: [
|
|
// Text(
|
|
// "Department",
|
|
// 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: selectedDepartment,
|
|
// // value: widget.isViewMode ? null : selectedDepartment,
|
|
// 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(() {
|
|
// selectedDepartment = newValue;
|
|
// });
|
|
// widget.onDepartmentChanged?.call(newValue);
|
|
// },
|
|
//
|
|
// items:
|
|
// apiCostData?.map<DropdownMenuItem<String>>((item) {
|
|
// return DropdownMenuItem(
|
|
//
|
|
// value: item['department_id'], // ID as value
|
|
// child: Text(item['name'] ?? "Unknown"),
|
|
// );
|
|
// }).toList(),
|
|
// hint: Text("Select"),
|
|
// disabledHint: Text(
|
|
// selectedDepartment ?? "Select Department",
|
|
// style: GoogleFonts.poppins(fontSize: 12, color: Colors.black),
|
|
// ),
|
|
// ),
|
|
// ),
|
|
// ),
|
|
// ],
|
|
// );
|
|
// }
|
|
|
|
Widget buildDepartmentField() {
|
|
// Map department_id to department_name
|
|
Map<String, String> departmentMap = {
|
|
for (var item in apiCostData ?? [])
|
|
if (item['dropdown_key'] != null &&
|
|
item['dropdown_value'] != null &&
|
|
int.tryParse(item['dropdown_key'].toString()) != 5)
|
|
item['dropdown_key'].toString(): item['dropdown_value'].toString(),
|
|
// item['department_id'] as String: item['name'] as String,
|
|
};
|
|
|
|
return Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
"Department",
|
|
style: GoogleFonts.poppins(
|
|
fontSize: 12,
|
|
fontWeight: FontWeight.w600,
|
|
color: Color(0xFF575A74),
|
|
),
|
|
),
|
|
SizedBox(height: 5),
|
|
CustomTextFieldUserWrapper(
|
|
isFocused: _departmentFocus,
|
|
padding: const EdgeInsets.symmetric(horizontal: 0),
|
|
isDesktop: widget.isDesktop,
|
|
child: SizedBox(
|
|
height: 40,
|
|
width: double.infinity,
|
|
child: Focus(
|
|
focusNode: _departmentFocusNode,
|
|
onFocusChange: (hasFocus) {
|
|
setState(() {
|
|
_departmentFocus = hasFocus;
|
|
});
|
|
},
|
|
child: GestureDetector(
|
|
onTap: () {
|
|
// Request focus when user taps
|
|
_departmentFocusNode?.requestFocus();
|
|
},
|
|
child: DropdownSearch<String>(
|
|
selectedItem: departmentMap[selectedDepartment],
|
|
enabled: !widget.isViewMode,
|
|
popupProps: PopupProps.menu(
|
|
showSearchBox: true,
|
|
fit: FlexFit.loose,
|
|
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: 200),
|
|
searchFieldProps: TextFieldProps(
|
|
decoration: InputDecoration(
|
|
hintText: "Search Department...",
|
|
contentPadding: EdgeInsets.symmetric(horizontal: 10),
|
|
),
|
|
),
|
|
),
|
|
items: departmentMap.values.toList(),
|
|
|
|
dropdownDecoratorProps: DropDownDecoratorProps(
|
|
dropdownSearchDecoration: InputDecoration(
|
|
border: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(8),
|
|
borderSide: BorderSide(
|
|
color:
|
|
(_departmentFocus)
|
|
? (layoutColor)
|
|
: Colors.white,
|
|
width: 0.5,
|
|
),
|
|
),
|
|
enabledBorder: OutlineInputBorder(
|
|
borderSide: BorderSide(
|
|
color:
|
|
(_departmentFocus)
|
|
? (layoutColor)
|
|
: 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),
|
|
// contentPadding: EdgeInsets.symmetric(horizontal: 1),
|
|
),
|
|
),
|
|
dropdownBuilder:
|
|
(context, selectedItem) => Align(
|
|
alignment: Alignment.centerLeft,
|
|
child: Text(
|
|
selectedItem ?? "Select Department",
|
|
style: GoogleFonts.poppins(
|
|
fontSize: 12,
|
|
color: Colors.black,
|
|
),
|
|
),
|
|
),
|
|
onChanged:
|
|
widget.isViewMode
|
|
? null
|
|
: (String? newValue) {
|
|
if (newValue == null) return;
|
|
|
|
final departmentId =
|
|
departmentMap.entries
|
|
.firstWhere((entry) => entry.value == newValue)
|
|
.key;
|
|
|
|
setState(() {
|
|
selectedDepartment = departmentId;
|
|
});
|
|
|
|
widget.onDepartmentChanged?.call(
|
|
departmentId,
|
|
); // Send department_id
|
|
},
|
|
),),),
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
Widget buildGroupField() {
|
|
return Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
"Group",
|
|
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: selectedLevel,
|
|
// enabled: !widget.isViewMode,
|
|
// popupProps: PopupProps.menu(
|
|
// // showSearchBox: true,
|
|
// fit: FlexFit.loose, // Allows flexible height
|
|
// constraints: BoxConstraints(maxHeight: 250),
|
|
// ),
|
|
// items: ["Level 1", "Level 2", "Level 3"],
|
|
// 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, color: Colors.black),
|
|
// ),
|
|
// ),
|
|
// onChanged: (String? newValue) {
|
|
// setState(() {
|
|
// // Find the country_code based on selected country_name
|
|
// selectedLevel = newValue;
|
|
//
|
|
// // if (selectedCountry!.isNotEmpty) {
|
|
// // errorMessages.remove("country_code");
|
|
// // }
|
|
// });
|
|
// },
|
|
// ),
|
|
// ),
|
|
// ),
|
|
CustomTextFieldUserWrapper(
|
|
isFocused: _groupFocus,
|
|
padding: const EdgeInsets.symmetric(horizontal: 0),
|
|
isDesktop: widget.isDesktop,
|
|
child: SizedBox(
|
|
height: 40,
|
|
width: double.infinity,
|
|
child: Focus(
|
|
focusNode: _groupFocusNode,
|
|
onFocusChange: (hasFocus) {
|
|
setState(() {
|
|
_groupFocus = hasFocus;
|
|
});
|
|
},
|
|
child: GestureDetector(
|
|
onTap: () {
|
|
// Request focus when user taps
|
|
_groupFocusNode?.requestFocus();
|
|
},
|
|
child:
|
|
apiAllGroups == null
|
|
? Center(
|
|
child: Transform.scale(
|
|
scale: 0.5,
|
|
child: CircularProgressIndicator(),
|
|
),
|
|
)
|
|
: DropdownSearch<String>(
|
|
selectedItem: groupMap[selectedLevel],
|
|
enabled: !widget.isViewMode,
|
|
|
|
popupProps: PopupProps.menu(
|
|
menuProps: const MenuProps(
|
|
backgroundColor: Colors.white,
|
|
),
|
|
showSearchBox: true,
|
|
fit: FlexFit.loose, // Allows flexible height
|
|
itemBuilder:
|
|
(context, item, isSelected) => Container(
|
|
padding: EdgeInsets.symmetric(
|
|
horizontal: 10,
|
|
vertical: 6,
|
|
),
|
|
child: Text(
|
|
item,
|
|
style: GoogleFonts.poppins(fontSize: 11.5),
|
|
),
|
|
),
|
|
constraints: BoxConstraints(maxHeight: 250),
|
|
searchFieldProps: TextFieldProps(
|
|
decoration: InputDecoration(
|
|
hintText: "Search Group...",
|
|
contentPadding: EdgeInsets.symmetric(
|
|
horizontal: 10,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
|
|
// items: apiAllGroups!.map((group) {
|
|
// return "${group['name']} ";
|
|
// }).toList(),
|
|
items:
|
|
apiAllGroups!.map((group) {
|
|
return group['name']
|
|
.toString()
|
|
.trim(); // <-- trim spaces
|
|
}).toList(),
|
|
|
|
dropdownDecoratorProps: DropDownDecoratorProps(
|
|
dropdownSearchDecoration: InputDecoration(
|
|
border: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(8),
|
|
borderSide: BorderSide(
|
|
color:
|
|
(_groupFocus ?? false)
|
|
? (layoutColor ?? Colors.white)
|
|
: Colors.white,
|
|
width: 0.5,
|
|
),
|
|
),
|
|
enabledBorder: OutlineInputBorder(
|
|
borderSide: BorderSide(
|
|
color:
|
|
(_groupFocus ?? false)
|
|
? (layoutColor ?? Colors.white)
|
|
: 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",
|
|
style: GoogleFonts.poppins(fontSize: 12),
|
|
),
|
|
),
|
|
|
|
onChanged: (String? newValue) {
|
|
if (newValue == null) return;
|
|
|
|
final levelId =
|
|
groupMap.entries
|
|
.firstWhere((entry) => entry.value == newValue)
|
|
.key;
|
|
|
|
setState(() {
|
|
selectedLevel = levelId;
|
|
});
|
|
|
|
widget.onLevelChanged?.call(
|
|
levelId,
|
|
); // ✅ pass the ID not the name
|
|
},
|
|
),
|
|
),),
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
Widget buildApprover1(bool isDesktop) {
|
|
return Container(
|
|
color: Colors.white,
|
|
// child: Expanded(
|
|
// Allow first column to take available space
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Row(
|
|
children: [
|
|
Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
"First Approver",
|
|
style: GoogleFonts.poppins(
|
|
fontSize: 12,
|
|
fontWeight: FontWeight.w600,
|
|
color: Color(0xFF575A74),
|
|
),
|
|
),
|
|
SizedBox(height: 5),
|
|
CustomTextFieldUserWrapper(
|
|
isFocused: _firstApprovalFocus,
|
|
padding: const EdgeInsets.symmetric(horizontal: 0),
|
|
isDesktop: isDesktop,
|
|
child: SizedBox(
|
|
height: 40,
|
|
width: double.infinity,
|
|
child: Focus(
|
|
focusNode: _firstApprovalFocusNode,
|
|
onFocusChange: (hasFocus) {
|
|
setState(() {
|
|
_firstApprovalFocus= hasFocus;
|
|
});
|
|
},
|
|
child: GestureDetector(
|
|
onTap: () {
|
|
// Request focus when user taps
|
|
_firstApprovalFocusNode?.requestFocus();
|
|
},
|
|
child:
|
|
apiUserData == null
|
|
? Center(
|
|
child: Transform.scale(
|
|
scale: 0.5,
|
|
child: CircularProgressIndicator(),
|
|
),
|
|
)
|
|
: DropdownSearch<String>(
|
|
selectedItem: userMap[selectedFirstApprover],
|
|
enabled: !widget.isViewMode,
|
|
popupProps: PopupProps.menu(
|
|
showSearchBox: true,
|
|
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: 250),
|
|
searchFieldProps: TextFieldProps(
|
|
decoration: InputDecoration(
|
|
hintText: "Search User...",
|
|
contentPadding: EdgeInsets.symmetric(
|
|
horizontal: 10,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
items:
|
|
apiUserData!.map((user) {
|
|
return "${user['first_name']} ${user['last_name']}";
|
|
}).toList(),
|
|
dropdownDecoratorProps: DropDownDecoratorProps(
|
|
dropdownSearchDecoration: InputDecoration(
|
|
border: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(8),
|
|
borderSide: BorderSide(
|
|
color:
|
|
(_firstApprovalFocus ?? false)
|
|
? (layoutColor ?? Colors.white)
|
|
: Colors.white,
|
|
width: 0.5,
|
|
),
|
|
),
|
|
enabledBorder: OutlineInputBorder(
|
|
borderSide: BorderSide(
|
|
color:
|
|
(_firstApprovalFocus ?? false)
|
|
? (layoutColor ?? Colors.white)
|
|
: 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",
|
|
style: GoogleFonts.poppins(
|
|
fontSize: 12,
|
|
),
|
|
),
|
|
),
|
|
|
|
// onChanged: (String? newValue) {
|
|
// setState(() {
|
|
// selectedFirstApprover = userMap.entries
|
|
// .firstWhere(
|
|
// (entry) => entry.value == newValue)
|
|
// .key;
|
|
//
|
|
// // if (selectedCountry!.isNotEmpty) {
|
|
// // errorMessages.remove("country_code");
|
|
// // }
|
|
// });
|
|
// widget.onFirstApproverChanged?.call(newValue);
|
|
// },
|
|
onChanged: (String? newValue) {
|
|
if (newValue == null) return;
|
|
|
|
final approverId =
|
|
userMap.entries
|
|
.firstWhere(
|
|
(entry) => entry.value == newValue,
|
|
)
|
|
.key;
|
|
|
|
setState(() {
|
|
selectedFirstApprover = approverId;
|
|
});
|
|
|
|
widget.onFirstApproverChanged?.call(
|
|
approverId,
|
|
); // ✅ not newValue, but approverId
|
|
},
|
|
),
|
|
),),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
|
|
// ),
|
|
);
|
|
}
|
|
|
|
Widget buildApprover2(bool isDesktop) {
|
|
return Container(
|
|
color: Colors.white,
|
|
// child: Expanded(
|
|
// Allow second column to take available space
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Row(
|
|
children: [
|
|
Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
"Second Approver",
|
|
style: GoogleFonts.poppins(
|
|
fontSize: 12,
|
|
fontWeight: FontWeight.w600,
|
|
color: Color(0xFF575A74),
|
|
),
|
|
),
|
|
SizedBox(height: 5),
|
|
CustomTextFieldUserWrapper(
|
|
isFocused: _secondApprovalFocus,
|
|
padding: const EdgeInsets.symmetric(horizontal: 0),
|
|
isDesktop: isDesktop,
|
|
child: SizedBox(
|
|
height: 40,
|
|
width: double.infinity,
|
|
child: Focus(
|
|
focusNode: _secondApprovalFocusNode,
|
|
onFocusChange: (hasFocus) {
|
|
setState(() {
|
|
_secondApprovalFocus = hasFocus;
|
|
});
|
|
},
|
|
child: GestureDetector(
|
|
onTap: () {
|
|
// Request focus when user taps
|
|
_secondApprovalFocusNode?.requestFocus();
|
|
},
|
|
child:
|
|
apiUserData == null
|
|
? Center(
|
|
child: Transform.scale(
|
|
scale: 0.5,
|
|
child: CircularProgressIndicator(),
|
|
),
|
|
)
|
|
: DropdownSearch<String>(
|
|
selectedItem: userMap[selectedSecondApprover],
|
|
enabled: !widget.isViewMode,
|
|
popupProps: PopupProps.menu(
|
|
showSearchBox: true,
|
|
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: 250),
|
|
searchFieldProps: TextFieldProps(
|
|
decoration: InputDecoration(
|
|
hintText: "Search User...",
|
|
contentPadding: EdgeInsets.symmetric(
|
|
horizontal: 10,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
items:
|
|
apiUserData!.map((user) {
|
|
return "${user['first_name']} ${user['last_name']}";
|
|
}).toList(),
|
|
dropdownDecoratorProps: DropDownDecoratorProps(
|
|
dropdownSearchDecoration: InputDecoration(
|
|
border: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(8),
|
|
borderSide: BorderSide(
|
|
color:
|
|
(_secondApprovalFocus ?? false)
|
|
? (layoutColor ?? Colors.white)
|
|
: Colors.white,
|
|
width: 0.5,
|
|
),
|
|
),
|
|
enabledBorder: OutlineInputBorder(
|
|
borderSide: BorderSide(
|
|
color:
|
|
(_secondApprovalFocus ?? false)
|
|
? (layoutColor ?? Colors.white)
|
|
: 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",
|
|
style: GoogleFonts.poppins(
|
|
fontSize: 12,
|
|
),
|
|
),
|
|
),
|
|
|
|
onChanged: (String? newValue) {
|
|
if (newValue == null) return;
|
|
|
|
final approverId =
|
|
userMap.entries
|
|
.firstWhere(
|
|
(entry) => entry.value == newValue,
|
|
)
|
|
.key;
|
|
|
|
setState(() {
|
|
selectedSecondApprover = approverId;
|
|
});
|
|
|
|
widget.onSecondApproverChanged?.call(
|
|
approverId,
|
|
); // ✅ not newValue, but approverId
|
|
},
|
|
// onChanged: (String? newValue) {
|
|
// setState(() {
|
|
// selectedSecondApprover = userMap.entries
|
|
// .firstWhere(
|
|
// (entry) => entry.value == newValue)
|
|
// .key;
|
|
//
|
|
// // if (selectedCountry!.isNotEmpty) {
|
|
// // errorMessages.remove("country_code");
|
|
// // }
|
|
// });
|
|
// },
|
|
),
|
|
),),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
// ),
|
|
);
|
|
}
|
|
|
|
Widget buildApprover3(bool isDesktop) {
|
|
return Container(
|
|
color: Colors.white,
|
|
|
|
// child: Expanded(
|
|
// Allow second column to take available space
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Row(
|
|
children: [
|
|
Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
"Third Approver",
|
|
style: GoogleFonts.poppins(
|
|
fontSize: 12,
|
|
fontWeight: FontWeight.w600,
|
|
color: Color(0xFF575A74),
|
|
),
|
|
),
|
|
SizedBox(height: 5),
|
|
CustomTextFieldUserWrapper(
|
|
isFocused: _thirdApprovalFocus,
|
|
padding: const EdgeInsets.symmetric(horizontal: 0),
|
|
isDesktop: isDesktop,
|
|
child: SizedBox(
|
|
height: 40,
|
|
width: double.infinity,
|
|
child: Focus(
|
|
focusNode: _thirdApprovalFocusNode,
|
|
onFocusChange: (hasFocus) {
|
|
setState(() {
|
|
_thirdApprovalFocus = hasFocus;
|
|
});
|
|
},
|
|
child: GestureDetector(
|
|
onTap: () {
|
|
// Request focus when user taps
|
|
_thirdApprovalFocusNode?.requestFocus();
|
|
},
|
|
child:
|
|
apiUserData == null
|
|
? Center(
|
|
child: Transform.scale(
|
|
scale: 0.5,
|
|
child: CircularProgressIndicator(),
|
|
),
|
|
)
|
|
: DropdownSearch<String>(
|
|
selectedItem: userMap[selectedThirdApprover],
|
|
enabled: !widget.isViewMode,
|
|
popupProps: PopupProps.menu(
|
|
showSearchBox: true,
|
|
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: 250),
|
|
searchFieldProps: TextFieldProps(
|
|
decoration: InputDecoration(
|
|
hintText: "Search User...",
|
|
contentPadding: EdgeInsets.symmetric(
|
|
horizontal: 10,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
items:
|
|
apiUserData!.map((user) {
|
|
return "${user['first_name']} ${user['last_name']}";
|
|
}).toList(),
|
|
dropdownDecoratorProps: DropDownDecoratorProps(
|
|
dropdownSearchDecoration: InputDecoration(
|
|
border: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(8),
|
|
borderSide: BorderSide(
|
|
color:
|
|
(_thirdApprovalFocus ?? false)
|
|
? (layoutColor ?? Colors.white)
|
|
: Colors.white,
|
|
width: 0.5,
|
|
),
|
|
),
|
|
enabledBorder: OutlineInputBorder(
|
|
borderSide: BorderSide(
|
|
color:
|
|
(_thirdApprovalFocus ?? false)
|
|
? (layoutColor ?? Colors.white)
|
|
: 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",
|
|
style: GoogleFonts.poppins(
|
|
fontSize: 12,
|
|
),
|
|
),
|
|
),
|
|
onChanged: (String? newValue) {
|
|
if (newValue == null) return;
|
|
|
|
final approverId =
|
|
userMap.entries
|
|
.firstWhere(
|
|
(entry) => entry.value == newValue,
|
|
)
|
|
.key;
|
|
|
|
setState(() {
|
|
selectedThirdApprover = approverId;
|
|
});
|
|
|
|
widget.onThirdApproverChanged?.call(
|
|
approverId,
|
|
); // ✅ not newValue, but approverId
|
|
},
|
|
// onChanged: (String? newValue) {
|
|
// setState(() {
|
|
// selectedThirdApprover = userMap.entries
|
|
// .firstWhere(
|
|
// (entry) => entry.value == newValue)
|
|
// .key;
|
|
//
|
|
// // if (selectedCountry!.isNotEmpty) {
|
|
// // errorMessages.remove("country_code");
|
|
// // }
|
|
// });
|
|
// },
|
|
),
|
|
),),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
|
|
// ),
|
|
);
|
|
}
|
|
|
|
Widget buildExceptionApprover(bool isDesktop) {
|
|
return Container(
|
|
color: Colors.white,
|
|
// child: Expanded(
|
|
// Allow first column to take available space
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Row(
|
|
children: [
|
|
Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
"Exceptional Approver",
|
|
style: GoogleFonts.poppins(
|
|
fontSize: 12,
|
|
fontWeight: FontWeight.w600,
|
|
color: Color(0xFF575A74),
|
|
),
|
|
),
|
|
SizedBox(height: 5),
|
|
CustomTextFieldUserWrapper(
|
|
isFocused: _exceptionalApprovalFocus,
|
|
padding: const EdgeInsets.symmetric(horizontal: 0),
|
|
isDesktop: isDesktop,
|
|
child: SizedBox(
|
|
height: 40,
|
|
width: double.infinity,
|
|
child: Focus(
|
|
focusNode: _exceptionalApprovalFocusNode,
|
|
onFocusChange: (hasFocus) {
|
|
setState(() {
|
|
_exceptionalApprovalFocus = hasFocus;
|
|
});
|
|
},
|
|
child: GestureDetector(
|
|
onTap: () {
|
|
// Request focus when user taps
|
|
_exceptionalApprovalFocusNode?.requestFocus();
|
|
},
|
|
child:
|
|
apiUserData == null
|
|
? Center(
|
|
child: Transform.scale(
|
|
scale: 0.5,
|
|
child: CircularProgressIndicator(),
|
|
),
|
|
)
|
|
: DropdownSearch<String>(
|
|
selectedItem: userMap[selectedExceptionalApprover],
|
|
enabled: !widget.isViewMode,
|
|
popupProps: PopupProps.menu(
|
|
showSearchBox: true,
|
|
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: 250),
|
|
searchFieldProps: TextFieldProps(
|
|
decoration: InputDecoration(
|
|
hintText: "Search User...",
|
|
contentPadding: EdgeInsets.symmetric(
|
|
horizontal: 10,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
items:
|
|
apiUserData!.map((user) {
|
|
return "${user['first_name']} ${user['last_name']}";
|
|
}).toList(),
|
|
dropdownDecoratorProps: DropDownDecoratorProps(
|
|
dropdownSearchDecoration: InputDecoration(
|
|
border: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(8),
|
|
borderSide: BorderSide(
|
|
color:
|
|
(_exceptionalApprovalFocus ?? false)
|
|
? (layoutColor ?? Colors.white)
|
|
: Colors.white,
|
|
width: 0.5,
|
|
),
|
|
),
|
|
enabledBorder: OutlineInputBorder(
|
|
borderSide: BorderSide(
|
|
color:
|
|
(_exceptionalApprovalFocus ?? false)
|
|
? (layoutColor ?? Colors.white)
|
|
: Colors.white,
|
|
width: 0.5,
|
|
// const Color(0xFFD6D5E6),
|
|
),
|
|
),
|
|
focusedBorder: OutlineInputBorder(
|
|
borderSide: BorderSide(
|
|
color : (layoutColor ?? Colors.white), 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",
|
|
style: GoogleFonts.poppins(
|
|
fontSize: 12,
|
|
),
|
|
),
|
|
),
|
|
|
|
onChanged: (String? newValue) {
|
|
if (newValue == null) return;
|
|
|
|
final approverId =
|
|
userMap.entries
|
|
.firstWhere(
|
|
(entry) => entry.value == newValue,
|
|
)
|
|
.key;
|
|
|
|
setState(() {
|
|
selectedExceptionalApprover = approverId;
|
|
});
|
|
|
|
widget.onExceptionalApproverChanged?.call(
|
|
approverId,
|
|
); // ✅ not newValue, but approverId
|
|
},
|
|
),
|
|
),),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
|
|
// ),
|
|
);
|
|
}
|
|
|
|
|
|
Widget buildApproverSubstitute1(bool isDesktop) {
|
|
return Container(
|
|
color: Colors.white,
|
|
// child: Expanded(
|
|
// Allow first column to take available space
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Row(
|
|
children: [
|
|
Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
"Delegate To",
|
|
style: GoogleFonts.poppins(
|
|
fontSize: 12,
|
|
fontWeight: FontWeight.w600,
|
|
color: Color(0xFF575A74),
|
|
),
|
|
),
|
|
SizedBox(height: 5),
|
|
CustomTextFieldUserWrapper(
|
|
isFocused: _delegateFocus,
|
|
padding: const EdgeInsets.symmetric(horizontal: 0),
|
|
isDesktop: isDesktop,
|
|
child: SizedBox(
|
|
height: 40,
|
|
width: double.infinity,
|
|
child: Focus(
|
|
focusNode: _delegateFocusNode,
|
|
onFocusChange: (hasFocus) {
|
|
setState(() {
|
|
_delegateFocus = hasFocus;
|
|
});
|
|
},
|
|
child: GestureDetector(
|
|
onTap: () {
|
|
_delegateFocusNode?.requestFocus();
|
|
},
|
|
child:
|
|
apiUserData == null
|
|
? Center(
|
|
child: Transform.scale(
|
|
scale: 0.5,
|
|
child: CircularProgressIndicator(),
|
|
),
|
|
)
|
|
: DropdownSearch<String>(
|
|
// selectedItem: userMap[selectedSubstituteApprover],
|
|
selectedItem:
|
|
selectedSubstituteApprover != null
|
|
? userMap[selectedSubstituteApprover]
|
|
: null,
|
|
enabled: !widget.isViewMode,
|
|
popupProps: PopupProps.menu(
|
|
showSearchBox: true,
|
|
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: 133),
|
|
searchFieldProps: TextFieldProps(
|
|
decoration: InputDecoration(
|
|
hintText: "Search User...",
|
|
contentPadding: EdgeInsets.symmetric(
|
|
horizontal: 10,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
items:
|
|
apiUserData!.map((user) {
|
|
return "${user['first_name']} ${user['last_name']}";
|
|
}).toList(),
|
|
dropdownDecoratorProps: DropDownDecoratorProps(
|
|
dropdownSearchDecoration: InputDecoration(
|
|
border: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(8),
|
|
borderSide: BorderSide(
|
|
color:
|
|
(_delegateFocus ?? false)
|
|
? (layoutColor ?? Colors.white)
|
|
: Colors.white,
|
|
width: 0.5,
|
|
),
|
|
),
|
|
enabledBorder: OutlineInputBorder(
|
|
borderSide: BorderSide(
|
|
color:
|
|
(_delegateFocus ?? false)
|
|
? (layoutColor ?? Colors.white)
|
|
: 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",
|
|
style: GoogleFonts.poppins(
|
|
fontSize: 12,
|
|
),
|
|
),
|
|
),
|
|
|
|
// onChanged: (String? newValue) {
|
|
// setState(() {
|
|
// selectedFirstApprover = userMap.entries
|
|
// .firstWhere(
|
|
// (entry) => entry.value == newValue)
|
|
// .key;
|
|
//
|
|
// // if (selectedCountry!.isNotEmpty) {
|
|
// // errorMessages.remove("country_code");
|
|
// // }
|
|
// });
|
|
// widget.onFirstApproverChanged?.call(newValue);
|
|
// },
|
|
onChanged: (String? newValue) {
|
|
if (newValue == null) return;
|
|
|
|
final approverId =
|
|
userMap.entries
|
|
.firstWhere(
|
|
(entry) => entry.value == newValue,
|
|
)
|
|
.key;
|
|
|
|
setState(() {
|
|
selectedSubstituteApprover = approverId;
|
|
});
|
|
|
|
widget.onFirstSubsApproverChanged?.call(
|
|
approverId,
|
|
); // ✅ not newValue, but approverId
|
|
},
|
|
),
|
|
),),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
|
|
// ),
|
|
);
|
|
}
|
|
|
|
Widget buildDelegationStartDateField(bool isDesktop) {
|
|
Future<void> _selectCheckOutDate(BuildContext context) async {
|
|
DateTime now = DateTime.now();
|
|
DateTime today = DateTime(now.year, now.month, now.day);
|
|
|
|
// Parse date from notifier if available, else use today
|
|
DateTime initialDate;
|
|
|
|
initialDate = today;
|
|
|
|
// // Use previously selected date if valid
|
|
// if (_selectedCheckOutDate != null &&
|
|
// _selectedCheckOutDate!.isAfter(today)) {
|
|
// initialDate = _selectedCheckOutDate!;
|
|
// }
|
|
//
|
|
// final pickedDate = await showDatePicker(
|
|
// context: context,
|
|
// initialDate: initialDate,
|
|
// firstDate: initialDate,
|
|
// lastDate: DateTime(2100),
|
|
// );
|
|
|
|
DateTime? pickedDate = await showDatePicker(
|
|
context: context,
|
|
initialDate:
|
|
_selectedCheckOutDate != null &&
|
|
_selectedCheckOutDate!.isAfter(today)
|
|
? _selectedCheckOutDate!
|
|
: today,
|
|
firstDate: today,
|
|
lastDate: DateTime(2100),
|
|
initialEntryMode: DatePickerEntryMode.calendarOnly,
|
|
);
|
|
|
|
if (pickedDate != null && pickedDate != _selectedCheckOutDate) {
|
|
setState(() {
|
|
_selectedCheckOutDate = pickedDate;
|
|
widget.controllers["delegationStartDate"]?.text = DateFormat(
|
|
'dd-MM-yyyy',
|
|
).format(pickedDate);
|
|
|
|
if (_selectedEndDate != null &&
|
|
_selectedEndDate!.isBefore(_selectedCheckOutDate!)) {
|
|
_selectedEndDate = null;
|
|
widget.controllers["delegationEndDate"]?.text = '';
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
return Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
"Start Date",
|
|
style: GoogleFonts.poppins(
|
|
fontSize: 12,
|
|
fontWeight: FontWeight.w600,
|
|
color: Color(0xFF575A74),
|
|
),
|
|
),
|
|
SizedBox(height: 5),
|
|
CustomTextFieldUserWrapper(
|
|
isFocused: _startDateFocus,
|
|
isDesktop: widget.isDesktop,
|
|
child: SizedBox(
|
|
height: 40,
|
|
child:Focus(
|
|
focusNode: _startDateFocusNode,
|
|
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)) {
|
|
_startDateFocusNode?.requestFocus();
|
|
_selectCheckOutDate(context);
|
|
return KeyEventResult.handled; // Stop default behavior
|
|
}
|
|
return KeyEventResult.ignored; // Allow normal handling
|
|
},
|
|
child: GestureDetector(
|
|
behavior: HitTestBehavior.opaque,
|
|
onTapDown: (details) {
|
|
_startDateFocusNode?.requestFocus();
|
|
_selectCheckOutDate(context);
|
|
},
|
|
child: AbsorbPointer(
|
|
child: TextField(
|
|
controller: widget.controllers["delegationStartDate"],
|
|
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),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
// child: GestureDetector(
|
|
// // onTap: () async{
|
|
// // _selectCheckOutDate(context);
|
|
// //
|
|
// // },
|
|
// onTap: () async {
|
|
// _startDateFocusNode?.requestFocus();
|
|
// await _selectCheckOutDate(context);
|
|
// },
|
|
// child: AbsorbPointer(
|
|
// child: TextField(
|
|
// focusNode: _startDateFocusNode,
|
|
// controller: widget.controllers["delegationStartDate"],
|
|
// readOnly: true,
|
|
// style: const TextStyle(fontSize: 12),
|
|
// decoration: InputDecoration(
|
|
// labelText: "Select Date",
|
|
// labelStyle: const TextStyle(
|
|
// fontSize: 12,
|
|
// color: Colors.grey,
|
|
// ),
|
|
// floatingLabelBehavior: FloatingLabelBehavior.never,
|
|
// border: InputBorder.none,
|
|
// contentPadding: const EdgeInsets.symmetric(vertical: 16),
|
|
// suffixIcon: const Icon(
|
|
// Icons.calendar_today,
|
|
// size: 16,
|
|
// color: Colors.grey,
|
|
// ),
|
|
// ),
|
|
// ),
|
|
// ),
|
|
// ),
|
|
),
|
|
),
|
|
// if (errorMessages["start_date"] != null) ...[
|
|
// SizedBox(height: 5), // Space before error message
|
|
// Text(
|
|
// "Select Start Date",
|
|
// style: TextStyle(color: Colors.red, fontSize: 12),
|
|
// ),
|
|
// ],
|
|
],
|
|
);
|
|
}
|
|
|
|
Widget buildDelegationEndDateField(bool isDesktop) {
|
|
// DateTime? _selectedEndDate;
|
|
Future<void> _selectForexEndDate(BuildContext context) async {
|
|
DateTime now = DateTime.now();
|
|
DateTime today = DateTime(now.year, now.month, now.day);
|
|
|
|
DateTime minDate =
|
|
_selectedCheckOutDate != null ? _selectedCheckOutDate! : today;
|
|
|
|
// Parse date from notifier if available, else use today
|
|
DateTime initialDate;
|
|
|
|
initialDate = today;
|
|
|
|
//
|
|
// final pickedDate = await showDatePicker(
|
|
// context: context,
|
|
// initialDate: initialDate,
|
|
// firstDate: initialDate,
|
|
// lastDate: DateTime(2100),
|
|
// );
|
|
|
|
DateTime? pickedDate = await showDatePicker(
|
|
context: context,
|
|
initialDate:
|
|
_selectedEndDate != null && _selectedEndDate!.isAfter(minDate)
|
|
? _selectedEndDate!
|
|
: minDate,
|
|
firstDate: minDate,
|
|
lastDate: DateTime(2100),
|
|
initialEntryMode: DatePickerEntryMode.calendarOnly,
|
|
);
|
|
|
|
if (pickedDate != null && pickedDate != _selectedEndDate) {
|
|
setState(() {
|
|
_selectedEndDate = pickedDate;
|
|
widget.controllers["delegationEndDate"]?.text = DateFormat(
|
|
'dd-MM-yyyy',
|
|
).format(pickedDate);
|
|
});
|
|
}
|
|
}
|
|
|
|
return Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
"End Date",
|
|
style: GoogleFonts.poppins(
|
|
fontSize: 12,
|
|
fontWeight: FontWeight.w600,
|
|
color: Color(0xFF575A74),
|
|
),
|
|
),
|
|
SizedBox(height: 5),
|
|
CustomTextFieldUserWrapper(
|
|
width: isDesktop ? MediaQuery.of(context).size.width * 0.2 : null,
|
|
isFocused: _endDateFocus,
|
|
isDesktop: widget.isDesktop,
|
|
child: SizedBox(
|
|
height: 40,
|
|
child:Focus(
|
|
focusNode: _endDateFocusNode,
|
|
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) {
|
|
_selectForexEndDate(context).then((_) {
|
|
_endDateFocusNode?.requestFocus();
|
|
});
|
|
}
|
|
return KeyEventResult.handled; // Stop default behavior
|
|
}
|
|
return KeyEventResult.ignored; // Allow normal handling
|
|
},
|
|
child: GestureDetector(
|
|
behavior: HitTestBehavior.opaque,
|
|
onTapDown: (details) {
|
|
if (!widget.isViewMode) {
|
|
_selectForexEndDate(context).then((_) {
|
|
_endDateFocusNode?.requestFocus();
|
|
});
|
|
}
|
|
},
|
|
child: AbsorbPointer(
|
|
child: TextField(
|
|
controller: widget.controllers["delegationEndDate"],
|
|
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),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
// child: GestureDetector(
|
|
// // onTap: () async{
|
|
// // _selectCheckOutDate(context);
|
|
// //
|
|
// // },
|
|
// onTap: () async {
|
|
// _endDateFocusNode?.requestFocus();
|
|
// await _selectForexEndDate(context);
|
|
// },
|
|
// child: AbsorbPointer(
|
|
// child: TextField(
|
|
// focusNode: _endDateFocusNode,
|
|
// controller: widget.controllers["delegationEndDate"],
|
|
// readOnly: true,
|
|
// style: const TextStyle(fontSize: 12),
|
|
// decoration: InputDecoration(
|
|
// labelText: "Select Date",
|
|
// labelStyle: const TextStyle(
|
|
// fontSize: 12,
|
|
// color: Colors.grey,
|
|
// ),
|
|
// floatingLabelBehavior: FloatingLabelBehavior.never,
|
|
// border: InputBorder.none,
|
|
// contentPadding: const EdgeInsets.symmetric(vertical: 16),
|
|
// suffixIcon: const Icon(
|
|
// Icons.calendar_today,
|
|
// size: 16,
|
|
// color: Colors.grey,
|
|
// ),
|
|
// ),
|
|
// ),
|
|
// ),
|
|
// ),
|
|
),
|
|
),
|
|
|
|
// if (widget.errorMessages["employeeCode"] != null) ...[
|
|
// SizedBox(height: 5), // Space before error message
|
|
// Text(
|
|
// widget.errorMessages["employeeCode"]!,
|
|
// style: TextStyle(color: Colors.red, fontSize: 12),
|
|
// ),
|
|
// ],
|
|
],
|
|
);
|
|
}
|
|
|
|
Widget buildReset(bool isDesktop) {
|
|
return Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
"",
|
|
style: GoogleFonts.poppins(
|
|
fontSize: 12,
|
|
fontWeight: FontWeight.w400,
|
|
color: Colors.black,
|
|
),
|
|
),
|
|
SizedBox(height: 8),
|
|
ElevatedButton(
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: Color(0xFF114D8B),
|
|
|
|
foregroundColor: Colors.white, // Keep original color
|
|
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(8),
|
|
),
|
|
padding: EdgeInsets.symmetric(horizontal: 10, vertical: 3),
|
|
),
|
|
onPressed: () {
|
|
handleReset();
|
|
},
|
|
child: Text("Reset", style: GoogleFonts.poppins(fontSize: 11)),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
}
|