diff --git a/lib/Screens/allTrips/list_all_plans.dart b/lib/Screens/allTrips/list_all_plans.dart index 6e8c628..81ff48b 100644 --- a/lib/Screens/allTrips/list_all_plans.dart +++ b/lib/Screens/allTrips/list_all_plans.dart @@ -62,7 +62,6 @@ class _ListAllPlansState extends State { // }); // }); }); - // futurePlans = fetchPlans(); } @@ -764,6 +763,7 @@ class _ListAllPlansState extends State { Icons.remove_red_eye, color: Color(0xFF475569), size: 18), + tooltip: 'View Trips', onPressed: () { Navigator.pop( context); // Close popup manually @@ -788,6 +788,7 @@ class _ListAllPlansState extends State { icon: Icon( Icons.cancel_rounded, size: 18), + tooltip: 'Cancellation Trips', onPressed: () { Navigator.pop(context); deletePlan(plan.planId); @@ -797,6 +798,7 @@ class _ListAllPlansState extends State { icon: Icon(Icons.download, color: Color(0xFF114D8B), size: 18), + tooltip: 'Download Trips Detials', onPressed: () { Navigator.pop(context); apiService.getPdfDownload( @@ -809,6 +811,7 @@ class _ListAllPlansState extends State { color: Color(0xFF475569), size: 11, ), + tooltip: 'Trips Comments', onPressed: () { showDialog( context: context, diff --git a/lib/Screens/traveller/travellerDetails.dart b/lib/Screens/traveller/travellerDetails.dart new file mode 100644 index 0000000..1cf933b --- /dev/null +++ b/lib/Screens/traveller/travellerDetails.dart @@ -0,0 +1,529 @@ +import 'dart:convert'; + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.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_text_forex.dart'; +import 'travellerList.dart'; + +class TravellerData extends StatefulWidget { + final Future> Function() fetchGetTraveller; + final bool isDesktop; + final Color? layoutColor; + + final int? travellerId; // <-- Add this + final Map? travellerData; + + const TravellerData( + {super.key, + required this.isDesktop, + this.layoutColor, + required this.fetchGetTraveller, + this.travellerId, + this.travellerData}); + + @override + TravellerDataState createState() => TravellerDataState(); +} + +class TravellerDataState extends State { + final ApiService apiService = ApiService(); + Map? apiData; + + final Map focusNodes = { + "name": FocusNode(), + "description": FocusNode(), + }; + + final Map controllers = {}; + Map errorMessages = {}; + + String? selectedName; + String? selectedDescription; + String? userId; + int? travellerDataId; + late String isActive = "1"; + + List dataHeader = [ + "first_name", + "last_name", + "email", + "mobile", + ]; + + Map travellerDetails() { + final data = { + // "traveller_id": int.parse(travellerId), + "first_name": controllers["first_name"]?.text, + "last_name": controllers["last_name"]?.text, + "email": controllers["email"]?.text, + "mobile": controllers["mobile"]?.text, + "is_active": isActive, + }; + return data; + } + + @override + void initState() { + super.initState(); + + + apiData = null; + for (var field in dataHeader) { + controllers[field] = TextEditingController(); + } + + if (widget.travellerId != null) { + print('Editing D ID: ${widget.travellerId}'); + updateTravellerDetails(); + } + } + + void _clearError() { + setState(() { + errorMessages.clear(); + }); + } + + @override + void dispose() { + for (var controller in controllers.values) { + controller.dispose(); + } + super.dispose(); + } + + void updateTravellerDetails() { + print("Inside Update Function - ${widget.travellerData}"); + + final data = widget.travellerData; + + if (data == null) return; + setState(() { + controllers['first_name']?.text = data['first_name'] ?? ''; + controllers['last_name']?.text = data['last_name'] ?? ''; + controllers['email']?.text = data['email'].toString(); + controllers['mobile']?.text = data['mobile'].toString(); + isActive = data["is_active"]; + final travellerId = int.tryParse(data['traveller_id'].toString()); + travellerDataId = travellerId; + }); + } + + + void toggleStatus() { + setState(() { + isActive = isActive == "1" ? "0" : "1"; + }); + } + + bool validateData() { + errorMessages.clear(); + + final data = { + "first_name": controllers["first_name"]?.text, + "last_name": controllers["last_name"]?.text, + "email": controllers["email"]?.text, + "mobile": controllers["mobile"]?.text, + }; + + final requiredFields = ["first_name","last_name","email","mobile"]; + bool hasFocused = false; + + // Check validation for each field + for (String field in requiredFields) { + if (data[field] == null || data[field]!.trim().isEmpty) { + errorMessages[field] = "Required"; + + if (!hasFocused) { + focusNodes[field]?.requestFocus(); + hasFocused = true; + } + } + } + + if (data["mobile"] != null && data["mobile"].toString().isNotEmpty) { + if (!RegExp(r"^\d{10}$").hasMatch(data["mobile"].toString())) { + errorMessages["mobile"] = + "Enter 10 digits"; // Invalid mobile number format + } + } + + if (data["email"] != null && data["email"].toString().isNotEmpty) { + if (!RegExp(r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$") + .hasMatch(data["email"].toString())) { + errorMessages["email"] = "Invalid email format"; // Invalid email format + } + } + + return errorMessages.isEmpty; + } + + Future handleSubmit() async { + userId = await getUserId(); + + setState(() { + // This triggers UI rebuild with error messages + if (validateData()) { + postTravellerData(); + } + }); + + final travellerData1 = travellerDetails(); + print("submit data - $travellerData1"); + } + + Future postTravellerData({int isActive = 1}) async { + // final remarksData = getData(); + + final travellerData = travellerDetails(); + + print("initially value of the Traveller - $travellerData"); + // static here + final orgId = await getOrgId(); + + final String apiUrldata; + travellerData["org_id"] = orgId; + + if (travellerDataId != null) { + print("for edit traveller id - $travellerDataId"); + apiUrldata = '$apiUrl/api/travellers/update/$travellerDataId'; + travellerData["traveller_id"] = travellerDataId.toString(); + travellerData["updated_by"] = userId; + (travellerData.containsKey("created_by")) ? travellerData.remove("created_by") : '' ; + } else { + print("for add Traveller id - null"); + apiUrldata = '$apiUrl/api/travellers/create'; + print("called apiUrl - $apiUrldata"); + travellerData["created_by"] = userId; + } + print("recently Traveller data - $travellerData"); + final token = await getToken(); // Fetch token + + if (token == null) { + throw Exception('Token not found. Please log in.'); + } + + try { + final uri = Uri.parse(apiUrldata); + final headers = { + 'Authorization': 'Bearer $token', + 'Content-Type': 'application/json', + }; + final body = jsonEncode(travellerData); + + final response = travellerDataId != null + ? await http.put(uri, headers: headers, body: body) + : await http.post(uri, headers: headers, body: body); + + + switch (response.statusCode) { + case 200: + print("Update - Response: ${response.body}"); + _clearError(); + widget.fetchGetTraveller(); + Navigator.of(context).pop(); + break; + + case 201: + print("Save - Response: ${response.body}"); + _clearError(); + await widget.fetchGetTraveller(); + Navigator.of(context).pop(); + break; + + default: + print("Failed to submit traveller. 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: SizedBox( + child: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + // Row 1: Title + Edit + Delete buttons + Row( + children: [ + Text( + (travellerDataId != null) ? 'Edit Traveller' : 'Create Traveller', + 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( + "First Name", + 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: 40, + child: TextField( + controller: controllers["first_name"], + focusNode: focusNodes["first_name"], + style: const TextStyle(fontSize: 12), + decoration: const InputDecoration( + labelText: "First Name", + labelStyle: TextStyle(fontSize: 11, color: Colors.grey), + floatingLabelBehavior: FloatingLabelBehavior.never, + border: InputBorder.none, + contentPadding: EdgeInsets.symmetric(vertical: 16), + ), + )), + ), + if (errorMessages["first_name"] != null) ...[ + SizedBox(height: 5), // Space before error message + Text( + errorMessages["first_name"]!, + style: TextStyle(color: Colors.red, fontSize: 12), + ), + ], + ], + ), + SizedBox( + height: 15, + ), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Last Name", + 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: 40, + child: TextField( + controller: controllers["last_name"], + focusNode: focusNodes["last_name"], + style: const TextStyle(fontSize: 12), + decoration: const InputDecoration( + labelText: "Last Name", + labelStyle: TextStyle(fontSize: 11, color: Colors.grey), + floatingLabelBehavior: FloatingLabelBehavior.never, + border: InputBorder.none, + contentPadding: EdgeInsets.symmetric(vertical: 16), + ), + )), + ), + if (errorMessages["last_name"] != null) ...[ + SizedBox(height: 5), // Space before error message + Text( + errorMessages["last_name"]!, + style: TextStyle(color: Colors.red, fontSize: 12), + ), + ], + ], + ), + SizedBox( + height: 15, + ), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Email", + 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: 40, + child: TextField( + controller: controllers["email"], + focusNode: focusNodes["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: 15, + ), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Mobile", + 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: 40, + child: TextField( + controller: controllers["mobile"], + focusNode: focusNodes["mobile"], + style: const TextStyle(fontSize: 12), + decoration: const InputDecoration( + labelText: "Mobile", + labelStyle: TextStyle(fontSize: 11, color: Colors.grey), + floatingLabelBehavior: FloatingLabelBehavior.never, + border: InputBorder.none, + contentPadding: EdgeInsets.symmetric(vertical: 16), + ), + )), + ), + if (errorMessages["mobile"] != null) ...[ + SizedBox(height: 5), // Space before error message + Text( + errorMessages["mobile"]!, + style: TextStyle(color: Colors.red, fontSize: 12), + ), + ], + ], + ), + SizedBox( + height: 15, + ), + if (travellerDataId != null) + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "Change Status ", + style: GoogleFonts.poppins( + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74)), + ), + Tooltip( + message: + isActive == "1" ? "Tap to deactivate" : "Tap to activate", + child: GestureDetector( + onTap: toggleStatus, + child: Text( + isActive == "1" ? "Active" : "Inactive", + style: TextStyle( + fontSize: 13, + fontFamily: "Inter", + color: isActive == "1" ? Colors.green : Colors.red, + ), + ), + ), + ) + ], + ), + if (travellerDataId != null) + 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: () { + handleSubmit(); + // 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('Save', + style: GoogleFonts.poppins( + fontSize: 11, color: Colors.white)), + ), + ), + ], + ) + // : SizedBox.shrink(), + ], + ), + ) + ) + ); + } +} \ No newline at end of file diff --git a/lib/Screens/traveller/travellerList.dart b/lib/Screens/traveller/travellerList.dart new file mode 100644 index 0000000..1076c5a --- /dev/null +++ b/lib/Screens/traveller/travellerList.dart @@ -0,0 +1,869 @@ +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 'travellerDetails.dart'; + +class TravellerList extends StatefulWidget { + const TravellerList({super.key}); + + @override + TravellerListState createState() => TravellerListState(); +} + +class TravellerListState extends State { + final GlobalKey travellerListKey = + GlobalKey(); + + final ApiService apiService = ApiService(); + late Future> futureTraveller; + + late Map depSingleData; + String? selectedTravellerId; + String? orgId; + + Color? layoutColor; + Color? bodyColor; + + List allTraveller = []; + List filteredTraveller = []; + TextEditingController searchController = TextEditingController(); + + int currentPage = 0; + int itemsPerPage = 10; + + @override + void initState() { + super.initState(); + futureTraveller = fetchGetTraveller(); + + futureTraveller.then((object) { + setState(() { + allTraveller = object; + }); + }); + + WidgetsBinding.instance.addPostFrameCallback((_) { + 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> refreshData() { + print("Calling Refresh Data"); + + futureTraveller = fetchGetTraveller(); + + return futureTraveller.then((object) { + print("Calling Refresh Data $object"); + setState(() { + allTraveller = object; + }); + return object; + }); + } + + Future> fetchGetTraveller() async { + String? ordId = await getOrgId(); + final String apiUrlData = '$apiUrl/api/travellers?org_id=$ordId'; + + + final String? token = await getToken(); + + print("Fetch Traveller"); + print("2KN Here : $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', + }, + ); + print("called api : $apiUrlData"); + if (response.statusCode == 200) { + final data = json.decode(response.body); + return data['data']; // Returning raw JSON list + } else { + throw Exception('Failed to load users'); + } + } + + void filterTraveller(String query) { + // print("all before filtering: $query"); + // final lowerQuery = query.toLowerCase(); + // setState(() { + // filteredTraveller = allTraveller.where((object) { + // return (object['traveller_id']?.toLowerCase().contains(lowerQuery) ?? + // false) || + // (object['description']?.toLowerCase().contains(lowerQuery) ?? false) || + // (object['user']?.toLowerCase().contains(lowerQuery) ?? false) || + // (object['is_active']?.toLowerCase().contains(lowerQuery) ?? false); + // }).toList(); + // }); + // print("filteredPlans: $filteredTraveller"); + + print("all before filtering: $query"); + final lowerQuery = query.toLowerCase(); + setState(() { + filteredTraveller = allTraveller.where((object) { + final isActiveStatus = + object['is_active'] == "1" ? "active" : "inactive"; + return (object['traveller_id']?.toLowerCase().contains(lowerQuery) ?? + false) || + (object['name']?.toLowerCase().contains(lowerQuery) ?? false) || + (object['description']?.toLowerCase().contains(lowerQuery) ?? + false) || + (isActiveStatus.contains(lowerQuery)); + }).toList(); + }); + print("filteredTraveller: $filteredTraveller"); + } + + @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( + 'Traveller Details', + style: GoogleFonts.poppins( + fontSize: isDesktop ? 16 : 14, + fontWeight: FontWeight.w600, + color: Colors.black, + ), + ), + ], + ), + if (isDesktop) + SizedBox( + width: MediaQuery.of(context).size.width * 0.16, + ), + + if (isDesktop) + Container( + width: MediaQuery.of(context).size.width * 0.2, + height: 40, + child: TextField( + controller: searchController, + onChanged: filterTraveller, + 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) => TravellerData( + isDesktop: isDesktop, + layoutColor: layoutColor!, + fetchGetTraveller: refreshData, + + // role: + // "Travel Agent" + ), + ); + }, + child: Row( + mainAxisSize: + MainAxisSize.min, // Ensures content fits nicely + children: [ + Text( + "Add Traveller", + 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: filterTraveller, + 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: futureTraveller, + 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 Traveller Available ", + textAlign: TextAlign.center, + style: GoogleFonts.poppins( + fontSize: 20, + fontWeight: FontWeight.bold, + color: Colors.grey), + ), + const SizedBox(height: 20), + Text( + "Please Create Traveller Details", + textAlign: TextAlign.center, + style: GoogleFonts.poppins( + fontSize: 16, color: Colors.grey), + ), + const SizedBox(height: 20), + ], + ), + ), + ); + } + /* Here collect the list to displayed the data in table or card Used */ + List object = filteredTraveller.isNotEmpty + ? filteredTraveller + : allTraveller; + + /* List is Sorting here */ + object.sort((a, b) { + DateTime dateA = DateTime.parse(a['created_on']); + DateTime dateB = DateTime.parse(b['created_on']); + + return dateB + .compareTo(dateA); // Descending: newest first + }); + + /* For pagination for list ... */ + List paginatedTraveller = object + .skip(currentPage * itemsPerPage) + .take(itemsPerPage) + .toList(); + + /* Table ... */ + 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( + 'Name', + style: GoogleFonts.poppins( + fontSize: 13, + fontWeight: FontWeight.w600), + )), + DataColumn( + label: Text( + 'Email', + style: GoogleFonts.poppins( + fontSize: 13, + fontWeight: FontWeight.w600), + )), + DataColumn( + label: Text( + 'Mobile', + style: GoogleFonts.poppins( + fontSize: 13, + fontWeight: FontWeight.w600), + )), + DataColumn( + label: Text( + 'Status', + style: GoogleFonts.poppins( + fontSize: 13, + fontWeight: FontWeight.w600), + )), + DataColumn( + label: Text( + 'Actions', + style: GoogleFonts.poppins( + fontSize: 13, + fontWeight: FontWeight.w600), + )), + ], + rows: paginatedTraveller.map((tableObject) { + String fullName = '${tableObject['first_name'] ?? ''} ${tableObject['last_name'] ?? ''}'; + String travellerId = + tableObject['traveller_id'] + .toString(); // Get user ID + bool isSelected = + selectedTravellerId == travellerId; + + return DataRow(cells: [ + DataCell(Text(fullName ?? 'N/A', + style: TextStyle( + fontSize: 13, + fontFamily: "Inter", + ))), + DataCell( + Text(tableObject['email'] ?? 'N/A', + style: TextStyle( + fontSize: 13, + fontFamily: "Inter", + ), + softWrap: true, + overflow: TextOverflow.ellipsis)), + DataCell( + Text(tableObject['mobile'] ?? 'N/A', + style: TextStyle( + fontSize: 13, + fontFamily: "Inter", + ), + softWrap: true, + overflow: TextOverflow.ellipsis)), + DataCell( + Text( + tableObject['is_active'] == "1" + ? 'Active' + : 'Inactive', + style: TextStyle( + fontSize: 13, + fontFamily: "Inter", + color: tableObject['is_active'] == "1" + ? Colors.green + : Colors.red, + ), + softWrap: true, + overflow: TextOverflow.ellipsis, + ), + ), + DataCell( + // UserActionsMenu( + // user: forex, + // getUserDetails: (id) => + // apiService.getSingleUser(id), + // ), + GestureDetector( + child: Image.asset( + 'assets/images/IconsImg/edit.png', + width: 20, + height: 15), + onTap: () async { + // final userId = getUserId(user['user_id']); + // final usersData = await getUserDetails(userId); + // + final travellerId = int.tryParse( + tableObject['traveller_id'] + .toString()); + + if (travellerId != null) { + print( + "Table cell - traveller Id -- $travellerId"); + final data = await apiService + .getTravellerDetailsFind( + travellerId); + print("TravellerId -- $data"); + + showDialog( + context: context, + builder: (context) => + TravellerData( + isDesktop: isDesktop, + travellerId: + travellerId, // Pass the ID + travellerData: data, + layoutColor: layoutColor!, + // fetchGetForex: fetchGetForex, + fetchGetTraveller: refreshData, + // role: + // "Travel Agent" + ), + ); + } else { + print("Invalid ID"); + } + }, + ), + ), + ]); + }).toList(), + ), + ); + }, + ); + + /* Card ... */ + Widget buildMobileCardView(List paginatedUser) { + return ListView.builder( + itemCount: paginatedUser.length, + itemBuilder: (context, index) { + final cardObject = paginatedUser[index]; + String fullName = '${cardObject['first_name'] ?? ''} ${cardObject['last_name'] ?? ''}'; + 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( + fullName ?? 'N/A', + style: GoogleFonts.poppins( + fontSize: 10, + color: Colors.black87, + fontWeight: FontWeight.w700), + ), + + GestureDetector( + child: Image.asset( + 'assets/images/IconsImg/edit.png', + width: 20, + height: 15), + onTap: () async { + // final userId = getUserId(user['user_id']); + // final usersData = await getUserDetails(userId); + // + final travellerId = int.tryParse( + cardObject['traveller_id'] + .toString()); + + if (travellerId != null) { + print( + "travellerId -- $travellerId"); + final data = await apiService + .getTravellerDetailsFind( + travellerId); + print("TravellerId -- $data"); + + showDialog( + context: context, + builder: (context) => + TravellerData( + isDesktop: isDesktop, + travellerId: + travellerId, // Pass the ID + travellerData: data, + layoutColor: layoutColor!, + // fetchGetTraveller: fetchGetTraveller, + fetchGetTraveller: + refreshData, + // role: + // "Travel Agent" + ), + ); + } else { + print("Invalid 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 + // Name + Row( + children: [ + Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text( + cardObject['email'] ?? '', + style: GoogleFonts.poppins( + fontSize: 10, + color: Colors.black87), + ), + ], + ), + SizedBox( + width: 10, + ), + Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Text( + cardObject['mobile'] ?? '', + style: GoogleFonts.poppins( + fontSize: 10, + color: Colors.black87), + ), + ], + ), + ], + ), + // Actions + // Actions + ], + ), + ), + ); + }, + ); + } + + return Expanded( + child: Column( + // mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: isDesktop + ? (searchController.text.isNotEmpty && + filteredTraveller.isEmpty + ? Center( + child: Text( + "No matches found", + style: GoogleFonts.poppins( + fontSize: 14, + color: Colors.grey), + ), + ) + : SingleChildScrollView( + scrollDirection: Axis.vertical, + child: table, + )) + : (searchController.text.isNotEmpty && + filteredTraveller.isEmpty + ? Center( + child: Text( + "No matches found", + style: GoogleFonts.poppins( + fontSize: 14, + color: Colors.grey), + ), + ) + : buildMobileCardView( + paginatedTraveller)), + ), + // Expanded( + // child: isDesktop + // ? SingleChildScrollView( + // scrollDirection: Axis.vertical, + // child: table, // <-- your existing table + // ) + // : buildMobileCardView(paginatedTraveller), + // ), + PaginationControls( + currentPage: currentPage, + itemsPerPage: itemsPerPage, + totalItems: object.length, + activeColor: layoutColor, // your theme color + onPageChanged: (page) { + setState(() { + currentPage = page; + }); + }, + onItemsPerPageChanged: (items) { + setState(() { + itemsPerPage = items; + currentPage = 0; + }); + }, + ), + ], + ), + ); + }, + ) + ]), + )), + ); + } +} \ No newline at end of file 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..5797b9e 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,26 +34,28 @@ 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( - {Key? key, - this.selectedGender, - this.selectedCountry, - this.selectedRole, - this.onServiceIdsChanged, - required this.personalDetailsKey, - this.initialSelectedServices, - required this.controllers, - required this.errorMessages, - required this.isDesktop, - required this.isViewMode, - required this.apiselectedUser, - this.onGenderChanged, - this.onCountryChanged, - this.onRoleChanged, - this.onUserTypeChanged}) - : super(key: key); + const PersonalDetails({ + Key? key, + this.selectedGender, + this.selectedCountry, + this.selectedRole, + this.onServiceIdsChanged, + required this.personalDetailsKey, + this.initialSelectedServices, + required this.controllers, + required this.errorMessages, + required this.isDesktop, + required this.isViewMode, + required this.apiselectedUser, + this.onGenderChanged, + this.onCountryChanged, + this.onRoleChanged, + this.onUserTypeChanged, + this.userIdApi, + }) : super(key: key); @override PersonalDetailsState createState() => PersonalDetailsState(); @@ -118,12 +123,15 @@ class PersonalDetailsState extends State { "employeeCode", "dateOfIssue", "dateOfExpiry", - "changePassword" + "changePassword", ]; Color? layoutColor; Color? bodyColor; + final Map controllers = {}; + Map errorMessages2 = {}; + @override void initState() { super.initState(); @@ -136,14 +144,16 @@ class PersonalDetailsState extends State { selectedRole = widget.selectedRole; fetchRoles(); fetchCountries(); - selectedServiceIds = - List>.from(widget.initialSelectedServices ?? []); + selectedServiceIds = List>.from( + widget.initialSelectedServices ?? [], + ); print("selectedServiceIdsAPI - $selectedServiceIds"); WidgetsBinding.instance.addPostFrameCallback((_) { loadAllServices(); getOrganizationData(); + loadInitialData(); }); } @@ -153,6 +163,23 @@ 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(); @@ -209,12 +236,11 @@ class PersonalDetailsState extends State { } if (selectedServiceIds.isEmpty && services.isNotEmpty) { - selectedServiceIds = services.map>((item) { - final map = Map.from(item); - return { - "service_id": map['service_id'].toString(), - }; - }).toList(); + selectedServiceIds = + services.map>((item) { + final map = Map.from(item); + return {"service_id": map['service_id'].toString()}; + }).toList(); } // selectedServiceIds = services.map>((item) { @@ -251,26 +277,16 @@ class PersonalDetailsState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - SizedBox( - height: 10, - ), + SizedBox(height: 10), _buildFirstRow(widget.isDesktop), - SizedBox( - height: 10, - ), + SizedBox(height: 10), _buildSecondRow(widget.isDesktop), - SizedBox( - height: 10, - ), + SizedBox(height: 10), _buildThirdRow(widget.isDesktop), - SizedBox( - height: 10, - ), + SizedBox(height: 10), _buildForthRow(widget.isDesktop), - SizedBox( - height: 10, - ), - if (selectedRole == "5") _buildServices(widget.isDesktop) + SizedBox(height: 10), + if (selectedRole == "5") _buildServices(widget.isDesktop), ], ), ), @@ -283,8 +299,9 @@ class PersonalDetailsState extends State { setState(() { // apiAllServices = result; setState(() { - apiAllServices = result - ..sort((a, b) => (a['order'] ?? 0).compareTo(b['order'] ?? 0)); + apiAllServices = + result + ..sort((a, b) => (a['order'] ?? 0).compareTo(b['order'] ?? 0)); }); }); print("Fetched services: $apiAllServices"); @@ -301,38 +318,39 @@ class PersonalDetailsState extends State { "Services", style: GoogleFonts.poppins( - fontSize: 14, fontWeight: FontWeight.w400, color: Colors.black), + fontSize: 14, + fontWeight: FontWeight.w400, + color: Colors.black, + ), // style: TextStyle( // fontFamily: "Archivo", // fontSize: 14, // fontWeight: FontWeight.w600, // color: Color(0xFF212121)), ), - SizedBox( - height: 10, - ), + SizedBox(height: 10), Container( decoration: BoxDecoration( - border: Border.all(color: Color(0xFFF4F4FB)), - borderRadius: BorderRadius.circular(1), - // color: bodyColor, - // color: Color(0xFFF5F5F5), - color: Colors.white), + border: Border.all(color: Color(0xFFF4F4FB)), + borderRadius: BorderRadius.circular(1), + // color: bodyColor, + // color: Color(0xFFF5F5F5), + color: Colors.white, + ), padding: EdgeInsets.only(left: 5, right: 5, top: 15, bottom: 5), - child: isDesktop - ? Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - // mainAxisSize: MainAxisSize.min, - children: _buildOptions(), - ) - : Expanded( - child: SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: Row( - children: _buildOptions(), + child: + isDesktop + ? Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + // mainAxisSize: MainAxisSize.min, + children: _buildOptions(), + ) + : Expanded( + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row(children: _buildOptions()), ), ), - ), ), ], ); @@ -357,8 +375,9 @@ class PersonalDetailsState extends State { String serviceId = service['service_id'].toString(); // bool isSelected = selectedServiceIds.contains(serviceId); - bool isSelected = - selectedServiceIds.any((item) => item["service_id"] == serviceId); + bool isSelected = selectedServiceIds.any( + (item) => item["service_id"] == serviceId, + ); return GestureDetector( onTap: () { @@ -366,8 +385,9 @@ class PersonalDetailsState extends State { String serviceId = service['service_id'].toString(); // Check if already selected - int existingIndex = selectedServiceIds - .indexWhere((item) => item["service_id"] == serviceId); + int existingIndex = selectedServiceIds.indexWhere( + (item) => item["service_id"] == serviceId, + ); if (existingIndex != -1) { selectedServiceIds.removeAt(existingIndex); @@ -379,47 +399,54 @@ class PersonalDetailsState extends State { widget.onServiceIdsChanged!(selectedServiceIds); }); }, - child: Row(children: [ - iconUrl.isNotEmpty - ? Image.network( + child: Row( + children: [ + iconUrl.isNotEmpty + ? Image.network( iconUrl, width: 18, height: 18, errorBuilder: (context, error, stackTrace) { - return Icon(fallbackIcon, - size: 18, - color: isSelected == name - ? Color(0xFF114D8B) - : Color(0xFF475569)); + return Icon( + fallbackIcon, + size: 18, + color: + isSelected == name + ? Color(0xFF114D8B) + : Color(0xFF475569), + ); }, ) - : Icon(fallbackIcon, + : Icon( + fallbackIcon, size: 18, color: - isSelected == name ? Color(0xFF114D8B) : Color(0xFF475569)), + isSelected == name ? Color(0xFF114D8B) : Color(0xFF475569), + ), - SizedBox(width: 5), + SizedBox(width: 5), - SizedBox(width: 5), - Text( - name, - style: GoogleFonts.poppins( + SizedBox(width: 5), + Text( + name, + style: GoogleFonts.poppins( fontSize: 12, color: isSelected == name ? Color(0xFF114D8B) : Color(0xFF475569), fontWeight: - isSelected == name ? FontWeight.bold : FontWeight.w500), - // style: TextStyle( - // fontSize: 13, - // color: isSelected == name ? Color(0xFF114D8B) : Color(0xFF475569), - // fontFamily: "Archivo", - // fontWeight: - // isSelected == name ? FontWeight.bold : FontWeight.w500), - // fontWeight: selectedListOption == title ? FontWeight.bold : FontWeight.normal,)), - ), + isSelected == name ? FontWeight.bold : FontWeight.w500, + ), + // style: TextStyle( + // fontSize: 13, + // color: isSelected == name ? Color(0xFF114D8B) : Color(0xFF475569), + // fontFamily: "Archivo", + // fontWeight: + // isSelected == name ? FontWeight.bold : FontWeight.w500), + // fontWeight: selectedListOption == title ? FontWeight.bold : FontWeight.normal,)), + ), - SizedBox(width: 5), - // if (selectedListOption == title && widget.isViewMode == false) - Container( + SizedBox(width: 5), + // if (selectedListOption == title && widget.isViewMode == false) + Container( height: 15, width: 15, decoration: BoxDecoration( @@ -435,8 +462,10 @@ class PersonalDetailsState extends State { color: isSelected ? Colors.green : Colors.white10, // color: Colors.grey, - )), - ]), + ), + ), + ], + ), ); } @@ -468,118 +497,204 @@ class PersonalDetailsState extends State { Widget _buildFirstRow(bool isDesktop) { return Container( color: Colors.white, - child: widget.isDesktop - ? Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - buildFirstNameField(), - Spacer(), - buildLastNameField(), - Spacer(), // Space after Last Name - buildGenderField(), - SizedBox( - width: 15, - ), - buildDobField(), - ], - ) - : Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - buildFirstNameField(), - SizedBox(height: 8), // Vertical space - buildLastNameField(), - SizedBox(height: 8), - buildGenderField(), - SizedBox(height: 8), - buildDobField(), - ], - ), + child: + widget.isDesktop + ? Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + buildFirstNameField(), + Spacer(), + buildLastNameField(), + Spacer(), // Space after Last Name + buildGenderField(), + SizedBox(width: 15), + buildDobField(), + ], + ) + : Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + buildFirstNameField(), + SizedBox(height: 8), // Vertical space + buildLastNameField(), + SizedBox(height: 8), + buildGenderField(), + SizedBox(height: 8), + buildDobField(), + ], + ), ); } Widget _buildSecondRow(bool isDesktop) { return Container( color: Colors.white, - child: widget.isDesktop - ? Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - buildEmailField(), - Spacer(), - buildMobileField(), - Spacer(), // Space after Last Name - buildAlternateMobileField(), - ], - ) - : Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - buildMobileField(), - SizedBox(height: 8), // Vertical space - buildAlternateMobileField(), - SizedBox(height: 8), - buildEmailField(), - ], - ), + child: + widget.isDesktop + ? Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + buildEmailField(), + Spacer(), + buildMobileField(), + Spacer(), // Space after Last Name + buildAlternateMobileField(), + ], + ) + : Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + buildMobileField(), + SizedBox(height: 8), // Vertical space + buildAlternateMobileField(), + SizedBox(height: 8), + buildEmailField(), + ], + ), ); } 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 - ? Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - if (!widget.apiselectedUser) ...[ - buildPassword(), - SizedBox(width: 15), - ], - buildRole() - ], - ) - : Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - if (!widget.apiselectedUser) ...[ - buildPassword(), - SizedBox(height: 8)], // - buildRole() + child: + isDesktop + ? !widget.apiselectedUser + ? Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (!widget.apiselectedUser) ...[ + buildPassword(), + SizedBox(width: 15), + buildRole(), + ] else ...[ + // TextButton( + // onPressed: () => _openPopup(), + // style: TextButton.styleFrom( + // foregroundColor: Colors.black, + // textStyle: GoogleFonts.poppins( + // fontSize: 12, + // fontWeight: FontWeight.w400, + // ), + // ), + // child: Text("Change Password"), + // ), + // + // SizedBox(height: 8, width: 15), + // buildRole(), + ], + ], + ) + : Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: + isDesktop + ? MediaQuery.of(context).size.width * 0.25 + : MediaQuery.of(context).size.width * 0.8, + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + TextButton( + onPressed: () => _openPopup(), + style: TextButton.styleFrom( + foregroundColor: Color(0xFF114D8B), + textStyle: GoogleFonts.poppins( + fontSize: 12, + fontWeight: FontWeight.w400, + ), + ), + child: Text("Change Password"), + ), + ], + ), + ), - ], - ), + SizedBox(height: 1), + buildRole(), + ], + ) + : Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (!widget.apiselectedUser) ...[ + buildPassword(), + SizedBox(height: 8), + buildRole(), + ] else ...[ + Container( + width: + isDesktop + ? MediaQuery.of(context).size.width * 0.25 + : MediaQuery.of(context).size.width * 0.8, + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + TextButton( + onPressed: () => _openPopup(), + style: TextButton.styleFrom( + foregroundColor: Color(0xFF114D8B), + textStyle: GoogleFonts.poppins( + fontSize: 12, + fontWeight: FontWeight.w400, + ), + ), + child: Text("Change Password"), + ), + ], + ), + ), + SizedBox(height: 1), + buildRole(), + ], + ], + ), ); } Widget _buildForthRow(bool isDesktop) { return Container( color: Colors.white, - child: isDesktop - ? Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - buildAddress(), - SizedBox( - width: 15, - ), - buildCountryField(), - SizedBox( - width: 15, - ), - buildPostalCodeField(), - ], - ) - : Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - buildAddress(), - SizedBox(height: 8), // - buildCountryField(), - SizedBox(height: 8), // Vertical space - buildPostalCodeField(), - ], - ), + child: + isDesktop + ? Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + buildAddress(), + SizedBox(width: 15), + buildCountryField(), + SizedBox(width: 15), + buildPostalCodeField(), + ], + ) + : Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + buildAddress(), + SizedBox(height: 8), // + buildCountryField(), + SizedBox(height: 8), // Vertical space + buildPostalCodeField(), + ], + ), ); } @@ -589,10 +704,11 @@ class PersonalDetailsState extends State { children: [ Text( "First Name", - style: GoogleFonts.poppins( - fontSize: 12, - fontWeight: FontWeight.w600, - color: Color(0xFF575A74)) + style: GoogleFonts.poppins( + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74), + ), ), SizedBox(height: 5), CustomTextFieldUserWrapper( @@ -610,9 +726,10 @@ class PersonalDetailsState extends State { decoration: InputDecoration( labelText: "FirstName", labelStyle: GoogleFonts.poppins( - fontSize: 12, - fontWeight: FontWeight.w400, - color: Colors.grey), + fontSize: 12, + fontWeight: FontWeight.w400, + color: Colors.grey, + ), floatingLabelBehavior: FloatingLabelBehavior.never, border: InputBorder.none, contentPadding: EdgeInsets.symmetric(vertical: 16), @@ -635,11 +752,14 @@ class PersonalDetailsState extends State { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text("Last Name", - style: GoogleFonts.poppins( - fontSize: 12, - fontWeight: FontWeight.w600, - color: Color(0xFF575A74))), + Text( + "Last Name", + style: GoogleFonts.poppins( + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74), + ), + ), SizedBox(height: 5), CustomTextFieldUserWrapper( isFocused: false, @@ -681,15 +801,17 @@ class PersonalDetailsState extends State { Text( "Gender", style: GoogleFonts.poppins( - fontSize: 12, - fontWeight: FontWeight.w600, - color: Color(0xFF575A74)), + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74), + ), ), SizedBox(height: 5), CustomTextFieldUserWrapper( - width: widget.isDesktop - ? MediaQuery.of(context).size.width * 0.12 - : null, + width: + widget.isDesktop + ? MediaQuery.of(context).size.width * 0.12 + : null, isFocused: false, isDesktop: widget.isDesktop, child: SizedBox( @@ -697,16 +819,17 @@ class PersonalDetailsState extends State { child: DropdownButtonFormField( // value: selectedGender, value: widget.isViewMode ? null : selectedGender, - onChanged: widget.isViewMode - ? null - : (String? newValue) { - setState(() { - selectedGender = newValue; - print("selectedGender - $selectedGender"); - }); + onChanged: + widget.isViewMode + ? null + : (String? newValue) { + setState(() { + selectedGender = newValue; + print("selectedGender - $selectedGender"); + }); - widget.onGenderChanged?.call(newValue); - }, + widget.onGenderChanged?.call(newValue); + }, decoration: InputDecoration( border: InputBorder.none, enabled: !widget.isViewMode, // Disables input when in view mode @@ -789,8 +912,9 @@ class PersonalDetailsState extends State { if (pickedDate != null && pickedDate != _selectedDateOfBirth) { setState(() { _selectedDateOfBirth = pickedDate; - widget.controllers["dob"]?.text = - DateFormat('dd-MM-yyyy').format(pickedDate); + widget.controllers["dob"]?.text = DateFormat( + 'dd-MM-yyyy', + ).format(pickedDate); }); } } @@ -800,31 +924,34 @@ class PersonalDetailsState extends State { children: [ Text( "Date of Birth", - style: GoogleFonts.poppins( - fontSize: 12, - fontWeight: FontWeight.w600, - color: Color(0xFF575A74)) + style: GoogleFonts.poppins( + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74), + ), ), SizedBox(height: 5), CustomTextFieldUserWrapper( - width: widget.isDesktop - ? MediaQuery.of(context).size.width * 0.12 - : null, + width: + widget.isDesktop + ? MediaQuery.of(context).size.width * 0.12 + : null, isFocused: false, isDesktop: widget.isDesktop, child: SizedBox( height: 40, child: GestureDetector( - onTap: widget.isViewMode - ? null - : () async { - await _selectCheckDateOfBirth(context); - if (widget.controllers["dob"]!.text.isNotEmpty) { - setState(() { - // errorMessages.remove("start_date"); - }); - } - }, + onTap: + widget.isViewMode + ? null + : () async { + await _selectCheckDateOfBirth(context); + if (widget.controllers["dob"]!.text.isNotEmpty) { + setState(() { + // errorMessages.remove("start_date"); + }); + } + }, child: AbsorbPointer( child: TextField( controller: widget.controllers["dob"], @@ -832,14 +959,18 @@ class PersonalDetailsState extends State { decoration: InputDecoration( labelText: "Select Date", labelStyle: GoogleFonts.poppins( - fontSize: 12, - fontWeight: FontWeight.w400, - color: Colors.grey), + fontSize: 12, + fontWeight: FontWeight.w400, + color: Colors.grey, + ), floatingLabelBehavior: FloatingLabelBehavior.never, border: InputBorder.none, contentPadding: EdgeInsets.symmetric(vertical: 16), - suffixIcon: Icon(Icons.calendar_today, - size: 16, color: Color(0xFF8B8FB2)), + suffixIcon: Icon( + Icons.calendar_today, + size: 16, + color: Color(0xFF8B8FB2), + ), ), ), ), @@ -856,10 +987,11 @@ class PersonalDetailsState extends State { children: [ Text( "Email", - style: GoogleFonts.poppins( - fontSize: 12, - fontWeight: FontWeight.w600, - color: Color(0xFF575A74)) + style: GoogleFonts.poppins( + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74), + ), ), SizedBox(height: 5), CustomTextFieldUserWrapper( @@ -876,8 +1008,10 @@ class PersonalDetailsState extends State { enabled: !widget.isViewMode, decoration: InputDecoration( labelText: "Email", - labelStyle: - GoogleFonts.poppins(fontSize: 12, color: Colors.grey), + labelStyle: GoogleFonts.poppins( + fontSize: 12, + color: Colors.grey, + ), floatingLabelBehavior: FloatingLabelBehavior.never, border: InputBorder.none, contentPadding: EdgeInsets.symmetric(vertical: 16), @@ -902,10 +1036,11 @@ class PersonalDetailsState extends State { children: [ Text( "Mobile Number", - style: GoogleFonts.poppins( - fontSize: 12, - fontWeight: FontWeight.w600, - color: Color(0xFF575A74)) + style: GoogleFonts.poppins( + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74), + ), ), SizedBox(height: 5), CustomTextFieldUserWrapper( @@ -922,13 +1057,16 @@ class PersonalDetailsState extends State { }, keyboardType: TextInputType.numberWithOptions(decimal: true), inputFormatters: [ - FilteringTextInputFormatter.allow(RegExp( - r'^\d*\.?\d*$')), // Allow only positive numbers with optional decimal + FilteringTextInputFormatter.allow( + RegExp(r'^\d*\.?\d*$'), + ), // Allow only positive numbers with optional decimal ], decoration: InputDecoration( labelText: "Mobile Number", - labelStyle: - GoogleFonts.poppins(fontSize: 12, color: Colors.grey), + labelStyle: GoogleFonts.poppins( + fontSize: 12, + color: Colors.grey, + ), floatingLabelBehavior: FloatingLabelBehavior.never, border: InputBorder.none, contentPadding: EdgeInsets.symmetric(vertical: 16), @@ -951,11 +1089,14 @@ class PersonalDetailsState extends State { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text("Alternate Mobile Number", - style: GoogleFonts.poppins( - fontSize: 12, - fontWeight: FontWeight.w600, - color: Color(0xFF575A74))), + Text( + "Alternate Mobile Number", + style: GoogleFonts.poppins( + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74), + ), + ), SizedBox(height: 5), CustomTextFieldUserWrapper( isFocused: false, @@ -968,13 +1109,16 @@ class PersonalDetailsState extends State { enabled: !widget.isViewMode, keyboardType: TextInputType.numberWithOptions(decimal: true), inputFormatters: [ - FilteringTextInputFormatter.allow(RegExp( - r'^\d*\.?\d*$')), // Allow only positive numbers with optional decimal + FilteringTextInputFormatter.allow( + RegExp(r'^\d*\.?\d*$'), + ), // Allow only positive numbers with optional decimal ], decoration: InputDecoration( labelText: "Alternate Number", - labelStyle: - GoogleFonts.poppins(fontSize: 12, color: Colors.grey), + labelStyle: GoogleFonts.poppins( + fontSize: 12, + color: Colors.grey, + ), floatingLabelBehavior: FloatingLabelBehavior.never, border: InputBorder.none, contentPadding: EdgeInsets.symmetric(vertical: 16), @@ -1003,7 +1147,7 @@ class PersonalDetailsState extends State { // Map country codes to country names countryMap = { for (var item in countryList) - item['country_code'] as String: item['country_name'] as String + item['country_code'] as String: item['country_name'] as String, }; // Extract only country codes for processing @@ -1016,10 +1160,11 @@ class PersonalDetailsState extends State { children: [ Text( "Country", - style: GoogleFonts.poppins( - fontSize: 12, - fontWeight: FontWeight.w600, - color: Color(0xFF575A74)) + style: GoogleFonts.poppins( + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74), + ), ), SizedBox(height: 5), CustomTextFieldUserWrapper( @@ -1045,19 +1190,22 @@ class PersonalDetailsState extends State { dropdownDecoratorProps: DropDownDecoratorProps( dropdownSearchDecoration: InputDecoration( border: InputBorder.none, - contentPadding: EdgeInsets.symmetric( - horizontal: 1, + contentPadding: EdgeInsets.symmetric(horizontal: 1), + ), + ), + dropdownBuilder: + (context, selectedItem) => Align( + // Center-align selected item + alignment: Alignment.centerLeft, + child: Text( + selectedItem ?? "Select Country", + style: GoogleFonts.poppins( + fontSize: 12, + color: Colors.black, + ), + ), ), - ), - ), - dropdownBuilder: (context, selectedItem) => Align( - // Center-align selected item - alignment: Alignment.centerLeft, - child: Text( - selectedItem ?? "Select Country", - style: GoogleFonts.poppins(fontSize: 12, color: Colors.black), - ), - ), + // onChanged: widget.isViewMode // ? null // : (String? newValue) { @@ -1073,23 +1221,25 @@ class PersonalDetailsState extends State { // }); // widget.onCountryChanged?.call(newValue); // }, + onChanged: + widget.isViewMode + ? null + : (String? newValue) { + if (newValue == null) return; - onChanged: widget.isViewMode - ? null - : (String? newValue) { - if (newValue == null) return; + final countryCode = + countryMap.entries + .firstWhere((entry) => entry.value == newValue) + .key; - final countryCode = countryMap.entries - .firstWhere((entry) => entry.value == newValue) - .key; + setState(() { + selectedCountry = countryCode; + }); - setState(() { - selectedCountry = countryCode; - }); - - widget.onCountryChanged - ?.call(countryCode); // ✅ send code not name - }, + widget.onCountryChanged?.call( + countryCode, + ); // ✅ send code not name + }, ), ), ), @@ -1103,10 +1253,11 @@ class PersonalDetailsState extends State { children: [ Text( "Postal Code", - style: GoogleFonts.poppins( - fontSize: 12, - fontWeight: FontWeight.w600, - color: Color(0xFF575A74)) + style: GoogleFonts.poppins( + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74), + ), ), SizedBox(height: 5), CustomTextFieldUserWrapper( @@ -1120,15 +1271,17 @@ class PersonalDetailsState extends State { enabled: !widget.isViewMode, keyboardType: TextInputType.numberWithOptions(decimal: true), inputFormatters: [ - FilteringTextInputFormatter.allow(RegExp( - r'^\d*\.?\d*$')), // Allow only positive numbers with optional decimal + FilteringTextInputFormatter.allow( + RegExp(r'^\d*\.?\d*$'), + ), // Allow only positive numbers with optional decimal ], decoration: InputDecoration( labelText: "Postal Code", labelStyle: GoogleFonts.poppins( - fontSize: 12, - fontWeight: FontWeight.w400, - color: Colors.grey), + fontSize: 12, + fontWeight: FontWeight.w400, + color: Colors.grey, + ), floatingLabelBehavior: FloatingLabelBehavior.never, border: InputBorder.none, contentPadding: EdgeInsets.symmetric(vertical: 16), @@ -1146,10 +1299,11 @@ class PersonalDetailsState extends State { children: [ Text( "Role ", - style: GoogleFonts.poppins( - fontSize: 12, - fontWeight: FontWeight.w600, - color: Color(0xFF575A74)) + style: GoogleFonts.poppins( + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74), + ), ), SizedBox(height: 5), CustomTextFieldUserWrapper( @@ -1163,25 +1317,29 @@ class PersonalDetailsState extends State { style: GoogleFonts.poppins(fontSize: 12, color: Colors.black), decoration: InputDecoration( border: InputBorder.none, - contentPadding: - EdgeInsets.symmetric(horizontal: 10), // Proper padding + contentPadding: EdgeInsets.symmetric( + horizontal: 10, + ), // Proper padding ), - onChanged: widget.isViewMode - ? null - : (newValue) { - setState(() { - selectedRole = newValue; - isTravelAgent = selectedRole == "5"; - // Pass the result back to parent - widget.onUserTypeChanged?.call(isTravelAgent); - }); - widget.onRoleChanged?.call(newValue); - }, - items: apiRoleData?.map>((item) { - return DropdownMenuItem( - value: item['dropdown_key'], // ID as value - child: Text(item['dropdown_value'] ?? "Select Role")); - }).toList(), + onChanged: + widget.isViewMode + ? null + : (newValue) { + setState(() { + selectedRole = newValue; + isTravelAgent = selectedRole == "5"; + // Pass the result back to parent + widget.onUserTypeChanged?.call(isTravelAgent); + }); + widget.onRoleChanged?.call(newValue); + }, + items: + apiRoleData?.map>((item) { + return DropdownMenuItem( + value: item['dropdown_key'], // ID as value + child: Text(item['dropdown_value'] ?? "Select Role"), + ); + }).toList(), hint: Text("Select Role"), disabledHint: Text( selectedRole ?? "Select Role", @@ -1200,10 +1358,11 @@ class PersonalDetailsState extends State { children: [ Text( "Address", - style: GoogleFonts.poppins( - fontSize: 12, - fontWeight: FontWeight.w600, - color: Color(0xFF575A74)) + style: GoogleFonts.poppins( + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74), + ), ), SizedBox(height: 5), CustomTextFieldUserWrapper( @@ -1219,8 +1378,10 @@ class PersonalDetailsState extends State { keyboardType: TextInputType.multiline, decoration: InputDecoration( labelText: "Address", - labelStyle: - GoogleFonts.poppins(fontSize: 12, color: Colors.grey), + labelStyle: GoogleFonts.poppins( + fontSize: 12, + color: Colors.grey, + ), floatingLabelBehavior: FloatingLabelBehavior.never, border: InputBorder.none, // contentPadding: EdgeInsets.symmetric(vertical: 16), @@ -1239,10 +1400,11 @@ class PersonalDetailsState extends State { children: [ Text( "Password", - style: GoogleFonts.poppins( - fontSize: 12, - fontWeight: FontWeight.w600, - color: Color(0xFF575A74)) + style: GoogleFonts.poppins( + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74), + ), ), SizedBox(height: 5), CustomTextFieldUserWrapper( @@ -1259,8 +1421,10 @@ class PersonalDetailsState extends State { }, decoration: InputDecoration( labelText: "Password", - labelStyle: - GoogleFonts.poppins(fontSize: 12, color: Colors.grey), + labelStyle: GoogleFonts.poppins( + fontSize: 12, + color: Colors.grey, + ), floatingLabelBehavior: FloatingLabelBehavior.never, border: InputBorder.none, contentPadding: EdgeInsets.symmetric(vertical: 16), diff --git a/lib/Screens/userManagement/create_user/traveller_details.dart b/lib/Screens/userManagement/create_user/traveller_details.dart index 7872878..df12175 100644 --- a/lib/Screens/userManagement/create_user/traveller_details.dart +++ b/lib/Screens/userManagement/create_user/traveller_details.dart @@ -1069,8 +1069,8 @@ class TravellerDetailsState extends State { "Passport Number", style: GoogleFonts.poppins( fontSize: 12, - fontWeight: FontWeight.w400, - color: Colors.black, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74), ), ), SizedBox(height: 5), @@ -1111,8 +1111,8 @@ class TravellerDetailsState extends State { "Place of Issue", style: GoogleFonts.poppins( fontSize: 12, - fontWeight: FontWeight.w400, - color: Colors.black, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74), ), ), SizedBox(height: 5), @@ -1179,8 +1179,8 @@ class TravellerDetailsState extends State { "Date of Issue", style: GoogleFonts.poppins( fontSize: 12, - fontWeight: FontWeight.w400, - color: Colors.black, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74), ), ), SizedBox(height: 5), @@ -1272,8 +1272,8 @@ class TravellerDetailsState extends State { "Date of Expiry", style: GoogleFonts.poppins( fontSize: 12, - fontWeight: FontWeight.w400, - color: Colors.black, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74), ), ), SizedBox(height: 5), @@ -1365,8 +1365,8 @@ class TravellerDetailsState extends State { "Passport Document", style: GoogleFonts.poppins( fontSize: 12, - fontWeight: FontWeight.w400, - color: Colors.black, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74), ), ), SizedBox(height: 5), @@ -1558,8 +1558,8 @@ class TravellerDetailsState extends State { "Id Number", style: GoogleFonts.poppins( fontSize: 12, - fontWeight: FontWeight.w400, - color: Colors.black, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74), ), ), SizedBox(height: 5), @@ -1607,8 +1607,8 @@ class TravellerDetailsState extends State { "Id Type", style: GoogleFonts.poppins( fontSize: 12, - fontWeight: FontWeight.w400, - color: Colors.black, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74), ), ), SizedBox(height: 5), @@ -1696,8 +1696,8 @@ class TravellerDetailsState extends State { "Full Name As ID", style: GoogleFonts.poppins( fontSize: 12, - fontWeight: FontWeight.w400, - color: Colors.black, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74), ), ), SizedBox(height: 5), @@ -1785,8 +1785,8 @@ class TravellerDetailsState extends State { "Seat Preference", style: GoogleFonts.poppins( fontSize: 12, - fontWeight: FontWeight.w400, - color: Colors.black, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74), ), ), SizedBox(height: 5), @@ -1878,8 +1878,8 @@ class TravellerDetailsState extends State { "Meal Preference", style: GoogleFonts.poppins( fontSize: 12, - fontWeight: FontWeight.w400, - color: Colors.black, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74), ), ), SizedBox(height: 5), @@ -1924,8 +1924,8 @@ class TravellerDetailsState extends State { "Additional Information", style: GoogleFonts.poppins( fontSize: 12, - fontWeight: FontWeight.w400, - color: Colors.black, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74), ), ), SizedBox(height: 5), @@ -2015,8 +2015,8 @@ class TravellerDetailsState extends State { "Seat Preference", style: GoogleFonts.poppins( fontSize: 12, - fontWeight: FontWeight.w400, - color: Colors.black, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74), ), ), SizedBox(height: 5), @@ -2114,8 +2114,8 @@ class TravellerDetailsState extends State { "Meal Preference", style: GoogleFonts.poppins( fontSize: 12, - fontWeight: FontWeight.w400, - color: Colors.black, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74), ), ), SizedBox(height: 5), @@ -2160,8 +2160,8 @@ class TravellerDetailsState extends State { "Additional Information", style: GoogleFonts.poppins( fontSize: 12, - fontWeight: FontWeight.w400, - color: Colors.black, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74), ), ), SizedBox(height: 5), @@ -2213,8 +2213,8 @@ class TravellerDetailsState extends State { "Emergency Contact Number", style: GoogleFonts.poppins( fontSize: 12, - fontWeight: FontWeight.w400, - color: Colors.black, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74), ), ), SizedBox(height: 5), @@ -2292,8 +2292,8 @@ class TravellerDetailsState extends State { "Forex Pre-Paid Card Number", style: GoogleFonts.poppins( fontSize: 12, - fontWeight: FontWeight.w400, - color: Colors.black, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74), ), ), SizedBox(height: 5), @@ -2362,8 +2362,8 @@ class TravellerDetailsState extends State { "Forex Expiry Date", style: GoogleFonts.poppins( fontSize: 12, - fontWeight: FontWeight.w400, - color: Colors.black, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74), ), ), SizedBox(height: 5), @@ -2522,8 +2522,8 @@ class TravellerDetailsState extends State { "Airline", style: GoogleFonts.poppins( fontSize: 12, - fontWeight: FontWeight.w400, - color: Colors.black, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74), ), ), SizedBox(height: 5), @@ -2538,12 +2538,7 @@ class TravellerDetailsState extends State { height: 40, child: hasAirlineCountryData - ? Center( - child: Transform.scale( - scale: 0.5, - child: CircularProgressIndicator(), - ), - ) + ? CircularProgressIndicator() : DropdownSearch( // selectedItem: countryMap[selectedCountry], // selectedItem: entry['airline'] != null @@ -2668,8 +2663,8 @@ class TravellerDetailsState extends State { "Frequent Flyer Information", style: GoogleFonts.poppins( fontSize: 12, - fontWeight: FontWeight.w400, - color: Colors.black, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74), ), ), SizedBox(height: 5), @@ -2807,8 +2802,8 @@ class TravellerDetailsState extends State { "Hotel", style: GoogleFonts.poppins( fontSize: 12, - fontWeight: FontWeight.w400, - color: Colors.black, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74), ), ), SizedBox(height: 5), @@ -2822,12 +2817,7 @@ class TravellerDetailsState extends State { height: 40, child: hasAirlineCountryData - ? Center( - child: Transform.scale( - scale: 0.5, - child: CircularProgressIndicator(), - ), - ) + ? CircularProgressIndicator() : DropdownSearch( selectedItem: (entry["hotel_id"] != null && @@ -2917,8 +2907,8 @@ class TravellerDetailsState extends State { "Hotel Membership Number", style: GoogleFonts.poppins( fontSize: 12, - fontWeight: FontWeight.w400, - color: Colors.black, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74), ), ), SizedBox(height: 5), @@ -3060,8 +3050,8 @@ class TravellerDetailsState extends State { "Country", style: GoogleFonts.poppins( fontSize: 12, - fontWeight: FontWeight.w400, - color: Colors.black, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74), ), ), SizedBox(height: 5), @@ -3189,8 +3179,8 @@ class TravellerDetailsState extends State { "Visa Type", style: GoogleFonts.poppins( fontSize: 12, - fontWeight: FontWeight.w400, - color: Colors.black, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74), ), ), SizedBox(height: 5), @@ -3233,6 +3223,112 @@ 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; @@ -3273,11 +3369,11 @@ class TravellerDetailsState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - "ValidFrom", + "Valid From", style: GoogleFonts.poppins( fontSize: 12, - fontWeight: FontWeight.w400, - color: Colors.black, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74), ), ), SizedBox(height: 5), @@ -3361,11 +3457,11 @@ class TravellerDetailsState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - "Valid UpTo", + "Valid To", style: GoogleFonts.poppins( fontSize: 12, - fontWeight: FontWeight.w400, - color: Colors.black, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74), ), ), SizedBox(height: 5), diff --git a/lib/routes/custom_router.dart b/lib/routes/custom_router.dart index a5199cc..d4c12fd 100644 --- a/lib/routes/custom_router.dart +++ b/lib/routes/custom_router.dart @@ -29,6 +29,7 @@ import '../Screens/department/department_list.dart'; import '../Screens/costCenter/costCenter_list.dart'; import '../Screens/dashboard/status_dashboard.dart'; import '../Screens/hotels/hotels_list.dart'; +import '../Screens/traveller/travellerList.dart'; final GoRouter router = GoRouter( routes: [ @@ -111,6 +112,10 @@ final GoRouter router = GoRouter( path: '/statusdashboard', builder: (context, state) => StatusDashboard(), ), + GoRoute( + path: '/traveller', + builder: (context, state) => TravellerList(), + ), GoRoute( path: '/CreateGroup', pageBuilder: diff --git a/lib/routes/organizationSetting.dart b/lib/routes/organizationSetting.dart index 432ac13..a4bf4d3 100644 --- a/lib/routes/organizationSetting.dart +++ b/lib/routes/organizationSetting.dart @@ -100,6 +100,13 @@ class OrganizationSettingState extends State { 'label': 'Hotels', 'description': 'Create and Edit Hotels' }, + { + 'value': '/traveller', + 'icon': Icons.travel_explore, + 'label': 'Traveller', + 'description': 'Create and Edit Traveller' + }, + ]; // List rows = []; diff --git a/lib/services/apiService.dart b/lib/services/apiService.dart index 5d60c78..99e61e8 100644 --- a/lib/services/apiService.dart +++ b/lib/services/apiService.dart @@ -1190,4 +1190,48 @@ class ApiService { throw Exception('Failed to load plans'); } } + + Future> getTravellerDetailsFind(int id) async { + final String apiUrldata = '$apiUrl/api/travellers/find?traveller_id=$id'; + + + //c + 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); + + if (!data.containsKey('data') || data['data'] is! List) { + throw Exception("Invalid response format: 'data' field is missing or not a List"); + } + + final List> listData = + List>.from(data['data']); + + if (listData.isEmpty) { + throw Exception("No Traveller data found with ID $id"); + } + + return listData[0]; + } catch (e) { + throw Exception('Error parsing response: $e'); + } + } else { + throw Exception('Failed to load Hotel details'); + } + } + }