search for all pages

This commit is contained in:
venbaittech 2025-05-03 17:59:17 +05:30
parent 36975e716f
commit a76464bbbc
26 changed files with 2625 additions and 1099 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 900 B

View File

@ -0,0 +1,798 @@
import 'dart:convert';
import 'dart:core';
import 'package:frontend/data/models/plan.dart';
import 'package:go_router/go_router.dart';
import 'package:http/http.dart' as http;
import 'package:flutter/material.dart';
import 'package:frontend/config/apiUrl.dart';
import 'package:intl/intl.dart';
import 'package:responsive_builder/responsive_builder.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../../routes/custom_appBar.dart';
import '../../routes/custom_drawer.dart';
import '../../services/apiService.dart';
import '../../utils/auth_utils.dart';
class ListAllPlans extends StatefulWidget {
const ListAllPlans({super.key});
@override
_ListAllPlansState createState() => _ListAllPlansState();
}
class _ListAllPlansState extends State<ListAllPlans> {
final ApiService apiService = ApiService();
int currentPage = 0;
int itemsPerPage = 8;
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;
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.');
}
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: Colors.white,
// appBar: isDesktop ? null : const CustomAppBar(title: 'Home'),
// drawer: isDesktop ? null : CustomDrawer(isDesktop: false),
appBar: const CustomAppBar(),
drawer: CustomDrawer(isDesktop: false),
body: Padding(
padding: isDesktop
? EdgeInsets.symmetric(
horizontal: MediaQuery.of(context).size.width *
0.1, // 30% of screen width as horizontal padding
vertical: MediaQuery.of(context).size.height *
0, // 5% of screen height as vertical padding
)
: EdgeInsets.all(8),
child: Row(
children: [
// if (isDesktop) CustomDrawer(isDesktop: true),
Expanded(child: buildGroupListLayout(isDesktop))
],
),
),
);
});
}
Widget buildGroupListLayout(bool isDesktop) {
return Container(
// decoration: BoxDecoration(
// // color: Colors.amber,
// // color: bodyColor,
// color: Color(0xFFE1F5FE),
// border: Border.all(
// // color: Color(0xFFF7F7FB),
// color: Colors.white,
// width: 3.5)),
child: buildTableLayout(isDesktop),
);
}
Widget buildTableLayout(isDesktop) {
String _formatDate(String rawDate) {
try {
final dateTime = DateTime.parse(rawDate);
return DateFormat('dd MMM yy hh:m a').format(dateTime);
} catch (e) {
return rawDate; // fallback if parsing fails
}
}
return Container(
margin: isDesktop
? EdgeInsets.all(10.0)
: EdgeInsets.only(left: 20.0, bottom: 20.0, top: 10.0, right: 20.0),
// padding: const EdgeInsets.all(10),
height: isDesktop
? MediaQuery.of(context).size.height * 0.98
: MediaQuery.of(context).size.height,
decoration: BoxDecoration(
border: isDesktop
? Border.all(
width: 2,
color: Colors.white,
// color: Color(0xFFF7F7FB),
)
: null,
color: Colors.white,
// color: Color(0xFFF7F7FB),
// color: Colors.amber,
),
child: Padding(
padding: const EdgeInsets.all(1.0),
child: Container(
// padding: const EdgeInsets.all(10.0),
color: Colors.white,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// Divider(
// thickness: 0.2, // how "thick" the line is
// color: Colors.grey, // optional
// ),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
const Text('All Trip List',
style: TextStyle(
fontFamily: "Inter",
fontSize: 16,
fontWeight: FontWeight.w600,
color: Color(0xFF212121))),
],
),
Spacer(),
Container(
width: MediaQuery.of(context).size.width * 0.2,
height: 40,
child: TextField(
controller: searchController,
onChanged: filterPlans,
decoration: InputDecoration(
hintText: "Search...",
hintStyle:
TextStyle(fontSize: 12, color: Color(0xFF9E9DBD)),
prefixIcon: Icon(
Icons.search,
color: Color(0xFF9E9DBD),
size: 18,
),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(
color: Colors.grey.shade200, width: 0.5),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide:
BorderSide(color: Colors.grey.shade300, width: 1),
),
),
style: TextStyle(
fontSize: 12,
fontFamily: "Inter",
),
),
),
// SizedBox(width: 16),
Spacer(),
// ElevatedButton(
// style: ElevatedButton.styleFrom(
// foregroundColor: Colors.white,
// backgroundColor: Colors.blueAccent,
// ),
// onPressed: () {
// context.go('/createPlan', extra: {
// 'orgId': orgId,
// });
// if (!isDesktop) Navigator.pop(context);
// },
// child: Row(
// children: [
// Icon(Icons.add_circle, color: Colors.white),
// SizedBox(width: 5),
// Text('NewPlan'),
//
// ],
// ),
// ),
ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Color(0xFF114D8B),
foregroundColor: Colors.white,
disabledBackgroundColor: Color(0xFF114D8B),
disabledForegroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
side: BorderSide(color: Color(0xFF114D8B), width: 2),
),
padding:
EdgeInsets.symmetric(horizontal: 20, vertical: 12),
),
onPressed: () {
if (TripPlanAction == "Plan Creation Not Allowed") {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text(
"Action Not Allowed",
style: TextStyle(
fontSize: 18, fontWeight: FontWeight.bold),
),
content: Text(
"Plan Creation Not Allowed For This User."),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: Text(
"OK",
style: TextStyle(color: layoutColor),
),
),
],
),
);
} else {
context.go('/createPlan', extra: {
'orgId': orgId,
});
if (!isDesktop) Navigator.pop(context);
}
},
child: Row(
mainAxisSize:
MainAxisSize.min, // Ensures content fits nicely
children: [
Text(
"Add New Plan",
style: TextStyle(fontSize: 13),
),
SizedBox(width: 8), // spacing between icon and text
Icon(
Icons.add_circle_outline_rounded,
size: 15,
color: Colors.white,
),
],
),
),
],
),
const SizedBox(height: 10),
FutureBuilder<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 [
Icon(Icons.error_outline,
color: Colors.redAccent, size: 60),
SizedBox(height: 16),
Text("Oops!",
style: TextStyle(
fontSize: 22,
fontWeight: FontWeight.bold,
color: Colors.redAccent)),
SizedBox(height: 8),
Text("No Plans Available For This User",
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: Colors.grey)),
SizedBox(height: 20),
Text("Please Create Plan",
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 16, color: Colors.grey)),
SizedBox(height: 20),
],
),
),
);
}
// List<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('Employee Code',
style: TextStyle(
// color: Color(0xFF9E9DBD),
fontSize: 13,
fontFamily: "Inter",
fontWeight: FontWeight.w600))),
DataColumn(
label: Text('Trip Name',
style: TextStyle(
fontSize: 13,
fontFamily: "Inter",
fontWeight: FontWeight.w600))),
DataColumn(
label: Text('Traveller',
style: TextStyle(
fontSize: 13,
fontFamily: "Inter",
fontWeight: FontWeight.w600))),
DataColumn(
label: Text('Trip Type',
style: TextStyle(
fontSize: 13,
fontFamily: "Inter",
fontWeight: FontWeight.w600))),
DataColumn(
label: Text('Created On',
style: TextStyle(
fontSize: 13,
fontFamily: "Inter",
fontWeight: FontWeight.w600))),
DataColumn(
label: Text('Status',
style: TextStyle(
fontSize: 13,
fontFamily: "Inter",
fontWeight: FontWeight.w600))),
DataColumn(
label: Text('Actions',
style: TextStyle(
fontSize: 13,
fontFamily: "Inter",
fontWeight: FontWeight.w600))),
],
rows: paginatedPlans.map((plan) {
return DataRow(cells: [
DataCell(Text(plan.planId,
style: TextStyle(
fontSize: 13,
fontFamily: "Inter",
))),
DataCell(Text(plan.employeeCode ?? "no data",
style: TextStyle(
fontSize: 13,
fontFamily: "Inter",
))),
DataCell(Text(plan.tripTitle,
style: TextStyle(
fontSize: 13,
fontFamily: "Inter",
),
softWrap: true,
overflow: TextOverflow.ellipsis)),
DataCell(Text(
plan.userName.isNotEmpty
? plan.userName
: plan.travellerName,
style: TextStyle(
fontSize: 13,
fontFamily: "Inter",
))),
DataCell(Text(plan.tripType,
style: TextStyle(
fontSize: 13,
fontFamily: "Inter",
))),
DataCell(Text(_formatDate(plan.createdOn),
// plan.createdOn,
style: TextStyle(
fontSize: 13,
fontFamily: "Inter",
))),
DataCell(
Container(
width: double
.infinity, // Set your desired fixed size (equal width and height)
height: 25,
alignment: Alignment.center,
decoration: BoxDecoration(
color: getStatusColor(plan.statusValue),
// color: plan.statusValue ==
// "Partially Approved"
// ? Colors.yellow.shade100
// : plan.statusValue == "Approved"
// ? Colors.green.shade100
// : plan.statusValue == "Completed"
// ? Colors.green.shade500
// : plan.statusValue == "Rejected"
// ? Colors.red.shade100
// : Colors.grey.shade100,
borderRadius: BorderRadius.circular(10),
),
child: Text(
plan.statusValue,
textAlign: TextAlign.center,
style: TextStyle(
color:
getStatusTextColor(plan.statusValue),
// color: (plan.statusValue ==
// "Partially Approved" ||
// plan.statusValue == "Approved" ||
// plan.statusValue == "Rejected")
// ? Colors.black
// : plan.statusValue == "Completed"
// ? Colors.white
// : Colors.grey,
fontSize: 12,
fontFamily: "Inter",
fontWeight: FontWeight.w400,
),
),
),
),
DataCell(Row(children: [
IconButton(
icon: const Icon(
Icons.remove_red_eye,
color: Color(0xFF475569),
size: 18,
),
onPressed: () => ApiService.viewPlan(
context, plan.planId,
isViewMode: true)),
GestureDetector(
onTap: () => ApiService.viewPlan(
context, plan.planId,
isViewMode: false),
child: Image.asset(
'assets/images/IconsImg/edit.png',
width: 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(),
),
);
},
);
return Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
isDesktop
? table
: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: SingleChildScrollView(
scrollDirection: Axis.vertical,
child: table,
),
),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
// Previous button with arrow icon
IconButton(
icon: Icon(
Icons.arrow_back_ios_new,
size: 10,
),
onPressed: currentPage > 0
? () {
setState(() {
currentPage--;
});
}
: null,
),
SizedBox(width: 2),
// Page number text
Text(
'Page ${currentPage + 1}',
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w400,
color: Colors.black87,
fontFamily: "Inter",
),
),
SizedBox(width: 2),
// Next button with arrow icon
IconButton(
icon: Icon(
Icons.arrow_forward_ios,
size: 10,
),
onPressed: (currentPage + 1) * itemsPerPage <
plans.length
? () {
setState(() {
currentPage++;
});
}
: null,
),
],
),
],
),
);
},
)
],
),
),
),
);
}
// Helper functions for colors
Color getStatusColor(String status) {
if (status == "Partially Approved") return Colors.yellow.shade100;
if (status == "Approved") return Colors.green.shade100;
if (status == "Completed") return Colors.green.shade500;
if (status == "Rejected") return Colors.red.shade100;
return Colors.grey.shade100;
}
Color getStatusTextColor(String status) {
if (status == "Partially Approved" ||
status == "Approved" ||
status == "Rejected") {
return Colors.black;
} else if (status == "Completed") {
return Colors.white;
}
return Colors.grey;
}
}

View File

