Merge branch 'main' of bitbucket.org:venbainformationtechnology/ts-tat

This commit is contained in:
venbaittech 2025-06-17 14:58:41 +05:30
commit 08f80a8afa
24 changed files with 4929 additions and 61 deletions

View File

@ -674,6 +674,15 @@ class _ApprovalListState extends State<ApprovalList> {
),
),
),
DataColumn(
label: Text(
'Emp Code',
style: GoogleFonts.poppins(
fontSize: 13,
fontWeight: FontWeight.w600,
),
),
),
DataColumn(
label: Text(
'Traveller',
@ -755,37 +764,61 @@ class _ApprovalListState extends State<ApprovalList> {
),
),
DataCell(
Column(
crossAxisAlignment:
CrossAxisAlignment.start,
mainAxisAlignment:
MainAxisAlignment.center,
children: [
SizedBox(height: 1),
Text(
plan.userName.isNotEmpty
? plan.userName
: plan.travellerName,
style: TextStyle(
fontSize: 12,
fontFamily: "Inter",
),
),
SizedBox(height: 3),
Expanded(
child: Text(
"(${plan.employeeCode})" ??
" - ",
style: GoogleFonts.poppins(
fontSize: 10,
// fontFamily: "Inter",
),
),
),
],
Text(
plan.employeeCode ?? " - ",
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",
),
softWrap: true,
overflow: TextOverflow.ellipsis,
),
),
// DataCell(
// Column(
// crossAxisAlignment:
// CrossAxisAlignment.start,
// mainAxisAlignment:
// MainAxisAlignment.center,
//
// children: [
// SizedBox(height: 1),
// Text(
// plan.userName.isNotEmpty
// ? plan.userName
// : plan.travellerName,
// style: TextStyle(
// fontSize: 12,
// fontFamily: "Inter",
// ),
// ),
// SizedBox(height: 3),
// Expanded(
// child: Text(
// "(${plan.employeeCode})" ??
// " - ",
// style: GoogleFonts.poppins(
// fontSize: 10,
// // fontFamily: "Inter",
// ),
// ),
// ),
// ],
// ),
// ),
DataCell(
Text(
plan.approverName ?? " ",

View File

@ -93,6 +93,8 @@ class CostCenterListState extends State<CostCenterList> {
print("Calling Refresh Data $object");
setState(() {
allCostCenter = object;
filteredCostCenter = object;
searchController.text = "";
});
return object;
});

View File

@ -93,6 +93,8 @@ class DepartmentListState extends State<DepartmentList> {
print("Calling Refresh Data $object");
setState(() {
allDepartment = object;
filteredDepartment = object;
searchController.text = "";
});
return object;
});

View File

@ -73,6 +73,8 @@ class ForexDataListState extends State<ForexDataList> {
return futureForex.then((users) {
setState(() {
allForex = users;
filteredForex = users;
searchController.text = "";
});
return users;
});

View File

@ -94,6 +94,8 @@ class _GroupListState extends State<GroupList> {
final result = await apiService.fetchAllGroup();
setState(() {
allGroups = result;
filteredGroups = result;
searchController.text = "";
});
print("Fetched services: $allGroups");
} catch (e) {

View File

@ -3,6 +3,7 @@ import 'dart:convert';
import 'package:dropdown_search/dropdown_search.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:http/http.dart' as http;
@ -55,6 +56,7 @@ class HotelsDataState extends State<HotelsData> {
List<String> dataHeader = [
"hotel_name",
"hotel_chain",
"category",
"country_code",
"city",
];
@ -63,6 +65,7 @@ class HotelsDataState extends State<HotelsData> {
final data = {
"hotel_name": controllers["hotel_name"]?.text,
"hotel_chain": controllers["hotel_chain"]?.text,
"category": controllers["category"]?.text,
"country_code": selectedCountry,
"country_name": selectedCountryName,
"city": controllers["city"]?.text,
@ -115,6 +118,7 @@ class HotelsDataState extends State<HotelsData> {
data['country_name']; // For dropdown label or display
controllers['city']?.text = data['city'] ?? '';
controllers['hotel_chain']?.text = data['hotel_chain'] ?? '';
controllers['category']?.text = data['category'] ?? '';
controllers['hotel_name']?.text = data['hotel_name'] ?? '';
isActive = data["is_active"];
final hotelsId = int.tryParse(data['hotel_id'].toString());
@ -145,6 +149,7 @@ class HotelsDataState extends State<HotelsData> {
final data = {
"hotel_name": controllers["hotel_name"]?.text,
"hotel_chain": controllers["hotel_chain"]?.text,
"category": controllers["category"]?.text,
"country_code": selectedCountry,
"country": selectedCountryName,
"city": controllers["city"]?.text,
@ -375,12 +380,43 @@ class HotelsDataState extends State<HotelsData> {
],
],
),
// - It has been observed that many of the dropdowns have overlapping issues, causing label names to be hidden - just copied searchable dropdown - still not completed (user mangement screen only ) master page except policy - my trips - flight taxi train insurance, visa misscenllo color white size padding data
// - Delete option is not working in the policy list page - completed
// - Label Names for all the modules should be set bold as it is looking like normal text in user management compared to trips page - completed
// - In the masters org mangement search option not working for traveller - particular 4 master page - - issues occur - commpleted - email master working fine, traveller master working fine, amount master working fine, group - completed
// - QC- Authentication - - completed - ask to check
const SizedBox(height: 5),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Category",
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74),
),
),
SizedBox(height: 5),
CustomTextFieldForexWrapper(
isFocused: false,
isDesktop: widget.isDesktop,
color: Colors.transparent,
child: SizedBox(
height: 40,
child: TextField(
controller: controllers["category"],
inputFormatters: [
FilteringTextInputFormatter.allow(RegExp(r'[a-zA-Z0-9 ]')),
],
style: const TextStyle(fontSize: 12),
decoration: const InputDecoration(
labelText: "Category",
labelStyle: TextStyle(fontSize: 11, color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16),
),
),
),
),
],
),
const SizedBox(height: 10),
Column(
crossAxisAlignment: CrossAxisAlignment.start,

View File

@ -73,6 +73,8 @@ class HotelsDataListState extends State<HotelsDataList> {
return futureHotels.then((objects) {
setState(() {
allHotels = objects;
filteredHotels = objects;
searchController.text = "";
});
return objects;
});

View File

@ -514,29 +514,35 @@ class _VisaScreenState extends State<VisaScreen> {
isFocused: _isHotelNameFocused,
isDesktop: isDesktop,
width: isDesktop
? MediaQuery.of(context).size.width * 0.34
: MediaQuery.of(context).size.width * 0.66,
? MediaQuery.of(context).size.width * 0.34
: MediaQuery.of(context).size.width * 0.66,
child: SizedBox(
height: 40,
child: DropdownSearch<String>(
selectedItem: countryMap[selectedCountry],
popupProps: PopupProps.menu(
showSearchBox: true,
fit: FlexFit.loose,
constraints: BoxConstraints(maxHeight: 200),
menuProps: const MenuProps(backgroundColor: Colors.white),
itemBuilder:
(context, item, isSelected) => Container(
color: Colors.white,
padding: EdgeInsets.symmetric(
horizontal: 10,
vertical: 6,
),
child: Text(
item,
style: GoogleFonts.poppins(fontSize: 11.5),
),
),
searchFieldProps: TextFieldProps(
decoration: InputDecoration(
hintText: "Search ...",
contentPadding: EdgeInsets.symmetric(horizontal: 10),
),
),
itemBuilder: (context, item, isSelected) {
return Container(
padding: EdgeInsets.symmetric(horizontal: 10, vertical: 4),
child: Text(
item,
style: TextStyle(fontSize: 12,color: Colors.black,),
),
);
},
),
items: countryMap.values.toList(),
dropdownDecoratorProps: DropDownDecoratorProps(
@ -545,19 +551,20 @@ class _VisaScreenState extends State<VisaScreen> {
contentPadding: EdgeInsets.symmetric(horizontal: 1),
),
),
dropdownBuilder: (context, selectedItem) => Align(
dropdownBuilder:
(context, selectedItem) => Align(
alignment: Alignment.centerLeft,
child: Text(
selectedItem ?? "Select",
style: TextStyle(fontSize: 12),
selectedItem ?? "Select ",
style: GoogleFonts.poppins(fontSize: 12,color: Colors.black,),
),
),
onChanged: (String? newValue) {
setState(() {
selectedCountry = countryMap.entries
.firstWhere((entry) => entry.value == newValue)
.key;
selectedCountry =
countryMap.entries.firstWhere(
(entry) => entry.value == newValue,
).key;
});
},
),

View File

@ -1211,15 +1211,16 @@ class CreateNewPlansState extends State<CreateNewPlan> {
// "${planData['traveller_id']},"
// " ${selectedPlanId}, "
// " ");
print("Start loader at: ${DateTime.now()}");
showDialog(
context: context,
barrierDismissible: false,
builder: (context) => const SavingLoader(),
);
// postPlanData(planData);
await postPlanData(planData);
postPlanData(planData);
// await postPlanData(planData);
print("Hide loader at: ${DateTime.now()}");
// Close loading dialog (ONLY if still mounted)
if (mounted) Navigator.of(context, rootNavigator: true).pop();
}

View File

@ -188,6 +188,8 @@ class _PolicyListState extends State<PolicyList> {
final result = await apiService.fetchAllPolicy();
setState(() {
allPolicy = result;
filteredPolicy = result;
searchController.text = "";
});
print("Fetched services: $allPolicy");
} catch (e) {

View File

@ -0,0 +1,666 @@
import 'dart:convert';
import 'dart:core';
import 'package:go_router/go_router.dart';
import 'package:google_fonts/google_fonts.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 '../../routes/mainLayout.dart';
import '../../services/apiService.dart';
import '../../utils/auth_utils.dart';
import '../../widgets/custom_text_field.dart';
class AdvancePurchase extends StatefulWidget {
const AdvancePurchase({super.key});
@override
_AdvancePurchaseState createState() => _AdvancePurchaseState();
}
class _AdvancePurchaseState extends State<AdvancePurchase> {
final ApiService apiService = ApiService();
String? userId;
String? orgId;
String? roleUser;
String? token;
Color? layoutColor;
Color? bodyColor;
Map<String, FocusNode> focusNodes = {};
Map<String, bool> focusStates = {};
Map<String, TextEditingController> textControllers = {};
Map<String, String> errorMessages = {};
// Map<String, dynamic> advancePurchaseReport = {};
List<Map<String, dynamic>> domesticData = [];
List<Map<String, dynamic>> internationData = [];
// Map<String, dynamic> domesticData = {};
// Map<String, dynamic> internationData = {};
@override
void initState() {
super.initState();
if (!textControllers.containsKey("from_date")) {
textControllers["from_date"] = TextEditingController();
}
if (!textControllers.containsKey("to_date")) {
textControllers["to_date"] = TextEditingController();
}
if (!focusNodes.containsKey("from_date")) {
focusNodes["from_date"] = FocusNode();
}
if (!focusNodes.containsKey("to_date")) {
focusNodes["to_date"] = FocusNode();
}
if (!focusStates.containsKey("to_date")) {
focusStates["to_date"] = false;
}
if (!focusStates.containsKey("from_date")) {
focusStates["from_date"] = false;
}
// Add focus listeners
focusNodes.forEach((key, focusNode) {
_addFocusListener(focusNode, (focus) {
setState(() {
focusStates[key] = focus;
});
});
});
getToken();
// fetchAdvancePurchaseReport();
}
void _addFocusListener(FocusNode node, Function(bool) updateState) {
node.addListener(() {
setState(() {
updateState(node.hasFocus);
});
});
}
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();
if (token == null || userId == null) {
print("Token or USerId missing");
return;
}
}
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');
}
Future<void> handleSubmit() async {
errorMessages.remove("from_date");
errorMessages.remove("to_date");
final fromDateText = textControllers["from_date"]?.text;
final toDateText = textControllers["to_date"]?.text;
if (fromDateText == null || fromDateText.isEmpty ||
toDateText == null || toDateText.isEmpty) {
errorMessages["from_date"] = "Required";
errorMessages["to_date"] = "Required";
setState(() {});
return;
}
// Parse the string to DateTime using the correct format
final dateFormat = DateFormat('dd-MM-yyyy');
final DateTime fromDate = dateFormat.parse(fromDateText);
final DateTime toDate = dateFormat.parse(toDateText);
// Check if from date is after to date
if (fromDate.isAfter(toDate)) {
errorMessages["to_date"] = "Invalid Date Range";
setState(() {});
return;
}
// Format for API request
final formattedfromDate = DateFormat('yyyy-MM-dd').format(fromDate);
final formattedtoDate = DateFormat('yyyy-MM-dd').format(toDate);
// Fetch and update
final result = await apiService.CallReports(
'advancePurchaseReport',
formattedfromDate,
formattedtoDate,
);
setState(() {
domesticData = List<Map<String, dynamic>>.from(result['data']['domestic'] ?? []);
internationData = List<Map<String, dynamic>>.from(result['data']['international'] ?? []);
});
}
@override
// Widget build(BuildContext context) {
// // TODO: implement build
// throw UnimplementedError();
// }
Widget build(BuildContext context) {
return ResponsiveBuilder(
builder: (context, sizingInfo) {
bool isDesktop = sizingInfo.deviceScreenType == DeviceScreenType.desktop;
return Scaffold(
backgroundColor: 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:
MediaQuery.of(context).size.height *
0, // 5% of screen height as vertical padding
)
: EdgeInsets.all(0),
child: Row(
children: [
// if (isDesktop) CustomDrawer(isDesktop: true),
// const Expanded(child: Center(child: Text("User Page Content"))),
Expanded(child: buildAdvancePurchaseLayout(isDesktop))
],
),
),
);
},
);
}
Widget buildAdvancePurchaseLayout(bool isDesktop) {
return Container( child: buildAdvancePurchaseFormLayout(isDesktop) );
}
Widget buildAdvancePurchaseFormLayout(bool isDesktop) {
return Container(
margin: isDesktop ? EdgeInsets.all(10.0) : null,
padding: const EdgeInsets.only(top: 15, bottom: 15, left: 20, right: 20),
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)
: null,
color: isDesktop ? Colors.white : Color(0xFFFCFCFC),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// ------------------- First Row -------------------
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'Advance Purchase',
style: GoogleFonts.poppins(
fontSize: isDesktop ? 16 : 14,
fontWeight: FontWeight.w600,
color: Colors.black,
),
),
Row(
children: [
ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Color(0xFF114D8B),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
side: BorderSide(color: Color(0xFF114D8B), width: 2),
),
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
),
onPressed: () {
// Your Excel export logic here
},
child: Row(
children: [
Text(
"Excel",
style: GoogleFonts.poppins(
fontSize: isDesktop ? 13 : 11,
color: Colors.white,
),
),
SizedBox(width: 8),
Icon(Icons.file_present_outlined, size: 15, color: Colors.white),
],
),
),
SizedBox(width: 10),
ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Color(0xFF114D8B),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
side: BorderSide(color: Color(0xFF114D8B), width: 2),
),
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
),
onPressed: () {
// Your PDF export logic here
},
child: Row(
children: [
Text(
"PDF",
style: GoogleFonts.poppins(
fontSize: isDesktop ? 13 : 11,
color: Colors.white,
),
),
SizedBox(width: 8),
Icon(Icons.picture_as_pdf_outlined, size: 15, color: Colors.white),
],
),
),
],
),
],
),
SizedBox(height: 15),
// ------------------- Second Row -------------------
Row(
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"From Date *",
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74),
),
),
SizedBox(height: 5),
CustomTextFieldWrapper(
isFocused: focusStates["from_date"] ?? false,
isDesktop: isDesktop,
child: SizedBox(
height: 40,
child: GestureDetector(
onTap: () async {
final pickedDate = await showDatePicker(
context: context,
initialDate: DateTime.now(),
firstDate: DateTime(2000),
lastDate: DateTime(2100),
);
if (pickedDate != null) {
textControllers["from_date"]?.text =
DateFormat('dd-MM-yyyy').format(pickedDate);
}
},
child: AbsorbPointer(
child: TextField(
focusNode: focusNodes["from_date"],
controller: textControllers["from_date"],
readOnly: true,
style: const TextStyle(fontSize: 12),
decoration: InputDecoration(
labelText: "Select Date",
labelStyle: const TextStyle(
fontSize: 12,
color: Colors.grey,
),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: const EdgeInsets.symmetric(vertical: 16),
suffixIcon: const Icon(
Icons.calendar_today,
size: 16,
color: Colors.grey,
),
),
),
),
),
),
),
if (errorMessages["from_date"] != null) ...[
SizedBox(height: 5), // Space before error message
Text(
errorMessages["from_date"]!,
style: TextStyle(color: Colors.red, fontSize: 12),
),
],
],
),
if (isDesktop) Spacer() else SizedBox(height: 8),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"To Date *",
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74),
),
),
SizedBox(height: 5),
CustomTextFieldWrapper(
isFocused: focusStates["to_date"] ?? false,
isDesktop: isDesktop,
child: SizedBox(
height: 40,
child: GestureDetector(
onTap: () async {
final fromDateText = textControllers["from_date"]?.text;
DateTime? mintoDate;
if (fromDateText != null && fromDateText.isNotEmpty) {
mintoDate = DateFormat('dd-MM-yyyy').parse(fromDateText);
}
final pickedDate = await showDatePicker(
context: context,
initialDate: mintoDate ?? DateTime.now(),
firstDate: mintoDate ?? DateTime(2000),
lastDate: DateTime(2100),
);
if (pickedDate != null) {
textControllers["to_date"]?.text =
DateFormat('dd-MM-yyyy').format(pickedDate);
}
},
child: AbsorbPointer(
child: TextField(
focusNode: focusNodes["to_date"],
controller: textControllers["to_date"],
readOnly: true,
style: const TextStyle(fontSize: 12),
decoration: const InputDecoration(
labelText: "Select Date",
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16),
suffixIcon: Icon(
Icons.calendar_today,
size: 16,
color: Colors.grey,
),
),
),
),
),
),
),
if (errorMessages["to_date"] != null) ...[
SizedBox(height: 5), // Space before error message
Text(
errorMessages["to_date"]!,
style: const TextStyle(color: Colors.red, fontSize: 12),
maxLines: 2, // Allow it to wrap onto two lines
overflow:
TextOverflow.ellipsis, // Add ellipsis if it still overflows
),
],
],
),
if (isDesktop) Spacer() else SizedBox(height: 8),
SizedBox(
child: 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: () {handleSubmit();},
child: Row(
mainAxisSize:
MainAxisSize.min, // Ensures content fits nicely
children: [
Text(
"Search",
style: GoogleFonts.poppins(
fontSize: isDesktop ? 13 : 11,
),
),
SizedBox(width: 8), // spacing between icon and text
Icon(
Icons.search,
size: 15,
color: Colors.white,
),
],
),
),
),
],
),
SizedBox(height: 15),
// ------------------- Third Row -------------------
DefaultTabController(
length: 2,
child: Column(
children: [
TabBar(
labelColor: Colors.black,
unselectedLabelColor: Colors.grey,
indicatorColor: Color(0xFF114D8B),
tabs: [
Tab(text: "Domestic"),
Tab(text: "International"),
],
),
SizedBox(height: 10),
Container(
height: 200, // Or use Expanded for flexible height
child: TabBarView(
children: [
// Domestic Tab
domesticData.isEmpty
? Center(
child: Padding(
padding: EdgeInsets.all(16),
child: Text(
"No data found",
style: GoogleFonts.poppins(fontSize: 14, fontWeight: FontWeight.w500),
),
),
)
: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8),
child: Text(
"Domestic Report (From: ${textControllers["from_date"]?.text} To: ${textControllers["to_date"]?.text})",
style: GoogleFonts.poppins(fontSize: 13, fontWeight: FontWeight.w500, color: Colors.grey[700]),
),
),
Expanded(
child: SingleChildScrollView(
scrollDirection: Axis.vertical,
child: DataTable(
dividerThickness: 0.5,
columnSpacing: isDesktop ? 24.0 : 16.0,
border: TableBorder(
horizontalInside: BorderSide(
width: 0.5,
color: Colors.grey.shade200,
),
),
columns: [
DataColumn(label: Text('Plan Id', style: GoogleFonts.poppins(fontSize: 12, fontWeight: FontWeight.w600))),
DataColumn(label: Text('Employee Code', style: GoogleFonts.poppins(fontSize: 12, fontWeight: FontWeight.w600))),
DataColumn(label: Text('SO Number', style: GoogleFonts.poppins(fontSize: 12, fontWeight: FontWeight.w600))),
DataColumn(label: Text('Department', style: GoogleFonts.poppins(fontSize: 12, fontWeight: FontWeight.w600))),
DataColumn(label: Text('Flight Trip Type', style: GoogleFonts.poppins(fontSize: 12, fontWeight: FontWeight.w600))),
DataColumn(label: Text('Plan Trip Type', style: GoogleFonts.poppins(fontSize: 12, fontWeight: FontWeight.w600))),
DataColumn(label: Text('Sector', style: GoogleFonts.poppins(fontSize: 12, fontWeight: FontWeight.w600))),
],
rows: domesticData.map((entry) {
return DataRow(cells: [
DataCell(Text('${entry['plan_id']}', style: GoogleFonts.poppins(fontSize: 12))),
DataCell(Text('${entry['employee_code']}', style: GoogleFonts.poppins(fontSize: 12))),
DataCell(Text('${entry['so_number']}', style: GoogleFonts.poppins(fontSize: 12))),
DataCell(Text('${entry['functional_department']}', style: GoogleFonts.poppins(fontSize: 12))),
DataCell(Text('${entry['flight_trip_type']}', style: GoogleFonts.poppins(fontSize: 12))),
DataCell(Text('${entry['plan_trip_type']}', style: GoogleFonts.poppins(fontSize: 12))),
DataCell(Text('${entry['sector']}', style: GoogleFonts.poppins(fontSize: 12))),
]);
}).toList(),
),
),
),
],
),
// International Tab
internationData.isEmpty
? Center(
child: Padding(
padding: EdgeInsets.all(16),
child: Text(
"No data found",
style: GoogleFonts.poppins(fontSize: 14, fontWeight: FontWeight.w500),
),
),
)
: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8),
child: Text(
"International Report (From: ${textControllers["from_date"]?.text} To: ${textControllers["to_date"]?.text})",
style: GoogleFonts.poppins(fontSize: 13, fontWeight: FontWeight.w500, color: Colors.grey[700]),
),
),
Expanded(
child: SingleChildScrollView(
scrollDirection: Axis.vertical,
child: DataTable(
dividerThickness: 0.5,
columnSpacing: isDesktop ? 24.0 : 16.0,
border: TableBorder(
horizontalInside: BorderSide(
width: 0.5,
color: Colors.grey.shade200,
),
),
columns: [
DataColumn(label: Text('Plan Id', style: GoogleFonts.poppins(fontSize: 12, fontWeight: FontWeight.w600))),
DataColumn(label: Text('Employee Code', style: GoogleFonts.poppins(fontSize: 12, fontWeight: FontWeight.w600))),
DataColumn(label: Text('SO Number', style: GoogleFonts.poppins(fontSize: 12, fontWeight: FontWeight.w600))),
DataColumn(label: Text('Department', style: GoogleFonts.poppins(fontSize: 12, fontWeight: FontWeight.w600))),
DataColumn(label: Text('Flight Trip Type', style: GoogleFonts.poppins(fontSize: 12, fontWeight: FontWeight.w600))),
DataColumn(label: Text('Plan Trip Type', style: GoogleFonts.poppins(fontSize: 12, fontWeight: FontWeight.w600))),
DataColumn(label: Text('Sector', style: GoogleFonts.poppins(fontSize: 12, fontWeight: FontWeight.w600))),
],
rows: internationData.map((entry) {
return DataRow(cells: [
DataCell(Text('${entry['plan_id']}', style: GoogleFonts.poppins(fontSize: 12))),
DataCell(Text('${entry['employee_code']}', style: GoogleFonts.poppins(fontSize: 12))),
DataCell(Text('${entry['so_number']}', style: GoogleFonts.poppins(fontSize: 12))),
DataCell(Text('${entry['functional_department']}', style: GoogleFonts.poppins(fontSize: 12))),
DataCell(Text('${entry['flight_trip_type']}', style: GoogleFonts.poppins(fontSize: 12))),
DataCell(Text('${entry['plan_trip_type']}', style: GoogleFonts.poppins(fontSize: 12))),
DataCell(Text('${entry['sector']}', style: GoogleFonts.poppins(fontSize: 12))),
]);
}).toList(),
),
),
),
],
),
],
),
),
],
),
),
SizedBox(height: 15),
],
),
);
}
}

