ts-tat/lib/Screens/userManagement/create_user/create_user1.dart
2025-10-08 11:37:26 +05:30

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
}
}