diff --git a/lib/Screens/userManagement/create_user/change_password.dart b/lib/Screens/userManagement/create_user/change_password.dart new file mode 100644 index 0000000..3ecc927 --- /dev/null +++ b/lib/Screens/userManagement/create_user/change_password.dart @@ -0,0 +1,383 @@ +import 'dart:convert'; + +import 'package:dropdown_search/dropdown_search.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:http/http.dart' as http; + +import '../../../config/apiUrl.dart'; +import '../../../services/apiService.dart'; +import '../../../utils/auth_utils.dart'; +import '../../../widgets/custom_user_form.dart'; + + +class ChangePasswordDialogData extends StatefulWidget { + + final dynamic isDesktop; + final dynamic layoutColor; + final dynamic updaterUserId; + final dynamic updaterEmail; + + const ChangePasswordDialogData({ + super.key, + this.isDesktop, + this.layoutColor, + this.updaterUserId, + this.updaterEmail + }); + + + @override + ChangePasswordDialogDataState createState() => ChangePasswordDialogDataState(); + } + +class ChangePasswordDialogDataState extends State { + + final ApiService apiService = ApiService(); + + final Map controllers = {}; + Map errorMessages = {}; + + + String? loggeduserId; + String? updaterUserIdForAPI; + + List dataHeader = [ + "email", + "changePassword", + "confirmPassword" + ]; + + // @override + // void initState() { + // super.initState(); + // + // for (var field in dataHeader) { + // controllers[field] = TextEditingController(); + // } + // + // setState(() { + // controllers['email']?.text = widget.updaterEmail ?? ''; + // controllers['changePassword']?.text = ''; + // controllers['confirmPassword']?.text = ''; + // }); + // } + + @override + void initState() { + super.initState(); + print("widget.updaterEmail: ${widget.updaterEmail}"); + + for (var field in dataHeader) { + controllers[field] = TextEditingController(); + } + + setState(() { + controllers['email']?.text = widget.updaterEmail ; + controllers['changePassword']?.text = ''; + controllers['confirmPassword']?.text = ''; + updaterUserIdForAPI = widget.updaterUserId; + }); + } + + + + void _clearError() { + setState(() { + errorMessages.clear(); + }); + } + + @override + void dispose() { + for (var controller in controllers.values) { + controller.dispose(); + } + super.dispose(); + } + + + + bool validateData() { + errorMessages.clear(); + + final String? email = controllers["email"]?.text; + final String? changePassword = controllers["changePassword"]?.text; + final String? confirmPassword = controllers["confirmPassword"]?.text; + + // Required fields check + if (email == null || email.trim().isEmpty) { + errorMessages["email"] = "Required"; + } + + if (changePassword == null || changePassword.trim().isEmpty) { + errorMessages["changePassword"] = "Required"; + } + + if (confirmPassword == null || confirmPassword.trim().isEmpty) { + errorMessages["confirmPassword"] = "Required"; + } + + // Password match check + if ((changePassword?.isNotEmpty ?? false) && + (confirmPassword?.isNotEmpty ?? false) && + changePassword != confirmPassword) { + errorMessages["changePassword"] = "Passwords do not match"; + errorMessages["confirmPassword"] = "Passwords do not match"; + } + + // setState(() {}); // Update UI with any error messages + return errorMessages.isEmpty; + } + + + Future handleSubmit() async { + loggeduserId = await getUserId(); + + setState(() { + // This triggers UI rebuild with error messages + if (validateData()) { + postData(); + } + }); + + } + + Future postData() async { + // final remarksData = getData(); + print('sss$updaterUserIdForAPI'); + final loggedInUserId = await getUserId(); + + final password = controllers["changePassword"]?.text ?? ''; + final confirmPassword = controllers["confirmPassword"]?.text ?? ''; + final String apiUrldata = '$apiUrl/api/user/user-password/$updaterUserIdForAPI'; + + final token = await getToken(); + final headers = { + 'Authorization': 'Bearer $token', + 'Content-Type': 'application/json', + }; + + try { + final uri = Uri.parse(apiUrldata); + final headers = { + 'Authorization': 'Bearer $token', + 'Content-Type': 'application/json', + }; + final body = jsonEncode({ + "password": password, + "updated_by": loggedInUserId, + }); + + final response = await http.put(uri, headers: headers, body: body); + + if (response.statusCode == 200 || response.statusCode == 201) { + print("Forex Details Created successfully!"); + print("Response: ${response.body}"); + _clearError(); + Navigator.of(context).pop(); + } else if (response.statusCode == 404) { + Navigator.of(context).pop(); + final message = jsonDecode(response.body)['message'] ?? 'Unknown error'; + + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(message), + backgroundColor: Colors.redAccent, + behavior: SnackBarBehavior.floating, + ), + ); + } else { + print("Failed to submit plan. Status: ${response.statusCode}"); + print("Error: ${response.body}"); + } + } catch (e) { + print(" Error submitting plan: $e"); + } + } + + @override + Widget build(BuildContext context) { + + return AlertDialog( + backgroundColor: Colors.white, + contentPadding: const EdgeInsets.fromLTRB(34, 30, 34, 30), + // contentPadding: const EdgeInsets.fromLTRB(24, 20, 24, 10), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + // Row 1: Title + Edit + Delete buttons + Row( + children: [ + Text( + 'Change Password', + style: GoogleFonts.poppins(fontSize: 15, color: Colors.black), + ), + const Spacer(), + ], + ), + const SizedBox(height: 2), + Divider( + thickness: 0.2, + color: Colors.blueGrey.shade100, + ), + const SizedBox(height: 5), + + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Email", + style: GoogleFonts.poppins( + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74)), + ), + SizedBox(height: 5), + CustomTextFieldUserWrapper( + isFocused: false, + isDesktop: widget.isDesktop, + color: Colors.transparent, + // width: isDesktop + // ? MediaQuery.of(context).size.width * 0.330 + // : MediaQuery.of(context).size.width * 0.66, + child: SizedBox( + height: 40, + child: TextField( + controller: controllers["email"], + style: const TextStyle(fontSize: 12), + decoration: const InputDecoration( + labelText: "Email", + labelStyle: TextStyle(fontSize: 11, 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: 10, + ), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Change Password", + style: GoogleFonts.poppins( + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74)), + ), + SizedBox(height: 5), + CustomTextFieldUserWrapper( + isFocused: false, + isDesktop: widget.isDesktop, + color: Colors.transparent, + child: SizedBox( + height: 40, + child: TextField( + controller: controllers["changePassword"], + style: const TextStyle(fontSize: 12), + decoration: const InputDecoration( + labelText: "Change Password", + labelStyle: TextStyle(fontSize: 11, color: Colors.grey), + floatingLabelBehavior: FloatingLabelBehavior.never, + border: InputBorder.none, + contentPadding: EdgeInsets.symmetric(vertical: 16), + ), + )), + ), + if (errorMessages["changePassword"] != null) ...[ + SizedBox(height: 5), // Space before error message + Text( + errorMessages["changePassword"]!, + style: TextStyle(color: Colors.red, fontSize: 12), + ), + ], + ], + ), + SizedBox( + height: 15, + ), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Confirm Password", + style: GoogleFonts.poppins( + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74)), + ), + SizedBox(height: 5), + CustomTextFieldUserWrapper( + isFocused: false, + isDesktop: widget.isDesktop, + color: Colors.transparent, + child: SizedBox( + height: 40, + child: TextField( + controller: controllers["confirmPassword"], + style: const TextStyle(fontSize: 12), + decoration: const InputDecoration( + labelText: "Confirm Password", + labelStyle: TextStyle(fontSize: 11, color: Colors.grey), + floatingLabelBehavior: FloatingLabelBehavior.never, + border: InputBorder.none, + contentPadding: EdgeInsets.symmetric(vertical: 16), + ), + )), + ), + if (errorMessages["confirmPassword"] != null) ...[ + SizedBox(height: 5), // Space before error message + Text( + errorMessages["confirmPassword"]!, + style: TextStyle(color: Colors.red, fontSize: 12), + ), + ], + ], + ), + SizedBox( + height: 15, + ), + + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + SizedBox( + child: ElevatedButton( + onPressed: () { + handleSubmit(); + }, + style: ElevatedButton.styleFrom( + backgroundColor: widget.layoutColor, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + ), + child: Text('Save', + style: GoogleFonts.poppins( + fontSize: 11, color: Colors.white)), + ), + ), + ], + ) + // : SizedBox.shrink(), + ], + ), + ); + } +} + diff --git a/lib/Screens/userManagement/create_user/create_user.dart b/lib/Screens/userManagement/create_user/create_user.dart index 86bad75..9cdff8d 100644 --- a/lib/Screens/userManagement/create_user/create_user.dart +++ b/lib/Screens/userManagement/create_user/create_user.dart @@ -54,6 +54,7 @@ class _CreateUserFormDetialsState extends State { String? userId; String? orgId; + String? userIdApi; String? token; @@ -201,7 +202,7 @@ class _CreateUserFormDetialsState extends State { 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"] ?? ""; @@ -976,6 +977,7 @@ class _CreateUserFormDetialsState extends State { personalDetailsKey: personalDetailsKey, isDesktop: isDesktop, // pass isDesktop as a named argument isViewMode: isViewMode, + userIdApi:userIdApi, controllers: controllers, errorMessages: errorMessages, selectedGender: selectedGender, diff --git a/lib/Screens/userManagement/create_user/office_details.dart b/lib/Screens/userManagement/create_user/office_details.dart index 4a6c71f..10950f4 100644 --- a/lib/Screens/userManagement/create_user/office_details.dart +++ b/lib/Screens/userManagement/create_user/office_details.dart @@ -1108,6 +1108,12 @@ class _OfficeDetailsState extends State { _selectedCheckOutDate = pickedDate; widget.controllers["delegationStartDate"]?.text = DateFormat('dd-MM-yyyy').format(pickedDate); + + if (_selectedEndDate != null && + _selectedEndDate!.isBefore(_selectedCheckOutDate!)) { + _selectedEndDate = null; + widget.controllers["delegationEndDate"]?.text = ''; + } }); } } @@ -1173,6 +1179,10 @@ class _OfficeDetailsState extends State { DateTime now = DateTime.now(); DateTime today = DateTime(now.year, now.month, now.day); + DateTime minDate = _selectedCheckOutDate != null + ? _selectedCheckOutDate! + : today; + // Parse date from notifier if available, else use today DateTime initialDate; @@ -1188,11 +1198,11 @@ class _OfficeDetailsState extends State { DateTime? pickedDate = await showDatePicker( context: context, - initialDate: - _selectedEndDate != null && _selectedEndDate!.isAfter(today) - ? _selectedEndDate! - : today, - firstDate: today, + initialDate: _selectedEndDate != null && + _selectedEndDate!.isAfter(minDate) + ? _selectedEndDate! + : minDate, + firstDate: minDate, lastDate: DateTime(2100), ); @@ -1201,8 +1211,6 @@ class _OfficeDetailsState extends State { _selectedEndDate = pickedDate; widget.controllers["delegationEndDate"]?.text = DateFormat('dd-MM-yyyy').format(pickedDate); - // textControllers["_forexEndDate"]?.text = - // DateFormat('dd-MM-yyyy').format(initialDate); }); } } diff --git a/lib/Screens/userManagement/create_user/personal_details.dart b/lib/Screens/userManagement/create_user/personal_details.dart index c07af96..63b5e3b 100644 --- a/lib/Screens/userManagement/create_user/personal_details.dart +++ b/lib/Screens/userManagement/create_user/personal_details.dart @@ -6,11 +6,14 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:google_fonts/google_fonts.dart'; +import 'package:http/http.dart' as http; import 'package:intl/intl.dart'; import '../../../services/apiService.dart'; import '../../../utils/auth_utils.dart'; import '../../../widgets/custom_user_form.dart'; +import '../../../config/apiUrl.dart'; +import 'change_password.dart'; class PersonalDetails extends StatefulWidget { final GlobalKey personalDetailsKey; @@ -31,6 +34,7 @@ class PersonalDetails extends StatefulWidget { final String? selectedGender; final String? selectedCountry; final String? selectedRole; + final String? userIdApi; // const PersonalDetails(this.isDesktop, this.isViewMode, {super.key},); const PersonalDetails( @@ -49,7 +53,7 @@ class PersonalDetails extends StatefulWidget { this.onGenderChanged, this.onCountryChanged, this.onRoleChanged, - this.onUserTypeChanged}) + this.onUserTypeChanged, this.userIdApi}) : super(key: key); @override @@ -124,8 +128,14 @@ class PersonalDetailsState extends State { Color? layoutColor; Color? bodyColor; + final Map controllers = {}; + Map errorMessages2 = {}; + @override void initState() { + + + super.initState(); apiCountryData = null; @@ -144,6 +154,7 @@ class PersonalDetailsState extends State { WidgetsBinding.instance.addPostFrameCallback((_) { loadAllServices(); getOrganizationData(); + loadInitialData(); }); } @@ -153,6 +164,21 @@ class PersonalDetailsState extends State { }); } + 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 fetchRoles() async { try { final response = await apiService.fetchMasterDropdown(); @@ -526,6 +552,24 @@ class PersonalDetailsState extends State { } Widget _buildThirdRow(bool isDesktop) { + + void _openPopup() { + final emailValue = widget.controllers["email"]?.text ?? ""; + final updaterUserId = widget.userIdApi ?? ""; + + showDialog( + context: context, + builder: (context) { + return ChangePasswordDialogData( + updaterEmail: emailValue, + updaterUserId: updaterUserId, + layoutColor: layoutColor, + isDesktop: widget.isDesktop, + ); + }, + ); + } + return Container( color: Colors.white, child: isDesktop @@ -535,8 +579,16 @@ class PersonalDetailsState extends State { if (!widget.apiselectedUser) ...[ buildPassword(), SizedBox(width: 15), + buildRole() + ] + else ...[ + buildRole(), + SizedBox(height: 8, width: 15), + TextButton( + onPressed: () => _openPopup(), + child: Text("Change Password"), + ), ], - buildRole() ], ) : Column( @@ -544,9 +596,15 @@ class PersonalDetailsState extends State { children: [ if (!widget.apiselectedUser) ...[ buildPassword(), - SizedBox(height: 8)], // - buildRole() - + SizedBox(height: 8),buildRole() + ] else ...[ + buildRole(), + SizedBox(height: 8), + TextButton( + onPressed: () => _openPopup(), + child: Text("Change Password"), + ), + ], ], ), ); @@ -1282,3 +1340,4 @@ class PersonalDetailsState extends State { // apiselectedUser != null // ? SizedBox() } + diff --git a/lib/Screens/userManagement/create_user/traveller_details.dart b/lib/Screens/userManagement/create_user/traveller_details.dart index 6d737ce..8421a29 100644 --- a/lib/Screens/userManagement/create_user/traveller_details.dart +++ b/lib/Screens/userManagement/create_user/traveller_details.dart @@ -3093,6 +3093,109 @@ class TravellerDetailsState extends State { ); } + Widget buildVisaType2(entry) { + late Map visaTypeMap; // Mapping country_code -> country_name + late List visaTypeCodes; // List of country codes + + // List purposeList = apiData?['visa_type_of_visa']; + + List purposeList = apiData?['visa_type_of_visa']; + + print("purposeList - $purposeList"); + + + visaTypeMap = { + for (var item in purposeList) + item['visa_type_id'] as String: item['visa_type_of_visa'] as String + }; + + // Extract only country codes for processing + visaTypeCodes = visaTypeMap.keys.toList(); + + // selectedPurpose ??= null; + + String? selectedPurpose = entry['visa_type_of_visa']; + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Visa Type", + style: GoogleFonts.poppins( + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74)) + ), + SizedBox(height: 5), + CustomTextFieldUserTravellerWrapper( + width: widget.isDesktop + ? MediaQuery.of(context).size.width * 0.17 + : null, + isFocused: false, + isDesktop: widget.isDesktop, + child: SizedBox( + height: 40, + child: DropdownSearch( + selectedItem: visaTypeMap[selectedPurpose], + popupProps: PopupProps.menu( + showSearchBox: true, // Enables search functionality + menuProps: const MenuProps( + backgroundColor: Colors.white, + ), + // constraints: BoxConstraints(maxHeight: 250), + itemBuilder: (context, item, isSelected) => Padding( + padding: const EdgeInsets.symmetric( + horizontal: 8.0, vertical: 6.0), + child: Text( + item, + style: GoogleFonts.poppins(fontSize: 11.5), + ), + ), + searchFieldProps: TextFieldProps( + decoration: InputDecoration( + hintText: "Search Visa Type...", + hintStyle: GoogleFonts.poppins(fontSize: 11.5), + contentPadding: EdgeInsets.symmetric(horizontal: 10), + ), + ), + ), + items: visaTypeMap.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 Visa Type", + style: TextStyle(fontSize: 12), + ), + ), + onChanged: (String? newValue) { + setState(() { + // Find the country_code based on selected country_name + // selectedCountry = countryMap.entries + // .firstWhere((entry) => entry.value == newValue) + // .key; + + final selectedPurpose = visaTypeMap.entries + .firstWhere((entry) => entry.value == newValue) + .key; + entry['visa_type_of_visa'] = selectedPurpose; + }); + }, + ), + ), + ), + ], + ); + } + Widget buildVisaValidFrom(Map entry) { DateTime? _selectedCheckOutDate; TimeOfDay? _selectedCheckOutTime; @@ -3132,7 +3235,7 @@ class TravellerDetailsState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - "ValidFrom", + "Valid From", style: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w600, @@ -3214,7 +3317,7 @@ class TravellerDetailsState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - "Valid UpTo", + "Valid To", style: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w600,