View File

@ -0,0 +1,666 @@
import 'dart:convert';
import 'dart:core';
import 'package:go_router/go_router.dart';
import 'package:google_fonts/google_fonts.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 '../../routes/mainLayout.dart';
import '../../services/apiService.dart';
import '../../utils/auth_utils.dart';
import '../../widgets/custom_text_field.dart';
class GuestHouse extends StatefulWidget {
const GuestHouse({super.key});
@override
_GuestHouseState createState() => _GuestHouseState();
}
class _GuestHouseState extends State<GuestHouse> {
final ApiService apiService = ApiService();
String? userId;
String? orgId;
String? roleUser;
String? token;
Color? layoutColor;
Color? bodyColor;
Map<String, FocusNode> focusNodes = {};
Map<String, bool> focusStates = {};
Map<String, TextEditingController> textControllers = {};
Map<String, String> errorMessages = {};
// Map<String, dynamic> GuestHouseReport = {};
List<Map<String, dynamic>> domesticData = [];
List<Map<String, dynamic>> internationData = [];
// Map<String, dynamic> domesticData = {};
// Map<String, dynamic> internationData = {};
@override
void initState() {
super.initState();
if (!textControllers.containsKey("from_date")) {
textControllers["from_date"] = TextEditingController();
}
if (!textControllers.containsKey("to_date")) {
textControllers["to_date"] = TextEditingController();
}
if (!focusNodes.containsKey("from_date")) {
focusNodes["from_date"] = FocusNode();
}
if (!focusNodes.containsKey("to_date")) {
focusNodes["to_date"] = FocusNode();
}
if (!focusStates.containsKey("to_date")) {
focusStates["to_date"] = false;
}
if (!focusStates.containsKey("from_date")) {
focusStates["from_date"] = false;
}
// Add focus listeners
focusNodes.forEach((key, focusNode) {
_addFocusListener(focusNode, (focus) {
setState(() {
focusStates[key] = focus;
});
});
});
getToken();
// fetchGuestHouseReport();
}
void _addFocusListener(FocusNode node, Function(bool) updateState) {
node.addListener(() {
setState(() {
updateState(node.hasFocus);
});
});
}
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();
if (token == null || userId == null) {
print("Token or USerId missing");
return;
}
}
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');
}
Future<void> handleSubmit() async {
errorMessages.remove("from_date");
errorMessages.remove("to_date");
final fromDateText = textControllers["from_date"]?.text;
final toDateText = textControllers["to_date"]?.text;
if (fromDateText == null || fromDateText.isEmpty ||
toDateText == null || toDateText.isEmpty) {
errorMessages["from_date"] = "Required";
errorMessages["to_date"] = "Required";
setState(() {});
return;
}
// Parse the string to DateTime using the correct format
final dateFormat = DateFormat('dd-MM-yyyy');
final DateTime fromDate = dateFormat.parse(fromDateText);
final DateTime toDate = dateFormat.parse(toDateText);
// Check if from date is after to date
if (fromDate.isAfter(toDate)) {
errorMessages["to_date"] = "Invalid Date Range";
setState(() {});
return;
}
// Format for API request
final formattedfromDate = DateFormat('yyyy-MM-dd').format(fromDate);
final formattedtoDate = DateFormat('yyyy-MM-dd').format(toDate);
// Fetch and update
final result = await apiService.CallReports(
'guestHouseReport',
formattedfromDate,
formattedtoDate,
);
setState(() {
domesticData = List<Map<String, dynamic>>.from(result['data']['domestic'] ?? []);
internationData = List<Map<String, dynamic>>.from(result['data']['international'] ?? []);
});
}
@override
// Widget build(BuildContext context) {
// // TODO: implement build
// throw UnimplementedError();
// }
Widget build(BuildContext context) {
return ResponsiveBuilder(
builder: (context, sizingInfo) {
bool isDesktop = sizingInfo.deviceScreenType == DeviceScreenType.desktop;
return Scaffold(
backgroundColor: 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:
MediaQuery.of(context).size.height *
0, // 5% of screen height as vertical padding
)
: EdgeInsets.all(0),
child: Row(
children: [
// if (isDesktop) CustomDrawer(isDesktop: true),
// const Expanded(child: Center(child: Text("User Page Content"))),
Expanded(child: buildGuestHouseLayout(isDesktop))
],
),
),
);
},
);
}
Widget buildGuestHouseLayout(bool isDesktop) {
return Container( child: buildGuestHouseFormLayout(isDesktop) );
}
Widget buildGuestHouseFormLayout(bool isDesktop) {
return Container(
margin: isDesktop ? EdgeInsets.all(10.0) : null,
padding: const EdgeInsets.only(top: 15, bottom: 15, left: 20, right: 20),
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)
: null,
color: isDesktop ? Colors.white : Color(0xFFFCFCFC),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// ------------------- First Row -------------------
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'Guest House',
style: GoogleFonts.poppins(
fontSize: isDesktop ? 16 : 14,
fontWeight: FontWeight.w600,
color: Colors.black,
),
),
Row(
children: [
ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Color(0xFF114D8B),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
side: BorderSide(color: Color(0xFF114D8B), width: 2),
),
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
),
onPressed: () {
// Your Excel export logic here
},
child: Row(
children: [
Text(
"Excel",
style: GoogleFonts.poppins(
fontSize: isDesktop ? 13 : 11,
color: Colors.white,
),
),
SizedBox(width: 8),
Icon(Icons.file_present_outlined, size: 15, color: Colors.white),
],
),
),
SizedBox(width: 10),
ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Color(0xFF114D8B),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
side: BorderSide(color: Color(0xFF114D8B), width: 2),
),
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
),
onPressed: () {
// Your PDF export logic here
},
child: Row(
children: [
Text(
"PDF",
style: GoogleFonts.poppins(
fontSize: isDesktop ? 13 : 11,
color: Colors.white,
),
),
SizedBox(width: 8),
Icon(Icons.picture_as_pdf_outlined, size: 15, color: Colors.white),
],
),
),
],
),
],
),
SizedBox(height: 15),
// ------------------- Second Row -------------------
Row(
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"From Date *",
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74),
),
),
SizedBox(height: 5),
CustomTextFieldWrapper(
isFocused: focusStates["from_date"] ?? false,
isDesktop: isDesktop,
child: SizedBox(
height: 40,
child: GestureDetector(
onTap: () async {
final pickedDate = await showDatePicker(
context: context,
initialDate: DateTime.now(),
firstDate: DateTime(2000),
lastDate: DateTime(2100),
);
if (pickedDate != null) {
textControllers["from_date"]?.text =
DateFormat('dd-MM-yyyy').format(pickedDate);
}
},
child: AbsorbPointer(
child: TextField(
focusNode: focusNodes["from_date"],
controller: textControllers["from_date"],
readOnly: true,
style: const TextStyle(fontSize: 12),
decoration: InputDecoration(
labelText: "Select Date",
labelStyle: const TextStyle(
fontSize: 12,
color: Colors.grey,
),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: const EdgeInsets.symmetric(vertical: 16),
suffixIcon: const Icon(
Icons.calendar_today,
size: 16,
color: Colors.grey,
),
),
),
),
),
),
),
if (errorMessages["from_date"] != null) ...[
SizedBox(height: 5), // Space before error message
Text(
errorMessages["from_date"]!,
style: TextStyle(color: Colors.red, fontSize: 12),
),
],
],
),
if (isDesktop) Spacer() else SizedBox(height: 8),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"To Date *",
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74),
),
),
SizedBox(height: 5),
CustomTextFieldWrapper(
isFocused: focusStates["to_date"] ?? false,
isDesktop: isDesktop,
child: SizedBox(
height: 40,
child: GestureDetector(
onTap: () async {
final fromDateText = textControllers["from_date"]?.text;
DateTime? mintoDate;
if (fromDateText != null && fromDateText.isNotEmpty) {
mintoDate = DateFormat('dd-MM-yyyy').parse(fromDateText);
}
final pickedDate = await showDatePicker(
context: context,
initialDate: mintoDate ?? DateTime.now(),
firstDate: mintoDate ?? DateTime(2000),
lastDate: DateTime(2100),
);
if (pickedDate != null) {
textControllers["to_date"]?.text =
DateFormat('dd-MM-yyyy').format(pickedDate);
}
},
child: AbsorbPointer(
child: TextField(
focusNode: focusNodes["to_date"],
controller: textControllers["to_date"],
readOnly: true,
style: const TextStyle(fontSize: 12),
decoration: const InputDecoration(
labelText: "Select Date",
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16),
suffixIcon: Icon(
Icons.calendar_today,
size: 16,
color: Colors.grey,
),
),
),
),
),
),
),
if (errorMessages["to_date"] != null) ...[
SizedBox(height: 5), // Space before error message
Text(
errorMessages["to_date"]!,
style: const TextStyle(color: Colors.red, fontSize: 12),
maxLines: 2, // Allow it to wrap onto two lines
overflow:
TextOverflow.ellipsis, // Add ellipsis if it still overflows
),
],
],
),
if (isDesktop) Spacer() else SizedBox(height: 8),
SizedBox(
child: 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: () {handleSubmit();},
child: Row(
mainAxisSize:
MainAxisSize.min, // Ensures content fits nicely
children: [
Text(
"Search",
style: GoogleFonts.poppins(
fontSize: isDesktop ? 13 : 11,
),
),
SizedBox(width: 8), // spacing between icon and text
Icon(
Icons.search,
size: 15,
color: Colors.white,
),
],
),
),
),
],
),
SizedBox(height: 15),
// ------------------- Third Row -------------------
DefaultTabController(
length: 2,
child: Column(
children: [
TabBar(
labelColor: Colors.black,
unselectedLabelColor: Colors.grey,
indicatorColor: Color(0xFF114D8B),
tabs: [
Tab(text: "Domestic"),
Tab(text: "International"),
],
),
SizedBox(height: 10),
Container(
height: 200, // Or use Expanded for flexible height
child: TabBarView(
children: [
// Domestic Tab
domesticData.isEmpty
? Center(
child: Padding(
padding: EdgeInsets.all(16),
child: Text(
"No data found",
style: GoogleFonts.poppins(fontSize: 14, fontWeight: FontWeight.w500),
),
),
)
: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8),
child: Text(
"Domestic Report (From: ${textControllers["from_date"]?.text} To: ${textControllers["to_date"]?.text})",
style: GoogleFonts.poppins(fontSize: 13, fontWeight: FontWeight.w500, color: Colors.grey[700]),
),
),
Expanded(
child: SingleChildScrollView(
scrollDirection: Axis.vertical,
child: DataTable(
dividerThickness: 0.5,
columnSpacing: isDesktop ? 24.0 : 16.0,
border: TableBorder(
horizontalInside: BorderSide(
width: 0.5,
color: Colors.grey.shade200,
),
),
columns: [
DataColumn(label: Text('Plan Id', style: GoogleFonts.poppins(fontSize: 12, fontWeight: FontWeight.w600))),
DataColumn(label: Text('Employee Code', style: GoogleFonts.poppins(fontSize: 12, fontWeight: FontWeight.w600))),
DataColumn(label: Text('SO Number', style: GoogleFonts.poppins(fontSize: 12, fontWeight: FontWeight.w600))),
DataColumn(label: Text('Department', style: GoogleFonts.poppins(fontSize: 12, fontWeight: FontWeight.w600))),
DataColumn(label: Text('Flight Trip Type', style: GoogleFonts.poppins(fontSize: 12, fontWeight: FontWeight.w600))),
DataColumn(label: Text('Plan Trip Type', style: GoogleFonts.poppins(fontSize: 12, fontWeight: FontWeight.w600))),
DataColumn(label: Text('Sector', style: GoogleFonts.poppins(fontSize: 12, fontWeight: FontWeight.w600))),
],
rows: domesticData.map((entry) {
return DataRow(cells: [
DataCell(Text('${entry['plan_id']}', style: GoogleFonts.poppins(fontSize: 12))),
DataCell(Text('${entry['employee_code']}', style: GoogleFonts.poppins(fontSize: 12))),
DataCell(Text('${entry['so_number']}', style: GoogleFonts.poppins(fontSize: 12))),
DataCell(Text('${entry['functional_department']}', style: GoogleFonts.poppins(fontSize: 12))),
DataCell(Text('${entry['flight_trip_type']}', style: GoogleFonts.poppins(fontSize: 12))),
DataCell(Text('${entry['plan_trip_type']}', style: GoogleFonts.poppins(fontSize: 12))),
DataCell(Text('${entry['sector']}', style: GoogleFonts.poppins(fontSize: 12))),
]);
}).toList(),
),
),
),
],
),
// International Tab
internationData.isEmpty
? Center(
child: Padding(
padding: EdgeInsets.all(16),
child: Text(
"No data found",
style: GoogleFonts.poppins(fontSize: 14, fontWeight: FontWeight.w500),
),
),
)
: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8),
child: Text(
"International Report (From: ${textControllers["from_date"]?.text} To: ${textControllers["to_date"]?.text})",
style: GoogleFonts.poppins(fontSize: 13, fontWeight: FontWeight.w500, color: Colors.grey[700]),
),
),
Expanded(
child: SingleChildScrollView(
scrollDirection: Axis.vertical,
child: DataTable(
dividerThickness: 0.5,
columnSpacing: isDesktop ? 24.0 : 16.0,
border: TableBorder(
horizontalInside: BorderSide(
width: 0.5,
color: Colors.grey.shade200,
),
),
columns: [
DataColumn(label: Text('Plan Id', style: GoogleFonts.poppins(fontSize: 12, fontWeight: FontWeight.w600))),
DataColumn(label: Text('Employee Code', style: GoogleFonts.poppins(fontSize: 12, fontWeight: FontWeight.w600))),
DataColumn(label: Text('SO Number', style: GoogleFonts.poppins(fontSize: 12, fontWeight: FontWeight.w600))),
DataColumn(label: Text('Department', style: GoogleFonts.poppins(fontSize: 12, fontWeight: FontWeight.w600))),
DataColumn(label: Text('Flight Trip Type', style: GoogleFonts.poppins(fontSize: 12, fontWeight: FontWeight.w600))),
DataColumn(label: Text('Plan Trip Type', style: GoogleFonts.poppins(fontSize: 12, fontWeight: FontWeight.w600))),
DataColumn(label: Text('Sector', style: GoogleFonts.poppins(fontSize: 12, fontWeight: FontWeight.w600))),
],
rows: internationData.map((entry) {
return DataRow(cells: [
DataCell(Text('${entry['plan_id']}', style: GoogleFonts.poppins(fontSize: 12))),
DataCell(Text('${entry['employee_code']}', style: GoogleFonts.poppins(fontSize: 12))),
DataCell(Text('${entry['so_number']}', style: GoogleFonts.poppins(fontSize: 12))),
DataCell(Text('${entry['functional_department']}', style: GoogleFonts.poppins(fontSize: 12))),
DataCell(Text('${entry['flight_trip_type']}', style: GoogleFonts.poppins(fontSize: 12))),
DataCell(Text('${entry['plan_trip_type']}', style: GoogleFonts.poppins(fontSize: 12))),
DataCell(Text('${entry['sector']}', style: GoogleFonts.poppins(fontSize: 12))),
]);
}).toList(),
),
),
),
],
),
],
),
),
],
),
),
SizedBox(height: 15),
],
),
);
}
}

