2005 lines
63 KiB
Dart
2005 lines
63 KiB
Dart
import 'dart:async';
|
|
import 'dart:convert';
|
|
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/rendering.dart';
|
|
import 'package:flutter/services.dart';
|
|
import 'package:frontend/Screens/userManagement/create_user/personal_details.dart';
|
|
import 'package:frontend/Screens/userManagement/create_user/traveller_details.dart';
|
|
import 'package:frontend/Screens/userManagement/create_user/office_details.dart';
|
|
import 'package:frontend/utils/auth_utils.dart';
|
|
import 'package:go_router/go_router.dart';
|
|
import 'package:google_fonts/google_fonts.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_breadcrumb_navigation.dart';
|
|
import '../../../widgets/custom_text_field.dart';
|
|
import '../../../widgets/custom_text_forex.dart';
|
|
import '../../../widgets/custom_user_form.dart';
|
|
import '../../../widgets/saving_loader.dart';
|
|
|
|
import 'package:universal_html/html.dart' as html;
|
|
|
|
class CreateUserFormDetials extends StatefulWidget {
|
|
@override
|
|
_CreateUserFormDetialsState createState() => _CreateUserFormDetialsState();
|
|
}
|
|
|
|
class _CreateUserFormDetialsState extends State<CreateUserFormDetials> {
|
|
final ApiService apiService = ApiService();
|
|
|
|
final GlobalKey<PersonalDetailsState> personalDetailsKey =
|
|
GlobalKey<PersonalDetailsState>();
|
|
final GlobalKey<TravellerDetailsState> travellerDetailsKey =
|
|
GlobalKey<TravellerDetailsState>();
|
|
final GlobalKey<OfficeDetailsState> officeDetailsKey =
|
|
GlobalKey<OfficeDetailsState>();
|
|
|
|
// late List<Map<String, dynamic>?> travelDetailsData;
|
|
Map<String, dynamic>? travelDetailsData;
|
|
Map<String, dynamic>? travelDetailsDataFromAPI;
|
|
Map<String, String> errorMessagesTravel = {};
|
|
late TabController _tabController;
|
|
|
|
String? userId;
|
|
String? orgId;
|
|
String? roleId;
|
|
String? userIdApi;
|
|
|
|
bool isCheckingToken = false;
|
|
bool isDisable = false;
|
|
bool isValid = false;
|
|
|
|
final FocusNode submitButtonFocusNode = FocusNode();
|
|
|
|
String? token;
|
|
|
|
late bool isapiselectedUser = false;
|
|
|
|
// 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;
|
|
List<Map<String, dynamic>> selectedServiceIds = [];
|
|
|
|
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? selectedTab;
|
|
|
|
String? selectedCountry;
|
|
String? selectedGender;
|
|
|
|
// String? selectedGender = personalDetailsKey.currentState?.selectedGender;
|
|
|
|
String? selectedUserType;
|
|
String? selectedRole;
|
|
String? selectedLevel;
|
|
String? selectedDepartment;
|
|
String? selectedFirstApprover;
|
|
String? selectedSecondApprover;
|
|
String? selectedThirdApprover;
|
|
String? selectedSubstituteApprover;
|
|
String? selectedExceptionalApprover;
|
|
|
|
String? selectedFileNames;
|
|
Uint8List? passportDocumentBytes;
|
|
String? passportFileUrlFromApi;
|
|
String? base64PDF;
|
|
|
|
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",
|
|
"delegationStartDate",
|
|
"delegationEndDate",
|
|
// "dateOfIssue",
|
|
// "dateOfExpiry",
|
|
"changePassword",
|
|
];
|
|
|
|
Color? layoutColor;
|
|
Color? bodyColor;
|
|
|
|
bool setSelectesUserType = false;
|
|
|
|
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,
|
|
|
|
// "gender": personalDetailsKey.currentState?.selectedGender,
|
|
// "agent_supported_service_ids": selectedServiceIds,
|
|
"agent_supported_service_ids": selectedServiceIds,
|
|
"postal_code": controllers["postalCode"]?.text,
|
|
"country_code": selectedCountry,
|
|
"employee_code": controllers["employeeCode"]?.text,
|
|
"delegation_start_date": controllers["delegationStartDate"]?.text,
|
|
"delegation_end_date": controllers["delegationEndDate"]?.text,
|
|
"user_type": selectedUserType,
|
|
"role_id": selectedRole,
|
|
"department_id": selectedDepartment,
|
|
|
|
"group_id": selectedLevel,
|
|
|
|
"first_approver": selectedFirstApprover,
|
|
"second_approver": selectedSecondApprover,
|
|
"third_approver": selectedThirdApprover,
|
|
"fourth_approver": selectedExceptionalApprover,
|
|
// "exceptional_approver": selectedExceptionalApprover,
|
|
"delegated_to_user_id": selectedSubstituteApprover,
|
|
// "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,
|
|
"travel_details": travelDetailsData ?? {},
|
|
// "passport_fileData": passportFile,
|
|
};
|
|
return data;
|
|
}
|
|
|
|
void handleUserTypeChange(bool isTravelAgent) {
|
|
print("handleUserTypeChange- $isTravelAgent");
|
|
|
|
setState(() {
|
|
setSelectesUserType = isTravelAgent;
|
|
});
|
|
}
|
|
|
|
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
|
|
|
|
if (apiselectedUser != null && apiselectedUser!.isNotEmpty) {
|
|
isapiselectedUser = true;
|
|
|
|
print("API Selected User Has Data - $apiselectedUser");
|
|
}
|
|
userIdApi = apiselectedUser?["user_id"] ?? "";
|
|
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["delegationStartDate"]?.text =
|
|
apiselectedUser?["delegation_start_date"] ?? "";
|
|
|
|
controllers["delegationEndDate"]?.text =
|
|
apiselectedUser?["delegation_end_date"] ?? "";
|
|
|
|
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() ?? "";
|
|
}
|
|
if (apiselectedUser?["group_id"] != null) {
|
|
selectedLevel = apiselectedUser?["group_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() ?? "";
|
|
}
|
|
|
|
// if (apiselectedUser?["exceptional_approver"] != null) {
|
|
// selectedExceptionalApprover =
|
|
// apiselectedUser?["exceptional_approver"]?.toString() ?? "";
|
|
// }
|
|
|
|
if (apiselectedUser?["fourth_approver"] != null) {
|
|
selectedExceptionalApprover =
|
|
apiselectedUser?["fourth_approver"]?.toString() ?? "";
|
|
}
|
|
|
|
if (apiselectedUser?["delegated_to_user_id"] != null) {
|
|
print(
|
|
"UPDADele- ${apiselectedUser?["delegated_to_user_id"]?.toString()}",
|
|
);
|
|
selectedSubstituteApprover =
|
|
apiselectedUser?["delegated_to_user_id"]?.toString() ?? "";
|
|
|
|
print("UPDADele1- $selectedSubstituteApprover");
|
|
}
|
|
try {
|
|
final raw = apiselectedUser!["agent_supported_service_ids"];
|
|
|
|
// Fix the invalid JSON (dangerous if the format changes)
|
|
final fixedJson = raw.replaceAllMapped(
|
|
RegExp(r'(\w+):'), // matches `service_id:`
|
|
(match) => '"${match.group(1)}":',
|
|
);
|
|
|
|
List<dynamic> decodedList = jsonDecode(fixedJson);
|
|
|
|
selectedServiceIds =
|
|
decodedList.map<Map<String, dynamic>>((item) {
|
|
final map = Map<String, dynamic>.from(item);
|
|
return {"service_id": map['service_id'].toString()};
|
|
}).toList();
|
|
} catch (e) {
|
|
print("❌ Error decoding fixed agent_supported_service_ids: $e");
|
|
selectedServiceIds = [];
|
|
}
|
|
|
|
print("Updated selectedGender: $selectedGender"); // Debugging
|
|
|
|
travelDetailsDataFromAPI = apiselectedUser?["travel_details"];
|
|
|
|
print("travelDetailsDataFromAPI00 - $travelDetailsDataFromAPI");
|
|
// if (travelDetails != null) {
|
|
// WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
// // Safe to call updateTravel now
|
|
// travellerDetailsKey.currentState?.updateTravel();
|
|
// });
|
|
// }
|
|
|
|
// Optionally load other travel-related fields like meal/seat preferences, etc.
|
|
});
|
|
} else {
|
|
print("API Selected User Has Data - No data available yet");
|
|
}
|
|
}
|
|
|
|
@override
|
|
// void initState() {
|
|
// super.initState();
|
|
// prepareForNewEntry();
|
|
// selectedTab = "personal";
|
|
//
|
|
// // WidgetsFlutterBinding.ensureInitialized();
|
|
//
|
|
// // Step 1: Set 'reloaded' flag before page unload
|
|
// // html.window.onBeforeUnload.listen((event) {
|
|
// // html.window.localStorage['reloaded'] = 'true';
|
|
// // });
|
|
//
|
|
// 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();
|
|
// }
|
|
// }
|
|
void initState() {
|
|
super.initState();
|
|
setState(() {
|
|
isCheckingToken = true;
|
|
});
|
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
_checkAuthAndLoadData(); // Safe place to use context
|
|
});
|
|
}
|
|
|
|
void _checkAuthAndLoadData() async {
|
|
setState(() {
|
|
isCheckingToken = true;
|
|
});
|
|
|
|
final String? token = await getToken();
|
|
if (!mounted) return;
|
|
|
|
if (token == null || token.isEmpty) {
|
|
context.go("/");
|
|
return;
|
|
}
|
|
|
|
setState(() {
|
|
isCheckingToken = false;
|
|
});
|
|
|
|
prepareForNewEntry();
|
|
selectedTab = "personal";
|
|
|
|
apiCountryData = null;
|
|
apiUserData = null;
|
|
apiCostData = null;
|
|
apiRoleData = null;
|
|
|
|
for (var field in dataHeader) {
|
|
controllers[field] = TextEditingController();
|
|
}
|
|
|
|
/// ✅ Now this is safe here
|
|
try {
|
|
final extraData = GoRouterState.of(context).extra;
|
|
if (extraData != null && extraData is Map<String, dynamic>) {
|
|
setState(() {
|
|
apiselectedUser = extraData['selectedUser'] as Map<String, dynamic>?;
|
|
isViewMode = extraData['isViewMode'] ?? false;
|
|
isEditProfile = extraData['isEditProfile'] ?? false;
|
|
});
|
|
updateData();
|
|
}
|
|
} catch (e) {
|
|
print("Router extra error: $e");
|
|
}
|
|
|
|
// Final async data fetches
|
|
initializeData();
|
|
fetchCountries();
|
|
fetchDepartment();
|
|
fetchUsers();
|
|
fetchRoles();
|
|
loadInitialData();
|
|
}
|
|
|
|
void _checkAuthAndLoadData22() async {
|
|
final String? token = await getToken(); // Your async function to get token
|
|
|
|
if (!mounted) return;
|
|
|
|
if (token == null || token.isEmpty) {
|
|
// Token doesn't exist → redirect to login
|
|
context.go(
|
|
"/",
|
|
); // or use: router.go("/") if you're using `GoRouter` directly
|
|
setState(() {
|
|
isCheckingToken = false;
|
|
});
|
|
|
|
return;
|
|
} else {
|
|
if (!mounted) return;
|
|
|
|
// Future.microtask(() {
|
|
prepareForNewEntry();
|
|
selectedTab = "personal";
|
|
|
|
// WidgetsFlutterBinding.ensureInitialized();
|
|
|
|
// Step 1: Set 'reloaded' flag before page unload
|
|
// html.window.onBeforeUnload.listen((event) {
|
|
// html.window.localStorage['reloaded'] = 'true';
|
|
// });
|
|
|
|
apiCountryData = null;
|
|
apiUserData = null;
|
|
// apiselectedUser = null;
|
|
apiCostData = null;
|
|
apiRoleData = null;
|
|
|
|
// Initialize controllers for each field
|
|
|
|
// WidgetsBinding.instance.addPostFrameCallback((_) async {
|
|
// if (!mounted) return;
|
|
// 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();
|
|
}
|
|
|
|
setState(() {
|
|
isCheckingToken = false;
|
|
});
|
|
|
|
// });
|
|
}
|
|
}
|
|
|
|
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();
|
|
roleId = await getRoleId();
|
|
|
|
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();
|
|
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;
|
|
}
|
|
|
|
Future<String?> getRoleId() 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['role_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 prepareForNewEntry() {
|
|
for (var controller in controllers.values) {
|
|
controller.clear();
|
|
}
|
|
}
|
|
|
|
void handleGoBack() async {
|
|
print("hello, please Go Back");
|
|
printFormData();
|
|
}
|
|
|
|
void handleBack() async {
|
|
print("USR Detail back");
|
|
|
|
final tabs = {
|
|
"personal": "Personal Details",
|
|
"office": "Office Details",
|
|
"travel": "Travel Details",
|
|
};
|
|
|
|
final tabKeys = tabs.keys.toList(); // ["personal", "office", "travel"]
|
|
|
|
final currentIndex = tabKeys.indexOf(selectedTab ?? "travel");
|
|
|
|
print("currentIndex - $currentIndex");
|
|
|
|
if (currentIndex > 0) {
|
|
// Move to next tab
|
|
setState(() {
|
|
selectedTab = tabKeys[currentIndex - 1];
|
|
});
|
|
} else {
|
|
// Final step — submit or show done
|
|
print("All tabs completed!");
|
|
// Submit the full form here
|
|
}
|
|
}
|
|
|
|
void handleNext() async {
|
|
print("USR Detail Next");
|
|
printFormData();
|
|
|
|
// travelDetailsData = [travellerDetailsKey.currentState?.travel_Detials];
|
|
|
|
travelDetailsData = travellerDetailsKey.currentState?.travel_Detials;
|
|
// errorMessagesTravel = travellerDetailsKey.currentState!.errorMessages;
|
|
|
|
print("TRAVEL DETAILS FROM CHILD: $travelDetailsData");
|
|
|
|
Map<String, dynamic> data = userDetials;
|
|
|
|
print("USERDETAILS : $data");
|
|
|
|
final tabs = {
|
|
"personal": "Personal Details",
|
|
"office": "Office Details",
|
|
"travel": "Travel Details",
|
|
};
|
|
|
|
final tabKeys = tabs.keys.toList(); // ["personal", "office", "travel"]
|
|
|
|
final currentIndex = tabKeys.indexOf(selectedTab ?? "personal");
|
|
|
|
print("currentIndex - $currentIndex");
|
|
|
|
bool isValid = false;
|
|
|
|
final currentTab = tabKeys[currentIndex];
|
|
if (currentTab == "personal") {
|
|
// isValid = isValidData(userDetials);
|
|
isValid = await isValidData(userDetials);
|
|
} else if (currentTab == "office") {
|
|
isValid = await isValidDataTwo(userDetials);
|
|
} else {
|
|
isValid = true; // Travel tab might not need validation at this point
|
|
}
|
|
|
|
if (!isValid) {
|
|
print("Validation Failed on $currentTab: $userDetials");
|
|
setState(() {}); // To trigger UI update showing errors
|
|
return;
|
|
}
|
|
|
|
if (currentIndex < tabKeys.length - 1) {
|
|
// Move to next tab
|
|
setState(() {
|
|
selectedTab = tabKeys[currentIndex + 1];
|
|
});
|
|
} else {
|
|
// Final step — submit or show done
|
|
print("All tabs completed!");
|
|
// Submit the full form here
|
|
}
|
|
}
|
|
|
|
void closeLoaderIfOpen() {
|
|
if (mounted) {
|
|
Navigator.of(context, rootNavigator: true).pop();
|
|
}
|
|
}
|
|
|
|
void handleSubmit() async {
|
|
print("USR Detail Submit");
|
|
// printFormData();
|
|
print("travel validation");
|
|
bool isValid = travellerDetailsKey.currentState?.boolValidation() ?? false;
|
|
|
|
print("Start loader at: ${DateTime.now()}");
|
|
showDialog(
|
|
context: context,
|
|
barrierDismissible: false,
|
|
builder: (context) => const SavingLoader(),
|
|
);
|
|
if (selectedRole == "5" || setSelectesUserType == true) {
|
|
print("NO validation");
|
|
|
|
Map<String, dynamic> data = userDetials;
|
|
|
|
print("userDetials ====> $userDetials");
|
|
if (!await isValidData(data)) {
|
|
print("USERDETAILS : $userDetials");
|
|
print("Validation Failed: Required fields are missing.");
|
|
closeLoaderIfOpen();
|
|
setState(() {});
|
|
return; // Stop execution if validation fails
|
|
} else {
|
|
print("USERDETAILS : $userDetials");
|
|
orgId = await getOrgId();
|
|
|
|
createUserData(userDetials);
|
|
}
|
|
} else {
|
|
print("isValid- $isValid");
|
|
if (!isValid) {
|
|
print("Validation failed. Please check the inputs.");
|
|
closeLoaderIfOpen();
|
|
setState(() {});
|
|
return; // ❌ STOP execution here if not valid
|
|
}
|
|
|
|
travelDetailsData = travellerDetailsKey.currentState?.travel_Detials;
|
|
|
|
print("travelDetailsData - $travelDetailsData");
|
|
|
|
print("travelDetailsData - $travelDetailsData");
|
|
// errorMessagesTravel = travellerDetailsKey.currentState!.errorMessages;
|
|
|
|
print("TRAVEL DETAILS FROM CHILD");
|
|
// print("TRAVEL DETAILS FROM CHILD: $travelDetailsData");
|
|
|
|
passportFile = travellerDetailsKey.currentState?.passportFile;
|
|
|
|
print("passportFile : $passportFile");
|
|
|
|
Map<String, dynamic> data = userDetials;
|
|
|
|
print("USERDETAILS : $data");
|
|
|
|
// block-submit-here
|
|
|
|
final format = DateFormat("dd-MM-yyyy");
|
|
|
|
DateTime? start_Date;
|
|
DateTime? end_Date;
|
|
|
|
try {
|
|
if (travelDetailsData?['date_of_issue'] != null &&
|
|
travelDetailsData!['date_of_issue'].toString().isNotEmpty) {
|
|
start_Date = format.parse(travelDetailsData!['date_of_issue']);
|
|
}
|
|
if (travelDetailsData?['date_of_expiry'] != null &&
|
|
travelDetailsData!['date_of_expiry'].toString().isNotEmpty) {
|
|
end_Date = format.parse(travelDetailsData!['date_of_expiry']);
|
|
}
|
|
|
|
if (start_Date != null &&
|
|
end_Date != null &&
|
|
end_Date.isBefore(start_Date)) {
|
|
print("Invalid date range");
|
|
return;
|
|
}
|
|
} catch (e) {
|
|
print("Invalid passport date format");
|
|
return;
|
|
}
|
|
|
|
// Additional validation starts - travelDetailsData passport
|
|
// DateTime? start_Date = travelDetailsData?['date_of_issue'];
|
|
// DateTime? end_Date = travelDetailsData?['date_of_expiry'];
|
|
//
|
|
// if (start_Date != null &&
|
|
// end_Date != null &&
|
|
// start_Date.toString().isNotEmpty &&
|
|
// end_Date.toString().isNotEmpty) {
|
|
// try {
|
|
// final format = DateFormat("dd-MM-yyyy");
|
|
// final checkStartDate = format.parse("$start_Date");
|
|
// final checkEndDate = format.parse("$end_Date");
|
|
//
|
|
// if (checkEndDate.isBefore(checkStartDate)) {
|
|
// // return "End date cannot be earlier than start date";;
|
|
// return;
|
|
// }
|
|
// } catch (e) {
|
|
// // return "End date cannot be earlier than start date";
|
|
// return;
|
|
// // errorMessages["end_date"] = "Invalid date format";
|
|
// }
|
|
// }
|
|
|
|
// valid_from: 20-06-2025, valid_upto: 19-06-2025
|
|
DateTime? validFrom = data['valid_from'];
|
|
DateTime? validUpto = data['valid_upto'];
|
|
|
|
if (validFrom != null &&
|
|
validUpto != null &&
|
|
validFrom.toString().isNotEmpty &&
|
|
validUpto.toString().isNotEmpty) {
|
|
try {
|
|
final format = DateFormat("dd-MM-yyyy");
|
|
final checkValidFrom = format.parse("$validFrom");
|
|
final checkValidUpto = format.parse("$validUpto");
|
|
|
|
if (checkValidUpto.isBefore(checkValidFrom)) {
|
|
// return "valid upto cannot be earlier than valid from";
|
|
return;
|
|
}
|
|
} catch (e) {
|
|
// return "valid upto cannot be earlier than valid from";
|
|
return;
|
|
// errorMessages["end_date"] = "Invalid date format";
|
|
}
|
|
}
|
|
|
|
// Map<String, dynamic> data = userDetials;
|
|
|
|
if (!await isValidData(data) && !await isValidDataTwo(data)) {
|
|
print("USERDETAILS : $userDetials");
|
|
print("Validation Failed: Required fields are missing.");
|
|
closeLoaderIfOpen();
|
|
setState(() {});
|
|
return; // Stop execution if validation fails
|
|
} else {
|
|
print("USERDETAILS : $userDetials");
|
|
orgId = await getOrgId();
|
|
|
|
createUserData(userDetials);
|
|
}
|
|
}
|
|
|
|
if (mounted) Navigator.of(context, rootNavigator: true).pop();
|
|
}
|
|
|
|
// Future<bool> apiCheckDuplicate(
|
|
// String label,
|
|
// String field,
|
|
// String value,
|
|
// String? userId,
|
|
// ) async
|
|
// {
|
|
// try {
|
|
// // Basic validation: Check mobile number length
|
|
// if (field == "mobile_no" && value.length != 10) {
|
|
// return true; // Skip the API call if input is invalid
|
|
// }
|
|
//
|
|
// final response = await apiService.CheckDuplicate(
|
|
// label,
|
|
// field,
|
|
// value,
|
|
// userId,
|
|
// );
|
|
// if (response.isNotEmpty) {
|
|
// // _clearError(field);
|
|
// errorMessages[field] = response['message'] ?? "$label Already Exists";
|
|
// print("Duplicate found: ${response['message']}");
|
|
//
|
|
// setState(() {});
|
|
// return true;
|
|
// } else {
|
|
// _clearError(field);
|
|
// print("No duplicates found.");
|
|
//
|
|
// return false;
|
|
// }
|
|
// } catch (e) {
|
|
// print("Error in checkDuplicate: $e");
|
|
// return false;
|
|
// }
|
|
// }
|
|
|
|
Future<bool> apiCheckDuplicate(
|
|
String label,
|
|
String field,
|
|
String value,
|
|
String? userId,
|
|
) {
|
|
return apiService.CheckDuplicate(context, label, field, value, userId)
|
|
.then((response) {
|
|
if (response.isNotEmpty) {
|
|
errorMessages[field] =
|
|
response['message'] ?? "$label Already Exists";
|
|
print("Duplicate found: ${response['message']}");
|
|
// setState(() {});
|
|
print("Duplicate found: true");
|
|
return true;
|
|
} else {
|
|
_clearError(field);
|
|
print("No duplicates found.");
|
|
return false;
|
|
}
|
|
})
|
|
.catchError((e) {
|
|
print("Error in checkDuplicate: $e");
|
|
return false;
|
|
});
|
|
}
|
|
|
|
Future<bool> isValidData(Map<String, dynamic> data) async {
|
|
errorMessages.clear(); // Reset errors
|
|
|
|
// Required fields that must not be empty
|
|
List<String> requiredFields = [
|
|
"first_name",
|
|
"last_name",
|
|
"email",
|
|
"mobile_no",
|
|
"role_id",
|
|
// "employeeCode",
|
|
];
|
|
|
|
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
|
|
} else {
|
|
bool isDuplicate = await apiCheckDuplicate(
|
|
"Mobile Number",
|
|
"mobile_no",
|
|
data["mobile_no"]!,
|
|
userIdApi,
|
|
);
|
|
|
|
if (isDuplicate) {
|
|
print("REsduplica1 - $isDuplicate");
|
|
errorMessages["mobile_no"] = "Mobile Number already exist";
|
|
// errorMessages is already updated inside apiCheckDuplicate
|
|
// setState(() {});
|
|
}
|
|
}
|
|
|
|
// else {
|
|
// apiCheckDuplicate(
|
|
// "Mobile Number",
|
|
// "mobile_no",
|
|
// data["mobile_no"]!,
|
|
// userIdApi,
|
|
// ).then((isDuplicate) {
|
|
// print("REsduplica - $isDuplicate");
|
|
//
|
|
// if (isDuplicate) {
|
|
// print("REsduplica1 - $isDuplicate");
|
|
// errorMessages["mobile_no"] = "Mobile Number Already Exist";
|
|
// // errorMessages is already updated inside apiCheckDuplicate
|
|
// // setState(() {});
|
|
// } else {
|
|
// // Optional: do something if no duplicate found
|
|
// }
|
|
// });
|
|
//
|
|
// // errorMessages["mobile_no"] = "Mobile Number already exist";
|
|
// }
|
|
}
|
|
|
|
if (data["alternate_mobile_no"] != null &&
|
|
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
|
|
} else {
|
|
bool isDuplicate = await apiCheckDuplicate(
|
|
"Email",
|
|
"email",
|
|
data["email"]!,
|
|
userIdApi,
|
|
);
|
|
|
|
if (isDuplicate) {
|
|
errorMessages["email"] = "Email already exist";
|
|
}
|
|
}
|
|
|
|
// else {
|
|
// apiCheckDuplicate("Email", "email", data["email"]!, userIdApi).then((
|
|
// isDuplicate,
|
|
// ) {
|
|
// print("REsduplica - $isDuplicate");
|
|
//
|
|
// if (isDuplicate) {
|
|
// print("REsduplica1 - $isDuplicate");
|
|
// errorMessages["email"] = "Email Already Exist";
|
|
// // errorMessages is already updated inside apiCheckDuplicate
|
|
// // setState(() {});
|
|
// }
|
|
// });
|
|
//
|
|
// // errorMessages["mobile_no"] = "Mobile Number already exist";
|
|
// }
|
|
}
|
|
|
|
print("ISEMPTY");
|
|
|
|
return errorMessages.isEmpty; // Valid if there are no errors
|
|
}
|
|
|
|
Future<bool> isValidDataTwo(Map<String, dynamic> data) async {
|
|
errorMessages.clear(); // Reset errors
|
|
|
|
// Required fields that must not be empty
|
|
List<String> requiredFields = ["employee_code"];
|
|
|
|
// Check validation for each field
|
|
for (String field in requiredFields) {
|
|
if (data[field] == null || data[field].toString().trim().isEmpty) {
|
|
errorMessages[field] = "Required";
|
|
} else {
|
|
bool isDuplicate = await apiCheckDuplicate(
|
|
"Employee Code",
|
|
"employee_code",
|
|
data["employee_code"]!,
|
|
userIdApi,
|
|
);
|
|
|
|
if (isDuplicate) {
|
|
print("REsduplica1 - $isDuplicate");
|
|
errorMessages["employee_code"] = "Employee Code already exist";
|
|
// errorMessages is already updated inside apiCheckDuplicate
|
|
// setState(() {});
|
|
}
|
|
}
|
|
}
|
|
|
|
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';
|
|
request.headers['app-signature'] = 'ts-traveltool-2025-signature-123456';
|
|
|
|
// If updating, spoof the method Laravel-style
|
|
if (isUpdating) {
|
|
request.fields['_method'] = 'PUT';
|
|
request.fields['user_id'] = apiselectedUser!["user_id"].toString();
|
|
}
|
|
|
|
// userData.forEach((key, value) {
|
|
// // if (value == null) {
|
|
// // // skip nulls
|
|
// // return;
|
|
// // }
|
|
//
|
|
// if (key == 'travel_details' && value is Map<String, dynamic>) {
|
|
// // Encode travel_details map as JSON string
|
|
// request.fields[key] = jsonEncode(value);
|
|
// print("✅ Encoded travel_details: ${request.fields[key]}");
|
|
// } else {
|
|
// // If the value is empty or only whitespace, add it as empty string
|
|
// if (value.toString().trim().isEmpty) {
|
|
// request.fields[key] = '';
|
|
// } else {
|
|
// // Otherwise add the actual string value
|
|
// request.fields[key] = value.toString();
|
|
// }
|
|
// }
|
|
// });
|
|
|
|
userData.forEach((key, value) {
|
|
// if (value != null && value.toString().trim().isNotEmpty) {
|
|
if (key == 'travel_details' && value is Map<String, dynamic>) {
|
|
// ✅ Encode travel_details as a proper JSON string
|
|
request.fields[key] = jsonEncode(value);
|
|
|
|
print("✅ Encoded travel_details1: ${request.fields[key]}");
|
|
} else if (key == 'agent_supported_service_ids' && value is List) {
|
|
request.fields[key] = jsonEncode(value);
|
|
print("✅ Encoded agent_supported_service_ids: ${request.fields[key]}");
|
|
} else {
|
|
// request.fields[key] = jsonEncode(value);
|
|
print("✅ Encoded travel_details:${key} - ${value}");
|
|
request.fields[key] = value.toString();
|
|
print("✅ Encoded travel_details2: ${request.fields[key]}");
|
|
}
|
|
// }
|
|
});
|
|
|
|
// 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) {
|
|
// dispose();
|
|
print("✅ User submitted successfully!");
|
|
|
|
print("📨 Response: ${response.body}");
|
|
|
|
if (roleId == "4") {
|
|
context.go('/listPlan');
|
|
} else {
|
|
context.go('/listUser');
|
|
}
|
|
} else {
|
|
print("❌ Submission failed. Status: ${response.statusCode}");
|
|
print("📨 Body: ${response.body}");
|
|
|
|
showDialog(
|
|
context: context,
|
|
builder: (BuildContext context) {
|
|
return AlertDialog(
|
|
title: Text("User Creation Failed"),
|
|
content: Text(
|
|
"There was a problem in creating user. Please try again.",
|
|
),
|
|
actions: [
|
|
TextButton(
|
|
child: Text("OK"),
|
|
onPressed: () {
|
|
Navigator.of(context).pop();
|
|
},
|
|
),
|
|
],
|
|
);
|
|
},
|
|
);
|
|
}
|
|
} 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;
|
|
|
|
if (isCheckingToken) {
|
|
return Center(child: CircularProgressIndicator());
|
|
}
|
|
|
|
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: [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),
|
|
),
|
|
child: Column(
|
|
children: [
|
|
Expanded(
|
|
child: Container(
|
|
height:
|
|
isDesktop
|
|
? MediaQuery.of(context).size.height * 0.98
|
|
: MediaQuery.of(context).size.height,
|
|
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: [
|
|
if (selectedTab != "personal")
|
|
..._buildBack(isDesktop, layoutColor!),
|
|
Spacer(), // spacing between buttons
|
|
// Next or Submit based on role or user type
|
|
if (selectedTab == "travel" ||
|
|
selectedRole == "5" ||
|
|
setSelectesUserType == true)
|
|
..._buildSubmit(isDesktop, layoutColor!)
|
|
else
|
|
..._buildNext(isDesktop, layoutColor!),
|
|
// (selectedTab == "travel" ||
|
|
// selectedRole == "5" ||
|
|
// setSelectesUserType == true)
|
|
// ? _buildSubmit(isDesktop, layoutColor!)
|
|
// : _buildNext(
|
|
// isDesktop,
|
|
// layoutColor!,
|
|
// ), // _buildGoBack(isDesktop, layoutColor!),
|
|
],
|
|
)
|
|
: Row(
|
|
mainAxisAlignment: MainAxisAlignment.end,
|
|
children:
|
|
(selectedTab == "travel" ||
|
|
selectedRole == "5" ||
|
|
setSelectesUserType == true)
|
|
? _buildSubmit(isDesktop, layoutColor!)
|
|
: _buildNext(isDesktop, layoutColor!),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildUserDetails(bool isDesktop) {
|
|
return Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Padding(
|
|
padding: const EdgeInsets.all(8.0),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
// Text(
|
|
// apiselectedUser != null ? "Profile" : "Create New User",
|
|
// style: GoogleFonts.poppins(
|
|
// fontSize: isDesktop ? 15 : 12,
|
|
// fontWeight: FontWeight.w600,
|
|
// color: Colors.black,
|
|
// ),
|
|
// ), //old very first
|
|
|
|
// if (isEditProfile)
|
|
// IconButton(
|
|
// icon: Icon(
|
|
// Icons.edit,
|
|
// color: Color(0xFF114D8B),
|
|
// size: 14,
|
|
// ),
|
|
// onPressed: () {
|
|
// setState(() {
|
|
// isViewMode = !isViewMode;
|
|
// });
|
|
// })
|
|
Row(
|
|
children: [
|
|
if (apiselectedUser != null)
|
|
isEditProfile
|
|
? Text(
|
|
"Profile",
|
|
style: GoogleFonts.poppins(
|
|
fontSize: isDesktop ? 15 : 12,
|
|
fontWeight: FontWeight.w600,
|
|
color: Colors.black,
|
|
),
|
|
)
|
|
: BreadcrumbNavigation(
|
|
isDesktop: isDesktop,
|
|
breadcrumbItems: [
|
|
BreadcrumbItem(
|
|
title: 'Users',
|
|
tooltip: 'Go To Users',
|
|
onTap: (context) {
|
|
context.go("/listUser");
|
|
},
|
|
),
|
|
BreadcrumbItem(title: 'Edit User'),
|
|
],
|
|
)
|
|
else
|
|
BreadcrumbNavigation(
|
|
isDesktop: isDesktop,
|
|
breadcrumbItems: [
|
|
BreadcrumbItem(
|
|
title: 'Users',
|
|
tooltip: 'Go To Users',
|
|
onTap: (context) {
|
|
context.go("/listUser");
|
|
},
|
|
),
|
|
BreadcrumbItem(title: 'Create User'),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
SizedBox(height: 18),
|
|
isDesktop
|
|
? buildTabsForUser()
|
|
: SingleChildScrollView(
|
|
scrollDirection: Axis.horizontal,
|
|
child: buildTabsForUser(),
|
|
),
|
|
Container(
|
|
// color: Colors.yellow.shade50,
|
|
height: MediaQuery.of(context).size.height * 0.64,
|
|
child: Row(
|
|
children: [
|
|
Expanded(child: buildTabContents(isDesktop, isViewMode)),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
Widget buildTabContents(isDesktop, isViewMode) {
|
|
switch (selectedTab) {
|
|
case "personal":
|
|
return PersonalDetails(
|
|
personalDetailsKey: personalDetailsKey,
|
|
isDesktop: isDesktop, // pass isDesktop as a named argument
|
|
isViewMode: isViewMode,
|
|
isEditProfile: isEditProfile,
|
|
userIdApi: userIdApi,
|
|
controllers: controllers,
|
|
errorMessages: errorMessages,
|
|
selectedGender: selectedGender,
|
|
selectedCountry: selectedCountry,
|
|
selectedRole: selectedRole,
|
|
apiselectedUser: isapiselectedUser,
|
|
onUserTypeChanged: handleUserTypeChange,
|
|
onGenderChanged: (gender) {
|
|
setState(() {
|
|
selectedGender = gender;
|
|
});
|
|
},
|
|
onCountryChanged: (country) {
|
|
setState(() {
|
|
selectedCountry = country;
|
|
});
|
|
},
|
|
onRoleChanged: (role) {
|
|
setState(() {
|
|
print("Roleu $role");
|
|
selectedRole = role;
|
|
});
|
|
},
|
|
onServiceIdsChanged: (List<Map<String, dynamic>> updatedList) {
|
|
setState(() {
|
|
selectedServiceIds = updatedList;
|
|
});
|
|
},
|
|
initialSelectedServices: selectedServiceIds,
|
|
);
|
|
case "office":
|
|
return OfficeDetails(
|
|
isDesktop: isDesktop, // pass isDesktop as a named argument
|
|
isViewMode: isViewMode,
|
|
userIdApi: userIdApi,
|
|
controllers: controllers,
|
|
errorMessages: errorMessages,
|
|
selectedLevel: selectedLevel,
|
|
selectedDepartment: selectedDepartment,
|
|
selectedFirstApprover: selectedFirstApprover,
|
|
selectedSecondApprover: selectedSecondApprover,
|
|
selectedThirdApprover: selectedThirdApprover,
|
|
selectedExceptionalApprover: selectedExceptionalApprover,
|
|
selectedSubstituteApprover: selectedSubstituteApprover,
|
|
onLevelChanged: (gender) {
|
|
setState(() {
|
|
selectedLevel = gender;
|
|
});
|
|
},
|
|
onDepartmentChanged: (country) {
|
|
setState(() {
|
|
selectedDepartment = country;
|
|
});
|
|
},
|
|
onFirstApproverChanged: (role) {
|
|
setState(() {
|
|
selectedFirstApprover = role;
|
|
});
|
|
},
|
|
onSecondApproverChanged: (role) {
|
|
setState(() {
|
|
selectedSecondApprover = role;
|
|
});
|
|
},
|
|
onThirdApproverChanged: (role) {
|
|
setState(() {
|
|
selectedThirdApprover = role;
|
|
});
|
|
},
|
|
onExceptionalApproverChanged: (role) {
|
|
setState(() {
|
|
selectedExceptionalApprover = role;
|
|
});
|
|
},
|
|
onFirstSubsApproverChanged: (role) {
|
|
setState(() {
|
|
selectedSubstituteApprover = role;
|
|
|
|
print("onFirstSubsApproverChanged - $selectedSubstituteApprover");
|
|
});
|
|
},
|
|
);
|
|
case "travel":
|
|
final fullName =
|
|
"${controllers["Fname"]?.text ?? ""} ${controllers["Lname"]?.text ?? ""}"
|
|
.trim();
|
|
return TravellerDetails(
|
|
key: travellerDetailsKey,
|
|
isDesktop: isDesktop,
|
|
isViewMode: isViewMode,
|
|
controllers: controllers,
|
|
errorMessages: errorMessages,
|
|
travelDetails: travelDetailsDataFromAPI, // 👈 Pass this down
|
|
passportFileUrlFromApi: passportFileUrlFromApi,
|
|
userIdApi: userIdApi,
|
|
fullName: fullName,
|
|
);
|
|
default:
|
|
return PersonalDetails(
|
|
personalDetailsKey: personalDetailsKey,
|
|
isDesktop: isDesktop, // pass isDesktop as a named argument
|
|
isViewMode: isViewMode,
|
|
isEditProfile: isEditProfile,
|
|
userIdApi: userIdApi,
|
|
controllers: controllers,
|
|
errorMessages: errorMessages,
|
|
selectedGender: selectedGender,
|
|
selectedCountry: selectedCountry,
|
|
apiselectedUser: isapiselectedUser,
|
|
onUserTypeChanged: handleUserTypeChange,
|
|
onServiceIdsChanged: (List<Map<String, dynamic>> updatedList) {
|
|
setState(() {
|
|
selectedServiceIds = updatedList;
|
|
});
|
|
},
|
|
);
|
|
}
|
|
}
|
|
|
|
Widget buildTabsForUser() {
|
|
// final tabs = {
|
|
// "personal": "Personal Details",
|
|
// "office": "Office Details",
|
|
// "travel": "Travel Details",
|
|
// };
|
|
|
|
final Map<String, String> allTabs = {
|
|
"personal": "Personal Details",
|
|
"office": "Office Details",
|
|
"travel": "Travel Details",
|
|
};
|
|
|
|
Map<String, String> getTabs(bool setSelectesUserType) {
|
|
if (setSelectesUserType || selectedRole == "5") {
|
|
return {"personal": "Personal Details"};
|
|
} else {
|
|
return allTabs;
|
|
}
|
|
}
|
|
|
|
final tabs = getTabs(setSelectesUserType);
|
|
|
|
return Row(
|
|
crossAxisAlignment: CrossAxisAlignment.end, // important
|
|
children:
|
|
tabs.entries.map((entry) {
|
|
final targetTab = entry.key;
|
|
|
|
print("TargetsTAb: $targetTab");
|
|
|
|
final isSelected = selectedTab == entry.key;
|
|
print("isSelected: $isSelected");
|
|
|
|
return GestureDetector(
|
|
onTap: () {
|
|
setState(() async {
|
|
bool isValid = false;
|
|
|
|
final currentTab = selectedTab;
|
|
if (currentTab == "personal") {
|
|
// isValid = isValidData(userDetials);
|
|
isValid = await isValidData(userDetials);
|
|
|
|
if (isValid) {
|
|
setState(() {
|
|
selectedTab = entry.key;
|
|
});
|
|
}
|
|
} else if (currentTab == "office" &&
|
|
targetTab == "personal") {
|
|
setState(() {
|
|
selectedTab = entry.key;
|
|
});
|
|
} else if (currentTab == "office") {
|
|
isValid = await isValidDataTwo(userDetials);
|
|
if (isValid) {
|
|
setState(() {
|
|
selectedTab = entry.key;
|
|
});
|
|
}
|
|
} else {
|
|
isValid =
|
|
true; // Travel tab might not need validation at this point
|
|
setState(() {
|
|
selectedTab = entry.key;
|
|
});
|
|
}
|
|
});
|
|
},
|
|
child: Padding(
|
|
padding: const EdgeInsets.only(
|
|
right: 24.0,
|
|
), // space between tabs
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Text(
|
|
entry.value,
|
|
style: GoogleFonts.poppins(
|
|
fontSize: 12,
|
|
fontWeight: FontWeight.w600,
|
|
color:
|
|
isSelected ? Color(0xFF114D8B) : Color(0xFF475569),
|
|
),
|
|
),
|
|
const SizedBox(height: 8),
|
|
AnimatedContainer(
|
|
duration: Duration(milliseconds: 300),
|
|
height: 2,
|
|
width: isSelected ? 50 : 0, // small line
|
|
color: Color(0xFF114D8B),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}).toList(),
|
|
);
|
|
}
|
|
|
|
// ---- Submit ---------------------------------------
|
|
|
|
List<Widget> _buildGoBack(isDesktop, Color layoutColor) {
|
|
return [
|
|
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: handleGoBack,
|
|
child: Text("Back"),
|
|
),
|
|
),
|
|
];
|
|
}
|
|
|
|
List<Widget> _buildNext(isDesktop, Color layoutColor) {
|
|
return [
|
|
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: handleNext,
|
|
// onPressed:
|
|
// isViewMode ? null : handleNext, // Disable when in view mode
|
|
child: Text("Next"),
|
|
),
|
|
),
|
|
];
|
|
}
|
|
|
|
List<Widget> _buildBack(isDesktop, Color layoutColor) {
|
|
return [
|
|
MouseRegion(
|
|
cursor:
|
|
isViewMode
|
|
? SystemMouseCursors.forbidden
|
|
: SystemMouseCursors.click,
|
|
child: TextButton(
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor:
|
|
isViewMode ? Colors.white : Colors.white, // Keep original color
|
|
foregroundColor:
|
|
isViewMode ? layoutColor : layoutColor, // 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: 10),
|
|
),
|
|
onPressed: handleBack,
|
|
// onPressed:
|
|
// isViewMode ? null : handleNext, // Disable when in view mode
|
|
child: Text("Back"),
|
|
),
|
|
),
|
|
];
|
|
}
|
|
|
|
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: Text("Cancel"),
|
|
),
|
|
SizedBox(width: 20),
|
|
if (!isViewMode)
|
|
MouseRegion(
|
|
cursor:
|
|
isViewMode
|
|
? SystemMouseCursors.forbidden
|
|
: SystemMouseCursors.click,
|
|
child: Focus(
|
|
// 👈 Wrap with Focus here
|
|
focusNode: submitButtonFocusNode,
|
|
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
|
|
}
|
|
}
|