import 'dart:async'; import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:fluttertoast/fluttertoast.dart'; import 'package:frontend/utils/auth_utils.dart'; import 'package:go_router/go_router.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:http/http.dart' as http; import 'package:responsive_builder/responsive_builder.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'dart:typed_data'; import 'package:universal_html/html.dart' as html; import 'package:http_parser/http_parser.dart' as http_parser; import '../../config/apiUrl.dart'; import '../../routes/custom_appBar.dart'; import '../../routes/custom_drawer.dart'; import '../../services/apiService.dart'; import '../../utils/pagination.dart'; import '../../widgets/custom_popup.dart'; // import '../../widgets/popup_userList_action.dart'; class UserListScreen extends StatefulWidget { @override _UserListScreenState createState() => _UserListScreenState(); } class _UserListScreenState extends State { final ApiService apiService = ApiService(); late Future> futureUsers; late bool _dialogShown = false; late Map userSingleData; List? apiCountryData; String? selectedUserId; String? orgId; Color? layoutColor; Color? bodyColor; List allUsers = []; List filteredUsers = []; TextEditingController searchController = TextEditingController(); int currentPage = 0; int itemsPerPage = 10; bool _isUploading = false; bool _isDownloading = false; @override void initState() { super.initState(); futureUsers = fetchUsers(); // // futureUsers.then((users) { // setState(() { // allUsers = users; // }); // }); // // WidgetsBinding.instance.addPostFrameCallback((_) { // fetchCountryList(); // loadInitialData(); // }); _checkAuthAndLoadData(); } void _checkAuthAndLoadData() async { final String? token = await getToken(); // Your async function to get token final roleUser = await getRoleUser(); if (token == null || token.isEmpty) { // Token doesn't exist → redirect to login context.go( "/", ); // or use: router.go("/") if you're using `GoRouter` directly return; } else { if (roleUser != null && (roleUser == 'Org Admin' || roleUser == 'Travel Admin')) { futureUsers = fetchUsers(); futureUsers.then((users) { setState(() { allUsers = users; }); }); fetchCountryList(); loadInitialData(); } else { apiService.logout(context); } // WidgetsBinding.instance.addPostFrameCallback((_) { // fetchCountryList(); // loadInitialData(); // }); } } 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> fetchUsers() async { // return []; orgId = await getOrgId(); final String apiUrlData = '$apiUrl/api/users?org_id=$orgId&for=table_view'; 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', 'app-signature': 'ts-traveltool-2025-signature-123456', }, ); if (response.statusCode == 200) { final data = json.decode(response.body); return data['data']; // Returning raw JSON list } else if (response.statusCode == 403) { print("403-FORB"); await apiService.logout(context); return []; // throw Exception('Failed to load users'); } 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', 'app-signature': 'ts-traveltool-2025-signature-123456', }, ); 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 if (response.statusCode == 403) { print("403-FORB"); await apiService.logout(context); return null; // throw Exception('Failed to load users'); } else { throw Exception('Failed to load plans'); } } void handleDelete(userId) { print("handDel - $userId"); } void _showConfirmationDialog( String subtitle, List successList, List> failedList, ) { _dialogShown = true; showDialog( context: context, builder: (context) => AlertDialog( // title: Text("User Details"), title: Text( "Upload Status - $subtitle", style: GoogleFonts.poppins( fontSize: 18, fontWeight: FontWeight.w600, color: Colors.black, ), overflow: TextOverflow.ellipsis, ), content: SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Text(subtitle, style: TextStyle(fontWeight: FontWeight.bold)), // // SizedBox(height: 12), Text( "Success Report :", style: GoogleFonts.poppins( fontSize: 14, fontWeight: FontWeight.w400, color: Colors.black, ), ), // if (successList.isNotEmpty) // ...successList.asMap().entries.map((entry) => Text("${entry.key + 1}) ${entry.value} created.")) if (successList.isNotEmpty) SizedBox( height: 250, child: SingleChildScrollView( scrollDirection: Axis.horizontal, child: SingleChildScrollView( scrollDirection: Axis.vertical, child: DataTable( columns: [ DataColumn( label: Text( 'S.No', style: GoogleFonts.poppins( fontSize: 14, fontWeight: FontWeight.w600, color: Colors.black, ), ), ), DataColumn( label: Text( 'Data', style: GoogleFonts.poppins( fontSize: 14, fontWeight: FontWeight.w600, color: Colors.black, ), ), ), ], rows: successList.asMap().entries.map((entry) { final newIndex = entry.key + 1; final data = entry.value ?.toString() .trim() .isNotEmpty == true ? entry.value.toString() : '-'; return DataRow( cells: [ DataCell( Text( '$newIndex', style: GoogleFonts.poppins( fontSize: 14, fontWeight: FontWeight.w400, color: Colors.black, ), ), ), DataCell( SizedBox( width: 460, child: Text( data, style: GoogleFonts.poppins( fontSize: 14, fontWeight: FontWeight.w400, color: Colors.black, ), overflow: TextOverflow.ellipsis, ), ), ), ], ); }).toList(), ), ), ), ) else Text( "-- no data. --", style: GoogleFonts.poppins( fontSize: 14, fontWeight: FontWeight.w200, color: Colors.black, ), ), SizedBox(height: 12), // Text("Failed Report :", style: TextStyle(fontWeight: FontWeight.bold)), Text( "Failed Report :", style: GoogleFonts.poppins( fontSize: 14, fontWeight: FontWeight.w400, color: Colors.black, ), ), // if (failedList.isNotEmpty) // ...failedList.asMap().entries.map((entry) { // final data = entry.value["data"] ?? "-"; // final reason = entry.value["reason"] ?? "Unknown error"; // return Text("${entry.key + 1}) $data - unable to create due to \"$reason\"."); // }) if (failedList.isNotEmpty) SizedBox( height: 250, // Vertical scroll height child: SingleChildScrollView( scrollDirection: Axis.horizontal, // Enable horizontal scrolling child: SingleChildScrollView( scrollDirection: Axis.vertical, // Enable vertical scrolling child: DataTable( columns: [ DataColumn( label: Text( 'S.No', style: GoogleFonts.poppins( fontSize: 14, fontWeight: FontWeight.w600, color: Colors.black, ), ), ), DataColumn( label: Text( 'Data', style: GoogleFonts.poppins( fontSize: 14, fontWeight: FontWeight.w600, color: Colors.black, ), ), ), DataColumn( label: Text( 'Reason', style: GoogleFonts.poppins( fontSize: 14, fontWeight: FontWeight.w600, color: Colors.black, ), ), ), ], rows: failedList.asMap().entries.map((entry) { final index = entry.key + 1; final rawData = entry.value['data']; final data = (rawData == null || rawData.toString().trim().isEmpty) ? '-' : rawData.toString(); final reason = entry.value['reason'] ?? 'Unknown error'; return DataRow( cells: [ DataCell( Text( '$index', style: GoogleFonts.poppins( fontSize: 14, fontWeight: FontWeight.w400, color: Colors.black, ), ), ), DataCell( SizedBox( width: 180, child: Text( data, style: GoogleFonts.poppins( fontSize: 14, fontWeight: FontWeight.w400, color: Colors.black, ), overflow: TextOverflow.ellipsis, ), ), ), DataCell( SizedBox( width: 280, child: Text( reason, style: GoogleFonts.poppins( fontSize: 14, fontWeight: FontWeight.w400, color: Colors.black, ), overflow: TextOverflow.ellipsis, ), ), ), ], ); }).toList(), ), ), ), ) else Text( "-- no data. --", style: GoogleFonts.poppins( fontSize: 14, fontWeight: FontWeight.w200, color: Colors.black, ), ), ], ), ), actions: [ TextButton( onPressed: () { Navigator.pop(context); _dialogShown = false; }, child: Text("Close"), ), ], ), ); } Future handleDownload() async { final result = await apiService.getDownloadUserTemplateForUpload(context); final String displayBackgroundColor = result['status'] ? '#28a745' : '#dc1c13'; final Color displayColor = result['status'] ? Colors.green : Colors.redAccent; final String displayMessage = result['message']; // final bool status = result['status'] == true; Fluttertoast.showToast( msg: displayMessage, toastLength: Toast.LENGTH_SHORT, gravity: ToastGravity.CENTER, timeInSecForIosWeb: 2, backgroundColor: displayColor, textColor: Colors.white, fontSize: 18.0, webBgColor: "linear-gradient(to right, $displayBackgroundColor, $displayBackgroundColor)", ); } Future handleUpload() async { final Completer completer = Completer(); final String apiUrldata = '$apiUrl/api/user/userUpload'; // api final String? token = await getToken(); // 2kn if (token == null) { throw Exception('Token not found. Please log in.'); } html.FileUploadInputElement uploadInput = html.FileUploadInputElement(); // uploadInput.accept = '.xlsx,.xls,.csv'; uploadInput.accept = '.xlsx'; uploadInput.click(); uploadInput.onChange.listen((e) async { final file = uploadInput.files!.first; final reader = html.FileReader(); reader.readAsArrayBuffer(file); await reader.onLoadEnd.first; print(' file - extn : ${file.name.endsWith(".xlsx")}'); if (!file.name.endsWith('.xlsx')) { Fluttertoast.showToast( msg: 'Invalid file format. Please upload a .xlsx file.', toastLength: Toast.LENGTH_SHORT, gravity: ToastGravity.CENTER, timeInSecForIosWeb: 2, backgroundColor: Colors.redAccent, textColor: Colors.white, fontSize: 18.0, webBgColor: "linear-gradient(to right, #dc1c13, #dc1c13)", ); print("Error: Not a valid .xlsx file"); completer.complete(); // Complete even on error return; // Wait for upload to finish before proceeding } if (reader.readyState == html.FileReader.DONE) { Uint8List? fileBytes = reader.result as Uint8List?; if (fileBytes != null) { // Save fileBytes to local storage final jsonString = json.encode(fileBytes); html.window.localStorage['fileBytes'] = jsonString; print('File Bytes: $fileBytes'); if (fileBytes == null) { print('File Bytes: null'); Fluttertoast.showToast( msg: 'Issues occur in file format conversation', toastLength: Toast.LENGTH_SHORT, gravity: ToastGravity.CENTER, timeInSecForIosWeb: 2, backgroundColor: Colors.redAccent, textColor: Colors.white, fontSize: 18.0, webBgColor: "linear-gradient(to right, #dc1c13, #dc1c13)", ); completer.complete(); return; // No file selected } else { print('File Bytes: CALLING API'); // // Prepare form data // final formData = html.FormData(); // formData.appendBlob('file', html.Blob([fileBytes]), fileName); // Create a multipart request final request = http.MultipartRequest( 'POST', Uri.parse(apiUrldata), ); // Attach the file to the request // Set authorization token in headers request.headers['Authorization'] = 'Bearer $token'; request.headers['app-signature'] = 'ts-traveltool-2025-signature-123456'; // request.files.add(http.MultipartFile.fromBytes('file', fileBytes, filename: fileName)); request.files.add( http.MultipartFile.fromBytes( 'user_file', fileBytes, filename: 'user_file.xlsx', ), ); print('request : $request'); // Send the request final response = await request.send(); // Read response stream as a string final responseString = await response.stream.bytesToString(); // Check the status code of the response if (response.statusCode == 200) { Map responseData = json.decode(responseString); final message = responseData['message'] ?? ''; final status = responseData['status'] ?? ''; final data = responseData['data'] ?? {}; final List successList = List.from( data['successfulUsers'] ?? [], ); final List> failedList = List>.from( (data['failedUsers'] ?? []).map( (item) => Map.from(item), ), ); _showConfirmationDialog(message, successList, failedList); // final successfulUsers = List.from(data['successfulUsers'] ?? []); // final failedUsers = List.from(data['failedUsers'] ?? []); // Unified SnackBar logic // ScaffoldMessenger.of(context).showSnackBar( // SnackBar( // content: Text( // combinedMessage, // style: TextStyle(color: Colors.white), // ), // backgroundColor: Colors.green, // behavior: SnackBarBehavior.floating, // // duration: Duration(seconds: 6), // ), // ); // Refresh list if needed refreshUserList(); } else { // For non-200 responses final errorMsg = 'Failed to upload file: ${response.reasonPhrase}'; Fluttertoast.showToast( msg: errorMsg, toastLength: Toast.LENGTH_SHORT, gravity: ToastGravity.CENTER, timeInSecForIosWeb: 2, backgroundColor: Colors.redAccent, textColor: Colors.white, fontSize: 18.0, webBgColor: "linear-gradient(to right, #dc1c13, #dc1c13)", ); print(errorMsg); } } } return completer.future; // Wait for upload to finish before proceeding } // if (!file.name.endsWith('.xlsx')) { // print("Error: Not a valid .xlsx file"); // return; // } // reader.onLoadEnd.listen((e) async { // final data = reader.result as Uint8List; // // final request = http.MultipartRequest('POST', Uri.parse(apiUrldata)); // request.headers['Authorization'] = 'Bearer $token'; // // final multipartFile = http.MultipartFile.fromBytes( // 'file', // must match the API expected field name // data, // filename: file.name, // contentType: MediaType('application', 'vnd.openxmlformats-officedocument.spreadsheetml.sheet'), // ); // // request.files.add(multipartFile); // // print('Uploading file: ${file.name}'); // // try { // final response = await request.send(); // final respStr = await response.stream.bytesToString(); // print("Response status: ${response.statusCode}"); // print("Response body: $respStr"); // if (response.statusCode == 200) { // print("Upload successful!"); // } else { // print("Upload failed with status: ${response.statusCode}"); // } // } catch (e) { // print("Error uploading file: $e"); // } // }); // // reader.readAsArrayBuffer(file); }); } 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'; request.headers['app-signature'] = 'ts-traveltool-2025-signature-123456'; // 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(() { print("Search cleared 1"); searchController.clear(); // or wrap in setState if needed print("Search cleared - ${searchController.text}"); filteredUsers = []; futureUsers = fetchUsers(); futureUsers.then((users) { setState(() { allUsers = users; }); }); // 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(() { filteredUsers = allUsers.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(); currentPage = 0; }); print("filteredPlans: $filteredUsers"); } @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)), // 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( 'Users', style: GoogleFonts.poppins( fontSize: isDesktop ? 16 : 14, fontWeight: FontWeight.w600, color: Colors.black, ), ), ], ), if (isDesktop) SizedBox(width: MediaQuery.of(context).size.width * 0.23), // SizedBox(width: MediaQuery.of(context).size.width * 0.15), 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, // color: layoutColor!, width: 1, ), ), ), style: GoogleFonts.poppins(fontSize: 12), ), ), // SizedBox(width: 16), Spacer(), // OutlinedButton( // onPressed: () { // apiService.getDownloadUserTemplateForUpload(); // }, // style: OutlinedButton.styleFrom( // backgroundColor: Colors.white, // foregroundColor: Color(0xFF114D8B), // side: BorderSide(color: Colors.white), // padding: EdgeInsets.symmetric( // horizontal: 20, // vertical: 16, // ), // ), // child: Text("Sample Template Download"), // ), IconButton( icon: Image.asset( 'assets/images/IconsImg/download.png', width: 25, height: 25, ), tooltip: 'Sample Template Download', onPressed: () { apiService.getDownloadUserTemplateForUpload(context); }, ), SizedBox(width: 5), // Upload Button // OutlinedButton( // onPressed: handleUpload, // style: OutlinedButton.styleFrom( // backgroundColor: Colors.white, // foregroundColor: Color(0xFF114D8B), // side: BorderSide(color: Colors.white), // padding: EdgeInsets.symmetric( // horizontal: 20, // vertical: 16, // ), // ), // child: Text("User Bulk Upload"), // ), // IconButton( // icon: Image.asset( // 'assets/images/IconsImg/upload.png', // width: 25, // height: 25, // ), // tooltip: 'User Bulk Upload', // onPressed: () { // handleUpload(); // }, // ), // UPLOAD BUTTON Align( alignment: Alignment.centerLeft, child: _isUploading ? const SizedBox( width: 28, height: 28, child: CircularProgressIndicator(strokeWidth: 3), ) : IconButton( icon: Image.asset( 'assets/images/IconsImg/upload.png', width: 25, height: 25, ), tooltip: 'User Bulk Upload', onPressed: () async { setState(() => _isUploading = true); await handleUpload(); setState(() => _isUploading = false); }, ), ), // Spacer(), SizedBox(width: 5), 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 { List users = await futureUsers; // Print the resolved value print("CREATELIAS - $users"); context.go( "/CreateUserDetails", // extra: { // // 'apiCountryData': apiCountryData, // 'apiUserData': users, // } ); }, child: Row( mainAxisSize: MainAxisSize.min, // Ensures content fits nicely children: [ Text( "Add New User", 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 for a User", 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: futureUsers, builder: (context, snapshot) { final adjHgt = MediaQuery.of(context).size.height; 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), SizedBox(height: adjHgt / 4), Text( "No Data Found", textAlign: TextAlign.center, style: GoogleFonts.poppins( fontSize: 20, fontWeight: FontWeight.bold, color: Colors.grey, ), ), const SizedBox(height: 20), // Text( // "Please Create User", // textAlign: TextAlign.center, // style: GoogleFonts.poppins( // fontSize: 16, // color: Colors.grey, // ), // ), // const SizedBox(height: 20), ], ), ), ); } List users = filteredUsers.isNotEmpty ? filteredUsers : allUsers; 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( 'UserName', style: GoogleFonts.poppins( fontSize: 13, fontWeight: FontWeight.w600, ), ), ), DataColumn( label: Text( 'Email ID', style: GoogleFonts.poppins( fontSize: 13, fontWeight: FontWeight.w600, ), ), ), DataColumn( label: Text( 'Role', 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: paginatedUser.map((user) { String userId = user['user_id'].toString(); // Get user ID bool isSelected = selectedUserId == userId; return DataRow( cells: [ DataCell( Text( "${user['first_name'] ?? ''} ${user['last_name'] ?? ''}", style: TextStyle( fontSize: 13, fontFamily: "Inter", ), ), ), DataCell( Text( user['email'] ?? '', style: TextStyle( fontSize: 13, fontFamily: "Inter", ), ), ), DataCell( Text( user['role_value'] ?? 'N/A', style: TextStyle( fontSize: 13, fontFamily: "Inter", ), softWrap: true, overflow: TextOverflow.ellipsis, ), ), DataCell( MouseRegion( cursor: SystemMouseCursors.click, child: GestureDetector( onTap: () { handleToggleUserStatus( user['user_id'], user['is_active'], user, ); }, child: Text( user['is_active'] == "1" ? "Active" : "Inactive", style: TextStyle( color: user['is_active'] == "1" ? Colors.green : Colors.grey, fontFamily: "Inter", fontWeight: FontWeight.w400, ), ), ), ), ), DataCell( Row( mainAxisAlignment: MainAxisAlignment.start, children: [ GestureDetector( child: Tooltip( message: 'Edit User Details', child: Image.asset( 'assets/images/IconsImg/edit.png', width: 20, height: 15, ), ), onTap: () async { // Navigator.pop(context); // Close the menu // final userId = user['user_id']; final userId = int.parse( user['user_id'].toString(), ); final usersData = await apiService .getSingleUser( context, userId, ); context.go( "/CreateUserDetails", extra: { "selectedUser": usersData, "isViewMode": false, }, ); }, ), ], ), ), // DataCell( // UserActionsMenu( // user: user, // 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 user = 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( user['role_value'] ?? 'N/A', style: GoogleFonts.poppins( fontSize: 10, color: Colors.black87, fontWeight: FontWeight.w700, ), ), Row( mainAxisAlignment: MainAxisAlignment.start, children: [ GestureDetector( child: Tooltip( message: 'Edit User Details', child: Image.asset( 'assets/images/IconsImg/edit.png', width: 20, height: 15, ), ), onTap: () async { // Navigator.pop(context); // Close the menu // final userId = user['user_id']; final userId = int.parse( user['user_id'].toString(), ); final usersData = await apiService .getSingleUser(context, userId); context.go( "/CreateUserDetails", extra: { "selectedUser": usersData, "isViewMode": false, }, ); }, // onTap: () async { // Navigator.pop(context); // Close the menu // final userId = int.parse(user['user_id'].toString()); // final usersData = await apiService.getSingleUser(userId); // context.go("/CreateUserDetails", extra: { // "selectedUser": usersData, // "isViewMode": true, // }); // }, ), ], ), // UserActionsMenu( // user: user, // 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( "${user['first_name'] ?? ''} ${user['last_name'] ?? ''}", style: GoogleFonts.poppins( fontSize: 12, color: Colors.black87, fontWeight: FontWeight.w500, ), ), ], ), ], ), SizedBox(height: 2), // Name Row( children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( user['email'] ?? '', style: GoogleFonts.poppins( fontSize: 10, color: Colors.black87, ), ), ], ), Spacer(), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ GestureDetector( onTap: () { handleToggleUserStatus( user['user_id'], user['is_active'], user, ); }, child: Text( user['is_active'] == "1" ? "Active" : "Inactive", style: TextStyle( color: user['is_active'] == "1" ? Colors.green : Colors.grey, fontFamily: "Inter", fontSize: 11, fontWeight: FontWeight.w400, ), ), ), ], ), ], ), // Actions // Actions ], ), ), ); }, ); } return Expanded( child: Column( // mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ // Expanded( // child: // isDesktop // ? SingleChildScrollView( // scrollDirection: Axis.vertical, // child: table, // <-- your existing table // ) // : buildMobileCardView(paginatedUser), // ), Expanded( child: isDesktop ? (searchController.text.isNotEmpty && filteredUsers.isEmpty ? Center( child: Text( "No Matches Found", style: GoogleFonts.poppins( fontSize: 14, color: Colors.grey, ), ), ) : SingleChildScrollView( scrollDirection: Axis.vertical, child: table, )) : (searchController.text.isNotEmpty && filteredUsers.isEmpty ? Center( child: Text( "No Matches Found", style: GoogleFonts.poppins( fontSize: 14, color: Colors.grey, ), ), ) : buildMobileCardView(paginatedUser)), // child: isDesktop // ? SingleChildScrollView( // scrollDirection: Axis.vertical, // child: table, // <-- your existing table // ) // : buildMobileCardView(paginatedPlans), ), 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; }); }, ), ], ), ); }, ), ], ), ), ), ); } }