View File

@ -0,0 +1,651 @@
import 'dart:convert';
import 'dart:core';
import 'package:go_router/go_router.dart';
import 'package:google_fonts/google_fonts.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 '../../routes/mainLayout.dart';
import '../../services/apiService.dart';
import '../../utils/auth_utils.dart';
import '../../widgets/custom_text_field.dart';
class MiscellaneousAir extends StatefulWidget {
const MiscellaneousAir({super.key});
@override
_MiscellaneousAirState createState() => _MiscellaneousAirState();
}
class _MiscellaneousAirState extends State<MiscellaneousAir> {
final ApiService apiService = ApiService();
String? userId;
String? orgId;
String? roleUser;
String? token;
Color? layoutColor;
Color? bodyColor;
Map<String, FocusNode> focusNodes = {};
Map<String, bool> focusStates = {};
Map<String, TextEditingController> textControllers = {};
Map<String, String> errorMessages = {};
// Map<String, dynamic> MiscellaneousAirReport = {};
List<Map<String, dynamic>> domesticData = [];
List<Map<String, dynamic>> internationData = [];
// Map<String, dynamic> domesticData = {};
// Map<String, dynamic> internationData = {};
@override
void initState() {
super.initState();
if (!textControllers.containsKey("from_date")) {
textControllers["from_date"] = TextEditingController();
}
if (!textControllers.containsKey("to_date")) {
textControllers["to_date"] = TextEditingController();
}
if (!focusNodes.containsKey("from_date")) {
focusNodes["from_date"] = FocusNode();
}
if (!focusNodes.containsKey("to_date")) {
focusNodes["to_date"] = FocusNode();
}
if (!focusStates.containsKey("to_date")) {
focusStates["to_date"] = false;
}
if (!focusStates.containsKey("from_date")) {
focusStates["from_date"] = false;
}
// Add focus listeners
focusNodes.forEach((key, focusNode) {
_addFocusListener(focusNode, (focus) {
setState(() {
focusStates[key] = focus;
});
});
});
getToken();
// fetchMiscellaneousAirReport();
}
void _addFocusListener(FocusNode node, Function(bool) updateState) {
node.addListener(() {
setState(() {
updateState(node.hasFocus);
});
});
}
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();
if (token == null || userId == null) {
print("Token or USerId missing");
return;
}
}
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');
}
Future<void> handleSubmit() async {
errorMessages.remove("from_date");
errorMessages.remove("to_date");
final fromDateText = textControllers["from_date"]?.text;
final toDateText = textControllers["to_date"]?.text;
if (fromDateText == null || fromDateText.isEmpty ||
toDateText == null || toDateText.isEmpty) {
errorMessages["from_date"] = "Required";
errorMessages["to_date"] = "Required";
setState(() {});
return;
}
// Parse the string to DateTime using the correct format
final dateFormat = DateFormat('dd-MM-yyyy');
final DateTime fromDate = dateFormat.parse(fromDateText);
final DateTime toDate = dateFormat.parse(toDateText);
// Check if from date is after to date
if (fromDate.isAfter(toDate)) {
errorMessages["to_date"] = "Invalid Date Range";
setState(() {});
return;
}
// Format for API request
final formattedfromDate = DateFormat('yyyy-MM-dd').format(fromDate);
final formattedtoDate = DateFormat('yyyy-MM-dd').format(toDate);
// Fetch and update
final result = await apiService.CallReports(
'misAirReport',
formattedfromDate,
formattedtoDate,
);
setState(() {
domesticData = List<Map<String, dynamic>>.from(result['data']['domestic'] ?? []);
internationData = List<Map<String, dynamic>>.from(result['data']['international'] ?? []);
});
}
@override
// Widget build(BuildContext context) {
// // TODO: implement build
// throw UnimplementedError();
// }
Widget build(BuildContext context) {
return ResponsiveBuilder(
builder: (context, sizingInfo) {
bool isDesktop = sizingInfo.deviceScreenType == DeviceScreenType.desktop;
return Scaffold(
backgroundColor: 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:
MediaQuery.of(context).size.height *
0, // 5% of screen height as vertical padding
)
: EdgeInsets.all(0),
child: Row(
children: [
// if (isDesktop) CustomDrawer(isDesktop: true),
// const Expanded(child: Center(child: Text("User Page Content"))),
Expanded(child: buildMiscellaneousAirLayout(isDesktop))
],
),
),
);
},
);
}
Widget buildMiscellaneousAirLayout(bool isDesktop) {
return Container( child: buildMiscellaneousAirFormLayout(isDesktop) );
}
Widget buildMiscellaneousAirFormLayout(bool isDesktop) {
return Container(
margin: isDesktop ? EdgeInsets.all(10.0) : null,
padding: const EdgeInsets.only(top: 15, bottom: 15, left: 20, right: 20),
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)
: null,
color: isDesktop ? Colors.white : Color(0xFFFCFCFC),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// ------------------- First Row -------------------
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'Miscellaneous Air',
style: GoogleFonts.poppins(
fontSize: isDesktop ? 16 : 14,
fontWeight: FontWeight.w600,
color: Colors.black,
),
),
Row(
children: [
ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Color(0xFF114D8B),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
side: BorderSide(color: Color(0xFF114D8B), width: 2),
),
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
),
onPressed: () {
// Your Excel export logic here
},
child: Row(
children: [
Text(
"Excel",
style: GoogleFonts.poppins(
fontSize: isDesktop ? 13 : 11,
color: Colors.white,
),
),
SizedBox(width: 8),
Icon(Icons.file_present_outlined, size: 15, color: Colors.white),
],
),
),
SizedBox(width: 10),
ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Color(0xFF114D8B),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
side: BorderSide(color: Color(0xFF114D8B), width: 2),
),
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
),
onPressed: () {
// Your PDF export logic here
},
child: Row(
children: [
Text(
"PDF",
style: GoogleFonts.poppins(
fontSize: isDesktop ? 13 : 11,
color: Colors.white,
),
),
SizedBox(width: 8),
Icon(Icons.picture_as_pdf_outlined, size: 15, color: Colors.white),
],
),
),
],
),
],
),
SizedBox(height: 15),
// ------------------- Second Row -------------------
Row(
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"From Date *",
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74),
),
),
SizedBox(height: 5),
CustomTextFieldWrapper(
isFocused: focusStates["from_date"] ?? false,
isDesktop: isDesktop,
child: SizedBox(
height: 40,
child: GestureDetector(
onTap: () async {
final pickedDate = await showDatePicker(
context: context,
initialDate: DateTime.now(),
firstDate: DateTime(2000),
lastDate: DateTime(2100),
);
if (pickedDate != null) {
textControllers["from_date"]?.text =
DateFormat('dd-MM-yyyy').format(pickedDate);
}
},
child: AbsorbPointer(
child: TextField(
focusNode: focusNodes["from_date"],
controller: textControllers["from_date"],
readOnly: true,
style: const TextStyle(fontSize: 12),
decoration: InputDecoration(
labelText: "Select Date",
labelStyle: const TextStyle(
fontSize: 12,
color: Colors.grey,
),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: const EdgeInsets.symmetric(vertical: 16),
suffixIcon: const Icon(
Icons.calendar_today,
size: 16,
color: Colors.grey,
),
),
),
),
),
),
),
if (errorMessages["from_date"] != null) ...[
SizedBox(height: 5), // Space before error message
Text(
errorMessages["from_date"]!,
style: TextStyle(color: Colors.red, fontSize: 12),
),
],
],
),
if (isDesktop) Spacer() else SizedBox(height: 8),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"To Date *",
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74),
),
),
SizedBox(height: 5),
CustomTextFieldWrapper(
isFocused: focusStates["to_date"] ?? false,
isDesktop: isDesktop,
child: SizedBox(
height: 40,
child: GestureDetector(
onTap: () async {
final fromDateText = textControllers["from_date"]?.text;
DateTime? mintoDate;
if (fromDateText != null && fromDateText.isNotEmpty) {
mintoDate = DateFormat('dd-MM-yyyy').parse(fromDateText);
}
final pickedDate = await showDatePicker(
context: context,
initialDate: mintoDate ?? DateTime.now(),
firstDate: mintoDate ?? DateTime(2000),
lastDate: DateTime(2100),
);
if (pickedDate != null) {
textControllers["to_date"]?.text =
DateFormat('dd-MM-yyyy').format(pickedDate);
}
},
child: AbsorbPointer(
child: TextField(
focusNode: focusNodes["to_date"],
controller: textControllers["to_date"],
readOnly: true,
style: const TextStyle(fontSize: 12),
decoration: const InputDecoration(
labelText: "Select Date",
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16),
suffixIcon: Icon(
Icons.calendar_today,
size: 16,
color: Colors.grey,
),
),
),
),
),
),
),
if (errorMessages["to_date"] != null) ...[
SizedBox(height: 5), // Space before error message
Text(
errorMessages["to_date"]!,
style: const TextStyle(color: Colors.red, fontSize: 12),
maxLines: 2, // Allow it to wrap onto two lines
overflow:
TextOverflow.ellipsis, // Add ellipsis if it still overflows
),
],
],
),
if (isDesktop) Spacer() else SizedBox(height: 8),
SizedBox(
child: ElevatedButton(
onPressed: () {
handleSubmit();
// You can get text from commentController.text
// Navigator.of(context).pop(); // Close the modal
},
style: ElevatedButton.styleFrom(
// backgroundColor: layoutColor,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
child: Text(
'Search',
style: GoogleFonts.poppins(
fontSize: 11,
color: Colors.white,
),
),
),
),
],
),
SizedBox(height: 15),
// ------------------- Third Row -------------------
DefaultTabController(
length: 2,
child: Column(
children: [
TabBar(
labelColor: Colors.black,
unselectedLabelColor: Colors.grey,
indicatorColor: Color(0xFF114D8B),
tabs: [
Tab(text: "Domestic"),
Tab(text: "International"),
],
),
SizedBox(height: 10),
Container(
height: 200, // Or use Expanded for flexible height
child: TabBarView(
children: [
// Domestic Tab
domesticData.isEmpty
? Center(
child: Padding(
padding: EdgeInsets.all(16),
child: Text(
"No data found",
style: GoogleFonts.poppins(fontSize: 14, fontWeight: FontWeight.w500),
),
),
)
: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8),
child: Text(
"Domestic Report (From: ${textControllers["from_date"]?.text} To: ${textControllers["to_date"]?.text})",
style: GoogleFonts.poppins(fontSize: 13, fontWeight: FontWeight.w500, color: Colors.grey[700]),
),
),
Expanded(
child: SingleChildScrollView(
scrollDirection: Axis.vertical,
child: DataTable(
dividerThickness: 0.5,
columnSpacing: isDesktop ? 24.0 : 16.0,
border: TableBorder(
horizontalInside: BorderSide(
width: 0.5,
color: Colors.grey.shade200,
),
),
columns: [
DataColumn(label: Text('Plan Id', style: GoogleFonts.poppins(fontSize: 12, fontWeight: FontWeight.w600))),
DataColumn(label: Text('Employee Code', style: GoogleFonts.poppins(fontSize: 12, fontWeight: FontWeight.w600))),
DataColumn(label: Text('SO Number', style: GoogleFonts.poppins(fontSize: 12, fontWeight: FontWeight.w600))),
DataColumn(label: Text('Department', style: GoogleFonts.poppins(fontSize: 12, fontWeight: FontWeight.w600))),
DataColumn(label: Text('Flight Trip Type', style: GoogleFonts.poppins(fontSize: 12, fontWeight: FontWeight.w600))),
DataColumn(label: Text('Plan Trip Type', style: GoogleFonts.poppins(fontSize: 12, fontWeight: FontWeight.w600))),
DataColumn(label: Text('Sector', style: GoogleFonts.poppins(fontSize: 12, fontWeight: FontWeight.w600))),
],
rows: domesticData.map((entry) {
return DataRow(cells: [
DataCell(Text('${entry['plan_id']}', style: GoogleFonts.poppins(fontSize: 12))),
DataCell(Text('${entry['employee_code']}', style: GoogleFonts.poppins(fontSize: 12))),
DataCell(Text('${entry['so_number']}', style: GoogleFonts.poppins(fontSize: 12))),
DataCell(Text('${entry['functional_department']}', style: GoogleFonts.poppins(fontSize: 12))),
DataCell(Text('${entry['flight_trip_type']}', style: GoogleFonts.poppins(fontSize: 12))),
DataCell(Text('${entry['plan_trip_type']}', style: GoogleFonts.poppins(fontSize: 12))),
DataCell(Text('${entry['sector']}', style: GoogleFonts.poppins(fontSize: 12))),
]);
}).toList(),
),
),
),
],
),
// International Tab
internationData.isEmpty
? Center(
child: Padding(
padding: EdgeInsets.all(16),
child: Text(
"No data found",
style: GoogleFonts.poppins(fontSize: 14, fontWeight: FontWeight.w500),
),
),
)
: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8),
child: Text(
"International Report (From: ${textControllers["from_date"]?.text} To: ${textControllers["to_date"]?.text})",
style: GoogleFonts.poppins(fontSize: 13, fontWeight: FontWeight.w500, color: Colors.grey[700]),
),
),
Expanded(
child: SingleChildScrollView(
scrollDirection: Axis.vertical,
child: DataTable(
dividerThickness: 0.5,
columnSpacing: isDesktop ? 24.0 : 16.0,
border: TableBorder(
horizontalInside: BorderSide(
width: 0.5,
color: Colors.grey.shade200,
),
),
columns: [
DataColumn(label: Text('Plan Id', style: GoogleFonts.poppins(fontSize: 12, fontWeight: FontWeight.w600))),
DataColumn(label: Text('Employee Code', style: GoogleFonts.poppins(fontSize: 12, fontWeight: FontWeight.w600))),
DataColumn(label: Text('SO Number', style: GoogleFonts.poppins(fontSize: 12, fontWeight: FontWeight.w600))),
DataColumn(label: Text('Department', style: GoogleFonts.poppins(fontSize: 12, fontWeight: FontWeight.w600))),
DataColumn(label: Text('Flight Trip Type', style: GoogleFonts.poppins(fontSize: 12, fontWeight: FontWeight.w600))),
DataColumn(label: Text('Plan Trip Type', style: GoogleFonts.poppins(fontSize: 12, fontWeight: FontWeight.w600))),
DataColumn(label: Text('Sector', style: GoogleFonts.poppins(fontSize: 12, fontWeight: FontWeight.w600))),
],
rows: internationData.map((entry) {
return DataRow(cells: [
DataCell(Text('${entry['plan_id']}', style: GoogleFonts.poppins(fontSize: 12))),
DataCell(Text('${entry['employee_code']}', style: GoogleFonts.poppins(fontSize: 12))),
DataCell(Text('${entry['so_number']}', style: GoogleFonts.poppins(fontSize: 12))),
DataCell(Text('${entry['functional_department']}', style: GoogleFonts.poppins(fontSize: 12))),
DataCell(Text('${entry['flight_trip_type']}', style: GoogleFonts.poppins(fontSize: 12))),
DataCell(Text('${entry['plan_trip_type']}', style: GoogleFonts.poppins(fontSize: 12))),
DataCell(Text('${entry['sector']}', style: GoogleFonts.poppins(fontSize: 12))),
]);
}).toList(),
),
),
),
],
),
],
),
),
],
),
),
SizedBox(height: 15),
],
),
);
}
}

