search for all pages
This commit is contained in:
parent
36975e716f
commit
a76464bbbc
BIN
assets/images/IconsImg/planPdf_icon.png
Normal file
BIN
assets/images/IconsImg/planPdf_icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 900 B |
798
lib/Screens/allTrips/list_all_plans.dart
Normal file
798
lib/Screens/allTrips/list_all_plans.dart
Normal 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;
|
||||
}
|
||||
}
|
||||
@ -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,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
)
|
||||
|
||||
@ -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");
|
||||
|
||||
|
||||
@ -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
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@ -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)),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
)
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@ -506,7 +506,7 @@ class _TaxiScreenState extends State<TaxiScreen> {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
"Destination",
|
||||
"City",
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w600,
|
||||
|
||||
@ -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(() {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -279,7 +279,7 @@ class _FlightListWidgetState extends State<FlightListWidget> {
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: Text(
|
||||
"Planned Trips",
|
||||
"Sector",
|
||||
style: TextStyle(fontSize: 11, fontFamily: "Archivo"),
|
||||
)),
|
||||
Expanded(
|
||||
|
||||
@ -231,7 +231,7 @@ class TaxiListWidget extends StatelessWidget {
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: Text(
|
||||
"Destination",
|
||||
"City",
|
||||
style: TextStyle(
|
||||
fontSize: 11, fontFamily: "Archivo"),
|
||||
)),
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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),
|
||||
|
||||
@ -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(() {
|
||||
|
||||
@ -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,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
@ -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;
|
||||
});
|
||||
|
||||
@ -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(),
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
// );
|
||||
// },
|
||||
// ),
|
||||
//
|
||||
]),
|
||||
)),
|
||||
);
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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",
|
||||
),
|
||||
)),
|
||||
),
|
||||
],
|
||||
|
||||
@ -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(),
|
||||
|
||||
@ -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');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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');
|
||||
|
||||
40
pubspec.lock
40
pubspec.lock
@ -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:
|
||||
|
||||
@ -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:
|
||||
|
||||
Loading…
Reference in New Issue
Block a user