diff --git a/lib/Screens/allTrips/list_all_plans.dart b/lib/Screens/allTrips/list_all_plans.dart index 792c4a7..bfdc978 100644 --- a/lib/Screens/allTrips/list_all_plans.dart +++ b/lib/Screens/allTrips/list_all_plans.dart @@ -13,6 +13,9 @@ 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_popup.dart'; +import '../../widgets/popup_listPlan_action.dart'; class ListAllPlans extends StatefulWidget { const ListAllPlans({super.key}); @@ -25,7 +28,7 @@ class _ListAllPlansState extends State { final ApiService apiService = ApiService(); int currentPage = 0; - int itemsPerPage = 8; + int itemsPerPage = 10; String? userId; String? orgId; @@ -158,12 +161,14 @@ class _ListAllPlansState extends State { Future> fetchPlans() async { late String apiUrldata; - if (roleUser == "Org Admin") { - apiUrldata = '$apiUrl/api/plans?org_id=$orgId'; - } else { - apiUrldata = - '$apiUrl/api/plans/findTravelAgentPlanList?org_id=$orgId&user_id=$userId'; - } + apiUrldata = '$apiUrl/api/plans?org_id=$orgId'; + + // if (roleUser == "Org Admin") { + // apiUrldata = '$apiUrl/api/plans?org_id=$orgId'; + // } else { + // apiUrldata = + // '$apiUrl/api/plans/findTravelAgentPlanList?org_id=$orgId&user_id=$userId'; + // } if (token == null) { throw Exception('Token not found. Please log in.'); @@ -238,10 +243,12 @@ class _ListAllPlansState extends State { bool isDesktop = sizingInfo.deviceScreenType == DeviceScreenType.desktop; return Scaffold( - backgroundColor: Colors.white, + backgroundColor: Color(0xFFf5f5f5), + // backgroundColor: Color(0xFFFCFCFC), + // appBar: isDesktop ? null : const CustomAppBar(title: 'Home'), // drawer: isDesktop ? null : CustomDrawer(isDesktop: false), - appBar: const CustomAppBar(), + appBar: CustomAppBar(isDesktop: isDesktop), drawer: CustomDrawer(isDesktop: false), body: Padding( padding: isDesktop @@ -251,7 +258,7 @@ class _ListAllPlansState extends State { vertical: MediaQuery.of(context).size.height * 0, // 5% of screen height as vertical padding ) - : EdgeInsets.all(8), + : EdgeInsets.all(0), child: Row( children: [ // if (isDesktop) CustomDrawer(isDesktop: true), @@ -266,13 +273,14 @@ class _ListAllPlansState extends State { 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)), + // color: Colors.amber, + // // color: bodyColor, + // // color: Color(0xFFE1F5FE), + // // border: Border.all( + // // // color: Color(0xFFF7F7FB), + // // color: Colors.white, + // // width: 3.5) + // ), child: buildTableLayout(isDesktop), ); } @@ -288,10 +296,8 @@ class _ListAllPlansState extends State { } 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), + margin: isDesktop ? EdgeInsets.all(10.0) : null, + padding: const EdgeInsets.only(top: 15, bottom: 15, left: 20, right: 20), height: isDesktop ? MediaQuery.of(context).size.height * 0.98 : MediaQuery.of(context).size.height, @@ -325,7 +331,7 @@ class _ListAllPlansState extends State { children: [ Row( children: [ - const Text('All Trip List', + const Text('All Trips', style: TextStyle( fontFamily: "Inter", fontSize: 16, @@ -334,43 +340,46 @@ class _ListAllPlansState extends State { ], ), + SizedBox( + width: 1, + ), 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, + 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), + ), ), - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(12), + style: TextStyle( + fontSize: 12, + fontFamily: "Inter", ), - 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), @@ -396,69 +405,110 @@ class _ListAllPlansState extends State { // ), // ), - 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, - ), - ], - ), - ), + // 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), + + if (!isDesktop) + Row( + children: [ + Container( + width: MediaQuery.of(context).size.width * 0.85, + 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", + ), + ), + ), + ], + ), FutureBuilder>( future: futurePlans, builder: (context, snapshot) { @@ -475,25 +525,27 @@ class _ListAllPlansState extends State { children: const [ Icon(Icons.error_outline, color: Colors.redAccent, size: 60), - SizedBox(height: 16), + SizedBox(height: 1), Text("Oops!", style: TextStyle( fontSize: 22, fontWeight: FontWeight.bold, color: Colors.redAccent)), - SizedBox(height: 8), - Text("No Plans Available For This User", + SizedBox(height: 15), + Text("No Trips", textAlign: TextAlign.center, style: TextStyle( fontSize: 20, - fontWeight: FontWeight.bold, - color: Colors.grey)), - SizedBox(height: 20), - Text("Please Create Plan", + fontFamily: "Inter", + fontWeight: FontWeight.w600, + color: Colors.black54)), + SizedBox(height: 10), + Text("Please Create Trip", textAlign: TextAlign.center, style: TextStyle( - fontSize: 16, color: Colors.grey)), - SizedBox(height: 20), + fontFamily: "Inter", + fontSize: 12, + color: Colors.grey)), ], ), ), @@ -535,15 +587,15 @@ class _ListAllPlansState extends State { fontFamily: "Inter", fontWeight: FontWeight.w600))), DataColumn( - label: Text('Employee Code', + label: Text('Trip Name', style: TextStyle( - // color: Color(0xFF9E9DBD), fontSize: 13, fontFamily: "Inter", fontWeight: FontWeight.w600))), DataColumn( - label: Text('Trip Name', + label: Text('Employee Code', style: TextStyle( + // color: Color(0xFF9E9DBD), fontSize: 13, fontFamily: "Inter", fontWeight: FontWeight.w600))), @@ -585,11 +637,6 @@ class _ListAllPlansState extends State { fontSize: 13, fontFamily: "Inter", ))), - DataCell(Text(plan.employeeCode ?? "no data", - style: TextStyle( - fontSize: 13, - fontFamily: "Inter", - ))), DataCell(Text(plan.tripTitle, style: TextStyle( fontSize: 13, @@ -597,6 +644,11 @@ class _ListAllPlansState extends State { ), softWrap: true, overflow: TextOverflow.ellipsis)), + DataCell(Text(plan.employeeCode ?? "no data", + style: TextStyle( + fontSize: 13, + fontFamily: "Inter", + ))), DataCell(Text( plan.userName.isNotEmpty ? plan.userName @@ -624,16 +676,6 @@ class _ListAllPlansState extends State { 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( @@ -642,14 +684,6 @@ class _ListAllPlansState extends State { 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, @@ -657,45 +691,77 @@ class _ListAllPlansState extends State { ), ), ), - DataCell(Row(children: [ - IconButton( - icon: const Icon( - Icons.remove_red_eye, - color: Color(0xFF475569), - size: 18, + DataCell( + 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: () { + Navigator.pop( + context); // Close popup manually + ApiService.viewPlan( + context, plan.planId, + isViewMode: true); + }, + ), + IconButton( + icon: Image.asset( + 'assets/images/IconsImg/edit.png', + width: 20, + height: 15), + onPressed: () { + Navigator.pop(context); + ApiService.viewPlan( + context, plan.planId, + isViewMode: false); + }, + ), + IconButton( + icon: Image.asset( + 'assets/images/IconsImg/delete.png', + width: 20, + height: 15), + onPressed: () { + Navigator.pop(context); + deletePlan(plan.planId); + }, + ), + IconButton( + icon: Icon(Icons.download, + color: Color(0xFF114D8B), + size: 18), + onPressed: () { + Navigator.pop(context); + apiService.getPdfDownload( + plan.planId); + }, + ), + ], + ), + ), ), - 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: 20, - height: 15), + ], ), - 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(), ), @@ -703,66 +769,148 @@ class _ListAllPlansState extends State { }, ); + 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, + child: Padding( + padding: const EdgeInsets.all(12.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Status and Employee Code + Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Container( + padding: EdgeInsets.symmetric( + horizontal: 8, vertical: 4), + decoration: BoxDecoration( + color: getStatusColor(plan.statusValue), + borderRadius: BorderRadius.circular(8), + ), + child: Text( + plan.statusValue, + style: TextStyle( + color: getStatusTextColor( + plan.statusValue), + fontSize: 12, + fontWeight: FontWeight.w600, + ), + ), + ), + PlanPopupMenu( + plan: plan, + deletePlan: deletePlan, + apiService: apiService, + ), + ], + ), + SizedBox(height: 8), + // Name + Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Text( + plan.employeeCode ?? 'No Data', + style: TextStyle( + fontSize: 12, fontFamily: "Inter"), + ), + Text( + plan.userName.isNotEmpty + ? plan.userName + : plan.travellerName, + style: TextStyle( + fontSize: 12, fontFamily: "Inter"), + ), + ], + ), + SizedBox(height: 5), + // Trip Id and Trip Name + Row( + children: [ + Expanded( + child: Text('${plan.tripTitle}', + style: TextStyle( + fontSize: 13, + fontFamily: "Inter", + fontWeight: FontWeight.bold)), + ), + ], + ), + SizedBox(height: 2), + // Type and Created On + Row( + mainAxisSize: MainAxisSize.min, + children: [ + Expanded( + child: Text('${plan.tripType}', + style: TextStyle( + fontSize: 10, + color: Colors.black87, + fontFamily: "Inter", + )), + ), + Expanded( + child: + Text('${_formatDate(plan.createdOn)}', + style: TextStyle( + fontSize: 10, + color: Colors.black87, + fontFamily: "Inter", + )), + ), + ], + ), + + // Actions + ], + ), + ), + ); + }, + ); + } + return Expanded( child: Column( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + // mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - isDesktop - ? table - : SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: SingleChildScrollView( + Expanded( + child: isDesktop + ? 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, - ), - ], + 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; + }); + }, ), ], ), diff --git a/lib/Screens/allTrips/travel_agent_list.dart b/lib/Screens/allTrips/travel_agent_list.dart new file mode 100644 index 0000000..3bf28e4 --- /dev/null +++ b/lib/Screens/allTrips/travel_agent_list.dart @@ -0,0 +1,933 @@ +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'; +import '../../utils/pagination.dart'; +import '../../widgets/custom_popup.dart'; +import '../../widgets/popup_listPlan_action.dart'; + +class TravelAgentListPlans extends StatefulWidget { + const TravelAgentListPlans({super.key}); + + @override + _TravelAgentListPlansState createState() => _TravelAgentListPlansState(); +} + +class _TravelAgentListPlansState extends State { + final ApiService apiService = ApiService(); + + int currentPage = 0; + int itemsPerPage = 10; + + String? userId; + String? orgId; + String? roleUser; + 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(); + roleUser = await getRoleUser(); + 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 { + late String apiUrldata; + + apiUrldata = + '$apiUrl/api/plans/findTravelAgentPlanList?org_id=$orgId&user_id=$userId'; + + 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"); + } + } + + 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: 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 yy hh:m a').format(dateTime); + } catch (e) { + return rawDate; // fallback if parsing fails + } + } + + return Container( + margin: isDesktop ? EdgeInsets.all(10.0) : null, + padding: const EdgeInsets.only(top: 15, bottom: 15, left: 20, right: 20), + 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('My Trips', + style: TextStyle( + fontFamily: "Inter", + fontSize: 16, + fontWeight: FontWeight.w600, + color: Color(0xFF212121))), + ], + ), + + Spacer(), + 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: 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'), + // + // ], + // ), + // ), + ], + ), + if (!isDesktop) const SizedBox(height: 10), + if (!isDesktop) + Row( + children: [ + Container( + width: MediaQuery.of(context).size.width * 0.85, + 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", + ), + ), + ), + ], + ), + 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 [ + SizedBox(height: 20), + 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: 20), + Text("No Approvals", + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 20, + fontFamily: "Inter", + fontWeight: FontWeight.w600, + color: Colors.black54)), + SizedBox(height: 10), + // Text("Please Create Trip", + // textAlign: TextAlign.center, + // style: TextStyle( + // fontFamily: "Inter", + // fontSize: 12, + // 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('Trip Name', + style: TextStyle( + 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('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.tripTitle, + style: TextStyle( + fontSize: 13, + fontFamily: "Inter", + ), + softWrap: true, + overflow: TextOverflow.ellipsis)), + + DataCell(Text(plan.employeeCode ?? "no data", + style: TextStyle( + fontSize: 13, + fontFamily: "Inter", + ))), + 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( + PopupMenuButton( + color: Colors.white, + padding: EdgeInsets.zero, + offset: Offset(0, 30), + icon: Icon(Icons.more_vert, + color: Color(0xFF475569)), + 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: 14), + onPressed: () { + Navigator.pop( + context); // Close popup manually + ApiService.viewPlan( + context, plan.planId, + isViewMode: true); + }, + ), + IconButton( + icon: Image.asset( + 'assets/images/IconsImg/edit.png', + width: 20, + height: 15), + onPressed: () { + Navigator.pop(context); + ApiService.viewPlan( + context, plan.planId, + isViewMode: false); + }, + ), + IconButton( + icon: Image.asset( + 'assets/images/IconsImg/delete.png', + width: 20, + height: 15), + onPressed: () { + Navigator.pop(context); + deletePlan(plan.planId); + }, + ), + IconButton( + icon: Icon(Icons.download, + color: Color(0xFF114D8B), + size: 18), + onPressed: () { + Navigator.pop(context); + apiService.getPdfDownload( + plan.planId); + }, + ), + ], + ), + ), + ), + ], + ), + ), + + // 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: 20, + // height: 15), + // ), + // 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, + child: Padding( + padding: const EdgeInsets.all(12.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Status and Employee Code + Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Container( + padding: EdgeInsets.symmetric( + horizontal: 8, vertical: 4), + decoration: BoxDecoration( + color: getStatusColor(plan.statusValue), + borderRadius: BorderRadius.circular(8), + ), + child: Text( + plan.statusValue, + style: TextStyle( + color: getStatusTextColor( + plan.statusValue), + fontSize: 12, + fontWeight: FontWeight.w600, + ), + ), + ), + PlanPopupMenu( + plan: plan, + deletePlan: deletePlan, + apiService: apiService, + ), + ], + ), + SizedBox(height: 8), + // Name + Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Text( + plan.employeeCode ?? 'No Data', + style: TextStyle( + fontSize: 12, fontFamily: "Inter"), + ), + Text( + plan.userName.isNotEmpty + ? plan.userName + : plan.travellerName, + style: TextStyle( + fontSize: 12, fontFamily: "Inter"), + ), + ], + ), + SizedBox(height: 5), + // Trip Id and Trip Name + Row( + children: [ + Expanded( + child: Text('${plan.tripTitle}', + style: TextStyle( + fontSize: 13, + fontFamily: "Inter", + fontWeight: FontWeight.bold)), + ), + ], + ), + SizedBox(height: 2), + // Type and Created On + Row( + mainAxisSize: MainAxisSize.min, + children: [ + Expanded( + child: Text('${plan.tripType}', + style: TextStyle( + fontSize: 10, + color: Colors.black87, + fontFamily: "Inter", + )), + ), + Expanded( + child: + Text('${_formatDate(plan.createdOn)}', + style: TextStyle( + fontSize: 10, + color: Colors.black87, + fontFamily: "Inter", + )), + ), + ], + ), + + // Actions + ], + ), + ), + ); + }, + ); + } + + return Expanded( + child: Column( + // mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + 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; + } +} diff --git a/lib/Screens/approvals/approval_list.dart b/lib/Screens/approvals/approval_list.dart index d63d8d7..3beaa29 100644 --- a/lib/Screens/approvals/approval_list.dart +++ b/lib/Screens/approvals/approval_list.dart @@ -13,6 +13,9 @@ 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_popup.dart'; +import '../../widgets/popup_listPlan_action.dart'; class ApprovalList extends StatefulWidget { const ApprovalList({super.key}); @@ -32,7 +35,7 @@ class _ApprovalListState extends State { Color? bodyColor; int currentPage = 0; - int itemsPerPage = 8; + int itemsPerPage = 10; List allPlans = []; List filteredPlans = []; TextEditingController searchController = TextEditingController(); @@ -296,10 +299,10 @@ class _ApprovalListState extends State { bool isDesktop = sizingInfo.deviceScreenType == DeviceScreenType.desktop; return Scaffold( - backgroundColor: Colors.white, + backgroundColor: Color(0xFFf5f5f5), // appBar: isDesktop ? null : const CustomAppBar(title: 'Home'), // drawer: isDesktop ? null : CustomDrawer(isDesktop: false), - appBar: const CustomAppBar(), + appBar: CustomAppBar(isDesktop: isDesktop), drawer: CustomDrawer(isDesktop: false), body: Padding( padding: isDesktop @@ -309,7 +312,7 @@ class _ApprovalListState extends State { vertical: MediaQuery.of(context).size.height * 0, // 5% of screen height as vertical padding ) - : EdgeInsets.all(8), + : EdgeInsets.all(0), child: Row( children: [ // if (isDesktop) CustomDrawer(isDesktop: true), @@ -346,18 +349,28 @@ class _ApprovalListState extends State { } 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), + margin: isDesktop ? EdgeInsets.all(10.0) : null, + padding: const EdgeInsets.only(top: 15, bottom: 15, left: 20, right: 20), 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(1.0), + // padding: const EdgeInsets.all(10.0), color: Colors.white, child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, @@ -370,7 +383,7 @@ class _ApprovalListState extends State { children: [ Row( children: [ - const Text('List For Approval', + const Text('My Approvals', style: TextStyle( fontFamily: "Inter", fontSize: 16, @@ -439,7 +452,7 @@ class _ApprovalListState extends State { controller: searchController, onChanged: filterPlans, decoration: InputDecoration( - hintText: "Search for a plan", + hintText: "Search...", hintStyle: TextStyle( fontSize: 12, color: Color(0xFF9E9DBD)), prefixIcon: Icon( @@ -488,25 +501,20 @@ class _ApprovalListState extends State { children: const [ Icon(Icons.error_outline, color: Colors.redAccent, size: 60), - SizedBox(height: 16), + SizedBox(height: 1), Text("Oops!", style: TextStyle( fontSize: 22, fontWeight: FontWeight.bold, color: Colors.redAccent)), - SizedBox(height: 8), - Text("No Plans Available For This User", + SizedBox(height: 15), + Text("No Approvals", 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), + fontFamily: "Inter", + fontWeight: FontWeight.w600, + color: Colors.black54)), ], ), ), @@ -637,30 +645,16 @@ class _ApprovalListState extends State { height: 25, alignment: Alignment.center, decoration: BoxDecoration( - 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), + color: getStatusColor(plan.statusValue), + borderRadius: BorderRadius.circular(8), ), + child: Text( plan.statusValue, textAlign: TextAlign.center, style: TextStyle( - color: (plan.statusValue == - "Partially Approved" || - plan.statusValue == "Approved" || - plan.statusValue == "Rejected") - ? Colors.black - : plan.statusValue == "Completed" - ? Colors.white - : Colors.grey, + color: + getStatusTextColor(plan.statusValue), fontSize: 12, fontFamily: "Inter", fontWeight: FontWeight.w400, @@ -668,55 +662,127 @@ class _ApprovalListState extends State { ), ), ), - DataCell(Row(children: [ - IconButton( - icon: const Icon( - Icons.remove_red_eye, - color: Color(0xFF475569), - size: 18, + + DataCell( + 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: () { + Navigator.pop( + context); // Close popup manually + ApiService.viewPlan( + context, plan.planId, + isViewMode: true); + }, + ), + IconButton( + icon: Image.asset( + 'assets/images/IconsImg/edit.png', + width: 20, + height: 15), + onPressed: () { + Navigator.pop(context); + ApiService.viewPlan( + context, plan.planId, + isViewMode: false); + }, + ), + IconButton( + icon: Image.asset( + 'assets/images/IconsImg/delete.png', + width: 20, + height: 15), + onPressed: () { + Navigator.pop(context); + deletePlan(plan.planId); + }, + ), + IconButton( + icon: Icon(Icons.download, + color: Color(0xFF114D8B), + size: 18), + onPressed: () { + Navigator.pop(context); + apiService.getPdfDownload( + plan.planId); + }, + ), + ], + ), + ), ), - 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); - }), - ])), + ), + // 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(), ), @@ -724,66 +790,154 @@ class _ApprovalListState extends State { }, ); + 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, + child: Padding( + padding: const EdgeInsets.all(12.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Status and Employee Code + Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Container( + padding: EdgeInsets.symmetric( + horizontal: 8, vertical: 4), + decoration: BoxDecoration( + color: getStatusColor(plan.statusValue), + borderRadius: BorderRadius.circular(8), + ), + child: Text( + plan.statusValue, + style: TextStyle( + color: getStatusTextColor( + plan.statusValue), + fontSize: 12, + fontWeight: FontWeight.w600, + ), + ), + ), + PlanPopupMenu( + plan: plan, + deletePlan: deletePlan, + apiService: apiService, + ), + ], + ), + SizedBox(height: 8), + // Name + Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Text( + plan.employeeCode ?? 'No Data', + style: TextStyle( + fontSize: 12, fontFamily: "Inter"), + ), + Text( + plan.userName.isNotEmpty + ? plan.userName + : plan.travellerName, + style: TextStyle( + fontSize: 12, fontFamily: "Inter"), + ), + ], + ), + SizedBox(height: 5), + // Trip Id and Trip Name + Row( + children: [ + Expanded( + child: Text('${plan.tripTitle}', + style: TextStyle( + fontSize: 13, + fontFamily: "Inter", + fontWeight: FontWeight.bold)), + ), + ], + ), + SizedBox(height: 2), + // Type and Created On + Row( + mainAxisSize: MainAxisSize.min, + children: [ + Expanded( + child: Text('${plan.tripType}', + style: TextStyle( + fontSize: 10, + color: Colors.black87, + fontFamily: "Inter", + )), + ), + Expanded( + child: + Text('${_formatDate(plan.createdOn)}', + style: TextStyle( + fontSize: 10, + color: Colors.black87, + fontFamily: "Inter", + )), + ), + ], + ), + + // Actions + ], + ), + ), + ); + }, + ); + } + return Expanded( child: Column( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + // mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - isDesktop - ? table - : SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: SingleChildScrollView( + Expanded( + child: isDesktop + ? SingleChildScrollView( scrollDirection: Axis.vertical, - child: table, + child: table, // <-- your existing 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, - ), - ], + ), + 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; + }); + }, ), ], ), @@ -797,3 +951,23 @@ class _ApprovalListState extends State { ); } } + +// 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; +} diff --git a/lib/Screens/authentication/login/login_widget.dart b/lib/Screens/authentication/login/login_widget.dart index 5b15bf1..3807c5d 100644 --- a/lib/Screens/authentication/login/login_widget.dart +++ b/lib/Screens/authentication/login/login_widget.dart @@ -26,6 +26,7 @@ class _LoginWidgetState extends State { final TextEditingController _newPasswordController = TextEditingController(); final TextEditingController _confirmPasswordController = TextEditingController(); + String? userRole; bool _obscureText = true; bool _isForgotPassword = false; bool _showOtpResetFields = false; @@ -61,6 +62,12 @@ class _LoginWidgetState extends State { final pref = await SharedPreferences.getInstance(); await pref.setString('auth_token', token); await pref.setString('user_data', jsonEncode(userData)); + + userRole = userData['role']; + + print("userData - $userData"); + print("userData11 - ${userData['role']}"); + print("userData12 - $userRole"); } } catch (e) { print('Error decoding token: $e'); @@ -99,7 +106,14 @@ class _LoginWidgetState extends State { backgroundColor: Colors.green, // Set background to green ), ); - context.go('/listPlan'); // Navigate to home + + if (userRole == "Travel Agent") { + context.go('/listTravelAgentPlan'); + } else if (userRole == "Org Admin" || userRole == "Travel Admin") { + context.go('/listAllPlan'); + } else { + context.go('/listPlan'); + } } else { ScaffoldMessenger.of(context).showSnackBar( SnackBar( diff --git a/lib/Screens/dashboard/home_page.dart b/lib/Screens/dashboard/home_page.dart index 669978b..6b4773d 100644 --- a/lib/Screens/dashboard/home_page.dart +++ b/lib/Screens/dashboard/home_page.dart @@ -13,7 +13,7 @@ class HomePage extends StatelessWidget { bool isDesktop = sizingInfo.deviceScreenType == DeviceScreenType.desktop; return Scaffold( - appBar: isDesktop ? null : const CustomAppBar(), + appBar: CustomAppBar(isDesktop: isDesktop), drawer: isDesktop ? null : CustomDrawer(isDesktop: false), body: Row( children: [ diff --git a/lib/Screens/group/group.dart b/lib/Screens/group/group.dart index 16e06ce..195e127 100644 --- a/lib/Screens/group/group.dart +++ b/lib/Screens/group/group.dart @@ -324,7 +324,7 @@ class _groupState extends State { return Scaffold( backgroundColor: Colors.white, - appBar: const CustomAppBar(), + appBar: CustomAppBar(isDesktop: isDesktop), drawer: CustomDrawer(isDesktop: false), body: Padding( padding: isDesktop diff --git a/lib/Screens/group/groupList.dart b/lib/Screens/group/groupList.dart index 11c1721..8979f07 100644 --- a/lib/Screens/group/groupList.dart +++ b/lib/Screens/group/groupList.dart @@ -132,7 +132,7 @@ class _GroupListState extends State { return Scaffold( backgroundColor: Colors.white, - appBar: const CustomAppBar(), + appBar: CustomAppBar(isDesktop: isDesktop), drawer: CustomDrawer(isDesktop: false), body: Padding( padding: isDesktop diff --git a/lib/Screens/organization/orgSetup.dart b/lib/Screens/organization/orgSetup.dart index a2d2935..2d1ab3a 100644 --- a/lib/Screens/organization/orgSetup.dart +++ b/lib/Screens/organization/orgSetup.dart @@ -331,7 +331,7 @@ class _OrgSetUpState extends State { return Scaffold( backgroundColor: Colors.white, - appBar: const CustomAppBar(), + appBar: CustomAppBar(isDesktop: isDesktop), drawer: CustomDrawer(isDesktop: false), body: Padding( padding: isDesktop diff --git a/lib/Screens/organization/org_List.dart b/lib/Screens/organization/org_List.dart index 4f439cb..f71eb91 100644 --- a/lib/Screens/organization/org_List.dart +++ b/lib/Screens/organization/org_List.dart @@ -61,7 +61,7 @@ class _OrganizationListState extends State { backgroundColor: Colors.white, // appBar: isDesktop ? null : const CustomAppBar(title: 'Home'), // drawer: isDesktop ? null : CustomDrawer(isDesktop: false), - appBar: const CustomAppBar(), + appBar: CustomAppBar(isDesktop: isDesktop), drawer: CustomDrawer(isDesktop: false), body: Row( children: [ diff --git a/lib/Screens/plans/create_plans.dart b/lib/Screens/plans/create_plans.dart index 0774bb4..7cbd44f 100644 --- a/lib/Screens/plans/create_plans.dart +++ b/lib/Screens/plans/create_plans.dart @@ -65,8 +65,10 @@ class _CreatePlansState extends State { bool isDesktop = sizingInfo.deviceScreenType == DeviceScreenType.desktop; return Scaffold( - backgroundColor: Colors.white, - appBar: const CustomAppBar(), + // backgroundColor: Colors.white, + backgroundColor: Color(0xFFf5f5f5), + // backgroundColor: Color(0xFFFCFCFC), + appBar: CustomAppBar(isDesktop: isDesktop), drawer: CustomDrawer(isDesktop: false), body: Padding( padding: isDesktop @@ -109,16 +111,18 @@ class _CreatePlansState extends State { // print("RECived palndata - ${planData}"); return Container( - // margin: const EdgeInsets.only(left: 10.0, right: 15.0, top: 10.0, bottom: 10.0), - // decoration: BoxDecoration( - // // color: Colors.amber, - // // color: bodyColor, - // color: Color(0xFFE1F5FE), - // border: Border.all( - // color: Colors.white, - // // color: Color(0xFFF7F7FB), - // - // width: 3.5)), + margin: const EdgeInsets.only(top: 10.0, bottom: 10.0), + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + // color: Colors.amber, + // color: bodyColor, + color: Colors.white, + // border: Border.all( + // color: Colors.white, + // // color: Color(0xFFF7F7FB), + // + // width: 3.5) + ), child: Column( children: [ // Container( diff --git a/lib/Screens/plans/list_plans.dart b/lib/Screens/plans/list_plans.dart index 4d49138..1cf67b0 100644 --- a/lib/Screens/plans/list_plans.dart +++ b/lib/Screens/plans/list_plans.dart @@ -13,6 +13,9 @@ 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_popup.dart'; +import '../../widgets/popup_listPlan_action.dart'; class ListPlans extends StatefulWidget { const ListPlans({super.key}); @@ -25,7 +28,7 @@ class _ListPlansState extends State { final ApiService apiService = ApiService(); int currentPage = 0; - int itemsPerPage = 8; + int itemsPerPage = 10; String? userId; String? orgId; @@ -272,10 +275,10 @@ class _ListPlansState extends State { bool isDesktop = sizingInfo.deviceScreenType == DeviceScreenType.desktop; return Scaffold( - backgroundColor: Colors.white, + backgroundColor: Color(0xFFf5f5f5), // appBar: isDesktop ? null : const CustomAppBar(title: 'Home'), // drawer: isDesktop ? null : CustomDrawer(isDesktop: false), - appBar: const CustomAppBar(), + appBar: CustomAppBar(isDesktop: isDesktop), drawer: CustomDrawer(isDesktop: false), body: Padding( padding: isDesktop @@ -285,7 +288,7 @@ class _ListPlansState extends State { vertical: MediaQuery.of(context).size.height * 0, // 5% of screen height as vertical padding ) - : EdgeInsets.all(8), + : EdgeInsets.all(0), child: Row( children: [ // if (isDesktop) CustomDrawer(isDesktop: true), @@ -322,10 +325,8 @@ class _ListPlansState extends State { } 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), + margin: isDesktop ? EdgeInsets.all(10.0) : null, + padding: const EdgeInsets.only(top: 15, bottom: 15, left: 20, right: 20), height: isDesktop ? MediaQuery.of(context).size.height * 0.98 : MediaQuery.of(context).size.height, @@ -359,7 +360,7 @@ class _ListPlansState extends State { children: [ Row( children: [ - const Text('Trip List', + const Text('My Trip Request', style: TextStyle( fontFamily: "Inter", fontSize: 16, @@ -369,46 +370,45 @@ class _ListPlansState extends State { ), 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, + 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), + ), ), - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(12), + style: TextStyle( + fontSize: 12, + fontFamily: "Inter", ), - 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(), + if (isDesktop) Spacer(), // ElevatedButton( // style: ElevatedButton.styleFrom( // foregroundColor: Colors.white, @@ -478,7 +478,7 @@ class _ListPlansState extends State { MainAxisSize.min, // Ensures content fits nicely children: [ Text( - "Add New Plan", + "Add New Trip", style: TextStyle(fontSize: 13), ), SizedBox(width: 8), // spacing between icon and text @@ -492,7 +492,50 @@ class _ListPlansState extends State { ), ], ), + if (!isDesktop) const SizedBox(height: 10), + if (!isDesktop) + Row( + children: [ + Container( + width: MediaQuery.of(context).size.width * 0.85, + 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", + ), + ), + ), + ], + ), + const SizedBox(height: 10), + FutureBuilder>( future: futurePlans, builder: (context, snapshot) { @@ -509,25 +552,27 @@ class _ListPlansState extends State { children: const [ Icon(Icons.error_outline, color: Colors.redAccent, size: 60), - SizedBox(height: 16), + SizedBox(height: 1), Text("Oops!", style: TextStyle( fontSize: 22, fontWeight: FontWeight.bold, color: Colors.redAccent)), - SizedBox(height: 8), - Text("No Plans Available For This User", + SizedBox(height: 15), + Text("No Trips", textAlign: TextAlign.center, style: TextStyle( fontSize: 20, - fontWeight: FontWeight.bold, - color: Colors.grey)), - SizedBox(height: 20), - Text("Please Create Plan", + fontFamily: "Inter", + fontWeight: FontWeight.w600, + color: Colors.black54)), + SizedBox(height: 10), + Text("Please Create Trip", textAlign: TextAlign.center, style: TextStyle( - fontSize: 16, color: Colors.grey)), - SizedBox(height: 20), + fontFamily: "Inter", + fontSize: 12, + color: Colors.grey)), ], ), ), @@ -569,15 +614,15 @@ class _ListPlansState extends State { fontFamily: "Inter", fontWeight: FontWeight.w600))), DataColumn( - label: Text('Employee Code', + label: Text('Trip Name', style: TextStyle( - // color: Color(0xFF9E9DBD), fontSize: 13, fontFamily: "Inter", fontWeight: FontWeight.w600))), DataColumn( - label: Text('Trip Name', + label: Text('Employee Code', style: TextStyle( + // color: Color(0xFF9E9DBD), fontSize: 13, fontFamily: "Inter", fontWeight: FontWeight.w600))), @@ -619,11 +664,7 @@ class _ListPlansState extends State { fontSize: 13, fontFamily: "Inter", ))), - DataCell(Text(plan.employeeCode ?? "no data", - style: TextStyle( - fontSize: 13, - fontFamily: "Inter", - ))), + DataCell(Text(plan.tripTitle, style: TextStyle( fontSize: 13, @@ -631,6 +672,12 @@ class _ListPlansState extends State { ), softWrap: true, overflow: TextOverflow.ellipsis)), + + DataCell(Text(plan.employeeCode ?? "no data", + style: TextStyle( + fontSize: 13, + fontFamily: "Inter", + ))), DataCell(Text( plan.userName.isNotEmpty ? plan.userName @@ -691,53 +738,122 @@ class _ListPlansState extends State { ), ), ), - DataCell(Row(children: [ - IconButton( - icon: const Icon( - Icons.remove_red_eye, - color: Color(0xFF475569), - size: 18, + DataCell( + PopupMenuButton( + color: Colors.white, + padding: EdgeInsets.zero, + offset: Offset(0, 30), + icon: Icon(Icons.more_vert, + size: 14, color: Color(0xFF475569)), + 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: () { + Navigator.pop( + context); // Close popup manually + ApiService.viewPlan( + context, plan.planId, + isViewMode: true); + }, + ), + IconButton( + icon: Image.asset( + 'assets/images/IconsImg/edit.png', + width: 20, + height: 15), + onPressed: () { + Navigator.pop(context); + ApiService.viewPlan( + context, plan.planId, + isViewMode: false); + }, + ), + IconButton( + icon: Image.asset( + 'assets/images/IconsImg/delete.png', + width: 20, + height: 15), + onPressed: () { + Navigator.pop(context); + deletePlan(plan.planId); + }, + ), + IconButton( + icon: Icon(Icons.download, + color: Color(0xFF114D8B), + size: 18), + onPressed: () { + Navigator.pop(context); + apiService.getPdfDownload( + plan.planId); + }, + ), + ], + ), + ), ), - 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); - }), - ])), + // 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(), ), @@ -745,66 +861,148 @@ class _ListPlansState extends State { }, ); + 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, + child: Padding( + padding: const EdgeInsets.all(12.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Status and Employee Code + Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Container( + padding: EdgeInsets.symmetric( + horizontal: 8, vertical: 4), + decoration: BoxDecoration( + color: getStatusColor(plan.statusValue), + borderRadius: BorderRadius.circular(8), + ), + child: Text( + plan.statusValue, + style: TextStyle( + color: getStatusTextColor( + plan.statusValue), + fontSize: 12, + fontWeight: FontWeight.w600, + ), + ), + ), + PlanPopupMenu( + plan: plan, + deletePlan: deletePlan, + apiService: apiService, + ), + ], + ), + SizedBox(height: 8), + // Name + Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Text( + plan.employeeCode ?? 'No Data', + style: TextStyle( + fontSize: 12, fontFamily: "Inter"), + ), + Text( + plan.userName.isNotEmpty + ? plan.userName + : plan.travellerName, + style: TextStyle( + fontSize: 12, fontFamily: "Inter"), + ), + ], + ), + SizedBox(height: 5), + // Trip Id and Trip Name + Row( + children: [ + Expanded( + child: Text('${plan.tripTitle}', + style: TextStyle( + fontSize: 13, + fontFamily: "Inter", + fontWeight: FontWeight.bold)), + ), + ], + ), + SizedBox(height: 2), + // Type and Created On + Row( + mainAxisSize: MainAxisSize.min, + children: [ + Expanded( + child: Text('${plan.tripType}', + style: TextStyle( + fontSize: 10, + color: Colors.black87, + fontFamily: "Inter", + )), + ), + Expanded( + child: + Text('${_formatDate(plan.createdOn)}', + style: TextStyle( + fontSize: 10, + color: Colors.black87, + fontFamily: "Inter", + )), + ), + ], + ), + + // Actions + ], + ), + ), + ); + }, + ); + } + return Expanded( child: Column( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + // mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - isDesktop - ? table - : SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: SingleChildScrollView( + Expanded( + child: isDesktop + ? 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, - ), - ], + 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; + }); + }, ), ], ), diff --git a/lib/Screens/policy/policy.dart b/lib/Screens/policy/policy.dart index 0f7fc5b..e8444aa 100644 --- a/lib/Screens/policy/policy.dart +++ b/lib/Screens/policy/policy.dart @@ -384,7 +384,7 @@ class _PolicyState extends State { return Scaffold( backgroundColor: Colors.white, - appBar: const CustomAppBar(), + appBar: CustomAppBar(isDesktop: isDesktop), drawer: CustomDrawer(isDesktop: false), body: Padding( padding: isDesktop diff --git a/lib/Screens/policy/policy_list.dart b/lib/Screens/policy/policy_list.dart index 34357cd..4646778 100644 --- a/lib/Screens/policy/policy_list.dart +++ b/lib/Screens/policy/policy_list.dart @@ -148,7 +148,7 @@ class _PolicyListState extends State { return Scaffold( backgroundColor: Colors.white54, - appBar: const CustomAppBar(), + appBar: CustomAppBar(isDesktop: isDesktop), drawer: CustomDrawer(isDesktop: false), body: Padding( padding: isDesktop diff --git a/lib/Screens/userManagement/create_user/create_user.dart b/lib/Screens/userManagement/create_user/create_user.dart index dfa7ab2..fcc5f9b 100644 --- a/lib/Screens/userManagement/create_user/create_user.dart +++ b/lib/Screens/userManagement/create_user/create_user.dart @@ -666,7 +666,7 @@ class _CreateUserFormState extends State { backgroundColor: Colors.white, // appBar: isDesktop ? null : const CustomAppBar(title: 'Create User '), // drawer: isDesktop ? null : CustomDrawer(isDesktop: false), - appBar: const CustomAppBar(), + appBar: CustomAppBar(isDesktop: isDesktop), drawer: CustomDrawer(isDesktop: false), body: Padding( padding: isDesktop diff --git a/lib/Screens/userManagement/user_List.dart b/lib/Screens/userManagement/user_List.dart index 1d02459..7e4b71d 100644 --- a/lib/Screens/userManagement/user_List.dart +++ b/lib/Screens/userManagement/user_List.dart @@ -306,7 +306,7 @@ class _UserListScreenState extends State { backgroundColor: Colors.white, // appBar: isDesktop ? null : const CustomAppBar(title: 'User Management'), // drawer: isDesktop ? null : CustomDrawer(isDesktop: false), - appBar: const CustomAppBar(), + appBar: CustomAppBar(isDesktop: isDesktop), drawer: CustomDrawer(isDesktop: false), body: Padding( padding: isDesktop diff --git a/lib/routes/custom_appBar.dart b/lib/routes/custom_appBar.dart index 0e3a7e4..0ac1e5e 100644 --- a/lib/routes/custom_appBar.dart +++ b/lib/routes/custom_appBar.dart @@ -5,6 +5,7 @@ import 'package:shared_preferences/shared_preferences.dart'; // don't forget import '../services/apiService.dart'; class CustomAppBar extends StatefulWidget implements PreferredSizeWidget { + final bool isDesktop; final String? logoUrl; final String? username; final List? actions; @@ -16,6 +17,7 @@ class CustomAppBar extends StatefulWidget implements PreferredSizeWidget { this.username, this.actions, this.showBackButton = false, + required this.isDesktop, }); @override @@ -140,161 +142,231 @@ class _CustomAppBarState extends State { return AppBar( backgroundColor: Colors.white, surfaceTintColor: Colors.white, - // elevation: 3, - automaticallyImplyLeading: false, - // leading: showBackButton - // ? IconButton( - // icon: const Icon(Icons.arrow_back, color: Colors.black87), - // onPressed: () => Navigator.pop(context), - // ) - // : null, + automaticallyImplyLeading: !widget.isDesktop, titleSpacing: 0, - title: Padding( - padding: EdgeInsets.symmetric( - horizontal: MediaQuery.of(context).size.width * 0.05), - child: Row( - children: [ - Padding( - padding: const EdgeInsets.all(10), - // padding: const EdgeInsets.symmetric(horizontal: 40, vertical: 10), - child: selectedOrg?['logo'] != null - ? ClipRect( - child: Image.network( - selectedOrg!['logo'], - width: 100, - height: 80, - // fit: BoxFit.contain, - errorBuilder: (context, error, stackTrace) { - return const CircleAvatar( + title: !widget.isDesktop + ? Text('') + : Padding( + padding: EdgeInsets.symmetric( + horizontal: MediaQuery.of(context).size.width * 0.05), + child: Row( + children: [ + Padding( + padding: const EdgeInsets.all(10), + // padding: const EdgeInsets.symmetric(horizontal: 40, vertical: 10), + + child: selectedOrg?['logo'] != null + ? ClipRect( + child: Image.network( + selectedOrg!['logo'], + width: 100, + height: 80, + // fit: BoxFit.contain, + errorBuilder: (context, error, stackTrace) { + return const CircleAvatar( + radius: 20, + backgroundColor: Colors.redAccent, + child: Icon(Icons.error, size: 10), + ); + }, + ), + ) + : const CircleAvatar( radius: 20, - backgroundColor: Colors.redAccent, - child: Icon(Icons.error, size: 10), - ); - }, - ), - ) - : const CircleAvatar( - radius: 20, - backgroundColor: Colors.white, - child: Icon( - Icons.add_a_photo, - size: 10, - color: Colors.grey, - ), - ), - ), - SizedBox( - width: MediaQuery.of(context).size.width * 0.25, - ), - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - if (userData?["role"] == "Org Admin" || - userData?["role"] == "Travel Admin") - MouseRegion( - cursor: SystemMouseCursors.click, - onEnter: (_) { - setState(() { - // _myTravelRequestPadding = - // EdgeInsets.symmetric(horizontal: 12, vertical: 78); - // _myTravelRequestColor = - // Color(0xFF114D8B); // Change color on hover - }); - }, - onExit: (_) { - setState(() { - // _myTravelRequestPadding = EdgeInsets.symmetric( - // horizontal: 8, vertical: 4); // Normal padding - // _myTravelRequestColor = - // Color(0xFF475569); // Revert color when hover ends - }); - }, - child: InkWell( - onTap: () { - context.go('/listAllPlan'); - }, - child: Text( - "All Trips", - style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.w600, - // color: Color(0xFF475569), - color: _myTravelRequestColor, - fontFamily: "Inter", + backgroundColor: Colors.white, + child: Icon( + Icons.add_a_photo, + size: 10, + color: Colors.grey, + ), ), - )), ), - const SizedBox(width: 20), - MouseRegion( - cursor: SystemMouseCursors.click, - onEnter: (_) { - setState(() { - // _myTravelRequestPadding = - // EdgeInsets.symmetric(horizontal: 12, vertical: 78); - // _myTravelRequestColor = - // Color(0xFF114D8B); // Change color on hover - }); - }, - onExit: (_) { - setState(() { - // _myTravelRequestPadding = EdgeInsets.symmetric( - // horizontal: 8, vertical: 4); // Normal padding - // _myTravelRequestColor = - // Color(0xFF475569); // Revert color when hover ends - }); - }, - child: InkWell( - onTap: () { - context.go('/listPlan'); - }, - child: Text( - "My Travel Request", - style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.w600, - // color: Color(0xFF475569), - color: _myTravelRequestColor, - fontFamily: "Inter", - ), - )), - ), - const SizedBox(width: 20), - MouseRegion( - cursor: SystemMouseCursors.click, - onEnter: (_) { - setState(() { - _myApprovalsColor = - Color(0xFF114D8B); // Change color on hover - }); - }, - onExit: (_) { - setState(() { - _myApprovalsColor = - Color(0xFF475569); // Revert color when hover ends - }); - }, - child: InkWell( - onTap: () { - context.go('/ApprovalList'); - }, - child: Text( - "My Approvals", - style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.w600, - color: _myApprovalsColor, - // color: Color(0xFF475569), - fontFamily: "Inter", - ), - )), - ), - ], + SizedBox( + width: MediaQuery.of(context).size.width * 0.2, + ), + Container( + width: MediaQuery.of(context).size.width * 0.35, + // color: Colors.blueGrey, + child: Row( + // crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + if (userData?["role"] == "Org Admin" || + userData?["role"] == "Travel Admin") + MouseRegion( + cursor: SystemMouseCursors.click, + onEnter: (_) { + setState(() { + // _myTravelRequestPadding = + // EdgeInsets.symmetric(horizontal: 12, vertical: 78); + // _myTravelRequestColor = + // Color(0xFF114D8B); // Change color on hover + }); + }, + onExit: (_) { + setState(() { + // _myTravelRequestPadding = EdgeInsets.symmetric( + // horizontal: 8, vertical: 4); // Normal padding + // _myTravelRequestColor = + // Color(0xFF475569); // Revert color when hover ends + }); + }, + child: InkWell( + onTap: () { + context.go('/listAllPlan'); + }, + child: Text( + "All Trips", + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w600, + // color: Color(0xFF475569), + color: _myTravelRequestColor, + fontFamily: "Inter", + ), + )), + ), + const SizedBox(width: 20), + if (userDetails["role"] == "Travel Agent") + MouseRegion( + cursor: SystemMouseCursors.click, + onEnter: (_) { + setState(() { + // _myTravelRequestPadding = + // EdgeInsets.symmetric(horizontal: 12, vertical: 78); + // _myTravelRequestColor = + // Color(0xFF114D8B); // Change color on hover + }); + }, + onExit: (_) { + setState(() { + // _myTravelRequestPadding = EdgeInsets.symmetric( + // horizontal: 8, vertical: 4); // Normal padding + // _myTravelRequestColor = + // Color(0xFF475569); // Revert color when hover ends + }); + }, + child: InkWell( + onTap: () { + context.go('/listTravelAgentPlan'); + }, + child: Text( + "My Trips", + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w600, + // color: Color(0xFF475569), + color: _myTravelRequestColor, + fontFamily: "Inter", + ), + )), + ), + if (userDetails["role"] != "Travel Agent") + const SizedBox(width: 20), + if (userDetails["role"] != "Travel Agent") + MouseRegion( + cursor: SystemMouseCursors.click, + onEnter: (_) { + setState(() { + // _myTravelRequestPadding = + // EdgeInsets.symmetric(horizontal: 12, vertical: 78); + // _myTravelRequestColor = + // Color(0xFF114D8B); // Change color on hover + }); + }, + onExit: (_) { + setState(() { + // _myTravelRequestPadding = EdgeInsets.symmetric( + // horizontal: 8, vertical: 4); // Normal padding + // _myTravelRequestColor = + // Color(0xFF475569); // Revert color when hover ends + }); + }, + child: InkWell( + onTap: () { + context.go('/listPlan'); + }, + child: Text( + "My Trip Request", + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w600, + // color: Color(0xFF475569), + color: _myTravelRequestColor, + fontFamily: "Inter", + ), + )), + ), + const SizedBox(width: 20), + if (userData?["role"] != "Travel Agent") + MouseRegion( + cursor: SystemMouseCursors.click, + onEnter: (_) { + setState(() { + _myApprovalsColor = + Color(0xFF114D8B); // Change color on hover + }); + }, + onExit: (_) { + setState(() { + _myApprovalsColor = Color( + 0xFF475569); // Revert color when hover ends + }); + }, + child: InkWell( + onTap: () { + context.go('/ApprovalList'); + }, + child: Text( + "My Approvals", + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w600, + color: _myApprovalsColor, + // color: Color(0xFF475569), + fontFamily: "Inter", + ), + )), + ), + if (userData?["role"] == "Travel Agent") + MouseRegion( + cursor: SystemMouseCursors.click, + onEnter: (_) { + setState(() { + _myApprovalsColor = + Color(0xFF114D8B); // Change color on hover + }); + }, + onExit: (_) { + setState(() { + _myApprovalsColor = Color( + 0xFF475569); // Revert color when hover ends + }); + }, + child: InkWell( + onTap: () { + context.go('/listTravelAgentPlan'); + }, + child: Text( + "My Approvals", + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w600, + color: _myApprovalsColor, + // color: Color(0xFF475569), + fontFamily: "Inter", + ), + )), + ), + ], + ), + ), + Spacer(), + ], + ), ), - ], - ), - ), actions: [ Padding( padding: EdgeInsets.symmetric( @@ -306,8 +378,6 @@ class _CustomAppBarState extends State { builder: (context) => PopupMenuButton( color: Colors.white, padding: EdgeInsets.zero, - // icon: const Icon(Icons.arrow_drop_down, - // size: 20, color: Colors.black87), offset: const Offset(0, 50), // 👈 shift it 50 pixels down onSelected: (String value) { switch (value) { @@ -341,14 +411,23 @@ class _CustomAppBarState extends State { // menuItems.map(buildMenuItem).toList(), itemBuilder: (BuildContext context) { - final isUser = userData?["role"] == "User"; - final filteredItems = isUser - ? menuItems - .where((item) => - item['value'] == '/CreateUserDetails' || - item['value'] == '/logout') - .toList() - : menuItems; + // final isUser = userData?["role"] == "User"; + final role = userData?["role"]; + List> filteredItems; + + if (role == "User") { + filteredItems = menuItems + .where((item) => + item['value'] == '/CreateUserDetails' || + item['value'] == '/logout') + .toList(); + } else if (role == "Travel Agent") { + filteredItems = menuItems + .where((item) => item['value'] == '/logout') + .toList(); + } else { + filteredItems = menuItems; + } // Create a new list starting with role display and divider return [ diff --git a/lib/routes/custom_drawer.dart b/lib/routes/custom_drawer.dart index 3fb2ebf..a22bb0c 100644 --- a/lib/routes/custom_drawer.dart +++ b/lib/routes/custom_drawer.dart @@ -165,167 +165,28 @@ class _CustomDrawerState extends State { ), ], ), - - SizedBox( - height: 20, - ), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Row( - children: [ - Text( - "Welcome,", - style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.w500, - fontFamily: "Archivo", - color: Colors.black, - ), - ), - ], - ), - MouseRegion( - cursor: SystemMouseCursors.click, - child: Row( - children: [ - GestureDetector( - onTap: () { - print("ONTAP Custom"); - print("ONTAP Custom- $userDetails "); - context.go( - "/CreateUserDetails", - extra: { - "selectedUser": userDetails, - "isEditProfile": true, - "isViewMode": true - }, - ); - }, - child: Text( - userData?["name"] ?? "N/A", - style: TextStyle( - fontSize: 13, - fontWeight: FontWeight.w600, - fontFamily: "Archivo", - // color: Color(0xFF12B24B), - color: layoutColor, - ), - ), - ), - ], - ), - ), - ], - ), - - // GestureDetector( - // onTap: () { - // print("ONTAP Custom"); - // print("ONTAP Custom- $userDetails "); - // context.go( - // "/CreateUserDetails", - // extra: { - // "selectedUser": userDetails, - // "isEditProfile": true, - // "isViewMode": true - // }, - // ); - // }, - // child: SizedBox( - // height: 80, - // child: Container( - // color: Color(0xFFF3F3FA), - // padding: EdgeInsets.all(16), - // // width: double.infinity, - // child: Row( - // // mainAxisAlignment: MainAxisAlignment.center, - // children: [ - // Column( - // crossAxisAlignment: CrossAxisAlignment.start, - // mainAxisAlignment: MainAxisAlignment.start, - // children: [ - // Column( - // children: [ - // Text( - // "Welcome", - // style: TextStyle( - // color: Colors.black, - // fontSize: 13, - // fontWeight: FontWeight.w400, - // fontFamily: "Archivo"), - // ), - // ], - // ), - // Column( - // children: [ - // Text( - // userData?["name"] ?? "N/A", - // style: TextStyle( - // color: Color(0xFF12B24B), - // fontSize: 13, - // fontWeight: FontWeight.w600, - // fontFamily: "Archivo"), - // ), - // ], - // ) - // ], - // ), - // ], - // )), - // ), - // ), - SizedBox( - height: 15, - ), ], ), ), // _buildDrawerItem(context, Icons.home_outlined, 'Home', '/home'), - _buildDrawerItem(context, Icons.request_page_outlined, - 'My Travel Request', '/listPlan'), - _buildDrawerItem(context, Icons.assessment_outlined, 'My Approvals', - '/ApprovalList'), - if (userData?["role"] != "User") - _buildDrawerItem(context, Icons.account_circle_outlined, - 'User List', '/listUser'), - // _buildExpandableItem( - // context, - // Icons.assessment_outlined, - // 'Plans', - // [ - // _buildSubDrawerItem( - // context, 'My Travel Request', '/listPlan'), - // _buildSubDrawerItem(context, 'My Approvals', '/ApprovalList') - // ], - // '/listPlan'), - // - // _buildExpandableItem( - // context, - // Icons.account_circle_outlined, - // 'User ', - // [ - // _buildSubDrawerItem(context, 'User List', '/listUser'), - // // _buildSubDrawerItem(context,'PlanB','/PlanB') - // ], - // '/listUser'), - if (userData?["role"] != "User") - _buildExpandableItem( - context, - Icons.settings_outlined, - 'Settings ', - [ - _buildSubDrawerItem( - context, 'Organization', '/OrganizationSetup'), - _buildSubDrawerItem(context, 'Group', '/group'), - _buildSubDrawerItem(context, 'Policy', '/PolicyList'), - // _buildSubDrawerItem(context,'PlanB','/PlanB') - ], - '/OrganizationSetup', - ), - _buildDrawerItem(context, Icons.login_outlined, 'Logout', '/'), - if (widget.isDesktop) Spacer(), + + if (userData?["role"] == "Org Admin" || + userData?["role"] == "Travel Admin") + _buildDrawerItem(context, Icons.request_page_outlined, + 'All Trips', '/listAllPlan'), + + if (userDetails["role"] != "Travel Agent") + _buildDrawerItem(context, Icons.request_page_outlined, + 'My Approvals1', '/listTravelAgentPlan'), + + if (userDetails["role"] != "Travel Agent") + _buildDrawerItem(context, Icons.request_page_outlined, + 'My Trip Request', '/listPlan'), + + if (userDetails["role"] != "Travel Agent") + _buildDrawerItem(context, Icons.assessment_outlined, + 'My Approvals', '/ApprovalList'), + Container( margin: const EdgeInsets.all(20), child: Row( diff --git a/lib/routes/custom_router.dart b/lib/routes/custom_router.dart index 87e328c..11d5689 100644 --- a/lib/routes/custom_router.dart +++ b/lib/routes/custom_router.dart @@ -15,6 +15,7 @@ import 'package:frontend/Screens/userManagement/user_List.dart'; import 'package:go_router/go_router.dart'; import '../Screens/allTrips/list_all_plans.dart'; +import '../Screens/allTrips/travel_agent_list.dart'; import '../Screens/approvals/approval_list.dart'; import '../Screens/group/group.dart'; import '../Screens/group/groupList.dart'; @@ -33,6 +34,10 @@ final GoRouter router = GoRouter( path: '/listAllPlan', builder: (context, state) => ListAllPlans(), ), + GoRoute( + path: '/listTravelAgentPlan', + builder: (context, state) => TravelAgentListPlans(), + ), GoRoute( path: '/listPlan', builder: (context, state) => ListPlans(), diff --git a/lib/utils/pagination.dart b/lib/utils/pagination.dart new file mode 100644 index 0000000..d5b4abf --- /dev/null +++ b/lib/utils/pagination.dart @@ -0,0 +1,103 @@ +import 'package:flutter/material.dart'; + +class PaginationControls extends StatelessWidget { + final int currentPage; + final int itemsPerPage; + final int totalItems; + final Function(int) onPageChanged; + final Function(int) onItemsPerPageChanged; + final Color? activeColor; + + const PaginationControls({ + Key? key, + required this.currentPage, + required this.itemsPerPage, + required this.totalItems, + required this.onPageChanged, + required this.onItemsPerPageChanged, + this.activeColor = Colors.blue, // fallback if no color given + }) : super(key: key); + + @override + Widget build(BuildContext context) { + int totalPages = (totalItems / itemsPerPage).ceil(); + + return Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Text( + 'Items per page:', + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w400, + color: Colors.black87, + fontFamily: "Inter", + ), + ), + SizedBox(width: 8), + DropdownButton( + value: itemsPerPage, + items: [10, 20, 50, 100] + .map((value) => DropdownMenuItem( + value: value, + child: Text('$value', + style: TextStyle( + fontSize: 12, + fontFamily: "Inter", + )), + )) + .toList(), + onChanged: (value) { + if (value != null) { + onItemsPerPageChanged(value); + } + }, + style: TextStyle( + fontSize: 12, + color: Colors.black, + fontFamily: "Inter", + ), + underline: SizedBox(), + iconSize: 16, + ), + SizedBox(width: 16), + IconButton( + icon: Icon(Icons.arrow_back_ios_new, size: 10), + onPressed: + currentPage > 0 ? () => onPageChanged(currentPage - 1) : null, + ), + Row( + children: List.generate( + totalPages, + (index) => Padding( + padding: const EdgeInsets.symmetric(horizontal: 4), + child: ElevatedButton( + onPressed: () => onPageChanged(index), + style: ElevatedButton.styleFrom( + backgroundColor: + currentPage == index ? activeColor : Colors.grey[300]!, + minimumSize: Size(30, 30), + padding: EdgeInsets.zero, + ), + child: Text( + '${index + 1}', + style: TextStyle( + fontSize: 12, + fontFamily: "Inter", + color: currentPage == index ? Colors.white : Colors.black, + ), + ), + ), + ), + ), + ), + IconButton( + icon: Icon(Icons.arrow_forward_ios, size: 10), + onPressed: (currentPage + 1) * itemsPerPage < totalItems + ? () => onPageChanged(currentPage + 1) + : null, + ), + ], + ); + } +} diff --git a/lib/widgets/custom_popup.dart b/lib/widgets/custom_popup.dart new file mode 100644 index 0000000..428b0f8 --- /dev/null +++ b/lib/widgets/custom_popup.dart @@ -0,0 +1,26 @@ +import 'package:flutter/material.dart'; + +class CustomPopupMenuEntry extends PopupMenuEntry { + final Widget child; + + CustomPopupMenuEntry({required this.child}); + + @override + double get height => 0; // 0 because height depends on child + + @override + bool represents(int? value) => false; + + @override + State createState() => _CustomPopupMenuEntryState(); +} + +class _CustomPopupMenuEntryState extends State { + @override + Widget build(BuildContext context) { + return IntrinsicHeight( + // <-- wrap with IntrinsicHeight + child: widget.child, + ); + } +} diff --git a/lib/widgets/popup_listPlan_action.dart b/lib/widgets/popup_listPlan_action.dart new file mode 100644 index 0000000..7fc5b04 --- /dev/null +++ b/lib/widgets/popup_listPlan_action.dart @@ -0,0 +1,81 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + +import '../data/models/plan.dart'; +import '../services/apiService.dart'; +import 'custom_popup.dart'; + +class PlanPopupMenu extends StatelessWidget { + final Plan plan; + final Function(String planId) deletePlan; + final ApiService apiService; + + const PlanPopupMenu({ + Key? key, + required this.plan, + required this.deletePlan, + required this.apiService, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Align( + alignment: Alignment.centerRight, + child: PopupMenuButton( + color: Colors.white, + padding: EdgeInsets.zero, + offset: Offset(0, 30), + icon: Icon( + Icons.more_vert, + color: Color(0xFF475569), + size: 20, + ), + itemBuilder: (context) => [ + CustomPopupMenuEntry( + child: Container( + padding: EdgeInsets.symmetric(horizontal: 8, vertical: 8), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + IconButton( + icon: Icon(Icons.remove_red_eye, size: 18), + onPressed: () { + Navigator.pop(context); + ApiService.viewPlan(context, plan.planId, + isViewMode: true); + }, + ), + IconButton( + icon: Image.asset('assets/images/IconsImg/edit.png', + width: 20, height: 15), + onPressed: () { + Navigator.pop(context); + ApiService.viewPlan(context, plan.planId, + isViewMode: false); + }, + ), + IconButton( + icon: Image.asset('assets/images/IconsImg/delete.png', + width: 20, height: 15), + onPressed: () { + Navigator.pop(context); + deletePlan(plan.planId); + }, + ), + IconButton( + icon: Icon(Icons.download, + color: Color(0xFF114D8B), size: 18), + onPressed: () { + Navigator.pop(context); + apiService.getPdfDownload(plan.planId); + }, + ), + ], + ), + ), + ), + ], + ), + ); + } +}