View File

@ -0,0 +1,666 @@
import 'dart:convert';
import 'dart:core';
import 'package:go_router/go_router.dart';
import 'package:google_fonts/google_fonts.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 '../../routes/mainLayout.dart';
import '../../services/apiService.dart';
import '../../utils/auth_utils.dart';
import '../../widgets/custom_text_field.dart';
class MiscellaneousForex extends StatefulWidget {
const MiscellaneousForex({super.key});
@override
_MiscellaneousForexState createState() => _MiscellaneousForexState();
}
class _MiscellaneousForexState extends State<MiscellaneousForex> {
final ApiService apiService = ApiService();
String? userId;
String? orgId;
String? roleUser;
String? token;
Color? layoutColor;
Color? bodyColor;
Map<String, FocusNode> focusNodes = {};
Map<String, bool> focusStates = {};
Map<String, TextEditingController> textControllers = {};
Map<String, String> errorMessages = {};
// Map<String, dynamic> MiscellaneousForexReport = {};
List<Map<String, dynamic>> domesticData = [];
List<Map<String, dynamic>> internationData = [];
// Map<String, dynamic> domesticData = {};
// Map<String, dynamic> internationData = {};
@override
void initState() {
super.initState();
if (!textControllers.containsKey("from_date")) {
textControllers["from_date"] = TextEditingController();
}
if (!textControllers.containsKey("to_date")) {
textControllers["to_date"] = TextEditingController();
}
if (!focusNodes.containsKey("from_date")) {
focusNodes["from_date"] = FocusNode();
}
if (!focusNodes.containsKey("to_date")) {
focusNodes["to_date"] = FocusNode();
}
if (!focusStates.containsKey("to_date")) {
focusStates["to_date"] = false;
}
if (!focusStates.containsKey("from_date")) {
focusStates["from_date"] = false;
}
// Add focus listeners
focusNodes.forEach((key, focusNode) {
_addFocusListener(focusNode, (focus) {
setState(() {
focusStates[key] = focus;
});
});
});
getToken();
// fetchMiscellaneousForexReport();
}
void _addFocusListener(FocusNode node, Function(bool) updateState) {
node.addListener(() {
setState(() {
updateState(node.hasFocus);
});
});
}
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();
if (token == null || userId == null) {
print("Token or USerId missing");
return;
}
}
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');
}
Future<void> handleSubmit() async {
errorMessages.remove("from_date");
errorMessages.remove("to_date");
final fromDateText = textControllers["from_date"]?.text;
final toDateText = textControllers["to_date"]?.text;
if (fromDateText == null || fromDateText.isEmpty ||
toDateText == null || toDateText.isEmpty) {
errorMessages["from_date"] = "Required";
errorMessages["to_date"] = "Required";
setState(() {});
return;
}
// Parse the string to DateTime using the correct format
final dateFormat = DateFormat('dd-MM-yyyy');
final DateTime fromDate = dateFormat.parse(fromDateText);
final DateTime toDate = dateFormat.parse(toDateText);
// Check if from date is after to date
if (fromDate.isAfter(toDate)) {
errorMessages["to_date"] = "Invalid Date Range";
setState(() {});
return;
}
// Format for API request
final formattedfromDate = DateFormat('yyyy-MM-dd').format(fromDate);
final formattedtoDate = DateFormat('yyyy-MM-dd').format(toDate);
// Fetch and update
final result = await apiService.CallReports(
'misForexReport',
formattedfromDate,
formattedtoDate,
);
setState(() {
domesticData = List<Map<String, dynamic>>.from(result['data']['domestic'] ?? []);
internationData = List<Map<String, dynamic>>.from(result['data']['international'] ?? []);
});
}
@override
// Widget build(BuildContext context) {
// // TODO: implement build
// throw UnimplementedError();
// }
Widget build(BuildContext context) {
return ResponsiveBuilder(
builder: (context, sizingInfo) {
bool isDesktop = sizingInfo.deviceScreenType == DeviceScreenType.desktop;
return Scaffold(
backgroundColor: 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:
MediaQuery.of(context).size.height *
0, // 5% of screen height as vertical padding
)
: EdgeInsets.all(0),
child: Row(
children: [
// if (isDesktop) CustomDrawer(isDesktop: true),
// const Expanded(child: Center(child: Text("User Page Content"))),
Expanded(child: buildMiscellaneousForexLayout(isDesktop))
],
),
),
);
},
);
}
Widget buildMiscellaneousForexLayout(bool isDesktop) {
return Container( child: buildMiscellaneousForexFormLayout(isDesktop) );
}
Widget buildMiscellaneousForexFormLayout(bool isDesktop) {
return Container(
margin: isDesktop ? EdgeInsets.all(10.0) : null,
padding: const EdgeInsets.only(top: 15, bottom: 15, left: 20, right: 20),
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)
: null,
color: isDesktop ? Colors.white : Color(0xFFFCFCFC),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// ------------------- First Row -------------------
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'Miscellaneous Forex',
style: GoogleFonts.poppins(
fontSize: isDesktop ? 16 : 14,
fontWeight: FontWeight.w600,
color: Colors.black,
),
),
Row(
children: [
ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Color(0xFF114D8B),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
side: BorderSide(color: Color(0xFF114D8B), width: 2),
),
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
),
onPressed: () {
// Your Excel export logic here
},
child: Row(
children: [
Text(
"Excel",
style: GoogleFonts.poppins(
fontSize: isDesktop ? 13 : 11,
color: Colors.white,
),
),
SizedBox(width: 8),
Icon(Icons.file_present_outlined, size: 15, color: Colors.white),
],
),
),
SizedBox(width: 10),
ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Color(0xFF114D8B),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
side: BorderSide(color: Color(0xFF114D8B), width: 2),
),
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
),
onPressed: () {
// Your PDF export logic here
},
child: Row(
children: [
Text(
"PDF",
style: GoogleFonts.poppins(
fontSize: isDesktop ? 13 : 11,
color: Colors.white,
),
),
SizedBox(width: 8),
Icon(Icons.picture_as_pdf_outlined, size: 15, color: Colors.white),
],
),
),
],
),
],
),
SizedBox(height: 15),
// ------------------- Second Row -------------------
Row(
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"From Date *",
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74),
),
),
SizedBox(height: 5),
CustomTextFieldWrapper(
isFocused: focusStates["from_date"] ?? false,
isDesktop: isDesktop,
child: SizedBox(
height: 40,
child: GestureDetector(
onTap: () async {
final pickedDate = await showDatePicker(
context: context,
initialDate: DateTime.now(),
firstDate: DateTime(2000),
lastDate: DateTime(2100),
);
if (pickedDate != null) {
textControllers["from_date"]?.text =
DateFormat('dd-MM-yyyy').format(pickedDate);
}
},
child: AbsorbPointer(
child: TextField(
focusNode: focusNodes["from_date"],
controller: textControllers["from_date"],
readOnly: true,
style: const TextStyle(fontSize: 12),
decoration: InputDecoration(
labelText: "Select Date",
labelStyle: const TextStyle(
fontSize: 12,
color: Colors.grey,
),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: const EdgeInsets.symmetric(vertical: 16),
suffixIcon: const Icon(
Icons.calendar_today,
size: 16,
color: Colors.grey,
),
),
),
),
),
),
),
if (errorMessages["from_date"] != null) ...[
SizedBox(height: 5), // Space before error message
Text(
errorMessages["from_date"]!,
style: TextStyle(color: Colors.red, fontSize: 12),
),
],
],
),
if (isDesktop) Spacer() else SizedBox(height: 8),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"To Date *",
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74),
),
),
SizedBox(height: 5),
CustomTextFieldWrapper(
isFocused: focusStates["to_date"] ?? false,
isDesktop: isDesktop,
child: SizedBox(
height: 40,
child: GestureDetector(
onTap: () async {
final fromDateText = textControllers["from_date"]?.text;
DateTime? mintoDate;
if (fromDateText != null && fromDateText.isNotEmpty) {
mintoDate = DateFormat('dd-MM-yyyy').parse(fromDateText);
}
final pickedDate = await showDatePicker(
context: context,
initialDate: mintoDate ?? DateTime.now(),
firstDate: mintoDate ?? DateTime(2000),
lastDate: DateTime(2100),
);
if (pickedDate != null) {
textControllers["to_date"]?.text =
DateFormat('dd-MM-yyyy').format(pickedDate);
}
},
child: AbsorbPointer(
child: TextField(
focusNode: focusNodes["to_date"],
controller: textControllers["to_date"],
readOnly: true,
style: const TextStyle(fontSize: 12),
decoration: const InputDecoration(
labelText: "Select Date",
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16),
suffixIcon: Icon(
Icons.calendar_today,
size: 16,
color: Colors.grey,
),
),
),
),
),
),
),
if (errorMessages["to_date"] != null) ...[
SizedBox(height: 5), // Space before error message
Text(
errorMessages["to_date"]!,
style: const TextStyle(color: Colors.red, fontSize: 12),
maxLines: 2, // Allow it to wrap onto two lines
overflow:
TextOverflow.ellipsis, // Add ellipsis if it still overflows
),
],
],
),
if (isDesktop) Spacer() else SizedBox(height: 8),
SizedBox(
child: 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: () {handleSubmit();},
child: Row(
mainAxisSize:
MainAxisSize.min, // Ensures content fits nicely
children: [
Text(
"Search",
style: GoogleFonts.poppins(
fontSize: isDesktop ? 13 : 11,
),
),
SizedBox(width: 8), // spacing between icon and text
Icon(
Icons.search,
size: 15,
color: Colors.white,
),
],
),
),
),
],
),
SizedBox(height: 15),
// ------------------- Third Row -------------------
DefaultTabController(
length: 2,
child: Column(
children: [
TabBar(
labelColor: Colors.black,
unselectedLabelColor: Colors.grey,
indicatorColor: Color(0xFF114D8B),
tabs: [
Tab(text: "Domestic"),
Tab(text: "International"),
],
),
SizedBox(height: 10),
Container(
height: 200, // Or use Expanded for flexible height
child: TabBarView(
children: [
// Domestic Tab
domesticData.isEmpty
? Center(
child: Padding(
padding: EdgeInsets.all(16),
child: Text(
"No data found",
style: GoogleFonts.poppins(fontSize: 14, fontWeight: FontWeight.w500),
),
),
)
: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8),
child: Text(
"Domestic Report (From: ${textControllers["from_date"]?.text} To: ${textControllers["to_date"]?.text})",
style: GoogleFonts.poppins(fontSize: 13, fontWeight: FontWeight.w500, color: Colors.grey[700]),
),
),
Expanded(
child: SingleChildScrollView(
scrollDirection: Axis.vertical,
child: DataTable(
dividerThickness: 0.5,
columnSpacing: isDesktop ? 24.0 : 16.0,
border: TableBorder(
horizontalInside: BorderSide(
width: 0.5,
color: Colors.grey.shade200,
),
),
columns: [
DataColumn(label: Text('Plan Id', style: GoogleFonts.poppins(fontSize: 12, fontWeight: FontWeight.w600))),
DataColumn(label: Text('Employee Code', style: GoogleFonts.poppins(fontSize: 12, fontWeight: FontWeight.w600))),
DataColumn(label: Text('SO Number', style: GoogleFonts.poppins(fontSize: 12, fontWeight: FontWeight.w600))),
DataColumn(label: Text('Department', style: GoogleFonts.poppins(fontSize: 12, fontWeight: FontWeight.w600))),
DataColumn(label: Text('Flight Trip Type', style: GoogleFonts.poppins(fontSize: 12, fontWeight: FontWeight.w600))),
DataColumn(label: Text('Plan Trip Type', style: GoogleFonts.poppins(fontSize: 12, fontWeight: FontWeight.w600))),
DataColumn(label: Text('Sector', style: GoogleFonts.poppins(fontSize: 12, fontWeight: FontWeight.w600))),
],
rows: domesticData.map((entry) {
return DataRow(cells: [
DataCell(Text('${entry['plan_id']}', style: GoogleFonts.poppins(fontSize: 12))),
DataCell(Text('${entry['employee_code']}', style: GoogleFonts.poppins(fontSize: 12))),
DataCell(Text('${entry['so_number']}', style: GoogleFonts.poppins(fontSize: 12))),
DataCell(Text('${entry['functional_department']}', style: GoogleFonts.poppins(fontSize: 12))),
DataCell(Text('${entry['flight_trip_type']}', style: GoogleFonts.poppins(fontSize: 12))),
DataCell(Text('${entry['plan_trip_type']}', style: GoogleFonts.poppins(fontSize: 12))),
DataCell(Text('${entry['sector']}', style: GoogleFonts.poppins(fontSize: 12))),
]);
}).toList(),
),
),
),
],
),
// International Tab
internationData.isEmpty
? Center(
child: Padding(
padding: EdgeInsets.all(16),
child: Text(
"No data found",
style: GoogleFonts.poppins(fontSize: 14, fontWeight: FontWeight.w500),
),
),
)
: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8),
child: Text(
"International Report (From: ${textControllers["from_date"]?.text} To: ${textControllers["to_date"]?.text})",
style: GoogleFonts.poppins(fontSize: 13, fontWeight: FontWeight.w500, color: Colors.grey[700]),
),
),
Expanded(
child: SingleChildScrollView(
scrollDirection: Axis.vertical,
child: DataTable(
dividerThickness: 0.5,
columnSpacing: isDesktop ? 24.0 : 16.0,
border: TableBorder(
horizontalInside: BorderSide(
width: 0.5,
color: Colors.grey.shade200,
),
),
columns: [
DataColumn(label: Text('Plan Id', style: GoogleFonts.poppins(fontSize: 12, fontWeight: FontWeight.w600))),
DataColumn(label: Text('Employee Code', style: GoogleFonts.poppins(fontSize: 12, fontWeight: FontWeight.w600))),
DataColumn(label: Text('SO Number', style: GoogleFonts.poppins(fontSize: 12, fontWeight: FontWeight.w600))),
DataColumn(label: Text('Department', style: GoogleFonts.poppins(fontSize: 12, fontWeight: FontWeight.w600))),
DataColumn(label: Text('Flight Trip Type', style: GoogleFonts.poppins(fontSize: 12, fontWeight: FontWeight.w600))),
DataColumn(label: Text('Plan Trip Type', style: GoogleFonts.poppins(fontSize: 12, fontWeight: FontWeight.w600))),
DataColumn(label: Text('Sector', style: GoogleFonts.poppins(fontSize: 12, fontWeight: FontWeight.w600))),
],
rows: internationData.map((entry) {
return DataRow(cells: [
DataCell(Text('${entry['plan_id']}', style: GoogleFonts.poppins(fontSize: 12))),
DataCell(Text('${entry['employee_code']}', style: GoogleFonts.poppins(fontSize: 12))),
DataCell(Text('${entry['so_number']}', style: GoogleFonts.poppins(fontSize: 12))),
DataCell(Text('${entry['functional_department']}', style: GoogleFonts.poppins(fontSize: 12))),
DataCell(Text('${entry['flight_trip_type']}', style: GoogleFonts.poppins(fontSize: 12))),
DataCell(Text('${entry['plan_trip_type']}', style: GoogleFonts.poppins(fontSize: 12))),
DataCell(Text('${entry['sector']}', style: GoogleFonts.poppins(fontSize: 12))),
]);
}).toList(),
),
),
),
],
),
],
),
),
],
),
),
SizedBox(height: 15),
],
),
);
}
}

