diff --git a/lib/Screens/approvals/approval_list.dart b/lib/Screens/approvals/approval_list.dart index 372f6d4..a38714e 100644 --- a/lib/Screens/approvals/approval_list.dart +++ b/lib/Screens/approvals/approval_list.dart @@ -674,6 +674,15 @@ class _ApprovalListState extends State { ), ), ), + 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 { ), ), 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 ?? " ", diff --git a/lib/Screens/costCenter/costCenter_list.dart b/lib/Screens/costCenter/costCenter_list.dart index 1970b8b..ecef9be 100644 --- a/lib/Screens/costCenter/costCenter_list.dart +++ b/lib/Screens/costCenter/costCenter_list.dart @@ -93,6 +93,8 @@ class CostCenterListState extends State { print("Calling Refresh Data $object"); setState(() { allCostCenter = object; + filteredCostCenter = object; + searchController.text = ""; }); return object; }); diff --git a/lib/Screens/department/department_list.dart b/lib/Screens/department/department_list.dart index f7b63b1..9eb112e 100644 --- a/lib/Screens/department/department_list.dart +++ b/lib/Screens/department/department_list.dart @@ -93,6 +93,8 @@ class DepartmentListState extends State { print("Calling Refresh Data $object"); setState(() { allDepartment = object; + filteredDepartment = object; + searchController.text = ""; }); return object; }); diff --git a/lib/Screens/forex/forex_list.dart b/lib/Screens/forex/forex_list.dart index 2d99fc6..0169cbd 100644 --- a/lib/Screens/forex/forex_list.dart +++ b/lib/Screens/forex/forex_list.dart @@ -73,6 +73,8 @@ class ForexDataListState extends State { return futureForex.then((users) { setState(() { allForex = users; + filteredForex = users; + searchController.text = ""; }); return users; }); diff --git a/lib/Screens/group/groupList.dart b/lib/Screens/group/groupList.dart index 02e0ff4..ade5b54 100644 --- a/lib/Screens/group/groupList.dart +++ b/lib/Screens/group/groupList.dart @@ -94,6 +94,8 @@ class _GroupListState extends State { final result = await apiService.fetchAllGroup(); setState(() { allGroups = result; + filteredGroups = result; + searchController.text = ""; }); print("Fetched services: $allGroups"); } catch (e) { diff --git a/lib/Screens/hotels/hotelsDetails.dart b/lib/Screens/hotels/hotelsDetails.dart index 76eb5f9..a230726 100644 --- a/lib/Screens/hotels/hotelsDetails.dart +++ b/lib/Screens/hotels/hotelsDetails.dart @@ -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 { List dataHeader = [ "hotel_name", "hotel_chain", + "category", "country_code", "city", ]; @@ -63,6 +65,7 @@ class HotelsDataState extends State { 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 { 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 { 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 { ], ], ), - - // - 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, diff --git a/lib/Screens/hotels/hotels_list.dart b/lib/Screens/hotels/hotels_list.dart index b3ffcce..48731af 100644 --- a/lib/Screens/hotels/hotels_list.dart +++ b/lib/Screens/hotels/hotels_list.dart @@ -73,6 +73,8 @@ class HotelsDataListState extends State { return futureHotels.then((objects) { setState(() { allHotels = objects; + filteredHotels = objects; + searchController.text = ""; }); return objects; }); diff --git a/lib/Screens/itnerary/visa.dart b/lib/Screens/itnerary/visa.dart index 1bd433f..30b98c0 100644 --- a/lib/Screens/itnerary/visa.dart +++ b/lib/Screens/itnerary/visa.dart @@ -514,29 +514,35 @@ class _VisaScreenState extends State { 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( 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 { 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; }); }, ), diff --git a/lib/Screens/plans/create_plans.dart b/lib/Screens/plans/create_plans.dart index 558bc06..ec64663 100644 --- a/lib/Screens/plans/create_plans.dart +++ b/lib/Screens/plans/create_plans.dart @@ -1211,15 +1211,16 @@ class CreateNewPlansState extends State { // "${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(); } diff --git a/lib/Screens/policy/policy_list.dart b/lib/Screens/policy/policy_list.dart index 8b0a94c..709c2b1 100644 --- a/lib/Screens/policy/policy_list.dart +++ b/lib/Screens/policy/policy_list.dart @@ -188,6 +188,8 @@ class _PolicyListState extends State { final result = await apiService.fetchAllPolicy(); setState(() { allPolicy = result; + filteredPolicy = result; + searchController.text = ""; }); print("Fetched services: $allPolicy"); } catch (e) { diff --git a/lib/Screens/reports/advancePurchase.dart b/lib/Screens/reports/advancePurchase.dart new file mode 100644 index 0000000..37fb63b --- /dev/null +++ b/lib/Screens/reports/advancePurchase.dart @@ -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 { + final ApiService apiService = ApiService(); + + String? userId; + String? orgId; + String? roleUser; + String? token; + + Color? layoutColor; + Color? bodyColor; + + Map focusNodes = {}; + Map focusStates = {}; + Map textControllers = {}; + Map errorMessages = {}; + // Map advancePurchaseReport = {}; + List> domesticData = []; + List> internationData = []; + // Map domesticData = {}; + // Map 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 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 getUserId() async { + final prefs = await SharedPreferences.getInstance(); + final String? userDataString = prefs.getString('user_data'); + + if (userDataString != null) { + try { + final Map userData = jsonDecode(userDataString); + return userData["user_id"]?.toString(); + } catch (e) { + return null; + } + } + return null; + } + + Future getOrgId() async { + final prefs = await SharedPreferences.getInstance(); + final String? userDataString = prefs.getString('user_data'); + + if (userDataString != null) { + try { + final Map userData = jsonDecode(userDataString); + return userData["org_id"]?.toString(); + } catch (e) { + return null; + } + } + return null; + } + + Future getToken() async { + final prefs = await SharedPreferences.getInstance(); + return prefs.getString('auth_token'); + } + + + Future 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>.from(result['data']['domestic'] ?? []); + internationData = List>.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), + + ], + ), + ); + } + +} + diff --git a/lib/Screens/reports/guestHouse.dart b/lib/Screens/reports/guestHouse.dart new file mode 100644 index 0000000..be55a2a --- /dev/null +++ b/lib/Screens/reports/guestHouse.dart @@ -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 { + final ApiService apiService = ApiService(); + + String? userId; + String? orgId; + String? roleUser; + String? token; + + Color? layoutColor; + Color? bodyColor; + + Map focusNodes = {}; + Map focusStates = {}; + Map textControllers = {}; + Map errorMessages = {}; + // Map GuestHouseReport = {}; + List> domesticData = []; + List> internationData = []; + // Map domesticData = {}; + // Map 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 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 getUserId() async { + final prefs = await SharedPreferences.getInstance(); + final String? userDataString = prefs.getString('user_data'); + + if (userDataString != null) { + try { + final Map userData = jsonDecode(userDataString); + return userData["user_id"]?.toString(); + } catch (e) { + return null; + } + } + return null; + } + + Future getOrgId() async { + final prefs = await SharedPreferences.getInstance(); + final String? userDataString = prefs.getString('user_data'); + + if (userDataString != null) { + try { + final Map userData = jsonDecode(userDataString); + return userData["org_id"]?.toString(); + } catch (e) { + return null; + } + } + return null; + } + + Future getToken() async { + final prefs = await SharedPreferences.getInstance(); + return prefs.getString('auth_token'); + } + + + Future 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>.from(result['data']['domestic'] ?? []); + internationData = List>.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), + + ], + ), + ); + } + +} + diff --git a/lib/Screens/reports/miscellaneousAir.dart b/lib/Screens/reports/miscellaneousAir.dart new file mode 100644 index 0000000..591551d --- /dev/null +++ b/lib/Screens/reports/miscellaneousAir.dart @@ -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 { + final ApiService apiService = ApiService(); + + String? userId; + String? orgId; + String? roleUser; + String? token; + + Color? layoutColor; + Color? bodyColor; + + Map focusNodes = {}; + Map focusStates = {}; + Map textControllers = {}; + Map errorMessages = {}; + // Map MiscellaneousAirReport = {}; + List> domesticData = []; + List> internationData = []; + // Map domesticData = {}; + // Map 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 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 getUserId() async { + final prefs = await SharedPreferences.getInstance(); + final String? userDataString = prefs.getString('user_data'); + + if (userDataString != null) { + try { + final Map userData = jsonDecode(userDataString); + return userData["user_id"]?.toString(); + } catch (e) { + return null; + } + } + return null; + } + + Future getOrgId() async { + final prefs = await SharedPreferences.getInstance(); + final String? userDataString = prefs.getString('user_data'); + + if (userDataString != null) { + try { + final Map userData = jsonDecode(userDataString); + return userData["org_id"]?.toString(); + } catch (e) { + return null; + } + } + return null; + } + + Future getToken() async { + final prefs = await SharedPreferences.getInstance(); + return prefs.getString('auth_token'); + } + + + Future 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>.from(result['data']['domestic'] ?? []); + internationData = List>.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), + + ], + ), + ); + } + +} + diff --git a/lib/Screens/reports/miscellaneousForex.dart b/lib/Screens/reports/miscellaneousForex.dart new file mode 100644 index 0000000..14c2ca7 --- /dev/null +++ b/lib/Screens/reports/miscellaneousForex.dart @@ -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 { + final ApiService apiService = ApiService(); + + String? userId; + String? orgId; + String? roleUser; + String? token; + + Color? layoutColor; + Color? bodyColor; + + Map focusNodes = {}; + Map focusStates = {}; + Map textControllers = {}; + Map errorMessages = {}; + // Map MiscellaneousForexReport = {}; + List> domesticData = []; + List> internationData = []; + // Map domesticData = {}; + // Map 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 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 getUserId() async { + final prefs = await SharedPreferences.getInstance(); + final String? userDataString = prefs.getString('user_data'); + + if (userDataString != null) { + try { + final Map userData = jsonDecode(userDataString); + return userData["user_id"]?.toString(); + } catch (e) { + return null; + } + } + return null; + } + + Future getOrgId() async { + final prefs = await SharedPreferences.getInstance(); + final String? userDataString = prefs.getString('user_data'); + + if (userDataString != null) { + try { + final Map userData = jsonDecode(userDataString); + return userData["org_id"]?.toString(); + } catch (e) { + return null; + } + } + return null; + } + + Future getToken() async { + final prefs = await SharedPreferences.getInstance(); + return prefs.getString('auth_token'); + } + + + Future 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>.from(result['data']['domestic'] ?? []); + internationData = List>.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), + + ], + ), + ); + } + +} + diff --git a/lib/Screens/reports/miscellaneousHotel.dart b/lib/Screens/reports/miscellaneousHotel.dart new file mode 100644 index 0000000..b81bd07 --- /dev/null +++ b/lib/Screens/reports/miscellaneousHotel.dart @@ -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 { + final ApiService apiService = ApiService(); + + String? userId; + String? orgId; + String? roleUser; + String? token; + + Color? layoutColor; + Color? bodyColor; + + Map focusNodes = {}; + Map focusStates = {}; + Map textControllers = {}; + Map errorMessages = {}; + // Map MiscellaneousHotelReport = {}; + List> domesticData = []; + List> internationData = []; + // Map domesticData = {}; + // Map 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 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 getUserId() async { + final prefs = await SharedPreferences.getInstance(); + final String? userDataString = prefs.getString('user_data'); + + if (userDataString != null) { + try { + final Map userData = jsonDecode(userDataString); + return userData["user_id"]?.toString(); + } catch (e) { + return null; + } + } + return null; + } + + Future getOrgId() async { + final prefs = await SharedPreferences.getInstance(); + final String? userDataString = prefs.getString('user_data'); + + if (userDataString != null) { + try { + final Map userData = jsonDecode(userDataString); + return userData["org_id"]?.toString(); + } catch (e) { + return null; + } + } + return null; + } + + Future getToken() async { + final prefs = await SharedPreferences.getInstance(); + return prefs.getString('auth_token'); + } + + + Future 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>.from(result['data']['domestic'] ?? []); + internationData = List>.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), + + ], + ), + ); + } + +} + diff --git a/lib/Screens/reports/reportList.dart b/lib/Screens/reports/reportList.dart new file mode 100644 index 0000000..6b29641 --- /dev/null +++ b/lib/Screens/reports/reportList.dart @@ -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 { + 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> 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(), + ), + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/Screens/reports/reportSample.dart b/lib/Screens/reports/reportSample.dart new file mode 100644 index 0000000..8db8a93 --- /dev/null +++ b/lib/Screens/reports/reportSample.dart @@ -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 { + 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 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 getUserId() async { + final prefs = await SharedPreferences.getInstance(); + final String? userDataString = prefs.getString('user_data'); + + if (userDataString != null) { + try { + final Map userData = jsonDecode(userDataString); + return userData["user_id"]?.toString(); + } catch (e) { + return null; + } + } + return null; + } + + Future getOrgId() async { + final prefs = await SharedPreferences.getInstance(); + final String? userDataString = prefs.getString('user_data'); + + if (userDataString != null) { + try { + final Map userData = jsonDecode(userDataString); + return userData["org_id"]?.toString(); + } catch (e) { + return null; + } + } + return null; + } + + Future 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))), + ], + ), + ), + ), + ), + ), + + ], + ), + ); + } + +} \ No newline at end of file diff --git a/lib/Screens/reports/servicesAnalysis.dart b/lib/Screens/reports/servicesAnalysis.dart new file mode 100644 index 0000000..29e73c1 --- /dev/null +++ b/lib/Screens/reports/servicesAnalysis.dart @@ -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 { + final ApiService apiService = ApiService(); + + String? userId; + String? orgId; + String? roleUser; + String? token; + + Color? layoutColor; + Color? bodyColor; + + Map focusNodes = {}; + Map focusStates = {}; + Map textControllers = {}; + Map errorMessages = {}; + // Map ServicesAnalysisReport = {}; + List> domesticData = []; + List> internationData = []; + // Map domesticData = {}; + // Map 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 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 getUserId() async { + final prefs = await SharedPreferences.getInstance(); + final String? userDataString = prefs.getString('user_data'); + + if (userDataString != null) { + try { + final Map userData = jsonDecode(userDataString); + return userData["user_id"]?.toString(); + } catch (e) { + return null; + } + } + return null; + } + + Future getOrgId() async { + final prefs = await SharedPreferences.getInstance(); + final String? userDataString = prefs.getString('user_data'); + + if (userDataString != null) { + try { + final Map userData = jsonDecode(userDataString); + return userData["org_id"]?.toString(); + } catch (e) { + return null; + } + } + return null; + } + + Future getToken() async { + final prefs = await SharedPreferences.getInstance(); + return prefs.getString('auth_token'); + } + + + Future 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>.from(result['data']['domestic'] ?? []); + internationData = List>.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), + + ], + ), + ); + } + +} + diff --git a/lib/Screens/traveller/travellerList.dart b/lib/Screens/traveller/travellerList.dart index 1919534..78a8c06 100644 --- a/lib/Screens/traveller/travellerList.dart +++ b/lib/Screens/traveller/travellerList.dart @@ -93,6 +93,8 @@ class TravellerListState extends State { print("Calling Refresh Data $object"); setState(() { allTraveller = object; + filteredTraveller = object; + searchController.text = ""; }); return object; }); diff --git a/lib/Screens/userManagement/create_user/traveller_details.dart b/lib/Screens/userManagement/create_user/traveller_details.dart index 8b97cd0..03d051c 100644 --- a/lib/Screens/userManagement/create_user/traveller_details.dart +++ b/lib/Screens/userManagement/create_user/traveller_details.dart @@ -2901,6 +2901,10 @@ class TravellerDetailsState extends State { 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"); diff --git a/lib/Screens/userManagement/user_List.dart b/lib/Screens/userManagement/user_List.dart index 9e10cdb..d9a8303 100644 --- a/lib/Screens/userManagement/user_List.dart +++ b/lib/Screens/userManagement/user_List.dart @@ -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 { print("handDel - $userId"); } + Future 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 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 createUserData( Map userData, String userId, @@ -421,7 +573,7 @@ class _UserListScreenState extends State { ], ), 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 { ), // 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), diff --git a/lib/routes/custom_appBar.dart b/lib/routes/custom_appBar.dart index 8d8e720..7b97d6b 100644 --- a/lib/routes/custom_appBar.dart +++ b/lib/routes/custom_appBar.dart @@ -466,9 +466,9 @@ class _CustomAppBarState extends State { 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> 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, diff --git a/lib/routes/custom_router.dart b/lib/routes/custom_router.dart index 73cecd8..8eba2f5 100644 --- a/lib/routes/custom_router.dart +++ b/lib/routes/custom_router.dart @@ -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(), + ), ], ), ], diff --git a/lib/services/apiService.dart b/lib/services/apiService.dart index e247f48..ea20516 100644 --- a/lib/services/apiService.dart +++ b/lib/services/apiService.dart @@ -1510,4 +1510,35 @@ class ApiService { } // ----------------------- User Management - CheckDuplicate end here ---------------------------------- + // ----------------------- Report ------------------------- + Future> 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 ------------------------- }