@ -31,6 +31,12 @@ class _ApprovalListState extends State<ApprovalList> {
Color? layoutColor;
Color? bodyColor;
int currentPage = 0;
int itemsPerPage = 8;
List<Plan> allPlans = [];
List<Plan> filteredPlans = [];
TextEditingController searchController = TextEditingController();
late List<dynamic> plansJson;
@override
@ -85,6 +91,15 @@ class _ApprovalListState extends State<ApprovalList> {
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
});
});
}
}
@ -193,49 +208,6 @@ class _ApprovalListState extends State<ApprovalList> {
}
}
// Future<Map<String, dynamic>> getViewPlan(String planId) async {
// final String apiUrldata = '$apiUrl/api/plans/find/$planId';
// print("API URL: $apiUrldata");
// // final token = await getToken();
//
// if (token == null) {
// throw Exception('Token not found. Please log in.');
// }
//
// final response = await http.put(
// Uri.parse(apiUrldata),
// headers: {
// 'Authorization': 'Bearer $token', // Add token here
// 'Content-Type': 'application/json',
// },
// );
//
// if (response.statusCode == 200) {
// final Map<String, dynamic>? resData = json.decode(response.body);
//
// return resData?["data"];
// } else {
// throw Exception('Failed to load plans');
// }
// }
// Future<Map<String, dynamic>> getViewPlan(String planId, List plansJson) async {
// try {
// final plan = plansJson.firstWhere(
// (item) => item["plan_id"].toString() == planId,
// orElse: () => null,
// );
//
// if (plan == null) {
// throw Exception("Plan with ID $planId not found.");
// }
//
// return Map<String, dynamic>.from(plan);
// } catch (e) {
// throw Exception("Error finding plan: $e");
// }
// }
void deletePlan(String planId) async {
try {
Map<String, dynamic> planData = await getViewPlan(planId);
@ -301,6 +273,24 @@ class _ApprovalListState extends State<ApprovalList> {
// }
// }
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");
}
Widget build(BuildContext context) {
return ResponsiveBuilder(builder: (context, sizingInfo) {
bool isDesktop = sizingInfo.deviceScreenType == DeviceScreenType.desktop;
@ -356,30 +346,18 @@ 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),
margin: isDesktop
? EdgeInsets.all(10.0)
: EdgeInsets.only(left: 20.0, bottom: 20.0, top: 10.0, right: 20.0),
// padding: const EdgeInsets.all(10),
height: isDesktop
? MediaQuery.of(context).size.height * 0.98
: MediaQuery.of(context).size.height,
// decoration: BoxDecoration(
// border: isDesktop
// ? Border.all(
// width: 2,
// color: Colors.white,
// // color: Color(0xFFF7F7FB),
// )
// : null,
// color: Colors.white,
// // color: Color(0xFFF7F7FB),
//
// // color: Colors.amber,
// ),
child: Padding(
padding: const EdgeInsets.all(1.0),
child: Container(
padding: const EdgeInsets.all(10.0),
padding: const EdgeInsets.all(1.0),
color: Colors.white,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
@ -389,47 +367,110 @@ class _ApprovalListState extends State<ApprovalList> {
// color: Colors.grey, // optional
// ),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
const Text('List For Approval',
style: TextStyle(
fontFamily: "Archivo",
fontSize: 16,
fontWeight: FontWeight.w600,
color: Color(0xFF212121))),
fontFamily: "Inter",
fontSize: 16,
fontWeight: FontWeight.w600,
)),
],
),
Container(
width: MediaQuery.of(context).size.width * 0.2,
// or use Flexible
child: TextField(
onChanged: (query) {},
decoration: InputDecoration(
hintText: "Search for a plan",
hintStyle:
TextStyle(fontSize: 14, color: Color(0xFF9E9DBD)),
prefixIcon:
Icon(Icons.search, color: Color(0xFF9E9DBD)),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
if (isDesktop)
SizedBox(
width: MediaQuery.of(context).size.width * 0.19,
),
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),
),
),
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Colors.grey.shade300, width: 0.5),
),
focusedBorder: OutlineInputBorder(
// borderRadius: BorderRadius.circular(8),
borderSide:
BorderSide(color: Colors.blueAccent, width: 1),
style: TextStyle(
fontSize: 12,
fontFamily: "Inter",
),
),
),
),
// SizedBox(width: 16),
],
),
if (!isDesktop)
SizedBox(
height: 5,
),
isDesktop
? SizedBox.shrink()
: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Container(
width: MediaQuery.of(context).size.width * 0.8,
height: 40,
child: TextField(
controller: searchController,
onChanged: filterPlans,
decoration: InputDecoration(
hintText: "Search for a plan",
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),
],
),
const SizedBox(height: 10),
FutureBuilder<List<Plan>>(
future: futurePlans,
@ -472,10 +513,16 @@ class _ApprovalListState extends State<ApprovalList> {
);
}
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;
@ -493,50 +540,57 @@ class _ApprovalListState extends State<ApprovalList> {
),
columns: const [
DataColumn(
label: Text('Plan Id',
label: Text('Trip Id',
style: TextStyle(
color: Color(0xFF9E9DBD),
fontSize: 14,
fontFamily: "Archivo",
fontWeight: FontWeight.bold))),
fontSize: 13,
fontFamily: "Inter",
fontWeight: FontWeight.w600))),
DataColumn(
label: Text('Trip Planned User',
label: Text('Employee Code',
style: TextStyle(
color: Color(0xFF9E9DBD),
fontFamily: "Archivo",
fontWeight: FontWeight.bold))),
// color: Color(0xFF9E9DBD),
fontSize: 13,
fontFamily: "Inter",
fontWeight: FontWeight.w600))),
DataColumn(
label: Text('Trip Title',
label: Text('Trip Name',
style: TextStyle(
color: Color(0xFF9E9DBD),
fontFamily: "Archivo",
fontWeight: FontWeight.bold))),
fontSize: 13,
fontFamily: "Inter",
fontWeight: FontWeight.w600))),
DataColumn(
label: Text('Traveller',
style: TextStyle(
// color: Color(0xFF9E9DBD),
fontSize: 13,
fontFamily: "Inter",
fontWeight: FontWeight.w600))),
DataColumn(
label: Text('Trip Type',
style: TextStyle(
color: Color(0xFF9E9DBD),
fontFamily: "Archivo",
fontWeight: FontWeight.bold))),
fontSize: 13,
fontFamily: "Inter",
fontWeight: FontWeight.w600))),
DataColumn(
label: Text('Created On',
style: TextStyle(
color: Color(0xFF9E9DBD),
fontFamily: "Archivo",
fontWeight: FontWeight.bold))),
fontSize: 13,
fontFamily: "Inter",
fontWeight: FontWeight.w600))),
DataColumn(
label: Text('Status',
style: TextStyle(
color: Color(0xFF9E9DBD),
fontFamily: "Archivo",
fontWeight: FontWeight.bold))),
fontSize: 13,
fontFamily: "Inter",
fontWeight: FontWeight.w600))),
DataColumn(
label: Text('Actions',
style: TextStyle(
color: Color(0xFF9E9DBD),
fontFamily: "Archivo",
fontWeight: FontWeight.bold))),
fontSize: 13,
fontFamily: "Inter",
fontWeight: FontWeight.w600))),
],
rows: plans.map((plan) {
rows: paginatedPlans.map((plan) {
return DataRow(cells: [
DataCell(Text(plan.planId,
style: TextStyle(
@ -549,27 +603,32 @@ class _ApprovalListState extends State<ApprovalList> {
: plan.travellerName,
style: TextStyle(
fontSize: 13,
fontFamily: "Archivo",
fontFamily: "Inter",
))),
DataCell(Text(plan.employeeCode ?? "no data",
style: TextStyle(
fontSize: 13,
fontFamily: "Inter",
))),
DataCell(Text(plan.tripType,
style: TextStyle(
fontSize: 13,
fontFamily: "Inter",
))),
DataCell(Text(plan.tripTitle,
style: TextStyle(
fontSize: 13,
fontFamily: "Archivo",
fontFamily: "Inter",
),
softWrap: true,
overflow: TextOverflow.ellipsis)),
DataCell(Text(plan.tripType,
style: TextStyle(
fontSize: 13,
fontFamily: "Archivo",
))),
DataCell(Text(
// plan.createdOn,
_formatDate(plan.createdOn),
style: TextStyle(
fontSize: 13,
fontFamily: "Archivo",
fontFamily: "Inter",
))),
DataCell(
Container(
@ -603,6 +662,7 @@ class _ApprovalListState extends State<ApprovalList> {
? Colors.white
: Colors.grey,
fontSize: 12,
fontFamily: "Inter",
fontWeight: FontWeight.w400,
),
),
@ -646,6 +706,16 @@ class _ApprovalListState extends State<ApprovalList> {
width: 20,
height: 15),
),
IconButton(
icon: const Icon(
Icons.download,
color: Color(0xFF475569),
size: 18,
),
onPressed: () {
apiService.getPdfDownload(plan.planId);
}),
])),
]);
}).toList(),
@ -655,15 +725,68 @@ class _ApprovalListState extends State<ApprovalList> {
);
return Expanded(
child: isDesktop
? table
: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: SingleChildScrollView(
scrollDirection: Axis.vertical,
child: table,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
isDesktop
? table
: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: SingleChildScrollView(
scrollDirection: Axis.vertical,
child: table,
),
),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
// Previous button with arrow icon
IconButton(
icon: Icon(
Icons.arrow_back_ios_new,
size: 10,
),
onPressed: currentPage > 0
? () {
setState(() {
currentPage--;
});
}
: null,
),
),
SizedBox(width: 2),
// Page number text
Text(
'Page ${currentPage + 1}',
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w400,
color: Colors.black87,
fontFamily: "Inter",
),
),
SizedBox(width: 2),
// Next button with arrow icon
IconButton(
icon: Icon(
Icons.arrow_forward_ios,
size: 10,
),
onPressed: (currentPage + 1) * itemsPerPage <
plans.length
? () {
setState(() {
currentPage++;
});
}
: null,
),
],
),
],
),
);
},
)

