import 'dart:convert'; import 'dart:core'; import 'package:frontend/data/models/plan.dart'; import 'package:go_router/go_router.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'; class ListPlans extends StatefulWidget { const ListPlans({super.key}); @override _ListPlansState createState() => _ListPlansState(); } class _ListPlansState extends State { final ApiService apiService = ApiService(); int currentPage = 0; int itemsPerPage = 8; String? userId; String? orgId; String? token; String? TripPlanAction; Color? layoutColor; Color? bodyColor; late Future> futurePlans; List allPlans = []; List filteredPlans = []; TextEditingController searchController = TextEditingController(); @override void initState() { super.initState(); getToken(); WidgetsBinding.instance.addPostFrameCallback((_) { initializeData(); loadInitialData(); // futurePlans.then((plans) { // setState(() { // allPlans = plans; // filteredPlans = plans; // }); // }); }); // futurePlans = fetchPlans(); } 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.tripType?.toLowerCase().contains(lowerQuery) ?? false) || (plan.createdOn?.toLowerCase().contains(lowerQuery) ?? false) || (plan.statusValue?.toLowerCase().contains(lowerQuery) ?? false); }).toList(); }); print("filteredPlans: $filteredPlans"); } 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 initializeData() async { token = await getToken(); userId = await getUserId(); orgId = await getOrgId(); TripPlanAction = await getTripPlanAction(); if (token == null || userId == null) { print("Token or USerId missing"); return; } else { 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'; // final String apiUrldata = '$apiUrl/api/plans?user_id=$userId'; // final String apiUrldata = '$apiUrl/api/plans?user_id=$userId'; final String apiUrldata = '$apiUrl/api/plans?org_id=$orgId&user_id=$userId'; // api/plans?org_id=1&user_id=1 // 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', }, ); if (response.statusCode == 200) { final data = json.decode(response.body); List plansJson = data['data']; return plansJson.map((json) => Plan.fromJson(json)).toList(); } 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', }, body: jsonEncode(planData), // Convert map to JSON ); if (response.statusCode == 200) { print("Plan Deleted successfully!"); print("Response: ${response.body}"); initializeData(); } else { print("Failed to submit plan. Status: ${response.statusCode}"); print("Error: ${response.body}"); } } catch (e) { print(" Error submitting plan: $e"); } } // 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', // }, // ); // // if (response.statusCode == 200) { // final Map? resData = json.decode(response.body); // // return resData?["data"]; // } else { // throw Exception('Failed to load plans'); // } // } // // 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 deletePlan(String planId) async { try { Map planData = await ApiService.getViewPlan(planId); print("ViewAAA - $planData"); postPlanData(planData, planId); } catch (e) { print("Error fetching plan: $e"); } } Widget build(BuildContext context) { return ResponsiveBuilder(builder: (context, sizingInfo) { bool isDesktop = sizingInfo.deviceScreenType == DeviceScreenType.desktop; return Scaffold( backgroundColor: Colors.white, // appBar: isDesktop ? null : const CustomAppBar(title: 'Home'), // drawer: isDesktop ? null : CustomDrawer(isDesktop: false), appBar: const CustomAppBar(), 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(8), 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 yy hh:m a').format(dateTime); } catch (e) { return rawDate; // fallback if parsing fails } } 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, decoration: BoxDecoration( border: isDesktop ? Border.all( width: 2, color: Colors.white, // color: Color(0xFFF7F7FB), ) : null, color: Colors.white, // color: Color(0xFFF7F7FB), // color: Colors.amber, ), child: Padding( padding: const EdgeInsets.all(1.0), child: Container( // padding: const EdgeInsets.all(10.0), color: Colors.white, 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: [ const Text('Trip List', style: TextStyle( fontFamily: "Inter", fontSize: 16, fontWeight: FontWeight.w600, color: Color(0xFF212121))), ], ), Spacer(), 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: TextStyle( fontSize: 12, fontFamily: "Inter", ), ), ), // SizedBox(width: 16), Spacer(), // ElevatedButton( // style: ElevatedButton.styleFrom( // foregroundColor: Colors.white, // backgroundColor: Colors.blueAccent, // ), // onPressed: () { // context.go('/createPlan', extra: { // 'orgId': orgId, // }); // if (!isDesktop) Navigator.pop(context); // }, // child: Row( // children: [ // Icon(Icons.add_circle, color: Colors.white), // SizedBox(width: 5), // Text('NewPlan'), // // ], // ), // ), 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: () { if (TripPlanAction == "Plan Creation Not Allowed") { showDialog( context: context, builder: (context) => AlertDialog( title: Text( "Action Not Allowed", style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold), ), content: Text( "Plan Creation Not Allowed For This User."), actions: [ TextButton( onPressed: () => Navigator.pop(context), child: Text( "OK", style: TextStyle(color: layoutColor), ), ), ], ), ); } else { context.go('/createPlan', extra: { 'orgId': orgId, }); if (!isDesktop) Navigator.pop(context); } }, child: Row( mainAxisSize: MainAxisSize.min, // Ensures content fits nicely children: [ Text( "Add New Plan", style: TextStyle(fontSize: 13), ), SizedBox(width: 8), // spacing between icon and text Icon( Icons.add_circle_outline_rounded, size: 15, color: Colors.white, ), ], ), ), ], ), 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, children: const [ Icon(Icons.error_outline, color: Colors.redAccent, size: 60), SizedBox(height: 16), Text("Oops!", style: TextStyle( fontSize: 22, fontWeight: FontWeight.bold, color: Colors.redAccent)), SizedBox(height: 8), Text("No Plans Available For This User", textAlign: TextAlign.center, style: TextStyle( fontSize: 20, fontWeight: FontWeight.bold, color: Colors.grey)), SizedBox(height: 20), Text("Please Create Plan", textAlign: TextAlign.center, style: TextStyle( fontSize: 16, color: Colors.grey)), SizedBox(height: 20), ], ), ), ); } // List plans = snapshot.data!; List plans = filteredPlans.isNotEmpty ? filteredPlans : allPlans; 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 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: const [ DataColumn( label: Text('Trip Id', style: TextStyle( // color: Colors.grey, // color: Color(0xFF9E9DBD), fontSize: 13, fontFamily: "Inter", fontWeight: FontWeight.w600))), DataColumn( label: Text('Employee Code', style: TextStyle( // color: Color(0xFF9E9DBD), fontSize: 13, fontFamily: "Inter", fontWeight: FontWeight.w600))), DataColumn( label: Text('Trip Name', style: TextStyle( fontSize: 13, fontFamily: "Inter", fontWeight: FontWeight.w600))), DataColumn( label: Text('Traveller', style: TextStyle( fontSize: 13, fontFamily: "Inter", fontWeight: FontWeight.w600))), DataColumn( label: Text('Trip Type', style: TextStyle( fontSize: 13, fontFamily: "Inter", fontWeight: FontWeight.w600))), DataColumn( label: Text('Created On', style: TextStyle( fontSize: 13, fontFamily: "Inter", fontWeight: FontWeight.w600))), DataColumn( label: Text('Status', style: TextStyle( fontSize: 13, fontFamily: "Inter", fontWeight: FontWeight.w600))), DataColumn( label: Text('Actions', style: TextStyle( fontSize: 13, fontFamily: "Inter", fontWeight: FontWeight.w600))), ], rows: paginatedPlans.map((plan) { return DataRow(cells: [ DataCell(Text(plan.planId, style: TextStyle( fontSize: 13, fontFamily: "Inter", ))), DataCell(Text(plan.employeeCode ?? "no data", style: TextStyle( fontSize: 13, fontFamily: "Inter", ))), DataCell(Text(plan.tripTitle, 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", ))), DataCell(Text(plan.tripType, style: TextStyle( fontSize: 13, fontFamily: "Inter", ))), DataCell(Text(_formatDate(plan.createdOn), // 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), // color: plan.statusValue == // "Partially Approved" // ? Colors.yellow.shade100 // : plan.statusValue == "Approved" // ? Colors.green.shade100 // : plan.statusValue == "Completed" // ? Colors.green.shade500 // : plan.statusValue == "Rejected" // ? Colors.red.shade100 // : Colors.grey.shade100, borderRadius: BorderRadius.circular(10), ), child: Text( plan.statusValue, textAlign: TextAlign.center, style: TextStyle( color: getStatusTextColor(plan.statusValue), // color: (plan.statusValue == // "Partially Approved" || // plan.statusValue == "Approved" || // plan.statusValue == "Rejected") // ? Colors.black // : plan.statusValue == "Completed" // ? Colors.white // : Colors.grey, fontSize: 12, fontFamily: "Inter", fontWeight: FontWeight.w400, ), ), ), ), DataCell(Row(children: [ IconButton( icon: const Icon( Icons.remove_red_eye, color: Color(0xFF475569), size: 18, ), onPressed: () => ApiService.viewPlan( context, plan.planId, isViewMode: true)), GestureDetector( onTap: () => ApiService.viewPlan( context, plan.planId, isViewMode: false), child: Image.asset( 'assets/images/IconsImg/edit.png', width: 15, height: 15), ), // IconButton( // icon: const Icon(Icons.edit, // color: Colors.green), // onPressed: () => viewPlan(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(), ), ); }, ); return Expanded( child: Column( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ isDesktop ? table : SingleChildScrollView( scrollDirection: Axis.horizontal, child: SingleChildScrollView( scrollDirection: Axis.vertical, child: table, ), ), Row( mainAxisAlignment: MainAxisAlignment.end, children: [ // Previous button with arrow icon IconButton( icon: Icon( Icons.arrow_back_ios_new, size: 10, ), onPressed: currentPage > 0 ? () { setState(() { currentPage--; }); } : null, ), SizedBox(width: 2), // Page number text Text( 'Page ${currentPage + 1}', style: TextStyle( fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black87, fontFamily: "Inter", ), ), SizedBox(width: 2), // Next button with arrow icon IconButton( icon: Icon( Icons.arrow_forward_ios, size: 10, ), onPressed: (currentPage + 1) * itemsPerPage < plans.length ? () { setState(() { currentPage++; }); } : null, ), ], ), ], ), ); }, ) ], ), ), ), ); } // 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; } }