935 lines
32 KiB
Dart
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,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
),
|
|
);
|
|
},
|
|
);
|
|
}
|
|
}
|