View File

@ -0,0 +1,651 @@
import 'dart:convert';
import 'dart:core';
import 'package:go_router/go_router.dart';
import 'package:google_fonts/google_fonts.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 '../../routes/mainLayout.dart';
import '../../services/apiService.dart';
import '../../utils/auth_utils.dart';
import '../../widgets/custom_text_field.dart';
class MiscellaneousHotel extends StatefulWidget {
const MiscellaneousHotel({super.key});
@override
_MiscellaneousHotelState createState() => _MiscellaneousHotelState();
}
class _MiscellaneousHotelState extends State<MiscellaneousHotel> {
final ApiService apiService = ApiService();
String? userId;
String? orgId;
String? roleUser;
String? token;
Color? layoutColor;
Color? bodyColor;
Map<String, FocusNode> focusNodes = {};
Map<String, bool> focusStates = {};
Map<String, TextEditingController> textControllers = {};
Map<String, String> errorMessages = {};
// Map<String, dynamic> MiscellaneousHotelReport = {};
List<Map<String, dynamic>> domesticData = [];
List<Map<String, dynamic>> internationData = [];
// Map<String, dynamic> domesticData = {};
// Map<String, dynamic> internationData = {};
@override
void initState() {
super.initState();
if (!textControllers.containsKey("from_date")) {
textControllers["from_date"] = TextEditingController();
}
if (!textControllers.containsKey("to_date")) {
textControllers["to_date"] = TextEditingController();
}
if (!focusNodes.containsKey("from_date")) {
focusNodes["from_date"] = FocusNode();
}
if (!focusNodes.containsKey("to_date")) {
focusNodes["to_date"] = FocusNode();
}
if (!focusStates.containsKey("to_date")) {
focusStates["to_date"] = false;
}
if (!focusStates.containsKey("from_date")) {
focusStates["from_date"] = false;
}
// Add focus listeners
focusNodes.forEach((key, focusNode) {
_addFocusListener(focusNode, (focus) {
setState(() {
focusStates[key] = focus;
});
});
});
getToken();
// fetchMiscellaneousHotelReport();
}
void _addFocusListener(FocusNode node, Function(bool) updateState) {
node.addListener(() {
setState(() {
updateState(node.hasFocus);
});
});
}
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();
if (token == null || userId == null) {
print("Token or USerId missing");
return;
}
}
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');
}
Future<void> handleSubmit() async {
errorMessages.remove("from_date");
errorMessages.remove("to_date");
final fromDateText = textControllers["from_date"]?.text;
final toDateText = textControllers["to_date"]?.text;
if (fromDateText == null || fromDateText.isEmpty ||
toDateText == null || toDateText.isEmpty) {
errorMessages["from_date"] = "Required";
errorMessages["to_date"] = "Required";
setState(() {});
return;
}
// Parse the string to DateTime using the correct format
final dateFormat = DateFormat('dd-MM-yyyy');
final DateTime fromDate = dateFormat.parse(fromDateText);
final DateTime toDate = dateFormat.parse(toDateText);
// Check if from date is after to date
if (fromDate.isAfter(toDate)) {
errorMessages["to_date"] = "Invalid Date Range";
setState(() {});
return;
}
// Format for API request
final formattedfromDate = DateFormat('yyyy-MM-dd').format(fromDate);
final formattedtoDate = DateFormat('yyyy-MM-dd').format(toDate);
// Fetch and update
final result = await apiService.CallReports(
'misHotelReport',
formattedfromDate,
formattedtoDate,
);
setState(() {
domesticData = List<Map<String, dynamic>>.from(result['data']['domestic'] ?? []);
internationData = List<Map<String, dynamic>>.from(result['data']['international'] ?? []);
});
}
@override
// Widget build(BuildContext context) {
// // TODO: implement build
// throw UnimplementedError();
// }
Widget build(BuildContext context) {
return ResponsiveBuilder(
builder: (context, sizingInfo) {
bool isDesktop = sizingInfo.deviceScreenType == DeviceScreenType.desktop;
return Scaffold(
backgroundColor: 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:
MediaQuery.of(context).size.height *
0, // 5% of screen height as vertical padding
)
: EdgeInsets.all(0),
child: Row(
children: [
// if (isDesktop) CustomDrawer(isDesktop: true),
// const Expanded(child: Center(child: Text("User Page Content"))),
Expanded(child: buildMiscellaneousHotelLayout(isDesktop))
],
),
),
);
},
);
}
Widget buildMiscellaneousHotelLayout(bool isDesktop) {
return Container( child: buildMiscellaneousHotelFormLayout(isDesktop) );
}
Widget buildMiscellaneousHotelFormLayout(bool isDesktop) {
return Container(
margin: isDesktop ? EdgeInsets.all(10.0) : null,
padding: const EdgeInsets.only(top: 15, bottom: 15, left: 20, right: 20),
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)
: null,
color: isDesktop ? Colors.white : Color(0xFFFCFCFC),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// ------------------- First Row -------------------
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'Miscellaneous Hotel',
style: GoogleFonts.poppins(
fontSize: isDesktop ? 16 : 14,
fontWeight: FontWeight.w600,
color: Colors.black,
),
),
Row(
children: [
ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Color(0xFF114D8B),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
side: BorderSide(color: Color(0xFF114D8B), width: 2),
),
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
),
onPressed: () {
// Your Excel export logic here
},
child: Row(
children: [
Text(
"Excel",
style: GoogleFonts.poppins(
fontSize: isDesktop ? 13 : 11,
color: Colors.white,
),
),
SizedBox(width: 8),
Icon(Icons.file_present_outlined, size: 15, color: Colors.white),
],
),
),
SizedBox(width: 10),
ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Color(0xFF114D8B),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
side: BorderSide(color: Color(0xFF114D8B), width: 2),
),
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
),
onPressed: () {
// Your PDF export logic here
},
child: Row(
children: [
Text(
"PDF",
style: GoogleFonts.poppins(
fontSize: isDesktop ? 13 : 11,
color: Colors.white,
),
),
SizedBox(width: 8),
Icon(Icons.picture_as_pdf_outlined, size: 15, color: Colors.white),
],
),
),
],
),
],
),
SizedBox(height: 15),
// ------------------- Second Row -------------------
Row(
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"From Date *",
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74),
),
),
SizedBox(height: 5),
CustomTextFieldWrapper(
isFocused: focusStates["from_date"] ?? false,
isDesktop: isDesktop,
child: SizedBox(
height: 40,
child: GestureDetector(
onTap: () async {
final pickedDate = await showDatePicker(
context: context,
initialDate: DateTime.now(),
firstDate: DateTime(2000),
lastDate: DateTime(2100),
);
if (pickedDate != null) {
textControllers["from_date"]?.text =
DateFormat('dd-MM-yyyy').format(pickedDate);
}
},
child: AbsorbPointer(
child: TextField(
focusNode: focusNodes["from_date"],
controller: textControllers["from_date"],
readOnly: true,
style: const TextStyle(fontSize: 12),
decoration: InputDecoration(
labelText: "Select Date",
labelStyle: const TextStyle(
fontSize: 12,
color: Colors.grey,
),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: const EdgeInsets.symmetric(vertical: 16),
suffixIcon: const Icon(
Icons.calendar_today,
size: 16,
color: Colors.grey,
),
),
),
),
),
),
),
if (errorMessages["from_date"] != null) ...[
SizedBox(height: 5), // Space before error message
Text(
errorMessages["from_date"]!,
style: TextStyle(color: Colors.red, fontSize: 12),
),
],
],
),
if (isDesktop) Spacer() else SizedBox(height: 8),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"To Date *",
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74),
),
),
SizedBox(height: 5),
CustomTextFieldWrapper(
isFocused: focusStates["to_date"] ?? false,
isDesktop: isDesktop,
child: SizedBox(
height: 40,
child: GestureDetector(
onTap: () async {
final fromDateText = textControllers["from_date"]?.text;
DateTime? mintoDate;
if (fromDateText != null && fromDateText.isNotEmpty) {
mintoDate = DateFormat('dd-MM-yyyy').parse(fromDateText);
}
final pickedDate = await showDatePicker(
context: context,
initialDate: mintoDate ?? DateTime.now(),
firstDate: mintoDate ?? DateTime(2000),
lastDate: DateTime(2100),
);
if (pickedDate != null) {
textControllers["to_date"]?.text =
DateFormat('dd-MM-yyyy').format(pickedDate);
}
},
child: AbsorbPointer(
child: TextField(
focusNode: focusNodes["to_date"],
controller: textControllers["to_date"],
readOnly: true,
style: const TextStyle(fontSize: 12),
decoration: const InputDecoration(
labelText: "Select Date",
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16),
suffixIcon: Icon(
Icons.calendar_today,
size: 16,
color: Colors.grey,
),
),
),
),
),
),
),
if (errorMessages["to_date"] != null) ...[
SizedBox(height: 5), // Space before error message
Text(
errorMessages["to_date"]!,
style: const TextStyle(color: Colors.red, fontSize: 12),
maxLines: 2, // Allow it to wrap onto two lines
overflow:
TextOverflow.ellipsis, // Add ellipsis if it still overflows
),
],
],
),
if (isDesktop) Spacer() else SizedBox(height: 8),
SizedBox(
child: ElevatedButton(
onPressed: () {
handleSubmit();
// You can get text from commentController.text
// Navigator.of(context).pop(); // Close the modal
},
style: ElevatedButton.styleFrom(
// backgroundColor: layoutColor,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
child: Text(
'Search',
style: GoogleFonts.poppins(
fontSize: 11,
color: Colors.white,
),
),
),
),
],
),
SizedBox(height: 15),
// ------------------- Third Row -------------------
DefaultTabController(
length: 2,
child: Column(
children: [
TabBar(
labelColor: Colors.black,
unselectedLabelColor: Colors.grey,
indicatorColor: Color(0xFF114D8B),
tabs: [
Tab(text: "Domestic"),
Tab(text: "International"),
],
),
SizedBox(height: 10),
Container(
height: 200, // Or use Expanded for flexible height
child: TabBarView(
children: [
// Domestic Tab
domesticData.isEmpty
? Center(
child: Padding(
padding: EdgeInsets.all(16),
child: Text(
"No data found",
style: GoogleFonts.poppins(fontSize: 14, fontWeight: FontWeight.w500),
),
),
)
: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8),
child: Text(
"Domestic Report (From: ${textControllers["from_date"]?.text} To: ${textControllers["to_date"]?.text})",
style: GoogleFonts.poppins(fontSize: 13, fontWeight: FontWeight.w500, color: Colors.grey[700]),
),
),
Expanded(
child: SingleChildScrollView(
scrollDirection: Axis.vertical,
child: DataTable(
dividerThickness: 0.5,
columnSpacing: isDesktop ? 24.0 : 16.0,
border: TableBorder(
horizontalInside: BorderSide(
width: 0.5,
color: Colors.grey.shade200,
),
),
columns: [
DataColumn(label: Text('Plan Id', style: GoogleFonts.poppins(fontSize: 12, fontWeight: FontWeight.w600))),
DataColumn(label: Text('Employee Code', style: GoogleFonts.poppins(fontSize: 12, fontWeight: FontWeight.w600))),
DataColumn(label: Text('SO Number', style: GoogleFonts.poppins(fontSize: 12, fontWeight: FontWeight.w600))),
DataColumn(label: Text('Department', style: GoogleFonts.poppins(fontSize: 12, fontWeight: FontWeight.w600))),
DataColumn(label: Text('Flight Trip Type', style: GoogleFonts.poppins(fontSize: 12, fontWeight: FontWeight.w600))),
DataColumn(label: Text('Plan Trip Type', style: GoogleFonts.poppins(fontSize: 12, fontWeight: FontWeight.w600))),
DataColumn(label: Text('Sector', style: GoogleFonts.poppins(fontSize: 12, fontWeight: FontWeight.w600))),
],
rows: domesticData.map((entry) {
return DataRow(cells: [
DataCell(Text('${entry['plan_id']}', style: GoogleFonts.poppins(fontSize: 12))),
DataCell(Text('${entry['employee_code']}', style: GoogleFonts.poppins(fontSize: 12))),
DataCell(Text('${entry['so_number']}', style: GoogleFonts.poppins(fontSize: 12))),
DataCell(Text('${entry['functional_department']}', style: GoogleFonts.poppins(fontSize: 12))),
DataCell(Text('${entry['flight_trip_type']}', style: GoogleFonts.poppins(fontSize: 12))),
DataCell(Text('${entry['plan_trip_type']}', style: GoogleFonts.poppins(fontSize: 12))),
DataCell(Text('${entry['sector']}', style: GoogleFonts.poppins(fontSize: 12))),
]);
}).toList(),
),
),
),
],
),
// International Tab
internationData.isEmpty
? Center(
child: Padding(
padding: EdgeInsets.all(16),
child: Text(
"No data found",
style: GoogleFonts.poppins(fontSize: 14, fontWeight: FontWeight.w500),
),
),
)
: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8),
child: Text(
"International Report (From: ${textControllers["from_date"]?.text} To: ${textControllers["to_date"]?.text})",
style: GoogleFonts.poppins(fontSize: 13, fontWeight: FontWeight.w500, color: Colors.grey[700]),
),
),
Expanded(
child: SingleChildScrollView(
scrollDirection: Axis.vertical,
child: DataTable(
dividerThickness: 0.5,
columnSpacing: isDesktop ? 24.0 : 16.0,
border: TableBorder(
horizontalInside: BorderSide(
width: 0.5,
color: Colors.grey.shade200,
),
),
columns: [
DataColumn(label: Text('Plan Id', style: GoogleFonts.poppins(fontSize: 12, fontWeight: FontWeight.w600))),
DataColumn(label: Text('Employee Code', style: GoogleFonts.poppins(fontSize: 12, fontWeight: FontWeight.w600))),
DataColumn(label: Text('SO Number', style: GoogleFonts.poppins(fontSize: 12, fontWeight: FontWeight.w600))),
DataColumn(label: Text('Department', style: GoogleFonts.poppins(fontSize: 12, fontWeight: FontWeight.w600))),
DataColumn(label: Text('Flight Trip Type', style: GoogleFonts.poppins(fontSize: 12, fontWeight: FontWeight.w600))),
DataColumn(label: Text('Plan Trip Type', style: GoogleFonts.poppins(fontSize: 12, fontWeight: FontWeight.w600))),
DataColumn(label: Text('Sector', style: GoogleFonts.poppins(fontSize: 12, fontWeight: FontWeight.w600))),
],
rows: internationData.map((entry) {
return DataRow(cells: [
DataCell(Text('${entry['plan_id']}', style: GoogleFonts.poppins(fontSize: 12))),
DataCell(Text('${entry['employee_code']}', style: GoogleFonts.poppins(fontSize: 12))),
DataCell(Text('${entry['so_number']}', style: GoogleFonts.poppins(fontSize: 12))),
DataCell(Text('${entry['functional_department']}', style: GoogleFonts.poppins(fontSize: 12))),
DataCell(Text('${entry['flight_trip_type']}', style: GoogleFonts.poppins(fontSize: 12))),
DataCell(Text('${entry['plan_trip_type']}', style: GoogleFonts.poppins(fontSize: 12))),
DataCell(Text('${entry['sector']}', style: GoogleFonts.poppins(fontSize: 12))),
]);
}).toList(),
),
),
),
],
),
],
),
),
],
),
),
SizedBox(height: 15),
],
),
);
}
}

