import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:http/http.dart' as http; import 'package:responsive_builder/responsive_builder.dart'; import 'package:shared_preferences/shared_preferences.dart'; import '../../config/apiUrl.dart'; import '../../routes/custom_appBar.dart'; import '../../routes/custom_drawer.dart'; import '../../services/apiService.dart'; import '../../utils/auth_utils.dart'; import '../../utils/pagination.dart'; import '../../widgets/custom_breadcrumb_navigation.dart'; import 'groupDetails.dart'; class GroupList extends StatefulWidget { @override _GroupListState createState() => _GroupListState(); } class _GroupListState extends State { final ApiService apiService = ApiService(); // late Future> futureGroups; Future>? futureGroups; late Map groupSingleData; List? apiCountryData; String? selectedGroupId; String? orgId; Color? layoutColor; Color? bodyColor; List allGroups = []; List filteredGroups = []; TextEditingController searchController = TextEditingController(); int currentPage = 0; int itemsPerPage = 10; List? apiAllGroups; @override void initState() { super.initState(); _checkAuthAndLoadData(); // futureGroups = fetchGroups(); // // futureGroups.then((object) { // setState(() { // allGroups = object; // }); // }); // // WidgetsBinding.instance.addPostFrameCallback((_) { // loadInitialData(); // fetchGroups(); // loadAllGroups(); // }); // futurePlans = fetchPlans(); } void _checkAuthAndLoadData() async { final String? token = await getToken(); // Your async function to get token 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; } if (!mounted) return; try { futureGroups = fetchGroups(); futureGroups?.then((object) { setState(() { allGroups = object; }); }); loadInitialData(); fetchGroups(); loadAllGroups(); } catch (e) { print("group : $e"); } } 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> fetchGroups() async { // return []; final result = await apiService.fetchAllGroup(context); return result; // Returning raw JSON list } Future loadAllGroups() async { try { final result = await apiService.fetchAllGroup(context); setState(() { allGroups = result; filteredGroups = result; searchController.text = ""; }); print("Fetched services: $allGroups"); } catch (e) { print('Error fetching role list: $e'); } } void filterGroups(String query) { final lowerQuery = query.toLowerCase(); setState(() { filteredGroups = allGroups.where((object) { final isActiveStatus = object['is_active'] == "1" ? "active" : "inactive"; return (object['group_id']?.toLowerCase().contains(lowerQuery) ?? false) || (object['name']?.toLowerCase().contains(lowerQuery) ?? false) || (object['domestic_policy_name']?.toLowerCase().contains( lowerQuery, ) ?? false) || (object['international_policy_name']?.toLowerCase().contains( lowerQuery, ) ?? false) || (object['description']?.toLowerCase().contains(lowerQuery) ?? false) || (isActiveStatus.contains(lowerQuery)); }).toList(); currentPage = 0; }); print("filteredGroups: $filteredGroups"); } void handleActiveStatus( Map groupData, String groupId, String currentStatus, ) async { print("Toggling group status - $groupId (Current: $currentStatus)"); final String apiUrlData = '$apiUrl/api/groups/update/$groupId'; // API for updating group 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"); try { final response = await http.put( Uri.parse(apiUrlData), headers: { 'Authorization': 'Bearer $token', 'Content-Type': 'application/json', 'app-signature': 'ts-traveltool-2025-signature-123456', }, body: jsonEncode({ "is_active": newStatus, // Set new status dynamically }), ); if (response.statusCode == 200 || response.statusCode == 201) { print("Group status updated successfully to $newStatus!"); loadAllGroups(); // Refresh groups list after update } else if (response.statusCode == 403) { print("403-FORB"); await apiService.logout(context); return null; // throw Exception('Failed to load users'); } else { print("Failed to update group status. Status: ${response.statusCode}"); print("Error: ${response.body}"); } } catch (e) { print("Error updating group status: $e"); } } void deleteGroup(Map groupdata, groupId, status) { print("GroupId : $groupId"); print("Groupstatus: $status"); print("GroupsData: $groupdata"); // handleActiveStatus(groupdata, groupId, status); print("Calling handleActiveStatus with: id=$groupId, status=$status"); handleActiveStatus(groupdata, groupId.toString(), status.toString()); } Future refreshData() async { loadAllGroups(); } @override Widget build(BuildContext context) { return ResponsiveBuilder( builder: (context, sizingInfo) { bool isDesktop = sizingInfo.deviceScreenType == DeviceScreenType.desktop; return Scaffold( backgroundColor: Color(0xFFf5f5f5), appBar: CustomAppBar(isDesktop: isDesktop), drawer: CustomDrawer(isDesktop: false), body: Padding( padding: isDesktop ? EdgeInsets.symmetric( horizontal: MediaQuery.of(context).size.width * 0.1, // 30% of screen width as horizontal padding vertical: MediaQuery.of(context).size.height * 0, // 5% of screen height as vertical padding ) : EdgeInsets.all(0), child: Row(children: [Expanded(child: 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: buildGroupTable(isDesktop), ); } Widget buildGroupTable(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: [ Container( child: BreadcrumbNavigation( isDesktop: isDesktop, breadcrumbItems: [ BreadcrumbItem( title: 'Org Settings', tooltip: 'Go To Organization Settings', onTap: (context) { context.go("/OrganizationSettings"); }, ), BreadcrumbItem(title: 'Group '), ], ), ), // Text( // 'Group', // style: GoogleFonts.poppins( // fontSize: isDesktop ? 16 : 14, // fontWeight: FontWeight.w600, // color: Colors.black, // ), // ), ], ), if (isDesktop) SizedBox(width: MediaQuery.of(context).size.width * 0.145), // SizedBox(width: MediaQuery.of(context).size.width * 0.28), if (isDesktop) Container( width: MediaQuery.of(context).size.width * 0.2, height: 40, child: TextField( controller: searchController, onChanged: filterGroups, 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 { // List groups = await futureGroups; // context.go('/CreateGroup'); showDialog( context: context, builder: (context) => GroupData( isDesktop: isDesktop, groupId: null, layoutColor: layoutColor!, fetchGetGroup: refreshData, ), ); }, child: Row( mainAxisSize: MainAxisSize.min, // Ensures content fits nicely children: [ Text( "Add Group", 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: filterGroups, 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: futureGroups, builder: (context, snapshot) { final adjHgt = MediaQuery.of(context).size.height; if (futureGroups == null) { return const CircularProgressIndicator(); } 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: [ 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 Group", // textAlign: TextAlign.center, // style: GoogleFonts.poppins( // fontSize: 16, // color: Colors.grey, // ), // ), // const SizedBox(height: 20), ], ), ), ); } List groups = filteredGroups.isNotEmpty ? filteredGroups : allGroups; groups.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 paginatedGroup = groups .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( 'Group Name', style: GoogleFonts.poppins( fontSize: 13, fontWeight: FontWeight.w600, ), ), ), DataColumn( label: Text( 'Domestic Policy', style: GoogleFonts.poppins( fontSize: 13, fontWeight: FontWeight.w600, ), ), ), DataColumn( label: Text( 'International Policy', style: GoogleFonts.poppins( fontSize: 13, fontWeight: FontWeight.w600, ), ), ), DataColumn( label: Text( 'Description', 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: paginatedGroup.map((group) { String groupId = group['group_id'] .toString(); // Get group ID bool isSelected = selectedGroupId == groupId; return DataRow( cells: [ // DataCell( // Text( // "${group['name'] ?? ''}", // style: TextStyle( // fontSize: 13, // fontFamily: "Inter", // ), // ), // ), DataCell( ConstrainedBox( constraints: BoxConstraints( maxWidth: 200, ), // limit description width child: Text( "${group['name'] ?? ''}", style: TextStyle( fontSize: 13, fontFamily: "Inter", ), overflow: TextOverflow.ellipsis, maxLines: 1, // optional: show only 1 line softWrap: false, ), ), ), DataCell( Text( "${group['domestic_policy_name'] ?? 'N/A'}", style: TextStyle( fontSize: 13, fontFamily: "Inter", ), ), ), DataCell( Text( "${group['international_policy_name'] ?? 'N/A'}", style: TextStyle( fontSize: 13, fontFamily: "Inter", ), ), ), // DataCell( // Text( // "${group['description'] ?? 'N/A'}", // style: TextStyle( // fontSize: 13, // fontFamily: "Inter", // ), // ), // ), DataCell( ConstrainedBox( constraints: BoxConstraints( maxWidth: 200, ), // limit description width child: Text( "${group['description'] ?? 'N/A'}", style: TextStyle( fontSize: 13, fontFamily: "Inter", ), overflow: TextOverflow.ellipsis, maxLines: 1, softWrap: false, ), ), ), DataCell( Text( group['is_active'] == "1" ? "Active" : "Inactive", style: TextStyle( color: group['is_active'] == "1" ? Colors.green : Colors.grey, fontFamily: "Inter", fontWeight: FontWeight.w400, ), ), ), DataCell( Row( mainAxisAlignment: MainAxisAlignment.start, children: [ GestureDetector( onTap: () async { if (group['group_id'] != null) { final newGroupID = int.tryParse( group['group_id'].toString(), ); if (newGroupID != null) { final data = await apiService .getGroupDetailsFind( context, newGroupID, ); // ✅ Always an int showDialog( context: context, builder: (context) => GroupData( isDesktop: isDesktop, groupId: newGroupID, // Pass the ID groupData: data, layoutColor: layoutColor!, fetchGetGroup: refreshData, ), ); } } else { print( "something went wrong check properly", ); } }, // onTap: () { // context.go("/CreateGroup", extra: group); // }, child: Tooltip( message: 'Edit Group Details', child: Image.asset( 'assets/images/IconsImg/edit.png', width: 20, height: 15, ), ), ), SizedBox(width: 5), GestureDetector( onTap: () { final idStr = group['group_id']; final id = int.tryParse( idStr.toString(), ); if (id == null) { print("group_id is null"); return; } final status = group['is_active']; // print("GroupId : ${group['group_id']} "); deleteGroup(group, id, status); }, child: Tooltip( message: 'Delete Group Details', child: Image.asset( 'assets/images/IconsImg/delete.png', width: 20, height: 15, ), ), ), ], ), ), ], ); }).toList(), ), ); }, ); Widget buildMobileCardView(List paginatedGroup) { return ListView.builder( itemCount: paginatedGroup.length, itemBuilder: (context, index) { final object = paginatedGroup[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: [ // Row 1: Policy Name and Actions Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Expanded( child: RichText( text: TextSpan( style: GoogleFonts.poppins( fontSize: 13, color: Colors.black, ), children: [ TextSpan( text: "Group Name: ", style: GoogleFonts.poppins( fontSize: 13, fontWeight: FontWeight.w600, ), ), TextSpan( text: "${object['name'] ?? 'N/A'}", style: TextStyle( fontSize: 13, fontFamily: "Inter", fontWeight: FontWeight.normal, ), ), ], ), ), ), Row( mainAxisSize: MainAxisSize.min, children: [ GestureDetector( onTap: () async { if (object['group_id'] != null) { final newGroupID = int.tryParse( object['group_id'].toString(), ); if (newGroupID != null) { final data = await apiService .getGroupDetailsFind( context, newGroupID, ); // ✅ Always an int showDialog( context: context, builder: (context) => GroupData( isDesktop: isDesktop, groupId: newGroupID, // Pass the ID groupData: data, layoutColor: layoutColor!, fetchGetGroup: refreshData, ), ); } } else { print( "something went wrong check properly", ); } }, // onTap: () { // context.go("/CreateGroup", extra: group); // }, child: Tooltip( message: 'Edit Group Details', child: Image.asset( 'assets/images/IconsImg/edit.png', width: 20, height: 15, ), ), ), SizedBox(width: 5), GestureDetector( onTap: () { final idStr = object['group_id']; final id = int.tryParse( idStr.toString(), ); if (id == null) { print("group_id is null"); return; } final status = object['is_active']; // print("GroupId : ${group['group_id']} "); deleteGroup(object, id, status); }, child: Tooltip( message: 'Edit Group Details', child: Image.asset( 'assets/images/IconsImg/delete.png', width: 20, height: 15, ), ), ), ], ), ], ), SizedBox(height: 8), // Spacing // Row 2: RichText( text: TextSpan( style: GoogleFonts.poppins( fontSize: 13, color: Colors.black, ), children: [ TextSpan( text: "Domestic Policy: ", style: GoogleFonts.poppins( fontSize: 13, fontWeight: FontWeight.w600, ), ), TextSpan( text: "${object['domestic_policy_name'] ?? 'N/A'}", style: TextStyle( fontSize: 13, fontFamily: "Inter", fontWeight: FontWeight.normal, ), ), ], ), ), SizedBox(height: 8), // Spacing // Row 3: RichText( text: TextSpan( style: GoogleFonts.poppins( fontSize: 13, color: Colors.black, ), children: [ TextSpan( text: "International Policy: ", style: GoogleFonts.poppins( fontSize: 13, fontWeight: FontWeight.w600, ), ), TextSpan( text: "${object['international_policy_name'] ?? 'N/A'}", style: TextStyle( fontSize: 13, fontFamily: "Inter", fontWeight: FontWeight.normal, ), ), ], ), ), SizedBox(height: 8), // Spacing // Row 4: RichText( text: TextSpan( style: GoogleFonts.poppins( fontSize: 13, color: Colors.black, ), children: [ TextSpan( text: "Description: ", style: GoogleFonts.poppins( fontSize: 13, fontWeight: FontWeight.w600, ), ), TextSpan( text: "${object['description'] ?? 'N/A'}", style: TextStyle( fontSize: 13, fontFamily: "Inter", fontWeight: FontWeight.normal, ), ), ], ), ), ], ), ), ); }, ); } return Expanded( child: Column( // mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Expanded( child: isDesktop ? (searchController.text.isNotEmpty && filteredGroups.isEmpty ? Center( child: Text( "No Matches Found", style: GoogleFonts.poppins( fontSize: 14, color: Colors.grey, ), ), ) : SingleChildScrollView( scrollDirection: Axis.vertical, child: table, )) : (searchController.text.isNotEmpty && paginatedGroup.isEmpty ? Center( child: Text( "No Matches Found", style: GoogleFonts.poppins( fontSize: 14, color: Colors.grey, ), ), ) : buildMobileCardView(paginatedGroup)), ), PaginationControls( currentPage: currentPage, itemsPerPage: itemsPerPage, totalItems: groups.length, activeColor: layoutColor, // your theme color onPageChanged: (page) { setState(() { currentPage = page; }); }, onItemsPerPageChanged: (items) { setState(() { itemsPerPage = items; currentPage = 0; }); }, ), ], ), ); }, ), ], ), ), ), ); } }