View File

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:responsive_builder/responsive_builder.dart';
import '../../services/apiService.dart';
import '../../widgets/custom_text_field.dart';
import '../../widgets/custom_text_itnerary_sub.dart';
@ -24,8 +25,14 @@ class AccomodationScreen extends StatefulWidget {
}
class _AccomodationScreenState extends State<AccomodationScreen> {
ApiService apiService = ApiService();
// late Map<String, String> countryMap;
Map<String, String> countryMap = {};
late ValueNotifier<String?> flightFirstTripDateNotifier;
late ValueNotifier<String?> flightLastTripDateNotifier;
late ValueNotifier<String?> flightFirstToDestinationNotifier;
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
@ -95,6 +102,7 @@ class _AccomodationScreenState extends State<AccomodationScreen> {
@override
void initState() {
super.initState();
_addFocusListener(
_destinationFocusNode, (focus) => _destinationFocused = focus);
_addFocusListener(
@ -124,11 +132,21 @@ class _AccomodationScreenState extends State<AccomodationScreen> {
flightFirstTripDateNotifier = ValueNotifier<String?>(null);
flightLastTripDateNotifier = ValueNotifier<String?>(null);
flightFirstToDestinationNotifier = ValueNotifier<String?>(null);
WidgetsBinding.instance.addPostFrameCallback((_) {
loadCountryList();
final result = getFlightTripDateRange(widget.flightData);
print("Rs: $result");
flightFirstTripDateNotifier.value = result['firstTripDate'];
flightLastTripDateNotifier.value = result['lastTripDate'];
flightFirstToDestinationNotifier.value = result['firstToDestination'];
print(
"flightfirstToDestinationNotifier: $flightFirstToDestinationNotifier.value ");
// Only set controller after value is updated
final parsedDate =
@ -190,10 +208,14 @@ class _AccomodationScreenState extends State<AccomodationScreen> {
final firstTrip = allTrips.first;
final lastTrip = allTrips.last;
final firstTripToDestination = allTrips.first;
print("allTrips - $allTrips");
return {
'firstTripDate': firstTrip['date'],
'lastTripDate': lastTrip['date'],
'firstToDestination': firstTripToDestination['to_place']
};
}
@ -269,6 +291,37 @@ class _AccomodationScreenState extends State<AccomodationScreen> {
return errorMessages.isEmpty; // Valid if there are no errors
}
Future<void> loadCountryList() async {
final result = await apiService.fetchFlightsCountryList();
print("ResultCountry : $result");
// Create a map: Country_Code -> "City, Airport"
Map<String, String> tempCountryMap = {};
for (var country in result) {
String city = country['City'] ?? '';
String airport = country['Airport'] ?? '';
String displayName = '${country['City']}';
// String displayName = '${country['City']} | ${country['Airport']}';
tempCountryMap[country['Code']] = displayName;
}
setState(() {
countryMap = tempCountryMap; // Update the map
_updateDestinationCity();
});
}
void _updateDestinationCity() {
final toDestination = flightFirstToDestinationNotifier.value ?? '';
final toDestinationCity = countryMap[toDestination] ?? "";
if (toDestinationCity != '') {
_destinationController.text = toDestinationCity;
}
}
void handleSave() {
print("Handle Save accomadationData $accomadationData");

View File

@ -603,7 +603,7 @@ class _FlightScreenState extends State<FlightScreen> {
child: Column(
children: [
Padding(
padding: const EdgeInsets.all(28.0),
padding: const EdgeInsets.all(20.0),
child: Center(
child: Column(children: _buildAccomadtionForm(isDesktop)),
),
@ -1126,7 +1126,7 @@ class _FlightScreenState extends State<FlightScreen> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Class $index *",
selectedTripType == "Oneway" ? "Class *" : "Class $index *",
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
@ -1203,12 +1203,27 @@ class _FlightScreenState extends State<FlightScreen> {
? countryMap[selectedFrom[index]]
: null,
popupProps: PopupProps.menu(
showSearchBox: true,
fit: FlexFit.loose,
constraints: BoxConstraints(maxHeight: 220),
showSearchBox: true, // Enables search functionality
searchFieldProps: TextFieldProps(
decoration: InputDecoration(
hintText: "Search ...",
contentPadding:
EdgeInsets.symmetric(horizontal: 10),
decoration: InputDecoration(
hintText: "Search...",
contentPadding: EdgeInsets.symmetric(
horizontal: 10, vertical: 1),
),
style: TextStyle(fontSize: 12)),
menuProps: MenuProps(
backgroundColor: Colors.white,
),
itemBuilder: (context, item, isSelected) => Padding(
padding: const EdgeInsets.symmetric(
horizontal: 8.0, vertical: 6.0),
child: Text(
item,
style: TextStyle(
fontSize:
13), // 👈 Set your desired text size here
),
),
),
@ -1297,12 +1312,27 @@ class _FlightScreenState extends State<FlightScreen> {
? countryMap[selectedTo[index]]
: null,
popupProps: PopupProps.menu(
showSearchBox: true,
fit: FlexFit.loose,
constraints: BoxConstraints(maxHeight: 220),
showSearchBox: true, // Enables search functionality
searchFieldProps: TextFieldProps(
decoration: InputDecoration(
hintText: "Search ...",
contentPadding:
EdgeInsets.symmetric(horizontal: 10),
decoration: InputDecoration(
hintText: "Search...",
contentPadding: EdgeInsets.symmetric(
horizontal: 10, vertical: 1),
),
style: TextStyle(fontSize: 12)),
menuProps: MenuProps(
backgroundColor: Colors.white,
),
itemBuilder: (context, item, isSelected) => Padding(
padding: const EdgeInsets.symmetric(
horizontal: 8.0, vertical: 6.0),
child: Text(
item,
style: TextStyle(
fontSize:
13), // 👈 Set your desired text size here
),
),
),

View File

@ -8,6 +8,7 @@ import 'package:responsive_builder/responsive_builder.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../../config/apiUrl.dart';
import '../../utils/auth_utils.dart';
import '../../widgets/custom_text_field.dart';
import '../../widgets/custom_text_forex.dart';
import '../../widgets/custom_text_itnerary_sub.dart';
@ -41,6 +42,8 @@ class _ForexScreenState extends State<ForexScreen> {
late ValueNotifier<String?> flightFirstTripDateNotifier;
late ValueNotifier<String?> flightLastTripDateNotifier;
late String? userCardNumber;
Map<String, String?> selectedValues = {};
bool isChecked = false; // State variable for checkbox
@ -214,6 +217,8 @@ class _ForexScreenState extends State<ForexScreen> {
bool isCardChecked = data["have_card"] == "1";
if (isCardChecked) {
requiredFields.add("delivery_location");
} else {
requiredFields.add("card_number");
}
// Check validation for each field
@ -341,8 +346,18 @@ class _ForexScreenState extends State<ForexScreen> {
});
}
void handleUpdatedField() {
void handleUpdatedField() async {
// Set the selected value if available
userCardNumber = await getForexCardNumber();
// userCardNumber = "CD7909043";
print("userCardNumber - $userCardNumber");
if (widget.selectedItem == null &&
textControllers["_cardNumber"]?.text == "") {
print("userCardNumber11 - $userCardNumber");
textControllers["_cardNumber"]?.text = userCardNumber ?? "";
}
if (widget.selectedItem != null) {
print("UPDATAED SELECTION");
@ -365,6 +380,12 @@ class _ForexScreenState extends State<ForexScreen> {
selectedPerdiemAmount = widget.selectedItem!["perdiem_amount"] as String?;
isChecked =
widget.selectedItem!["have_card"] == "1"; // Convert string to bool
// if (textControllers["_cardNumber"] != null) {
// print("userCardNumber11 - $userCardNumber");
// textControllers["_cardNumber"]!.text = userCardNumber ?? '';
// }
_onFieldChangedForOthers();
setState(() {}); // Update the UI
@ -604,8 +625,8 @@ class _ForexScreenState extends State<ForexScreen> {
),
...buildResponsiveRow(_buildSecondRow(isDesktop)),
...buildResponsiveRow(_buildCardDetailsRow(isDesktop)),
...buildResponsiveRow(_buildFprexCard(isDesktop)),
...buildResponsiveRow(_buildThirdRow(isDesktop)),
...buildResponsiveRow(_buildForexCard(isDesktop)),
...buildResponsiveRow(_buildCommetsRow(isDesktop)),
];
}
@ -1361,7 +1382,9 @@ class _ForexScreenState extends State<ForexScreen> {
),
if (isDesktop)
Spacer()
SizedBox(
width: MediaQuery.of(context).size.width * 0.035,
)
else
SizedBox(
height: 8,
@ -1413,7 +1436,9 @@ class _ForexScreenState extends State<ForexScreen> {
),
if (isDesktop)
Spacer()
SizedBox(
width: MediaQuery.of(context).size.width * 0.035,
)
else
SizedBox(
height: 8,
@ -1453,12 +1478,6 @@ class _ForexScreenState extends State<ForexScreen> {
],
),
if (isDesktop)
Spacer()
else
SizedBox(
height: 8,
),
// Column(
// crossAxisAlignment: CrossAxisAlignment.start,
// children: [
@ -1499,96 +1518,6 @@ class _ForexScreenState extends State<ForexScreen> {
// ),
// ],
// ),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Card Number",
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74)),
),
SizedBox(height: 5),
CustomTextFieldItnerarySubWrapper(
isFocused: focusStates["_cardNumber"] ?? false,
isDesktop: isDesktop,
child: SizedBox(
height: 40,
child: TextField(
enabled: !isChecked,
focusNode: focusNodes["_cardNumber"],
controller: textControllers["_cardNumber"],
style: const TextStyle(fontSize: 12),
decoration: const InputDecoration(
labelText: "Card Number",
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16),
),
),
),
),
if (errorMessages["card_number"] != null) ...[
SizedBox(height: 5), // Space before error message
Text(
"Required",
style: TextStyle(color: Colors.red, fontSize: 12),
),
],
],
),
];
}
List<Widget> _buildThirdRow(bool isDesktop) {
return [
isChecked
? Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Delivery Location",
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74)),
),
SizedBox(height: 5),
CustomTextFieldWrapper(
isFocused: focusStates["_deliveryLocation"] ??
false, // Dropdown doesn't use focus
isDesktop: isDesktop,
width: isDesktop
? MediaQuery.of(context).size.width * 0.330
: MediaQuery.of(context).size.width * 0.66,
child: TextField(
focusNode: focusNodes["_deliveryLocation"],
controller: textControllers["_deliveryLocation"],
maxLines: 3,
keyboardType: TextInputType.multiline,
style: TextStyle(fontSize: 12),
decoration: InputDecoration(
labelText: "Location",
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 4),
),
),
),
if (errorMessages["delivery_location"] != null) ...[
SizedBox(height: 5), // Space before error message
Text(
"Required",
style: TextStyle(color: Colors.red, fontSize: 12),
),
],
],
)
: SizedBox.shrink()
];
}
@ -1644,36 +1573,141 @@ class _ForexScreenState extends State<ForexScreen> {
];
}
List<Widget> _buildFprexCard(bool isDesktop) {
List<Widget> _buildForexCard(bool isDesktop) {
return [
Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Checkbox(
value: isChecked,
side: BorderSide(
color: Colors.grey, // Change border color
width: 1, // Adjust thickness
!isChecked
? Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Card Number",
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74)),
),
SizedBox(height: 5),
CustomTextFieldItnerarySubWrapper(
isFocused: focusStates["_cardNumber"] ?? false,
isDesktop: isDesktop,
child: SizedBox(
height: 40,
child: TextField(
enabled: !isChecked,
focusNode: focusNodes["_cardNumber"],
controller: textControllers["_cardNumber"],
style: const TextStyle(fontSize: 12),
decoration: const InputDecoration(
labelText: "Card Number",
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16),
),
),
),
),
if (errorMessages["card_number"] != null) ...[
SizedBox(height: 5), // Space before error message
Text(
"Required",
style: TextStyle(color: Colors.red, fontSize: 12),
),
],
],
)
: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Delivery Location",
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74)),
),
SizedBox(height: 5),
CustomTextFieldWrapper(
isFocused: focusStates["_deliveryLocation"] ??
false, // Dropdown doesn't use focus
isDesktop: isDesktop,
width: isDesktop
? MediaQuery.of(context).size.width * 0.34
: null,
child: SizedBox(
height: 35,
child: TextField(
focusNode: focusNodes["_deliveryLocation"],
controller: textControllers["_deliveryLocation"],
style: TextStyle(fontSize: 12),
decoration: InputDecoration(
labelText: "Location",
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16),
),
),
),
),
if (errorMessages["delivery_location"] != null) ...[
SizedBox(height: 5), // Space before error message
Text(
"Required",
style: TextStyle(color: Colors.red, fontSize: 12),
),
],
],
),
onChanged: (bool? value) {
setState(() {
isChecked = value!;
if (isChecked) {
textControllers["_cardNumber"]
?.clear(); // Clear the value when isChecked is true
}
});
},
),
Text(
"Check If You Don't Have a forex Account",
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74)),
if (isDesktop)
SizedBox(
width: MediaQuery.of(context).size.width * 0.035,
)
else
SizedBox(
height: 8,
),
Column(
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
alignment: Alignment.bottomLeft,
child: Transform.scale(
scale: 0.8,
child: Checkbox(
value: isChecked,
activeColor: Color(0xFF114D8B),
// checkColor: Color(0xFF114D8B),
side: BorderSide(
color: Colors.grey, // Change border color
width: 1, // Adjust thickness
),
onChanged: (bool? value) {
setState(() {
isChecked = value!;
if (isChecked) {
textControllers["_cardNumber"]
?.clear(); // Clear the value when isChecked is true
}
});
},
),
),
),
Text(
"Check If You Don't Have a forex Account",
style: TextStyle(
fontSize: 10,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74)),
),
],
),
],
)
),
];
}

View File

@ -506,7 +506,7 @@ class _TaxiScreenState extends State<TaxiScreen> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Destination",
"City",
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,

View File