View File

@ -0,0 +1,230 @@
import 'dart:convert';
import 'dart:core';
import 'package:go_router/go_router.dart';
import 'package:google_fonts/google_fonts.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 '../../routes/mainLayout.dart';
import '../../services/apiService.dart';
import '../../utils/auth_utils.dart';
class ReportList extends StatefulWidget {
const ReportList({super.key});
@override
_ReportListState createState() => _ReportListState();
}
class _ReportListState extends State<ReportList> {
final ApiService apiService = ApiService();
Color? layoutColor;
void initState() {
super.initState();
loadInitialData();
}
void loadInitialData() async {
String? layoutString = await getLayoutColor();
// String? bodyStringColor = await getBodyColor();
setState(() {
layoutColor =
layoutString != null
? Color(int.parse(layoutString))
: Colors.redAccent;
});
}
@override
Widget build(BuildContext context) {
return ResponsiveBuilder(
builder: (context, sizingInfo) {
bool isDesktop =
sizingInfo.deviceScreenType == DeviceScreenType.desktop;
return Scaffold(
backgroundColor: Color(0xFFf5f5f5),
// backgroundColor: Color(0xFFFCFCFC),
// appBar: isDesktop ? null : const CustomAppBar(title: 'Home'),
// drawer: isDesktop ? null : CustomDrawer(isDesktop: false),
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:
MediaQuery.of(context).size.height *
0, // 5% of screen height as vertical padding
)
: EdgeInsets.all(0),
child: Row(
children: [
// if (isDesktop) CustomDrawer(isDesktop: true),
Expanded(child: buildGroupListLayout(isDesktop)),
],
),
),
);
},
);
}
Widget buildGroupListLayout(bool isDesktop) {
final int cardsPerRow = 3;
final List<Map<String, dynamic>> menuItems = [
{
'value': '/advancePurchase',
'icon': Icons.trolley,
'label': 'Advance Purchase Report',
'description': 'View Advance Purchase Report',
},
{
'value': '/misServicesAnalysis',
'icon': Icons.miscellaneous_services,
'label': 'Miscellaneous Services Analysis',
'description': 'View Miscellaneous Services Analysis',
},
{
'value': '/misAirReport',
'icon': Icons.flight,
'label': 'Miscellaneous Air Report',
'description': 'View Miscellaneous Air Report',
},
{
'value': '/misHotelReport',
'icon': Icons.add_business,
'label': 'Miscellaneous Hotel Report',
'description': 'View Miscellaneous Hotel Report',
},
{
'value': '/misForexReport',
'icon': Icons.credit_card,
'label': 'Miscellaneous Forex Report',
'description': 'View Miscellaneous Forex Report',
},
{
'value': '/guestHouseReport',
'icon': Icons.add_home_work_sharp,
'label': 'Guest House Report',
'description': 'View Guest House Report',
}
];
return Container(
padding: EdgeInsets.symmetric(vertical: 16, horizontal: 12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Report List',
style: GoogleFonts.poppins(
fontSize: 15,
fontWeight: FontWeight.w500,
color: Colors.black87,
),
),
SizedBox(height: 8),
Divider(thickness: 0.2, color: Colors.blueGrey.shade100),
SizedBox(height: 12),
Expanded(
child: SingleChildScrollView(
child: Center(
child: Wrap(
spacing: 16,
runSpacing: 16,
children:
menuItems.map((item) {
return SizedBox(
width: isDesktop ? 325 : double.infinity,
child: Card(
color: Colors.white,
child: InkWell(
onTap: () async {
if (item['value'] as String ==
"/forexTexmplate") {
final data =
await apiService.getForexTemplate();
print("ForexId -- $data");
context.go(
'/templateForex',
extra: {'templateData': data},
);
} else {
final route = item['value'] as String;
context.go(route);
}
},
child: Padding(
padding: EdgeInsets.all(12),
child: Row(
children: [
Icon(
item['icon'],
size: 40,
color: layoutColor,
// color: Color(0xFF114D8B),
),
SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text(
item['label'],
style: GoogleFonts.poppins(
fontSize: 14,
fontWeight: FontWeight.w500,
),
),
SizedBox(height: 4),
Text(
item['description'],
maxLines: 1,
overflow: TextOverflow.ellipsis,
softWrap: false,
style: TextStyle(
fontSize: 14,
color: Colors.grey[700],
),
),
],
),
),
],
),
),
),
),
);
}).toList(),
),
),
),
),
],
),
);
}
}

View File

