import 'dart:convert';
import 'dart:html' as html;
import 'dart:typed_data'; // Import for Uint8List
import 'package:bcrypt/bcrypt.dart';
import 'package:dropdown_search/dropdown_search.dart';
import 'package:file_picker/file_picker.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:go_router/go_router.dart';
import 'package:http/http.dart' as http;
import 'package:intl/intl.dart';
import 'package:responsive_builder/responsive_builder.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../../../config/apiUrl.dart';
import '../../../routes/custom_appBar.dart';
import '../../../routes/custom_drawer.dart';
import '../../../services/apiService.dart';
import '../../../widgets/custom_text_field.dart';
import '../../../widgets/custom_text_forex.dart';
import '../../../widgets/custom_user_form.dart';
class CreateUserForm extends StatefulWidget{
@override
_CreateUserFormState createState() => _CreateUserFormState();
}
class _CreateUserFormState extends State{
final ApiService apiService = ApiService();
String? userId;
String? token;
// Map to store controllers dynamically
final Map controllers = {};
bool isViewMode = false;
bool isEditProfile = true;
// late final List? apiCountryData ;
late List? apiCountryData;
late List? apiCostData;
late List? apiRoleData;
late List? apiUserData;
Map? apiselectedUser;
late List userList;
late Future> futureUsers;
List? resolvedUserData;
late List userIdsApi;
late Map userMap;
Map errorMessages = {};
String? selectedCountry;
String? selectedGender;
String? selectedUserType;
String? selectedRole;
String? selectedLevel;
String? selectedDepartment;
String? selectedFirstApprover;
String? selectedSecondApprover;
String? selectedThirdApprover;
String? selectedFileNames;
Uint8List? passportDocumentBytes;
String? base64PDF;
List dataHeader = [
"Fname", "Lname", "email", "password", "dob","gender","mobileNumber", "alternateMobile", "address",
"postalCode","country","passportNumber", "placeOfIssue", "passportDoc","userType","roleId","deptId",
"levelId", "firstApproval", "secondApproval", "thirdApproval","employeeCode","dateOfIssue","dateOfExpiry",
"changePassword"
];
Map get userDetials{
Map data = {
"first_name" : controllers["Fname"]?.text,
"last_name" : controllers["Lname"]?.text,
"email" : controllers["email"]?.text,
"password" : controllers["password"]?.text,
"mobile_no" : controllers["mobileNumber"]?.text,
"alternate_mobile_no": controllers["alternateMobile"]?.text,
"date_of_birth": controllers["dob"]?.text,
"address" : controllers["address"]?.text,
"gender" : selectedGender,
"postal_code" : controllers["postalCode"]?.text,
"country" : selectedCountry,
"employee_code": controllers["employeeCode"]?.text,
"user_type" : selectedUserType,
"role_id" : selectedRole,
"department_id": selectedDepartment,
"group_id" : selectedLevel,
"first_approver": selectedFirstApprover,
"second_approver": selectedSecondApprover,
"third_approver": selectedThirdApprover,
"passport_number": controllers["passportNumber"]?.text,
"place_of_issue": controllers["placeOfIssue"]?.text,
"passport_document": base64PDF,
"date_of_issue": controllers["dateOfIssue"]?.text,
"date_of_expiry": controllers["dateOfExpiry"]?.text,
"created_by": userId,
"is_active": "1",
// "passport_fileData": base64PDF,
};
return data;
}
void updateData() {
// Ensure apiselectedUser is not null before printing
if (apiselectedUser != null) {
print("API Selected User Has Data - $apiselectedUser");
setState(() { // ✅ Wrap in setState to update the UI
controllers["Fname"]?.text = apiselectedUser?["first_name"] ?? "";
controllers["Lname"]?.text = apiselectedUser?["last_name"] ?? "";
controllers["email"]?.text = apiselectedUser?["email"] ?? "";
controllers["password"]?.text = "";
// controllers["password"]?.text = apiselectedUser?["password"] ?? "";
controllers["mobileNumber"]?.text = apiselectedUser?["mobile_no"] ?? "";
controllers["alternateMobile"]?.text = apiselectedUser?["alternate_mobile_no"] ?? "";
controllers["dob"]?.text = apiselectedUser?["date_of_birth"] ?? "";
controllers["address"]?.text = apiselectedUser?["address"] ?? "";
controllers["postalCode"]?.text = apiselectedUser?["postal_code"] ?? "";
controllers["employeeCode"]?.text = apiselectedUser?["employee_code"] ?? "";
controllers["passportNumber"]?.text = apiselectedUser?["passport_number"] ?? "";
controllers["placeOfIssue"]?.text = apiselectedUser?["place_of_issue"] ?? "";
controllers["dateOfIssue"]?.text = apiselectedUser?["date_of_issue"] ?? "";
controllers["dateOfExpiry"]?.text = apiselectedUser?["date_of_expiry"] ?? "";
selectedCountry = apiselectedUser?["country_code"]?.toString() ?? "";
selectedGender = apiselectedUser?["gender"]?.toString().trim() ?? "";
base64PDF = apiselectedUser?["passport_document"]?.toString().trim() ?? "";
selectedUserType = apiselectedUser?["user_type"]?.toString().trim() ?? "";
selectedRole = apiselectedUser?["role_id"]?.toString().trim() ?? "";
selectedDepartment = apiselectedUser?["department_id"]?.toString().trim() ?? "";
selectedLevel = apiselectedUser?["level_id"]?.toString().trim() ?? "";
selectedFirstApprover = apiselectedUser?["first_approver"]?.toString() ?? "";
selectedSecondApprover = apiselectedUser?["second_approver"]?.toString() ?? "";
selectedThirdApprover = apiselectedUser?["third_approver"]?.toString() ?? "";
print("Updated selectedGender: $selectedGender"); // Debugging
});
} else {
print("API Selected User Has Data - No data available yet");
}
}
@override
void initState() {
super.initState();
WidgetsFlutterBinding.ensureInitialized();
// apiCountryData = extraData['apiCountryData']; // Extract apiCountryData
// futureUsers = extraData['apiUserData']; // Extract futureUsers (Future>)
apiCountryData = null;
apiUserData = null;
apiselectedUser = null;
apiCostData = null;
apiRoleData = null;
// Delay accessing context until the widget is fully initialized
// WidgetsBinding.instance.addPostFrameCallback((_) {
// setState(() {
// apiCountryData = (GoRouterState.of(context).extra as Map)['apiCountryData'];
// apiUserData = (GoRouterState.of(context).extra as Map)['apiUserData'];
// apiselectedUser = (GoRouterState.of(context).extra as Map)['selectedUser'];
//
//
// userList = apiUserData ?? [];
// userMap = {
// for (var user in userList)
// user['user_id'] as String: "${user['first_name']} ${user['last_name']}"
// };
//
// userIdsApi = userMap.keys.toList();
//
//
// });
// });
// Initialize controllers for each field
WidgetsBinding.instance.addPostFrameCallback((_) async{
final extraData = GoRouterState.of(context).extra as Map?;
if (extraData != null) {
print("extraData: ${extraData['selectedUser']}");
setState(() {
// apiCountryData = extraData['apiCountryData'];
// apiUserData = extraData['apiUserData'];
//
// userList = apiUserData ?? [];
// userMap = {
// for (var user in userList)
// user['user_id'].toString(): "${user['first_name']} ${user['last_name']}"
// };
// userIdsApi = userMap.keys.toList();
// Handle selectedUser as a Map (not a List)
apiselectedUser = extraData['selectedUser'] as Map?; // Cast it as a Map
isViewMode = extraData['isViewMode'] ?? false;
isEditProfile = extraData['isEditProfile'] ?? false;
});
// Add another post-frame callback to check after setState
await Future.delayed(Duration(milliseconds: 100)); // Optional delay to ensure UI has updated
updateData();
}
});
for(var field in dataHeader) {
controllers[field] = TextEditingController();
}
initializeData();
fetchCountries();
fetchDepartment();
fetchUsers();
fetchRoles();
}
Future fetchCountries() async {
try {
List countries = await apiService.fetchCountryList();
setState(() {
apiCountryData = countries;
});
} catch (e) {
print('Error fetching country list: $e');
}
}
Future fetchDepartment() async {
try {
List department = await apiService.fetchCostCenter();
setState(() {
apiCostData = department;
});
} catch (e) {
print('Error fetching department list: $e');
}
}
Future fetchUsers() async {
try {
List users = await apiService.fetchUsers();
setState(() {
// apiUserData = users;
apiUserData = users.where((user) => user["role_id"] == "3").toList();
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 fetchRoles() async {
try {
final response = await apiService.fetchMasterDropdown();
if (response is Map && response.containsKey("role")) {
List roleList = response["role"]; // Extract the list
setState(() {
apiRoleData = roleList;
});
print("APIROLEData - $apiRoleData");
} else {
print("Error: 'role' key not found or response is not a Map.");
}
} catch (e) {
print('Error fetching role list: $e');
}
}
void printFormData() {
for (var field in dataHeader) {
print("$field: ${controllers[field]?.text}");
}
}
Future initializeData ()async{
token = await getToken();
userId = await getUserId();
if(token == null || userId == null){
print("Token or USerId missing");
print("Retrieved Token: $token");
print("Retrieved UserId: $userId");
return;
}
else{
setState(() {
});
}
}
Future getToken() async {
final prefs = await SharedPreferences.getInstance();
return prefs.getString('auth_token');
}
// Future getUserId() async {
// final prefs = await SharedPreferences.getInstance();
// return prefs.getString('userId');
// }
Future getUserId() async {
final prefs = await SharedPreferences.getInstance();
final userData = prefs.getString('user_data');
if (userData != null) {
final decodedData = jsonDecode(userData);
// Return the user_id from the decoded data
return decodedData['user_id']?.toString();
}
return null;
}
@override
void dispose() {
// Dispose all controllers to prevent memory leaks
for (var controller in controllers.values) {
controller.dispose();
}
super.dispose();
}
void handleSubmit(){
print("USR Detail Submit");
printFormData();
Map data = userDetials;
if (!isValidData(data)) {
print("USERDETAILS : $userDetials");
print("Validation Failed: Required fields are missing.");
setState(() {});
return; // Stop execution if validation fails
}else {
print("USERDETAILS : $userDetials");
createUserData(userDetials);
}
}
bool isValidData(Map data) {
errorMessages.clear(); // Reset errors
// Required fields that must not be empty
List requiredFields = ["first_name", "last_name","email","mobile_no"];
if (apiselectedUser == null) {
requiredFields.add("password");
}
// Check validation for each field
for (String field in requiredFields) {
if (data[field] == null || data[field].toString().trim().isEmpty) {
errorMessages[field] = "Required";
}
}
// Mobile number validation for both mobile_no and alternate_mobile_no
if (data["mobile_no"] != null && data["mobile_no"].toString().isNotEmpty) {
if (!RegExp(r"^\d{10}$").hasMatch(data["mobile_no"].toString())) {
errorMessages["mobile_no"] = "Enter 10 digits"; // Invalid mobile number format
}
}
if (data["alternate_mobile_no"] != null && data["alternate_mobile_no"].toString().isNotEmpty) {
if (!RegExp(r"^\d{10}$").hasMatch(data["alternate_mobile_no"].toString())) {
errorMessages["alternate_mobile_no"] = "Enter 10 digits"; // Invalid mobile number format
}
}
// Email validation
if (data["email"] != null && data["email"].toString().isNotEmpty) {
if (!RegExp(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
}
}
return errorMessages.isEmpty; // Valid if there are no errors
}
void _clearError(String field) {
if (mounted && errorMessages.containsKey(field)) {
setState(() {
errorMessages.remove(field);
});
}
}
void pickPDFWeb() {
html.FileUploadInputElement uploadInput = html.FileUploadInputElement();
uploadInput.accept = '.pdf';
uploadInput.click();
uploadInput.onChange.listen((e) {
final file = uploadInput.files!.first;
final reader = html.FileReader();
reader.readAsArrayBuffer(file);
reader.onLoadEnd.listen((event) {
print('File picked: ${file.name}');
print('File size: ${file.size} bytes');
// Ensure the file is a PDF
if (!file.type.contains("pdf")) {
print("Error: Not a PDF file");
return;
}
setState(() {
selectedFileNames = file.name; // Store file name
passportDocumentBytes = reader.result as Uint8List; // Store file data
// 🔹 Convert to Base64 properly
base64PDF = base64Encode(passportDocumentBytes!);
print('Base64 Length: ${base64PDF!.length}');
print('Base64 (first 50 chars): ${base64PDF!.substring(0, 50)}');
// Ensure Base64 starts with "JVBERi0x"
if (!base64PDF!.startsWith("JVBERi0x")) {
print("Error: Base64 does not start with 'JVBERi0x'");
return;
}
// 🔹 File size check: Ensure it does not exceed 3MB
int maxFileSize = 3 * 1024 * 1024; // 3MB in bytes
if (file.size > maxFileSize) {
print('Error: File size exceeds 3MB');
return;
}
});
});
});
}
// void pickPDFWeb() {
// html.FileUploadInputElement uploadInput = html.FileUploadInputElement();
// uploadInput.accept = '.pdf';
// uploadInput.click();
//
// uploadInput.onChange.listen((e) {
// final file = uploadInput.files!.first;
// final reader = html.FileReader();
//
// reader.readAsArrayBuffer(file);
// reader.onLoadEnd.listen((event) {
// print('File picked: ${file.name}');
// print('File size: ${file.size} bytes');
//
//
// // Update the state with the selected file name
// setState(() {
// selectedFileNames = file.name; // Store only one file name
// passportDocumentBytes = reader.result as Uint8List; // Store file data
//
// // 🔹 Convert file to Base64
// base64PDF = base64Encode(passportDocumentBytes! as List);
// // File size check: Ensure the file size does not exceed 3MB
// int maxFileSize = 3 * 1024 * 1024; // 3MB in bytes
//
// if (file.size > maxFileSize) {
// print('Error: File size exceeds 3MB');
// // You can show an error message here if necessary
// // For example:
// // showError('File size cannot exceed 3MB');
// return;
// }
//
// print('File size: ${file.size} bytes');
// print('Base64 Data: $base64PDF'); // Debugging
// });
//
//
//
// });
// });
// }
Future createUserData(Map userData) async {
bool isUpdating = apiselectedUser != null && apiselectedUser!.isNotEmpty;
final String apiUrldata = isUpdating?
'$apiUrl/api/users/update/${apiselectedUser?["user_id"]}'
:'$apiUrl/api/users/create';
if (token == null) {
throw Exception('Token not found. Please log in.');
}
// Add user_id only if updating
if (isUpdating) {
userData['user_id'] = apiselectedUser?["user_id"];
}
try {
final response = isUpdating
? await http.put(
Uri.parse(apiUrldata),
headers: {
'Authorization': 'Bearer $token',
'Content-Type': 'application/json',
},
body: jsonEncode(userData),
)
: await http.post(
Uri.parse(apiUrldata),
headers: {
'Authorization': 'Bearer $token',
'Content-Type': 'application/json',
},
body: jsonEncode(userData),
);
if (response.statusCode == 200 || response.statusCode == 201) {
print("Plan submitted successfully!");
print("Response: ${response.body}");
context.go('/listUser');
} else {
print("Failed to submit plan. Status: ${response.statusCode}");
print("Error: ${response.body}");
}
} catch (e) {
print(" Error submitting plan: $e");
}
}
void handleChangePassword(String enteredPassword) {
var hashedPassword = apiselectedUser?["password"]; // Get stored bcrypt hash
print("HAshPAssword - $hashedPassword ");
print("enteredPassword - $enteredPassword ");
if (hashedPassword != null && hashedPassword.isNotEmpty) {
bool isMatch = BCrypt.checkpw(enteredPassword, hashedPassword); // Compare passwords
setState(() { // ✅ Ensure UI updates
if (isMatch) {
errorMessages["password"] = "New password is not similar to old password";
print(" Password match!");
} else {
print(" Password NOT match!");
errorMessages.remove("password"); // Clear error if password is different
}
});
} else {
print("No stored password found.");
}
}
@override
Widget build(BuildContext context){
return ResponsiveBuilder(builder: (context, sizingInfo) {
bool isDesktop = sizingInfo.deviceScreenType == DeviceScreenType.desktop;
return Scaffold(
appBar: isDesktop ? null : const CustomAppBar(title:'Create User '),
drawer: isDesktop ? null : CustomDrawer(isDesktop: false),
body:
Container(
color: Colors.white,
child: Row(
children: [
if(isDesktop)
CustomDrawer(isDesktop: true),
// const Expanded(child: Center(child: Text("User Details Content"))),
Expanded(child: buildUserTable(isDesktop)),
],
),
),
);
});
}
Widget buildUserTable(bool isDesktop){
return Container(
margin: const EdgeInsets.only(left: 10.0, right: 15.0, top: 10.0,bottom: 10.0),
decoration: BoxDecoration(
border: Border.all(color: Color(0xFFF7F7FB),width: 3.5)
),
child: Column(
children: [
Container(
color: Color(0xFFF7F7FB),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Row(children: [
Text(apiselectedUser != null ? "Profile": "New User",
style: TextStyle(fontSize: 18,fontWeight: FontWeight.w200, color: Colors.black ),),
if(isEditProfile)
IconButton(
icon: Icon(Icons.edit, color: Colors.blueAccent,size: 18,),
onPressed:(){
setState(() {
isViewMode = !isViewMode;
});
}
)
],
),
),
),
SizedBox(height: 15 ,),
Expanded(
child: SingleChildScrollView(
scrollDirection: Axis.vertical,
child: _buildUserDetails(isDesktop), // Ensure this returns a scrollable widget
),
),
Container(
color: Colors.white,
child: Padding(
padding: const EdgeInsets.all(18.0),
child: isDesktop
? Row(
mainAxisAlignment: MainAxisAlignment.end,
children: _buildSubmit(isDesktop),)
:Row(
mainAxisAlignment: MainAxisAlignment.center,
children: _buildSubmit(isDesktop),)
),
)
],
),
);
}
Widget _buildUserDetails(bool isDesktop){
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(children: [
Icon(Icons.filter_1_outlined, color: Color(0xFF8B8FB2),size: 18,),
SizedBox(width: 10,),
Text("Personal Details", style: TextStyle(fontSize: 18,fontWeight: FontWeight.bold,
// color: Colors.black,
color: Color(0xFF8B8FB2)
),
)
],),
SizedBox(height: 20,),
Padding(
padding: const EdgeInsets.only(left:16.0),
// padding: const EdgeInsets.all(8.0),
// color: Colors.grey,
child: SizedBox(
width: isDesktop? MediaQuery.of(context).size.width *0.7: null,
// height: isDesktop? null :MediaQuery.of(context).size.height ,
child: isDesktop
? Row(mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(child: _buildFirstRowLeftColumn(isDesktop)),
SizedBox(width: 20),
Expanded(child: _buildFirstRowRightColumn(isDesktop)),
],
)
: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildFirstRowLeftColumn(isDesktop),
SizedBox(height: 1),
_buildFirstRowRightColumn(isDesktop),
],
),
),
),
],),
],
),
),
// SizedBox(
// child: Expanded(
// child: Container(
// margin: const EdgeInsets.only(left: 10.0, right: 15.0),
// child: Divider(
// color: Color(0xFF8B8FB2),
// thickness: 0.5,
// height: 50,
// ),
// ),
// ),
// ),
SizedBox(
width: double.infinity, // Ensures the divider takes full width
child: Stack(
alignment: Alignment.center, // Centers the icon
children: [
Divider(
color: Color(0xFF8B8FB2),
thickness: 0.5,
height: 50,
),
Container(
// padding: EdgeInsets.all(4),
color: Colors.white, // Background to avoid overlapping
child: Row(
mainAxisSize: MainAxisSize.min, // Prevents row from taking full width
children: [
Icon(Icons.keyboard_arrow_right_outlined, color: Colors.blue, size: 28), SizedBox(width: 1),
Icon(Icons.keyboard_arrow_right_outlined, color: Colors.blue, size: 28), SizedBox(width: 1),
Icon(Icons.keyboard_arrow_right_outlined, color: Colors.blue, size: 28), SizedBox(width: 1),
],
),
),
],
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(children: [
Icon(Icons.filter_2_outlined, color: Color(0xFF8B8FB2),size: 18,),
SizedBox(width: 10,),
Text("Passport Details", style: TextStyle(fontSize: 18,fontWeight: FontWeight.bold,
// color: Colors.black,
color: Color(0xFF8B8FB2)
),
)
],),
SizedBox(height: 20,),
Padding(
padding: const EdgeInsets.only(left:16.0),
child: SizedBox(
width: isDesktop? MediaQuery.of(context).size.width *0.7: null,
// height: isDesktop? null :MediaQuery.of(context).size.height ,
child: isDesktop
? Row(mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(child: _buildSecondRowLeftColumn(isDesktop)),
SizedBox(width: 20),
Expanded(child: _buildSecondRowRightColumn(isDesktop)),
],
)
: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildSecondRowLeftColumn(isDesktop),
SizedBox(height: 1),
_buildSecondRowRightColumn(isDesktop),
],
),
),
),
],),
],
),
),
SizedBox(
width: double.infinity, // Ensures the divider takes full width
child: Stack(
alignment: Alignment.center, // Centers the icon
children: [
Divider(
color: Color(0xFF8B8FB2),
thickness: 0.5,
height: 50,
),
Container(
// padding: EdgeInsets.all(4),
color: Colors.white, // Background to avoid overlapping
child: Row(
mainAxisSize: MainAxisSize.min, // Prevents row from taking full width
children: [
Icon(Icons.keyboard_arrow_right_outlined, color: Colors.blue, size: 28), SizedBox(width: 1),
Icon(Icons.keyboard_arrow_right_outlined, color: Colors.blue, size: 28), SizedBox(width: 1),
Icon(Icons.keyboard_arrow_right_outlined, color: Colors.blue, size: 28), SizedBox(width: 1),
],
),
),
],
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(children: [
Icon(Icons.filter_3_outlined, color: Color(0xFF8B8FB2),size: 18,),
SizedBox(width: 10,),
Text("Employee Organization Details", style: TextStyle(fontSize: 18,fontWeight: FontWeight.bold,
// color: Colors.black,
color: Color(0xFF8B8FB2)
),
)
],),
SizedBox(height: 20,),
Padding(
padding: const EdgeInsets.only(left:16.0),
child: SizedBox(
width: isDesktop? MediaQuery.of(context).size.width *0.7: null,
// height: isDesktop? null :MediaQuery.of(context).size.height ,
child: isDesktop
? Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(child: _buildThirdRowLeftColumn(isDesktop)),
SizedBox(width: 20),
Expanded(child: _buildThirdRowRightColumn(isDesktop)),
],
)
: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildThirdRowLeftColumn(isDesktop),
SizedBox(height: 1),
_buildThirdRowRightColumn(isDesktop),
],
),
),
),
],),
],
),
),
SizedBox(
width: double.infinity, // Ensures the divider takes full width
child: Stack(
alignment: Alignment.center, // Centers the icon
children: [
Divider(
color: Color(0xFF8B8FB2),
thickness: 0.5,
height: 50,
),
Container(
// padding: EdgeInsets.all(4),
color: Colors.white, // Background to avoid overlapping
child: Row(
mainAxisSize: MainAxisSize.min, // Prevents row from taking full width
children: [
Icon(Icons.keyboard_arrow_right_outlined, color: Colors.blue, size: 28), SizedBox(width: 1),
Icon(Icons.keyboard_arrow_right_outlined, color: Colors.blue, size: 28), SizedBox(width: 1),
Icon(Icons.keyboard_arrow_right_outlined, color: Colors.blue, size: 28), SizedBox(width: 1),
],
),
),
],
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(children: [
Icon(Icons.filter_4_outlined, color: Color(0xFF8B8FB2),size: 18,),
SizedBox(width: 10,),
Text("Approver", style: TextStyle(fontSize: 18,fontWeight: FontWeight.bold,
// color: Colors.black,
color: Color(0xFF8B8FB2)
),
)
],),
SizedBox(height: 20,),
Padding(
padding: const EdgeInsets.only(left:16.0),
child: SizedBox(
width: isDesktop? MediaQuery.of(context).size.width *0.7: null,
// height: isDesktop? null :MediaQuery.of(context).size.height ,
child: isDesktop
? Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(child: _buildForthRowLeftColumn(isDesktop)),
SizedBox(width: 20),
Expanded(child: _buildForthRowRightColumn(isDesktop)),
SizedBox(width: 20),
Expanded(child: _buildForthRowLastColumn(isDesktop)),
],
)
: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildForthRowLeftColumn(isDesktop),
SizedBox(height: 1),
_buildForthRowRightColumn(isDesktop),
SizedBox(height: 1),
_buildForthRowLastColumn(isDesktop),
],
),
),
),
],),
],
),
),
SizedBox(
width: double.infinity, // Ensures the divider takes full width
child: Stack(
alignment: Alignment.center, // Centers the icon
children: [
Divider(
color: Color(0xFF8B8FB2),
thickness: 0.5,
height: 50,
),
Container(
// padding: EdgeInsets.all(4),
color: Colors.white, // Background to avoid overlapping
child: Row(
mainAxisSize: MainAxisSize.min, // Prevents row from taking full width
children: [
Icon(Icons.keyboard_arrow_right_outlined, color: Colors.blue, size: 28), SizedBox(width: 1),
Icon(Icons.keyboard_arrow_right_outlined, color: Colors.blue, size: 28), SizedBox(width: 1),
Icon(Icons.keyboard_arrow_right_outlined, color: Colors.blue, size: 28), SizedBox(width: 1),
],
),
),
],
),
),
],
);
}
//--------------------------------- Personal Details ---------------------------
Widget _buildFirstRowLeftColumn(bool isDesktop){
DateTime? _selectedDateOfBirth;
Future _selectCheckDateOfBirth(BuildContext context) async {
DateTime now = DateTime.now();
DateTime today = DateTime(now.year, now.month, now.day);
DateTime? pickedDate = await showDatePicker(
context: context,
initialDate: _selectedDateOfBirth != null && _selectedDateOfBirth!.isAfter(today)
? _selectedDateOfBirth!
: today,
firstDate: today,
lastDate: DateTime(2100),
);
if (pickedDate != null && pickedDate != _selectedDateOfBirth) {
setState(() {
_selectedDateOfBirth = pickedDate;
controllers["dob"]?.text = DateFormat('yyyy-MM-dd').format(pickedDate);
});
}
}
return Container(
// color: Colors.yellow,
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 Name", style: TextStyle(fontSize: 15, fontWeight: FontWeight.w200, color: Colors.black)),
SizedBox(height: 5),
CustomTextFieldUserWrapper(
isFocused: false,
isDesktop: isDesktop,
child: SizedBox(
height: 40,
child: TextField(
style: TextStyle(fontSize: 12),
controller: controllers["Fname"],
enabled: !isViewMode,
onChanged: (value) {_clearError("first_name");},
decoration: InputDecoration(
labelText: "FirstName",
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16),
),
),
),
),
if (errorMessages["first_name"] != null) ...[
SizedBox(height: 5), // Space before error message
Text(
errorMessages["first_name"]!,
style: TextStyle(color: Colors.red, fontSize: 12),
),
],
],
),
],
),
SizedBox(height: 3),
Row(
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("Last Name", style: TextStyle(fontSize: 15, fontWeight: FontWeight.w200, color: Colors.black)),
SizedBox(height: 5),
CustomTextFieldUserWrapper(
isFocused: false,
isDesktop: isDesktop,
child: SizedBox(
height: 40,
child: TextField(
style: TextStyle(fontSize: 12),
controller: controllers["Lname"],
enabled: !isViewMode,
onChanged: (value) {_clearError("last_name");},
decoration: InputDecoration(
labelText: "LastName",
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16),
),
),
),
),
if (errorMessages["last_name"] != null) ...[
SizedBox(height: 5), // Space before error message
Text(
errorMessages["last_name"]!,
style: TextStyle(color: Colors.red, fontSize: 12),
),
],
],
),
],
),
SizedBox(height: 3),
Row(
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("Gender", style: TextStyle(fontSize: 15, fontWeight: FontWeight.w200, color: Colors.black)),
SizedBox(height: 5),
CustomTextFieldUserWrapper(
isFocused: false,
isDesktop: isDesktop,
child: SizedBox(
height: 40,
child: DropdownButtonFormField(
value: selectedGender,
onChanged: isViewMode ? null : (String? newValue) {
setState(() {
selectedGender = newValue;
print("selectedGender - $selectedGender");
});
},
decoration: InputDecoration(
border: InputBorder.none,
enabled: !isViewMode, // Disables input when in view mode
),
style: TextStyle(fontSize: 12),
items: [
DropdownMenuItem(value: "Male", child: Text("Male")),
DropdownMenuItem(value: "Female", child: Text("Female")),
],
hint : Text(
selectedGender ?? "Select Gender",
style: TextStyle(fontSize: 12),),
),
// child: DropdownSearch(
// selectedItem: selectedGender,
// // key: ValueKey(selectedGender),
// popupProps: PopupProps.menu(
// fit: FlexFit.loose, // Allows flexible height
// constraints: BoxConstraints(maxHeight: 250),
//
// ),
// items: ["Male", "Female", "Others"],
// dropdownDecoratorProps: DropDownDecoratorProps(
// dropdownSearchDecoration: InputDecoration(
// border: InputBorder.none,
// contentPadding: EdgeInsets.symmetric(horizontal: 1,),
// ),
// ),
// dropdownBuilder: (context, selectedItem) => Align( // Center-align selected item
// alignment: Alignment.centerLeft,
// child: Text(
// selectedItem ?? "Select",
// style: TextStyle(fontSize: 12),
// ),
// ),
// onChanged: isViewMode
// ? null
// : (String? newValue) {
// setState(() {
// // Find the country_code based on selected country_name
// selectedGender = newValue;
// print("selectedGender - $selectedGender");
// // if (selectedCountry!.isNotEmpty) {
// // errorMessages.remove("country_code");
// // }
// });
// },
//
//
// ),
),
),
],
),
],
),
SizedBox(height: 3),
Row(
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("Date of Birth", style: TextStyle(fontSize: 15, fontWeight: FontWeight.w200, color: Colors.black)),
SizedBox(height: 5),
CustomTextFieldUserWrapper(
isFocused: false,
isDesktop: isDesktop,
child: SizedBox(
height: 40,
child: GestureDetector(
onTap: isViewMode ? null :()async{
await _selectCheckDateOfBirth(context);
if (controllers["dob"]!.text.isNotEmpty) {
setState(() {
// errorMessages.remove("start_date");
});
}
},
child: AbsorbPointer(
child: TextField(
controller: controllers["dob"],
style: const TextStyle(fontSize: 12),
decoration: const InputDecoration(
labelText: "Select Date",
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16),
suffixIcon: Icon(Icons.calendar_today,
size: 16,
color: Color(0xFF8B8FB2)
),
),
),
),
),
),
),
],
),
],
),
SizedBox(height: 3),
Row(
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("Email", style: TextStyle(fontSize: 15, fontWeight: FontWeight.w200, color: Colors.black)),
SizedBox(height: 5),
CustomTextFieldUserWrapper(
isFocused: false,
isDesktop: isDesktop,
child: SizedBox(
height: 40,
child: TextField(
style: TextStyle(fontSize: 12),
controller: controllers["email"],
onChanged: (value) {_clearError("email");},
enabled: !isViewMode,
decoration: InputDecoration(
labelText: "Email",
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16),
),
),
),
),
if (errorMessages["email"] != null) ...[
SizedBox(height: 5), // Space before error message
Text(
errorMessages["email"]!,
style: TextStyle(color: Colors.red, fontSize: 12),
),
],
],
),
],
),
SizedBox(height: 3),
apiselectedUser != null?
Row(
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("Change Password", style: TextStyle(fontSize: 15, fontWeight: FontWeight.w200, color: Colors.black)),
SizedBox(height: 5),
],
),
],
):
Row(
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("Password", style: TextStyle(fontSize: 15, fontWeight: FontWeight.w200, color: Colors.black)),
SizedBox(height: 5),
CustomTextFieldUserWrapper(
isFocused: false,
isDesktop: isDesktop,
child: SizedBox(
height: 40,
child: TextField(
style: TextStyle(fontSize: 12),
controller: controllers["password"],
enabled: !isViewMode,
onChanged: (value) {_clearError("password");},
decoration: InputDecoration(
labelText: "Password",
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16),
),
),
),
),
if (errorMessages["password"] != null) ...[
SizedBox(height: 5), // Space before error message
Text(
errorMessages["password"]!,
style: TextStyle(color: Colors.red, fontSize: 12),
),
],
],
),
],
),
if(!isDesktop) SizedBox(height: 3,),
],
),
),
);
}
Widget _buildFirstRowRightColumn(bool isDesktop){
late Map countryMap; // Mapping country_code -> country_name
late List countryCodes; // List of country codes
List countryList = apiCountryData ?? [];
countryList = apiCountryData ?? [];
// Map country codes to country names
countryMap = {
for (var item in countryList) item['country_code'] as String: item['country_name'] as String
};
// Extract only country codes for processing
countryCodes = countryMap.keys.toList();
selectedCountry ??= null;
return Container(
// color: Colors.yellow,
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("Mobile Number", style: TextStyle(fontSize: 15, fontWeight: FontWeight.w200, color: Colors.black)),
SizedBox(height: 5),
CustomTextFieldUserWrapper(
isFocused: false,
isDesktop: isDesktop,
child: SizedBox(
height: 40,
child: TextField(
style: TextStyle(fontSize: 12),
controller: controllers["mobileNumber"],
enabled: !isViewMode,
onChanged: (value) {_clearError("mobile_no");},
keyboardType: TextInputType.numberWithOptions(decimal: true),
inputFormatters: [
FilteringTextInputFormatter.allow(RegExp(r'^\d*\.?\d*$')), // Allow only positive numbers with optional decimal
],
decoration: InputDecoration(
labelText: "Mobile Number",
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16),
),
),
),
),
if (errorMessages["mobile_no"] != null) ...[
SizedBox(height: 5), // Space before error message
Text(
errorMessages["mobile_no"]!,
style: TextStyle(color: Colors.red, fontSize: 12),
),
],
],
),
],
),
SizedBox(height: 3),
Row(
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("Alternate Mobile Number", style: TextStyle(fontSize: 15, fontWeight: FontWeight.w200, color: Colors.black)),
SizedBox(height: 5),
CustomTextFieldUserWrapper(
isFocused: false,
isDesktop: isDesktop,
child: SizedBox(
height: 40,
child: TextField(
style: TextStyle(fontSize: 12),
controller: controllers["alternateMobile"],
enabled: !isViewMode,
keyboardType: TextInputType.numberWithOptions(decimal: true),
inputFormatters: [
FilteringTextInputFormatter.allow(RegExp(r'^\d*\.?\d*$')), // Allow only positive numbers with optional decimal
],
decoration: InputDecoration(
labelText: "Alternate Number",
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16),
),
),
),
),
if (errorMessages["alternate_mobile_no"] != null) ...[
SizedBox(height: 5), // Space before error message
Text(
errorMessages["alternate_mobile_no"]!,
style: TextStyle(color: Colors.red, fontSize: 12),
),
],
],
),
],
),
SizedBox(height: 3),
Row(
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("Address", style: TextStyle(fontSize: 15, fontWeight: FontWeight.w200, color: Colors.black)),
SizedBox(height: 5),
CustomTextFieldUserWrapper(
isFocused: false,
isDesktop: isDesktop,
child: SizedBox(
height: 110,
child: TextField(
style: TextStyle(fontSize: 12),
controller: controllers["address"],
enabled: !isViewMode,
maxLines: 4,
keyboardType: TextInputType.multiline,
decoration: InputDecoration(
labelText: "Address",
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
// contentPadding: EdgeInsets.symmetric(vertical: 16),
contentPadding: EdgeInsets.symmetric(vertical: 4),
),
),
),
),
],
),
],
),
SizedBox(height: 3),
Row(
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("Postal Code", style: TextStyle(fontSize: 15, fontWeight: FontWeight.w200, color: Colors.black)),
SizedBox(height: 5),
CustomTextFieldUserWrapper(
isFocused: false,
isDesktop: isDesktop,
child: SizedBox(
height: 40,
child: TextField(
style: TextStyle(fontSize: 12),
controller: controllers["postalCode"],
enabled: !isViewMode,
keyboardType: TextInputType.numberWithOptions(decimal: true),
inputFormatters: [
FilteringTextInputFormatter.allow(RegExp(r'^\d*\.?\d*$')), // Allow only positive numbers with optional decimal
],
decoration: InputDecoration(
labelText: "Postal Code",
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16),
),
),
),
),
],
),
],
),
SizedBox(height: 3),
Row(
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("Country", style: TextStyle(fontSize: 15, fontWeight: FontWeight.w200, color: Colors.black)),
SizedBox(height: 5),
CustomTextFieldUserWrapper(
isFocused: false,
isDesktop: isDesktop,
child: SizedBox(
height: 40,
child: DropdownSearch(
selectedItem: countryMap[selectedCountry],
enabled: !isViewMode,
popupProps: PopupProps.menu(
showSearchBox: true, // Enables search functionality
fit: FlexFit.loose, // Allows flexible height
constraints: BoxConstraints(maxHeight: 250),
searchFieldProps: TextFieldProps(
decoration: InputDecoration(
hintText: "Search Country...",
contentPadding: EdgeInsets.symmetric(horizontal: 10),
),
),
),
items: countryMap.values.toList(),
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 Country",
style: TextStyle(fontSize: 12),
),
),
onChanged: isViewMode ? null: (String? newValue) {
setState(() {
// Find the country_code based on selected country_name
selectedCountry = countryMap.entries
.firstWhere((entry) => entry.value == newValue)
.key;
// if (selectedCountry!.isNotEmpty) {
// errorMessages.remove("country_code");
// }
});
},
),
),
),
],
),
],
),
if(!isDesktop) SizedBox(height: 3,),
],
),
),
);
}
//--------------------------------- Passport Details -----------------------------
Widget _buildSecondRowLeftColumn(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("Passport Number", style: TextStyle(fontSize: 15, fontWeight: FontWeight.w200, color: Colors.black)),
SizedBox(height: 5),
CustomTextFieldUserWrapper(
isFocused: false,
isDesktop: isDesktop,
child: SizedBox(
height: 40,
child: TextField(
style: TextStyle(fontSize: 12),
controller: controllers["passportNumber"],
enabled: !isViewMode,
decoration: InputDecoration(
labelText: "Passport Number",
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16),
),
),
),
),
],
),
],
),
SizedBox(height: 3,),
Row(
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("Passport Document", style: TextStyle(fontSize: 15, fontWeight: FontWeight.w200, color: Colors.black)),
SizedBox(height: 5),
CustomTextFieldUserWrapper(
isFocused: false,
isDesktop: isDesktop,
child: SizedBox(
height: 40,
child: GestureDetector(
onTap: pickPDFWeb,
child: Container(
padding: EdgeInsets.symmetric(horizontal: 10),
alignment: Alignment.centerLeft,
child: Text(
selectedFileNames?? "Select Document",
style: TextStyle(fontSize: 12, color: Colors.black),
overflow: TextOverflow.ellipsis, // Prevents overflow issues
),
),
),
),
),
SizedBox(height: 10),
if(base64PDF != null)
// Centers the text
Container(
color: Colors.white,
width : isDesktop
? MediaQuery.of(context).size.width * 0.25
: MediaQuery.of(context).size.width * 0.8,
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.end,
children: [
GestureDetector(onTap: (){
print('DOWLOAS- $base64PDF ');
if (base64PDF != null && base64PDF!.isNotEmpty) {
try {
// ✅ Step 1: Clean the Base64 string
String cleanedBase64 = base64PDF!
.replaceAll("\n", "") // Remove newlines
.replaceAll("\r", "") // Remove carriage returns
.replaceAll(" ", "") // Remove spaces
.trim(); // Trim any whitespace
// ✅ Step 2: Ensure valid Base64 length (multiple of 4)
while (cleanedBase64.length % 4 != 0) {
cleanedBase64 += "_"; // Add '=' padding
}
// ✅ Step 3: Decode the cleaned Base64
Uint8List bytes;
try {
bytes = base64Decode(cleanedBase64);
} catch (e) {
print("Base64 decoding failed: $e");
return;
}
// ✅ Step 4: Create a Blob for download
final blob = html.Blob([bytes], 'application/pdf');
final url = html.Url.createObjectUrlFromBlob(blob);
// ✅ Step 5: Trigger the file download
final anchor = html.AnchorElement(href: url)
..setAttribute("download", selectedFileNames ?? "document.pdf")
..style.display = "none";
html.document.body!.append(anchor);
anchor.click();
// ✅ Step 6: Clean up
anchor.remove();
html.Url.revokeObjectUrl(url);
print("Download successful!");
} catch (e) {
print("Error downloading file: $e");
}
} else {
print("No file to download.");
}
} ,
child: Container(
padding: const EdgeInsets.all(5),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(5),
color: Colors.green.shade300,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
"Download",
style: TextStyle(fontSize: 12, color: Colors.white,fontWeight: FontWeight.bold),
overflow: TextOverflow.ellipsis,
textAlign: TextAlign.end, // Ensures text is centered within the Text widget
),
SizedBox(width: 5),
Icon(Icons.download ,size: 13, color: Colors.white,)
],
),
),
)
],
),
),
// CustomTextFieldUserWrapper(
// isFocused: false,
// isDesktop: isDesktop,
// child: SizedBox(
// height: 40,
// child: TextField(
// style: TextStyle(fontSize: 12),
// controller: controllers["passportDoc"],
// decoration: InputDecoration(
// labelText: "Select Document",
// labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
// floatingLabelBehavior: FloatingLabelBehavior.never,
// border: InputBorder.none,
// contentPadding: EdgeInsets.symmetric(vertical: 16),
// ),
// ),
// ),
// ),
],
),
],
),
if(!isDesktop) SizedBox(height: 3,),
],
),
),
);
}
Widget _buildSecondRowRightColumn(bool isDesktop){
DateTime? _selectedDateOfIssue;
Future _selectCheckDateOfIssue(BuildContext context) async {
DateTime now = DateTime.now();
DateTime today = DateTime(now.year, now.month, now.day);
DateTime? pickedDate = await showDatePicker(
context: context,
initialDate: _selectedDateOfIssue != null && _selectedDateOfIssue!.isAfter(today)
? _selectedDateOfIssue!
: today,
firstDate: today,
lastDate: DateTime(2100),
);
if (pickedDate != null && pickedDate != _selectedDateOfIssue) {
setState(() {
_selectedDateOfIssue = pickedDate;
controllers["dateOfIssue"]?.text = DateFormat('yyyy-MM-dd').format(pickedDate);
});
}
}
DateTime? _selectedDateOfExpiry;
Future _selectCheckDateOfExpiry(BuildContext context) async {
DateTime now = DateTime.now();
DateTime today = DateTime(now.year, now.month, now.day);
DateTime? pickedDate = await showDatePicker(
context: context,
initialDate: _selectedDateOfExpiry != null && _selectedDateOfExpiry!.isAfter(today)
? _selectedDateOfExpiry!
: today,
firstDate: today,
lastDate: DateTime(2100),
);
if (pickedDate != null && pickedDate != _selectedDateOfExpiry) {
setState(() {
_selectedDateOfExpiry = pickedDate;
controllers["dateOfExpiry"]?.text = DateFormat('yyyy-MM-dd').format(pickedDate);
});
}
}
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("Place of Issue", style: TextStyle(fontSize: 15, fontWeight: FontWeight.w200, color: Colors.black)),
SizedBox(height: 5),
CustomTextFieldUserWrapper(
isFocused: false,
isDesktop: isDesktop,
child: SizedBox(
height: 40,
child: TextField(
style: TextStyle(fontSize: 12),
controller: controllers["placeOfIssue"],
enabled: !isViewMode,
decoration: InputDecoration(
labelText: "Place Of Issue",
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16),
),
),
),
),
],
),
],
),
SizedBox(height: 3,),
Row(
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("Date of Issue", style: TextStyle(fontSize: 15, fontWeight: FontWeight.w200, color: Colors.black)),
SizedBox(height: 5),
CustomTextFieldUserWrapper(
isFocused: false,
isDesktop: isDesktop,
child: SizedBox(
height: 40,
child: GestureDetector(
onTap: isViewMode? null : ()async{
await _selectCheckDateOfIssue(context);
if (controllers["dateOfIssue"]!.text.isNotEmpty) {
setState(() {
// errorMessages.remove("start_date");
});
}
},
child: AbsorbPointer(
child: TextField(
controller: controllers["dateOfIssue"],
style: const TextStyle(fontSize: 12),
decoration: const InputDecoration(
labelText: "Select Date",
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16),
suffixIcon: Icon(Icons.calendar_today,
size: 16,
color: Color(0xFF8B8FB2)
),
),
),
),
),
),
),
],
),
],
),
SizedBox(height: 3,),
Row(
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("Date of Expiry", style: TextStyle(fontSize: 15, fontWeight: FontWeight.w200, color: Colors.black)),
SizedBox(height: 5),
CustomTextFieldUserWrapper(
isFocused: false,
isDesktop: isDesktop,
child: SizedBox(
height: 40,
child: GestureDetector(
onTap: isViewMode? null : ()async{
await _selectCheckDateOfExpiry(context);
if (controllers["dateOfExpiry"]!.text.isNotEmpty) {
setState(() {
// errorMessages.remove("start_date");
});
}
},
child: AbsorbPointer(
child: TextField(
controller: controllers["dateOfExpiry"],
style: const TextStyle(fontSize: 12),
decoration: const InputDecoration(
labelText: "Select Date",
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16),
suffixIcon: Icon(Icons.calendar_today,
size: 16,
color: Color(0xFF8B8FB2)
),
),
),
),
),
),
),
],
),
],
),
if(!isDesktop) SizedBox(height: 3,),
],
),
),
);
}
//----------------------------------- Employee Organization Details ------------------
Widget _buildThirdRowLeftColumn(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("Employee Code", style: TextStyle(fontSize: 15, fontWeight: FontWeight.w200, color: Colors.black)),
SizedBox(height: 5),
CustomTextFieldUserWrapper(
isFocused: false,
isDesktop: isDesktop,
child: SizedBox(
height: 40,
child: TextField(
style: TextStyle(fontSize: 12),
controller: controllers["employeeCode"],
enabled: !isViewMode,
decoration: InputDecoration(
labelText: "Employee Code",
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16),
),
),
),
),
],
),
],
),
SizedBox(height: 3),
Row(
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("User Type", style: TextStyle(fontSize: 15, fontWeight: FontWeight.w200, color: Colors.black)),
SizedBox(height: 5),
CustomTextFieldUserWrapper(
isFocused: false,
isDesktop: isDesktop,
child: SizedBox(
height: 40,
child: DropdownSearch(
selectedItem: selectedUserType,
enabled: !isViewMode,
popupProps: PopupProps.menu(
// showSearchBox: true,
fit: FlexFit.loose, // Allows flexible height
constraints: BoxConstraints(maxHeight: 250),
),
items: ["Normal", "Privilege",],
dropdownDecoratorProps: DropDownDecoratorProps(
dropdownSearchDecoration: InputDecoration(
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(horizontal: 1,),
),
),
dropdownBuilder: (context, selectedItem) => Align( // Center-align selected item
alignment: Alignment.centerLeft,
child: Text(
selectedItem ?? "Select",
style: TextStyle(fontSize: 12),
),
),
onChanged: (String? newValue) {
setState(() {
// Find the country_code based on selected country_name
selectedUserType = newValue;
// print("selectedUserType - $selectedUserType");
// if (selectedCountry!.isNotEmpty) {
// errorMessages.remove("country_code");
// }
});
},
),
),
),
],
),
],
),
if(!isDesktop)
SizedBox(height: 3),
],
),
),
);
}
Widget _buildThirdRowRightColumn(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("Role ", style: TextStyle(fontSize: 15, fontWeight: FontWeight.w200, color: Colors.black)),
SizedBox(height: 5),
CustomTextFieldUserWrapper(
isFocused: false, // Dropdown doesn't use focus
isDesktop: isDesktop,
child: SizedBox(
height: 45, // Set appropriate height
child: DropdownButtonFormField(
value: selectedRole,
style: TextStyle(fontSize: 12),
decoration: InputDecoration(
border: InputBorder.none,
contentPadding:
EdgeInsets.symmetric(horizontal: 10), // Proper padding
),
onChanged: isViewMode ? null : (newValue) {
setState(() {
selectedRole = newValue;
});
},
items:apiRoleData?.map>((item){
return DropdownMenuItem(
value: item['dropdown_key'], // ID as value
child:Text(item['dropdown_value'] ?? "Select")
);
}).toList(),
hint: Text("Select"),
),
),
),
],
),
],
),
SizedBox(height: 3),
Row(
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("Group", style: TextStyle(fontSize: 15, fontWeight: FontWeight.w200, color: Colors.black)),
SizedBox(height: 5),
CustomTextFieldUserWrapper(
isFocused: false,
isDesktop: isDesktop,
child: SizedBox(
height: 40,
child: DropdownSearch(
selectedItem: selectedLevel,
enabled: !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),
),
),
onChanged: (String? newValue) {
setState(() {
// Find the country_code based on selected country_name
selectedLevel = newValue;
// if (selectedCountry!.isNotEmpty) {
// errorMessages.remove("country_code");
// }
});
},
),
),
),
],
),
],
),
SizedBox(height: 3),
Row(
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("Department", style: TextStyle(fontSize: 15, fontWeight: FontWeight.w200, color: Colors.black)),
SizedBox(height: 5),
CustomTextFieldUserWrapper(
isFocused: false, // Dropdown doesn't use focus
isDesktop: isDesktop,
child: SizedBox(
height: 45, // Set appropriate height
child: DropdownButtonFormField(
value: selectedDepartment,
style: TextStyle(fontSize: 12),
decoration: InputDecoration(
border: InputBorder.none,
contentPadding:
EdgeInsets.symmetric(horizontal: 10), // Proper padding
),
onChanged: isViewMode ? null : (newValue) {
setState(() {
selectedDepartment = newValue;
});
},
items:apiCostData?.map>((item){
return DropdownMenuItem(
value: item['department_id'], // ID as value
child: Text(item['name'] ?? "Unknown"),
);
}).toList(),
hint: Text("Select"),
),
),
),
],
),
],
),
],
),
),
);
}
//----------------------------------- Approver ---------------------------------------
Widget _buildForthRowLeftColumn(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: TextStyle(fontSize: 15, fontWeight: FontWeight.w200, color: Colors.black)),
SizedBox(height: 5),
CustomTextFieldUserWrapper(
isFocused: false,
isDesktop: isDesktop,
width: isDesktop? MediaQuery.of(context).size.width * 0.2 : MediaQuery.of(context).size.width * 0.8,
child: SizedBox(
height: 40,
child: apiUserData == null
? Center(child: Transform.scale(scale: 0.5, child: CircularProgressIndicator(),),)
: DropdownSearch(
selectedItem: userMap[selectedFirstApprover],
enabled: !isViewMode,
popupProps: PopupProps.menu(
showSearchBox: true,
fit: FlexFit.loose, // Allows flexible height
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: InputBorder.none,
contentPadding: EdgeInsets.symmetric(horizontal: 1,),
),
),
dropdownBuilder: (context, selectedItem) => Align( // Center-align selected item
alignment: Alignment.centerLeft,
child: Text(
selectedItem ?? "Select",
style: TextStyle(fontSize: 12),
),
),
onChanged: (String? newValue) {
setState(() {
selectedFirstApprover = userMap.entries
.firstWhere((entry) => entry.value == newValue)
.key;
// if (selectedCountry!.isNotEmpty) {
// errorMessages.remove("country_code");
// }
});
},
),
),
),
],
),
],
),
],
),
),
);
}
Widget _buildForthRowRightColumn(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: TextStyle(fontSize: 15, fontWeight: FontWeight.w200, color: Colors.black)),
SizedBox(height: 5),
CustomTextFieldUserWrapper(
isFocused: false,
isDesktop: isDesktop,
width: isDesktop? MediaQuery.of(context).size.width * 0.2 : MediaQuery.of(context).size.width * 0.8,
child: SizedBox(
height: 40,
child: apiUserData == null
? Center(child: Transform.scale(scale: 0.5, child: CircularProgressIndicator(),),)
: DropdownSearch(
selectedItem: userMap[selectedSecondApprover],
enabled: !isViewMode,
popupProps: PopupProps.menu(
showSearchBox: true,
fit: FlexFit.loose, // Allows flexible height
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: InputBorder.none,
contentPadding: EdgeInsets.symmetric(horizontal: 1,),
),
),
dropdownBuilder: (context, selectedItem) => Align( // Center-align selected item
alignment: Alignment.centerLeft,
child: Text(
selectedItem ?? "Select",
style: TextStyle(fontSize: 12),
),
),
onChanged: (String? newValue) {
setState(() {
selectedSecondApprover = userMap.entries
.firstWhere((entry) => entry.value == newValue)
.key;
// if (selectedCountry!.isNotEmpty) {
// errorMessages.remove("country_code");
// }
});
},
),
),
),
],
),
],
),
],
),
),
);
}
Widget _buildForthRowLastColumn(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: TextStyle(fontSize: 15, fontWeight: FontWeight.w200, color: Colors.black)),
SizedBox(height: 5),
CustomTextFieldUserWrapper(
isFocused: false,
isDesktop: isDesktop,
width: isDesktop? MediaQuery.of(context).size.width * 0.2 : MediaQuery.of(context).size.width * 0.8,
child: SizedBox(
height: 40,
child: apiUserData == null
? Center(child: Transform.scale(scale: 0.5, child: CircularProgressIndicator(),),)
: DropdownSearch(
selectedItem: userMap[selectedThirdApprover],
enabled: !isViewMode,
popupProps: PopupProps.menu(
showSearchBox: true,
fit: FlexFit.loose, // Allows flexible height
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: InputBorder.none,
contentPadding: EdgeInsets.symmetric(horizontal: 1,),
),
),
dropdownBuilder: (context, selectedItem) => Align( // Center-align selected item
alignment: Alignment.centerLeft,
child: Text(
selectedItem ?? "Select",
style: TextStyle(fontSize: 12),
),
),
onChanged: (String? newValue) {
setState(() {
selectedThirdApprover = userMap.entries
.firstWhere((entry) => entry.value == newValue)
.key;
// if (selectedCountry!.isNotEmpty) {
// errorMessages.remove("country_code");
// }
});
},
),
),
),
],
),
],
),
],
),
),
);
}
//----------------------------------- Submit ---------------------------------------
List _buildSubmit(isDesktop){
return[
ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.white,
foregroundColor: Colors.blueAccent,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
side: BorderSide(color: Colors.blueAccent, width: 2),
),
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
),
onPressed: (){
context.go('/listUser');
},
child: Text("Cancel")
),
SizedBox(width: 20,),
MouseRegion(
cursor: isViewMode ? SystemMouseCursors.forbidden : SystemMouseCursors.click,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: isViewMode ? Colors.blueAccent : Colors.blueAccent, // Keep original color
foregroundColor: isViewMode ? Colors.white : Colors.white, // Keep original color
disabledBackgroundColor: Colors.blueAccent, // Ensure color remains when disabled
disabledForegroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
side: BorderSide(color: Colors.blueAccent, width: 2),
),
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
),
onPressed: isViewMode ? null : handleSubmit, // Disable when in view mode
child: Text("Submit"),
),
)
];
}
}
class DatabaseHelper {
// Example: Singleton pattern to ensure only one instance of the class exists
static final DatabaseHelper _instance = DatabaseHelper._internal();
// Private constructor to prevent external instantiation
DatabaseHelper._internal();
// Public method to access the instance
static DatabaseHelper get instance => _instance;
// Add your database methods here
Future fetchData() async {
// Implement data fetching logic
}
}