@ -247,7 +247,7 @@ class _TrainScreenState extends State<TrainScreen> {
isCountryLoading = true;
});
final result = await apiService.fetchFlightsCountryList();
final result = await apiService.fetchTrainCountryList();
print("ResultCountry : $result");
@ -255,11 +255,9 @@ class _TrainScreenState extends State<TrainScreen> {
Map<String, String> tempCountryMap = {};
for (var country in result) {
String city = country['City'] ?? '';
String airport = country['Airport'] ?? '';
String displayName = '${country['City']} - ${country['Airport']}';
String displayName = '${country['Station_Name']} ';
tempCountryMap[country['Code']] = displayName;
tempCountryMap[country['Station_Code']] = displayName;
}
setState(() {

View File

@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:responsive_builder/responsive_builder.dart';
import '../../services/apiService.dart';
import '../../widgets/custom_text_field.dart';
import '../../widgets/custom_text_itnerary_sub.dart';
@ -32,8 +33,14 @@ class VisaScreen extends StatefulWidget {
class _VisaScreenState extends State<VisaScreen> {
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
ApiService apiService = ApiService();
// late Map<String, String> countryMap;
Map<String, String> countryMap = {};
late ValueNotifier<String?> flightFirstTripDateNotifier;
late ValueNotifier<String?> flightLastTripDateNotifier;
late ValueNotifier<String?> flightFirstToDestinationNotifier;
Map<String, String?> selectedValues = {};
@ -136,6 +143,40 @@ class _VisaScreenState extends State<VisaScreen> {
});
}
// Future<void> loadCountryList() async {
// final result = await apiService.fetchFlightsCountryList();
//
// print("ResultCountry : $result");
//
// // Create a map: Country_Code -> "City, Airport"
// Map<String, String> tempCountryMap = {};
//
// for (var country in result) {
// String city = country['City'] ?? '';
// String airport = country['Airport'] ?? '';
// String displayName = '${country['City']}';
// // String displayName = '${country['City']} | ${country['Airport']}';
//
// tempCountryMap[country['Code']] = displayName;
// }
//
// setState(() {
// countryMap = tempCountryMap; // Update the map
// _updateDestinationCity();
// });
// }
void _updateDestinationCity() {
final toDestination = flightFirstToDestinationNotifier.value ?? '';
// final toDestinationCity = countryMap[toDestination] ?? "";
if (toDestination != '') {
// _destinationController.text = toDestinationCity;
selectedCountry = widget.selectedItem!["toDestination"] as String?;
}
}
Map<String, String?> getFlightTripDateRange(
List<Map<String, dynamic>> flightData) {
final allTrips = flightData

View File

@ -279,7 +279,7 @@ class _FlightListWidgetState extends State<FlightListWidget> {
Expanded(
flex: 2,
child: Text(
"Planned Trips",
"Sector",
style: TextStyle(fontSize: 11, fontFamily: "Archivo"),
)),
Expanded(

View File

@ -231,7 +231,7 @@ class TaxiListWidget extends StatelessWidget {
Expanded(
flex: 2,
child: Text(
"Destination",
"City",
style: TextStyle(
fontSize: 11, fontFamily: "Archivo"),
)),

View File

@ -71,7 +71,8 @@ class _TrainListWidgetState extends State<TrainListWidget> {
}
Future<void> loadCountryList() async {
final result = await apiService.fetchFlightsCountryList();
// final result = await apiService.fetchFlightsCountryList();
final result = await apiService.fetchTrainCountryList();
print("ResultCountry : $result");
@ -79,12 +80,11 @@ class _TrainListWidgetState extends State<TrainListWidget> {
Map<String, String> tempCountryMap = {};
for (var country in result) {
String city = country['City'] ?? '';
String airport = country['Airport'] ?? '';
String displayName = '${country['City']} - ${country['Airport']}';
String displayName = '${country['Station_Name']}';
// String displayName = '${country['City']} | ${country['Airport']}';
tempCountryMap[country['Code']] = displayName;
// tempCountryMap[country['Code']] = displayName;
tempCountryMap[country['Station_Code']] = displayName;
}
setState(() {
@ -361,8 +361,7 @@ class _TrainListWidgetState extends State<TrainListWidget> {
SizedBox(width: 10),
Expanded(
flex: 2,
child: Text(
"$fromPlaceCountry (from) - (to) $toPlaceCountry",
child: Text("$fromPlaceCountry - $toPlaceCountry",
// "${item["from_station"]!} - ${(item["to_station"])}",
style: TextStyle(

View File

@ -1,8 +1,6 @@
import 'dart:convert';
import 'dart:ui' as html;
import 'dart:async';
import 'dart:html' as html;
import 'dart:typed_data';
import 'package:dropdown_search/dropdown_search.dart';
import 'package:web/web.dart' as web;
@ -14,6 +12,7 @@ import 'package:go_router/go_router.dart';
import 'package:responsive_builder/responsive_builder.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:http/http.dart' as http;
import 'package:universal_html/html.dart' as html;
import '../../config/apiUrl.dart';
import '../../data/models/plan.dart';
@ -556,6 +555,69 @@ class CreateNewPlansState extends State<CreateNewPlan> {
});
}
Future<void> getPdfDownload() async {
final String apiUrldata =
'$apiUrl/api/plans/download?plan_id=$selectedPlanId';
// final String apiUrldata = '$apiUrl/auth/googlelogin';
final token = await getToken();
if (token == null) {
throw Exception('Token not found. Please log in.');
}
final response = await http.get(
Uri.parse(apiUrldata),
headers: {
'Authorization': 'Bearer $token',
'Content-Type': 'application/json',
},
);
if (response.statusCode == 200) {
try {
print("PDf Dowloaded");
// Create a blob from the response body
final blob = html.Blob([response.bodyBytes]);
// Generate a download URL for the blob
final url = html.Url.createObjectUrlFromBlob(blob);
// Create a link element to trigger the download
final anchor = html.AnchorElement(href: url)
..setAttribute('download', 'trip_plan_$selectedPlanId.pdf')
..click();
// Revoke the download URL to free up resources
html.Url.revokeObjectUrl(url);
} catch (e) {
throw Exception('Error parsing response: $e');
}
} else if (response.statusCode == 404) {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text('File not found.'),
// content: Text('File not found.'),
actions: [
TextButton(
child: Text('OK'),
onPressed: () {
Navigator.of(context).pop(); // Close the dialog
},
),
],
);
},
);
} else {
throw Exception('Failed to load plans');
}
}
void getSelectedPlanFor() {
if (!mounted) return;
@ -1031,80 +1093,105 @@ class CreateNewPlansState extends State<CreateNewPlan> {
DeviceScreenType.desktop;
return Container(
padding: const EdgeInsets.all(10),
padding: const EdgeInsets.all(5),
child: isDesktop
? Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: _buildApproverControls(),
crossAxisAlignment: CrossAxisAlignment.start,
children: [
..._buildApproverControls(isDesktop),
Spacer(),
Text(
widget.isViewMode
? "View Plan"
: (selectedPlanId != null &&
selectedPlanId!.isNotEmpty
? "Update Plan"
: "New Plan"),
style: TextStyle(fontSize: 18),
),
Spacer(),
..._buildPlanPdf()
],
)
: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: _buildApproverControls(),
children: [
..._buildApproverControls(isDesktop),
Text(
widget.isViewMode
? "View Plan"
: (planData.isNotEmpty
? "Update Plan"
: "New Plan"),
style: TextStyle(fontSize: 18),
),
..._buildPlanPdf()
],
),
);
}),
if (isStatusExpanded)
Container(
margin: isDesktop
? const EdgeInsets.only(left: 60, top: 0)
: const EdgeInsets.only(left: 5, top: 2),
padding: const EdgeInsets.all(12),
width: isDesktop
? MediaQuery.of(context).size.width * 0.2
: MediaQuery.of(context).size.width,
decoration: BoxDecoration(
color: Color(0xFFF5F5F5),
border: Border.all(
// color: Colors.grey.shade300,
color: Colors.white,
width: 0.2),
borderRadius: BorderRadius.circular(8),
// boxShadow: [
// BoxShadow(
// // color: Colors.grey.withAlpha(20),
// color: Colors.grey.withAlpha(20),
// spreadRadius: 1.5,
// blurRadius: 7,
// offset: Offset(0, 4), // shadow direction: bottom
// ),
// ],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (!hasApprovals)
Center(
child: Text(
"--- No Approvals ---",
style: TextStyle(
fontFamily: "Archivo",
fontSize: 11,
fontWeight: FontWeight.w500,
color: Colors.black87,
),
)),
for (int i = 0; i < planStatusList.length; i++) ...[
if (planStatusList[i].entries.any((entry) =>
entry.key.contains('status') &&
entry.value != null &&
entry.value.toString().isNotEmpty)) ...[
_buildApprovalItem(
"Approver ${i + 1}",
planStatusList[i]
.entries
.firstWhere(
(entry) => entry.key.contains('status'),
orElse: () => MapEntry('', ''),
)
.value
.toString(),
),
SizedBox(height: 6),
],
],
],
),
),
// if (isStatusExpanded)
// Container(
// margin: isDesktop
// ? const EdgeInsets.only(left: 0, top: 0)
// : const EdgeInsets.only(left: 5, top: 2),
// padding: const EdgeInsets.all(12),
// width: isDesktop
// ? MediaQuery.of(context).size.width * 0.2
// : MediaQuery.of(context).size.width,
// decoration: BoxDecoration(
// color: Color(0xFFF5F5F5),
// border: Border.all(
// // color: Colors.grey.shade300,
// color: Colors.white,
// width: 0.2),
// borderRadius: BorderRadius.circular(8),
// // boxShadow: [
// // BoxShadow(
// // // color: Colors.grey.withAlpha(20),
// // color: Colors.grey.withAlpha(20),
// // spreadRadius: 1.5,
// // blurRadius: 7,
// // offset: Offset(0, 4), // shadow direction: bottom
// // ),
// // ],
// ),
// child: Column(
// crossAxisAlignment: CrossAxisAlignment.start,
// children: [
// if (!hasApprovals)
// Center(
// child: Text(
// "--- No Approvals ---",
// style: TextStyle(
// fontFamily: "Archivo",
// fontSize: 11,
// fontWeight: FontWeight.w500,
// color: Colors.black87,
// ),
// )),
// for (int i = 0; i < planStatusList.length; i++) ...[
// if (planStatusList[i].entries.any((entry) =>
// entry.key.contains('status') &&
// entry.value != null &&
// entry.value.toString().isNotEmpty)) ...[
// _buildApprovalItem(
// "Approver ${i + 1}",
// planStatusList[i]
// .entries
// .firstWhere(
// (entry) => entry.key.contains('status'),
// orElse: () => MapEntry('', ''),
// )
// .value
// .toString(),
// ),
// SizedBox(height: 6),
// ],
// ],
// ],
// ),
// ),
if (isApproverRejected)
Row(
crossAxisAlignment: CrossAxisAlignment.start,
@ -1140,11 +1227,13 @@ class CreateNewPlansState extends State<CreateNewPlan> {
),
],
),
if (widget.isApprover || isStatusExpanded)
Divider(
thickness: 0.1,
color: Colors.blueGrey,
),
// if (widget.isApprover || isStatusExpanded)
Divider(
thickness: 0.1,
color: Colors.blueGrey,
),
if (widget.isApprover)
SizedBox(
height: 5,
@ -2283,55 +2372,217 @@ class CreateNewPlansState extends State<CreateNewPlan> {
);
}
List<Widget> _buildApproverControls() {
Color getStatusColor(String status) {
if (status == "Partially Approved") return Colors.yellow;
if (status == "Approved") return Colors.green;
if (status == "Completed") return Colors.green;
if (status == "Rejected") return Colors.red;
return Colors.grey;
}
List<Widget> _buildApproverControls(bool isDesktop) {
final statusText = isApproverApproved
? "Approved"
: isApproverRejected
? "Rejected"
: (statusValue ?? "");
bool hasApprovals = planStatusList.any(
(item) => item.entries.any(
(entry) =>
entry.key.contains('status') &&
entry.value != null &&
entry.value.toString().isNotEmpty,
),
);
return [
Row(
mainAxisSize: MainAxisSize.min,
Stack(
clipBehavior: Clip.none, // allow tooltip to overflow
children: [
Text("Status : "),
if (!isApproverApproved && !isApproverRejected)
Text(statusValue ?? "",
style: TextStyle(
fontFamily: "Archivo",
fontWeight: FontWeight.bold,
color: widget.layoutColor ?? Colors.grey,
)),
if (isApproverApproved)
Text("Approved",
style: TextStyle(
fontFamily: "Archivo",
fontWeight: FontWeight.bold,
color: widget.layoutColor ?? Colors.grey,
)),
if (isApproverRejected)
Text("Rejected",
style: TextStyle(
fontFamily: "Archivo",
fontWeight: FontWeight.bold,
color: widget.layoutColor ?? Colors.grey,
)),
SizedBox(
width: 5,
),
MouseRegion(
onEnter: (_) {
setState(() {
isStatusExpanded = true;
});
},
onExit: (_) {
setState(() {
isStatusExpanded = false;
});
},
child: Icon(
Icons.approval_outlined,
size: 18,
color: isStatusExpanded ? Colors.green : Colors.grey,
if (statusValue != "")
MouseRegion(
onEnter: (_) => setState(() => isStatusExpanded = true),
onExit: (_) => setState(() => isStatusExpanded = false),
child: Container(
decoration: BoxDecoration(
border: Border.all(color: getStatusColor(statusText)),
borderRadius: BorderRadius.circular(8),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding: const EdgeInsets.only(
top: 8.0, bottom: 8.0, left: 15, right: 15),
child: Text(
statusText,
style: TextStyle(
fontFamily: "Roboto",
fontWeight: FontWeight.w400,
fontSize: 12,
color: getStatusColor(
isApproverApproved
? "Approved"
: isApproverRejected
? "Rejected"
: (statusValue ?? ""),
),
),
),
),
],
),
),
),
),
// Tooltip
if (isStatusExpanded)
Positioned(
top: -20, // Ensure the tooltip is above the container
left:
MediaQuery.of(context).size.width * 0.06, // align with label
child: Material(
// important: avoid clipping, give elevation
elevation: 4,
borderRadius: BorderRadius.circular(8),
child: SizedBox(
child: Container(
width: isDesktop
? MediaQuery.of(context).size.width * 0.25
: MediaQuery.of(context).size.width,
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.white,
// color: Colors.transparent,
border: Border.all(color: Colors.grey.shade300),
borderRadius: BorderRadius.circular(8),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (!hasApprovals)
Center(
child: Text(
"--- No Approvals ---",
style: TextStyle(
fontFamily: "Archivo",
fontSize: 11,
fontWeight: FontWeight.w500,
color: Colors.black87,
),
)),
for (int i = 0; i < planStatusList.length; i++) ...[
if (planStatusList[i].entries.any((entry) =>
entry.key.contains('status') &&
entry.value != null &&
entry.value.toString().isNotEmpty)) ...[
_buildApprovalItem(
"Approver ${i + 1}",
planStatusList[i]
.entries
.firstWhere(
(entry) => entry.key.contains('status'),
orElse: () => MapEntry('', ''),
)
.value
.toString(),
),
SizedBox(height: 6),
],
],
],
),
),
),
),
),
// if (isStatusExpanded)
// Positioned(
// top: 20, // adjust how much above you want
// left: 100,
// child: Container(
// margin: isDesktop
// ? const EdgeInsets.only(left: 0, top: 0)
// : const EdgeInsets.only(left: 5, top: 2),
// padding: const EdgeInsets.all(12),
// width: isDesktop
// ? MediaQuery.of(context).size.width * 0.2
// : MediaQuery.of(context).size.width,
// decoration: BoxDecoration(
// // color: Color(0xFFF5F5F5),
// color: Colors.white,
// border: Border.all(color: Colors.white, width: 0.2),
// borderRadius: BorderRadius.circular(8),
// ),
// child: Column(
// crossAxisAlignment: CrossAxisAlignment.start,
// children: [
// if (!hasApprovals)
// Center(
// child: Text(
// "--- No Approvals ---",
// style: TextStyle(
// fontFamily: "Archivo",
// fontSize: 11,
// fontWeight: FontWeight.w500,
// color: Colors.black87,
// ),
// )),
// for (int i = 0; i < planStatusList.length; i++) ...[
// if (planStatusList[i].entries.any((entry) =>
// entry.key.contains('status') &&
// entry.value != null &&
// entry.value.toString().isNotEmpty)) ...[
// _buildApprovalItem(
// "Approver ${i + 1}",
// planStatusList[i]
// .entries
// .firstWhere(
// (entry) => entry.key.contains('status'),
// orElse: () => MapEntry('', ''),
// )
// .value
// .toString(),
// ),
// SizedBox(height: 6),
// ],
// ],
// ],
// ),
// ),
// ),
],
),
// Row(
// mainAxisSize: MainAxisSize.min,
// children: [
// Text(
// isApproverApproved
// ? "Approved"
// : isApproverRejected
// ? "Rejected"
// : (statusValue ?? ""),
// style: TextStyle(
// fontFamily: "Archivo",
// fontWeight: FontWeight.bold,
// color: widget.layoutColor ?? Colors.grey,
// ),
// ),
// SizedBox(width: 5),
// MouseRegion(
// onEnter: (_) => setState(() => isStatusExpanded = true),
// onExit: (_) => setState(() => isStatusExpanded = false),
// child: Icon(
// Icons.approval_outlined,
// size: 18,
// color: isStatusExpanded ? Colors.green : Colors.grey,
// ),
// ),
// ],
// ),
const SizedBox(height: 10, width: 10),
if (widget.isApprover)
GestureDetector(
@ -2461,6 +2712,38 @@ class CreateNewPlansState extends State<CreateNewPlan> {
];
}
List<Widget> _buildPlanPdf() {
return [
Column(
children: [
InkWell(
hoverColor: Colors.white,
onTap: () {
print('📄 PDF icon clicked!');
getPdfDownload();
},
child: Row(
children: [
Text("Download PDF"),
SizedBox(
width: 10,
),
Transform.scale(
scale: 1.5, // 1.0 = normal, 1.5 = 50% bigger
child: Image.asset(
'assets/images/IconsImg/planPdf_icon.png',
width: 25,
height: 25, // keep the real height small
),
),
],
)),
// SizedBox(height: 10), // small spacing
],
),
];
}
void _showInputDialog(String title) {
showDialog(
context: context,

View File

@ -154,7 +154,7 @@ class DynamicItineraryState extends State<DynamicItinerary> {
List<String> getAllowedServiceNames() {
if (widget.tripType == "1") {
return ["flight", "accomodation", "train", "bus"];
return ["flight", "accomodation", "train", "bus", "taxi"];
} else if (widget.tripType == "2") {
return [
"flight",
@ -162,7 +162,8 @@ class DynamicItineraryState extends State<DynamicItinerary> {
"forex",
"insurance",
"visa",
"miscellaneous"
"miscellaneous",
"taxi"
];
} else {
// tripType is null or not 1/2, allow everything

View File

@ -27,7 +27,6 @@ class _ListPlansState extends State<ListPlans> {
int currentPage = 0;
int itemsPerPage = 8;
late Future<List<Plan>> futurePlans;
String? userId;
String? orgId;
String? token;
@ -35,7 +34,7 @@ class _ListPlansState extends State<ListPlans> {
Color? layoutColor;
Color? bodyColor;
late Future<List<Plan>> futurePlans;
List<Plan> allPlans = [];
List<Plan> filteredPlans = [];
TextEditingController searchController = TextEditingController();
@ -49,22 +48,24 @@ class _ListPlansState extends State<ListPlans> {
initializeData();
loadInitialData();
futurePlans.then((plans) {
setState(() {
allPlans = plans;
filteredPlans = plans;
});
});
// 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) ||
@ -73,6 +74,7 @@ class _ListPlansState extends State<ListPlans> {
(plan.statusValue?.toLowerCase().contains(lowerQuery) ?? false);
}).toList();
});
print("filteredPlans: $filteredPlans");
}
void loadInitialData() async {
@ -103,6 +105,15 @@ class _ListPlansState extends State<ListPlans> {
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
});
});
}
}
@ -350,7 +361,7 @@ class _ListPlansState extends State<ListPlans> {
children: [
const Text('Trip List',
style: TextStyle(
fontFamily: "Archivo",
fontFamily: "Inter",
fontSize: 16,
fontWeight: FontWeight.w600,
color: Color(0xFF212121))),
@ -358,31 +369,43 @@ 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 for a plan",
hintText: "Search...",
hintStyle:
TextStyle(fontSize: 14, color: Color(0xFF9E9DBD)),
prefixIcon:
Icon(Icons.search, color: Color(0xFF9E9DBD)),
TextStyle(fontSize: 12, color: Color(0xFF9E9DBD)),
prefixIcon: Icon(
Icons.search,
color: Color(0xFF9E9DBD),
size: 18,
),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderRadius: BorderRadius.circular(12),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(
color: Colors.grey.shade300, width: 0.5),
color: Colors.grey.shade200, width: 0.5),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide:
BorderSide(color: Colors.blueAccent, width: 1),
BorderSide(color: Colors.grey.shade300, width: 1),
),
),
style: TextStyle(
fontSize: 12,
fontFamily: "Inter",
),
),
),
// SizedBox(width: 16),
Spacer(),
@ -511,7 +534,10 @@ class _ListPlansState extends State<ListPlans> {
);
}
List<Plan> plans = snapshot.data!;
// 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)));
@ -537,58 +563,71 @@ class _ListPlansState extends State<ListPlans> {
DataColumn(
label: Text('Trip Id',
style: TextStyle(
color: Color(0xFF9E9DBD),
fontSize: 14,
fontFamily: "Archivo",
fontWeight: FontWeight.bold))),
// color: Colors.grey,
// color: Color(0xFF9E9DBD),
fontSize: 13,
fontFamily: "Inter",
fontWeight: FontWeight.w600))),
DataColumn(
label: Text('Employee Code',
style: TextStyle(
// color: Color(0xFF9E9DBD),
fontSize: 13,
fontFamily: "Inter",
fontWeight: FontWeight.w600))),
DataColumn(
label: Text('Trip Name',
style: TextStyle(
color: Color(0xFF9E9DBD),
fontFamily: "Archivo",
fontWeight: FontWeight.bold))),
fontSize: 13,
fontFamily: "Inter",
fontWeight: FontWeight.w600))),
DataColumn(
label: Text('Traveller',
style: TextStyle(
color: Color(0xFF9E9DBD),
fontFamily: "Archivo",
fontWeight: FontWeight.bold))),
fontSize: 13,
fontFamily: "Inter",
fontWeight: FontWeight.w600))),
DataColumn(
label: Text('Trip Type',
style: TextStyle(
color: Color(0xFF9E9DBD),
fontFamily: "Archivo",
fontWeight: FontWeight.bold))),
fontSize: 13,
fontFamily: "Inter",
fontWeight: FontWeight.w600))),
DataColumn(
label: Text('Created On',
style: TextStyle(
color: Color(0xFF9E9DBD),
fontFamily: "Archivo",
fontWeight: FontWeight.bold))),
fontSize: 13,
fontFamily: "Inter",
fontWeight: FontWeight.w600))),
DataColumn(
label: Text('Status',
style: TextStyle(
color: Color(0xFF9E9DBD),
fontFamily: "Archivo",
fontWeight: FontWeight.bold))),
fontSize: 13,
fontFamily: "Inter",
fontWeight: FontWeight.w600))),
DataColumn(
label: Text('Actions',
style: TextStyle(
color: Color(0xFF9E9DBD),
fontFamily: "Archivo",
fontWeight: FontWeight.bold))),
fontSize: 13,
fontFamily: "Inter",
fontWeight: FontWeight.w600))),
],
rows: paginatedPlans.map((plan) {
return DataRow(cells: [
DataCell(Text(plan.planId,
style: TextStyle(
fontSize: 13,
fontFamily: "Archivo",
fontFamily: "Inter",
))),
DataCell(Text(plan.employeeCode ?? "no data",
style: TextStyle(
fontSize: 13,
fontFamily: "Inter",
))),
DataCell(Text(plan.tripTitle,
style: TextStyle(
fontSize: 13,
fontFamily: "Archivo",
fontFamily: "Inter",
),
softWrap: true,
overflow: TextOverflow.ellipsis)),
@ -598,18 +637,18 @@ class _ListPlansState extends State<ListPlans> {
: plan.travellerName,
style: TextStyle(
fontSize: 13,
fontFamily: "Archivo",
fontFamily: "Inter",
))),
DataCell(Text(plan.tripType,
style: TextStyle(
fontSize: 13,
fontFamily: "Archivo",
fontFamily: "Inter",
))),
DataCell(Text(_formatDate(plan.createdOn),
// plan.createdOn,
style: TextStyle(
fontSize: 13,
fontFamily: "Archivo",
fontFamily: "Inter",
))),
DataCell(
Container(
@ -646,6 +685,7 @@ class _ListPlansState extends State<ListPlans> {
// ? Colors.white
// : Colors.grey,
fontSize: 12,
fontFamily: "Inter",
fontWeight: FontWeight.w400,
),
),
@ -668,7 +708,7 @@ class _ListPlansState extends State<ListPlans> {
isViewMode: false),
child: Image.asset(
'assets/images/IconsImg/edit.png',
width: 20,
width: 15,
height: 15),
),
@ -688,6 +728,15 @@ class _ListPlansState extends State<ListPlans> {
width: 20,
height: 15),
),
IconButton(
icon: const Icon(
Icons.download,
color: Color(0xFF475569),
size: 18,
),
onPressed: () {
apiService.getPdfDownload(plan.planId);
}),
])),
]);
}).toList(),
@ -698,6 +747,7 @@ class _ListPlansState extends State<ListPlans> {
return Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
isDesktop
? table
@ -734,6 +784,7 @@ class _ListPlansState extends State<ListPlans> {
fontSize: 12,
fontWeight: FontWeight.w400,
color: Colors.black87,
fontFamily: "Inter",
),
),
SizedBox(width: 2),

