ts-tat/lib/Screens/plans/list_plans.dart
2025-05-03 17:59:17 +05:30

841 lines
33 KiB
Dart

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 ListPlans extends StatefulWidget {
const ListPlans({super.key});
@override
_ListPlansState createState() => _ListPlansState();
}
class _ListPlansState extends State<ListPlans> {
final ApiService apiService = ApiService();
int currentPage = 0;
int itemsPerPage = 8;
String? userId;
String? orgId;
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();
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 {
// final String apiUrldata = '$apiUrl/api/plans';
// final String apiUrldata = '$apiUrl/api/plans?user_id=$userId';
// final String apiUrldata = '$apiUrl/api/plans?user_id=$userId';
final String apiUrldata = '$apiUrl/api/plans?org_id=$orgId&user_id=$userId';
// api/plans?org_id=1&user_id=1
// 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', // 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");
}
}
// 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');
// }
// }
//
// void viewPlan(String planId, {bool isViewMode = false}) async {
// try {
// Map<String, dynamic> planData = await getViewPlan(planId);
// print("ViewAAA - $planData");
//
// context.go('/createPlan',
// extra: {'planData': planData, 'isViewMode': isViewMode});
// } catch (e) {
// print("Error fetching 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('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: 15,
height: 15),
),
// IconButton(
// icon: const Icon(Icons.edit,
// color: Colors.green),
// onPressed: () => viewPlan(plan.planId,
// isViewMode: false) ),
SizedBox(
width: 5,
),
GestureDetector(
onTap: () => deletePlan(plan.planId),
child: Image.asset(
'assets/images/IconsImg/delete.png',
width: 20,
height: 15),
),
IconButton(
icon: const Icon(
Icons.download,
color: Color(0xFF475569),
size: 18,
),
onPressed: () {
apiService.getPdfDownload(plan.planId);
}),
])),
]);
}).toList(),
),
);
},
);
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;
}
}