diff --git a/lib/Screens/allTrips/list_all_plans.dart b/lib/Screens/allTrips/list_all_plans.dart index 546276a..1461a44 100644 --- a/lib/Screens/allTrips/list_all_plans.dart +++ b/lib/Screens/allTrips/list_all_plans.dart @@ -567,8 +567,12 @@ class _ListAllPlansState extends State { // List plans = snapshot.data!; + // List plans = + // filteredPlans.isNotEmpty ? filteredPlans : allPlans; + List plans = - filteredPlans.isNotEmpty ? filteredPlans : allPlans; + searchController.text.isEmpty ? allPlans : filteredPlans; + plans.sort((a, b) => int.parse(b.planId).compareTo(int.parse(a.planId))); @@ -654,7 +658,7 @@ class _ListAllPlansState extends State { ), softWrap: true, overflow: TextOverflow.ellipsis)), - DataCell(Text(plan.employeeCode ?? "no data", + DataCell(Text(plan.employeeCode ?? " - ", style: TextStyle( fontSize: 13, fontFamily: "Inter", @@ -768,31 +772,32 @@ class _ListAllPlansState extends State { plan.planId); }, ), + IconButton( + icon: const Icon( + Icons.comment, + color: Color(0xFF475569), + size: 11, + ), + onPressed: () { + showDialog( + context: context, + builder: (context) => + CommentModalList( + // planId: plan.planId, + planId: plan + .planId + .toString(), + layoutColorForUser: + layoutColor!, + role: "Admin"), + ); + }), ], ), ), ), ], ), - IconButton( - icon: const Icon( - Icons.comment, - color: Color(0xFF475569), - size: 11, - ), - onPressed: () { - showDialog( - context: context, - builder: (context) => - CommentModalList( - // planId: plan.planId, - planId: - plan.planId.toString(), - layoutColorForUser: - layoutColor!, - role: "Admin"), - ); - }), ], ), ), @@ -861,26 +866,8 @@ class _ListAllPlansState extends State { plan: plan, deletePlan: deletePlan, apiService: apiService, + layoutColor: layoutColor, ), - IconButton( - icon: const Icon( - Icons.comment, - color: Color(0xFF475569), - size: 11, - ), - onPressed: () { - showDialog( - context: context, - builder: (context) => - CommentModalList( - // planId: plan.planId, - planId: plan.planId - .toString(), - layoutColorForUser: - layoutColor!, - role: "Admin"), - ); - }), ], ), ], @@ -985,11 +972,36 @@ class _ListAllPlansState extends State { children: [ Expanded( child: isDesktop - ? SingleChildScrollView( - scrollDirection: Axis.vertical, - child: table, // <-- your existing table - ) - : buildMobileCardView(paginatedPlans), + ? (searchController.text.isNotEmpty && + filteredPlans.isEmpty + ? Center( + child: Text( + "No matches found", + style: GoogleFonts.poppins( + fontSize: 14, color: Colors.grey), + ), + ) + : SingleChildScrollView( + scrollDirection: Axis.vertical, + child: table, + )) + : (searchController.text.isNotEmpty && + filteredPlans.isEmpty + ? Center( + child: Text( + "No matches found", + style: GoogleFonts.poppins( + fontSize: 14, color: Colors.grey), + ), + ) + : buildMobileCardView(paginatedPlans)), + + // child: isDesktop + // ? SingleChildScrollView( + // scrollDirection: Axis.vertical, + // child: table, // <-- your existing table + // ) + // : buildMobileCardView(paginatedPlans), ), PaginationControls( currentPage: currentPage, diff --git a/lib/Screens/allTrips/travel_agent_list.dart b/lib/Screens/allTrips/travel_agent_list.dart index bc1a7f4..3c7d7c3 100644 --- a/lib/Screens/allTrips/travel_agent_list.dart +++ b/lib/Screens/allTrips/travel_agent_list.dart @@ -498,8 +498,12 @@ class _TravelAgentListPlansState extends State { // List plans = snapshot.data!; + // List plans = + // filteredPlans.isNotEmpty ? filteredPlans : allPlans; + List plans = - filteredPlans.isNotEmpty ? filteredPlans : allPlans; + searchController.text.isEmpty ? allPlans : filteredPlans; + plans.sort((a, b) => int.parse(b.planId).compareTo(int.parse(a.planId))); @@ -587,7 +591,7 @@ class _TravelAgentListPlansState extends State { softWrap: true, overflow: TextOverflow.ellipsis)), - DataCell(Text(plan.employeeCode ?? "no data", + DataCell(Text(plan.employeeCode ?? " - ", style: TextStyle( fontSize: 13, fontFamily: "Inter", @@ -702,29 +706,33 @@ class _TravelAgentListPlansState extends State { plan.planId); }, ), + IconButton( + icon: const Icon( + Icons.comment, + color: Color(0xFF475569), + size: 11, + ), + onPressed: () { + showDialog( + context: context, + builder: (context) => + CommentModal( + // planId: plan.planId, + planId: plan + .planId + .toString(), + layoutColorForUser: + layoutColor!, + role: + "Travel Agent"), + ); + }), ], ), ), ), ], ), - IconButton( - icon: const Icon( - Icons.comment, - color: Color(0xFF475569), - size: 11, - ), - onPressed: () { - showDialog( - context: context, - builder: (context) => CommentModal( - // planId: plan.planId, - planId: plan.planId.toString(), - layoutColorForUser: - layoutColor!, - role: "Travel Agent"), - ); - }), ], ), ), @@ -819,24 +827,8 @@ class _TravelAgentListPlansState extends State { plan: plan, deletePlan: deletePlan, apiService: apiService, + layoutColor: layoutColor, ), - IconButton( - icon: const Icon( - Icons.comment, - color: Color(0xFF475569), - size: 11, - ), - onPressed: () { - showDialog( - context: context, - builder: (context) => CommentModal( - // planId: plan.planId, - planId: plan.planId.toString(), - layoutColorForUser: - layoutColor!, - role: "Travel Agent"), - ); - }), ], ), @@ -941,11 +933,36 @@ class _TravelAgentListPlansState extends State { children: [ Expanded( child: isDesktop - ? SingleChildScrollView( - scrollDirection: Axis.vertical, - child: table, // <-- your existing table - ) - : buildMobileCardView(paginatedPlans), + ? (searchController.text.isNotEmpty && + filteredPlans.isEmpty + ? Center( + child: Text( + "No matches found", + style: GoogleFonts.poppins( + fontSize: 14, color: Colors.grey), + ), + ) + : SingleChildScrollView( + scrollDirection: Axis.vertical, + child: table, + )) + : (searchController.text.isNotEmpty && + filteredPlans.isEmpty + ? Center( + child: Text( + "No matches found", + style: GoogleFonts.poppins( + fontSize: 14, color: Colors.grey), + ), + ) + : buildMobileCardView(paginatedPlans)), + + // child: isDesktop + // ? SingleChildScrollView( + // scrollDirection: Axis.vertical, + // child: table, // <-- your existing table + // ) + // : buildMobileCardView(paginatedPlans), ), PaginationControls( currentPage: currentPage, diff --git a/lib/Screens/approvals/approval_list.dart b/lib/Screens/approvals/approval_list.dart index e2afbbf..54b8503 100644 --- a/lib/Screens/approvals/approval_list.dart +++ b/lib/Screens/approvals/approval_list.dart @@ -529,8 +529,15 @@ class _ApprovalListState extends State { ); } + // List plans = + // filteredPlans.isNotEmpty ? filteredPlans : allPlans; + + // List plans = + // searchController.text.isEmpty ? allPlans : filteredPlans; + List plans = - filteredPlans.isNotEmpty ? filteredPlans : allPlans; + searchController.text.isEmpty ? allPlans : filteredPlans; + plans.sort((a, b) => int.parse(b.planId).compareTo(int.parse(a.planId))); @@ -619,7 +626,7 @@ class _ApprovalListState extends State { ), softWrap: true, overflow: TextOverflow.ellipsis)), - DataCell(Text(plan.employeeCode ?? "no data", + DataCell(Text(plan.employeeCode ?? " - ", style: TextStyle( fontSize: 13, fontFamily: "Inter", @@ -742,31 +749,33 @@ class _ApprovalListState extends State { plan.planId); }, ), + IconButton( + icon: const Icon( + Icons.comment, + color: Color(0xFF475569), + size: 11, + ), + onPressed: () { + showDialog( + context: context, + builder: (context) => + CommentModalList( + // planId: plan.planId, + planId: plan + .planId + .toString(), + layoutColorForUser: + layoutColor!, + role: + "Approver"), + ); + }), ], ), ), ), ], ), - IconButton( - icon: const Icon( - Icons.comment, - color: Color(0xFF475569), - size: 11, - ), - onPressed: () { - showDialog( - context: context, - builder: (context) => - CommentModalList( - // planId: plan.planId, - planId: - plan.planId.toString(), - layoutColorForUser: - layoutColor!, - role: "Approver"), - ); - }), ], ), ), @@ -934,31 +943,33 @@ class _ApprovalListState extends State { plan.planId); }, ), + IconButton( + icon: const Icon( + Icons.comment, + color: Color(0xFF475569), + size: 11, + ), + onPressed: () { + showDialog( + context: context, + builder: (context) => + CommentModalList( + // planId: plan.planId, + planId: plan + .planId + .toString(), + layoutColorForUser: + layoutColor!, + role: + "Approver"), + ); + }), ], ), ), ), ], ), - IconButton( - icon: const Icon( - Icons.comment, - color: Color(0xFF475569), - size: 11, - ), - onPressed: () { - showDialog( - context: context, - builder: (context) => - CommentModalList( - // planId: plan.planId, - planId: - plan.planId.toString(), - layoutColorForUser: - layoutColor!, - role: "Approver"), - ); - }), ], ), @@ -1062,11 +1073,36 @@ class _ApprovalListState extends State { children: [ Expanded( child: isDesktop - ? SingleChildScrollView( - scrollDirection: Axis.vertical, - child: table, // <-- your existing table - ) - : buildMobileCardView(paginatedPlans), + ? (searchController.text.isNotEmpty && + filteredPlans.isEmpty + ? Center( + child: Text( + "No matches found", + style: GoogleFonts.poppins( + fontSize: 14, color: Colors.grey), + ), + ) + : SingleChildScrollView( + scrollDirection: Axis.vertical, + child: table, + )) + : (searchController.text.isNotEmpty && + filteredPlans.isEmpty + ? Center( + child: Text( + "No matches found", + style: GoogleFonts.poppins( + fontSize: 14, color: Colors.grey), + ), + ) + : buildMobileCardView(paginatedPlans)), + + // child: isDesktop + // ? SingleChildScrollView( + // scrollDirection: Axis.vertical, + // child: table, // <-- your existing table + // ) + // : buildMobileCardView(paginatedPlans), ), PaginationControls( currentPage: currentPage, diff --git a/lib/Screens/authentication/login/login_widget.dart b/lib/Screens/authentication/login/login_widget.dart index e5d4677..0446170 100644 --- a/lib/Screens/authentication/login/login_widget.dart +++ b/lib/Screens/authentication/login/login_widget.dart @@ -365,7 +365,7 @@ class _LoginWidgetState extends State { ), const SizedBox(height: 2), Text( - "Welcome To TravelSpends", + "Welcome To Trip Approval Tools", style: GoogleFonts.poppins( fontSize: 11, fontWeight: FontWeight.w400, diff --git a/lib/Screens/dialog/user_selection_dialog.dart b/lib/Screens/dialog/user_selection_dialog.dart index 515e5bc..9427b0c 100644 --- a/lib/Screens/dialog/user_selection_dialog.dart +++ b/lib/Screens/dialog/user_selection_dialog.dart @@ -1,4 +1,5 @@ import 'dart:convert'; +import 'package:google_fonts/google_fonts.dart'; import 'package:http/http.dart' as http; import 'package:flutter/material.dart'; import 'package:responsive_builder/responsive_builder.dart'; @@ -95,7 +96,7 @@ class _UserSelectionDialogState extends State { print("Users fetched: ${_users.length}"); for (var user in _users) { - print("${user.firstName} ${user.lastName}"); + print("${user.firstName} ${user.lastName} ${user.empCode}"); } } else { throw Exception( @@ -145,7 +146,8 @@ class _UserSelectionDialogState extends State { print("Users fetched: ${_users.length}"); for (var travvelr in _traveller) { - print("${travvelr.firstName} ${travvelr.lastName}"); + print( + "${travvelr.firstName} ${travvelr.lastName} ${travvelr.mobileNo}"); } } else { throw Exception( @@ -199,6 +201,8 @@ class _UserSelectionDialogState extends State { } else { _filteredList = [ ..._users.where((user) { + print("usersLLL : ${user}"); + List searchFields = [ "${user.firstName} ${user.lastName}".toLowerCase(), user.email.toLowerCase() ?? "", @@ -221,6 +225,39 @@ class _UserSelectionDialogState extends State { } } + void _filterTravellers(String query) { + print("Filtering _filterUsersTravellers..."); + setState(() { + _filteredList.clear(); // Reset the list before filtering + + if (query.isEmpty) { + _filteredList = [ + ..._users.map((user) => {"type": "user", "data": user}), + ]; + } else { + _filteredList = [ + ..._traveller.where((traveller) { + List searchFields = [ + "${traveller.firstName} ${traveller.lastName}".toLowerCase(), + traveller.email.toLowerCase() ?? "", + traveller.travellerId.toLowerCase() ?? "", + traveller.mobileNo ?? "", + ]; + return searchFields + .any((field) => field.contains(query.toLowerCase())); + }).map((traveller) => {"type": "traveller", "data": traveller}), + ]; + } + }); + + print("Filtered List:"); + for (var item in _filteredList) { + var user = item["data"]; + print( + "${item["type"].toUpperCase()} - ${user.firstName} ${user.lastName}"); + } + } + void _filterUsersTravellers(String query) { print("Filtering _filterUsersTravellers..."); setState(() { @@ -286,7 +323,11 @@ class _UserSelectionDialogState extends State { mainAxisSize: MainAxisSize.min, // Ensures content doesn't expand unnecessarily children: [ - Text("Please Select User", style: TextStyle(fontSize: 14)), + widget.title == "Others" + ? Text("Please Select Other User", + style: GoogleFonts.poppins(fontSize: 14)) + : Text("Please Select Other Employee", + style: GoogleFonts.poppins(fontSize: 14)), SizedBox(height: 10), // Search Field @@ -297,22 +338,24 @@ class _UserSelectionDialogState extends State { _showTravellerForm = false; }); widget.title == "Others" - ? _filterUsersTravellers(query) + ? _filterTravellers(query) : _filterUsers(query); }, + style: GoogleFonts.poppins(fontSize: 12), decoration: InputDecoration( hintText: "Search for a user", - hintStyle: TextStyle(fontSize: 14), + hintStyle: + GoogleFonts.poppins(fontSize: 14, color: Colors.grey), prefixIcon: Icon(Icons.search), border: OutlineInputBorder(borderRadius: BorderRadius.circular(8)), enabledBorder: OutlineInputBorder( - borderSide: BorderSide(color: Colors.grey, width: 1), + borderSide: BorderSide(color: Colors.grey.shade200, width: 1), // borderSide: BorderSide(color: Color(0xFFF5F5F5), width: 2), ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(8), - borderSide: BorderSide(color: Colors.blueAccent, width: 2), + borderSide: BorderSide(color: Colors.grey.shade200, width: 1), ), ), ), @@ -324,7 +367,8 @@ class _UserSelectionDialogState extends State { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text("or create a new traveler", - style: TextStyle(fontSize: 14, color: Color(0xFF575A74))), + style: GoogleFonts.poppins( + fontSize: 14, color: Color(0xFF575A74))), TextButton( onPressed: () { setState(() { @@ -333,7 +377,7 @@ class _UserSelectionDialogState extends State { }); }, child: Text("Create", - style: TextStyle( + style: GoogleFonts.poppins( fontSize: 14, color: widget.layoutColorForUser)), ), ], @@ -351,8 +395,8 @@ class _UserSelectionDialogState extends State { ? Center( child: Text( "No users found", - style: - TextStyle(fontSize: 14, color: Colors.grey), + style: GoogleFonts.poppins( + fontSize: 14, color: Colors.grey), ), ) : ListView.builder( @@ -365,12 +409,30 @@ class _UserSelectionDialogState extends State { final user = item["data"]; // Extract user object final userType = item["type"]; // "user" or "traveller" - + if (user is Map) { + print( + "userLsirer - ${jsonEncode(user)}"); // pretty JSON-like string + } else { + print("userLsirer - $user"); // fallback + } return ListTile( title: Text( - "${user.firstName ?? "Unknown"} ${user.lastName ?? ""}"), - // subtitle: Text( - // "ID: ${userType == "user" ? user.userId : user.travellerId}"), + "${user.firstName ?? "Unknown"} ${user.lastName ?? ""}", + style: GoogleFonts.poppins(fontSize: 11), + ), + subtitle: userType == "user" + ? Text( + "Employee ID: ${user.empCode ?? ""} ", + // "Employee ID: ${userType == "user" ? user.userId : user.travellerId}", + style: + GoogleFonts.poppins(fontSize: 10), + ) + : Text( + "Mobile : ${user.mobileNo ?? ""} ", + // "Employee ID: ${userType == "user" ? user.userId : user.travellerId}", + style: + GoogleFonts.poppins(fontSize: 10), + ), onTap: () { String selectedUser = "${user.firstName ?? "Unknown"} ${user.lastName ?? ""}"; @@ -395,7 +457,7 @@ class _UserSelectionDialogState extends State { if (_showTravellerForm) Container( height: 300, // Set a defined height - color: Color(0xFFF4F4FB), + // color: Color(0xFFF4F4FB), child: Padding( padding: const EdgeInsets.all(16.0), child: TravelerForm( @@ -435,6 +497,7 @@ class _UserSelectionDialogState extends State { }, child: Text( "Cancel", + style: GoogleFonts.poppins(fontSize: 11), ), ), SizedBox(width: 10), @@ -456,7 +519,10 @@ class _UserSelectionDialogState extends State { _searchController.text, userIdSelected, isTraveller); Navigator.pop(context); }, - child: Text("Submit"), + child: Text( + "Submit", + style: GoogleFonts.poppins(fontSize: 11), + ), ), ], ), @@ -563,33 +629,37 @@ class _TravelerFormState extends State { ); if (response.statusCode == 200 || response.statusCode == 201) { - if (response.statusCode == 200 || response.statusCode == 201) { - final Map responseData = - jsonDecode(response.body); // Parse response - if (responseData["success"] == true && - responseData.containsKey("data")) { - final travellerData = responseData["data"]; + final Map responseData = jsonDecode(response.body); + if (responseData.containsKey("data")) { + // Parse response - String travellerId = travellerData["traveller_id"]; - String firstName = travellerData["first_name"]; - String lastName = travellerData["last_name"]; + print("responseData- $responseData "); - print( - "Traveller Added: ID: $travellerId, Name: $firstName $lastName"); + final travellerData = responseData["data"]; - // Pass data to callback - widget.onSubmit("$firstName $lastName", travellerId, true); + String travellerId = travellerData["traveller_id"]; + String firstName = travellerData["first_name"]; + String lastName = travellerData["last_name"]; - // Close the dialog - Navigator.pop(context); - } + print( + "Traveller Added: ID: $travellerId, Name: $firstName $lastName"); + + // // Pass data to callback + // widget.onSubmit("$firstName $lastName", travellerId, true); + + String fullName = "${firstName.trim()} ${lastName.trim()}"; + widget.onSubmit(fullName, travellerId, true); + + // Close the dialog + Navigator.pop(context); } ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text( "Traveller added successfully!", - style: TextStyle(color: Colors.white), // ✅ Set text color + style: + GoogleFonts.poppins(color: Colors.white), // ✅ Set text color ), backgroundColor: Colors.green, ), @@ -627,7 +697,8 @@ class _TravelerFormState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.center, children: [ - Text("Create Traveler", style: TextStyle(color: Colors.black54)), + Text("Create Traveler", + style: GoogleFonts.poppins(color: Colors.black54)), SizedBox(height: 7), Expanded( child: SingleChildScrollView( @@ -643,12 +714,13 @@ class _TravelerFormState extends State { onPressed: () { widget.formKey.currentState?.reset(); }, - child: Text("Clear", style: TextStyle(color: Colors.grey)), + child: Text("Clear", + style: GoogleFonts.poppins(color: Colors.grey)), ), TextButton( onPressed: () => _onSubmit(context), - child: - Text("Add", style: TextStyle(color: Colors.blueAccent)), + child: Text("Add", + style: GoogleFonts.poppins(color: Color(0xFF114D8B))), ), ], ), diff --git a/lib/Screens/forex/forexDetails.dart b/lib/Screens/forex/forexDetails.dart new file mode 100644 index 0000000..ddb6e09 --- /dev/null +++ b/lib/Screens/forex/forexDetails.dart @@ -0,0 +1,262 @@ +import 'package:dropdown_search/dropdown_search.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:google_fonts/google_fonts.dart'; + +import '../../widgets/custom_text_forex.dart'; + +class ForexData extends StatefulWidget { + final bool isDesktop; + final Color? layoutColor; + const ForexData({super.key, required this.isDesktop, this.layoutColor}); + + @override + ForexDataState createState() => ForexDataState(); +} + +class ForexDataState extends State { + List countryList = []; + String? selectedCountry; + String? selectedCurrency; + String? selectedDuration; + String? selectedPerdiemAmount; + + @override + Widget build(BuildContext context) { + late Map countryMap; // Mapping country_code -> country_name + late List countryCodes; // List of country codes + + countryList = []; + // countryList = widget.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 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( + 'Create Forex Details', + style: GoogleFonts.poppins(fontSize: 18, color: Colors.black), + ), + const Spacer(), + ], + ), + const SizedBox(height: 10), + + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Country", + style: GoogleFonts.poppins( + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74)), + ), + SizedBox(height: 5), + CustomTextFieldForexWrapper( + isFocused: false, + isDesktop: widget.isDesktop, + child: SizedBox( + height: 40, + child: DropdownSearch( + selectedItem: countryMap[selectedCountry], + popupProps: PopupProps.menu( + showSearchBox: true, // Enables search functionality + searchFieldProps: TextFieldProps( + decoration: InputDecoration( + hintText: "Search Country...", + contentPadding: EdgeInsets.symmetric(horizontal: 10), + ), + ), + ), + items: countryMap.values.toList(), + dropdownDecoratorProps: DropDownDecoratorProps( + dropdownSearchDecoration: InputDecoration( + border: InputBorder.none, + contentPadding: EdgeInsets.symmetric( + horizontal: 1, + ), + ), + ), + dropdownBuilder: (context, selectedItem) => Align( + // Center-align selected item + alignment: Alignment.centerLeft, + child: Text( + selectedItem ?? "Select Country", + style: TextStyle(fontSize: 12), + ), + ), + onChanged: (String? newValue) { + setState(() { + // Find the country_code based on selected country_name + selectedCountry = countryMap.entries + .firstWhere((entry) => entry.value == newValue) + .key; + }); + }, + ), + ), + ), + // if (errorMessages["country_code"] != null) ...[ + // SizedBox(height: 5), // Space before error message + // Text( + // "Select Country", + // style: TextStyle(color: Colors.red, fontSize: 12), + // ), + // ], + ], + ), + const SizedBox(height: 10), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Currency *", + style: GoogleFonts.poppins( + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74)), + ), + SizedBox(height: 5), + CustomTextFieldForexWrapper( + 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: Padding( + padding: const EdgeInsets.all(8.0), + child: Center( + child: Text( + // "cur", + // "${selectedCurrency}", + selectedCurrency ?? "Currency", + // selectedCurrency?.isNotEmpty == true ? selectedCurrency! : "Currency", + style: const TextStyle( + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74)), + ), + ), + ), + ), + ), + ], + ), + // if (isDesktop)SizedBox(width: 8,) else SizedBox(height: 8,), + + SizedBox( + height: 10, + ), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Perdiem Amount", + style: GoogleFonts.poppins( + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74)), + ), + SizedBox(height: 5), + CustomTextFieldForexWrapper( + isFocused: false, + isDesktop: widget.isDesktop, + color: Colors.transparent, + child: SizedBox( + height: 35, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + // "amo", + selectedPerdiemAmount ?? "Amount", + // selectedPerdiemAmount?.isNotEmpty == true ? selectedPerdiemAmount! : "Amount", + style: const TextStyle( + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74)), + // decoration: const InputDecoration( + // labelText: "To", + // labelStyle: TextStyle(fontSize: 12, color: Colors.grey), + // floatingLabelBehavior: FloatingLabelBehavior.never, + // border: InputBorder.none, + // contentPadding: EdgeInsets.symmetric(vertical: 16), + // ), + ), + ), + ), + ), + ], + ), + SizedBox( + height: 15, + ), + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + // SizedBox( + // child: ElevatedButton( + // onPressed: () { + // // You can get text from commentController.text + // Navigator.of(context).pop(); // Close the modal + // }, + // style: ElevatedButton.styleFrom( + // backgroundColor: widget.layoutColor, + // shape: RoundedRectangleBorder( + // borderRadius: BorderRadius.circular(8), + // ), + // ), + // child: Text('Cancel', + // style: GoogleFonts.poppins( + // fontSize: 13, color: Colors.white)), + // ), + // ), + // SizedBox( + // width: 10, + // ), + SizedBox( + child: ElevatedButton( + onPressed: () { + // You can get text from commentController.text + Navigator.of(context).pop(); // Close the modal + }, + style: ElevatedButton.styleFrom( + backgroundColor: widget.layoutColor, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + ), + child: Text('Submit', + style: GoogleFonts.poppins( + fontSize: 11, color: Colors.white)), + ), + ), + ], + ) + // : SizedBox.shrink(), + ], + ), + ); + } +} diff --git a/lib/Screens/forex/forex_list.dart b/lib/Screens/forex/forex_list.dart new file mode 100644 index 0000000..a4cc19d --- /dev/null +++ b/lib/Screens/forex/forex_list.dart @@ -0,0 +1,1062 @@ +import 'dart:convert'; + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:http/http.dart' as http; +import 'package:responsive_builder/responsive_builder.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +import '../../config/apiUrl.dart'; +import '../../routes/custom_appBar.dart'; +import '../../routes/custom_drawer.dart'; +import '../../services/apiService.dart'; +import '../../utils/auth_utils.dart'; +import '../../utils/pagination.dart'; +import '../../widgets/popup_userList_action.dart'; +import 'forexDetails.dart'; + +class ForexDataList extends StatefulWidget { + const ForexDataList({super.key}); + + @override + ForexDataListState createState() => ForexDataListState(); +} + +class ForexDataListState extends State { + final ApiService apiService = ApiService(); + late Future> futureForex; + + late Map userSingleData; + List? apiCountryData; + String? selectedUserId; + String? orgId; + + Color? layoutColor; + Color? bodyColor; + + List allForex = []; + List filteredForex = []; + TextEditingController searchController = TextEditingController(); + + int currentPage = 0; + int itemsPerPage = 10; + + @override + void initState() { + super.initState(); + futureForex = fetchGetForex(); + + futureForex.then((users) { + setState(() { + allForex = users; + }); + }); + + WidgetsBinding.instance.addPostFrameCallback((_) { + fetchCountryList(); + loadInitialData(); + }); + + // futurePlans = fetchPlans(); + } + + 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 getToken() async { + final prefs = await SharedPreferences.getInstance(); + return prefs.getString('auth_token'); + } + + Future> fetchGetForex() async { + orgId = await getOrgId(); + final String apiUrlData = '$apiUrl/api/getForexPerdiemList'; + + final String? token = await getToken(); + + print("Fetch Users"); + print("TOEKRWE: $token"); + + if (token == null) { + throw Exception('Token not found. Please log in.'); + } + + final response = await http.get( + Uri.parse(apiUrlData), + headers: { + 'Authorization': 'Bearer $token', + 'Content-Type': 'application/json', + }, + ); + + if (response.statusCode == 200) { + final data = json.decode(response.body); + return data['data']; // Returning raw JSON list + } else { + throw Exception('Failed to load users'); + } + } + + Future fetchCountryList() async { + final String apiUrldata = '$apiUrl/api/getcountryMaster'; + + final token = await getToken(); + + if (token == null) { + throw Exception('Token not found. Please log in.'); + } + + final response = await http.get( + Uri.parse(apiUrldata), + headers: { + 'Authorization': 'Bearer $token', + 'Content-Type': 'application/json', + }, + ); + + if (response.statusCode == 200) { + try { + final data = json.decode(response.body); + print("Country - $data"); + + if (!data.containsKey('data') || data['data'] is! List) { + throw Exception( + "Invalid response format: 'data' field is missing or not a List"); + } + + List plansJson = data['data']; // 'data' is a Map, not a List + + if (data['data'] is List) { + List plansJson = data['data']; + print("plansJson.length - ${plansJson.length}"); + } else { + print("The 'data' key does not contain a list."); + } + + setState(() { + apiCountryData = plansJson; // Store API response in state + }); + print('plansJSONContry - $plansJson'); + } catch (e) { + throw Exception('Error parsing response: $e'); + } + } else { + throw Exception('Failed to load plans'); + } + } + + void handleDelete(userId) { + print("handDel - $userId"); + } + + Future createUserData( + Map userData, String userId, String newStatus) async { + final uri = Uri.parse('$apiUrl/api/users/update/$userId'); + + final String? token = await getToken(); + 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 + + request.fields['_method'] = 'PUT'; + request.fields['user_id'] = userId; + + print("STatus 2 - $newStatus"); + + // 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(); + } + }); + + request.fields['is_active'] = newStatus; + + 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 Status submitted successfully! "); + print("📨 Response: ${response.body}"); + + refreshUserList(); + } else { + print("❌ Submission failed. Status: ${response.statusCode}"); + print("📨 Body: ${response.body}"); + } + } catch (e) { + print("🔥 Error submitting user: $e"); + } + } + + void handleToggleUserStatus(String userId, String currentStatus, + Map userData) async { + print("Toggling user status - $userId (Current: $currentStatus)"); + + final String apiUrlData = + '$apiUrl/api/users/update/$userId'; // API for updating user + final String? token = await getToken(); + + if (token == null) { + print("Error: Token not found"); + return; + } + + // Toggle status: If active ("1"), set to inactive ("0"); otherwise, activate ("1") + String newStatus = (currentStatus == "1") ? "0" : "1"; + + print("STatus 1 - $newStatus"); + + createUserData(userData, userId, newStatus); + + // try { + // final response = await http.put( + // Uri.parse(apiUrlData), + // headers: { + // 'Authorization': 'Bearer $token', + // 'Content-Type': 'application/json', + // }, + // body: jsonEncode({ + // "is_active": newStatus // Set new status dynamically + // }), + // ); + // + // if (response.statusCode == 200) { + // print("User status updated successfully to $newStatus!"); + // refreshUserList(); // Refresh users list after update + // } else { + // print("Failed to update user status. Status: ${response.statusCode}"); + // print("Error: ${response.body}"); + // } + // } catch (e) { + // print("Error updating user status: $e"); + // } + } + +// Refresh user list after update + void refreshUserList() { + setState(() { + futureForex = fetchGetForex(); // Re-fetch users after status update + // Wait for futurePlans to be fetched and update allPlans + }); + } + + void filterUsers(String query) { + print("allUsers before filtering: $query"); + final lowerQuery = query.toLowerCase(); + setState(() { + filteredForex = allForex.where((user) { + return (user['first_name']?.toLowerCase().contains(lowerQuery) ?? + false) || + (user['last_name']?.toLowerCase().contains(lowerQuery) ?? false) || + (user['email']?.toLowerCase().contains(lowerQuery) ?? false) || + (user['role_value']?.toLowerCase().contains(lowerQuery) ?? false); + }).toList(); + }); + print("filteredPlans: $filteredForex"); + } + + @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: 'User Management'), + // 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 Page Content"))), + Expanded(child: buildGroupList(isDesktop)), + ], + ), + ), + ); + }); + } + + Widget buildGroupList(bool isDesktop) { + return Container( + margin: isDesktop ? const EdgeInsets.only(top: 10.0, bottom: 10.0) : null, + padding: const EdgeInsets.all(1), + decoration: BoxDecoration( + color: isDesktop ? Colors.white : Color(0xFFFCFCFC), + ), + // decoration: BoxDecoration( + // // color: Colors.amber, + // // color: bodyColor, + // color: Color(0xFFE1F5FE), + // border: Border.all( + // color: Colors.white, + // // color: Color(0xFFF7F7FB), + // width: 3.5)), + child: buildUserTable(isDesktop), + ); + } + + Widget buildUserTable(bool isDesktop) { + return 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, + + child: Padding( + padding: const EdgeInsets.all(10.0), + child: Container( + color: Colors.white, + padding: const EdgeInsets.all(10.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + // Divider( + // thickness: 0.2, // how "thick" the line is + // color: Colors.grey, // optional + // ), + Row( + // mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + Text( + 'Forex Details', + style: GoogleFonts.poppins( + fontSize: isDesktop ? 16 : 14, + fontWeight: FontWeight.w600, + color: Colors.black, + ), + ), + ], + ), + if (isDesktop) + SizedBox( + width: MediaQuery.of(context).size.width * 0.22, + ), + + if (isDesktop) + Container( + width: MediaQuery.of(context).size.width * 0.2, + height: 40, + child: TextField( + controller: searchController, + onChanged: filterUsers, + decoration: InputDecoration( + hintText: "Search ...", + hintStyle: TextStyle( + fontSize: 12, color: Color(0xFF9E9DBD)), + prefixIcon: Icon( + Icons.search, + color: Color(0xFF9E9DBD), + size: 18, + ), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + ), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: BorderSide( + color: Colors.grey.shade200, width: 0.5), + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: BorderSide( + color: Colors.grey.shade300, width: 1), + ), + ), + style: GoogleFonts.poppins( + fontSize: 12, + ), + ), + ), + // SizedBox(width: 16), + Spacer(), + + ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: Color(0xFF114D8B), + foregroundColor: Colors.white, + disabledBackgroundColor: Color(0xFF114D8B), + disabledForegroundColor: Colors.white, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + side: + BorderSide(color: Color(0xFF114D8B), width: 2), + ), + padding: EdgeInsets.symmetric( + horizontal: 20, vertical: 12), + ), + onPressed: () async { + showDialog( + context: context, + builder: (context) => ForexData( + isDesktop: isDesktop, + // planId: plan.planId, + // planId: plan + // .planId + // .toString(), + layoutColor: layoutColor!, + // role: + // "Travel Agent" + ), + ); + }, + child: Row( + mainAxisSize: + MainAxisSize.min, // Ensures content fits nicely + children: [ + Text( + "Add Forex", + style: GoogleFonts.poppins( + fontSize: isDesktop ? 13 : 11, + ), + ), + SizedBox(width: 8), // spacing between icon and text + Icon( + Icons.add_circle_outline_rounded, + size: 15, + color: Colors.white, + ), + ], + ), + ), + ], + ), + + if (!isDesktop) + SizedBox( + height: 5, + ), + isDesktop + ? SizedBox.shrink() + : Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Container( + width: MediaQuery.of(context).size.width * 0.8, + height: 35, + child: TextField( + controller: searchController, + onChanged: filterUsers, + decoration: InputDecoration( + hintText: "Search ...", + hintStyle: TextStyle( + fontSize: 12, color: Color(0xFF9E9DBD)), + prefixIcon: Icon( + Icons.search, + color: Color(0xFF9E9DBD), + size: 18, + ), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + ), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: BorderSide( + color: Colors.grey.shade200, + width: 0.5), + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: BorderSide( + color: Colors.grey.shade300, width: 1), + ), + ), + style: GoogleFonts.poppins( + fontSize: 12, + ), + ), + ), + // SizedBox(width: 16), + ], + ), + const SizedBox(height: 10), + FutureBuilder>( + future: futureForex, + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + return const Center(child: CircularProgressIndicator()); + } else if (snapshot.hasError || + !snapshot.hasData || + snapshot.data!.isEmpty) { + return Center( + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + // const Icon(Icons.error_outline, + // color: Colors.redAccent, size: 60), + // const SizedBox(height: 16), + // Text( + // "Oops!", + // style: GoogleFonts.poppins( + // fontSize: 20, + // fontWeight: FontWeight.bold, + // color: Colors.redAccent), + // ), + const SizedBox(height: 8), + Text( + "No Plans Available For This User", + textAlign: TextAlign.center, + style: GoogleFonts.poppins( + fontSize: 20, + fontWeight: FontWeight.bold, + color: Colors.grey), + ), + const SizedBox(height: 20), + Text( + "Please Create Plan", + textAlign: TextAlign.center, + style: GoogleFonts.poppins( + fontSize: 16, color: Colors.grey), + ), + const SizedBox(height: 20), + ], + ), + ), + ); + } + + List users = + filteredForex.isNotEmpty ? filteredForex : allForex; + + users.sort((a, b) { + DateTime dateA = DateTime.parse(a['created_on']); + DateTime dateB = DateTime.parse(b['created_on']); + + return dateB + .compareTo(dateA); // Descending: newest first + }); + + List paginatedUser = users + .skip(currentPage * itemsPerPage) + .take(itemsPerPage) + .toList(); + + Widget table = LayoutBuilder( + builder: (context, constraints) { + double minWidth = + isDesktop ? constraints.maxWidth : 1300; + + return ConstrainedBox( + constraints: BoxConstraints(minWidth: minWidth), + child: DataTable( + dividerThickness: 0.5, + columnSpacing: isDesktop ? 24.0 : 16.0, + border: TableBorder( + horizontalInside: BorderSide( + width: 0.5, color: Colors.grey.shade200), + ), + columns: [ + DataColumn( + label: Text( + 'Country Code', + style: GoogleFonts.poppins( + fontSize: 13, + fontWeight: FontWeight.w600), + )), + DataColumn( + label: Text( + 'Country', + style: GoogleFonts.poppins( + fontSize: 13, + fontWeight: FontWeight.w600), + )), + DataColumn( + label: Text( + 'Currency', + style: GoogleFonts.poppins( + fontSize: 13, + fontWeight: FontWeight.w600), + )), + DataColumn( + label: Text( + 'Perdiem Amount', + style: GoogleFonts.poppins( + fontSize: 13, + fontWeight: FontWeight.w600), + )), + DataColumn( + label: Text( + 'Actions', + style: GoogleFonts.poppins( + fontSize: 13, + fontWeight: FontWeight.w600), + )), + ], + rows: paginatedUser.map((forex) { + String forexId = forex['forex_perdiem_id'] + .toString(); // Get user ID + bool isSelected = selectedUserId == forexId; + + return DataRow(cells: [ + DataCell( + Text("${forex['country_code'] ?? ''}", + style: TextStyle( + fontSize: 13, + fontFamily: "Inter", + ))), + DataCell(Text(forex['country_name'] ?? '', + style: TextStyle( + fontSize: 13, + fontFamily: "Inter", + ))), + DataCell(Text(forex['currency'] ?? 'N/A', + style: TextStyle( + fontSize: 13, + fontFamily: "Inter", + ), + softWrap: true, + overflow: TextOverflow.ellipsis)), + DataCell( + Text(forex['perdiem_amount'] ?? 'N/A', + style: TextStyle( + fontSize: 13, + fontFamily: "Inter", + ), + softWrap: true, + overflow: TextOverflow.ellipsis)), + DataCell( + UserActionsMenu( + user: forex, + getUserDetails: (id) => + apiService.getSingleUser(id), + ), + // PopupMenuButton( + // color: Colors.white, + // padding: EdgeInsets.zero, + // offset: Offset(0, 30), + // icon: Icon( + // Icons.more_vert, + // color: Color(0xFF475569), + // size: 14, + // ), + // itemBuilder: (context) => [ + // CustomPopupMenuEntry( + // child: Container( + // padding: EdgeInsets.symmetric( + // horizontal: 8, vertical: 8), + // child: Row( + // mainAxisSize: MainAxisSize.min, + // mainAxisAlignment: + // MainAxisAlignment.center, + // children: [ + // IconButton( + // icon: Icon( + // Icons.remove_red_eye, + // color: + // Color(0xFF475569), + // size: 18), + // onPressed: () async { + // print( + // "USerDAta1 - $user"); + // // Fetch the user data properly with await + // Map + // usersData = + // await apiService + // .getSingleUser(user[ + // 'user_id'] + // is String + // ? int.parse(user[ + // 'user_id']) + // : user[ + // 'user_id']); + // + // print( + // "USerDAta2 - $usersData"); + // + // // userSingleData = + // // await apiService + // // .getSingleUser(user[ + // // 'user_id']); + // + // context.go( + // "/CreateUserDetails", + // extra: { + // "selectedUser": + // usersData, + // "isViewMode": true + // }, + // ); + // }), + // IconButton( + // icon: Image.asset( + // 'assets/images/IconsImg/edit.png', + // width: 20, + // height: 15), + // onPressed: () async { + // // Fetch the user data properly with await + // Map + // usersData = + // await apiService + // .getSingleUser(user[ + // 'user_id'] + // is String + // ? int.parse(user[ + // 'user_id']) + // : user[ + // 'user_id']); + // + // print( + // "USerDAta2 - $usersData"); + // context.go( + // "/CreateUserDetails", + // extra: { + // "selectedUser": + // usersData, + // "isViewMode": false + // }, + // ); + // }, + // ), + // ], + // ), + // ), + // ), + // ], + // ), + // Row( + // children: [ + // MouseRegion( + // cursor: user['is_active'] == "0" + // ? SystemMouseCursors.forbidden + // : SystemMouseCursors.click, + // child: IconButton( + // icon: Icon(Icons.remove_red_eye, + // size: 18, + // color: user['is_active'] == "0" + // ? Colors.grey + // : Color(0xFF475569)), + // onPressed: user['is_active'] == "0" + // ? null + // : () { + // context.go( + // "/CreateUserDetails", + // extra: { + // "selectedUser": user, + // "isViewMode": true + // }, + // ); + // }, + // ), + // ), + // + // MouseRegion( + // cursor: user['is_active'] == "0" + // ? SystemMouseCursors.forbidden + // : SystemMouseCursors.click, + // child: GestureDetector( + // onTap: user['is_active'] == "0" + // ? null + // : () { + // + // }, + // child: Image.asset( + // 'assets/images/IconsImg/edit.png', + // width: 20, + // height: 15), + // ), + // ), + // + // // MouseRegion( + // // cursor: user['is_active'] == "0" + // // ? SystemMouseCursors + // // .forbidden + // // : SystemMouseCursors.click, + // // child: IconButton( + // // icon: Icon(Icons.edit, + // // color: + // // user['is_active'] == + // // "0" + // // ? Colors.grey + // // : Colors.green), + // // onPressed: + // // user['is_active'] == "0" + // // ? null + // // : () { + // // print( + // // "USER: $user"); + // // + // // // final userJson = jsonEncode( + // // // user); // Convert user map to string + // // // final encodedUser = + // // // Uri.encodeComponent( + // // // userJson); + // // + // // context.go( + // // "/CreateUserDetails", + // // extra: { + // // "selectedUser": + // // user, + // // "isViewMode": + // // false + // // }, + // // ); + // // }, + // // ), + // // ), + // ], + // ), + ), + ]); + }).toList(), + ), + ); + }, + ); + + Widget buildMobileCardView(List paginatedUser) { + return ListView.builder( + itemCount: paginatedUser.length, + itemBuilder: (context, index) { + final forex = paginatedUser[index]; + return Card( + color: Colors.white, + margin: EdgeInsets.symmetric( + horizontal: 12, vertical: 6), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + elevation: 3, + child: Padding( + padding: const EdgeInsets.all(12.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Status and Employee Code + Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Text( + forex['country_name'] ?? 'N/A', + style: GoogleFonts.poppins( + fontSize: 10, + color: Colors.black87, + fontWeight: FontWeight.w700), + ), + + UserActionsMenu( + user: forex, + getUserDetails: (id) => + apiService.getSingleUser(id), + ), + // PopupMenuButton( + // color: Colors.white, + // padding: EdgeInsets.zero, + // offset: Offset(0, 30), + // icon: Icon( + // Icons.more_vert, + // color: Color(0xFF475569), + // size: 14, + // ), + // itemBuilder: (context) => [ + // CustomPopupMenuEntry( + // child: Container( + // padding: EdgeInsets.symmetric( + // horizontal: 8, vertical: 8), + // child: Row( + // mainAxisSize: + // MainAxisSize.min, + // mainAxisAlignment: + // MainAxisAlignment.center, + // children: [ + // IconButton( + // icon: Icon( + // Icons + // .remove_red_eye, + // color: Color( + // 0xFF475569), + // size: 18), + // onPressed: () { + // print( + // "USerDAta - $user"); + // // dynamic usersData = apiService + // // .getSingleUser(user[ + // // 'user_id'] + // // is String + // // ? int.parse(user[ + // // 'user_id']) + // // : user[ + // // 'user_id']); + // // + // // print( + // // "USerDAta - $usersData"); + // + // context.go( + // "/CreateUserDetails", + // extra: { + // "selectedUser": + // user, + // "isViewMode": true + // }, + // ); + // }), + // IconButton( + // icon: Image.asset( + // 'assets/images/IconsImg/edit.png', + // width: 20, + // height: 15), + // onPressed: () { + // context.go( + // "/CreateUserDetails", + // extra: { + // "selectedUser": + // user, + // "isViewMode": false + // }, + // ); + // }, + // ), + // ], + // ), + // ), + // ), + // ], + // ), + ], + ), + + SizedBox(height: 2), + // Trip Id and Trip Name + Row( + children: [ + Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text( + "${forex['country_code'] ?? ''} ", + style: GoogleFonts.poppins( + fontSize: 12, + color: Colors.black87, + fontWeight: FontWeight.w500), + ), + ], + ), + ], + ), + SizedBox(height: 2), + + // Name + Row( + children: [ + Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text( + forex['currency'] ?? '', + style: GoogleFonts.poppins( + fontSize: 10, + color: Colors.black87), + ), + ], + ), + SizedBox( + width: 10, + ), + Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text( + forex['perdiem_amount'] ?? '', + style: GoogleFonts.poppins( + fontSize: 10, + color: Colors.black87), + ), + ], + ), + ], + ), + // Actions + // Actions + ], + ), + ), + ); + }, + ); + } + + return Expanded( + child: Column( + // mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: isDesktop + ? SingleChildScrollView( + scrollDirection: Axis.vertical, + child: table, // <-- your existing table + ) + : buildMobileCardView(paginatedUser), + ), + PaginationControls( + currentPage: currentPage, + itemsPerPage: itemsPerPage, + totalItems: users.length, + activeColor: layoutColor, // your theme color + onPageChanged: (page) { + setState(() { + currentPage = page; + }); + }, + onItemsPerPageChanged: (items) { + setState(() { + itemsPerPage = items; + currentPage = 0; + }); + }, + ), + ], + ), + ); + }, + ) + ]), + )), + ); + } +} diff --git a/lib/Screens/itnerary/accomodations.dart b/lib/Screens/itnerary/accomodations.dart index b931c87..b51bea4 100644 --- a/lib/Screens/itnerary/accomodations.dart +++ b/lib/Screens/itnerary/accomodations.dart @@ -158,6 +158,13 @@ class _AccomodationScreenState extends State { _checkInController.text = DateFormat('yyyy-MM-dd').format(parsedDate); } + final parsedEndDate = + DateTime.tryParse(flightLastTripDateNotifier.value ?? ''); + if (parsedEndDate != null) { + _checkOutController.text = + DateFormat('yyyy-MM-dd').format(parsedEndDate); + } + // ✅ Check and set default times if empty if (_checkInTimeController.text.isEmpty) { _checkInTimeController.text = '14:00'; // 2 PM diff --git a/lib/Screens/itnerary/flights.dart b/lib/Screens/itnerary/flights.dart index 5eab88f..faa86c1 100644 --- a/lib/Screens/itnerary/flights.dart +++ b/lib/Screens/itnerary/flights.dart @@ -513,6 +513,40 @@ class FlightScreenState extends State { print("FlightData - $flightsData"); } + void validateTimeDifference(int index) { + if (index <= 1) return; // Skip validation for the first row + + String? prevDateStr = textControllers["_date${index - 1}Controller"]?.text; + String? prevTimeStr = textControllers["_time${index - 1}Controller"]?.text; + String? currDateStr = textControllers["_date${index}Controller"]?.text; + String? currTimeStr = textControllers["_time${index}Controller"]?.text; + + if (prevDateStr != null && + prevDateStr.isNotEmpty && + prevTimeStr != null && + prevTimeStr.isNotEmpty && + currDateStr != null && + currDateStr.isNotEmpty && + currTimeStr != null && + currTimeStr.isNotEmpty) { + try { + final prevDateTime = DateTime.parse("$prevDateStr $prevTimeStr"); + final currDateTime = DateTime.parse("$currDateStr $currTimeStr"); + + if (!currDateTime.isAfter(prevDateTime)) { + errorMessages["time_$index"] = "Must be after previous time"; + } else if (currDateTime.difference(prevDateTime).inMinutes < 30) { + errorMessages["time_$index"] = + "Must be least 30 mins after previous time"; + } else { + errorMessages.remove("time_$index"); + } + } catch (e) { + errorMessages["time_$index"] = "Invalid time format"; + } + } + } + bool validateFields() { errorMessages.clear(); // Reset errors @@ -1124,7 +1158,8 @@ class FlightScreenState extends State { } } - Future _selectCheckOutTime(BuildContext context) async { + Future _selectCheckOutTime( + BuildContext context, int index, VoidCallback onPicked) async { TimeOfDay? pickedTime = await showTimePicker( context: context, initialTime: _selectedCheckOutTime ?? TimeOfDay.now(), @@ -1141,6 +1176,8 @@ class FlightScreenState extends State { ); // _timeController.text = formattedTime; textControllers["_time${index}Controller"]?.text = formattedTime; + + onPicked(); }); } } @@ -1485,12 +1522,23 @@ class FlightScreenState extends State { child: SizedBox( height: 40, child: GestureDetector( - onTap: () => _selectCheckOutTime(context), + onTap: () { + _clearError("time_$index"); + // validateTimeDifference(index); + // _selectCheckOutTime(context); + + _selectCheckOutTime(context, index, () { + validateTimeDifference(index); + setState( + () {}); // ✅ Force rebuild to show the error immediately + }); + }, child: AbsorbPointer( child: TextField( focusNode: focusNodes["_time${index}FocusNode"], // controller: _timeController, controller: textControllers["_time${index}Controller"], + style: const TextStyle(fontSize: 12), decoration: const InputDecoration( labelText: "Time", @@ -1509,7 +1557,7 @@ class FlightScreenState extends State { if (errorMessages["time_$index"] != null) ...[ SizedBox(height: 5), // Space before error message Text( - "Required", + errorMessages["time_$index"]!, style: TextStyle(color: Colors.red, fontSize: 12), ), ], diff --git a/lib/Screens/itnerary/forex.dart b/lib/Screens/itnerary/forex.dart index c9e67c2..3975a1a 100644 --- a/lib/Screens/itnerary/forex.dart +++ b/lib/Screens/itnerary/forex.dart @@ -492,7 +492,7 @@ class _ForexScreenState extends State { double.tryParse(textControllers["_telephone"]?.text ?? "0") ?? 0; double calclateVal = (transport + accommodation + telephone); - CalculatedOtherExpenses = (calclateVal).toStringAsFixed(2); + CalculatedOtherExpenses = (calclateVal).toStringAsFixed(0); // Convert selectedPerdiemAmount to double before performing the addition double perdiemAmount = double.tryParse(selectedPerdiemAmount ?? "0") ?? 0; diff --git a/lib/Screens/myTemplates/template.dart b/lib/Screens/myTemplates/template.dart new file mode 100644 index 0000000..e69de29 diff --git a/lib/Screens/plans/create_plans.dart b/lib/Screens/plans/create_plans.dart index 77c737c..2baac1c 100644 --- a/lib/Screens/plans/create_plans.dart +++ b/lib/Screens/plans/create_plans.dart @@ -375,6 +375,8 @@ class CreateNewPlansState extends State { late bool isApproverApproved = false; late bool isApproverRejected = false; + String? temporaryMessage; + //Getter Method Map get planData => { "org_id": orgId, @@ -474,6 +476,31 @@ class CreateNewPlansState extends State { super.dispose(); } + void handleSelectedUser() { + if (widget.selectedPlanData != null) { + setState(() { + String userId = widget.selectedPlanData['user_id'] ?? ''; + String userName = widget.selectedPlanData['user_name'] ?? ''; + String travellerId = widget.selectedPlanData['traveller_id'] ?? ''; + String travellerName = widget.selectedPlanData['traveller_name'] ?? ''; + + print("TestplanUsrId $userId --- $travellerId"); + + if (travellerId.isNotEmpty && travellerId != "0") { + print("TestplanUsrId1: $travellerId"); + planTravlrId = travellerId; + _selectedOption = "Option 3"; + otherUserName = travellerName; + } else if (userId.isNotEmpty && userId != "0") { + print("TestplanUsrId : $userId"); + print("TestplanUsrIdSelf : $selfId"); + _selectedOption = (selfId != userId) ? "Option 2" : "Option 1"; + otherUserName = userName; + } + }); + } + } + void handleUpdateData() { if (widget.selectedPlanData != null) { setState(() { @@ -641,10 +668,11 @@ class CreateNewPlansState extends State { } void getSelectedPlanFor() { - if (!mounted) return; - + // if (!mounted) return; + print("getSelectedPlanFor"); setState(() { if (selectedplanUserId != null) { + print("Is Not USER ID - $planUsrId "); if (selectedIstravelUser!) { planUsrId = ""; planTravlrId = selectedplanUserId; @@ -653,8 +681,10 @@ class CreateNewPlansState extends State { planTravlrId = ""; } } else { + print("Is USER ID - $planUsrId "); planUsrId = selfId; planTravlrId = ""; + _selectedOption = "Option 1"; } }); @@ -676,8 +706,12 @@ class CreateNewPlansState extends State { } orgId = await getOrgId(); print("userDetails - $selfId"); + // handleSelectedUser(); getSelectedPlanFor(); setTripPlanAction(); + + handleSelectedUser(); + // handleUpdateData(); } Future getToken() async { @@ -958,7 +992,20 @@ class CreateNewPlansState extends State { bool anyServiceSelected = serviceLists.any((list) => list != null && list.isNotEmpty); if (!anyServiceSelected) { - validationErrors["services"] = "Please select at least one service"; + // validationErrors["services"] = "Please select at least one service"; + + setState(() { + temporaryMessage = "Please select at least one service"; + }); + + // Clear message after 3 seconds + Future.delayed(Duration(seconds: 3), () { + if (mounted) { + setState(() { + temporaryMessage = null; + }); + } + }); } return validationErrors.isEmpty; // Returns true if no errors @@ -1279,52 +1326,53 @@ class CreateNewPlansState extends State { color: Color(0xFF575A74)), ), SizedBox(height: 6), - isMobile + isDesktop ? SingleChildScrollView( scrollDirection: Axis.horizontal, child: Row( - children: _buildPlanTrip(isMobile), + children: _buildPlanTrip(isDesktop), ), ) : Row( - children: _buildPlanTrip(isMobile), + children: _buildPlanTrip(isDesktop), ), SizedBox(height: 7), - Text( - otherUserName ?? userName ?? " ", // Your label - - style: GoogleFonts.poppins( - fontSize: 12, - fontWeight: FontWeight.w400, - color: widget.layoutColor, - ), - ) - // Text.rich( - // TextSpan( - // text: "Trip Planned For : ", // Static text - // style: TextStyle( - // fontFamily: "Archivo", - // fontSize: 13, - // fontWeight: FontWeight.w600, - // color: Color(0xFF212121), - // // color: Color(0xFF575A74), // Default color - // ), - // children: [ - // TextSpan( - // text: otherUserName ?? - // userName ?? - // " ", // Dynamic username - // style: TextStyle( - // fontSize: 12, - // fontWeight: FontWeight.w400, - // color: widget - // .layoutColor, // Change this to any color - // // color: Colors.blueAccent, // Change this to any color - // ), - // ), - // ], + // Text( + // otherUserName ?? userName ?? " ", // Your label + // + // style: GoogleFonts.poppins( + // fontSize: 11, + // fontWeight: FontWeight.w400, + // color: widget.layoutColor, // ), // ), + + Text.rich( + TextSpan( + text: "Trip Planned User : ", // Static text + style: GoogleFonts.poppins( + fontSize: 11, + fontWeight: FontWeight.w400, + color: Color(0xFF212121), + // color: Color(0xFF575A74), // Default color + ), + children: [ + TextSpan( + text: otherUserName ?? + userName ?? + " ", // Dynamic username + style: GoogleFonts.poppins( + fontSize: 11, + fontWeight: FontWeight.w400, + color: widget + .layoutColor, // Change this to any color + // color: Colors.blueAccent, // Change this to any color + ), + ), + ], + ), + ), + SizedBox(height: 5), ], ), ), @@ -1398,12 +1446,13 @@ class CreateNewPlansState extends State { height: 20, ), - if (validationErrors["services"] != null) + if (temporaryMessage != null) Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Text( - validationErrors["services"]!, + // validationErrors["services"]!, + temporaryMessage!, style: GoogleFonts.poppins( color: Colors.red, fontSize: 10, @@ -1412,6 +1461,10 @@ class CreateNewPlansState extends State { ], ), + if (temporaryMessage != null) + SizedBox( + height: 10, + ), Row( children: [ Expanded( @@ -1483,6 +1536,7 @@ class CreateNewPlansState extends State { color: Color(0xFF575A74)), ), SizedBox(height: 5), + CustomTextFieldWrapper( isFocused: false, isDesktop: widget.isDesktop, @@ -1513,8 +1567,9 @@ class CreateNewPlansState extends State { dropdownDecoratorProps: const DropDownDecoratorProps( dropdownSearchDecoration: InputDecoration( border: InputBorder.none, // No underline + contentPadding: - EdgeInsets.symmetric(horizontal: 1, vertical: 5), + EdgeInsets.symmetric(horizontal: 10, vertical: 5), ), ), dropdownBuilder: (context, selectedItem) { @@ -2205,6 +2260,77 @@ class CreateNewPlansState extends State { {"title": "Others", "value": "Option 3"}, ]; + return [ + CustomTextFieldWrapper( + isFocused: false, + isDesktop: isDesktop, + layoutColor: widget.layoutColor, + child: SizedBox( + height: 35, + width: double.infinity, + child: DropdownSearch( + selectedItem: options + .firstWhere((opt) => opt['value'] == _selectedOption)['title'], + enabled: !widget.isViewMode, + items: options.map((opt) => opt['title']!).toList(), + popupProps: PopupProps.menu( + showSearchBox: false, + menuProps: const MenuProps( + backgroundColor: Colors.white, + ), + constraints: BoxConstraints(maxHeight: 100), + itemBuilder: (context, item, isSelected) => Padding( + padding: + const EdgeInsets.symmetric(horizontal: 8.0, vertical: 6.0), + child: Text( + item, + style: GoogleFonts.poppins(fontSize: 13), + ), + ), + ), + dropdownDecoratorProps: DropDownDecoratorProps( + dropdownSearchDecoration: InputDecoration( + border: InputBorder.none, + contentPadding: + const EdgeInsets.symmetric(horizontal: 10, vertical: 5), + ), + ), + dropdownBuilder: (context, selectedItem) { + return Text( + selectedItem ?? "Select Purpose", + style: GoogleFonts.poppins( + fontSize: 12, + color: selectedItem == null ? Colors.grey : Colors.black, + ), + ); + }, + onChanged: (String? newTitle) { + if (newTitle == null) return; + final selected = + options.firstWhere((opt) => opt["title"] == newTitle); + setState(() { + _selectedOption = selected["value"]!; + if (_selectedOption == "Option 2" || + _selectedOption == "Option 3") { + _showInputDialog(selected["title"]!); + } else if (_selectedOption == "Option 1") { + otherUserName = userName; + } + }); + }, + ), + ), + ) + ]; + } + + List _buildPlanTrip1(bool isDesktop) { + List> options = [ + {"title": "Self", "value": "Option 1"}, + {"title": "Other Employee", "value": "Option 2"}, + {"title": "Others", "value": "Option 3"}, + ]; + print(" layoutColor: ${widget.layoutColor}"); return options.map((option) { return Padding( @@ -2502,7 +2628,8 @@ class CreateNewPlansState extends State { // ), // ), CustomTextFieldWrapper( - isFocused: _isdescriptionFocused, + isFocused: false, + // isFocused: _isdescriptionFocused, // width: isDesktop // ? MediaQuery.of(context).size.width * 0.38 // : MediaQuery.of(context).size.width * 0.85, @@ -2604,7 +2731,7 @@ class CreateNewPlansState extends State { decoration: InputDecoration( labelText: "Trip Name *", - labelStyle: GoogleFonts.poppins(fontSize: 14, color: Colors.grey), + labelStyle: GoogleFonts.poppins(fontSize: 14, color: Colors.black54), floatingLabelBehavior: FloatingLabelBehavior.never, border: InputBorder.none, contentPadding: EdgeInsets.symmetric(vertical: 16), @@ -3108,7 +3235,8 @@ class CreateNewPlansState extends State { getSelectedPlanFor(); }, onClose: () { - getSelectedPlanFor(); + print("Choosede Clsoes"); + fetchUserDetails(); }, layoutColorForUser: widget.layoutColor!, ); diff --git a/lib/Screens/plans/list_plans.dart b/lib/Screens/plans/list_plans.dart index aaf309f..38bd712 100644 --- a/lib/Screens/plans/list_plans.dart +++ b/lib/Screens/plans/list_plans.dart @@ -48,11 +48,24 @@ class _ListPlansState extends State { @override void initState() { super.initState(); + + super.initState(); + // futurePlans = fetchPlans(); + // + // futurePlans.then((plans) { + // setState(() { + // allPlans = plans; + // filteredPlans = plans; + // }); + // }); + getToken(); + initializeData(); + loadInitialData(); WidgetsBinding.instance.addPostFrameCallback((_) { - initializeData(); - loadInitialData(); + // initializeData(); + // loadInitialData(); // futurePlans.then((plans) { // setState(() { @@ -593,8 +606,11 @@ class _ListPlansState extends State { // List plans = snapshot.data!; + // List plans = + // filteredPlans.isNotEmpty ? filteredPlans : allPlans; + List plans = - filteredPlans.isNotEmpty ? filteredPlans : allPlans; + searchController.text.isEmpty ? allPlans : filteredPlans; plans.sort((a, b) => int.parse(b.planId).compareTo(int.parse(a.planId))); @@ -682,7 +698,7 @@ class _ListPlansState extends State { softWrap: true, overflow: TextOverflow.ellipsis)), - DataCell(Text(plan.employeeCode ?? "no data", + DataCell(Text(plan.employeeCode ?? " - ", style: TextStyle( fontSize: 13, fontFamily: "Inter", @@ -813,31 +829,32 @@ class _ListPlansState extends State { plan.planId); }, ), + IconButton( + icon: const Icon( + Icons.comment, + color: Color(0xFF475569), + size: 11, + ), + onPressed: () { + showDialog( + context: context, + builder: (context) => + CommentModalList( + // planId: plan.planId, + planId: plan + .planId + .toString(), + layoutColorForUser: + layoutColor!, + role: "User"), + ); + }), ], ), ), ), ], ), - IconButton( - icon: const Icon( - Icons.comment, - color: Color(0xFF475569), - size: 11, - ), - onPressed: () { - showDialog( - context: context, - builder: (context) => - CommentModalList( - // planId: plan.planId, - planId: - plan.planId.toString(), - layoutColorForUser: - layoutColor!, - role: "User"), - ); - }), ], ), ), @@ -940,26 +957,8 @@ class _ListPlansState extends State { plan: plan, deletePlan: deletePlan, apiService: apiService, + layoutColor: layoutColor, ), - IconButton( - icon: const Icon( - Icons.comment, - color: Color(0xFF475569), - size: 11, - ), - onPressed: () { - showDialog( - context: context, - builder: (context) => - CommentModalList( - // planId: plan.planId, - planId: - plan.planId.toString(), - layoutColorForUser: - layoutColor!, - role: "User"), - ); - }), ], ), @@ -1063,11 +1062,35 @@ class _ListPlansState extends State { children: [ Expanded( child: isDesktop - ? SingleChildScrollView( - scrollDirection: Axis.vertical, - child: table, // <-- your existing table - ) - : buildMobileCardView(paginatedPlans), + ? (searchController.text.isNotEmpty && + filteredPlans.isEmpty + ? Center( + child: Text( + "No matches found", + style: GoogleFonts.poppins( + fontSize: 14, color: Colors.grey), + ), + ) + : SingleChildScrollView( + scrollDirection: Axis.vertical, + child: table, + )) + : (searchController.text.isNotEmpty && + filteredPlans.isEmpty + ? Center( + child: Text( + "No matches found", + style: GoogleFonts.poppins( + fontSize: 14, color: Colors.grey), + ), + ) + : buildMobileCardView(paginatedPlans)), + // child: isDesktop + // ? SingleChildScrollView( + // scrollDirection: Axis.vertical, + // child: table, // <-- your existing table + // ) + // : buildMobileCardView(paginatedPlans), ), PaginationControls( currentPage: currentPage, diff --git a/lib/Screens/userManagement/create_user/create_user.dart b/lib/Screens/userManagement/create_user/create_user.dart index 4a92562..f79265d 100644 --- a/lib/Screens/userManagement/create_user/create_user.dart +++ b/lib/Screens/userManagement/create_user/create_user.dart @@ -594,7 +594,8 @@ class _CreateUserFormDetialsState extends State { "first_name", "last_name", "email", - "mobile_no" + "mobile_no", + "employeeCode" ]; if (apiselectedUser == null) { @@ -847,7 +848,6 @@ class _CreateUserFormDetialsState extends State { mainAxisAlignment: MainAxisAlignment.end, children: (selectedTab == "travel" || selectedRole == "5" || - isEditProfile || setSelectesUserType == true) ? _buildSubmit(isDesktop, layoutColor!) : _buildNext(isDesktop, layoutColor!), @@ -885,18 +885,18 @@ class _CreateUserFormDetialsState extends State { color: Colors.black, ), ), - if (isEditProfile) - IconButton( - icon: Icon( - Icons.edit, - color: Color(0xFF114D8B), - size: 14, - ), - onPressed: () { - setState(() { - isViewMode = !isViewMode; - }); - }) + // if (isEditProfile) + // IconButton( + // icon: Icon( + // Icons.edit, + // color: Color(0xFF114D8B), + // size: 14, + // ), + // onPressed: () { + // setState(() { + // isViewMode = !isViewMode; + // }); + // }) ], ), SizedBox( @@ -1115,8 +1115,9 @@ class _CreateUserFormDetialsState extends State { ), padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12), ), - onPressed: - isViewMode ? null : handleNext, // Disable when in view mode + onPressed: handleNext, + // onPressed: + // isViewMode ? null : handleNext, // Disable when in view mode child: Text("Next"), ), ) diff --git a/lib/Screens/userManagement/create_user/office_details.dart b/lib/Screens/userManagement/create_user/office_details.dart index 1cd3a01..4d3237b 100644 --- a/lib/Screens/userManagement/create_user/office_details.dart +++ b/lib/Screens/userManagement/create_user/office_details.dart @@ -312,6 +312,9 @@ class _OfficeDetailsState extends State { style: GoogleFonts.poppins(fontSize: 12, color: Colors.black), controller: widget.controllers["employeeCode"], enabled: !widget.isViewMode, + onChanged: (value) { + _clearError("employeeCode"); + }, decoration: InputDecoration( labelText: "Employee Code", labelStyle: @@ -323,6 +326,13 @@ class _OfficeDetailsState extends State { ), ), ), + if (widget.errorMessages["employeeCode"] != null) ...[ + SizedBox(height: 5), // Space before error message + Text( + widget.errorMessages["employeeCode"]!, + style: TextStyle(color: Colors.red, fontSize: 12), + ), + ], ], ); } @@ -343,7 +353,8 @@ class _OfficeDetailsState extends State { child: SizedBox( height: 45, // Set appropriate height child: DropdownButtonFormField( - value: widget.isViewMode ? null : selectedDepartment, + value: selectedDepartment, + // value: widget.isViewMode ? null : selectedDepartment, style: GoogleFonts.poppins(fontSize: 12, color: Colors.black), decoration: InputDecoration( border: InputBorder.none, diff --git a/lib/Screens/userManagement/create_user/personal_details.dart b/lib/Screens/userManagement/create_user/personal_details.dart index 48671b5..dc0bac0 100644 --- a/lib/Screens/userManagement/create_user/personal_details.dart +++ b/lib/Screens/userManagement/create_user/personal_details.dart @@ -280,7 +280,11 @@ class PersonalDetailsState extends State { try { final result = await apiService.fetchAllServices(); setState(() { - apiAllServices = result; + // apiAllServices = result; + setState(() { + apiAllServices = result + ..sort((a, b) => (a['order'] ?? 0).compareTo(b['order'] ?? 0)); + }); }); print("Fetched services: $apiAllServices"); } catch (e) { @@ -418,14 +422,17 @@ class PersonalDetailsState extends State { height: 15, width: 15, decoration: BoxDecoration( - shape: BoxShape.circle, + shape: BoxShape.rectangle, border: Border.all( - color: isSelected ? Colors.green : Colors.grey, width: 1), + color: isSelected ? Colors.green : Colors.grey.shade400, + width: isSelected ? 2 : 1, + ), ), child: Icon( - Icons.check_circle, + Icons.check, size: 10, - color: isSelected ? Colors.green : Colors.grey, + + color: isSelected ? Colors.green : Colors.white10, // color: Colors.grey, )), ]), diff --git a/lib/Screens/userManagement/create_user/traveller_details.dart b/lib/Screens/userManagement/create_user/traveller_details.dart index 21f497c..e5c3627 100644 --- a/lib/Screens/userManagement/create_user/traveller_details.dart +++ b/lib/Screens/userManagement/create_user/traveller_details.dart @@ -265,7 +265,7 @@ class TravellerDetailsState extends State { } List> get hotelMembershipList { - return hotelLoyaltyEntries.map((entry) { + final mappedList = hotelLoyaltyEntries.map((entry) { return { "id": entry["id"], "hotel_name": entry["controller_hotel"].text ?? '', @@ -275,6 +275,12 @@ class TravellerDetailsState extends State { "is_active": entry["is_active"] ?? "1", }; }).toList(); + + final allEntriesEmpty = mappedList.every((entry) => + (entry["hotel_name"] as String).trim().isEmpty && + (entry["membership_number"] as String).trim().isEmpty); + + return allEntriesEmpty ? [] : mappedList; } // List> get frequentFlierList { @@ -293,14 +299,14 @@ class TravellerDetailsState extends State { // } List> get frequentFlierList { - return frequentFlierEntries.map((entry) { + final mappedList = frequentFlierEntries.map((entry) { final frequent_flier_number = entry["controller_flier_number"]; print("frequent_flier_number- $frequent_flier_number"); return { "id": entry["id"], - "airline": entry["airline"], // ✅ string value + "airline": entry["airline"] ?? "", "frequent_flier_number": frequent_flier_number is TextEditingController ? frequent_flier_number.text : "", @@ -309,6 +315,12 @@ class TravellerDetailsState extends State { "is_active": entry["is_active"] ?? "1", }; }).toList(); + + final allEntriesEmpty = mappedList.every((entry) => + (entry["airline"] as String).trim().isEmpty && + (entry["frequent_flier_number"] as String).trim().isEmpty); + + return allEntriesEmpty ? [] : mappedList; } // List> get visaDetailsList { @@ -335,7 +347,7 @@ class TravellerDetailsState extends State { // } List> get visaDetailsList { - return visaEntries.map((entry) { + final mappedList = visaEntries.map((entry) { final fromController = entry["controller_valid_from"]; final uptoController = entry["controller_valid_upto"]; @@ -352,6 +364,18 @@ class TravellerDetailsState extends State { "is_active": entry["is_active"] ?? "1", }; }).toList(); + + final allEntriesEmpty = mappedList.every((entry) => + (entry["country_code"] == null || + entry["country_code"].toString().trim().isEmpty) && + (entry["visa_type_id"] == null || + entry["visa_type_id"].toString().trim().isEmpty) && + (entry["valid_from"] == null || + entry["valid_from"].toString().trim().isEmpty) && + (entry["valid_upto"] == null || + entry["valid_upto"].toString().trim().isEmpty)); + + return allEntriesEmpty ? [] : mappedList; } void updateTravel() { @@ -1983,11 +2007,16 @@ class TravellerDetailsState extends State { child: TextField( style: GoogleFonts.poppins(fontSize: 12, color: Colors.black), controller: controllers["emergency_contact"], + inputFormatters: [ + FilteringTextInputFormatter.allow(RegExp(r'[0-9\s]')), + ], + // maxLength: 10, enabled: !widget.isViewMode, onChanged: (value) { _clearError("emergency_contact"); }, decoration: InputDecoration( + // counterText: '', labelText: "Contact Number", labelStyle: GoogleFonts.poppins(fontSize: 12, color: Colors.grey), diff --git a/lib/data/models/Searchtraveller.dart b/lib/data/models/Searchtraveller.dart index f0f5ba2..7d4d2b3 100644 --- a/lib/data/models/Searchtraveller.dart +++ b/lib/data/models/Searchtraveller.dart @@ -4,7 +4,7 @@ class SearchTraveler { final String lastName; final String email; final String mobileNo; - + String? empCode; SearchTraveler({ required this.travellerId, @@ -12,20 +12,17 @@ class SearchTraveler { required this.lastName, required this.email, required this.mobileNo, - + this.empCode, }); - - factory SearchTraveler.fromJson(Map json){ + factory SearchTraveler.fromJson(Map json) { return SearchTraveler( - travellerId: json['traveller_id'].toString(), - firstName: json['first_name'].toString()?? '', - lastName: json['last_name'].toString()?? '', - email: json['email'].toString()?? '', - mobileNo: json['mobile_no'].toString()?? '', - + firstName: json['first_name'].toString() ?? '', + lastName: json['last_name'].toString() ?? '', + email: json['email'].toString() ?? '', + mobileNo: json['mobile'].toString() ?? '', + empCode: json['employee_code'].toString() ?? '', ); } - -} \ No newline at end of file +} diff --git a/lib/data/models/searchUser.dart b/lib/data/models/searchUser.dart index 20cb817..d8e2994 100644 --- a/lib/data/models/searchUser.dart +++ b/lib/data/models/searchUser.dart @@ -1,10 +1,11 @@ -class SearchUser{ +class SearchUser { final String userId; final String firstName; final String lastName; final String email; final String mobileNo; final String alternateMobileNo; + String? empCode; SearchUser({ required this.userId, @@ -13,18 +14,18 @@ class SearchUser{ required this.email, required this.mobileNo, required this.alternateMobileNo, - }); + this.empCode, + }); - - factory SearchUser.fromJson(Map json){ + factory SearchUser.fromJson(Map json) { return SearchUser( - userId: json['user_id'].toString(), - firstName: json['first_name'].toString()?? '', - lastName: json['last_name'].toString()?? '', - email: json['email'].toString()?? '', - mobileNo: json['mobile_no'].toString()?? '', - alternateMobileNo: json['alternate_mobile_no'].toString()?? '', + userId: json['user_id'].toString(), + firstName: json['first_name'].toString() ?? '', + lastName: json['last_name'].toString() ?? '', + email: json['email'].toString() ?? '', + mobileNo: json['mobile_no'].toString() ?? '', + alternateMobileNo: json['alternate_mobile_no'].toString() ?? '', + empCode: json['employee_code'].toString() ?? '', ); } - -} \ No newline at end of file +} diff --git a/lib/routes/custom_appBar.dart b/lib/routes/custom_appBar.dart index 902ab53..d7a9d76 100644 --- a/lib/routes/custom_appBar.dart +++ b/lib/routes/custom_appBar.dart @@ -268,7 +268,8 @@ class _CustomAppBarState extends State { TabSelection.allTrips, '/listAllPlan'), layoutColor!, isSelected: selectedTab == TabSelection.allTrips, - icon: Icons.insights_outlined, + icon: Icons.format_list_bulleted_rounded, + // icon: Icons.insights_outlined, ), if (userDetails["role"] != "Travel Agent") @@ -282,7 +283,8 @@ class _CustomAppBarState extends State { layoutColor!, // () => context.go('/listTravelAgentPlan'), isSelected: selectedTab == TabSelection.myTrips, - icon: Icons.request_page_outlined, + icon: Icons.shopping_bag_outlined, + // icon: Icons.request_page_outlined, ), if (userData?["role"] != "Travel Agent") // for others @@ -368,8 +370,6 @@ class _CustomAppBarState extends State { break; case '/PolicyList': context.go('/PolicyList'); - case '/templatesList': - context.go('/templatesList'); case '/CreateUserDetails': context.go( "/CreateUserDetails", @@ -511,11 +511,6 @@ final List> menuItems = [ 'icon': Icons.account_circle, 'label': 'My Profile' }, - { - 'value': '/templatesList', - 'icon': Icons.temple_buddhist_outlined, - 'label': 'My Templates' - }, {'value': '/logout', 'icon': Icons.login_outlined, 'label': 'Logout'}, ]; diff --git a/lib/routes/custom_router.dart b/lib/routes/custom_router.dart index 30e776c..27f5a7c 100644 --- a/lib/routes/custom_router.dart +++ b/lib/routes/custom_router.dart @@ -4,7 +4,7 @@ import 'package:flutter/material.dart'; import 'package:frontend/Screens/authentication/login/login_page.dart'; import 'package:frontend/Screens/authentication/loginPage1.dart'; import 'package:frontend/Screens/dashboard/home_page.dart'; -import 'package:frontend/Screens/myTemplates/templatesList.dart'; +import 'package:frontend/Screens/forex/forex_list.dart'; import 'package:frontend/Screens/organization/orgSetup.dart'; import 'package:frontend/Screens/organization/org_List.dart'; import 'package:frontend/Screens/plans/create_plans.dart'; @@ -44,10 +44,6 @@ final GoRouter router = GoRouter( path: '/listPlan', builder: (context, state) => ListPlans(), ), - GoRoute( - path: '/templatesList', - builder: (context, state) => templatesList(), - ), GoRoute( path: '/createPlan', builder: (context, state) => CreatePlan(), diff --git a/lib/utils/travelAgent_remarks.dart b/lib/utils/travelAgent_remarks.dart index 2c73a0a..192c868 100644 --- a/lib/utils/travelAgent_remarks.dart +++ b/lib/utils/travelAgent_remarks.dart @@ -227,6 +227,7 @@ class CommentModalState extends State { // You can also make a TextEditingController if you want to collect input return AlertDialog( + backgroundColor: Colors.white, contentPadding: const EdgeInsets.fromLTRB(24, 20, 24, 10), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), content: Column( diff --git a/lib/widgets/custom_text_traveller.dart b/lib/widgets/custom_text_traveller.dart index e23e2cf..b760878 100644 --- a/lib/widgets/custom_text_traveller.dart +++ b/lib/widgets/custom_text_traveller.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:google_fonts/google_fonts.dart'; import 'package:responsive_builder/responsive_builder.dart'; class CustomTextField extends StatelessWidget { @@ -22,7 +23,7 @@ class CustomTextField extends StatelessWidget { double widthFactor; if (sizingInfo.deviceScreenType == DeviceScreenType.desktop) { - widthFactor = 0.15; // Reduce width for desktop + widthFactor = 0.9; // Reduce width for desktop } else if (sizingInfo.deviceScreenType == DeviceScreenType.tablet) { widthFactor = 0.8; // Slightly reduced width for tablets } else { @@ -31,49 +32,58 @@ class CustomTextField extends StatelessWidget { return Center( child: Container( - height: 35, + height: 40, width: MediaQuery.of(context).size.width * widthFactor, child: TextFormField( controller: controller, keyboardType: keyboardType, cursorColor: Colors.blueAccent, - style: TextStyle(fontSize: 12.0), + style: GoogleFonts.poppins(fontSize: 11.5), decoration: InputDecoration( - hintStyle: TextStyle(color: Colors.grey), - floatingLabelStyle: TextStyle(color: Colors.blueAccent,), + hintStyle: GoogleFonts.poppins(color: Colors.grey), + floatingLabelStyle: GoogleFonts.poppins( + // color: Colors.black, + color: Color(0xFF575A74), + ), floatingLabelBehavior: FloatingLabelBehavior.always, labelText: labelText, - contentPadding: EdgeInsets.symmetric(vertical: 8, horizontal: 12), // Reduces inner padding - errorStyle: TextStyle(fontSize: 10, height: 0.8, color: Colors.red), + contentPadding: EdgeInsets.symmetric( + vertical: 8, horizontal: 12), // Reduces inner padding + errorStyle: GoogleFonts.poppins( + fontSize: 10, height: 0.8, color: Colors.red), border: OutlineInputBorder( borderRadius: BorderRadius.circular(8), - borderSide: BorderSide(color: Colors.grey, width: 1), // Grey border + borderSide: BorderSide( + color: Colors.grey.shade200, width: 1), // Grey border ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(8), - borderSide: BorderSide(color: Colors.grey, width: 1), // Grey border when not focused + borderSide: BorderSide( + color: Colors.grey.shade200, + width: 1), // Grey border when not focused ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(8), - borderSide: BorderSide(color: Colors.blueAccent, width: 2), // Blue border when focused + borderSide: BorderSide( + color: Colors.grey.shade200, + width: 1), // Blue border when focused ), errorBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(8), - borderSide: BorderSide(color: Colors.grey, width: 1), // Same as normal + borderSide: BorderSide( + color: Colors.grey.shade200, width: 1), // Same as normal ), focusedErrorBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(8), - borderSide: BorderSide(color: Colors.blueAccent, width: 2), // Same as focused + borderSide: BorderSide( + color: Colors.grey.shade200, width: 1), // Same as focused ), ), validator: validator, - ), ), - ); - - }, + }, ); } } diff --git a/lib/widgets/popup_listPlan_action.dart b/lib/widgets/popup_listPlan_action.dart index ab8cc42..c721c5f 100644 --- a/lib/widgets/popup_listPlan_action.dart +++ b/lib/widgets/popup_listPlan_action.dart @@ -1,20 +1,24 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; +import '../Screens/allTrips/remarks_list.dart'; import '../data/models/plan.dart'; import '../services/apiService.dart'; +import '../utils/travelAgent_remarks.dart'; import 'custom_popup.dart'; class PlanPopupMenu extends StatelessWidget { final Plan plan; final Function(String planId) deletePlan; final ApiService apiService; + final Color? layoutColor; const PlanPopupMenu({ Key? key, required this.plan, required this.deletePlan, required this.apiService, + required this.layoutColor, }) : super(key: key); @override @@ -72,6 +76,38 @@ class PlanPopupMenu extends StatelessWidget { apiService.getPdfDownload(plan.planId); }, ), + IconButton( + icon: const Icon( + Icons.comment, + color: Color(0xFF475569), + size: 11, + ), + onPressed: () { + showDialog( + context: context, + builder: (context) => CommentModalList( + // planId: plan.planId, + planId: plan.planId.toString(), + layoutColorForUser: layoutColor!, + role: "User"), + ); + }), + IconButton( + icon: const Icon( + Icons.comment, + color: Color(0xFF475569), + size: 11, + ), + onPressed: () { + showDialog( + context: context, + builder: (context) => CommentModal( + // planId: plan.planId, + planId: plan.planId.toString(), + layoutColorForUser: layoutColor!, + role: "Travel Agent"), + ); + }), ], ), ),