3154 lines
117 KiB
Dart
3154 lines
117 KiB
Dart
import 'dart:async';
|
|
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:frontend/utils/auth_utils.dart';
|
|
import 'package:go_router/go_router.dart';
|
|
import 'package:http/http.dart' as http;
|
|
import 'package:http_parser/http_parser.dart' as http_parser;
|
|
import 'package:intl/intl.dart';
|
|
import 'package:responsive_builder/responsive_builder.dart';
|
|
import 'package:shared_preferences/shared_preferences.dart';
|
|
import 'package:http_parser/http_parser.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 CreateUserForm1 extends StatefulWidget {
|
|
@override
|
|
_CreateUserFormState createState() => _CreateUserFormState();
|
|
}
|
|
|
|
class _CreateUserFormState extends State<CreateUserForm1> {
|
|
final ApiService apiService = ApiService();
|
|
late TabController _tabController;
|
|
|
|
String? userId;
|
|
String? orgId;
|
|
|
|
String? token;
|
|
|
|
// Map to store controllers dynamically
|
|
final Map<String, TextEditingController> controllers = {};
|
|
bool isViewMode = false;
|
|
bool isEditProfile = false;
|
|
// late final List<dynamic>? apiCountryData ;
|
|
|
|
late List<dynamic>? apiCountryData;
|
|
late List<dynamic>? apiCostData;
|
|
late List<dynamic>? apiRoleData;
|
|
late List<dynamic>? apiUserData;
|
|
Map<String, dynamic>? apiselectedUser;
|
|
|
|
late List<dynamic> userList;
|
|
late Future<List<dynamic>> futureUsers;
|
|
|
|
List<dynamic>? resolvedUserData;
|
|
late List<String> userIdsApi;
|
|
late Map<String, String> userMap;
|
|
|
|
Map<String, String> errorMessages = {};
|
|
|
|
String? selectedCountry;
|
|
String? selectedGender;
|
|
String? selectedUserType;
|
|
String? selectedRole;
|
|
String? selectedLevel;
|
|
String? selectedDepartment;
|
|
String? selectedFirstApprover;
|
|
String? selectedSecondApprover;
|
|
String? selectedThirdApprover;
|
|
|
|
String? selectedFileNames;
|
|
Uint8List? passportDocumentBytes;
|
|
String? passportFileUrlFromApi;
|
|
String? base64PDF;
|
|
|
|
html.File? passportFile;
|
|
|
|
List<String> dataHeader = [
|
|
"Fname",
|
|
"Lname",
|
|
"email",
|
|
"password",
|
|
"dob",
|
|
"gender",
|
|
"mobileNumber",
|
|
"alternateMobile",
|
|
"address",
|
|
"postalCode",
|
|
"country",
|
|
"passportNumber",
|
|
"placeOfIssue",
|
|
"passportDoc",
|
|
"userType",
|
|
"roleId",
|
|
"deptId",
|
|
"levelId",
|
|
"firstApproval",
|
|
"secondApproval",
|
|
"thirdApproval",
|
|
"employeeCode",
|
|
"dateOfIssue",
|
|
"dateOfExpiry",
|
|
"changePassword",
|
|
];
|
|
|
|
Color? layoutColor;
|
|
Color? bodyColor;
|
|
|
|
Map<String, dynamic> get userDetials {
|
|
Map<String, dynamic> 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_code": 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": passportFile,
|
|
|
|
"date_of_issue": controllers["dateOfIssue"]?.text,
|
|
"date_of_expiry": controllers["dateOfExpiry"]?.text,
|
|
"created_by": userId,
|
|
"is_active": "1",
|
|
"org_id": orgId,
|
|
// "passport_fileData": passportFile,
|
|
};
|
|
return data;
|
|
}
|
|
|
|
Future<void> updateData() async {
|
|
// Ensure apiselectedUser is not null before printing
|
|
if (apiselectedUser != null) {
|
|
print("API Selected User Has Data - $widget.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"] ?? "";
|
|
|
|
if (apiselectedUser?["date_of_issue"] != null) {
|
|
controllers["dateOfIssue"]?.text =
|
|
apiselectedUser?["date_of_issue"] ?? "";
|
|
}
|
|
|
|
if (apiselectedUser?["date_of_expiry"] != null) {
|
|
controllers["dateOfExpiry"]?.text =
|
|
apiselectedUser?["date_of_expiry"] ?? "";
|
|
}
|
|
|
|
if (apiselectedUser?["country_code"] != null) {
|
|
selectedCountry = apiselectedUser?["country_code"]?.toString() ?? "";
|
|
}
|
|
if (apiselectedUser?["gender"] != null) {
|
|
selectedGender = apiselectedUser?["gender"]?.toString().trim() ?? "";
|
|
}
|
|
if (apiselectedUser?["user_type"] != null) {
|
|
selectedUserType =
|
|
apiselectedUser?["user_type"]?.toString().trim() ?? "";
|
|
}
|
|
|
|
if (apiselectedUser?["role_id"] != null) {
|
|
selectedRole = apiselectedUser?["role_id"]?.toString().trim() ?? "";
|
|
}
|
|
// selectedDepartment =
|
|
// apiselectedUser?["department_id"]?.toString().trim() ?? "";
|
|
|
|
if (apiselectedUser?["department_id"] != null) {
|
|
selectedDepartment = apiselectedUser!["department_id"].toString();
|
|
}
|
|
// print(
|
|
// "selectedDepartment - $selectedDepartment - ${apiselectedUser?["department_id"]} ");
|
|
if (apiselectedUser?["level_id"] != null) {
|
|
selectedLevel = apiselectedUser?["level_id"]?.toString().trim() ?? "";
|
|
}
|
|
if (apiselectedUser?["first_approver"] != null) {
|
|
selectedFirstApprover =
|
|
apiselectedUser?["first_approver"]?.toString() ?? "";
|
|
}
|
|
|
|
if (apiselectedUser?["second_approver"] != null) {
|
|
selectedSecondApprover =
|
|
apiselectedUser?["second_approver"]?.toString() ?? "";
|
|
}
|
|
|
|
if (apiselectedUser?["third_approver"] != null) {
|
|
selectedThirdApprover =
|
|
apiselectedUser?["third_approver"]?.toString() ?? "";
|
|
}
|
|
|
|
print("Updated selectedGender: $selectedGender"); // Debugging
|
|
|
|
// ✅ Load passport document from API
|
|
String? apiDocPath = apiselectedUser?["passport_document"];
|
|
if (apiDocPath != null && apiDocPath.isNotEmpty) {
|
|
passportFileUrlFromApi = apiDocPath;
|
|
selectedFileNames =
|
|
apiDocPath.split('/').last; // Extract filename from path
|
|
passportFile = null; // No local file selected yet
|
|
} else {
|
|
passportFileUrlFromApi = null;
|
|
selectedFileNames = null;
|
|
passportFile = null;
|
|
}
|
|
});
|
|
} else {
|
|
print("API Selected User Has Data - No data available yet");
|
|
}
|
|
}
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
|
|
WidgetsFlutterBinding.ensureInitialized();
|
|
|
|
// Step 1: Set 'reloaded' flag before page unload
|
|
html.window.onBeforeUnload.listen((event) {
|
|
html.window.localStorage['reloaded'] = 'true';
|
|
});
|
|
|
|
// apiCountryData = extraData['apiCountryData']; // Extract apiCountryData
|
|
// futureUsers = extraData['apiUserData']; // Extract futureUsers (Future<List<dynamic>>)
|
|
apiCountryData = null;
|
|
apiUserData = null;
|
|
// apiselectedUser = null;
|
|
apiCostData = null;
|
|
apiRoleData = null;
|
|
|
|
// Initialize controllers for each field
|
|
|
|
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
|
final wasReloaded = html.window.localStorage['reloaded'] == 'true';
|
|
|
|
if (wasReloaded) {
|
|
html.window.localStorage.remove('reloaded'); // Clear it
|
|
context.go('/listUser'); // Navigate using go_router
|
|
}
|
|
|
|
final extraData =
|
|
GoRouterState.of(context).extra as Map<String, dynamic>?;
|
|
|
|
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<String, dynamic>?; // Cast it as a Map
|
|
isViewMode = extraData['isViewMode'] ?? false;
|
|
isEditProfile = extraData['isEditProfile'] ?? false;
|
|
});
|
|
|
|
print("selectedUser: $apiselectedUser");
|
|
|
|
// Add another post-frame callback to check after setState
|
|
// await Future.delayed(Duration(
|
|
// milliseconds: 100)); // Optional delay to ensure UI has updated
|
|
updateData();
|
|
}
|
|
|
|
initializeData();
|
|
fetchCountries();
|
|
fetchDepartment();
|
|
fetchUsers();
|
|
fetchRoles();
|
|
loadInitialData();
|
|
});
|
|
|
|
for (var field in dataHeader) {
|
|
controllers[field] = TextEditingController();
|
|
}
|
|
}
|
|
|
|
Future<void> fetchCountries() async {
|
|
try {
|
|
List<dynamic> countries = await apiService.fetchCountryList(context);
|
|
setState(() {
|
|
apiCountryData = countries;
|
|
});
|
|
} catch (e) {
|
|
print('Error fetching country list: $e');
|
|
}
|
|
}
|
|
|
|
Future<void> fetchDepartment() async {
|
|
try {
|
|
List<dynamic> department = await apiService.fetchCostCenter(context);
|
|
setState(() {
|
|
apiCostData = department;
|
|
});
|
|
} catch (e) {
|
|
print('Error fetching department list: $e');
|
|
}
|
|
}
|
|
|
|
Future<void> fetchUsers() async {
|
|
try {
|
|
List<dynamic> users = await apiService.fetchUsers(context);
|
|
setState(() {
|
|
// apiUserData = users;
|
|
|
|
apiUserData = users.where((user) => user["role_id"] == "4").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<void> fetchRoles() async {
|
|
try {
|
|
final response = await apiService.fetchMasterDropdown(context);
|
|
|
|
if (response is Map<String, dynamic> && response.containsKey("role")) {
|
|
List<dynamic> roleList = response["role"]; // Extract the list
|
|
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}");
|
|
}
|
|
}
|
|
|
|
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> 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<String?> getToken() async {
|
|
final prefs = await SharedPreferences.getInstance();
|
|
return prefs.getString('auth_token');
|
|
}
|
|
|
|
// Future<String?> getUserId() async {
|
|
// final prefs = await SharedPreferences.getInstance();
|
|
// return prefs.getString('userId');
|
|
// }
|
|
|
|
Future<String?> 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() async {
|
|
print("USR Detail Submit");
|
|
printFormData();
|
|
|
|
Map<String, dynamic> 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");
|
|
orgId = await getOrgId();
|
|
|
|
createUserData(userDetials);
|
|
}
|
|
}
|
|
|
|
bool isValidData(Map<String, dynamic> data) {
|
|
errorMessages.clear(); // Reset errors
|
|
|
|
// Required fields that must not be empty
|
|
List<String> 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;
|
|
|
|
// Ensure the file is a PDF
|
|
if (!file.type.contains("pdf")) {
|
|
print("Error: Not a PDF file");
|
|
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;
|
|
}
|
|
|
|
setState(() {
|
|
selectedFileNames = file.name;
|
|
passportFile = file;
|
|
passportFileUrlFromApi = null;
|
|
});
|
|
|
|
print('PDF File selected: ${file.name}');
|
|
});
|
|
}
|
|
|
|
Future<void> createUserData(Map<String, dynamic> userData) async {
|
|
final bool isUpdating =
|
|
apiselectedUser != null && apiselectedUser!.isNotEmpty;
|
|
final uri = Uri.parse(
|
|
isUpdating
|
|
? '$apiUrl/api/users/update/${apiselectedUser?["user_id"]}'
|
|
: '$apiUrl/api/users/create',
|
|
);
|
|
|
|
if (token == null) {
|
|
throw Exception('Token not found. Please log in.');
|
|
}
|
|
|
|
// Use MultipartRequest (POST only)
|
|
final request = http.MultipartRequest('POST', uri);
|
|
request.headers['Authorization'] = 'Bearer $token';
|
|
|
|
// If updating, spoof the method Laravel-style
|
|
if (isUpdating) {
|
|
request.fields['_method'] = 'PUT';
|
|
request.fields['user_id'] = apiselectedUser!["user_id"].toString();
|
|
}
|
|
|
|
// Add all non-null and non-empty user data fields
|
|
userData.forEach((key, value) {
|
|
if (value != null && value.toString().trim().isNotEmpty) {
|
|
request.fields[key] = value.toString();
|
|
}
|
|
});
|
|
|
|
// Attach file if selected
|
|
if (passportFile != null) {
|
|
try {
|
|
final reader = html.FileReader();
|
|
reader.readAsArrayBuffer(passportFile!);
|
|
await reader.onLoad.first;
|
|
|
|
final data = reader.result as Uint8List;
|
|
|
|
final multipartFile = http.MultipartFile.fromBytes(
|
|
'passport_document',
|
|
data,
|
|
filename: passportFile!.name,
|
|
);
|
|
|
|
request.files.add(multipartFile);
|
|
print("📎 File attached: ${passportFile!.name}");
|
|
} catch (e) {
|
|
print("❌ Failed to read file: $e");
|
|
}
|
|
} else {
|
|
print("⚠️ No passport file selected.");
|
|
}
|
|
|
|
print("🚀 Sending request with fields: ${request.fields}");
|
|
|
|
try {
|
|
final streamedResponse = await request.send();
|
|
final response = await http.Response.fromStream(streamedResponse);
|
|
print("Response status: ${response.statusCode}");
|
|
print("Response body: ${response.body}");
|
|
|
|
if (response.statusCode == 200 || response.statusCode == 201) {
|
|
print("✅ User submitted successfully!");
|
|
print("📨 Response: ${response.body}");
|
|
context.go('/listUser');
|
|
} else {
|
|
print("❌ Submission failed. Status: ${response.statusCode}");
|
|
print("📨 Body: ${response.body}");
|
|
}
|
|
} catch (e) {
|
|
print("🔥 Error submitting user: $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(
|
|
backgroundColor: Color(0xFFf5f5f5),
|
|
// appBar: isDesktop ? null : const CustomAppBar(title: 'Create User '),
|
|
// drawer: isDesktop ? null : CustomDrawer(isDesktop: false),
|
|
appBar: CustomAppBar(isDesktop: isDesktop),
|
|
drawer: CustomDrawer(isDesktop: false),
|
|
body: Padding(
|
|
padding:
|
|
isDesktop
|
|
? EdgeInsets.symmetric(
|
|
horizontal:
|
|
MediaQuery.of(context).size.width *
|
|
0.1, // 30% of screen width as horizontal padding
|
|
vertical:
|
|
MediaQuery.of(context).size.height *
|
|
0, // 5% of screen height as vertical padding
|
|
)
|
|
: EdgeInsets.all(0),
|
|
child: Row(
|
|
children: [
|
|
// if (isDesktop) CustomDrawer(isDesktop: true),
|
|
// const Expanded(child: Center(child: Text("User Details Content"))),
|
|
// Expanded(
|
|
// child: _buildUserDetails(isDesktop),
|
|
// ),
|
|
Expanded(child: buildData(isDesktop, context)),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
},
|
|
);
|
|
}
|
|
|
|
Widget buildData(bool isDesktop, context) {
|
|
return Container(
|
|
margin: isDesktop ? const EdgeInsets.only(top: 10.0, bottom: 10.0) : null,
|
|
padding: const EdgeInsets.only(left: 10, right: 10, top: 8),
|
|
decoration: BoxDecoration(
|
|
color: isDesktop ? Colors.white : Color(0xFFFCFCFC),
|
|
),
|
|
// margin: const EdgeInsets.only(left: 10.0, right: 15.0, top: 10.0, bottom: 10.0),
|
|
// decoration: BoxDecoration(
|
|
// // color: Colors.amber,
|
|
// // color: bodyColor,
|
|
// color: Color(0xFFE1F5FE),
|
|
// border: Border.all(
|
|
// color: Colors.white,
|
|
// // color: Color(0xFFF7F7FB),
|
|
// width: 3.5)),
|
|
child: Column(
|
|
children: [
|
|
Expanded(
|
|
child: Container(
|
|
// margin: isDesktop
|
|
// ? EdgeInsets.all(10.0)
|
|
// : EdgeInsets.only(
|
|
// left: 20.0, bottom: 20.0, top: 10.0, right: 20.0),
|
|
// padding: const EdgeInsets.all(10),
|
|
height:
|
|
isDesktop
|
|
? MediaQuery.of(context).size.height * 0.98
|
|
: MediaQuery.of(context).size.height,
|
|
// decoration: BoxDecoration(
|
|
// border: isDesktop
|
|
// ? Border.all(
|
|
// width: 2,
|
|
// color: Colors.white,
|
|
// // color: Color(0xFFF7F7FB),
|
|
// )
|
|
// : null,
|
|
// color: Colors.white,
|
|
// // color: Color(0xFFF7F7FB),
|
|
//
|
|
// // color: Colors.amber,
|
|
// ),
|
|
// color: bodyColor,
|
|
child: SingleChildScrollView(
|
|
child: Padding(
|
|
padding: EdgeInsets.all(0.0),
|
|
child: _buildUserDetails(isDesktop),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
Container(
|
|
color: Colors.white,
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(8.0),
|
|
child:
|
|
isDesktop
|
|
? Row(
|
|
mainAxisAlignment: MainAxisAlignment.end,
|
|
children: _buildSubmit(isDesktop, layoutColor!),
|
|
)
|
|
: Row(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: _buildSubmit(isDesktop, layoutColor!),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
// 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
|
|
// ),
|
|
// ),
|
|
// ],
|
|
// ),
|
|
// );
|
|
// }
|
|
|
|
Widget _buildUserDetails(bool isDesktop) {
|
|
return Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
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,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
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: 16,
|
|
),
|
|
SizedBox(width: 10),
|
|
Text(
|
|
"Personal Details",
|
|
style: TextStyle(
|
|
fontSize: 14,
|
|
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,
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
_buildFirstRowLeftColumn(isDesktop),
|
|
SizedBox(width: 20),
|
|
_buildFirstRowRightColumn(isDesktop),
|
|
],
|
|
)
|
|
: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
_buildFirstRowLeftColumn(isDesktop),
|
|
SizedBox(height: 1),
|
|
_buildFirstRowRightColumn(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_2_outlined,
|
|
color: Color(0xFF8B8FB2),
|
|
size: 16,
|
|
),
|
|
SizedBox(width: 10),
|
|
Text(
|
|
"Passport Details",
|
|
style: TextStyle(
|
|
fontSize: 14,
|
|
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: [
|
|
_buildSecondRowLeftColumn(isDesktop),
|
|
SizedBox(width: 20),
|
|
_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: 16,
|
|
),
|
|
SizedBox(width: 10),
|
|
Text(
|
|
"Employee Organization Details",
|
|
style: TextStyle(
|
|
fontSize: 14,
|
|
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: [
|
|
_buildThirdRowLeftColumn(isDesktop),
|
|
SizedBox(width: 20),
|
|
_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: 16,
|
|
),
|
|
SizedBox(width: 10),
|
|
Text(
|
|
"Approver",
|
|
style: TextStyle(
|
|
fontSize: 14,
|
|
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: [
|
|
_buildForthRowLeftColumn(isDesktop),
|
|
SizedBox(width: 20),
|
|
_buildForthRowRightColumn(isDesktop),
|
|
SizedBox(width: 20),
|
|
_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) {
|
|
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: 12,
|
|
fontWeight: FontWeight.w200,
|
|
color: Colors.black,
|
|
),
|
|
),
|
|
SizedBox(height: 5),
|
|
CustomTextFieldUserWrapper(
|
|
isFocused: false,
|
|
isDesktop: isDesktop,
|
|
child: SizedBox(
|
|
height: 40,
|
|
child: TextField(
|
|
style: TextStyle(fontSize: 12, color: Colors.black),
|
|
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(
|
|
"Gender",
|
|
style: TextStyle(
|
|
fontSize: 12,
|
|
fontWeight: FontWeight.w200,
|
|
color: Colors.black,
|
|
),
|
|
),
|
|
SizedBox(height: 5),
|
|
CustomTextFieldUserWrapper(
|
|
isFocused: false,
|
|
isDesktop: isDesktop,
|
|
child: SizedBox(
|
|
height: 40,
|
|
child: DropdownButtonFormField<String>(
|
|
// value: selectedGender,
|
|
value: isViewMode ? null : 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, color: Colors.black),
|
|
items: [
|
|
DropdownMenuItem(value: "Male", child: Text("Male")),
|
|
DropdownMenuItem(
|
|
value: "Female",
|
|
child: Text("Female"),
|
|
),
|
|
],
|
|
hint: Text(
|
|
selectedGender ?? "Select Gender",
|
|
style: TextStyle(fontSize: 12, color: Colors.black),
|
|
),
|
|
disabledHint: Text(
|
|
selectedGender ?? "Select Gender",
|
|
style: TextStyle(fontSize: 12, color: Colors.black),
|
|
),
|
|
),
|
|
|
|
// child: DropdownSearch<String>(
|
|
// 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(
|
|
"Mobile Number",
|
|
style: TextStyle(
|
|
fontSize: 12,
|
|
fontWeight: FontWeight.w200,
|
|
color: Colors.black,
|
|
),
|
|
),
|
|
SizedBox(height: 5),
|
|
CustomTextFieldUserWrapper(
|
|
isFocused: false,
|
|
isDesktop: isDesktop,
|
|
child: SizedBox(
|
|
height: 40,
|
|
child: TextField(
|
|
style: TextStyle(fontSize: 12, color: Colors.black),
|
|
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(
|
|
"Email",
|
|
style: TextStyle(
|
|
fontSize: 12,
|
|
fontWeight: FontWeight.w200,
|
|
color: Colors.black,
|
|
),
|
|
),
|
|
SizedBox(height: 5),
|
|
CustomTextFieldUserWrapper(
|
|
isFocused: false,
|
|
isDesktop: isDesktop,
|
|
child: SizedBox(
|
|
height: 40,
|
|
child: TextField(
|
|
style: TextStyle(fontSize: 12, color: Colors.black),
|
|
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),
|
|
Row(
|
|
children: [
|
|
Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
"Address",
|
|
style: TextStyle(
|
|
fontSize: 12,
|
|
fontWeight: FontWeight.w200,
|
|
color: Colors.black,
|
|
),
|
|
),
|
|
SizedBox(height: 5),
|
|
CustomTextFieldUserWrapper(
|
|
isFocused: false,
|
|
isDesktop: isDesktop,
|
|
child: SizedBox(
|
|
height: 110,
|
|
child: TextField(
|
|
style: TextStyle(fontSize: 12, color: Colors.black),
|
|
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),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
// ),
|
|
);
|
|
}
|
|
|
|
Widget _buildFirstRowRightColumn(bool isDesktop) {
|
|
DateTime? _selectedDateOfBirth;
|
|
|
|
Future<void> _selectCheckDateOfBirth(BuildContext context) async {
|
|
DateTime now = DateTime.now();
|
|
DateTime today = DateTime(now.year, now.month, now.day);
|
|
|
|
DateTime? pickedDate = await showDatePicker(
|
|
context: context,
|
|
initialDate:
|
|
_selectedDateOfBirth != null && _selectedDateOfBirth!.isAfter(today)
|
|
? _selectedDateOfBirth!
|
|
: today,
|
|
firstDate: DateTime(1900),
|
|
lastDate: DateTime(2100),
|
|
);
|
|
|
|
if (pickedDate != null && pickedDate != _selectedDateOfBirth) {
|
|
setState(() {
|
|
_selectedDateOfBirth = pickedDate;
|
|
controllers["dob"]?.text = DateFormat(
|
|
'yyyy-MM-dd',
|
|
).format(pickedDate);
|
|
});
|
|
}
|
|
}
|
|
|
|
// -----------------
|
|
|
|
late Map<String, String> countryMap; // Mapping country_code -> country_name
|
|
late List<String> countryCodes; // List of country codes
|
|
List<dynamic> countryList = apiCountryData ?? [];
|
|
|
|
countryList = apiCountryData ?? [];
|
|
|
|
// Map country codes to country names
|
|
countryMap = {
|
|
for (var item in countryList)
|
|
item['country_code'] as String: item['country_name'] as String,
|
|
};
|
|
|
|
// 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(
|
|
"Last Name",
|
|
style: TextStyle(
|
|
fontSize: 12,
|
|
fontWeight: FontWeight.w200,
|
|
color: Colors.black,
|
|
),
|
|
),
|
|
SizedBox(height: 5),
|
|
CustomTextFieldUserWrapper(
|
|
isFocused: false,
|
|
isDesktop: isDesktop,
|
|
child: SizedBox(
|
|
height: 40,
|
|
child: TextField(
|
|
style: TextStyle(fontSize: 12, color: Colors.black),
|
|
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(
|
|
"Date of Birth",
|
|
style: TextStyle(
|
|
fontSize: 12,
|
|
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,
|
|
color: Colors.black,
|
|
),
|
|
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(
|
|
"Alternate Mobile Number",
|
|
style: TextStyle(
|
|
fontSize: 12,
|
|
fontWeight: FontWeight.w200,
|
|
color: Colors.black,
|
|
),
|
|
),
|
|
SizedBox(height: 5),
|
|
CustomTextFieldUserWrapper(
|
|
isFocused: false,
|
|
isDesktop: isDesktop,
|
|
child: SizedBox(
|
|
height: 40,
|
|
child: TextField(
|
|
style: TextStyle(fontSize: 12, color: Colors.black),
|
|
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),
|
|
apiselectedUser != null
|
|
? SizedBox()
|
|
: Row(
|
|
children: [
|
|
Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
"Password",
|
|
style: TextStyle(
|
|
fontSize: 12,
|
|
fontWeight: FontWeight.w200,
|
|
color: Colors.black,
|
|
),
|
|
),
|
|
SizedBox(height: 5),
|
|
CustomTextFieldUserWrapper(
|
|
isFocused: false,
|
|
isDesktop: isDesktop,
|
|
child: SizedBox(
|
|
height: 40,
|
|
child: TextField(
|
|
style: TextStyle(fontSize: 12, color: Colors.black),
|
|
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),
|
|
SizedBox(height: 3),
|
|
Row(
|
|
children: [
|
|
Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
"Country",
|
|
style: TextStyle(
|
|
fontSize: 12,
|
|
fontWeight: FontWeight.w200,
|
|
color: Colors.black,
|
|
),
|
|
),
|
|
SizedBox(height: 5),
|
|
CustomTextFieldUserWrapper(
|
|
isFocused: false,
|
|
isDesktop: isDesktop,
|
|
child: SizedBox(
|
|
height: 40,
|
|
child: DropdownSearch<String>(
|
|
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,
|
|
color: Colors.black,
|
|
),
|
|
),
|
|
),
|
|
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");
|
|
// }
|
|
});
|
|
},
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
SizedBox(height: 3),
|
|
Row(
|
|
children: [
|
|
Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
"Postal Code",
|
|
style: TextStyle(
|
|
fontSize: 12,
|
|
fontWeight: FontWeight.w200,
|
|
color: Colors.black,
|
|
),
|
|
),
|
|
SizedBox(height: 5),
|
|
CustomTextFieldUserWrapper(
|
|
isFocused: false,
|
|
isDesktop: isDesktop,
|
|
child: SizedBox(
|
|
height: 40,
|
|
child: TextField(
|
|
style: TextStyle(fontSize: 12, color: Colors.black),
|
|
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),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
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: 12,
|
|
fontWeight: FontWeight.w200,
|
|
color: Colors.black,
|
|
),
|
|
),
|
|
SizedBox(height: 5),
|
|
CustomTextFieldUserWrapper(
|
|
isFocused: false,
|
|
isDesktop: isDesktop,
|
|
child: SizedBox(
|
|
height: 40,
|
|
child: TextField(
|
|
style: TextStyle(fontSize: 12, color: Colors.black),
|
|
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: 12,
|
|
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 (passportFile != null || passportFileUrlFromApi != 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('DOWNLOAD - $passportFile');
|
|
|
|
if (passportFile != null) {
|
|
try {
|
|
// ✅ Step 1: Create a Blob directly from the file
|
|
final blob = html.Blob([
|
|
passportFile!,
|
|
], 'application/pdf');
|
|
|
|
// ✅ Step 2: Generate a download URL from the Blob
|
|
final url = html.Url.createObjectUrlFromBlob(
|
|
blob,
|
|
);
|
|
|
|
// ✅ Step 3: Create an invisible anchor to trigger download
|
|
final anchor =
|
|
html.AnchorElement(href: url)
|
|
..setAttribute(
|
|
"download",
|
|
selectedFileNames ?? "document.pdf",
|
|
)
|
|
..style.display = "none";
|
|
|
|
// ✅ Step 4: Add anchor to DOM and click it
|
|
html.document.body!.append(anchor);
|
|
anchor.click();
|
|
|
|
// ✅ Step 5: Clean up
|
|
anchor.remove();
|
|
html.Url.revokeObjectUrl(url);
|
|
|
|
print("Download triggered successfully!");
|
|
} catch (e) {
|
|
print("Error during download: $e");
|
|
}
|
|
} else if (passportFileUrlFromApi != null) {
|
|
// Trigger file download from the server path
|
|
final anchor =
|
|
html.AnchorElement(
|
|
href: passportFileUrlFromApi!,
|
|
)
|
|
..target = 'blank'
|
|
..download =
|
|
selectedFileNames ?? "document.pdf"
|
|
..click();
|
|
} else {
|
|
print("No file available 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<void> _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<void> _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: 12,
|
|
fontWeight: FontWeight.w200,
|
|
color: Colors.black,
|
|
),
|
|
),
|
|
SizedBox(height: 5),
|
|
CustomTextFieldUserWrapper(
|
|
isFocused: false,
|
|
isDesktop: isDesktop,
|
|
child: SizedBox(
|
|
height: 40,
|
|
child: TextField(
|
|
style: TextStyle(fontSize: 12, color: Colors.black),
|
|
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: 12,
|
|
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,
|
|
color: Colors.black,
|
|
),
|
|
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: 12,
|
|
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,
|
|
color: Colors.black,
|
|
),
|
|
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: 12,
|
|
fontWeight: FontWeight.w200,
|
|
color: Colors.black,
|
|
),
|
|
),
|
|
SizedBox(height: 5),
|
|
CustomTextFieldUserWrapper(
|
|
isFocused: false,
|
|
isDesktop: isDesktop,
|
|
child: SizedBox(
|
|
height: 40,
|
|
child: TextField(
|
|
style: TextStyle(fontSize: 12, color: Colors.black),
|
|
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: 12,
|
|
// fontWeight: FontWeight.w200,
|
|
// color: Colors.black)),
|
|
// SizedBox(height: 5),
|
|
// CustomTextFieldUserWrapper(
|
|
// isFocused: false,
|
|
// isDesktop: isDesktop,
|
|
// child: SizedBox(
|
|
// height: 40,
|
|
// child: DropdownSearch<String>(
|
|
// 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");
|
|
// // }
|
|
// });
|
|
// },
|
|
// ),
|
|
// ),
|
|
// ),
|
|
// ],
|
|
// ),
|
|
// ],
|
|
// ),
|
|
Row(
|
|
children: [
|
|
Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
"Role ",
|
|
style: TextStyle(
|
|
fontSize: 12,
|
|
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<String>(
|
|
value: isViewMode ? null : selectedRole,
|
|
style: TextStyle(fontSize: 12, color: Colors.black),
|
|
decoration: InputDecoration(
|
|
border: InputBorder.none,
|
|
contentPadding: EdgeInsets.symmetric(
|
|
horizontal: 10,
|
|
), // Proper padding
|
|
),
|
|
onChanged:
|
|
isViewMode
|
|
? null
|
|
: (newValue) {
|
|
setState(() {
|
|
selectedRole = newValue;
|
|
});
|
|
},
|
|
items:
|
|
apiRoleData?.map<DropdownMenuItem<String>>((item) {
|
|
return DropdownMenuItem(
|
|
value: item['dropdown_key'], // ID as value
|
|
child: Text(item['dropdown_value'] ?? "Select"),
|
|
);
|
|
}).toList(),
|
|
hint: Text("Select"),
|
|
disabledHint: Text(
|
|
selectedRole ?? "Select Role",
|
|
style: TextStyle(fontSize: 12, color: Colors.black),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
SizedBox(height: 3),
|
|
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(
|
|
"Group",
|
|
style: TextStyle(
|
|
fontSize: 12,
|
|
fontWeight: FontWeight.w200,
|
|
color: Colors.black,
|
|
),
|
|
),
|
|
SizedBox(height: 5),
|
|
CustomTextFieldUserWrapper(
|
|
isFocused: false,
|
|
isDesktop: isDesktop,
|
|
child: SizedBox(
|
|
height: 40,
|
|
child: DropdownSearch<String>(
|
|
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,
|
|
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");
|
|
// }
|
|
});
|
|
},
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
SizedBox(height: 3),
|
|
Row(
|
|
children: [
|
|
Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
"Department",
|
|
style: TextStyle(
|
|
fontSize: 12,
|
|
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<String>(
|
|
value: isViewMode ? null : selectedDepartment,
|
|
style: TextStyle(fontSize: 12, color: Colors.black),
|
|
decoration: InputDecoration(
|
|
border: InputBorder.none,
|
|
contentPadding: EdgeInsets.symmetric(
|
|
horizontal: 10,
|
|
), // Proper padding
|
|
),
|
|
onChanged:
|
|
isViewMode
|
|
? null
|
|
: (newValue) {
|
|
setState(() {
|
|
selectedDepartment = 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: TextStyle(fontSize: 12, color: Colors.black),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
|
|
// ),
|
|
);
|
|
}
|
|
|
|
//----------------------------------- 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: 12,
|
|
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<String>(
|
|
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: 12,
|
|
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<String>(
|
|
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: 12,
|
|
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<String>(
|
|
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<Widget> _buildSubmit(isDesktop, Color layoutColor) {
|
|
return [
|
|
ElevatedButton(
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: Colors.white,
|
|
foregroundColor: layoutColor,
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(8),
|
|
side: BorderSide(color: layoutColor, width: 2),
|
|
),
|
|
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
|
|
),
|
|
onPressed: () {
|
|
isEditProfile ? context.go('/listPlan') : context.go('/listUser');
|
|
},
|
|
child: isViewMode ? Text("Back") : Text("Cancel"),
|
|
),
|
|
SizedBox(width: 20),
|
|
if (!isViewMode)
|
|
MouseRegion(
|
|
cursor:
|
|
isViewMode
|
|
? SystemMouseCursors.forbidden
|
|
: SystemMouseCursors.click,
|
|
child: ElevatedButton(
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor:
|
|
isViewMode ? layoutColor : layoutColor, // Keep original color
|
|
foregroundColor:
|
|
isViewMode
|
|
? Colors.white
|
|
: Colors.white, // Keep original color
|
|
disabledBackgroundColor:
|
|
layoutColor, // Ensure color remains when disabled
|
|
disabledForegroundColor: Colors.white,
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(8),
|
|
side: BorderSide(color: layoutColor, 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<void> fetchData() async {
|
|
// Implement data fetching logic
|
|
}
|
|
}
|