import 'dart:convert'; import 'dart:core'; import 'package:frontend/data/models/plan.dart'; import 'package:go_router/go_router.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:http/http.dart' as http; import 'package:flutter/material.dart'; import 'package:frontend/config/apiUrl.dart'; import 'package:intl/intl.dart'; import 'package:responsive_builder/responsive_builder.dart'; import 'package:shared_preferences/shared_preferences.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 '../../utils/travelAgent_remarks.dart'; import '../../widgets/custom_popup.dart'; import '../../widgets/popup_listPlan_action.dart'; import '../allTrips/remarks_list.dart'; class ApprovalList extends StatefulWidget { const ApprovalList({super.key}); @override _ApprovalListState createState() => _ApprovalListState(); } class _ApprovalListState extends State { final ApiService apiService = ApiService(); late Future> futurePlans; String? userId; String? orgId; String? token; Color? layoutColor; Color? bodyColor; int currentPage = 0; int itemsPerPage = 10; List allPlans = []; List filteredPlans = []; TextEditingController searchController = TextEditingController(); late bool _dialogShown = false; Map successList = {}; @override void initState() { super.initState(); _checkAuthAndLoadData(); // getToken(); // // WidgetsBinding.instance.addPostFrameCallback((_) { // initializeData(); // loadInitialData(); // }); // 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; } getToken(); initializeData(); 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 loadAllGroups() async { // try { // final result = await apiService.fetchUserApprovalList(); // // setState(() { // // apiAllGroups = result; // // }); // print("Fetched services: $result"); // } catch (e) { // print('Error fetching role list: $e'); // } // } Future initializeData() async { token = await getToken(); userId = await getUserId(); orgId = await getOrgId(); if (token == null || userId == null) { print("Token or USerId missing"); return; } else { setState(() { futurePlans = fetchPlans(); }); print("Token availa"); // Wait for futurePlans to be fetched and update allPlans futurePlans.then((plans) { setState(() { allPlans = plans; // Set allPlans after the fetch is complete filteredPlans = allPlans; // You can update filteredPlans too if needed }); }); } } void refresh() { print("Refresj"); setState(() { futurePlans = fetchPlans(); }); // Wait for futurePlans to be fetched and update allPlans futurePlans.then((plans) { setState(() { allPlans = plans; // Set allPlans after the fetch is complete filteredPlans = allPlans; // You can update filteredPlans too if needed }); }); } Future getUserId() async { final prefs = await SharedPreferences.getInstance(); final String? userDataString = prefs.getString('user_data'); if (userDataString != null) { try { final Map userData = jsonDecode(userDataString); return userData["user_id"]?.toString(); } catch (e) { return null; } } return null; } Future getOrgId() async { final prefs = await SharedPreferences.getInstance(); final String? userDataString = prefs.getString('user_data'); if (userDataString != null) { try { final Map userData = jsonDecode(userDataString); return userData["org_id"]?.toString(); } catch (e) { return null; } } return null; } Future getToken() async { final prefs = await SharedPreferences.getInstance(); return prefs.getString('auth_token'); } // Fetch API Data Future> fetchPlans() async { final String apiUrldata = '$apiUrl/api/plans/findApprovalList?user_id=$userId&org_id=$orgId'; // 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', // Add token here 'Content-Type': 'application/json', 'app-signature': 'ts-traveltool-2025-signature-123456', }, ); if (response.statusCode == 200) { final data = json.decode(response.body); print('plansJson1'); List plansJson = data['data']; print('plansJson $plansJson'); return plansJson.map((json) => Plan.fromJson(json)).toList(); } 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 plans'); } } Future postPlanData(planData, planId) async { final String apiUrldata = '$apiUrl/api/plans/createOrEditPlan'; final token = await getToken(); // Fetch token if (token == null) { throw Exception('Token not found. Please log in.'); } planData['is_active'] = "0"; print("POSTPlanTesting------- $planData"); // Add plan_id for update try { final response = await http.post( Uri.parse(apiUrldata), headers: { 'Authorization': 'Bearer $token', 'Content-Type': 'application/json', 'app-signature': 'ts-traveltool-2025-signature-123456', }, body: jsonEncode(planData), // Convert map to JSON ); if (response.statusCode == 200) { print("Plan Deleted successfully!"); print("Response: ${response.body}"); initializeData(); } else if (response.statusCode == 403) { print("403-FORB"); await apiService.logout(context); return null; // throw Exception('Failed to load users'); } else { print("Failed to submit plan. Status: ${response.statusCode}"); print("Error: ${response.body}"); } } catch (e) { print(" Error submitting plan: $e"); } } void deletePlan(String planId) async { // try { // Map planData = await getViewPlan(planId); // print("ViewAAA - $planData"); // // postPlanData(planData, planId); // } catch (e) { // print("Error fetching plan: $e"); // } bool confirmed = await apiService.showCancelConfirmationDialog( context, layoutColor, ); if (confirmed) { try { Map planData = await ApiService().getViewPlan( planId, context, ); print("ViewAAA - $planData"); refresh(); // postPlanData(planData, planId); } catch (e) { print("Error fetching plan: $e"); } } else { print("User cancelled"); } } Future> getViewPlan(String planId) async { final String apiUrldata = '$apiUrl/api/plans/find/$planId'; print("API URL: $apiUrldata"); // final token = await getToken(); if (token == null) { throw Exception('Token not found. Please log in.'); } final response = await http.put( Uri.parse(apiUrldata), headers: { 'Authorization': 'Bearer $token', // Add token here 'Content-Type': 'application/json', 'app-signature': 'ts-traveltool-2025-signature-123456', }, ); if (response.statusCode == 200) { final Map? resData = json.decode(response.body); return resData?["data"]; } 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 plans'); } } void viewPlanforApprover( String planId, String? approverId, String? delegaterId, { bool isViewMode = false, bool isApprover = true, }) async { try { Map planData = await getViewPlan(planId); print("ViewAAA - $planData"); context.replace( '/approver/plans', extra: { 'planData': planData, 'approverId': approverId, 'delegaterId': delegaterId, 'isViewMode': isViewMode, 'isApprover': isApprover, }, ); // context.go( // '/createPlan', // extra: { // 'planData': planData, // 'isViewMode': isViewMode, // 'isApprover': isApprover, // }, // ); } catch (e) { print("Error fetching plan: $e"); } } // void viewPlan(String planId, {bool isViewMode = false}) async { // try { // Map planData = await getViewPlan(planId); // print("ViewAAA - $planData"); // // context.go('/createPlan', // extra: {'planData': planData, 'isViewMode': isViewMode}); // } catch (e) { // print("Error fetching plan: $e"); // } // } void filterPlans(String query) { print("allPlans before filtering: $allPlans"); final lowerQuery = query.toLowerCase(); setState(() { filteredPlans = allPlans.where((plan) { return (plan.planId?.toLowerCase().contains(lowerQuery) ?? false) || (plan.employeeCode?.toLowerCase().contains(lowerQuery) ?? false) || (plan.tripTitle?.toLowerCase().contains(lowerQuery) ?? false) || (plan.userName?.toLowerCase().contains(lowerQuery) ?? false) || (plan.travellerName?.toLowerCase().contains(lowerQuery) ?? false) || (plan.approverName?.toLowerCase().contains(lowerQuery) ?? false) || (plan.tripType?.toLowerCase().contains(lowerQuery) ?? false) || (plan.createdOn?.toLowerCase().contains(lowerQuery) ?? false) || (plan.statusValue?.toLowerCase().contains(lowerQuery) ?? false); }).toList(); currentPage = 0; }); print("filteredPlans: $filteredPlans"); } Future _showDetails(id) async { _dialogShown = true; final String apiUrldata = '$apiUrl/api/plans/get_plan_approval_status?plan_id=$id'; final String? token = await getToken(); String label = ""; List> approverData = []; if (token == null) { print('Token not found. Please log in.'); label = "Error - Token not found"; } else { try { 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); label = data['data']['model_heading'] ?? ""; approverData = List>.from( data['data']['approver_data'] ?? [], ); } else if (response.statusCode == 403) { print("403-FORB"); await apiService.logout(context); return null; // throw Exception('Failed to load users'); } else { print('Failed to load'); label = "Error - Failed to load data"; } } catch (e) { print('Exception occurred: $e'); label = "Error - Something went wrong"; } } showDialog( context: context, builder: (context) => AlertDialog( title: Text( label, style: GoogleFonts.poppins( fontSize: 18, fontWeight: FontWeight.w600, color: Colors.black, ), overflow: TextOverflow.ellipsis, ), content: SingleChildScrollView( child: Column( mainAxisSize: MainAxisSize.min, children: [ if (approverData.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))), // DataColumn(label: Text('Approver ID', style: GoogleFonts.poppins(fontSize: 14, fontWeight: FontWeight.w600))), DataColumn( label: Text( 'Status', style: GoogleFonts.poppins( fontSize: 14, fontWeight: FontWeight.w600, ), ), ), ], rows: approverData.asMap().entries.map((entry) { final index = entry.key + 1; final item = entry.value; final approverIdKey = item.keys.firstWhere( (k) => k.endsWith('_id'), orElse: () => '', ); final statusKey = item.keys.firstWhere( (k) => k.endsWith('_status'), orElse: () => '', ); final approverId = item[approverIdKey]?.toString() ?? '-'; final statusText = item[statusKey]?.toString() ?? 'Unknown'; return DataRow( cells: [ // DataCell(Text('$index')), // DataCell(SizedBox(width: 120, child: Text(approverId, overflow: TextOverflow.ellipsis))), DataCell( SizedBox( child: Text( statusText, overflow: TextOverflow.ellipsis, ), ), ), ], ); }).toList(), ), ), ), ) else Text( "-- no data. --", style: GoogleFonts.poppins( fontSize: 14, fontWeight: FontWeight.w300, ), ), ], ), ), actions: [ TextButton( onPressed: () { Navigator.pop(context); _dialogShown = false; }, child: Text("Close"), ), ], ), ); } 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: 'Home'), // 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), Expanded(child: buildGroupListLayout(isDesktop)), ], ), ), ); }, ); } Widget buildGroupListLayout(bool isDesktop) { return Container( // decoration: BoxDecoration( // // color: Colors.amber, // // color: bodyColor, // color: Color(0xFFE1F5FE), // border: Border.all( // // color: Color(0xFFF7F7FB), // color: Colors.white, // width: 3.5)), child: buildTableLayout(isDesktop), ); } Widget buildTableLayout(isDesktop) { // String _formatDate(String rawDate) { // try { // final dateTime = DateTime.parse(rawDate); // return DateFormat('dd, MMM yyyy hh:m a').format(dateTime); // } catch (e) { // return rawDate; // fallback if parsing fails // } // } final adjHgt = MediaQuery.of(context).size.height; String _formatDate(String rawDate) { try { final dateTime = DateTime.parse(rawDate); return DateFormat( 'dd, MMM yyyy HH:mm', ).format(dateTime); // 24-hour format } catch (e) { return rawDate; // fallback if parsing fails } } return Container( margin: isDesktop ? EdgeInsets.all(10.0) : null, padding: isDesktop ? const EdgeInsets.only(top: 15, bottom: 15, left: 20, right: 20) : const EdgeInsets.only(top: 15, bottom: 15, left: 5, right: 5), height: isDesktop ? MediaQuery.of(context).size.height * 0.98 : MediaQuery.of(context).size.height, decoration: BoxDecoration( border: isDesktop ? Border.all( width: 2, color: Colors.white, // color: Color(0xFFF7F7FB), ) : null, color: isDesktop ? Colors.white : Color(0xFFFCFCFC), // color: Color(0xFFF7F7FB), // color: Colors.amber, ), child: Padding( padding: const EdgeInsets.all(1.0), child: Container( // padding: const EdgeInsets.all(10.0), color: isDesktop ? Colors.white : Color(0xFFFCFCFC), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ // Divider( // thickness: 0.2, // how "thick" the line is // color: Colors.grey, // optional // ), Row( children: [ Padding( padding: isDesktop ? const EdgeInsets.all(0) : const EdgeInsets.only(left: 14.0), child: Row( children: [ Text( 'My Approvals', style: GoogleFonts.poppins( fontSize: isDesktop ? 16 : 14, fontWeight: FontWeight.w600, color: Colors.black, ), // style: TextStyle( // fontFamily: "Inter", // fontSize: isDesktop ? 16 : 14, // fontWeight: FontWeight.w600, // ) ), ], ), ), if (isDesktop) SizedBox(width: MediaQuery.of(context).size.width * 0.23), if (isDesktop) Container( width: MediaQuery.of(context).size.width * 0.2, height: 40, child: TextField( controller: searchController, onChanged: filterPlans, 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), ], ), if (!isDesktop) SizedBox(height: 10), isDesktop ? SizedBox.shrink() : Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Container( width: MediaQuery.of(context).size.width * 0.85, height: 35, child: TextField( controller: searchController, onChanged: filterPlans, 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: futurePlans, 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, mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ // Icon(Icons.error_outline, // color: Colors.redAccent, size: 60), // SizedBox(height: 1), // Text("Oops!", // style: TextStyle( // fontSize: 22, // fontWeight: FontWeight.bold, // color: Colors.redAccent)), SizedBox(height: adjHgt / 4), Text( "No Data Found", textAlign: TextAlign.center, style: GoogleFonts.poppins( fontSize: 20, fontWeight: FontWeight.bold, color: Colors.grey, ), ), ], ), ), ); } // List plans = // filteredPlans.isNotEmpty ? filteredPlans : allPlans; // List plans = // searchController.text.isEmpty ? allPlans : filteredPlans; List plans = searchController.text.isEmpty ? allPlans : filteredPlans; plans.sort( (a, b) => int.parse(b.planId).compareTo(int.parse(a.planId)), ); List paginatedPlans = plans .skip(currentPage * itemsPerPage) .take(itemsPerPage) .toList(); Widget table = LayoutBuilder( builder: (context, constraints) { double minWidth = isDesktop ? constraints.maxWidth : 1300; return SingleChildScrollView( scrollDirection: Axis.horizontal, child: SingleChildScrollView( scrollDirection: Axis.vertical, child: 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( 'Trip ID', style: GoogleFonts.poppins( fontSize: 13, fontWeight: FontWeight.w600, ), ), ), DataColumn( label: Text( 'Trip Name', style: GoogleFonts.poppins( fontSize: 13, fontWeight: FontWeight.w600, ), ), ), DataColumn( label: Text( 'Emp Code', style: GoogleFonts.poppins( fontSize: 13, fontWeight: FontWeight.w600, ), ), ), DataColumn( label: Text( 'Traveller', style: GoogleFonts.poppins( fontSize: 13, fontWeight: FontWeight.w600, ), ), ), DataColumn( label: Text( 'Approver', style: GoogleFonts.poppins( fontSize: 13, fontWeight: FontWeight.w600, ), ), ), DataColumn( label: Text( 'Trip Type', style: GoogleFonts.poppins( fontSize: 13, fontWeight: FontWeight.w600, ), ), ), DataColumn( label: Text( 'Created On', 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: paginatedPlans.map((plan) { return DataRow( cells: [ DataCell( Text( plan.planId, style: TextStyle( fontSize: 13, fontFamily: "Archivo", ), ), ), DataCell( SizedBox( width: 130, child: Text( plan.tripTitle, style: TextStyle( fontSize: 13, fontFamily: "Inter", ), softWrap: true, maxLines: 2, // overflow: TextOverflow.ellipsis, ), ), ), DataCell( Text( plan.employeeCode ?? " - ", style: TextStyle( fontSize: 13, fontFamily: "Inter", ), softWrap: true, overflow: TextOverflow.ellipsis, ), ), DataCell( Text( plan.userName.isNotEmpty ? plan.userName : plan.travellerName, style: TextStyle( fontSize: 13, fontFamily: "Inter", ), softWrap: true, overflow: TextOverflow.ellipsis, ), ), // DataCell( // Column( // crossAxisAlignment: // CrossAxisAlignment.start, // mainAxisAlignment: // MainAxisAlignment.center, // // children: [ // SizedBox(height: 1), // Text( // plan.userName.isNotEmpty // ? plan.userName // : plan.travellerName, // style: TextStyle( // fontSize: 12, // fontFamily: "Inter", // ), // ), // SizedBox(height: 3), // Expanded( // child: Text( // "(${plan.employeeCode})" ?? // " - ", // style: GoogleFonts.poppins( // fontSize: 10, // // fontFamily: "Inter", // ), // ), // ), // ], // ), // ), DataCell( Text( plan.approverName ?? " ", style: TextStyle( fontSize: 13, fontFamily: "Inter", ), ), ), DataCell( Text( plan.tripType, style: TextStyle( fontSize: 13, fontFamily: "Inter", ), ), ), DataCell( Text( // plan.createdOn, _formatDate(plan.createdOn), style: TextStyle( fontSize: 13, fontFamily: "Inter", ), ), ), // DataCell( // Container( // width: // double // .infinity, // Set your desired fixed size (equal width and height) // height: 25, // alignment: Alignment.center, // decoration: BoxDecoration( // color: getStatusColor( // plan.statusValue, // ), // borderRadius: BorderRadius.circular( // 8, // ), // ), // // child: Text( // plan.statusValue, // textAlign: TextAlign.center, // style: TextStyle( // color: getStatusTextColor( // plan.statusValue, // ), // fontSize: 12, // fontFamily: "Inter", // fontWeight: FontWeight.w400, // ), // ), // ), // ), DataCell( GestureDetector( onTap: () => _showDetails(plan.planId), child: Container( width: double.infinity, height: 25, alignment: Alignment.center, decoration: BoxDecoration( color: getStatusColor( plan.statusValue, ), borderRadius: BorderRadius.circular(8), ), child: Text( plan.statusValue, textAlign: TextAlign.center, style: TextStyle( color: getStatusTextColor( plan.statusValue, ), fontSize: 12, fontFamily: "Inter", fontWeight: FontWeight.w400, ), ), ), ), ), DataCell( Row( children: [ 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, ), tooltip: 'View The Trip Details', onPressed: () { Navigator.pop( context, ); // Close popup manually viewPlanforApprover( plan.planId, plan.approverId, plan.delegaterId, isViewMode: true, isApprover: true, ); }, ), if (plan.statusValue == "Pending Approval" || plan.statusValue == "Partially Approved") IconButton( icon: Image.asset( 'assets/images/IconsImg/edit.png', width: 20, height: 15, ), tooltip: 'Edit The Trip Details', onPressed: () { print( "Approver Edit : ${plan.approverId}", ); print( "Approver Edit2 : ${plan.delegaterId}", ); print( "Approver Edit3 : ${plan.approver_status}", ); Navigator.pop( context, ); ApiService().viewPlanForApprover( context, plan.planId, plan.approverId, plan.delegaterId, plan.approver_status, isViewMode: false, isApprover: true, ); }, ), IconButton( icon: Icon( Icons .cancel_rounded, size: 18, ), tooltip: 'Cancellation The Trip Details', onPressed: () { Navigator.pop( context, ); deletePlan( plan.planId, ); }, ), IconButton( icon: Icon( Icons .download, color: Color( 0xFF475569, ), size: 18, ), tooltip: 'Download The PDF', onPressed: () { Navigator.pop( context, ); apiService .getPdfDownload( context, plan.planId, ); }, ), if (plan.statusValue == "Approved" && plan.forexId != null && plan.forexId != '') IconButton( icon: Icon( Icons .monetization_on_outlined, color: Color( 0xFF475569, ), size: 18, ), tooltip: 'Download Forex PDF', onPressed: () { Navigator.pop( context, ); apiService.getForexPdfDownload( context, plan.forexId, ); }, ), IconButton( icon: const Icon( Icons.comment, color: Color( 0xFF475569, ), size: 11, ), tooltip: 'Approver Comment', onPressed: () { showDialog( context: context, builder: ( context, ) => CommentModalList( // planId: plan.planId, planId: plan.planId.toString(), layoutColorForUser: layoutColor!, role: "Approver", ), ); }, ), ], ), ), ), ], ), ], ), ), // DataCell(Row(children: [ // IconButton( // icon: const Icon( // Icons.remove_red_eye, // color: Color(0xFF475569), // size: 18, // ), // onPressed: () => viewPlanforApprover( // plan.planId, // isViewMode: true, // isApprover: true)), // // GestureDetector( // onTap: () => ApiService.viewPlanForApprover( // context, plan.planId, // isViewMode: false, isApprover: true), // child: Image.asset( // 'assets/images/IconsImg/edit.png', // width: 20, // height: 15), // ), // // // IconButton( // // icon: const Icon(Icons.edit, // // color: Colors.green), // // onPressed: () => viewPlanforApprover(plan.planId, // // isViewMode: false) ), // // SizedBox( // width: 5, // ), // GestureDetector( // onTap: () => deletePlan(plan.planId), // child: Image.asset( // 'assets/images/IconsImg/delete.png', // width: 20, // height: 15), // ), // // IconButton( // icon: const Icon( // Icons.download, // color: Color(0xFF475569), // size: 18, // ), // onPressed: () { // apiService.getPdfDownload(plan.planId); // }), // ])), ], ); }).toList(), ), ), ), ); }, ); Widget buildMobileCardView(List paginatedPlans) { return ListView.builder( itemCount: paginatedPlans.length, itemBuilder: (context, index) { final plan = paginatedPlans[index]; return Card( color: Colors.white, margin: EdgeInsets.symmetric( horizontal: 12, vertical: 6, ), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), elevation: 3, // Control the strength of the shadow // shadowColor: Colors.black54, // Softer shadow // clipBehavior: Clip.antiAlias, child: Padding( padding: const EdgeInsets.all(10.0), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded( child: Container( // color: Colors.yellow.shade50, child: Column( children: [ Row( // mainAxisAlignment: // MainAxisAlignment.spaceBetween, children: [ Container( // color: Colors.red.shade50, child: Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ Image.asset( plan.tripType == 'Domestic' ? 'assets/images/IconsImg/Domestic_new.png' : 'assets/images/IconsImg/International_new.png', // width: 65, height: plan.tripType == 'Domestic' ? 28 : 30, ), ], ), ), SizedBox(width: 10), Expanded( flex: 1, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( '${plan.tripTitle}', style: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w500, ), ), ], ), ), SizedBox(width: 10), Column( children: [ // PlanPopupMenu( // plan: plan, // deletePlan: deletePlan, // apiService: apiService, // layoutColor: layoutColor, // ), 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: 1, vertical: 1, // 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, ), tooltip: 'View The Trip Details', onPressed: () { Navigator.pop( context, ); // Close popup manually viewPlanforApprover( plan.planId, plan.approverId, plan.delegaterId, isViewMode: true, isApprover: true, ); }, ), if (plan.statusValue == "Pending Approval" || plan.statusValue == "Partially Approved") IconButton( icon: Image.asset( 'assets/images/IconsImg/edit.png', width: 20, height: 15, ), tooltip: 'Edit The Trip Details', onPressed: () { Navigator.pop( context, ); // Close popup manually viewPlanforApprover( plan.planId, plan.approverId, plan.delegaterId, isViewMode: true, isApprover: true, ); }, ), IconButton( icon: Icon( Icons .download, color: Color( 0xFF475569, ), size: 18, ), tooltip: 'Download The PDF', onPressed: () { Navigator.pop( context, ); apiService.getPdfDownload( context, plan.planId, ); }, ), if (plan.statusValue == "Approved" && plan.forexId != null && plan.forexId != '') IconButton( icon: Icon( Icons .monetization_on_outlined, color: Color( 0xFF475569, ), size: 18, ), tooltip: 'Download Forex PDF', onPressed: () { Navigator.pop( context, ); apiService.getForexPdfDownload( context, plan.forexId, ); }, ), IconButton( icon: const Icon( Icons .comment, color: Color( 0xFF475569, ), size: 11, ), tooltip: 'Trip Comments', onPressed: () { showDialog( context: context, builder: ( context, ) => CommentModalList( // planId: plan.planId, planId: plan.planId.toString(), layoutColorForUser: layoutColor!, role: "Approver", ), ); }, ), ], ), ), ), ], ), ], ), ], ), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.end, children: [ Flexible( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( '${plan.userName.isNotEmpty ? plan.userName : plan.travellerName} (${plan.employeeCode ?? 'No Data'})', style: GoogleFonts.poppins( fontSize: 10, color: Colors.black87, ), // overflow: TextOverflow.ellipsis, // maxLines: 2, ), Text( '${_formatDate(plan.createdOn)}', style: GoogleFonts.poppins( fontSize: 9, color: Colors.grey, ), ), ], ), ), Column( mainAxisAlignment: MainAxisAlignment.end, crossAxisAlignment: CrossAxisAlignment.end, children: [ GestureDetector( onTap: () => _showDetails( plan.planId, ), child: Container( padding: EdgeInsets.symmetric( horizontal: 8, vertical: 4, ), decoration: BoxDecoration( color: getStatusColor( plan.statusValue, ), borderRadius: BorderRadius.circular( 8, ), ), child: Text( plan.statusValue, style: GoogleFonts.poppins( color: getStatusTextColor( plan.statusValue, ), fontSize: 9, fontWeight: FontWeight.w600, ), ), ), ), ], ), ], ), ], ), ), ), ], ), ), ); }, ); } return Expanded( child: Column( // mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Expanded( child: isDesktop ? (searchController.text.isNotEmpty && filteredPlans.isEmpty ? Center( child: Text( "No Matches Found", style: GoogleFonts.poppins( fontSize: 14, color: Colors.grey, ), ), ) : SingleChildScrollView( scrollDirection: Axis.vertical, child: table, )) : (searchController.text.isNotEmpty && filteredPlans.isEmpty ? Center( child: Text( "No Matches Found", style: GoogleFonts.poppins( fontSize: 14, color: Colors.grey, ), ), ) : buildMobileCardView(paginatedPlans)), // child: isDesktop // ? SingleChildScrollView( // scrollDirection: Axis.vertical, // child: table, // <-- your existing table // ) // : buildMobileCardView(paginatedPlans), ), PaginationControls( currentPage: currentPage, itemsPerPage: itemsPerPage, totalItems: plans.length, activeColor: layoutColor, // your theme color onPageChanged: (page) { setState(() { currentPage = page; }); }, onItemsPerPageChanged: (items) { setState(() { itemsPerPage = items; currentPage = 0; }); }, ), ], ), ); }, ), ], ), ), ), ); } } // Helper functions for colors Color getStatusColor(String status) { if (status == "Partially Approved") return Colors.yellow.shade100; if (status == "Approved") return Colors.green.shade100; if (status == "Completed") return Colors.green.shade500; if (status == "Rejected") return Colors.red.shade100; return Colors.grey.shade100; } Color getStatusTextColor(String status) { if (status == "Partially Approved" || status == "Approved" || status == "Rejected") { return Colors.black; } else if (status == "Completed") { return Colors.white; } return Colors.grey; }