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

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 '../../services/apiService.dart';
import '../../utils/auth_utils.dart';
import '../../utils/pagination.dart';
import '../../widgets/custom_popup.dart';
import '../../widgets/popup_listPlan_action.dart';
class ApprovalList extends StatefulWidget {
const ApprovalList({super.key});
@ -32,7 +35,7 @@ class _ApprovalListState extends State<ApprovalList> {
Color? bodyColor;
int currentPage = 0;
int itemsPerPage = 8;
int itemsPerPage = 10;
List<Plan> allPlans = [];
List<Plan> filteredPlans = [];
TextEditingController searchController = TextEditingController();
@ -296,10 +299,10 @@ class _ApprovalListState extends State<ApprovalList> {
bool isDesktop = sizingInfo.deviceScreenType == DeviceScreenType.desktop;
return Scaffold(
backgroundColor: Colors.white,
backgroundColor: Color(0xFFf5f5f5),
// appBar: isDesktop ? null : const CustomAppBar(title: 'Home'),
// drawer: isDesktop ? null : CustomDrawer(isDesktop: false),
appBar: const CustomAppBar(),
appBar: CustomAppBar(isDesktop: isDesktop),
drawer: CustomDrawer(isDesktop: false),
body: Padding(
padding: isDesktop
@ -309,7 +312,7 @@ class _ApprovalListState extends State<ApprovalList> {
vertical: MediaQuery.of(context).size.height *
0, // 5% of screen height as vertical padding
)
: EdgeInsets.all(8),
: EdgeInsets.all(0),
child: Row(
children: [
// if (isDesktop) CustomDrawer(isDesktop: true),
@ -346,18 +349,28 @@ class _ApprovalListState extends State<ApprovalList> {
}
return Container(
margin: isDesktop
? EdgeInsets.all(10.0)
: EdgeInsets.only(left: 20.0, bottom: 20.0, top: 10.0, right: 20.0),
// padding: const EdgeInsets.all(10),
margin: isDesktop ? EdgeInsets.all(10.0) : null,
padding: const EdgeInsets.only(top: 15, bottom: 15, left: 20, right: 20),
height: isDesktop
? MediaQuery.of(context).size.height * 0.98
: MediaQuery.of(context).size.height,
decoration: BoxDecoration(
border: isDesktop
? Border.all(
width: 2,
color: Colors.white,
// color: Color(0xFFF7F7FB),
)
: null,
color: Colors.white,
// color: Color(0xFFF7F7FB),
// color: Colors.amber,
),
child: Padding(
padding: const EdgeInsets.all(1.0),
child: Container(
padding: const EdgeInsets.all(1.0),
// padding: const EdgeInsets.all(10.0),
color: Colors.white,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
@ -370,7 +383,7 @@ class _ApprovalListState extends State<ApprovalList> {
children: [
Row(
children: [
const Text('List For Approval',
const Text('My Approvals',
style: TextStyle(
fontFamily: "Inter",
fontSize: 16,
@ -439,7 +452,7 @@ class _ApprovalListState extends State<ApprovalList> {
controller: searchController,
onChanged: filterPlans,
decoration: InputDecoration(
hintText: "Search for a plan",
hintText: "Search...",
hintStyle: TextStyle(
fontSize: 12, color: Color(0xFF9E9DBD)),
prefixIcon: Icon(
@ -488,25 +501,20 @@ class _ApprovalListState extends State<ApprovalList> {
children: const [
Icon(Icons.error_outline,
color: Colors.redAccent, size: 60),
SizedBox(height: 16),
SizedBox(height: 1),
Text("Oops!",
style: TextStyle(
fontSize: 22,
fontWeight: FontWeight.bold,
color: Colors.redAccent)),
SizedBox(height: 8),
Text("No Plans Available For This User",
SizedBox(height: 15),
Text("No Approvals",
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: Colors.grey)),
SizedBox(height: 20),
Text("Please Create Plan",
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 16, color: Colors.grey)),
SizedBox(height: 20),
fontFamily: "Inter",
fontWeight: FontWeight.w600,
color: Colors.black54)),
],
),
),
@ -637,30 +645,16 @@ class _ApprovalListState extends State<ApprovalList> {
height: 25,
alignment: Alignment.center,
decoration: BoxDecoration(
color: plan.statusValue ==
"Partially Approved"
? Colors.yellow.shade100
: plan.statusValue == "Approved"
? Colors.green.shade100
: plan.statusValue == "Completed"
? Colors.green.shade500
: plan.statusValue == "Rejected"
? Colors.red.shade100
: Colors.grey.shade100,
borderRadius: BorderRadius.circular(10),
color: getStatusColor(plan.statusValue),
borderRadius: BorderRadius.circular(8),
),
child: Text(
plan.statusValue,
textAlign: TextAlign.center,
style: TextStyle(
color: (plan.statusValue ==
"Partially Approved" ||
plan.statusValue == "Approved" ||
plan.statusValue == "Rejected")
? Colors.black
: plan.statusValue == "Completed"
? Colors.white
: Colors.grey,
color:
getStatusTextColor(plan.statusValue),
fontSize: 12,
fontFamily: "Inter",
fontWeight: FontWeight.w400,
@ -668,55 +662,127 @@ class _ApprovalListState extends State<ApprovalList> {
),
),
),
DataCell(Row(children: [
IconButton(
icon: const Icon(
Icons.remove_red_eye,
color: Color(0xFF475569),
size: 18,
DataCell(
PopupMenuButton<int>(
color: Colors.white,
padding: EdgeInsets.zero,
offset: Offset(0, 30),
icon: Icon(
Icons.more_vert,
color: Color(0xFF475569),
size: 14,
),
itemBuilder: (context) => [
CustomPopupMenuEntry(
child: Container(
padding: EdgeInsets.symmetric(
horizontal: 8, vertical: 8),
child: Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment:
MainAxisAlignment.center,
children: [
IconButton(
icon: Icon(Icons.remove_red_eye,
color: Color(0xFF475569),
size: 18),
onPressed: () {
Navigator.pop(
context); // Close popup manually
ApiService.viewPlan(
context, plan.planId,
isViewMode: true);
},
),
IconButton(
icon: Image.asset(
'assets/images/IconsImg/edit.png',
width: 20,
height: 15),
onPressed: () {
Navigator.pop(context);
ApiService.viewPlan(
context, plan.planId,
isViewMode: false);
},
),
IconButton(
icon: Image.asset(
'assets/images/IconsImg/delete.png',
width: 20,
height: 15),
onPressed: () {
Navigator.pop(context);
deletePlan(plan.planId);
},
),
IconButton(
icon: Icon(Icons.download,
color: Color(0xFF114D8B),
size: 18),
onPressed: () {
Navigator.pop(context);
apiService.getPdfDownload(
plan.planId);
},
),
],
),
),
),
onPressed: () => viewPlanforApprover(
plan.planId,
isViewMode: true,
isApprover: true)),
GestureDetector(
onTap: () => ApiService.viewPlanForApprover(
context, plan.planId,
isViewMode: false, isApprover: true),
child: Image.asset(
'assets/images/IconsImg/edit.png',
width: 20,
height: 15),
],
),
// IconButton(
// icon: const Icon(Icons.edit,
// color: Colors.green),
// onPressed: () => viewPlanforApprover(plan.planId,
// isViewMode: false) ),
SizedBox(
width: 5,
),
GestureDetector(
onTap: () => deletePlan(plan.planId),
child: Image.asset(
'assets/images/IconsImg/delete.png',
width: 20,
height: 15),
),
IconButton(
icon: const Icon(
Icons.download,
color: Color(0xFF475569),
size: 18,
),
onPressed: () {
apiService.getPdfDownload(plan.planId);
}),
])),
),
// DataCell(Row(children: [
// IconButton(
// icon: const Icon(
// Icons.remove_red_eye,
// color: Color(0xFF475569),
// size: 18,
// ),
// onPressed: () => viewPlanforApprover(
// plan.planId,
// isViewMode: true,
// isApprover: true)),
//
// GestureDetector(
// onTap: () => ApiService.viewPlanForApprover(
// context, plan.planId,
// isViewMode: false, isApprover: true),
// child: Image.asset(
// 'assets/images/IconsImg/edit.png',
// width: 20,
// height: 15),
// ),
//
// // IconButton(
// // icon: const Icon(Icons.edit,
// // color: Colors.green),
// // onPressed: () => viewPlanforApprover(plan.planId,
// // isViewMode: false) ),
//
// SizedBox(
// width: 5,
// ),
// GestureDetector(
// onTap: () => deletePlan(plan.planId),
// child: Image.asset(
// 'assets/images/IconsImg/delete.png',
// width: 20,
// height: 15),
// ),
//
// IconButton(
// icon: const Icon(
// Icons.download,
// color: Color(0xFF475569),
// size: 18,
// ),
// onPressed: () {
// apiService.getPdfDownload(plan.planId);
// }),
// ])),
]);
}).toList(),
),
@ -724,66 +790,154 @@ class _ApprovalListState extends State<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(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
isDesktop
? table
: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: SingleChildScrollView(
Expanded(
child: isDesktop
? SingleChildScrollView(
scrollDirection: Axis.vertical,
child: table,
child: table, // <-- your existing table
)
: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: SingleChildScrollView(
scrollDirection: Axis.vertical,
child: table,
),
),
),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
// Previous button with arrow icon
IconButton(
icon: Icon(
Icons.arrow_back_ios_new,
size: 10,
),
onPressed: currentPage > 0
? () {
setState(() {
currentPage--;
});
}
: null,
),
SizedBox(width: 2),
// Page number text
Text(
'Page ${currentPage + 1}',
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w400,
color: Colors.black87,
fontFamily: "Inter",
),
),
SizedBox(width: 2),
// Next button with arrow icon
IconButton(
icon: Icon(
Icons.arrow_forward_ios,
size: 10,
),
onPressed: (currentPage + 1) * itemsPerPage <
plans.length
? () {
setState(() {
currentPage++;
});
}
: null,
),
],
),
PaginationControls(
currentPage: currentPage,
itemsPerPage: itemsPerPage,
totalItems: plans.length,
activeColor: layoutColor, // your theme color
onPageChanged: (page) {
setState(() {
currentPage = page;
});
},
onItemsPerPageChanged: (items) {
setState(() {
itemsPerPage = items;
currentPage = 0;
});
},
),
],
),
@ -797,3 +951,23 @@ class _ApprovalListState extends State<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 _confirmPasswordController =
TextEditingController();
String? userRole;
bool _obscureText = true;
bool _isForgotPassword = false;
bool _showOtpResetFields = false;
@ -61,6 +62,12 @@ class _LoginWidgetState extends State<LoginWidget> {
final pref = await SharedPreferences.getInstance();
await pref.setString('auth_token', token);
await pref.setString('user_data', jsonEncode(userData));
userRole = userData['role'];
print("userData - $userData");
print("userData11 - ${userData['role']}");
print("userData12 - $userRole");
}
} catch (e) {
print('Error decoding token: $e');
@ -99,7 +106,14 @@ class _LoginWidgetState extends State<LoginWidget> {
backgroundColor: Colors.green, // Set background to green
),
);
context.go('/listPlan'); // Navigate to home
if (userRole == "Travel Agent") {
context.go('/listTravelAgentPlan');
} else if (userRole == "Org Admin" || userRole == "Travel Admin") {
context.go('/listAllPlan');
} else {
context.go('/listPlan');
}
} else {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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.request_page_outlined,
'My Travel Request', '/listPlan'),
_buildDrawerItem(context, Icons.assessment_outlined, 'My Approvals',
'/ApprovalList'),
if (userData?["role"] != "User")
_buildDrawerItem(context, Icons.account_circle_outlined,
'User List', '/listUser'),
// _buildExpandableItem(
// context,
// Icons.assessment_outlined,
// 'Plans',
// [
// _buildSubDrawerItem(
// context, 'My Travel Request', '/listPlan'),
// _buildSubDrawerItem(context, 'My Approvals', '/ApprovalList')
// ],
// '/listPlan'),
//
// _buildExpandableItem(
// context,
// Icons.account_circle_outlined,
// 'User ',
// [
// _buildSubDrawerItem(context, 'User List', '/listUser'),
// // _buildSubDrawerItem(context,'PlanB','/PlanB')
// ],
// '/listUser'),
if (userData?["role"] != "User")
_buildExpandableItem(
context,
Icons.settings_outlined,
'Settings ',
[
_buildSubDrawerItem(
context, 'Organization', '/OrganizationSetup'),
_buildSubDrawerItem(context, 'Group', '/group'),
_buildSubDrawerItem(context, 'Policy', '/PolicyList'),
// _buildSubDrawerItem(context,'PlanB','/PlanB')
],
'/OrganizationSetup',
),
_buildDrawerItem(context, Icons.login_outlined, 'Logout', '/'),
if (widget.isDesktop) Spacer(),
if (userData?["role"] == "Org Admin" ||
userData?["role"] == "Travel Admin")
_buildDrawerItem(context, Icons.request_page_outlined,
'All Trips', '/listAllPlan'),
if (userDetails["role"] != "Travel Agent")
_buildDrawerItem(context, Icons.request_page_outlined,
'My Approvals1', '/listTravelAgentPlan'),
if (userDetails["role"] != "Travel Agent")
_buildDrawerItem(context, Icons.request_page_outlined,
'My Trip Request', '/listPlan'),
if (userDetails["role"] != "Travel Agent")
_buildDrawerItem(context, Icons.assessment_outlined,
'My Approvals', '/ApprovalList'),
Container(
margin: const EdgeInsets.all(20),
child: Row(

View File

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

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