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 { Map? apiData; String? organizationId; late bool _dialogShown = false; bool loader = false; late StreamSubscription _popStateListener; String selectedView = 'Today'; String AccessKeyFlag = "Both"; Map? currentData; List typeData = []; List statusData = []; List flightData = []; List accommodationData = []; List forexData = []; final ApiService apiService = ApiService(); final categoryIcons = { "flight": Icons.flight, "accommodation": Icons.hotel_outlined, "forex": Icons.attach_money, }; List statusLabels = [ "Pending Approval", "Partially Approved", "Approved", "Rejected", "Cancelled", ]; // Extract counts for "Both" section List getStatusValues(Map 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 _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 loadDashboardData() async { try { final data = await fetchStatusDashboard(); setState(() { apiData = data; }); } catch (e) { print("Error loading dashboard: $e"); } } Future getToken() async { final prefs = await SharedPreferences.getInstance(); return prefs.getString('auth_token'); } Future getOrgId() async { final prefs = await SharedPreferences.getInstance(); final String? userDataString = prefs.getString('user_data'); if (userDataString != null) { try { final Map userData = jsonDecode(userDataString); return userData["org_id"]?.toString(); } catch (e) { return null; } } return null; } Future> 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>.from( (apiData?['data']?['typeBasedCount']) ?? [], ); print("apiData - => $apiData"); print("typeBasedCount => $typeBasedCount"); final statusBasedCount = List>.from( (apiData?['data']?['statusBasedCount']) ?? [], ); final statusBasedTodayCount = List>.from( (apiData?['data']?['statusBasedTodayCount']) ?? [], ); final statusBasedWeeklyCount = List>.from( (apiData?['data']?['statusBasedWeeklyCount']) ?? [], ); final typeBasedTodayCount = List>.from( (apiData?['data']?['typeBasedTodayCount']) ?? [], ); final typeBasedWeeklyCount = List>.from( (apiData?['data']?['typeBasedWeeklyCount']) ?? [], ); final typeTodayBasedCount = List>.from( (apiData?['data']?['typeTodayBasedCount']) ?? [], ); final typeWeeklyBasedCount = List>.from( (apiData?['data']?['typeWeeklyBasedCount']) ?? [], ); final statusTodayBasedCount = List>.from( (apiData?['data']?['statusTodayBasedCount']) ?? [], ); final statusWeeklyBasedCount = List>.from( (apiData?['data']?['statusWeeklyBasedCount']) ?? [], ); final List todayList = List>.from( apiData?['data']?['todayTripCounts'] ?? [], ); final List weekList = List>.from( apiData?['data']?['weekTripCounts'] ?? [], ); // Merge all maps in the list into one map Map todayBasedCount = {}; Map 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 statusLabels, List 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, ), ), ], ), ], ), ); }, ), ), ), ); }, ); } }