ts-tat/lib/Screens/dashboard/status_dashboard_web.dart
2025-10-08 11:37:26 +05:30

935 lines
32 KiB
Dart

import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'dart:html' as html;
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:frontend/routes/custom_router.dart';
import 'package:go_router/go_router.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:http/http.dart' as http;
import 'package:responsive_builder/responsive_builder.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../../config/apiUrl.dart';
import '../../routes/custom_appBar.dart';
import '../../routes/custom_drawer.dart';
import '../../services/apiService.dart';
import '../../utils/auth_utils.dart';
import '../../utils/pagination.dart';
class StatusDashboard extends StatefulWidget {
const StatusDashboard({super.key});
@override
StatusDashboardState createState() => StatusDashboardState();
}
class StatusDashboardState extends State<StatusDashboard> {
Map<String, dynamic>? apiData;
String? organizationId;
late bool _dialogShown = false;
bool loader = false;
late StreamSubscription<html.PopStateEvent> _popStateListener;
String selectedView = 'Today';
String AccessKeyFlag = "Both";
Map<String, dynamic>? currentData;
List typeData = [];
List statusData = [];
List<int> flightData = [];
List<int> accommodationData = [];
List<int> forexData = [];
final ApiService apiService = ApiService();
final categoryIcons = {
"flight": Icons.flight,
"accommodation": Icons.hotel_outlined,
"forex": Icons.attach_money,
};
List<String> statusLabels = [
"Pending Approval",
"Partially Approved",
"Approved",
"Rejected",
"Cancelled",
];
// Extract counts for "Both" section
List<int> getStatusValues(Map<String, dynamic> serviceData) {
final both = serviceData['Both'] ?? {};
return statusLabels.map((label) => (both[label] ?? 0) as int).toList();
}
@override
void initState() {
super.initState();
_checkAuthAndLoadData();
checkbackbutton();
// loadDashboardData();
}
void checkbackbutton() async {
// Push a dummy state so back button triggers popstate instead of navigating
html.window.history.pushState(null, 'home', html.window.location.href);
_popStateListener = html.window.onPopState.listen((event) {
if (!_dialogShown && mounted) {
_showBackConfirmationDialog();
}
// Re-push to prevent leaving
html.window.history.pushState(null, 'home', html.window.location.href);
});
}
@override
void dispose() {
_popStateListener.cancel(); // ✅ Remove the browser popstate listener
super.dispose();
}
void _showBackConfirmationDialog() {
if (!mounted) return;
_dialogShown = true;
showDialog(
context: context,
builder:
(context) => AlertDialog(
title: Text("Confirm"),
content: Text("Do you want to logout?"),
actions: [
TextButton(
onPressed: () {
Navigator.pop(context); // Close dialog
_dialogShown = false;
},
child: Text("Cancel"),
),
TextButton(
onPressed: () async {
// Navigator.pop(context); // Close dialog
_dialogShown = false;
await _logoutAndRedirect(context);
},
child: Text("Logout"),
),
],
),
);
}
Future<void> _logoutAndRedirect(BuildContext context) async {
print("logue 0");
Navigator.pop(context); // closes dialog
_dialogShown = false;
await apiService.logout(context);
// Example: clear session or shared preferences
// final prefs = await SharedPreferences.getInstance();
// await prefs.clear();
// context.go("/");
print("logue 1");
}
void _checkAuthAndLoadData() async {
final String? token = await getToken(); // Your async function to get token
if (token == null || token.isEmpty) {
// Token doesn't exist → redirect to login
context.go(
"/",
); // or use: router.go("/") if you're using `GoRouter` directly
return;
}
WidgetsBinding.instance.addPostFrameCallback((_) {
checkbackbutton();
});
// If token exists, load the dashboard data
loadDashboardData();
}
Future<void> loadDashboardData() async {
try {
final data = await fetchStatusDashboard();
setState(() {
apiData = data;
});
} catch (e) {
print("Error loading dashboard: $e");
}
}
Future<String?> getToken() async {
final prefs = await SharedPreferences.getInstance();
return prefs.getString('auth_token');
}
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<Map<String, dynamic>> fetchStatusDashboard() async {
organizationId = await getOrgId();
final String apiUrlData =
'$apiUrl/api/plans/statusDashboard?org_id=$organizationId';
final String? token = await getToken();
print("Fetch StatusDashboard -- 2KN Here : $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',
},
);
print("called api : $apiUrlData");
if (response.statusCode == 200) {
final apiData = json.decode(response.body);
print("Fetch StatusDashboard -- Reponse Here : $apiData");
return apiData; // Returning raw JSON list
} else if (response.statusCode == 403) {
print("403-FORB");
await apiService.logout(context);
return {};
} else {
throw Exception('Failed to load users');
}
}
Color getStatusColor(String status) {
if (status == "Domestic") return Colors.white;
if (status == "International") return Colors.white;
if (status == "Partially Approved") return Colors.yellow.shade100;
if (status == "Pending Approval") return Colors.greenAccent.shade100;
if (status == "Approved") return Colors.green.shade100;
if (status == "Completed") return Colors.green.shade500;
if (status == "Rejected") return Colors.red.shade100;
if (status == "Cancelled") return Colors.red.shade200;
return Colors.grey.shade100;
}
IconData getStatusIcon(String title) {
switch (title) {
case "Domestic":
return Icons.directions_bus_filled_outlined;
case "International":
return Icons.airplanemode_active;
case "Partially Approved":
return Icons.pending;
case "Approved":
return Icons.check_circle_outline;
case "Completed":
return Icons.done_all;
case "Rejected":
return Icons.cancel_outlined;
case "Cancelled":
return Icons.cancel_schedule_send_outlined;
default:
return Icons.info_outline;
}
}
@override
Widget build(BuildContext context) {
final typeBasedCount = List<Map<String, dynamic>>.from(
(apiData?['data']?['typeBasedCount']) ?? [],
);
print("apiData - => $apiData");
print("typeBasedCount => $typeBasedCount");
final statusBasedCount = List<Map<String, dynamic>>.from(
(apiData?['data']?['statusBasedCount']) ?? [],
);
final statusBasedTodayCount = List<Map<String, dynamic>>.from(
(apiData?['data']?['statusBasedTodayCount']) ?? [],
);
final statusBasedWeeklyCount = List<Map<String, dynamic>>.from(
(apiData?['data']?['statusBasedWeeklyCount']) ?? [],
);
final typeBasedTodayCount = List<Map<String, dynamic>>.from(
(apiData?['data']?['typeBasedTodayCount']) ?? [],
);
final typeBasedWeeklyCount = List<Map<String, dynamic>>.from(
(apiData?['data']?['typeBasedWeeklyCount']) ?? [],
);
final typeTodayBasedCount = List<Map<String, dynamic>>.from(
(apiData?['data']?['typeTodayBasedCount']) ?? [],
);
final typeWeeklyBasedCount = List<Map<String, dynamic>>.from(
(apiData?['data']?['typeWeeklyBasedCount']) ?? [],
);
final statusTodayBasedCount = List<Map<String, dynamic>>.from(
(apiData?['data']?['statusTodayBasedCount']) ?? [],
);
final statusWeeklyBasedCount = List<Map<String, dynamic>>.from(
(apiData?['data']?['statusWeeklyBasedCount']) ?? [],
);
final List todayList = List<Map<String, dynamic>>.from(
apiData?['data']?['todayTripCounts'] ?? [],
);
final List weekList = List<Map<String, dynamic>>.from(
apiData?['data']?['weekTripCounts'] ?? [],
);
// Merge all maps in the list into one map
Map<String, dynamic> todayBasedCount = {};
Map<String, dynamic> weekBasedCount = {};
for (final item in todayList) {
todayBasedCount.addAll(item);
}
for (final item in weekList) {
weekBasedCount.addAll(item);
}
setState(() {
// currentData = selectedView == "Today" ? todayBasedCount : weekBasedCount;
typeData =
selectedView == "Today" ? typeBasedTodayCount : typeBasedWeeklyCount;
statusData =
selectedView == "Today"
? statusBasedTodayCount
: statusBasedWeeklyCount;
flightData =
selectedView == "Today"
? getStatusValues(todayBasedCount['flight'] ?? {})
: getStatusValues(weekBasedCount['flight'] ?? {});
accommodationData =
selectedView == "Today"
? getStatusValues(todayBasedCount['acomodation'] ?? {})
: getStatusValues(weekBasedCount['acomodation'] ?? {});
forexData =
selectedView == "Today"
? getStatusValues(todayBasedCount['forex'] ?? {})
: getStatusValues(weekBasedCount['forex'] ?? {});
});
print("statusBasedCount - => $statusBasedCount");
print("Current Data - => $currentData");
// 👇 Local function to create the card widget
Widget buildInfoCard(String title, int count, double width) {
print('title $title');
final color = getStatusColor(title);
final icon = getStatusIcon(title);
return Card(
elevation: 3,
color: color,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
child: Container(
width: width,
padding: const EdgeInsets.all(12),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
icon,
size: 32,
color:
(title == 'Domestic' || title == 'International')
? Colors.green
: Colors.black54,
), // 👈 Add Icon here
const SizedBox(height: 8),
Text(
title,
textAlign: TextAlign.center,
style: TextStyle(
fontWeight: FontWeight.w600,
fontSize:
(title == 'Domestic' || title == 'International')
? 16
: 14,
color: Colors.black87,
),
),
const SizedBox(height: 8),
Text(
count.toString(),
style: const TextStyle(
fontSize: 22,
fontWeight: FontWeight.bold,
// color: Colors.blue,
),
),
],
),
),
);
}
void _handleBackButton() {
final location = GoRouterState.of(context).uri.toString();
print("location - $location");
if (location.contains('/StatusDashboard')) {
// Do nothing or show "Press again to exit" toast
print("Blocked back on dashboard");
} else {
print("dashboard ..");
}
}
String capitalize(String s) => s[0].toUpperCase() + s.substring(1);
Widget toggleChip(String label) {
bool isSelected = selectedView == label;
return GestureDetector(
onTap: () {
setState(() {
selectedView = label;
});
},
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 20),
decoration: BoxDecoration(
color: isSelected ? const Color(0xFF003A78) : Colors.transparent,
borderRadius: BorderRadius.circular(20),
),
alignment: Alignment.center,
height: 36,
child: Text(
label,
style: GoogleFonts.poppins(
fontSize: 10,
color: isSelected ? Colors.white : Colors.black,
fontWeight: FontWeight.w700,
),
),
),
);
}
Widget buildTopCard({
// required IconData icon,
required Color color,
required String label,
required int count,
required Color bgColor,
}) {
return Container(
padding: const EdgeInsets.all(1),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(1),
),
child: Column(
children: [
Tooltip(
message: label,
child: Image.asset(
label == 'Domestic'
? 'assets/images/IconsImg/Domestic_new.png'
: 'assets/images/IconsImg/International_new.png',
// width: 65,
// height: label == 'Domestic' ? 37 : 40,
),
),
// Icon(icon, color: color, size: 40),
const SizedBox(height: 10),
Text(
count.toString(),
style: GoogleFonts.poppins(
fontSize: 28,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 5),
Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
decoration: BoxDecoration(
color: bgColor,
borderRadius: BorderRadius.circular(20),
),
child: Text(
label,
style: GoogleFonts.poppins(
fontSize: 10,
color: color,
// color: const Color(0xFFEAF3FB),
fontWeight: FontWeight.w700,
),
),
),
],
),
);
}
Widget statusCard(String label, int count) {
return Column(
children: [
Container(
padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 15),
decoration: BoxDecoration(
color: const Color(0xFFEAF3FB),
borderRadius: BorderRadius.circular(8),
),
child: Column(
children: [
Text(
count.toString(),
style: GoogleFonts.poppins(
fontSize: 18,
color: Colors.black87,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 5),
Text(
label,
style: GoogleFonts.poppins(
fontSize: 12,
color: Colors.black87,
),
),
],
),
),
],
);
}
Widget buildCategoryCard(
String title,
IconData icon,
List<String> statusLabels,
List<int> values,
) {
final statusIcons = [
Icons.calendar_today,
Icons.assignment,
Icons.verified,
Icons.block,
Icons.cancel,
];
final statusColors = [
Color(0xFFFBFFCA),
Color(0xFFFFF1CD), // Colors.yellow.shade100, // Color(0xFFCAE77B),
Color(0xFFDAFFE8), // Colors.green.shade100, // Color(0xFF72D480),
Color(0xFFFFD6D3), // Colors.red.shade100, // Color(0xFFF88C8C),
Color(0xFFFFA8A8), // Colors.red.shade200, // Color(0xFFE94B4B),
];
final statusIconColors = [
Color(0xFFB1BC1A),
Color(0xFFCC9300),
Color(0xFF509269),
Color(0xFFD25E55),
Color(0xFFAE0303),
];
return Container(
padding: const EdgeInsets.all(16),
// decoration: BoxDecoration(
// color: Colors.white,
// borderRadius: BorderRadius.circular(12),
// ),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Colors.blueGrey,
blurRadius: 5,
offset: Offset(0, 2),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Container(
padding: const EdgeInsets.all(8),
decoration: const BoxDecoration(
// color: Colors.blue,
color: Color(0xFF004A8E),
shape: BoxShape.circle,
),
child: Icon(icon, color: Colors.white, size: 20),
),
const SizedBox(width: 10),
Text(
title,
style: GoogleFonts.poppins(
color: Colors.black87,
fontWeight: FontWeight.bold,
),
),
],
),
const SizedBox(height: 12),
...List.generate(values.length, (index) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 10),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
// Icon(statusIcons[index], size: 15, color: statusIconColors[index]),
Container(
width: 28,
height: 28,
decoration: BoxDecoration(
color: statusColors[index], // background color
borderRadius: BorderRadius.circular(
4,
), // square with slight rounding
),
alignment: Alignment.center,
child: Icon(
statusIcons[index],
size: 15,
color: statusIconColors[index], // icon color
),
),
const SizedBox(width: 6),
Text(
statusLabels[index],
style: GoogleFonts.poppins(color: Colors.black87),
),
],
),
Text(
values[index].toString(),
style: GoogleFonts.poppins(
fontSize: 10,
color: Colors.black87,
fontWeight: FontWeight.bold,
),
),
],
),
);
}),
],
),
);
}
// Widget buildCategoryCard(String title, IconData icon,statusLabels,statusvalue) {
//
//
// return Container(
// padding: const EdgeInsets.all(16),
// // decoration: BoxDecoration(
// // color: Colors.white,
// // borderRadius: BorderRadius.circular(12),
// // ),
//
// child: Column(
// crossAxisAlignment: CrossAxisAlignment.start,
// children: [
// Row(
// children: [
// Container(
// padding: const EdgeInsets.all(8),
// decoration: const BoxDecoration(
// color: Colors.blue,
// shape: BoxShape.circle,
// ),
// child: Icon(
// icon,
// color: Colors.white,
// size: 20,
// ),
// ),
// const SizedBox(width: 10),
// Text(title, style: GoogleFonts.poppins(
// color: Colors.black87,
// fontWeight: FontWeight.bold
// ),),
// ],
// ),
// const SizedBox(height: 12),
// ...List.generate(statusvalue.length, (index) {
// return Padding(
// padding: const EdgeInsets.symmetric(vertical: 10),
// child: Row(
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
// children: [
// Row(
// children: [
// // Icon(statusIcons[index], size: 20, color: statusColors[index]),
// Container(
// padding: const EdgeInsets.all(8),
// decoration: BoxDecoration(
// color: statusColors[index], // Background color behind the icon
// borderRadius: BorderRadius.circular(10), // Rounded square
// boxShadow: [
// BoxShadow(
// color: Colors.grey, // Shadow color
// spreadRadius: 1,
// blurRadius: 5,
// offset: const Offset(0, 2), // Shadow position
// ),
// ],
// ),
// child: Icon(
// statusIcons[index],
// color: statusIconColors[index],
// size: 20,
// // color: statusColors[index],
// ),
// ),
// const SizedBox(width: 6),
// Text(statusLabels[index]),
// ],
// ),
//
// ],
// ),
// );
// }),
// ],
// ),
// );
// }
return ResponsiveBuilder(
builder: (context, sizingInfo) {
bool isDesktop =
sizingInfo.deviceScreenType == DeviceScreenType.desktop;
return PopScope(
canPop: false, // Allow back navigation only if not login screen
onPopInvokedWithResult: (didPop, result) {
if (didPop) return;
_handleBackButton(); // Show exit confirmation dialog
},
child: Scaffold(
backgroundColor: const Color(0xFFf5f5f5),
appBar: CustomAppBar(isDesktop: isDesktop),
drawer: CustomDrawer(isDesktop: false),
body: Padding(
padding:
isDesktop
? EdgeInsets.symmetric(
horizontal:
MediaQuery.of(context).size.width *
0.1, // 30% of screen width as horizontal padding
vertical: 10, // 5% of screen height as vertical padding
)
: EdgeInsets.all(0),
child: LayoutBuilder(
builder: (context, constraints) {
return SingleChildScrollView(
// child: ConstrainedBox(
// constraints: BoxConstraints(
// minHeight: constraints.maxHeight,
// ),
// ),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// First Row: Toggle Buttons Row
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Container(
height: 36,
decoration: BoxDecoration(
color: const Color(0xFFEAEAEA),
borderRadius: BorderRadius.circular(20),
),
child: Row(
children: [
toggleChip("Today"),
toggleChip("This Week"),
],
),
),
],
),
const SizedBox(height: 20),
// Second Row: Domestic & International and Status
Row(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
// LEFT SIDE
Expanded(
flex: 2,
child: Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Colors.blueGrey,
blurRadius: 5,
offset: Offset(0, 2),
),
],
),
padding: const EdgeInsets.all(15),
child: Row(
children: [
...typeData.map((item) {
return Expanded(
child: Column(
children: [
buildTopCard(
// icon: item['value'] == "Domestic"
// ? Icons.home
// : Icons.travel_explore,
color:
item['value'] == "Domestic"
? Color(0xFF0DB04B)
: Color(0xFF004A8E),
label: item['value'],
count: item['count'],
bgColor:
item['value'] == "Domestic"
? const Color(0xFFD6FBE4)
: const Color(0xFFD9E8FF),
),
],
),
);
}).toList(),
],
),
),
),
const SizedBox(width: 20),
// RIGHT SIDE
Expanded(
flex: 3,
child: Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Colors.blueGrey,
blurRadius: 5,
offset: Offset(0, 2),
),
],
),
padding: const EdgeInsets.all(15),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Status",
style: GoogleFonts.poppins(
fontSize: 19,
color: Colors.black87,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 23),
Row(
mainAxisAlignment:
MainAxisAlignment.spaceAround,
children: [
...statusData
.map(
(item) => statusCard(
item['value'],
item['count'],
),
)
.toList(),
],
),
],
),
),
),
],
),
const SizedBox(height: 20),
// Third Row: Flight, Accommodation, Forex
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: buildCategoryCard(
"Flight",
Icons.flight,
statusLabels,
flightData,
),
),
const SizedBox(width: 10),
Expanded(
child: buildCategoryCard(
"Accomodation",
Icons.hotel_outlined,
statusLabels,
accommodationData,
),
),
const SizedBox(width: 10),
Expanded(
child: buildCategoryCard(
"Forex",
Icons.attach_money,
statusLabels,
forexData,
),
),
],
),
],
),
);
},
),
),
),
);
},
);
}
}