@ -0,0 +1,333 @@
import 'dart:convert';
import 'dart:core';
import 'package:go_router/go_router.dart';
import 'package:google_fonts/google_fonts.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 '../../routes/mainLayout.dart';
import '../../services/apiService.dart';
import '../../utils/auth_utils.dart';
class ReportSample extends StatefulWidget {
const ReportSample({super.key});
@override
_ReportSampleState createState() => _ReportSampleState();
}
class _ReportSampleState extends State<ReportSample> {
final ApiService apiService = ApiService();
String? userId;
String? orgId;
String? roleUser;
String? token;
Color? layoutColor;
Color? bodyColor;
@override
void initState() {
super.initState();
getToken();
}
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();
if (token == null || userId == null) {
print("Token or USerId missing");
return;
}
}
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');
}
@override
// Widget build(BuildContext context) {
// // TODO: implement build
// throw UnimplementedError();
// }
Widget build(BuildContext context) {
return ResponsiveBuilder(builder: (context, sizingInfo) {
bool isDesktop = sizingInfo.deviceScreenType == DeviceScreenType.desktop;
return Scaffold(
backgroundColor: Colors.white,
appBar: CustomAppBar(isDesktop: isDesktop),
drawer: CustomDrawer(isDesktop: false),
body: Row(
children: [
// if (isDesktop) CustomDrawer(isDesktop: true),
Expanded(child: buildReportSampleLayout(isDesktop))
],
),
);
});
}
Widget buildReportSampleLayout(bool isDesktop) {
return Container( child: buildFormLayout(isDesktop) );
}
Widget buildFormLayout(bool isDesktop) {
return Container(
margin: isDesktop ? EdgeInsets.all(10.0) : null,
padding: const EdgeInsets.only(top: 15, bottom: 15, left: 20, right: 20),
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)
: null,
color: isDesktop ? Colors.white : Color(0xFFFCFCFC),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// ------------------- First Row -------------------
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'ReportSample',
style: GoogleFonts.poppins(
fontSize: isDesktop ? 16 : 14,
fontWeight: FontWeight.w600,
color: Colors.black,
),
),
Row(
children: [
ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Color(0xFF114D8B),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
side: BorderSide(color: Color(0xFF114D8B), width: 2),
),
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
),
onPressed: () {
// Your Excel export logic here
},
child: Row(
children: [
Text(
"Excel",
style: GoogleFonts.poppins(
fontSize: isDesktop ? 13 : 11,
color: Colors.white,
),
),
SizedBox(width: 8),
Icon(Icons.file_present_outlined, size: 15, color: Colors.white),
],
),
),
SizedBox(width: 10),
ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Color(0xFF114D8B),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
side: BorderSide(color: Color(0xFF114D8B), width: 2),
),
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
),
onPressed: () {
// Your PDF export logic here
},
child: Row(
children: [
Text(
"PDF",
style: GoogleFonts.poppins(
fontSize: isDesktop ? 13 : 11,
color: Colors.white,
),
),
SizedBox(width: 8),
Icon(Icons.picture_as_pdf_outlined, size: 15, color: Colors.white),
],
),
),
],
),
],
),
SizedBox(height: 15),
// ------------------- Second Row -------------------
Row(
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("From Date", style: GoogleFonts.poppins(fontSize: 12)),
SizedBox(height: 5),
Container(
height: 40,
padding: EdgeInsets.symmetric(horizontal: 10),
decoration: BoxDecoration(
border: Border.all(color: Colors.grey.shade300),
borderRadius: BorderRadius.circular(8),
color: Colors.white,
),
child: Center(child: Text("Select From Date")), // Replace with date picker
),
],
),
),
SizedBox(width: 20),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text("To Date", style: GoogleFonts.poppins(fontSize: 12)),
SizedBox(height: 5),
Container(
height: 40,
padding: EdgeInsets.symmetric(horizontal: 10),
decoration: BoxDecoration(
border: Border.all(color: Colors.grey.shade300),
borderRadius: BorderRadius.circular(8),
color: Colors.white,
),
child: Center(child: Text("Select To Date")), // Replace with date picker
),
],
),
),
],
),
SizedBox(height: 15),
// ------------------- Third Row -------------------
DefaultTabController(
length: 2,
child: Column(
children: [
TabBar(
labelColor: Colors.black,
unselectedLabelColor: Colors.grey,
indicatorColor: Color(0xFF114D8B),
tabs: [
Tab(text: "Domestic"),
Tab(text: "International"),
],
),
SizedBox(height: 10),
Container(
height: 200, // Set desired height for tab content
child: TabBarView(
children: [
Center(child: Text("Domestic Content")),
Center(child: Text("International Content")),
],
),
),
],
),
),
// ------------------- Fourth Row (Table) -------------------
Expanded(
child: SingleChildScrollView(
scrollDirection: Axis.vertical,
child: DataTable(
headingRowColor: MaterialStateColor.resolveWith(
(states) => Color(0xFFE6F0FA)), // Light blue header
columnSpacing: 20,
columns: [
DataColumn(label: Text('ID', style: GoogleFonts.poppins(fontSize: 12, fontWeight: FontWeight.w600))),
DataColumn(label: Text('Name', style: GoogleFonts.poppins(fontSize: 12, fontWeight: FontWeight.w600))),
DataColumn(label: Text('Status', style: GoogleFonts.poppins(fontSize: 12, fontWeight: FontWeight.w600))),
DataColumn(label: Text('Date', style: GoogleFonts.poppins(fontSize: 12, fontWeight: FontWeight.w600))),
],
rows: List.generate(
10,
(index) => DataRow(
cells: [
DataCell(Text('#$index', style: GoogleFonts.poppins(fontSize: 12))),
DataCell(Text('User $index', style: GoogleFonts.poppins(fontSize: 12))),
DataCell(Text(index % 2 == 0 ? 'Active' : 'Inactive', style: GoogleFonts.poppins(fontSize: 12))),
DataCell(Text('13 Jun 2025', style: GoogleFonts.poppins(fontSize: 12))),
],
),
),
),
),
),
],
),
);
}
}

View File

@ -0,0 +1,666 @@
import 'dart:convert';
import 'dart:core';
import 'package:go_router/go_router.dart';
import 'package:google_fonts/google_fonts.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 '../../routes/mainLayout.dart';
import '../../services/apiService.dart';
import '../../utils/auth_utils.dart';
import '../../widgets/custom_text_field.dart';
class ServicesAnalysis extends StatefulWidget {
const ServicesAnalysis({super.key});
@override
_ServicesAnalysisState createState() => _ServicesAnalysisState();
}
class _ServicesAnalysisState extends State<ServicesAnalysis> {
final ApiService apiService = ApiService();
String? userId;
String? orgId;
String? roleUser;
String? token;
Color? layoutColor;
Color? bodyColor;
Map<String, FocusNode> focusNodes = {};
Map<String, bool> focusStates = {};
Map<String, TextEditingController> textControllers = {};
Map<String, String> errorMessages = {};
// Map<String, dynamic> ServicesAnalysisReport = {};
List<Map<String, dynamic>> domesticData = [];
List<Map<String, dynamic>> internationData = [];
// Map<String, dynamic> domesticData = {};
// Map<String, dynamic> internationData = {};
@override
void initState() {
super.initState();
if (!textControllers.containsKey("from_date")) {
textControllers["from_date"] = TextEditingController();
}
if (!textControllers.containsKey("to_date")) {
textControllers["to_date"] = TextEditingController();
}
if (!focusNodes.containsKey("from_date")) {
focusNodes["from_date"] = FocusNode();
}
if (!focusNodes.containsKey("to_date")) {
focusNodes["to_date"] = FocusNode();
}
if (!focusStates.containsKey("to_date")) {
focusStates["to_date"] = false;
}
if (!focusStates.containsKey("from_date")) {
focusStates["from_date"] = false;
}
// Add focus listeners
focusNodes.forEach((key, focusNode) {
_addFocusListener(focusNode, (focus) {
setState(() {
focusStates[key] = focus;
});
});
});
getToken();
// fetchServicesAnalysisReport();
}
void _addFocusListener(FocusNode node, Function(bool) updateState) {
node.addListener(() {
setState(() {
updateState(node.hasFocus);
});
});
}
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();
if (token == null || userId == null) {
print("Token or USerId missing");
return;
}
}
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');
}
Future<void> handleSubmit() async {
errorMessages.remove("from_date");
errorMessages.remove("to_date");
final fromDateText = textControllers["from_date"]?.text;
final toDateText = textControllers["to_date"]?.text;
if (fromDateText == null || fromDateText.isEmpty ||
toDateText == null || toDateText.isEmpty) {
errorMessages["from_date"] = "Required";
errorMessages["to_date"] = "Required";
setState(() {});
return;
}
// Parse the string to DateTime using the correct format
final dateFormat = DateFormat('dd-MM-yyyy');
final DateTime fromDate = dateFormat.parse(fromDateText);
final DateTime toDate = dateFormat.parse(toDateText);
// Check if from date is after to date
if (fromDate.isAfter(toDate)) {
errorMessages["to_date"] = "Invalid Date Range";
setState(() {});
return;
}
// Format for API request
final formattedfromDate = DateFormat('yyyy-MM-dd').format(fromDate);
final formattedtoDate = DateFormat('yyyy-MM-dd').format(toDate);
// Fetch and update
final result = await apiService.CallReports(
'misServicesAnalysis',
formattedfromDate,
formattedtoDate,
);
setState(() {
domesticData = List<Map<String, dynamic>>.from(result['data']['domestic'] ?? []);
internationData = List<Map<String, dynamic>>.from(result['data']['international'] ?? []);
});
}
@override
// Widget build(BuildContext context) {
// // TODO: implement build
// throw UnimplementedError();
// }
Widget build(BuildContext context) {
return ResponsiveBuilder(
builder: (context, sizingInfo) {
bool isDesktop = sizingInfo.deviceScreenType == DeviceScreenType.desktop;
return Scaffold(
backgroundColor: 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:
MediaQuery.of(context).size.height *
0, // 5% of screen height as vertical padding
)
: EdgeInsets.all(0),
child: Row(
children: [
// if (isDesktop) CustomDrawer(isDesktop: true),
// const Expanded(child: Center(child: Text("User Page Content"))),
Expanded(child: buildServicesAnalysisLayout(isDesktop))
],
),
),
);
},
);
}
Widget buildServicesAnalysisLayout(bool isDesktop) {
return Container( child: buildServicesAnalysisFormLayout(isDesktop) );
}
Widget buildServicesAnalysisFormLayout(bool isDesktop) {
return Container(
margin: isDesktop ? EdgeInsets.all(10.0) : null,
padding: const EdgeInsets.only(top: 15, bottom: 15, left: 20, right: 20),
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)
: null,
color: isDesktop ? Colors.white : Color(0xFFFCFCFC),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// ------------------- First Row -------------------
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'Miscellaneous Services Analysis',
style: GoogleFonts.poppins(
fontSize: isDesktop ? 16 : 14,
fontWeight: FontWeight.w600,
color: Colors.black,
),
),
Row(
children: [
ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Color(0xFF114D8B),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
side: BorderSide(color: Color(0xFF114D8B), width: 2),
),
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
),
onPressed: () {
// Your Excel export logic here
},
child: Row(
children: [
Text(
"Excel",
style: GoogleFonts.poppins(
fontSize: isDesktop ? 13 : 11,
color: Colors.white,
),
),
SizedBox(width: 8),
Icon(Icons.file_present_outlined, size: 15, color: Colors.white),
],
),
),
SizedBox(width: 10),
ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Color(0xFF114D8B),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
side: BorderSide(color: Color(0xFF114D8B), width: 2),
),
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
),
onPressed: () {
// Your PDF export logic here
},
child: Row(
children: [
Text(
"PDF",
style: GoogleFonts.poppins(
fontSize: isDesktop ? 13 : 11,
color: Colors.white,
),
),
SizedBox(width: 8),
Icon(Icons.picture_as_pdf_outlined, size: 15, color: Colors.white),
],
),
),
],
),
],
),
SizedBox(height: 15),
// ------------------- Second Row -------------------
Row(
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"From Date *",
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74),
),
),
SizedBox(height: 5),
CustomTextFieldWrapper(
isFocused: focusStates["from_date"] ?? false,
isDesktop: isDesktop,
child: SizedBox(
height: 40,
child: GestureDetector(
onTap: () async {
final pickedDate = await showDatePicker(
context: context,
initialDate: DateTime.now(),
firstDate: DateTime(2000),
lastDate: DateTime(2100),
);
if (pickedDate != null) {
textControllers["from_date"]?.text =
DateFormat('dd-MM-yyyy').format(pickedDate);
}
},
child: AbsorbPointer(
child: TextField(
focusNode: focusNodes["from_date"],
controller: textControllers["from_date"],
readOnly: true,
style: const TextStyle(fontSize: 12),
decoration: InputDecoration(
labelText: "Select Date",
labelStyle: const TextStyle(
fontSize: 12,
color: Colors.grey,
),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: const EdgeInsets.symmetric(vertical: 16),
suffixIcon: const Icon(
Icons.calendar_today,
size: 16,
color: Colors.grey,
),
),
),
),
),
),
),
if (errorMessages["from_date"] != null) ...[
SizedBox(height: 5), // Space before error message
Text(
errorMessages["from_date"]!,
style: TextStyle(color: Colors.red, fontSize: 12),
),
],
],
),
if (isDesktop) Spacer() else SizedBox(height: 8),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"To Date *",
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74),
),
),
SizedBox(height: 5),
CustomTextFieldWrapper(
isFocused: focusStates["to_date"] ?? false,
isDesktop: isDesktop,
child: SizedBox(
height: 40,
child: GestureDetector(
onTap: () async {
final fromDateText = textControllers["from_date"]?.text;
DateTime? mintoDate;
if (fromDateText != null && fromDateText.isNotEmpty) {
mintoDate = DateFormat('dd-MM-yyyy').parse(fromDateText);
}
final pickedDate = await showDatePicker(
context: context,
initialDate: mintoDate ?? DateTime.now(),
firstDate: mintoDate ?? DateTime(2000),
lastDate: DateTime(2100),
);
if (pickedDate != null) {
textControllers["to_date"]?.text =
DateFormat('dd-MM-yyyy').format(pickedDate);
}
},
child: AbsorbPointer(
child: TextField(
focusNode: focusNodes["to_date"],
controller: textControllers["to_date"],
readOnly: true,
style: const TextStyle(fontSize: 12),
decoration: const InputDecoration(
labelText: "Select Date",
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16),
suffixIcon: Icon(
Icons.calendar_today,
size: 16,
color: Colors.grey,
),
),
),
),
),
),
),
if (errorMessages["to_date"] != null) ...[
SizedBox(height: 5), // Space before error message
Text(
errorMessages["to_date"]!,
style: const TextStyle(color: Colors.red, fontSize: 12),
maxLines: 2, // Allow it to wrap onto two lines
overflow:
TextOverflow.ellipsis, // Add ellipsis if it still overflows
),
],
],
),
if (isDesktop) Spacer() else SizedBox(height: 8),
SizedBox(
child: 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: () {handleSubmit();},
child: Row(
mainAxisSize:
MainAxisSize.min, // Ensures content fits nicely
children: [
Text(
"Search",
style: GoogleFonts.poppins(
fontSize: isDesktop ? 13 : 11,
),
),
SizedBox(width: 8), // spacing between icon and text
Icon(
Icons.search,
size: 15,
color: Colors.white,
),
],
),
),
),
],
),
SizedBox(height: 15),
// ------------------- Third Row -------------------
DefaultTabController(
length: 2,
child: Column(
children: [
TabBar(
labelColor: Colors.black,
unselectedLabelColor: Colors.grey,
indicatorColor: Color(0xFF114D8B),
tabs: [
Tab(text: "Domestic"),
Tab(text: "International"),
],
),
SizedBox(height: 10),
Container(
height: 200, // Or use Expanded for flexible height
child: TabBarView(
children: [
// Domestic Tab
domesticData.isEmpty
? Center(
child: Padding(
padding: EdgeInsets.all(16),
child: Text(
"No data found",
style: GoogleFonts.poppins(fontSize: 14, fontWeight: FontWeight.w500),
),
),
)
: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8),
child: Text(
"Domestic Report (From: ${textControllers["from_date"]?.text} To: ${textControllers["to_date"]?.text})",
style: GoogleFonts.poppins(fontSize: 13, fontWeight: FontWeight.w500, color: Colors.grey[700]),
),
),
Expanded(
child: SingleChildScrollView(
scrollDirection: Axis.vertical,
child: DataTable(
dividerThickness: 0.5,
columnSpacing: isDesktop ? 24.0 : 16.0,
border: TableBorder(
horizontalInside: BorderSide(
width: 0.5,
color: Colors.grey.shade200,
),
),
columns: [
DataColumn(label: Text('Plan Id', style: GoogleFonts.poppins(fontSize: 12, fontWeight: FontWeight.w600))),
DataColumn(label: Text('Employee Code', style: GoogleFonts.poppins(fontSize: 12, fontWeight: FontWeight.w600))),
DataColumn(label: Text('SO Number', style: GoogleFonts.poppins(fontSize: 12, fontWeight: FontWeight.w600))),
DataColumn(label: Text('Department', style: GoogleFonts.poppins(fontSize: 12, fontWeight: FontWeight.w600))),
DataColumn(label: Text('Flight Trip Type', style: GoogleFonts.poppins(fontSize: 12, fontWeight: FontWeight.w600))),
DataColumn(label: Text('Plan Trip Type', style: GoogleFonts.poppins(fontSize: 12, fontWeight: FontWeight.w600))),
DataColumn(label: Text('Sector', style: GoogleFonts.poppins(fontSize: 12, fontWeight: FontWeight.w600))),
],
rows: domesticData.map((entry) {
return DataRow(cells: [
DataCell(Text('${entry['plan_id']}', style: GoogleFonts.poppins(fontSize: 12))),
DataCell(Text('${entry['employee_code']}', style: GoogleFonts.poppins(fontSize: 12))),
DataCell(Text('${entry['so_number']}', style: GoogleFonts.poppins(fontSize: 12))),
DataCell(Text('${entry['functional_department']}', style: GoogleFonts.poppins(fontSize: 12))),
DataCell(Text('${entry['flight_trip_type']}', style: GoogleFonts.poppins(fontSize: 12))),
DataCell(Text('${entry['plan_trip_type']}', style: GoogleFonts.poppins(fontSize: 12))),
DataCell(Text('${entry['sector']}', style: GoogleFonts.poppins(fontSize: 12))),
]);
}).toList(),
),
),
),
],
),
// International Tab
internationData.isEmpty
? Center(
child: Padding(
padding: EdgeInsets.all(16),
child: Text(
"No data found",
style: GoogleFonts.poppins(fontSize: 14, fontWeight: FontWeight.w500),
),
),
)
: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8),
child: Text(
"International Report (From: ${textControllers["from_date"]?.text} To: ${textControllers["to_date"]?.text})",
style: GoogleFonts.poppins(fontSize: 13, fontWeight: FontWeight.w500, color: Colors.grey[700]),
),
),
Expanded(
child: SingleChildScrollView(
scrollDirection: Axis.vertical,
child: DataTable(
dividerThickness: 0.5,
columnSpacing: isDesktop ? 24.0 : 16.0,
border: TableBorder(
horizontalInside: BorderSide(
width: 0.5,
color: Colors.grey.shade200,
),
),
columns: [
DataColumn(label: Text('Plan Id', style: GoogleFonts.poppins(fontSize: 12, fontWeight: FontWeight.w600))),
DataColumn(label: Text('Employee Code', style: GoogleFonts.poppins(fontSize: 12, fontWeight: FontWeight.w600))),
DataColumn(label: Text('SO Number', style: GoogleFonts.poppins(fontSize: 12, fontWeight: FontWeight.w600))),
DataColumn(label: Text('Department', style: GoogleFonts.poppins(fontSize: 12, fontWeight: FontWeight.w600))),
DataColumn(label: Text('Flight Trip Type', style: GoogleFonts.poppins(fontSize: 12, fontWeight: FontWeight.w600))),
DataColumn(label: Text('Plan Trip Type', style: GoogleFonts.poppins(fontSize: 12, fontWeight: FontWeight.w600))),
DataColumn(label: Text('Sector', style: GoogleFonts.poppins(fontSize: 12, fontWeight: FontWeight.w600))),
],
rows: internationData.map((entry) {
return DataRow(cells: [
DataCell(Text('${entry['plan_id']}', style: GoogleFonts.poppins(fontSize: 12))),
DataCell(Text('${entry['employee_code']}', style: GoogleFonts.poppins(fontSize: 12))),
DataCell(Text('${entry['so_number']}', style: GoogleFonts.poppins(fontSize: 12))),
DataCell(Text('${entry['functional_department']}', style: GoogleFonts.poppins(fontSize: 12))),
DataCell(Text('${entry['flight_trip_type']}', style: GoogleFonts.poppins(fontSize: 12))),
DataCell(Text('${entry['plan_trip_type']}', style: GoogleFonts.poppins(fontSize: 12))),
DataCell(Text('${entry['sector']}', style: GoogleFonts.poppins(fontSize: 12))),
]);
}).toList(),
),
),
),
],
),
],
),
),
],
),
),
SizedBox(height: 15),
],
),
);
}
}

View File

@ -93,6 +93,8 @@ class TravellerListState extends State<TravellerList> {
print("Calling Refresh Data $object");
setState(() {
allTraveller = object;
filteredTraveller = object;
searchController.text = "";
});
return object;
});

View File

@ -2901,6 +2901,10 @@ class TravellerDetailsState extends State<TravellerDetails> {
child: TextField(
style: GoogleFonts.poppins(fontSize: 12, color: Colors.black),
controller: controllers["forex_card_num"],
inputFormatters: [
FilteringTextInputFormatter.allow(RegExp(r'[0-9\s]')),
LengthLimitingTextInputFormatter(16),
],
enabled: !widget.isViewMode,
onChanged: (value) {
_clearError("forex_pre_paid_card_number");

View File

@ -7,6 +7,10 @@ import 'package:http/http.dart' as http;
import 'package:responsive_builder/responsive_builder.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'dart:typed_data';
import 'package:universal_html/html.dart' as html;
import 'package:http_parser/http_parser.dart' as http_parser;
import '../../config/apiUrl.dart';
import '../../routes/custom_appBar.dart';
import '../../routes/custom_drawer.dart';
@ -190,6 +194,154 @@ class _UserListScreenState extends State<UserListScreen> {
print("handDel - $userId");
}
Future<void> handleUpload ()async {
final String apiUrldata = '$apiUrl/api/user/userUpload'; // api
final String? token = await getToken(); // 2kn
if (token == null) {
throw Exception('Token not found. Please log in.');
}
html.FileUploadInputElement uploadInput = html.FileUploadInputElement();
// uploadInput.accept = '.xlsx,.xls,.csv';
uploadInput.accept = '.xlsx';
uploadInput.click();
uploadInput.onChange.listen((e) async {
final file = uploadInput.files!.first;
final reader = html.FileReader();
reader.readAsArrayBuffer(file);
await reader.onLoadEnd.first;
print(' file - extn : ${file.name.endsWith(".xlsx")}');
if (!file.name.endsWith('.xlsx')) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
// content: Text("Error: Not a valid Excel file "),
content: Text('Invalid file format. Please upload a .xlsx file.'),
backgroundColor: Colors.redAccent,
behavior: SnackBarBehavior.floating,
),
);
print("Error: Not a valid .xlsx file");
return;
}
if (reader.readyState == html.FileReader.DONE) {
Uint8List? fileBytes = reader.result as Uint8List?;
if (fileBytes != null) {
// Save fileBytes to local storage
final jsonString = json.encode(fileBytes);
html.window.localStorage['fileBytes'] = jsonString;
print('File Bytes: $fileBytes');
if (fileBytes == null) {
print('File Bytes: null');
// ScaffoldMessenger.of(context).showSnackBar(
// SnackBar(
// content: Text("Issues Occur In Files"),
// backgroundColor: Colors.redAccent,
// behavior: SnackBarBehavior.floating,
// ),
// );
return; // No file selected
} else {
print('File Bytes: CALLING API');
// // Prepare form data
// final formData = html.FormData();
// formData.appendBlob('file', html.Blob([fileBytes]), fileName);
// Create a multipart request
final request = http.MultipartRequest('POST', Uri.parse(apiUrldata));
// Attach the file to the request
// Set authorization token in headers
request.headers['Authorization'] = 'Bearer $token';
// request.files.add(http.MultipartFile.fromBytes('file', fileBytes, filename: fileName));
request.files.add(http.MultipartFile.fromBytes( 'user_file', fileBytes, filename: 'user_file.xlsx' ));
print('request : $request');
// Send the request
final response = await request.send();
// Read response stream as a string
final responseString = await response.stream.bytesToString();
// Check the status code of the response
if (response.statusCode == 200) {
Map<String, dynamic> data = json.decode(responseString);
if (data['status'] == 'success') {
print(data['message']);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(data['message']),
backgroundColor: Colors.green.shade500,
behavior: SnackBarBehavior.floating,
),
);
refreshUserList();
} else {
print(data['message']);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(data['message']),
backgroundColor: Colors.redAccent,
behavior: SnackBarBehavior.floating,
),
);
refreshUserList();
}
} else {
print('Something went wrong');
print('Failed to upload file: ${response.reasonPhrase}');
}
}
}
}
// if (!file.name.endsWith('.xlsx')) {
// print("Error: Not a valid .xlsx file");
// return;
// }
// reader.onLoadEnd.listen((e) async {
// final data = reader.result as Uint8List;
//
// final request = http.MultipartRequest('POST', Uri.parse(apiUrldata));
// request.headers['Authorization'] = 'Bearer $token';
//
// final multipartFile = http.MultipartFile.fromBytes(
// 'file', // must match the API expected field name
// data,
// filename: file.name,
// contentType: MediaType('application', 'vnd.openxmlformats-officedocument.spreadsheetml.sheet'),
// );
//
// request.files.add(multipartFile);
//
// print('Uploading file: ${file.name}');
//
// try {
// final response = await request.send();
// final respStr = await response.stream.bytesToString();
// print("Response status: ${response.statusCode}");
// print("Response body: $respStr");
// if (response.statusCode == 200) {
// print("Upload successful!");
// } else {
// print("Upload failed with status: ${response.statusCode}");
// }
// } catch (e) {
// print("Error uploading file: $e");
// }
// });
//
// reader.readAsArrayBuffer(file);
});
}
Future<void> createUserData(
Map<String, dynamic> userData,
String userId,
@ -421,7 +573,7 @@ class _UserListScreenState extends State<UserListScreen> {
],
),
if (isDesktop)
SizedBox(width: MediaQuery.of(context).size.width * 0.28),
SizedBox(width: MediaQuery.of(context).size.width * 0.15),
if (isDesktop)
Container(
@ -464,7 +616,27 @@ class _UserListScreenState extends State<UserListScreen> {
),
// SizedBox(width: 16),
Spacer(),
OutlinedButton(
onPressed: () { apiService.getDownloadUserTemplateForUpload(); },
style: OutlinedButton.styleFrom(
backgroundColor: Colors.white,
foregroundColor: Color(0xFF114D8B),
side: BorderSide(color: Colors.white),
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 16),
),
child: Text("Sample Template Download"),
), Spacer(),
// Upload Button
OutlinedButton(
onPressed: handleUpload,
style: OutlinedButton.styleFrom(
backgroundColor: Colors.white,
foregroundColor: Color(0xFF114D8B),
side: BorderSide(color: Colors.white),
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 16),
),
child: Text("User Bulk Upload"),
), Spacer(),
ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Color(0xFF114D8B),

View File

@ -466,9 +466,9 @@ class _CustomAppBarState extends State<CustomAppBar> {
case '/listUser':
context.go('/listUser');
break;
// case '/group':
// context.go('/group');
// break;
case '/report':
context.go('/report');
break;
// case '/department':
// context.go('/department');
// break;
@ -651,6 +651,11 @@ final List<Map<String, dynamic>> menuItems = [
// 'label': 'Template List'
// },
// {'value': '/template', 'icon': Icons.ac_unit_sharp, 'label': 'Template'},
// {
// 'value': '/report',
// 'icon': Icons.auto_graph,
// 'label': 'Reports',
// },
{
'value': '/CreateUserDetails',
'icon': Icons.account_circle,

View File

@ -32,6 +32,14 @@ import '../Screens/costCenter/costCenter_list.dart';
import '../Screens/dashboard/status_dashboard.dart';
import '../Screens/hotels/hotels_list.dart';
import '../Screens/traveller/travellerList.dart';
import '../Screens/reports/reportList.dart';
import '../Screens/reports/advancePurchase.dart';
import '../Screens/reports/servicesAnalysis.dart';
import '../Screens/reports/miscellaneousAir.dart';
import '../Screens/reports/miscellaneousHotel.dart';
import '../Screens/reports/miscellaneousForex.dart';
import '../Screens/reports/guestHouse.dart';
import 'mainLayout.dart';
final GoRouter router = GoRouter(
@ -142,6 +150,34 @@ final GoRouter router = GoRouter(
pageBuilder:
(context, state) => MaterialPage(child: Group.fromState(state)),
),
GoRoute(
path: '/report',
builder: (context, state) => ReportList(),
),
GoRoute(
path: '/advancePurchase',
builder: (context, state) => AdvancePurchase(),
),
GoRoute(
path: '/misservicesAnalysis',
builder: (context, state) => ServicesAnalysis(),
),
GoRoute(
path: '/misAirReport',
builder: (context, state) => MiscellaneousAir(),
),
GoRoute(
path: '/misHotelReport',
builder: (context, state) => MiscellaneousHotel(),
),
GoRoute(
path: '/misForexReport',
builder: (context, state) => MiscellaneousForex(),
),
GoRoute(
path: '/guestHouseReport',
builder: (context, state) => GuestHouse(),
),
],
),
],

View File

@ -1510,4 +1510,35 @@ class ApiService {
}
// ----------------------- User Management - CheckDuplicate end here ----------------------------------
// ----------------------- Report -------------------------
Future<Map<String, dynamic>> CallReports(String apiRoute,String FromDate,String ToDate) async {
String apiUrldata = '$apiUrl/api/$apiRoute';
final token = await getToken();
final body = jsonEncode({"fromDate":'$FromDate',"toDate":'$ToDate'});
if (token == null) {
throw Exception('Token not found. Please log in.');
}
final response = await http.post(
Uri.parse(apiUrldata),
headers: {'Authorization': 'Bearer $token','Content-Type': 'application/json'},
body: body
);
if (response.statusCode == 200) {
try {
final data = json.decode(response.body);
return data;
} catch (e) {
throw Exception('Error parsing response: $e');
}
} else {
throw Exception(
'Failed to load checkDuplicate data. Status code: ${response.statusCode}',
);
}
}
// ----------------------- Report -------------------------
}