Pagination, ListView

This commit is contained in:
venbaittech 2025-05-05 18:25:42 +05:30
parent 1999e63c0f
commit 94d2a730fb
21 changed files with 2509 additions and 883 deletions

View File

@ -13,6 +13,9 @@ import '../../routes/custom_appBar.dart';
import '../../routes/custom_drawer.dart'; import '../../routes/custom_drawer.dart';
import '../../services/apiService.dart'; import '../../services/apiService.dart';
import '../../utils/auth_utils.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 { class ListAllPlans extends StatefulWidget {
const ListAllPlans({super.key}); const ListAllPlans({super.key});
@ -25,7 +28,7 @@ class _ListAllPlansState extends State<ListAllPlans> {
final ApiService apiService = ApiService(); final ApiService apiService = ApiService();
int currentPage = 0; int currentPage = 0;
int itemsPerPage = 8; int itemsPerPage = 10;
String? userId; String? userId;
String? orgId; String? orgId;
@ -158,12 +161,14 @@ class _ListAllPlansState extends State<ListAllPlans> {
Future<List<Plan>> fetchPlans() async { Future<List<Plan>> fetchPlans() async {
late String apiUrldata; late String apiUrldata;
if (roleUser == "Org Admin") { apiUrldata = '$apiUrl/api/plans?org_id=$orgId';
apiUrldata = '$apiUrl/api/plans?org_id=$orgId';
} else { // if (roleUser == "Org Admin") {
apiUrldata = // apiUrldata = '$apiUrl/api/plans?org_id=$orgId';
'$apiUrl/api/plans/findTravelAgentPlanList?org_id=$orgId&user_id=$userId'; // } else {
} // apiUrldata =
// '$apiUrl/api/plans/findTravelAgentPlanList?org_id=$orgId&user_id=$userId';
// }
if (token == null) { if (token == null) {
throw Exception('Token not found. Please log in.'); throw Exception('Token not found. Please log in.');
@ -238,10 +243,12 @@ class _ListAllPlansState extends State<ListAllPlans> {
bool isDesktop = sizingInfo.deviceScreenType == DeviceScreenType.desktop; bool isDesktop = sizingInfo.deviceScreenType == DeviceScreenType.desktop;
return Scaffold( return Scaffold(
backgroundColor: Colors.white, backgroundColor: Color(0xFFf5f5f5),
// backgroundColor: Color(0xFFFCFCFC),
// appBar: isDesktop ? null : const CustomAppBar(title: 'Home'), // appBar: isDesktop ? null : const CustomAppBar(title: 'Home'),
// drawer: isDesktop ? null : CustomDrawer(isDesktop: false), // drawer: isDesktop ? null : CustomDrawer(isDesktop: false),
appBar: const CustomAppBar(), appBar: CustomAppBar(isDesktop: isDesktop),
drawer: CustomDrawer(isDesktop: false), drawer: CustomDrawer(isDesktop: false),
body: Padding( body: Padding(
padding: isDesktop padding: isDesktop
@ -251,7 +258,7 @@ class _ListAllPlansState extends State<ListAllPlans> {
vertical: MediaQuery.of(context).size.height * vertical: MediaQuery.of(context).size.height *
0, // 5% of screen height as vertical padding 0, // 5% of screen height as vertical padding
) )
: EdgeInsets.all(8), : EdgeInsets.all(0),
child: Row( child: Row(
children: [ children: [
// if (isDesktop) CustomDrawer(isDesktop: true), // if (isDesktop) CustomDrawer(isDesktop: true),
@ -266,13 +273,14 @@ class _ListAllPlansState extends State<ListAllPlans> {
Widget buildGroupListLayout(bool isDesktop) { Widget buildGroupListLayout(bool isDesktop) {
return Container( return Container(
// decoration: BoxDecoration( // decoration: BoxDecoration(
// // color: Colors.amber, // color: Colors.amber,
// // color: bodyColor, // // color: bodyColor,
// color: Color(0xFFE1F5FE), // // color: Color(0xFFE1F5FE),
// border: Border.all( // // border: Border.all(
// // color: Color(0xFFF7F7FB), // // // color: Color(0xFFF7F7FB),
// color: Colors.white, // // color: Colors.white,
// width: 3.5)), // // width: 3.5)
// ),
child: buildTableLayout(isDesktop), child: buildTableLayout(isDesktop),
); );
} }
@ -288,10 +296,8 @@ class _ListAllPlansState extends State<ListAllPlans> {
} }
return Container( return Container(
margin: isDesktop margin: isDesktop ? EdgeInsets.all(10.0) : null,
? EdgeInsets.all(10.0) padding: const EdgeInsets.only(top: 15, bottom: 15, left: 20, right: 20),
: EdgeInsets.only(left: 20.0, bottom: 20.0, top: 10.0, right: 20.0),
// padding: const EdgeInsets.all(10),
height: isDesktop height: isDesktop
? MediaQuery.of(context).size.height * 0.98 ? MediaQuery.of(context).size.height * 0.98
: MediaQuery.of(context).size.height, : MediaQuery.of(context).size.height,
@ -325,7 +331,7 @@ class _ListAllPlansState extends State<ListAllPlans> {
children: [ children: [
Row( Row(
children: [ children: [
const Text('All Trip List', const Text('All Trips',
style: TextStyle( style: TextStyle(
fontFamily: "Inter", fontFamily: "Inter",
fontSize: 16, fontSize: 16,
@ -334,43 +340,46 @@ class _ListAllPlansState extends State<ListAllPlans> {
], ],
), ),
SizedBox(
width: 1,
),
Spacer(), Spacer(),
if (isDesktop)
Container( Container(
width: MediaQuery.of(context).size.width * 0.2, width: MediaQuery.of(context).size.width * 0.2,
height: 40, height: 40,
child: TextField( child: TextField(
controller: searchController, controller: searchController,
onChanged: filterPlans, onChanged: filterPlans,
decoration: InputDecoration( decoration: InputDecoration(
hintText: "Search...", hintText: "Search...",
hintStyle: hintStyle:
TextStyle(fontSize: 12, color: Color(0xFF9E9DBD)), TextStyle(fontSize: 12, color: Color(0xFF9E9DBD)),
prefixIcon: Icon( prefixIcon: Icon(
Icons.search, Icons.search,
color: Color(0xFF9E9DBD), color: Color(0xFF9E9DBD),
size: 18, 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( style: TextStyle(
borderRadius: BorderRadius.circular(12), 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), // SizedBox(width: 16),
@ -396,69 +405,110 @@ class _ListAllPlansState extends State<ListAllPlans> {
// ), // ),
// ), // ),
ElevatedButton( // ElevatedButton(
style: ElevatedButton.styleFrom( // style: ElevatedButton.styleFrom(
backgroundColor: Color(0xFF114D8B), // backgroundColor: Color(0xFF114D8B),
foregroundColor: Colors.white, // foregroundColor: Colors.white,
disabledBackgroundColor: Color(0xFF114D8B), // disabledBackgroundColor: Color(0xFF114D8B),
disabledForegroundColor: Colors.white, // disabledForegroundColor: Colors.white,
shape: RoundedRectangleBorder( // shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8), // borderRadius: BorderRadius.circular(8),
side: BorderSide(color: Color(0xFF114D8B), width: 2), // side: BorderSide(color: Color(0xFF114D8B), width: 2),
), // ),
padding: // padding:
EdgeInsets.symmetric(horizontal: 20, vertical: 12), // EdgeInsets.symmetric(horizontal: 20, vertical: 12),
), // ),
onPressed: () { // onPressed: () {
if (TripPlanAction == "Plan Creation Not Allowed") { // if (TripPlanAction == "Plan Creation Not Allowed") {
showDialog( // showDialog(
context: context, // context: context,
builder: (context) => AlertDialog( // builder: (context) => AlertDialog(
title: Text( // title: Text(
"Action Not Allowed", // "Action Not Allowed",
style: TextStyle( // style: TextStyle(
fontSize: 18, fontWeight: FontWeight.bold), // fontSize: 18, fontWeight: FontWeight.bold),
), // ),
content: Text( // content: Text(
"Plan Creation Not Allowed For This User."), // "Plan Creation Not Allowed For This User."),
actions: [ // actions: [
TextButton( // TextButton(
onPressed: () => Navigator.pop(context), // onPressed: () => Navigator.pop(context),
child: Text( // child: Text(
"OK", // "OK",
style: TextStyle(color: layoutColor), // style: TextStyle(color: layoutColor),
), // ),
), // ),
], // ],
), // ),
); // );
} else { // } else {
context.go('/createPlan', extra: { // context.go('/createPlan', extra: {
'orgId': orgId, // 'orgId': orgId,
}); // });
if (!isDesktop) Navigator.pop(context); // if (!isDesktop) Navigator.pop(context);
} // }
}, // },
child: Row( // child: Row(
mainAxisSize: // mainAxisSize:
MainAxisSize.min, // Ensures content fits nicely // MainAxisSize.min, // Ensures content fits nicely
children: [ // children: [
Text( // Text(
"Add New Plan", // "Add New Plan",
style: TextStyle(fontSize: 13), // style: TextStyle(fontSize: 13),
), // ),
SizedBox(width: 8), // spacing between icon and text // SizedBox(width: 8), // spacing between icon and text
Icon( // Icon(
Icons.add_circle_outline_rounded, // Icons.add_circle_outline_rounded,
size: 15, // size: 15,
color: Colors.white, // color: Colors.white,
), // ),
], // ],
), // ),
), // ),
], ],
), ),
const SizedBox(height: 10), 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<List<Plan>>( FutureBuilder<List<Plan>>(
future: futurePlans, future: futurePlans,
builder: (context, snapshot) { builder: (context, snapshot) {
@ -475,25 +525,27 @@ class _ListAllPlansState extends State<ListAllPlans> {
children: const [ children: const [
Icon(Icons.error_outline, Icon(Icons.error_outline,
color: Colors.redAccent, size: 60), color: Colors.redAccent, size: 60),
SizedBox(height: 16), SizedBox(height: 1),
Text("Oops!", Text("Oops!",
style: TextStyle( style: TextStyle(
fontSize: 22, fontSize: 22,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
color: Colors.redAccent)), color: Colors.redAccent)),
SizedBox(height: 8), SizedBox(height: 15),
Text("No Plans Available For This User", Text("No Trips",
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: TextStyle( style: TextStyle(
fontSize: 20, fontSize: 20,
fontWeight: FontWeight.bold, fontFamily: "Inter",
color: Colors.grey)), fontWeight: FontWeight.w600,
SizedBox(height: 20), color: Colors.black54)),
Text("Please Create Plan", SizedBox(height: 10),
Text("Please Create Trip",
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: TextStyle( style: TextStyle(
fontSize: 16, color: Colors.grey)), fontFamily: "Inter",
SizedBox(height: 20), fontSize: 12,
color: Colors.grey)),
], ],
), ),
), ),
@ -535,15 +587,15 @@ class _ListAllPlansState extends State<ListAllPlans> {
fontFamily: "Inter", fontFamily: "Inter",
fontWeight: FontWeight.w600))), fontWeight: FontWeight.w600))),
DataColumn( DataColumn(
label: Text('Employee Code', label: Text('Trip Name',
style: TextStyle( style: TextStyle(
// color: Color(0xFF9E9DBD),
fontSize: 13, fontSize: 13,
fontFamily: "Inter", fontFamily: "Inter",
fontWeight: FontWeight.w600))), fontWeight: FontWeight.w600))),
DataColumn( DataColumn(
label: Text('Trip Name', label: Text('Employee Code',
style: TextStyle( style: TextStyle(
// color: Color(0xFF9E9DBD),
fontSize: 13, fontSize: 13,
fontFamily: "Inter", fontFamily: "Inter",
fontWeight: FontWeight.w600))), fontWeight: FontWeight.w600))),
@ -585,11 +637,6 @@ class _ListAllPlansState extends State<ListAllPlans> {
fontSize: 13, fontSize: 13,
fontFamily: "Inter", fontFamily: "Inter",
))), ))),
DataCell(Text(plan.employeeCode ?? "no data",
style: TextStyle(
fontSize: 13,
fontFamily: "Inter",
))),
DataCell(Text(plan.tripTitle, DataCell(Text(plan.tripTitle,
style: TextStyle( style: TextStyle(
fontSize: 13, fontSize: 13,
@ -597,6 +644,11 @@ class _ListAllPlansState extends State<ListAllPlans> {
), ),
softWrap: true, softWrap: true,
overflow: TextOverflow.ellipsis)), overflow: TextOverflow.ellipsis)),
DataCell(Text(plan.employeeCode ?? "no data",
style: TextStyle(
fontSize: 13,
fontFamily: "Inter",
))),
DataCell(Text( DataCell(Text(
plan.userName.isNotEmpty plan.userName.isNotEmpty
? plan.userName ? plan.userName
@ -624,16 +676,6 @@ class _ListAllPlansState extends State<ListAllPlans> {
alignment: Alignment.center, alignment: Alignment.center,
decoration: BoxDecoration( decoration: BoxDecoration(
color: getStatusColor(plan.statusValue), 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), borderRadius: BorderRadius.circular(10),
), ),
child: Text( child: Text(
@ -642,14 +684,6 @@ class _ListAllPlansState extends State<ListAllPlans> {
style: TextStyle( style: TextStyle(
color: color:
getStatusTextColor(plan.statusValue), 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, fontSize: 12,
fontFamily: "Inter", fontFamily: "Inter",
fontWeight: FontWeight.w400, fontWeight: FontWeight.w400,
@ -657,45 +691,77 @@ class _ListAllPlansState extends State<ListAllPlans> {
), ),
), ),
), ),
DataCell(Row(children: [ DataCell(
IconButton( PopupMenuButton<int>(
icon: const Icon( color: Colors.white,
Icons.remove_red_eye, padding: EdgeInsets.zero,
color: Color(0xFF475569), offset: Offset(0, 30),
size: 18, 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(), }).toList(),
), ),
@ -703,66 +769,148 @@ class _ListAllPlansState extends State<ListAllPlans> {
}, },
); );
Widget buildMobileCardView(List<Plan> 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( return Expanded(
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween, // mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
isDesktop Expanded(
? table child: isDesktop
: SingleChildScrollView( ? SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: SingleChildScrollView(
scrollDirection: Axis.vertical, scrollDirection: Axis.vertical,
child: table, child: table, // <-- your existing table
), )
), : buildMobileCardView(paginatedPlans),
Row( ),
mainAxisAlignment: MainAxisAlignment.end, PaginationControls(
children: [ currentPage: currentPage,
// Previous button with arrow icon itemsPerPage: itemsPerPage,
IconButton( totalItems: plans.length,
icon: Icon( activeColor: layoutColor, // your theme color
Icons.arrow_back_ios_new, onPageChanged: (page) {
size: 10, setState(() {
), currentPage = page;
onPressed: currentPage > 0 });
? () { },
setState(() { onItemsPerPageChanged: (items) {
currentPage--; setState(() {
}); itemsPerPage = items;
} currentPage = 0;
: 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,
),
],
), ),
], ],
), ),

View File

@ -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<TravelAgentListPlans> {
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<List<Plan>> futurePlans;
List<Plan> allPlans = [];
List<Plan> 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<void> 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<String?> getUserId() async {
final prefs = await SharedPreferences.getInstance();
final String? userDataString = prefs.getString('user_data');
if (userDataString != null) {
try {
final Map<String, dynamic> userData = jsonDecode(userDataString);
return userData["user_id"]?.toString();
} catch (e) {
return null;
}
}
return null;
}
Future<String?> getOrgId() async {
final prefs = await SharedPreferences.getInstance();
final String? userDataString = prefs.getString('user_data');
if (userDataString != null) {
try {
final Map<String, dynamic> userData = jsonDecode(userDataString);
return userData["org_id"]?.toString();
} catch (e) {
return null;
}
}
return null;
}
Future<String?> getToken() async {
final prefs = await SharedPreferences.getInstance();
return prefs.getString('auth_token');
}
// Fetch API Data
Future<List<Plan>> 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<dynamic> plansJson = data['data'];
return plansJson.map((json) => Plan.fromJson(json)).toList();
} else {
throw Exception('Failed to load plans');
}
}
Future<void> 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<String, dynamic> 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<List<Plan>>(
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<Plan> plans = snapshot.data!;
List<Plan> plans =
filteredPlans.isNotEmpty ? filteredPlans : allPlans;
plans.sort((a, b) =>
int.parse(b.planId).compareTo(int.parse(a.planId)));
List<Plan> 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<int>(
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<Plan> 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;
}
}

View File

@ -13,6 +13,9 @@ import '../../routes/custom_appBar.dart';
import '../../routes/custom_drawer.dart'; import '../../routes/custom_drawer.dart';
import '../../services/apiService.dart'; import '../../services/apiService.dart';
import '../../utils/auth_utils.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 { class ApprovalList extends StatefulWidget {
const ApprovalList({super.key}); const ApprovalList({super.key});
@ -32,7 +35,7 @@ class _ApprovalListState extends State<ApprovalList> {
Color? bodyColor; Color? bodyColor;
int currentPage = 0; int currentPage = 0;
int itemsPerPage = 8; int itemsPerPage = 10;
List<Plan> allPlans = []; List<Plan> allPlans = [];
List<Plan> filteredPlans = []; List<Plan> filteredPlans = [];
TextEditingController searchController = TextEditingController(); TextEditingController searchController = TextEditingController();
@ -296,10 +299,10 @@ class _ApprovalListState extends State<ApprovalList> {
bool isDesktop = sizingInfo.deviceScreenType == DeviceScreenType.desktop; bool isDesktop = sizingInfo.deviceScreenType == DeviceScreenType.desktop;
return Scaffold( return Scaffold(
backgroundColor: Colors.white, backgroundColor: Color(0xFFf5f5f5),
// appBar: isDesktop ? null : const CustomAppBar(title: 'Home'), // appBar: isDesktop ? null : const CustomAppBar(title: 'Home'),
// drawer: isDesktop ? null : CustomDrawer(isDesktop: false), // drawer: isDesktop ? null : CustomDrawer(isDesktop: false),
appBar: const CustomAppBar(), appBar: CustomAppBar(isDesktop: isDesktop),
drawer: CustomDrawer(isDesktop: false), drawer: CustomDrawer(isDesktop: false),
body: Padding( body: Padding(
padding: isDesktop padding: isDesktop
@ -309,7 +312,7 @@ class _ApprovalListState extends State<ApprovalList> {
vertical: MediaQuery.of(context).size.height * vertical: MediaQuery.of(context).size.height *
0, // 5% of screen height as vertical padding 0, // 5% of screen height as vertical padding
) )
: EdgeInsets.all(8), : EdgeInsets.all(0),
child: Row( child: Row(
children: [ children: [
// if (isDesktop) CustomDrawer(isDesktop: true), // if (isDesktop) CustomDrawer(isDesktop: true),
@ -346,18 +349,28 @@ class _ApprovalListState extends State<ApprovalList> {
} }
return Container( return Container(
margin: isDesktop margin: isDesktop ? EdgeInsets.all(10.0) : null,
? EdgeInsets.all(10.0) padding: const EdgeInsets.only(top: 15, bottom: 15, left: 20, right: 20),
: EdgeInsets.only(left: 20.0, bottom: 20.0, top: 10.0, right: 20.0),
// padding: const EdgeInsets.all(10),
height: isDesktop height: isDesktop
? MediaQuery.of(context).size.height * 0.98 ? MediaQuery.of(context).size.height * 0.98
: MediaQuery.of(context).size.height, : 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( child: Padding(
padding: const EdgeInsets.all(1.0), padding: const EdgeInsets.all(1.0),
child: Container( child: Container(
padding: const EdgeInsets.all(1.0), // padding: const EdgeInsets.all(10.0),
color: Colors.white, color: Colors.white,
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
@ -370,7 +383,7 @@ class _ApprovalListState extends State<ApprovalList> {
children: [ children: [
Row( Row(
children: [ children: [
const Text('List For Approval', const Text('My Approvals',
style: TextStyle( style: TextStyle(
fontFamily: "Inter", fontFamily: "Inter",
fontSize: 16, fontSize: 16,
@ -439,7 +452,7 @@ class _ApprovalListState extends State<ApprovalList> {
controller: searchController, controller: searchController,
onChanged: filterPlans, onChanged: filterPlans,
decoration: InputDecoration( decoration: InputDecoration(
hintText: "Search for a plan", hintText: "Search...",
hintStyle: TextStyle( hintStyle: TextStyle(
fontSize: 12, color: Color(0xFF9E9DBD)), fontSize: 12, color: Color(0xFF9E9DBD)),
prefixIcon: Icon( prefixIcon: Icon(
@ -488,25 +501,20 @@ class _ApprovalListState extends State<ApprovalList> {
children: const [ children: const [
Icon(Icons.error_outline, Icon(Icons.error_outline,
color: Colors.redAccent, size: 60), color: Colors.redAccent, size: 60),
SizedBox(height: 16), SizedBox(height: 1),
Text("Oops!", Text("Oops!",
style: TextStyle( style: TextStyle(
fontSize: 22, fontSize: 22,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
color: Colors.redAccent)), color: Colors.redAccent)),
SizedBox(height: 8), SizedBox(height: 15),
Text("No Plans Available For This User", Text("No Approvals",
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: TextStyle( style: TextStyle(
fontSize: 20, fontSize: 20,
fontWeight: FontWeight.bold, fontFamily: "Inter",
color: Colors.grey)), fontWeight: FontWeight.w600,
SizedBox(height: 20), color: Colors.black54)),
Text("Please Create Plan",
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 16, color: Colors.grey)),
SizedBox(height: 20),
], ],
), ),
), ),
@ -637,30 +645,16 @@ class _ApprovalListState extends State<ApprovalList> {
height: 25, height: 25,
alignment: Alignment.center, alignment: Alignment.center,
decoration: BoxDecoration( decoration: BoxDecoration(
color: plan.statusValue == color: getStatusColor(plan.statusValue),
"Partially Approved" borderRadius: BorderRadius.circular(8),
? 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( child: Text(
plan.statusValue, plan.statusValue,
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: TextStyle( style: TextStyle(
color: (plan.statusValue == color:
"Partially Approved" || getStatusTextColor(plan.statusValue),
plan.statusValue == "Approved" ||
plan.statusValue == "Rejected")
? Colors.black
: plan.statusValue == "Completed"
? Colors.white
: Colors.grey,
fontSize: 12, fontSize: 12,
fontFamily: "Inter", fontFamily: "Inter",
fontWeight: FontWeight.w400, fontWeight: FontWeight.w400,
@ -668,55 +662,127 @@ class _ApprovalListState extends State<ApprovalList> {
), ),
), ),
), ),
DataCell(Row(children: [
IconButton( DataCell(
icon: const Icon( PopupMenuButton<int>(
Icons.remove_red_eye, color: Colors.white,
color: Color(0xFF475569), padding: EdgeInsets.zero,
size: 18, 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( // DataCell(Row(children: [
// icon: const Icon(Icons.edit, // IconButton(
// color: Colors.green), // icon: const Icon(
// onPressed: () => viewPlanforApprover(plan.planId, // Icons.remove_red_eye,
// isViewMode: false) ), // color: Color(0xFF475569),
// size: 18,
SizedBox( // ),
width: 5, // onPressed: () => viewPlanforApprover(
), // plan.planId,
GestureDetector( // isViewMode: true,
onTap: () => deletePlan(plan.planId), // isApprover: true)),
child: Image.asset( //
'assets/images/IconsImg/delete.png', // GestureDetector(
width: 20, // onTap: () => ApiService.viewPlanForApprover(
height: 15), // context, plan.planId,
), // isViewMode: false, isApprover: true),
// child: Image.asset(
IconButton( // 'assets/images/IconsImg/edit.png',
icon: const Icon( // width: 20,
Icons.download, // height: 15),
color: Color(0xFF475569), // ),
size: 18, //
), // // IconButton(
onPressed: () { // // icon: const Icon(Icons.edit,
apiService.getPdfDownload(plan.planId); // // 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(), }).toList(),
), ),
@ -724,66 +790,154 @@ class _ApprovalListState extends State<ApprovalList> {
}, },
); );
Widget buildMobileCardView(List<Plan> 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( return Expanded(
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween, // mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
isDesktop Expanded(
? table child: isDesktop
: SingleChildScrollView( ? SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: SingleChildScrollView(
scrollDirection: Axis.vertical, scrollDirection: Axis.vertical,
child: table, child: table, // <-- your existing table
)
: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: SingleChildScrollView(
scrollDirection: Axis.vertical,
child: table,
),
), ),
), ),
Row( PaginationControls(
mainAxisAlignment: MainAxisAlignment.end, currentPage: currentPage,
children: [ itemsPerPage: itemsPerPage,
// Previous button with arrow icon totalItems: plans.length,
IconButton( activeColor: layoutColor, // your theme color
icon: Icon( onPageChanged: (page) {
Icons.arrow_back_ios_new, setState(() {
size: 10, currentPage = page;
), });
onPressed: currentPage > 0 },
? () { onItemsPerPageChanged: (items) {
setState(() { setState(() {
currentPage--; itemsPerPage = items;
}); currentPage = 0;
} });
: 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,
),
],
), ),
], ],
), ),
@ -797,3 +951,23 @@ class _ApprovalListState extends State<ApprovalList> {
); );
} }
} }
// 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;
}

View File

@ -26,6 +26,7 @@ class _LoginWidgetState extends State<LoginWidget> {
final TextEditingController _newPasswordController = TextEditingController(); final TextEditingController _newPasswordController = TextEditingController();
final TextEditingController _confirmPasswordController = final TextEditingController _confirmPasswordController =
TextEditingController(); TextEditingController();
String? userRole;
bool _obscureText = true; bool _obscureText = true;
bool _isForgotPassword = false; bool _isForgotPassword = false;
bool _showOtpResetFields = false; bool _showOtpResetFields = false;
@ -61,6 +62,12 @@ class _LoginWidgetState extends State<LoginWidget> {
final pref = await SharedPreferences.getInstance(); final pref = await SharedPreferences.getInstance();
await pref.setString('auth_token', token); await pref.setString('auth_token', token);
await pref.setString('user_data', jsonEncode(userData)); await pref.setString('user_data', jsonEncode(userData));
userRole = userData['role'];
print("userData - $userData");
print("userData11 - ${userData['role']}");
print("userData12 - $userRole");
} }
} catch (e) { } catch (e) {
print('Error decoding token: $e'); print('Error decoding token: $e');
@ -99,7 +106,14 @@ class _LoginWidgetState extends State<LoginWidget> {
backgroundColor: Colors.green, // Set background to green 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 { } else {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar( SnackBar(

View File

@ -13,7 +13,7 @@ class HomePage extends StatelessWidget {
bool isDesktop = sizingInfo.deviceScreenType == DeviceScreenType.desktop; bool isDesktop = sizingInfo.deviceScreenType == DeviceScreenType.desktop;
return Scaffold( return Scaffold(
appBar: isDesktop ? null : const CustomAppBar(), appBar: CustomAppBar(isDesktop: isDesktop),
drawer: isDesktop ? null : CustomDrawer(isDesktop: false), drawer: isDesktop ? null : CustomDrawer(isDesktop: false),
body: Row( body: Row(
children: [ children: [

View File

@ -324,7 +324,7 @@ class _groupState extends State<Group> {
return Scaffold( return Scaffold(
backgroundColor: Colors.white, backgroundColor: Colors.white,
appBar: const CustomAppBar(), appBar: CustomAppBar(isDesktop: isDesktop),
drawer: CustomDrawer(isDesktop: false), drawer: CustomDrawer(isDesktop: false),
body: Padding( body: Padding(
padding: isDesktop padding: isDesktop

View File

@ -132,7 +132,7 @@ class _GroupListState extends State<GroupList> {
return Scaffold( return Scaffold(
backgroundColor: Colors.white, backgroundColor: Colors.white,
appBar: const CustomAppBar(), appBar: CustomAppBar(isDesktop: isDesktop),
drawer: CustomDrawer(isDesktop: false), drawer: CustomDrawer(isDesktop: false),
body: Padding( body: Padding(
padding: isDesktop padding: isDesktop

View File

@ -331,7 +331,7 @@ class _OrgSetUpState extends State<OrgSetUp> {
return Scaffold( return Scaffold(
backgroundColor: Colors.white, backgroundColor: Colors.white,
appBar: const CustomAppBar(), appBar: CustomAppBar(isDesktop: isDesktop),
drawer: CustomDrawer(isDesktop: false), drawer: CustomDrawer(isDesktop: false),
body: Padding( body: Padding(
padding: isDesktop padding: isDesktop

View File

@ -61,7 +61,7 @@ class _OrganizationListState extends State<OrganizationList> {
backgroundColor: Colors.white, backgroundColor: Colors.white,
// appBar: isDesktop ? null : const CustomAppBar(title: 'Home'), // appBar: isDesktop ? null : const CustomAppBar(title: 'Home'),
// drawer: isDesktop ? null : CustomDrawer(isDesktop: false), // drawer: isDesktop ? null : CustomDrawer(isDesktop: false),
appBar: const CustomAppBar(), appBar: CustomAppBar(isDesktop: isDesktop),
drawer: CustomDrawer(isDesktop: false), drawer: CustomDrawer(isDesktop: false),
body: Row( body: Row(
children: [ children: [

View File

@ -65,8 +65,10 @@ class _CreatePlansState extends State<CreatePlan> {
bool isDesktop = sizingInfo.deviceScreenType == DeviceScreenType.desktop; bool isDesktop = sizingInfo.deviceScreenType == DeviceScreenType.desktop;
return Scaffold( return Scaffold(
backgroundColor: Colors.white, // backgroundColor: Colors.white,
appBar: const CustomAppBar(), backgroundColor: Color(0xFFf5f5f5),
// backgroundColor: Color(0xFFFCFCFC),
appBar: CustomAppBar(isDesktop: isDesktop),
drawer: CustomDrawer(isDesktop: false), drawer: CustomDrawer(isDesktop: false),
body: Padding( body: Padding(
padding: isDesktop padding: isDesktop
@ -109,16 +111,18 @@ class _CreatePlansState extends State<CreatePlan> {
// print("RECived palndata - ${planData}"); // print("RECived palndata - ${planData}");
return Container( return Container(
// margin: const EdgeInsets.only(left: 10.0, right: 15.0, top: 10.0, bottom: 10.0), margin: const EdgeInsets.only(top: 10.0, bottom: 10.0),
// decoration: BoxDecoration( padding: const EdgeInsets.all(8),
// // color: Colors.amber, decoration: BoxDecoration(
// // color: bodyColor, // color: Colors.amber,
// color: Color(0xFFE1F5FE), // color: bodyColor,
// border: Border.all( color: Colors.white,
// color: Colors.white, // border: Border.all(
// // color: Color(0xFFF7F7FB), // color: Colors.white,
// // // color: Color(0xFFF7F7FB),
// width: 3.5)), //
// width: 3.5)
),
child: Column( child: Column(
children: [ children: [
// Container( // Container(

View File

@ -13,6 +13,9 @@ import '../../routes/custom_appBar.dart';
import '../../routes/custom_drawer.dart'; import '../../routes/custom_drawer.dart';
import '../../services/apiService.dart'; import '../../services/apiService.dart';
import '../../utils/auth_utils.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 { class ListPlans extends StatefulWidget {
const ListPlans({super.key}); const ListPlans({super.key});
@ -25,7 +28,7 @@ class _ListPlansState extends State<ListPlans> {
final ApiService apiService = ApiService(); final ApiService apiService = ApiService();
int currentPage = 0; int currentPage = 0;
int itemsPerPage = 8; int itemsPerPage = 10;
String? userId; String? userId;
String? orgId; String? orgId;
@ -272,10 +275,10 @@ class _ListPlansState extends State<ListPlans> {
bool isDesktop = sizingInfo.deviceScreenType == DeviceScreenType.desktop; bool isDesktop = sizingInfo.deviceScreenType == DeviceScreenType.desktop;
return Scaffold( return Scaffold(
backgroundColor: Colors.white, backgroundColor: Color(0xFFf5f5f5),
// appBar: isDesktop ? null : const CustomAppBar(title: 'Home'), // appBar: isDesktop ? null : const CustomAppBar(title: 'Home'),
// drawer: isDesktop ? null : CustomDrawer(isDesktop: false), // drawer: isDesktop ? null : CustomDrawer(isDesktop: false),
appBar: const CustomAppBar(), appBar: CustomAppBar(isDesktop: isDesktop),
drawer: CustomDrawer(isDesktop: false), drawer: CustomDrawer(isDesktop: false),
body: Padding( body: Padding(
padding: isDesktop padding: isDesktop
@ -285,7 +288,7 @@ class _ListPlansState extends State<ListPlans> {
vertical: MediaQuery.of(context).size.height * vertical: MediaQuery.of(context).size.height *
0, // 5% of screen height as vertical padding 0, // 5% of screen height as vertical padding
) )
: EdgeInsets.all(8), : EdgeInsets.all(0),
child: Row( child: Row(
children: [ children: [
// if (isDesktop) CustomDrawer(isDesktop: true), // if (isDesktop) CustomDrawer(isDesktop: true),
@ -322,10 +325,8 @@ class _ListPlansState extends State<ListPlans> {
} }
return Container( return Container(
margin: isDesktop margin: isDesktop ? EdgeInsets.all(10.0) : null,
? EdgeInsets.all(10.0) padding: const EdgeInsets.only(top: 15, bottom: 15, left: 20, right: 20),
: EdgeInsets.only(left: 20.0, bottom: 20.0, top: 10.0, right: 20.0),
// padding: const EdgeInsets.all(10),
height: isDesktop height: isDesktop
? MediaQuery.of(context).size.height * 0.98 ? MediaQuery.of(context).size.height * 0.98
: MediaQuery.of(context).size.height, : MediaQuery.of(context).size.height,
@ -359,7 +360,7 @@ class _ListPlansState extends State<ListPlans> {
children: [ children: [
Row( Row(
children: [ children: [
const Text('Trip List', const Text('My Trip Request',
style: TextStyle( style: TextStyle(
fontFamily: "Inter", fontFamily: "Inter",
fontSize: 16, fontSize: 16,
@ -369,46 +370,45 @@ class _ListPlansState extends State<ListPlans> {
), ),
Spacer(), Spacer(),
if (isDesktop)
Container( Container(
width: MediaQuery.of(context).size.width * 0.2, width: MediaQuery.of(context).size.width * 0.2,
height: 40, height: 40,
child: TextField( child: TextField(
controller: searchController, controller: searchController,
onChanged: filterPlans, onChanged: filterPlans,
decoration: InputDecoration( decoration: InputDecoration(
hintText: "Search...", hintText: "Search...",
hintStyle: hintStyle:
TextStyle(fontSize: 12, color: Color(0xFF9E9DBD)), TextStyle(fontSize: 12, color: Color(0xFF9E9DBD)),
prefixIcon: Icon( prefixIcon: Icon(
Icons.search, Icons.search,
color: Color(0xFF9E9DBD), color: Color(0xFF9E9DBD),
size: 18, 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( style: TextStyle(
borderRadius: BorderRadius.circular(12), 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), // SizedBox(width: 16),
if (isDesktop) Spacer(),
Spacer(),
// ElevatedButton( // ElevatedButton(
// style: ElevatedButton.styleFrom( // style: ElevatedButton.styleFrom(
// foregroundColor: Colors.white, // foregroundColor: Colors.white,
@ -478,7 +478,7 @@ class _ListPlansState extends State<ListPlans> {
MainAxisSize.min, // Ensures content fits nicely MainAxisSize.min, // Ensures content fits nicely
children: [ children: [
Text( Text(
"Add New Plan", "Add New Trip",
style: TextStyle(fontSize: 13), style: TextStyle(fontSize: 13),
), ),
SizedBox(width: 8), // spacing between icon and text SizedBox(width: 8), // spacing between icon and text
@ -492,7 +492,50 @@ class _ListPlansState extends State<ListPlans> {
), ),
], ],
), ),
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), const SizedBox(height: 10),
FutureBuilder<List<Plan>>( FutureBuilder<List<Plan>>(
future: futurePlans, future: futurePlans,
builder: (context, snapshot) { builder: (context, snapshot) {
@ -509,25 +552,27 @@ class _ListPlansState extends State<ListPlans> {
children: const [ children: const [
Icon(Icons.error_outline, Icon(Icons.error_outline,
color: Colors.redAccent, size: 60), color: Colors.redAccent, size: 60),
SizedBox(height: 16), SizedBox(height: 1),
Text("Oops!", Text("Oops!",
style: TextStyle( style: TextStyle(
fontSize: 22, fontSize: 22,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
color: Colors.redAccent)), color: Colors.redAccent)),
SizedBox(height: 8), SizedBox(height: 15),
Text("No Plans Available For This User", Text("No Trips",
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: TextStyle( style: TextStyle(
fontSize: 20, fontSize: 20,
fontWeight: FontWeight.bold, fontFamily: "Inter",
color: Colors.grey)), fontWeight: FontWeight.w600,
SizedBox(height: 20), color: Colors.black54)),
Text("Please Create Plan", SizedBox(height: 10),
Text("Please Create Trip",
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: TextStyle( style: TextStyle(
fontSize: 16, color: Colors.grey)), fontFamily: "Inter",
SizedBox(height: 20), fontSize: 12,
color: Colors.grey)),
], ],
), ),
), ),
@ -569,15 +614,15 @@ class _ListPlansState extends State<ListPlans> {
fontFamily: "Inter", fontFamily: "Inter",
fontWeight: FontWeight.w600))), fontWeight: FontWeight.w600))),
DataColumn( DataColumn(
label: Text('Employee Code', label: Text('Trip Name',
style: TextStyle( style: TextStyle(
// color: Color(0xFF9E9DBD),
fontSize: 13, fontSize: 13,
fontFamily: "Inter", fontFamily: "Inter",
fontWeight: FontWeight.w600))), fontWeight: FontWeight.w600))),
DataColumn( DataColumn(
label: Text('Trip Name', label: Text('Employee Code',
style: TextStyle( style: TextStyle(
// color: Color(0xFF9E9DBD),
fontSize: 13, fontSize: 13,
fontFamily: "Inter", fontFamily: "Inter",
fontWeight: FontWeight.w600))), fontWeight: FontWeight.w600))),
@ -619,11 +664,7 @@ class _ListPlansState extends State<ListPlans> {
fontSize: 13, fontSize: 13,
fontFamily: "Inter", fontFamily: "Inter",
))), ))),
DataCell(Text(plan.employeeCode ?? "no data",
style: TextStyle(
fontSize: 13,
fontFamily: "Inter",
))),
DataCell(Text(plan.tripTitle, DataCell(Text(plan.tripTitle,
style: TextStyle( style: TextStyle(
fontSize: 13, fontSize: 13,
@ -631,6 +672,12 @@ class _ListPlansState extends State<ListPlans> {
), ),
softWrap: true, softWrap: true,
overflow: TextOverflow.ellipsis)), overflow: TextOverflow.ellipsis)),
DataCell(Text(plan.employeeCode ?? "no data",
style: TextStyle(
fontSize: 13,
fontFamily: "Inter",
))),
DataCell(Text( DataCell(Text(
plan.userName.isNotEmpty plan.userName.isNotEmpty
? plan.userName ? plan.userName
@ -691,53 +738,122 @@ class _ListPlansState extends State<ListPlans> {
), ),
), ),
), ),
DataCell(Row(children: [ DataCell(
IconButton( PopupMenuButton<int>(
icon: const Icon( color: Colors.white,
Icons.remove_red_eye, padding: EdgeInsets.zero,
color: Color(0xFF475569), offset: Offset(0, 30),
size: 18, 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( // DataCell(Row(children: [
// icon: const Icon(Icons.edit, // IconButton(
// color: Colors.green), // icon: const Icon(
// onPressed: () => viewPlan(plan.planId, // Icons.remove_red_eye,
// isViewMode: false) ), // color: Color(0xFF475569),
// size: 18,
SizedBox( // ),
width: 5, // onPressed: () => ApiService.viewPlan(
), // context, plan.planId,
GestureDetector( // isViewMode: true)),
onTap: () => deletePlan(plan.planId), //
child: Image.asset( // GestureDetector(
'assets/images/IconsImg/delete.png', // onTap: () => ApiService.viewPlan(
width: 20, // context, plan.planId,
height: 15), // isViewMode: false),
), // child: Image.asset(
IconButton( // 'assets/images/IconsImg/edit.png',
icon: const Icon( // width: 15,
Icons.download, // height: 15),
color: Color(0xFF475569), // ),
size: 18, //
), // // IconButton(
onPressed: () { // // icon: const Icon(Icons.edit,
apiService.getPdfDownload(plan.planId); // // 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(), }).toList(),
), ),
@ -745,66 +861,148 @@ class _ListPlansState extends State<ListPlans> {
}, },
); );
Widget buildMobileCardView(List<Plan> 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( return Expanded(
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween, // mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
isDesktop Expanded(
? table child: isDesktop
: SingleChildScrollView( ? SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: SingleChildScrollView(
scrollDirection: Axis.vertical, scrollDirection: Axis.vertical,
child: table, child: table, // <-- your existing table
), )
), : buildMobileCardView(paginatedPlans),
Row( ),
mainAxisAlignment: MainAxisAlignment.end, PaginationControls(
children: [ currentPage: currentPage,
// Previous button with arrow icon itemsPerPage: itemsPerPage,
IconButton( totalItems: plans.length,
icon: Icon( activeColor: layoutColor, // your theme color
Icons.arrow_back_ios_new, onPageChanged: (page) {
size: 10, setState(() {
), currentPage = page;
onPressed: currentPage > 0 });
? () { },
setState(() { onItemsPerPageChanged: (items) {
currentPage--; setState(() {
}); itemsPerPage = items;
} currentPage = 0;
: 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,
),
],
), ),
], ],
), ),

View File

@ -384,7 +384,7 @@ class _PolicyState extends State<Policy> {
return Scaffold( return Scaffold(
backgroundColor: Colors.white, backgroundColor: Colors.white,
appBar: const CustomAppBar(), appBar: CustomAppBar(isDesktop: isDesktop),
drawer: CustomDrawer(isDesktop: false), drawer: CustomDrawer(isDesktop: false),
body: Padding( body: Padding(
padding: isDesktop padding: isDesktop

View File

@ -148,7 +148,7 @@ class _PolicyListState extends State<PolicyList> {
return Scaffold( return Scaffold(
backgroundColor: Colors.white54, backgroundColor: Colors.white54,
appBar: const CustomAppBar(), appBar: CustomAppBar(isDesktop: isDesktop),
drawer: CustomDrawer(isDesktop: false), drawer: CustomDrawer(isDesktop: false),
body: Padding( body: Padding(
padding: isDesktop padding: isDesktop

View File

@ -666,7 +666,7 @@ class _CreateUserFormState extends State<CreateUserForm> {
backgroundColor: Colors.white, backgroundColor: Colors.white,
// appBar: isDesktop ? null : const CustomAppBar(title: 'Create User '), // appBar: isDesktop ? null : const CustomAppBar(title: 'Create User '),
// drawer: isDesktop ? null : CustomDrawer(isDesktop: false), // drawer: isDesktop ? null : CustomDrawer(isDesktop: false),
appBar: const CustomAppBar(), appBar: CustomAppBar(isDesktop: isDesktop),
drawer: CustomDrawer(isDesktop: false), drawer: CustomDrawer(isDesktop: false),
body: Padding( body: Padding(
padding: isDesktop padding: isDesktop

View File

@ -306,7 +306,7 @@ class _UserListScreenState extends State<UserListScreen> {
backgroundColor: Colors.white, backgroundColor: Colors.white,
// appBar: isDesktop ? null : const CustomAppBar(title: 'User Management'), // appBar: isDesktop ? null : const CustomAppBar(title: 'User Management'),
// drawer: isDesktop ? null : CustomDrawer(isDesktop: false), // drawer: isDesktop ? null : CustomDrawer(isDesktop: false),
appBar: const CustomAppBar(), appBar: CustomAppBar(isDesktop: isDesktop),
drawer: CustomDrawer(isDesktop: false), drawer: CustomDrawer(isDesktop: false),
body: Padding( body: Padding(
padding: isDesktop padding: isDesktop

View File

@ -5,6 +5,7 @@ import 'package:shared_preferences/shared_preferences.dart'; // don't forget
import '../services/apiService.dart'; import '../services/apiService.dart';
class CustomAppBar extends StatefulWidget implements PreferredSizeWidget { class CustomAppBar extends StatefulWidget implements PreferredSizeWidget {
final bool isDesktop;
final String? logoUrl; final String? logoUrl;
final String? username; final String? username;
final List<Widget>? actions; final List<Widget>? actions;
@ -16,6 +17,7 @@ class CustomAppBar extends StatefulWidget implements PreferredSizeWidget {
this.username, this.username,
this.actions, this.actions,
this.showBackButton = false, this.showBackButton = false,
required this.isDesktop,
}); });
@override @override
@ -140,161 +142,231 @@ class _CustomAppBarState extends State<CustomAppBar> {
return AppBar( return AppBar(
backgroundColor: Colors.white, backgroundColor: Colors.white,
surfaceTintColor: Colors.white, surfaceTintColor: Colors.white,
// elevation: 3, // elevation: 3,
automaticallyImplyLeading: false, automaticallyImplyLeading: !widget.isDesktop,
// leading: showBackButton
// ? IconButton(
// icon: const Icon(Icons.arrow_back, color: Colors.black87),
// onPressed: () => Navigator.pop(context),
// )
// : null,
titleSpacing: 0, 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 title: !widget.isDesktop
? ClipRect( ? Text('')
child: Image.network( : Padding(
selectedOrg!['logo'], padding: EdgeInsets.symmetric(
width: 100, horizontal: MediaQuery.of(context).size.width * 0.05),
height: 80, child: Row(
// fit: BoxFit.contain, children: [
errorBuilder: (context, error, stackTrace) { Padding(
return const CircleAvatar( 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, radius: 20,
backgroundColor: Colors.redAccent, backgroundColor: Colors.white,
child: Icon(Icons.error, size: 10), child: Icon(
); Icons.add_a_photo,
}, size: 10,
), color: Colors.grey,
) ),
: 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",
), ),
)),
), ),
const SizedBox(width: 20), SizedBox(
MouseRegion( width: MediaQuery.of(context).size.width * 0.2,
cursor: SystemMouseCursors.click, ),
onEnter: (_) { Container(
setState(() { width: MediaQuery.of(context).size.width * 0.35,
// _myTravelRequestPadding = // color: Colors.blueGrey,
// EdgeInsets.symmetric(horizontal: 12, vertical: 78); child: Row(
// _myTravelRequestColor = // crossAxisAlignment: CrossAxisAlignment.start,
// Color(0xFF114D8B); // Change color on hover mainAxisAlignment: MainAxisAlignment.center,
}); children: [
}, if (userData?["role"] == "Org Admin" ||
onExit: (_) { userData?["role"] == "Travel Admin")
setState(() { MouseRegion(
// _myTravelRequestPadding = EdgeInsets.symmetric( cursor: SystemMouseCursors.click,
// horizontal: 8, vertical: 4); // Normal padding onEnter: (_) {
// _myTravelRequestColor = setState(() {
// Color(0xFF475569); // Revert color when hover ends // _myTravelRequestPadding =
}); // EdgeInsets.symmetric(horizontal: 12, vertical: 78);
}, // _myTravelRequestColor =
child: InkWell( // Color(0xFF114D8B); // Change color on hover
onTap: () { });
context.go('/listPlan'); },
}, onExit: (_) {
child: Text( setState(() {
"My Travel Request", // _myTravelRequestPadding = EdgeInsets.symmetric(
style: TextStyle( // horizontal: 8, vertical: 4); // Normal padding
fontSize: 12, // _myTravelRequestColor =
fontWeight: FontWeight.w600, // Color(0xFF475569); // Revert color when hover ends
// color: Color(0xFF475569), });
color: _myTravelRequestColor, },
fontFamily: "Inter", child: InkWell(
), onTap: () {
)), context.go('/listAllPlan');
), },
const SizedBox(width: 20), child: Text(
MouseRegion( "All Trips",
cursor: SystemMouseCursors.click, style: TextStyle(
onEnter: (_) { fontSize: 12,
setState(() { fontWeight: FontWeight.w600,
_myApprovalsColor = // color: Color(0xFF475569),
Color(0xFF114D8B); // Change color on hover color: _myTravelRequestColor,
}); fontFamily: "Inter",
}, ),
onExit: (_) { )),
setState(() { ),
_myApprovalsColor = const SizedBox(width: 20),
Color(0xFF475569); // Revert color when hover ends if (userDetails["role"] == "Travel Agent")
}); MouseRegion(
}, cursor: SystemMouseCursors.click,
child: InkWell( onEnter: (_) {
onTap: () { setState(() {
context.go('/ApprovalList'); // _myTravelRequestPadding =
}, // EdgeInsets.symmetric(horizontal: 12, vertical: 78);
child: Text( // _myTravelRequestColor =
"My Approvals", // Color(0xFF114D8B); // Change color on hover
style: TextStyle( });
fontSize: 12, },
fontWeight: FontWeight.w600, onExit: (_) {
color: _myApprovalsColor, setState(() {
// color: Color(0xFF475569), // _myTravelRequestPadding = EdgeInsets.symmetric(
fontFamily: "Inter", // 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: [ actions: [
Padding( Padding(
padding: EdgeInsets.symmetric( padding: EdgeInsets.symmetric(
@ -306,8 +378,6 @@ class _CustomAppBarState extends State<CustomAppBar> {
builder: (context) => PopupMenuButton<String>( builder: (context) => PopupMenuButton<String>(
color: Colors.white, color: Colors.white,
padding: EdgeInsets.zero, 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 offset: const Offset(0, 50), // 👈 shift it 50 pixels down
onSelected: (String value) { onSelected: (String value) {
switch (value) { switch (value) {
@ -341,14 +411,23 @@ class _CustomAppBarState extends State<CustomAppBar> {
// menuItems.map(buildMenuItem).toList(), // menuItems.map(buildMenuItem).toList(),
itemBuilder: (BuildContext context) { itemBuilder: (BuildContext context) {
final isUser = userData?["role"] == "User"; // final isUser = userData?["role"] == "User";
final filteredItems = isUser final role = userData?["role"];
? menuItems List<Map<String, dynamic>> filteredItems;
.where((item) =>
item['value'] == '/CreateUserDetails' || if (role == "User") {
item['value'] == '/logout') filteredItems = menuItems
.toList() .where((item) =>
: menuItems; 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 // Create a new list starting with role display and divider
return [ return [

View File

@ -165,167 +165,28 @@ class _CustomDrawerState extends State<CustomDrawer> {
), ),
], ],
), ),
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.home_outlined, 'Home', '/home'),
_buildDrawerItem(context, Icons.request_page_outlined,
'My Travel Request', '/listPlan'), if (userData?["role"] == "Org Admin" ||
_buildDrawerItem(context, Icons.assessment_outlined, 'My Approvals', userData?["role"] == "Travel Admin")
'/ApprovalList'), _buildDrawerItem(context, Icons.request_page_outlined,
if (userData?["role"] != "User") 'All Trips', '/listAllPlan'),
_buildDrawerItem(context, Icons.account_circle_outlined,
'User List', '/listUser'), if (userDetails["role"] != "Travel Agent")
// _buildExpandableItem( _buildDrawerItem(context, Icons.request_page_outlined,
// context, 'My Approvals1', '/listTravelAgentPlan'),
// Icons.assessment_outlined,
// 'Plans', if (userDetails["role"] != "Travel Agent")
// [ _buildDrawerItem(context, Icons.request_page_outlined,
// _buildSubDrawerItem( 'My Trip Request', '/listPlan'),
// context, 'My Travel Request', '/listPlan'),
// _buildSubDrawerItem(context, 'My Approvals', '/ApprovalList') if (userDetails["role"] != "Travel Agent")
// ], _buildDrawerItem(context, Icons.assessment_outlined,
// '/listPlan'), 'My Approvals', '/ApprovalList'),
//
// _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(),
Container( Container(
margin: const EdgeInsets.all(20), margin: const EdgeInsets.all(20),
child: Row( child: Row(

View File

@ -15,6 +15,7 @@ import 'package:frontend/Screens/userManagement/user_List.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import '../Screens/allTrips/list_all_plans.dart'; import '../Screens/allTrips/list_all_plans.dart';
import '../Screens/allTrips/travel_agent_list.dart';
import '../Screens/approvals/approval_list.dart'; import '../Screens/approvals/approval_list.dart';
import '../Screens/group/group.dart'; import '../Screens/group/group.dart';
import '../Screens/group/groupList.dart'; import '../Screens/group/groupList.dart';
@ -33,6 +34,10 @@ final GoRouter router = GoRouter(
path: '/listAllPlan', path: '/listAllPlan',
builder: (context, state) => ListAllPlans(), builder: (context, state) => ListAllPlans(),
), ),
GoRoute(
path: '/listTravelAgentPlan',
builder: (context, state) => TravelAgentListPlans(),
),
GoRoute( GoRoute(
path: '/listPlan', path: '/listPlan',
builder: (context, state) => ListPlans(), builder: (context, state) => ListPlans(),

103
lib/utils/pagination.dart Normal file
View File

@ -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<int>(
value: itemsPerPage,
items: [10, 20, 50, 100]
.map((value) => DropdownMenuItem<int>(
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,
),
],
);
}
}

View File

@ -0,0 +1,26 @@
import 'package:flutter/material.dart';
class CustomPopupMenuEntry extends PopupMenuEntry<int> {
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<StatefulWidget> createState() => _CustomPopupMenuEntryState();
}
class _CustomPopupMenuEntryState extends State<CustomPopupMenuEntry> {
@override
Widget build(BuildContext context) {
return IntrinsicHeight(
// <-- wrap with IntrinsicHeight
child: widget.child,
);
}
}

View File

@ -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<int>(
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);
},
),
],
),
),
),
],
),
);
}
}