View File

@ -111,15 +111,18 @@ class _PolicyState extends State<Policy> {
WidgetsBinding.instance.addPostFrameCallback((_) {
loadinitializeData();
if (widget.policy != null) {
final details =
List<Map<String, dynamic>>.from(widget.policy!['policy_details']);
policyCriteriaKey.currentState?.loadPolicyDetails(details);
}
updateSelectedServices();
updateData();
loadInitialData();
if (widget.policy != null) {
final details =
List<Map<String, dynamic>>.from(widget.policy!['policy_details']);
policyCriteriaKey.currentState?.loadPolicyDetails(details);
policyCriteriaKey.currentState?.fetchTrainFlightClass();
}
});
}
@ -233,6 +236,7 @@ class _PolicyState extends State<Policy> {
String firstServiceName = ServicesChoosed?.first['name'];
print("✅ First service name selected for filter: $firstServiceName");
selectedService = firstServiceName;
policyCriteriaKey.currentState?.fetchTrainFlightClass();
}
}
}
@ -538,89 +542,25 @@ class _PolicyState extends State<Policy> {
),
isDesktop ? SizedBox(height: 0) : SizedBox(height: 5),
Container(
// color: Colors.amber,
// color: isDesktop ? Color(0xFFF7F7FB) : Colors.white,
padding: isDesktop ? const EdgeInsets.only(left: 35) : null,
child: Column(
children: [
Row(
padding: isDesktop ? const EdgeInsets.only(left: 35) : null,
child: isDesktop
? Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("Policy Name",
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w200,
color: Colors.black)),
SizedBox(height: 5),
CustomTextFieldUserWrapper(
isFocused: false,
isDesktop: isDesktop,
child: SizedBox(
height: 40,
child: TextField(
style: TextStyle(fontSize: 12),
controller: _policyController,
// enabled: !isViewMode,
onChanged: (value) {
_clearError("name");
},
decoration: InputDecoration(
labelText: "Policy Name",
labelStyle: TextStyle(
fontSize: 12, color: Colors.grey),
floatingLabelBehavior:
FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding:
EdgeInsets.symmetric(vertical: 16),
),
),
),
),
if (errorMessages["name"] != null) ...[
SizedBox(height: 5), // Space before error message
Text(
errorMessages["name"]!,
style: TextStyle(color: Colors.red, fontSize: 12),
),
],
],
),
Expanded(child: _buildPolicyNameField(isDesktop)),
Spacer(),
Expanded(child: _buildPolicyTypeField(isDesktop)),
],
)
: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildPolicyNameField(isDesktop),
SizedBox(height: 20),
_buildPolicyTypeField(isDesktop),
],
),
SizedBox(
height: 5,
),
Row(
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("Policy Type",
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w200,
color: Colors.black)),
SizedBox(height: 5),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: _buildTripType(isDesktop),
),
if (errorMessages["trip_type"] != null) ...[
SizedBox(height: 5), // Space before error message
Text(
errorMessages["trip_type"]!,
style: TextStyle(color: Colors.red, fontSize: 12),
),
],
],
)
],
),
],
)),
),
SizedBox(
height: 10,
),
@ -657,6 +597,67 @@ class _PolicyState extends State<Policy> {
);
}
Widget _buildPolicyNameField(bool isDesktop) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("Policy Name",
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w200,
color: Colors.black)),
SizedBox(height: 5),
CustomTextFieldUserWrapper(
isFocused: false,
isDesktop: isDesktop,
child: SizedBox(
height: 40,
child: TextField(
style: TextStyle(fontSize: 12),
controller: _policyController,
onChanged: (value) => _clearError("name"),
decoration: InputDecoration(
labelText: "Policy Name",
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16),
),
),
),
),
if (errorMessages["name"] != null) ...[
SizedBox(height: 5),
Text(errorMessages["name"]!,
style: TextStyle(color: Colors.red, fontSize: 12)),
],
],
);
}
Widget _buildPolicyTypeField(bool isDesktop) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("Policy Type",
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w200,
color: Colors.black)),
SizedBox(height: 5),
Row(
mainAxisAlignment: MainAxisAlignment.start,
children: _buildTripType(isDesktop),
),
if (errorMessages["trip_type"] != null) ...[
SizedBox(height: 5),
Text(errorMessages["trip_type"]!,
style: TextStyle(color: Colors.red, fontSize: 12)),
],
],
);
}
Widget _buildPolicyCategoryList(bool isDesktop) {
return Container(
color: Colors.white,
@ -727,6 +728,8 @@ class _PolicyState extends State<Policy> {
selectedService == "Train") {
showClass = true;
showCost = true;
int serviceCode = selectedService == "Flight" ? 1 : 2;
policyCriteriaKey.currentState?.fetchTrainFlightClass();
} else if (selectedService == "Accommodation") {
showClass = true;
showCost = false;
@ -825,23 +828,6 @@ class _PolicyState extends State<Policy> {
fontWeight: _selectedTripType == "1" ? FontWeight.w600 : null,
fontSize: 13),
),
// Radio<String>(
// activeColor: Colors.blueAccent,
// // contentPadding: EdgeInsets.zero,
// visualDensity: VisualDensity.compact,
// // dense: true,
// value: "1",
// groupValue: _selectedTripType,
// onChanged: widget.isViewMode
// ? null
// : (value) {
// setState(() {
// _selectedTripType = value!;
// });
// },
// ),
GestureDetector(
onTap: () {
setState(() {

View File

@ -1,6 +1,12 @@
import 'dart:convert';
import 'package:dropdown_search/dropdown_search.dart';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import '../../config/apiUrl.dart';
import '../../services/apiService.dart';
import '../../utils/auth_utils.dart';
import '../../widgets/custom_text_field.dart';
import '../../widgets/custom_user_form.dart';
@ -30,6 +36,12 @@ class PolicyCriteria extends StatefulWidget {
class PolicyCriteriaState extends State<PolicyCriteria> {
String? ServiceId = "1";
String? orgId;
String? userId;
Map<String, dynamic>? apiDataForClass;
bool isLoading = true;
// final TextEditingController _costController = TextEditingController();
// final TextEditingController _classController = TextEditingController();
// String? FirstApproverAction;
@ -37,12 +49,17 @@ class PolicyCriteriaState extends State<PolicyCriteria> {
// String? ThirdApproverAction;
// String? SelectedParallelProcess = "3";
final ApiService apiService = ApiService();
Map<String, TextEditingController> costController = {};
Map<String, TextEditingController> classController = {};
// Map<String, TextEditingController> classController = {};
Map<String, String?> classAction = {};
Map<String, String?> FirstApproverAction = {};
Map<String, String?> SecondApproverAction = {};
Map<String, String?> ThirdApproverAction = {};
Map<String, String?> SelectedParallelProcess = {};
Map<String, String> policyDetailsIdMap = {}; // new
Map<String, String> policyIdMap = {}; // new
Map<String, String> validationErrors = {};
@ -72,41 +89,72 @@ class PolicyCriteriaState extends State<PolicyCriteria> {
widget.selectedTabNotifier.addListener(() {
print("selectedTab changed: ${widget.selectedTabNotifier.value}");
fieldForPolicy();
fetchTrainFlightClass();
});
}
void loadinitializeData() async {
orgId = await getOrgId();
userId = await getUserId();
}
void saveCurrentPolicy() {
if (ServiceId != null) {
addOrUpdatePolicy(ServiceId!);
}
}
// To set the data (update)
void loadPolicyDetails(List<Map<String, dynamic>> details) {
for (var item in details) {
final serviceId = item['service_id'].toString();
costController[serviceId] =
TextEditingController(text: item['cost'] ?? '');
classController[serviceId] =
TextEditingController(text: item['class'] ?? '');
// classAction[serviceId] = classAction[serviceId]?.toString();
classAction[serviceId] = item['class']?.toString() ?? '';
FirstApproverAction[serviceId] = item['a1_action']?.toString();
SecondApproverAction[serviceId] = item['a2_action']?.toString();
ThirdApproverAction[serviceId] = item['a3_action']?.toString();
SelectedParallelProcess[serviceId] =
item['parallel_process_from']?.toString() ?? "3";
if (item['policy_details_id'] != null) {
policyDetailsIdMap[serviceId] = item['policy_details_id'].toString();
}
if (item['policy_id'] != null) {
policyIdMap[serviceId] = item['policy_id'].toString();
}
}
policyData = details;
widget.onPolicyDataChanged(policyData);
if (details.isNotEmpty) {
final firstServiceId = details.first['service_id'].toString();
print('First Service ID is: $firstServiceId');
// firstServiceId == "1"
// ? fetchTrainFlightClass(1)
// : fetchTrainFlightClass(2);
}
setState(() {});
print("detailsdetails- $details");
}
void addOrUpdatePolicy(String serviceId) {
// 1. First, find existing item if any
final existingIndex =
policyData!.indexWhere((item) => item["service_id"] == serviceId);
Map<String, dynamic> data = {
"service_id": serviceId,
"cost": costController[serviceId]?.text,
"class": classController[serviceId]?.text,
// "class": classController[serviceId]?.text,
"class": classAction[serviceId],
"a1_action": FirstApproverAction[serviceId],
"a2_action": SecondApproverAction[serviceId],
"a3_action": ThirdApproverAction[serviceId],
@ -115,12 +163,15 @@ class PolicyCriteriaState extends State<PolicyCriteria> {
};
final cost = costController[serviceId]?.text ?? "";
final travelClass = classController[serviceId]?.text ?? "";
// final travelClass = classController[serviceId]?.text ?? "";
final travelClass = classAction[serviceId];
final a1 = FirstApproverAction[serviceId];
final a2 = SecondApproverAction[serviceId];
final a3 = ThirdApproverAction[serviceId];
bool hasValue = cost.trim().isNotEmpty || travelClass.trim().isNotEmpty;
bool hasValue = cost.trim().isNotEmpty;
// bool hasValue = cost.trim().isNotEmpty || travelClass.trim().isNotEmpty;
bool allActionsNull = a1 == null && a2 == null && a3 == null;
bool someActionsMissing = [a1, a2, a3].where((a) => a != null).length > 0 &&
[a1, a2, a3].where((a) => a == null).length > 0;
@ -137,19 +188,103 @@ class PolicyCriteriaState extends State<PolicyCriteria> {
validationErrors.remove(serviceId);
int index =
policyData!.indexWhere((item) => item["service_id"] == serviceId);
if (index != -1) {
policyData![index] = data; // Replace existing entry
// 2. If updating an existing item, also **keep its IDs**
if (existingIndex != -1) {
final existingItem = policyData![existingIndex];
if (existingItem.containsKey("policy_details_id")) {
data["policy_details_id"] = existingItem["policy_details_id"];
}
if (existingItem.containsKey("policy_id")) {
data["policy_id"] = existingItem["policy_id"];
}
// 3. Now update it
policyData![existingIndex] = data;
print("🔁 Updated policy for ServiceId: $serviceId");
} else {
policyData!.add(data); // Add new entry
// 4. Else add as new
policyData!.add(data);
print(" Added policy for ServiceId: $serviceId");
}
// int index =
// policyData!.indexWhere((item) => item["service_id"] == serviceId);
// if (index != -1) {
// policyData![index] = data; // Replace existing entry
// print("🔁 Updated policy for ServiceId: $serviceId");
// } else {
// policyData!.add(data); // Add new entry
// print(" Added policy for ServiceId: $serviceId");
// }
print("📋 policyData - $policyData");
}
// Future<void> fetchTrainFlightClass(int tripId) async {
// // final String apiUrldata = '$apiUrl/api/getDropdownMaster';
//
// // final String apiUrldata =
// // '$apiUrl/api/getFlightAndTrainClass?user_id=${widget.userId}&trip_type=tripId';
// final String apiUrldata = await apiService.fetchMasterDropdown();
//
// final token = await getToken();
//
// if (token == null) {
// throw Exception('Token not found. Please log in.');
// }
//
// final response = await http.get(
// Uri.parse(apiUrldata),
// headers: {
// 'Authorization': 'Bearer $token',
// 'Content-Type': 'application/json',
// },
// );
//
// if (response.statusCode == 200) {
// try {
// final data = json.decode(response.body);
// print(data);
//
// if (!data.containsKey('data') || data['data'] is! Map) {
// throw Exception(
// "Invalid response format: 'data' field is missing or not a Map");
// }
//
// Map<String, dynamic> plansJson =
// data['data']; // 'data' is a Map, not a List
// setState(() {
// apiDataForClass = plansJson; // Store API response in state
// isLoading = false;
// });
// } catch (e) {
// throw Exception('Error parsing response: $e');
// }
// } else {
// throw Exception('Failed to load plans');
// }
// }
Future<void> fetchTrainFlightClass() async {
try {
final data = await apiService.fetchMasterDropdown();
print("fetchTrainFlightClass - $data");
if (data is! Map) {
throw Exception("Invalid response format: expected a Map");
}
setState(() {
apiDataForClass = data; // Store API response in state
isLoading = false;
});
} catch (e) {
print('Error fetching role list: $e');
}
}
void fieldForPolicy() {
print("fieldForPolicy - ${widget.selectedTabNotifier.value}");
@ -162,7 +297,8 @@ class PolicyCriteriaState extends State<PolicyCriteria> {
ServiceId = widget.selectedTabNotifier.value ?? "1";
// Initialize controllers and variables if not present
costController.putIfAbsent(ServiceId!, () => TextEditingController());
classController.putIfAbsent(ServiceId!, () => TextEditingController());
classAction.putIfAbsent(ServiceId!, () => null);
// classController.putIfAbsent(ServiceId!, () => TextEditingController());
FirstApproverAction.putIfAbsent(ServiceId!, () => null);
SecondApproverAction.putIfAbsent(ServiceId!, () => null);
@ -210,7 +346,7 @@ class PolicyCriteriaState extends State<PolicyCriteria> {
? Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
if (widget.isClass!)
if (widget.isClass)
buildClassWidget(widget.isDesktop),
SizedBox(height: 10),
if (widget.isCost!)
@ -372,6 +508,7 @@ class PolicyCriteriaState extends State<PolicyCriteria> {
items: [
"Approval",
"Notification",
"None"
],
dropdownDecoratorProps:
DropDownDecoratorProps(
@ -500,6 +637,7 @@ class PolicyCriteriaState extends State<PolicyCriteria> {
items: [
"Approval",
"Notification",
"None"
],
dropdownDecoratorProps:
DropDownDecoratorProps(
@ -633,6 +771,7 @@ class PolicyCriteriaState extends State<PolicyCriteria> {
items: [
"Approval",
"Notification",
"None"
],
dropdownDecoratorProps:
DropDownDecoratorProps(
@ -740,6 +879,58 @@ class PolicyCriteriaState extends State<PolicyCriteria> {
}
Widget buildClassWidget(isDesktop) {
List<dynamic> purposeList = [];
if (ServiceId != null) {
purposeList = (ServiceId == "1")
? (apiDataForClass?['flight_class'] ?? [])
: (apiDataForClass?['train_class'] ?? []);
}
print("CLASS1 - ");
List<DropdownMenuItem<String>> dropdownItems = purposeList
.map((item) => DropdownMenuItem<String>(
value: item['dropdown_key'],
child: Text(item['dropdown_value']),
))
.toList();
print("CLASS 2 - ");
if (dropdownItems.isEmpty) {
dropdownItems.add(
DropdownMenuItem<String>(
value: null,
child: Text("No options available",
style: TextStyle(color: Colors.grey)),
),
);
}
// if (ServiceId != null) {
// if (classAction[ServiceId!] == null && dropdownItems.isNotEmpty) {
// classAction[ServiceId!] = dropdownItems.first.value!;
// }
// }
// if (ServiceId != null &&
// classAction[ServiceId!] == null &&
// dropdownItems.isNotEmpty) {
// classAction[ServiceId!] = dropdownItems.first.value!;
// }
// 1. First, when dropdownItems are ready, assign default value:
if (ServiceId != null &&
classAction[ServiceId!] == null &&
dropdownItems.isNotEmpty) {
// Important to wrap inside setState if async
WidgetsBinding.instance.addPostFrameCallback((_) {
setState(() {
classAction[ServiceId!] = dropdownItems.first.value!;
});
});
}
return Row(
children: [
Column(
@ -754,21 +945,48 @@ class PolicyCriteriaState extends State<PolicyCriteria> {
CustomTextFieldUserWrapper(
isFocused: false,
isDesktop: widget.isDesktop,
// child: SizedBox(
// height: 40,
// child: TextField(
// style: TextStyle(fontSize: 12),
// // controller: classController[ServiceId],
// // enabled: !isViewMode,
// onChanged: (value) {},
// decoration: InputDecoration(
// labelText: "Class",
// labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
// floatingLabelBehavior: FloatingLabelBehavior.never,
// border: InputBorder.none,
// contentPadding: EdgeInsets.symmetric(vertical: 16),
// ),
// ),
// ),
child: SizedBox(
height: 40,
child: TextField(
style: TextStyle(fontSize: 12),
controller: classController[ServiceId],
// enabled: !isViewMode,
onChanged: (value) {},
decoration: InputDecoration(
labelText: "Class",
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16),
),
),
child: ServiceId != null && (isLoading || dropdownItems.isEmpty)
? const Center(child: CircularProgressIndicator())
: DropdownButtonFormField<String>(
// Assign the correct focus node
// controller: _hotelNameController,
value: classAction[ServiceId],
style: TextStyle(fontSize: 12),
decoration: InputDecoration(
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(
horizontal: 10), // Proper padding
),
onChanged: (newValue) {
if (ServiceId != null && newValue != null) {
setState(() {
classAction[ServiceId!] = newValue;
});
}
},
items: dropdownItems,
),
),
),
],

View File

@ -52,6 +52,14 @@ class _PolicyListState extends State<PolicyList> {
Future<void> loadAllGroups() async {
try {
final result = await apiService.fetchAllPolicy();
// Sort by policy_id descending (latest first)
result.sort((a, b) {
int idA = int.tryParse(a['policy_id'].toString()) ?? 0;
int idB = int.tryParse(b['policy_id'].toString()) ?? 0;
return idB.compareTo(idA); // latest first
});
setState(() {
apiAllGroups = result;
});

View File

@ -24,11 +24,24 @@ class _UserListScreenState extends State<UserListScreen> {
Color? layoutColor;
Color? bodyColor;
List allUsers = [];
List filteredUsers = [];
TextEditingController searchController = TextEditingController();
int currentPage = 0;
int itemsPerPage = 8;
@override
void initState() {
super.initState();
futureUsers = fetchUsers();
futureUsers.then((users) {
setState(() {
allUsers = users;
});
});
WidgetsBinding.instance.addPostFrameCallback((_) {
fetchCountryList();
loadInitialData();
@ -57,6 +70,35 @@ class _UserListScreenState extends State<UserListScreen> {
return prefs.getString('auth_token');
}
// Future<List<dynamic>> fetchSingleUsers(userId) async {
// orgId = await getOrgId();
// final String apiUrlData = '$apiUrl/api/users/find/$userId';
//
// final String? token = await getToken();
//
// print("Fetch Users");
// print("TOEKRWE: $token");
//
// if (token == null) {
// throw Exception('Token not found. Please log in.');
// }
//
// final response = await http.get(
// Uri.parse(apiUrlData),
// headers: {
// 'Authorization': 'Bearer $token',
// 'Content-Type': 'application/json',
// },
// );
//
// if (response.statusCode == 200) {
// final data = json.decode(response.body);
// return data['data']; // Returning raw JSON list
// } else {
// throw Exception('Failed to load users');
// }
// }
Future<List<dynamic>> fetchUsers() async {
orgId = await getOrgId();
final String apiUrlData = '$apiUrl/api/users?org_id=$orgId';
@ -236,9 +278,25 @@ class _UserListScreenState extends State<UserListScreen> {
void refreshUserList() {
setState(() {
futureUsers = fetchUsers(); // Re-fetch users after status update
// Wait for futurePlans to be fetched and update allPlans
});
}
void filterUsers(String query) {
print("allUsers before filtering: $query");
final lowerQuery = query.toLowerCase();
setState(() {
filteredUsers = allUsers.where((user) {
return (user['first_name']?.toLowerCase().contains(lowerQuery) ??
false) ||
(user['last_name']?.toLowerCase().contains(lowerQuery) ?? false) ||
(user['email']?.toLowerCase().contains(lowerQuery) ?? false) ||
(user['role_value']?.toLowerCase().contains(lowerQuery) ?? false);
}).toList();
});
print("filteredPlans: $filteredUsers");
}
@override
Widget build(BuildContext context) {
return ResponsiveBuilder(builder: (context, sizingInfo) {
@ -294,19 +352,7 @@ class _UserListScreenState extends State<UserListScreen> {
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(10.0),
child: Container(
@ -326,7 +372,7 @@ class _UserListScreenState extends State<UserListScreen> {
children: [
const Text('User List',
style: TextStyle(
fontFamily: "Archivo",
fontFamily: "Inter",
fontSize: 16,
fontWeight: FontWeight.w600,
color: Color(0xFF212121))),
@ -334,29 +380,38 @@ class _UserListScreenState extends State<UserListScreen> {
),
Spacer(),
Container(
width: MediaQuery.of(context).size.width * 0.3,
// or use Flexible
width: MediaQuery.of(context).size.width * 0.2,
height: 40,
child: TextField(
onChanged: (query) {},
controller: searchController,
onChanged: filterUsers,
decoration: InputDecoration(
hintText: "Search for a plan",
hintText: "Search for a User",
hintStyle: TextStyle(
fontSize: 14, color: Color(0xFF9E9DBD)),
prefixIcon:
Icon(Icons.search, color: Color(0xFF9E9DBD)),
fontSize: 12, color: Color(0xFF9E9DBD)),
prefixIcon: Icon(
Icons.search,
color: Color(0xFF9E9DBD),
size: 18,
),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
borderRadius: BorderRadius.circular(12),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(
color: Colors.grey.shade300, width: 0.5),
color: Colors.grey.shade200, width: 0.5),
),
focusedBorder: OutlineInputBorder(
// borderRadius: BorderRadius.circular(8),
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(
color: Colors.blueAccent, width: 1),
color: Colors.grey.shade300, width: 1),
),
),
style: TextStyle(
fontSize: 12,
fontFamily: "Inter",
),
),
),
// SizedBox(width: 16),
@ -451,9 +506,21 @@ class _UserListScreenState extends State<UserListScreen> {
);
}
List<dynamic> users = snapshot.data!;
// users.sort((a, b) =>
// int.parse(b.planId).compareTo(int.parse(a.planId)));
List<dynamic> users =
filteredUsers.isNotEmpty ? filteredUsers : allUsers;
users.sort((a, b) {
DateTime dateA = DateTime.parse(a['created_on']);
DateTime dateB = DateTime.parse(b['created_on']);
return dateB
.compareTo(dateA); // Descending: newest first
});
List paginatedUser = users
.skip(currentPage * itemsPerPage)
.take(itemsPerPage)
.toList();
Widget table = LayoutBuilder(
builder: (context, constraints) {
@ -474,36 +541,36 @@ class _UserListScreenState extends State<UserListScreen> {
label: Text('UserName',
style: TextStyle(
// color: Color(0xFF9E9DBD),
color: Color(0xFF9E9DBD),
fontSize: 14,
fontFamily: "Archivo",
fontWeight: FontWeight.w500))),
// color: Color(0xFF9E9DBD),
fontSize: 13,
fontFamily: "Inter",
fontWeight: FontWeight.w600))),
DataColumn(
label: Text('Email Id',
style: TextStyle(
color: Color(0xFF9E9DBD),
fontFamily: "Archivo",
fontWeight: FontWeight.bold))),
fontSize: 13,
fontFamily: "Inter",
fontWeight: FontWeight.w600))),
DataColumn(
label: Text('Role',
style: TextStyle(
color: Color(0xFF9E9DBD),
fontFamily: "Archivo",
fontWeight: FontWeight.bold))),
fontSize: 13,
fontFamily: "Inter",
fontWeight: FontWeight.w600))),
DataColumn(
label: Text('Status',
style: TextStyle(
color: Color(0xFF9E9DBD),
fontFamily: "Archivo",
fontWeight: FontWeight.bold))),
fontSize: 13,
fontFamily: "Inter",
fontWeight: FontWeight.w600))),
DataColumn(
label: Text('Actions',
style: TextStyle(
color: Color(0xFF9E9DBD),
fontFamily: "Archivo",
fontWeight: FontWeight.bold))),
fontSize: 13,
fontFamily: "Inter",
fontWeight: FontWeight.w600))),
],
rows: users.map((user) {
rows: paginatedUser.map((user) {
String userId =
user['user_id'].toString(); // Get user ID
bool isSelected = selectedUserId == userId;
@ -513,17 +580,17 @@ class _UserListScreenState extends State<UserListScreen> {
"${user['first_name'] ?? ''} ${user['last_name'] ?? ''}",
style: TextStyle(
fontSize: 13,
fontFamily: "Archivo",
fontFamily: "Inter",
))),
DataCell(Text(user['email'] ?? '',
style: TextStyle(
fontSize: 13,
fontFamily: "Archivo",
fontFamily: "Inter",
))),
DataCell(Text(user['role_id'] ?? 'N/A',
DataCell(Text(user['role_value'] ?? 'N/A',
style: TextStyle(
fontSize: 13,
fontFamily: "Archivo",
fontFamily: "Inter",
),
softWrap: true,
overflow: TextOverflow.ellipsis)),
@ -540,6 +607,7 @@ class _UserListScreenState extends State<UserListScreen> {
color: user['is_active'] == "1"
? Colors.green
: Colors.grey,
fontFamily: "Inter",
fontWeight: FontWeight.w400),
))),
DataCell(
@ -640,463 +708,70 @@ class _UserListScreenState extends State<UserListScreen> {
);
return Expanded(
child: isDesktop
? table
: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: SingleChildScrollView(
scrollDirection: Axis.vertical,
child: table,
child: Column(
children: [
isDesktop
? table
: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: SingleChildScrollView(
scrollDirection: Axis.vertical,
child: table,
),
),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
// Previous button with arrow icon
IconButton(
icon: Icon(
Icons.arrow_back_ios_new,
size: 10,
),
onPressed: currentPage > 0
? () {
setState(() {
currentPage--;
});
}
: null,
),
),
SizedBox(width: 2),
// Page number text
Text(
'Page ${currentPage + 1}',
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w400,
color: Colors.black87,
fontFamily: "Inter",
),
),
SizedBox(width: 2),
// Next button with arrow icon
IconButton(
icon: Icon(
Icons.arrow_forward_ios,
size: 10,
),
onPressed: (currentPage + 1) * itemsPerPage <
users.length
? () {
setState(() {
currentPage++;
});
}
: null,
),
],
),
],
),
);
},
)
// FutureBuilder<List<dynamic>>(
// future: futureUsers,
// builder: (context, snapshot) {
// if (snapshot.connectionState == ConnectionState.waiting) {
// return Center(child: CircularProgressIndicator());
// } else if (snapshot.hasError) {
// return Center(
// child: Padding(
// padding: const EdgeInsets.all(16.0),
// child: Column(
// mainAxisSize: MainAxisSize.min,
// children: [
// Icon(
// Icons.error_outline,
// color: Colors.redAccent,
// size: 60,
// ),
// SizedBox(height: 16),
// Text(
// "Oops!",
// style: TextStyle(
// fontSize: 22,
// fontWeight: FontWeight.bold,
// color: Colors.redAccent,
// ),
// ),
// SizedBox(height: 8),
// Text(
// "No User Available",
// textAlign: TextAlign.center,
// style: TextStyle(
// fontSize: 20,
// fontWeight: FontWeight.bold,
// color: Colors.grey,
// ),
// ),
// SizedBox(height: 20),
// Text(
// " Please Create NewUser",
// textAlign: TextAlign.center,
// style: TextStyle(
// fontSize: 16,
// color: Colors.grey[700],
// ),
// ),
// SizedBox(height: 20),
// // ElevatedButton.icon(
// // onPressed: () {
// // // Optional: retry logic or navigation
// // },
// // icon: Icon(Icons.refresh),
// // label: Text("Try Again"),
// // style: ElevatedButton.styleFrom(
// // backgroundColor: Colors.blueAccent,
// // ),
// // ),
// ],
// ),
// ),
// );
// } else if (!snapshot.hasData || snapshot.data!.isEmpty) {
// return Center(child: Text("No users found"));
// }
//
// List<dynamic> users = snapshot.data!;
// Color borderColor = Color(0xFF9E9DBD);
//
// return Expanded(
// child: SingleChildScrollView(
// scrollDirection: Axis.vertical,
// child: SizedBox(
// width: MediaQuery.of(context).size.width * 1.5,
// child: SingleChildScrollView(
// scrollDirection: Axis
// .horizontal, // Inner wrapper for vertical scrolling
//
// child: ConstrainedBox(
// constraints: BoxConstraints(
// minWidth:
// MediaQuery.of(context).size.width *
// 0.8),
// // constraints: BoxConstraints(minWidth: 1300),
// // width: MediaQuery.of(context).size.width ,
//
// child: Container(
// // color: Colors.grey,
// // color: Colors.amber,
// child: DataTable(
// columnSpacing:
// 20.0, // Adjust spacing between columns
// dividerThickness: 0.5,
// dataRowMinHeight:
// 60.0, // Minimum row height
// dataRowMaxHeight: 100.0,
// border: TableBorder(
// horizontalInside: BorderSide(
// width: 0.5,
// color: Colors.grey.shade200),
// ),
// columns: const [
// DataColumn(
// label: Text(
// 'User Details',
// style: TextStyle(
// color: Color(0xFF9E9DBD),
// fontSize: 15,
// fontWeight: FontWeight.bold),
// )),
// DataColumn(
// label: Text(
// 'Role',
// style: TextStyle(
// color: Color(0xFF9E9DBD),
// fontSize: 15,
// fontWeight: FontWeight.bold),
// )),
// DataColumn(
// label: Text(
// 'Level',
// style: TextStyle(
// color: Color(0xFF9E9DBD),
// fontSize: 15,
// fontWeight: FontWeight.bold),
// )),
// DataColumn(
// label: Text(
// 'Status',
// style: TextStyle(
// color: Color(0xFF9E9DBD),
// fontSize: 15,
// fontWeight: FontWeight.bold),
// )),
// DataColumn(
// label: Text(
// 'Actions',
// style: TextStyle(
// color: Color(0xFF9E9DBD),
// fontSize: 15,
// fontWeight: FontWeight.bold),
// )),
// ],
//
// rows: users.map((user) {
// String userId = user['user_id']
// .toString(); // Get user ID
// bool isSelected =
// selectedUserId == userId;
//
// return DataRow(cells: [
// // DataCell(Text(user['user_id'].toString())),
// DataCell(Row(
// mainAxisAlignment:
// MainAxisAlignment.start,
// crossAxisAlignment:
// CrossAxisAlignment.start,
// children: [
// Align(
// alignment: Alignment.center,
// child: GestureDetector(
// onTap: () {
// setState(() {
// selectedUserId =
// userId; // Store clicked user ID
// });
// },
// child: Container(
// width: 15, // Adjust size
// height: 15,
// decoration: BoxDecoration(
// // Background color
// shape: BoxShape.rectangle,
// border: Border.all(
// color: isSelected
// ? Colors.blueAccent
// : Color(0xFF9E9DBD),
// // color: Color(0xFF9E9DBD),
// // color: Color.fromRGBO(128, 128, 128, 0.6),
// width: isSelected
// ? 2
// : 1), // Grey outline
// ),
// ),
// ),
// ),
// SizedBox(
// width: 50,
// ),
// Align(
// alignment: Alignment.center,
// child: Container(
// decoration: BoxDecoration(
// shape: BoxShape.circle,
// border: Border.all(
// color:
// Color(0xFF9E9DBD),
// width:
// 1), // Grey outline
// ),
// child: Padding(
// padding:
// const EdgeInsets.all(
// 2.0),
// child: Container(
// width: 40, // Adjust size
// height: 40,
// decoration: BoxDecoration(
// color: Colors
// .amber, // Inner circle background
// shape: BoxShape.circle,
// ),
// child: Column(
// mainAxisAlignment:
// MainAxisAlignment
// .center,
// crossAxisAlignment:
// CrossAxisAlignment
// .center,
// children: [
// Text(
// (user['first_name'] !=
// null &&
// user['first_name']!
// .isNotEmpty)
// ? user['first_name']![
// 0]
// .toUpperCase()
// : "?",
// style: TextStyle(
// fontSize: 18,
// fontWeight:
// FontWeight
// .bold,
// color:
// Colors.white,
// ),
// ),
// ],
// ),
// ),
// )),
// ),
// SizedBox(
// width: 20,
// ),
// Column(
// mainAxisAlignment:
// MainAxisAlignment.center,
// crossAxisAlignment:
// CrossAxisAlignment.start,
// children: [
// Row(
// children: [
// Text(
// "${user['first_name'] ?? ''} ${user['last_name'] ?? ''}",
// style: TextStyle(
// color:
// Colors.blueAccent,
// fontSize: 16),
// ),
// ],
// ),
// SizedBox(height: 5),
// Row(
// children: [
// Icon(Icons.mail_outline,
// size: 15,
// color:
// Color(0xFF9E9EBE)),
// SizedBox(
// width: 10,
// ),
// Text(user['email'] ?? '')
// ],
// ),
// SizedBox(height: 5),
// Row(
// children: [
// Icon(
// Icons
// .account_tree_outlined,
// size: 15,
// color:
// Color(0xFF9E9EBE)),
// SizedBox(
// width: 10,
// ),
// Text(
// user['user_type'] ?? '')
// ],
// ),
// ],
// ),
// ],
// )),
//
// DataCell(Text(
// user['role_id'] ?? 'N/A',
// style: TextStyle(
// color: user['is_active'] == "1"
// ? Colors.black
// : Colors.grey,
// fontWeight: FontWeight.bold),
// )),
// DataCell(Text(
// user['level_id'] ?? 'N/A',
// style: TextStyle(
// color: user['is_active'] == "1"
// ? Colors.black
// : Colors.grey,
// fontWeight: FontWeight.bold),
// )),
// DataCell(GestureDetector(
// onTap: () {
// handleToggleUserStatus(
// user['user_id'],
// user['is_active'],
// user);
// },
// child: Text(
// user['is_active'] == "1"
// ? "Active"
// : "Inactive",
// style: TextStyle(
// color:
// user['is_active'] == "1"
// ? Colors.lightGreen
// : Colors.grey,
// fontWeight: FontWeight.bold),
// ))),
//
// DataCell(
// Row(
// children: [
// MouseRegion(
// cursor: user['is_active'] == "0"
// ? SystemMouseCursors
// .forbidden
// : SystemMouseCursors.click,
// child: IconButton(
// icon: Icon(
// Icons.remove_red_eye,
// size: 18,
// color:
// user['is_active'] ==
// "0"
// ? Colors.grey
// : Color(
// 0xFF475569)),
// onPressed:
// user['is_active'] == "0"
// ? null
// : () {
// context.go(
// "/CreateUserDetails",
// extra: {
// "selectedUser":
// user,
// "isViewMode":
// true
// },
// );
// },
// ),
// ),
//
// MouseRegion(
// cursor: user['is_active'] == "0"
// ? SystemMouseCursors
// .forbidden
// : SystemMouseCursors.click,
// child: GestureDetector(
// onTap:
// user['is_active'] == "0"
// ? null
// : () {
// context.go(
// "/CreateUserDetails",
// extra: {
// "selectedUser":
// user,
// "isViewMode":
// false
// },
// );
// },
// child: Image.asset(
// 'assets/images/IconsImg/edit.png',
// width: 20,
// height: 15),
// ),
// ),
//
// // MouseRegion(
// // cursor: user['is_active'] == "0"
// // ? SystemMouseCursors
// // .forbidden
// // : SystemMouseCursors.click,
// // child: IconButton(
// // icon: Icon(Icons.edit,
// // color:
// // user['is_active'] ==
// // "0"
// // ? Colors.grey
// // : Colors.green),
// // onPressed:
// // user['is_active'] == "0"
// // ? null
// // : () {
// // print(
// // "USER: $user");
// //
// // // final userJson = jsonEncode(
// // // user); // Convert user map to string
// // // final encodedUser =
// // // Uri.encodeComponent(
// // // userJson);
// //
// // context.go(
// // "/CreateUserDetails",
// // extra: {
// // "selectedUser":
// // user,
// // "isViewMode":
// // false
// // },
// // );
// // },
// // ),
// // ),
// ],
// ),
// ),
// ]);
// }).toList(),
// ),
// ),
// ),
// ),
// ),
// ),
// );
// },
// ),
//
]),
)),
);

View File

@ -1,5 +1,6 @@
class Plan {
final String planId;
final String? employeeCode;
final String tripTitle;
final String tripType;
final String status;
@ -17,6 +18,7 @@ class Plan {
Plan({
required this.planId,
this.employeeCode,
required this.tripTitle,
required this.tripType,
required this.status,
@ -33,8 +35,10 @@ class Plan {
});
factory Plan.fromJson(Map<String, dynamic> json) {
print('Parsing Plan from JSON: $json');
return Plan(
planId: json['plan_id'],
employeeCode: json['employee_code'],
tripTitle: json['trip_title'],
tripType: json['trip_type_value'],
status: json['status'] == "0" ? "Inactive" : "Active",

View File

@ -38,6 +38,8 @@ class _CustomAppBarState extends State<CustomAppBar> {
Color? bodyColor;
Color _myTravelRequestColor = Color(0xFF475569); // Default color
EdgeInsets _myTravelRequestPadding =
EdgeInsets.symmetric(horizontal: 8, vertical: 4);
Color _myApprovalsColor = Color(0xFF475569); // Default color
void initState() {
@ -183,45 +185,88 @@ class _CustomAppBarState extends State<CustomAppBar> {
),
),
),
const SizedBox(width: 8),
SizedBox(
width: MediaQuery.of(context).size.width * 0.25,
),
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(width: 15),
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),
MouseRegion(
cursor: SystemMouseCursors.click,
onEnter: (_) {
setState(() {
_myTravelRequestColor =
Colors.blue; // Change color on hover
// _myTravelRequestPadding =
// EdgeInsets.symmetric(horizontal: 12, vertical: 78);
// _myTravelRequestColor =
// Color(0xFF114D8B); // Change color on hover
});
},
onExit: (_) {
setState(() {
_myTravelRequestColor =
Color(0xFF475569); // Revert color when hover ends
// _myTravelRequestPadding = EdgeInsets.symmetric(
// horizontal: 8, vertical: 4); // Normal padding
// _myTravelRequestColor =
// Color(0xFF475569); // Revert color when hover ends
});
},
child: GestureDetector(
child: InkWell(
onTap: () {
context.go('/listPlan');
},
child: Text(
"My Travel Request",
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
// color: Color(0xFF475569),
color: _myTravelRequestColor,
fontFamily: "Archivo"),
fontSize: 12,
fontWeight: FontWeight.w600,
// color: Color(0xFF475569),
color: _myTravelRequestColor,
fontFamily: "Inter",
),
)),
),
const SizedBox(width: 25),
const SizedBox(width: 20),
MouseRegion(
cursor: SystemMouseCursors.click,
onEnter: (_) {
setState(() {
_myApprovalsColor = Colors.blue; // Change color on hover
_myApprovalsColor =
Color(0xFF114D8B); // Change color on hover
});
},
onExit: (_) {
@ -230,18 +275,19 @@ class _CustomAppBarState extends State<CustomAppBar> {
Color(0xFF475569); // Revert color when hover ends
});
},
child: GestureDetector(
child: InkWell(
onTap: () {
context.go('/ApprovalList');
},
child: Text(
"My Approvals",
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
color: _myApprovalsColor,
// color: Color(0xFF475569),
fontFamily: "Archivo"),
fontSize: 12,
fontWeight: FontWeight.w600,
color: _myApprovalsColor,
// color: Color(0xFF475569),
fontFamily: "Inter",
),
)),
),
],

View File

@ -14,6 +14,7 @@ import 'package:frontend/Screens/userManagement/create_user/create_user.dart';
import 'package:frontend/Screens/userManagement/user_List.dart';
import 'package:go_router/go_router.dart';
import '../Screens/allTrips/list_all_plans.dart';
import '../Screens/approvals/approval_list.dart';
import '../Screens/group/group.dart';
import '../Screens/group/groupList.dart';
@ -28,6 +29,10 @@ final GoRouter router = GoRouter(
path: '/home',
builder: (context, state) => HomePage(),
),
GoRoute(
path: '/listAllPlan',
builder: (context, state) => ListAllPlans(),
),
GoRoute(
path: '/listPlan',
builder: (context, state) => ListPlans(),

View File

@ -1,8 +1,11 @@
import 'dart:convert';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:frontend/utils/auth_utils.dart';
import 'package:go_router/go_router.dart';
import 'package:http/http.dart' as http;
import 'package:universal_html/html.dart' as html;
import 'package:universal_html/js.dart';
import '../../config/apiUrl.dart';
class ApiService {
@ -486,4 +489,102 @@ class ApiService {
throw Exception('Failed to load country list');
}
}
Future<List<dynamic>> fetchTrainCountryList() async {
final String apiUrldata = '$apiUrl/api/getTrainCodeMaster';
final token = await getToken();
if (token == null) {
throw Exception('Token not found. Please log in.');
}
final response = await http.get(
Uri.parse(apiUrldata),
headers: {
'Authorization': 'Bearer $token',
'Content-Type': 'application/json',
},
);
if (response.statusCode == 200) {
try {
final data = json.decode(response.body);
print("Country - $data");
if (!data.containsKey('data') || data['data'] is! List) {
throw Exception(
"Invalid response format: 'data' field is missing or not a List");
}
return data['data'];
} catch (e) {
throw Exception('Error parsing response: $e');
}
} else {
throw Exception('Failed to load country list');
}
}
Future<void> getPdfDownload(planId) async {
final String apiUrldata = '$apiUrl/api/plans/download?plan_id=$planId';
// final String apiUrldata = '$apiUrl/auth/googlelogin';
final token = await getToken();
if (token == null) {
throw Exception('Token not found. Please log in.');
}
final response = await http.get(
Uri.parse(apiUrldata),
headers: {
'Authorization': 'Bearer $token',
'Content-Type': 'application/json',
},
);
if (response.statusCode == 200) {
try {
print("PDf Dowloaded");
// Create a blob from the response body
final blob = html.Blob([response.bodyBytes]);
// Generate a download URL for the blob
final url = html.Url.createObjectUrlFromBlob(blob);
// Create a link element to trigger the download
final anchor = html.AnchorElement(href: url)
..setAttribute('download', 'trip_plan_$planId.pdf')
..click();
// Revoke the download URL to free up resources
html.Url.revokeObjectUrl(url);
} catch (e) {
throw Exception('Error parsing response: $e');
}
} else if (response.statusCode == 404) {
// showDialog(
// context: context,
// builder: (BuildContext context) {
// return AlertDialog(
// title: Text('File not found.'),
// // content: Text('File not found.'),
// actions: [
// TextButton(
// child: Text('OK'),
// onPressed: () {
// Navigator.of(context).pop(); // Close the dialog
// },
// ),
// ],
// );
// },
// );
} else {
throw Exception('Failed to load plans');
}
}
}

View File

@ -47,6 +47,36 @@ Future<String?> getOrgId() async {
return null;
}
Future<String?> getRoleUser() 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["role"]?.toString();
} catch (e) {
return null;
}
}
return null;
}
Future<String?> getForexCardNumber() 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["forex_pre_paid_card_number"]?.toString();
} catch (e) {
return null;
}
}
return null;
}
Future<String?> getTripPlanAction() async {
final prefs = await SharedPreferences.getInstance();
final String? userDataString = prefs.getString('user_data');

View File

@ -41,6 +41,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.4.0"
charcode:
dependency: transitive
description:
name: charcode
sha256: fb0f1107cac15a5ea6ef0a6ef71a807b9e4267c713bb93e00e92d737cc8dbd8a
url: "https://pub.dev"
source: hosted
version: "1.4.0"
clock:
dependency: transitive
description:
@ -73,6 +81,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "3.0.6"
csslib:
dependency: transitive
description:
name: csslib
sha256: "09bad715f418841f976c77db72d5398dc1253c21fb9c0c7f0b0b985860b2d58e"
url: "https://pub.dev"
source: hosted
version: "1.0.2"
cupertino_icons:
dependency: "direct main"
description:
@ -200,6 +216,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "14.8.1"
html:
dependency: transitive
description:
name: html
sha256: "6d1264f2dffa1b1101c25a91dff0dc2daee4c18e87cd8538729773c073dbf602"
url: "https://pub.dev"
source: hosted
version: "0.15.6"
http:
dependency: "direct main"
description:
@ -565,6 +589,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.4.0"
universal_html:
dependency: "direct main"
description:
name: universal_html
sha256: "56536254004e24d9d8cfdb7dbbf09b74cf8df96729f38a2f5c238163e3d58971"
url: "https://pub.dev"
source: hosted
version: "2.2.4"
universal_io:
dependency: transitive
description:
name: universal_io
sha256: "1722b2dcc462b4b2f3ee7d188dad008b6eb4c40bbd03a3de451d82c78bba9aad"
url: "https://pub.dev"
source: hosted
version: "2.2.2"
vector_math:
dependency: transitive
description:

View File

@ -46,6 +46,7 @@ dependencies:
http_parser: ^4.1.2
image_picker: ^1.1.2
web: ^1.1.0
universal_html: ^2.2.4
dev_dependencies:
@ -76,6 +77,7 @@ flutter:
- assets/images/login/VectorG.png
- assets/images/IconsImg/delete.png
- assets/images/IconsImg/edit.png
- assets/images/IconsImg/planPdf_icon.png
# To add assets to your application, add an assets section, like this: