From a76464bbbcfb2c56dc541b364f4b54301ae10a5d Mon Sep 17 00:00:00 2001 From: venbaittech Date: Sat, 3 May 2025 17:59:17 +0530 Subject: [PATCH] search for all pages --- assets/images/IconsImg/planPdf_icon.png | Bin 0 -> 900 bytes lib/Screens/allTrips/list_all_plans.dart | 798 ++++++++++++++++++ lib/Screens/approvals/approval_list.dart | 383 ++++++--- lib/Screens/itnerary/accomodations.dart | 53 ++ lib/Screens/itnerary/flights.dart | 54 +- lib/Screens/itnerary/forex.dart | 288 ++++--- lib/Screens/itnerary/taxi.dart | 2 +- lib/Screens/itnerary/train.dart | 8 +- lib/Screens/itnerary/visa.dart | 41 + lib/Screens/itnerary_list/flight_list.dart | 2 +- lib/Screens/itnerary_list/taxi_list.dart | 2 +- lib/Screens/itnerary_list/train_list.dart | 13 +- lib/Screens/plans/create_plans.dart | 519 +++++++++--- .../plans/dynamic_itinerary_stepper.dart | 5 +- lib/Screens/plans/list_plans.dart | 141 +++- lib/Screens/policy/policy.dart | 190 ++--- lib/Screens/policy/policyCriteria.dart | 270 +++++- lib/Screens/policy/policy_list.dart | 8 + lib/Screens/userManagement/user_List.dart | 679 ++++----------- lib/data/models/plan.dart | 4 + lib/routes/custom_appBar.dart | 86 +- lib/routes/custom_router.dart | 5 + lib/services/apiService.dart | 101 +++ lib/utils/auth_utils.dart | 30 + pubspec.lock | 40 + pubspec.yaml | 2 + 26 files changed, 2625 insertions(+), 1099 deletions(-) create mode 100644 assets/images/IconsImg/planPdf_icon.png create mode 100644 lib/Screens/allTrips/list_all_plans.dart diff --git a/assets/images/IconsImg/planPdf_icon.png b/assets/images/IconsImg/planPdf_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..b04498eda5cc7ff80101b009a8bcc10e5cfc7e02 GIT binary patch literal 900 zcmV-~1AF|5P)vGD4}F+m z+<{C$lg*nzWF={3R^zw5<_cthxpq`T15eWQ0_HG|1mEC^Am%ZCbKb1Mb2;#w#ua92 zAf9tV*m}Lu*@EYE0Pd-FM8EJ_Nb7>sYB%kc35zevFee1!D(TT!HrEgBNHGG({k88r z$O99`*FRSfjT7D1bAqCgYKXEHDM|oKA3Ci}4)8j5lyM?D0~9Xg00LErpq|lz01Kk- ze*gH}T>Df8A3YXTguZ?#>GV`NLT+59LVWY1*@nfJ zr!2RXm`c##;W(T}Ku&P6A}OXstWC&x^I>^QM?@JWzpdnMuX!B*KDPs!>j74Is@u3c z*!dg|g?0c@C{F25bV3mh@J4gMmF|KuY$vEq5EK@x!%?{8#&_Z`L^Yr(ctM^9ePgk= z*P)KU|LXSkj*}dS^99$1H69ObA><}8m4fsKFcK;Sm+tvqYx?`U2uNwMr8vP@;e{YJ zXDs`AWln;O!!7%4yhh}0yJ!0jBFj_ScBTusc)FawZM~8TN5zxZNgujDcLzMIB*<|3 z-P_)76;hC?4fRTRejL&TrwDSw;wu{!li@WuB4x;NHoOD6TwCo-4yb&>RM;OvcqCIi zaDNg0C&)1|2%ERfWs3H$8CXoUEqyogyCKIH;(%%yb#sq#?Eo{`fqrkCv=$Tu!Dbub z1EJpr!;2VA<(&Ju%7s4&L=F>67Yc+>n?rc)MiOs>LW^POh6F{%=-sS7rvvK8m$DwL z(q7u=dA&Q734#sXyXbIYXz*QPsH4x4fyl7i?3c@!MNuYjSk>(u=JcMZ8$-j zX62}i9$p2H7kt@J5Yqmf&hWOm(W$dAOoKxnRbj!)b^cRGOCKf)d0TBE;tm _ListAllPlansState(); +} + +class _ListAllPlansState extends State { + final ApiService apiService = ApiService(); + + int currentPage = 0; + int itemsPerPage = 8; + + String? userId; + String? orgId; + String? roleUser; + String? token; + String? TripPlanAction; + + Color? layoutColor; + Color? bodyColor; + late Future> futurePlans; + List allPlans = []; + List filteredPlans = []; + TextEditingController searchController = TextEditingController(); + + @override + void initState() { + super.initState(); + getToken(); + + WidgetsBinding.instance.addPostFrameCallback((_) { + initializeData(); + loadInitialData(); + + // futurePlans.then((plans) { + // setState(() { + // allPlans = plans; + // filteredPlans = plans; + // }); + // }); + }); + + // futurePlans = fetchPlans(); + } + + void filterPlans(String query) { + print("allPlans before filtering: $allPlans"); + final lowerQuery = query.toLowerCase(); + setState(() { + filteredPlans = allPlans.where((plan) { + return (plan.planId?.toLowerCase().contains(lowerQuery) ?? false) || + (plan.employeeCode?.toLowerCase().contains(lowerQuery) ?? false) || + (plan.tripTitle?.toLowerCase().contains(lowerQuery) ?? false) || + (plan.userName?.toLowerCase().contains(lowerQuery) ?? false) || + (plan.travellerName?.toLowerCase().contains(lowerQuery) ?? false) || + (plan.tripType?.toLowerCase().contains(lowerQuery) ?? false) || + (plan.createdOn?.toLowerCase().contains(lowerQuery) ?? false) || + (plan.statusValue?.toLowerCase().contains(lowerQuery) ?? false); + }).toList(); + }); + print("filteredPlans: $filteredPlans"); + } + + 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(); + TripPlanAction = await getTripPlanAction(); + + if (token == null || userId == null) { + print("Token or USerId missing"); + return; + } else { + setState(() { + futurePlans = fetchPlans(); + }); + + // Wait for futurePlans to be fetched and update allPlans + futurePlans.then((plans) { + setState(() { + allPlans = plans; // Set allPlans after the fetch is complete + filteredPlans = + allPlans; // You can update filteredPlans too if needed + }); + }); + } + } + + 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'); + } + + // Fetch API Data + Future> fetchPlans() async { + late String apiUrldata; + + if (roleUser == "Org Admin") { + apiUrldata = '$apiUrl/api/plans?org_id=$orgId'; + } else { + apiUrldata = + '$apiUrl/api/plans/findTravelAgentPlanList?org_id=$orgId&user_id=$userId'; + } + + if (token == null) { + throw Exception('Token not found. Please log in.'); + } + + final response = await http.get( + Uri.parse(apiUrldata), + headers: { + 'Authorization': 'Bearer $token', // Add token here + 'Content-Type': 'application/json', + }, + ); + + if (response.statusCode == 200) { + final data = json.decode(response.body); + List plansJson = data['data']; + return plansJson.map((json) => Plan.fromJson(json)).toList(); + } else { + throw Exception('Failed to load plans'); + } + } + + Future postPlanData(planData, planId) async { + final String apiUrldata = '$apiUrl/api/plans/createOrEditPlan'; + + final token = await getToken(); // Fetch token + + if (token == null) { + throw Exception('Token not found. Please log in.'); + } + + planData['is_active'] = "0"; + + print("POSTPlanTesting------- $planData"); // Add plan_id for update + + try { + final response = await http.post( + Uri.parse(apiUrldata), + headers: { + 'Authorization': 'Bearer $token', + 'Content-Type': 'application/json', + }, + body: jsonEncode(planData), // Convert map to JSON + ); + + if (response.statusCode == 200) { + print("Plan Deleted successfully!"); + print("Response: ${response.body}"); + initializeData(); + } else { + print("Failed to submit plan. Status: ${response.statusCode}"); + print("Error: ${response.body}"); + } + } catch (e) { + print(" Error submitting plan: $e"); + } + } + + void deletePlan(String planId) async { + try { + Map planData = await ApiService.getViewPlan(planId); + print("ViewAAA - $planData"); + + postPlanData(planData, planId); + } catch (e) { + print("Error fetching plan: $e"); + } + } + + Widget build(BuildContext context) { + return ResponsiveBuilder(builder: (context, sizingInfo) { + bool isDesktop = sizingInfo.deviceScreenType == DeviceScreenType.desktop; + + return Scaffold( + backgroundColor: Colors.white, + // appBar: isDesktop ? null : const CustomAppBar(title: 'Home'), + // drawer: isDesktop ? null : CustomDrawer(isDesktop: false), + appBar: const CustomAppBar(), + 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(8), + child: Row( + children: [ + // if (isDesktop) CustomDrawer(isDesktop: true), + Expanded(child: buildGroupListLayout(isDesktop)) + ], + ), + ), + ); + }); + } + + Widget buildGroupListLayout(bool isDesktop) { + return Container( + // decoration: BoxDecoration( + // // color: Colors.amber, + // // color: bodyColor, + // color: Color(0xFFE1F5FE), + // border: Border.all( + // // color: Color(0xFFF7F7FB), + // color: Colors.white, + // width: 3.5)), + child: buildTableLayout(isDesktop), + ); + } + + Widget buildTableLayout(isDesktop) { + String _formatDate(String rawDate) { + try { + final dateTime = DateTime.parse(rawDate); + return DateFormat('dd MMM yy hh:m a').format(dateTime); + } catch (e) { + return rawDate; // fallback if parsing fails + } + } + + return Container( + margin: isDesktop + ? EdgeInsets.all(10.0) + : EdgeInsets.only(left: 20.0, bottom: 20.0, top: 10.0, right: 20.0), + // padding: const EdgeInsets.all(10), + 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, + // color: Color(0xFFF7F7FB), + ) + : null, + color: Colors.white, + // color: Color(0xFFF7F7FB), + + // color: Colors.amber, + ), + child: Padding( + padding: const EdgeInsets.all(1.0), + child: Container( + // padding: const EdgeInsets.all(10.0), + color: Colors.white, + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + // Divider( + // thickness: 0.2, // how "thick" the line is + // color: Colors.grey, // optional + // ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + const Text('All Trip List', + style: TextStyle( + fontFamily: "Inter", + fontSize: 16, + fontWeight: FontWeight.w600, + color: Color(0xFF212121))), + ], + ), + + Spacer(), + + Container( + width: MediaQuery.of(context).size.width * 0.2, + height: 40, + child: TextField( + controller: searchController, + onChanged: filterPlans, + decoration: InputDecoration( + hintText: "Search...", + hintStyle: + TextStyle(fontSize: 12, color: Color(0xFF9E9DBD)), + prefixIcon: Icon( + Icons.search, + color: Color(0xFF9E9DBD), + size: 18, + ), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + ), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: BorderSide( + color: Colors.grey.shade200, width: 0.5), + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: + BorderSide(color: Colors.grey.shade300, width: 1), + ), + ), + style: TextStyle( + fontSize: 12, + fontFamily: "Inter", + ), + ), + ), + + // SizedBox(width: 16), + + Spacer(), + // ElevatedButton( + // style: ElevatedButton.styleFrom( + // foregroundColor: Colors.white, + // backgroundColor: Colors.blueAccent, + // ), + // onPressed: () { + // context.go('/createPlan', extra: { + // 'orgId': orgId, + // }); + // if (!isDesktop) Navigator.pop(context); + // }, + // child: Row( + // children: [ + // Icon(Icons.add_circle, color: Colors.white), + // SizedBox(width: 5), + // Text('NewPlan'), + // + // ], + // ), + // ), + + 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: () { + if (TripPlanAction == "Plan Creation Not Allowed") { + showDialog( + context: context, + builder: (context) => AlertDialog( + title: Text( + "Action Not Allowed", + style: TextStyle( + fontSize: 18, fontWeight: FontWeight.bold), + ), + content: Text( + "Plan Creation Not Allowed For This User."), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: Text( + "OK", + style: TextStyle(color: layoutColor), + ), + ), + ], + ), + ); + } else { + context.go('/createPlan', extra: { + 'orgId': orgId, + }); + if (!isDesktop) Navigator.pop(context); + } + }, + child: Row( + mainAxisSize: + MainAxisSize.min, // Ensures content fits nicely + children: [ + Text( + "Add New Plan", + style: TextStyle(fontSize: 13), + ), + SizedBox(width: 8), // spacing between icon and text + Icon( + Icons.add_circle_outline_rounded, + size: 15, + color: Colors.white, + ), + ], + ), + ), + ], + ), + const SizedBox(height: 10), + FutureBuilder>( + future: futurePlans, + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + return const Center(child: CircularProgressIndicator()); + } else if (snapshot.hasError || + !snapshot.hasData || + snapshot.data!.isEmpty) { + return Center( + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + mainAxisSize: MainAxisSize.min, + children: const [ + Icon(Icons.error_outline, + color: Colors.redAccent, size: 60), + SizedBox(height: 16), + Text("Oops!", + style: TextStyle( + fontSize: 22, + fontWeight: FontWeight.bold, + color: Colors.redAccent)), + SizedBox(height: 8), + Text("No Plans Available For This User", + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + color: Colors.grey)), + SizedBox(height: 20), + Text("Please Create Plan", + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 16, color: Colors.grey)), + SizedBox(height: 20), + ], + ), + ), + ); + } + + // List plans = snapshot.data!; + + List plans = + filteredPlans.isNotEmpty ? filteredPlans : allPlans; + plans.sort((a, b) => + int.parse(b.planId).compareTo(int.parse(a.planId))); + + List paginatedPlans = plans + .skip(currentPage * itemsPerPage) + .take(itemsPerPage) + .toList(); + + Widget table = LayoutBuilder( + builder: (context, constraints) { + double minWidth = isDesktop ? constraints.maxWidth : 1300; + + return ConstrainedBox( + constraints: BoxConstraints(minWidth: minWidth), + child: DataTable( + dividerThickness: 0.5, + columnSpacing: isDesktop ? 24.0 : 16.0, + border: TableBorder( + horizontalInside: BorderSide( + width: 0.5, color: Colors.grey.shade200), + ), + columns: const [ + DataColumn( + label: Text('Trip Id', + style: TextStyle( + // color: Colors.grey, + // color: Color(0xFF9E9DBD), + fontSize: 13, + fontFamily: "Inter", + fontWeight: FontWeight.w600))), + DataColumn( + label: Text('Employee Code', + style: TextStyle( + // color: Color(0xFF9E9DBD), + fontSize: 13, + fontFamily: "Inter", + fontWeight: FontWeight.w600))), + DataColumn( + label: Text('Trip Name', + style: TextStyle( + fontSize: 13, + fontFamily: "Inter", + fontWeight: FontWeight.w600))), + DataColumn( + label: Text('Traveller', + style: TextStyle( + fontSize: 13, + fontFamily: "Inter", + fontWeight: FontWeight.w600))), + DataColumn( + label: Text('Trip Type', + style: TextStyle( + fontSize: 13, + fontFamily: "Inter", + fontWeight: FontWeight.w600))), + DataColumn( + label: Text('Created On', + style: TextStyle( + fontSize: 13, + fontFamily: "Inter", + fontWeight: FontWeight.w600))), + DataColumn( + label: Text('Status', + style: TextStyle( + fontSize: 13, + fontFamily: "Inter", + fontWeight: FontWeight.w600))), + DataColumn( + label: Text('Actions', + style: TextStyle( + fontSize: 13, + fontFamily: "Inter", + fontWeight: FontWeight.w600))), + ], + rows: paginatedPlans.map((plan) { + return DataRow(cells: [ + DataCell(Text(plan.planId, + style: TextStyle( + fontSize: 13, + fontFamily: "Inter", + ))), + DataCell(Text(plan.employeeCode ?? "no data", + style: TextStyle( + fontSize: 13, + fontFamily: "Inter", + ))), + DataCell(Text(plan.tripTitle, + 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", + ))), + DataCell(Text(plan.tripType, + style: TextStyle( + fontSize: 13, + fontFamily: "Inter", + ))), + DataCell(Text(_formatDate(plan.createdOn), + // plan.createdOn, + style: TextStyle( + fontSize: 13, + fontFamily: "Inter", + ))), + DataCell( + Container( + width: double + .infinity, // Set your desired fixed size (equal width and height) + height: 25, + alignment: Alignment.center, + decoration: BoxDecoration( + color: getStatusColor(plan.statusValue), + // color: plan.statusValue == + // "Partially Approved" + // ? Colors.yellow.shade100 + // : plan.statusValue == "Approved" + // ? Colors.green.shade100 + // : plan.statusValue == "Completed" + // ? Colors.green.shade500 + // : plan.statusValue == "Rejected" + // ? Colors.red.shade100 + // : Colors.grey.shade100, + borderRadius: BorderRadius.circular(10), + ), + child: Text( + plan.statusValue, + textAlign: TextAlign.center, + style: TextStyle( + color: + getStatusTextColor(plan.statusValue), + // color: (plan.statusValue == + // "Partially Approved" || + // plan.statusValue == "Approved" || + // plan.statusValue == "Rejected") + // ? Colors.black + // : plan.statusValue == "Completed" + // ? Colors.white + // : Colors.grey, + fontSize: 12, + fontFamily: "Inter", + fontWeight: FontWeight.w400, + ), + ), + ), + ), + DataCell(Row(children: [ + IconButton( + icon: const Icon( + Icons.remove_red_eye, + color: Color(0xFF475569), + size: 18, + ), + onPressed: () => ApiService.viewPlan( + context, plan.planId, + isViewMode: true)), + GestureDetector( + onTap: () => ApiService.viewPlan( + context, plan.planId, + isViewMode: false), + child: Image.asset( + 'assets/images/IconsImg/edit.png', + width: 20, + height: 15), + ), + SizedBox( + width: 5, + ), + GestureDetector( + onTap: () => deletePlan(plan.planId), + child: Image.asset( + 'assets/images/IconsImg/delete.png', + width: 20, + height: 15), + ), + IconButton( + icon: const Icon( + Icons.download, + color: Color(0xFF475569), + size: 18, + ), + onPressed: () { + apiService.getPdfDownload(plan.planId); + }), + ])), + ]); + }).toList(), + ), + ); + }, + ); + + return Expanded( + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + isDesktop + ? table + : SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: SingleChildScrollView( + scrollDirection: Axis.vertical, + child: table, + ), + ), + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + // Previous button with arrow icon + IconButton( + icon: Icon( + Icons.arrow_back_ios_new, + size: 10, + ), + onPressed: currentPage > 0 + ? () { + setState(() { + currentPage--; + }); + } + : null, + ), + SizedBox(width: 2), + + // Page number text + Text( + 'Page ${currentPage + 1}', + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w400, + color: Colors.black87, + fontFamily: "Inter", + ), + ), + SizedBox(width: 2), + + // Next button with arrow icon + IconButton( + icon: Icon( + Icons.arrow_forward_ios, + size: 10, + ), + onPressed: (currentPage + 1) * itemsPerPage < + plans.length + ? () { + setState(() { + currentPage++; + }); + } + : null, + ), + ], + ), + ], + ), + ); + }, + ) + ], + ), + ), + ), + ); + } + + // Helper functions for colors + Color getStatusColor(String status) { + if (status == "Partially Approved") return Colors.yellow.shade100; + if (status == "Approved") return Colors.green.shade100; + if (status == "Completed") return Colors.green.shade500; + if (status == "Rejected") return Colors.red.shade100; + return Colors.grey.shade100; + } + + Color getStatusTextColor(String status) { + if (status == "Partially Approved" || + status == "Approved" || + status == "Rejected") { + return Colors.black; + } else if (status == "Completed") { + return Colors.white; + } + return Colors.grey; + } +} diff --git a/lib/Screens/approvals/approval_list.dart b/lib/Screens/approvals/approval_list.dart index 53936aa..1dfca8a 100644 --- a/lib/Screens/approvals/approval_list.dart +++ b/lib/Screens/approvals/approval_list.dart @@ -31,6 +31,12 @@ class _ApprovalListState extends State { Color? layoutColor; Color? bodyColor; + int currentPage = 0; + int itemsPerPage = 8; + List allPlans = []; + List filteredPlans = []; + TextEditingController searchController = TextEditingController(); + late List plansJson; @override @@ -85,6 +91,15 @@ class _ApprovalListState extends State { setState(() { futurePlans = fetchPlans(); }); + + // Wait for futurePlans to be fetched and update allPlans + futurePlans.then((plans) { + setState(() { + allPlans = plans; // Set allPlans after the fetch is complete + filteredPlans = + allPlans; // You can update filteredPlans too if needed + }); + }); } } @@ -193,49 +208,6 @@ class _ApprovalListState extends State { } } - // Future> getViewPlan(String planId) async { - // final String apiUrldata = '$apiUrl/api/plans/find/$planId'; - // print("API URL: $apiUrldata"); - // // final token = await getToken(); - // - // if (token == null) { - // throw Exception('Token not found. Please log in.'); - // } - // - // final response = await http.put( - // Uri.parse(apiUrldata), - // headers: { - // 'Authorization': 'Bearer $token', // Add token here - // 'Content-Type': 'application/json', - // }, - // ); - // - // if (response.statusCode == 200) { - // final Map? resData = json.decode(response.body); - // - // return resData?["data"]; - // } else { - // throw Exception('Failed to load plans'); - // } - // } - - // Future> getViewPlan(String planId, List plansJson) async { - // try { - // final plan = plansJson.firstWhere( - // (item) => item["plan_id"].toString() == planId, - // orElse: () => null, - // ); - // - // if (plan == null) { - // throw Exception("Plan with ID $planId not found."); - // } - // - // return Map.from(plan); - // } catch (e) { - // throw Exception("Error finding plan: $e"); - // } - // } - void deletePlan(String planId) async { try { Map planData = await getViewPlan(planId); @@ -301,6 +273,24 @@ class _ApprovalListState extends State { // } // } + void filterPlans(String query) { + print("allPlans before filtering: $allPlans"); + final lowerQuery = query.toLowerCase(); + setState(() { + filteredPlans = allPlans.where((plan) { + return (plan.planId?.toLowerCase().contains(lowerQuery) ?? false) || + (plan.employeeCode?.toLowerCase().contains(lowerQuery) ?? false) || + (plan.tripTitle?.toLowerCase().contains(lowerQuery) ?? false) || + (plan.userName?.toLowerCase().contains(lowerQuery) ?? false) || + (plan.travellerName?.toLowerCase().contains(lowerQuery) ?? false) || + (plan.tripType?.toLowerCase().contains(lowerQuery) ?? false) || + (plan.createdOn?.toLowerCase().contains(lowerQuery) ?? false) || + (plan.statusValue?.toLowerCase().contains(lowerQuery) ?? false); + }).toList(); + }); + print("filteredPlans: $filteredPlans"); + } + Widget build(BuildContext context) { return ResponsiveBuilder(builder: (context, sizingInfo) { bool isDesktop = sizingInfo.deviceScreenType == DeviceScreenType.desktop; @@ -356,30 +346,18 @@ class _ApprovalListState extends State { } return Container( - // margin: isDesktop - // ? EdgeInsets.all(10.0) - // : EdgeInsets.only(left: 20.0, bottom: 20.0, top: 10.0, right: 20.0), + margin: isDesktop + ? EdgeInsets.all(10.0) + : EdgeInsets.only(left: 20.0, bottom: 20.0, top: 10.0, right: 20.0), // padding: const EdgeInsets.all(10), 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, - // // color: Color(0xFFF7F7FB), - // ) - // : null, - // color: Colors.white, - // // color: Color(0xFFF7F7FB), - // - // // color: Colors.amber, - // ), + child: Padding( padding: const EdgeInsets.all(1.0), child: Container( - padding: const EdgeInsets.all(10.0), + padding: const EdgeInsets.all(1.0), color: Colors.white, child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, @@ -389,47 +367,110 @@ class _ApprovalListState extends State { // color: Colors.grey, // optional // ), Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Row( children: [ const Text('List For Approval', style: TextStyle( - fontFamily: "Archivo", - fontSize: 16, - fontWeight: FontWeight.w600, - color: Color(0xFF212121))), + fontFamily: "Inter", + fontSize: 16, + fontWeight: FontWeight.w600, + )), ], ), - Container( - width: MediaQuery.of(context).size.width * 0.2, - // or use Flexible - child: TextField( - onChanged: (query) {}, - decoration: InputDecoration( - hintText: "Search for a plan", - hintStyle: - TextStyle(fontSize: 14, color: Color(0xFF9E9DBD)), - prefixIcon: - Icon(Icons.search, color: Color(0xFF9E9DBD)), - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(8), + if (isDesktop) + SizedBox( + width: MediaQuery.of(context).size.width * 0.19, + ), + + if (isDesktop) + Container( + width: MediaQuery.of(context).size.width * 0.2, + height: 40, + child: TextField( + controller: searchController, + onChanged: filterPlans, + decoration: InputDecoration( + hintText: "Search...", + hintStyle: + TextStyle(fontSize: 12, color: Color(0xFF9E9DBD)), + prefixIcon: Icon( + Icons.search, + color: Color(0xFF9E9DBD), + size: 18, + ), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + ), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: BorderSide( + color: Colors.grey.shade200, width: 0.5), + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: BorderSide( + color: Colors.grey.shade300, width: 1), + ), ), - enabledBorder: OutlineInputBorder( - borderSide: BorderSide( - color: Colors.grey.shade300, width: 0.5), - ), - focusedBorder: OutlineInputBorder( - // borderRadius: BorderRadius.circular(8), - borderSide: - BorderSide(color: Colors.blueAccent, width: 1), + style: TextStyle( + fontSize: 12, + fontFamily: "Inter", ), ), ), - ), // SizedBox(width: 16), ], ), + + if (!isDesktop) + SizedBox( + height: 5, + ), + isDesktop + ? SizedBox.shrink() + : Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Container( + width: MediaQuery.of(context).size.width * 0.8, + height: 40, + child: TextField( + controller: searchController, + onChanged: filterPlans, + decoration: InputDecoration( + hintText: "Search for a plan", + hintStyle: TextStyle( + fontSize: 12, color: Color(0xFF9E9DBD)), + prefixIcon: Icon( + Icons.search, + color: Color(0xFF9E9DBD), + size: 18, + ), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + ), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: BorderSide( + color: Colors.grey.shade200, width: 0.5), + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: BorderSide( + color: Colors.grey.shade300, width: 1), + ), + ), + style: TextStyle( + fontSize: 12, + fontFamily: "Inter", + ), + ), + ), + // SizedBox(width: 16), + ], + ), + const SizedBox(height: 10), FutureBuilder>( future: futurePlans, @@ -472,10 +513,16 @@ class _ApprovalListState extends State { ); } - List plans = snapshot.data!; + List plans = + filteredPlans.isNotEmpty ? filteredPlans : allPlans; plans.sort((a, b) => int.parse(b.planId).compareTo(int.parse(a.planId))); + List paginatedPlans = plans + .skip(currentPage * itemsPerPage) + .take(itemsPerPage) + .toList(); + Widget table = LayoutBuilder( builder: (context, constraints) { double minWidth = isDesktop ? constraints.maxWidth : 1300; @@ -493,50 +540,57 @@ class _ApprovalListState extends State { ), columns: const [ DataColumn( - label: Text('Plan Id', + label: Text('Trip Id', style: TextStyle( - color: Color(0xFF9E9DBD), - fontSize: 14, - fontFamily: "Archivo", - fontWeight: FontWeight.bold))), + fontSize: 13, + fontFamily: "Inter", + fontWeight: FontWeight.w600))), DataColumn( - label: Text('Trip Planned User', + label: Text('Employee Code', style: TextStyle( - color: Color(0xFF9E9DBD), - fontFamily: "Archivo", - fontWeight: FontWeight.bold))), + // color: Color(0xFF9E9DBD), + fontSize: 13, + fontFamily: "Inter", + fontWeight: FontWeight.w600))), DataColumn( - label: Text('Trip Title', + label: Text('Trip Name', style: TextStyle( - color: Color(0xFF9E9DBD), - fontFamily: "Archivo", - fontWeight: FontWeight.bold))), + fontSize: 13, + fontFamily: "Inter", + fontWeight: FontWeight.w600))), + DataColumn( + label: Text('Traveller', + style: TextStyle( + // color: Color(0xFF9E9DBD), + fontSize: 13, + fontFamily: "Inter", + fontWeight: FontWeight.w600))), DataColumn( label: Text('Trip Type', style: TextStyle( - color: Color(0xFF9E9DBD), - fontFamily: "Archivo", - fontWeight: FontWeight.bold))), + fontSize: 13, + fontFamily: "Inter", + fontWeight: FontWeight.w600))), DataColumn( label: Text('Created On', style: TextStyle( - color: Color(0xFF9E9DBD), - fontFamily: "Archivo", - fontWeight: FontWeight.bold))), + fontSize: 13, + fontFamily: "Inter", + fontWeight: FontWeight.w600))), DataColumn( label: Text('Status', style: TextStyle( - color: Color(0xFF9E9DBD), - fontFamily: "Archivo", - fontWeight: FontWeight.bold))), + fontSize: 13, + fontFamily: "Inter", + fontWeight: FontWeight.w600))), DataColumn( label: Text('Actions', style: TextStyle( - color: Color(0xFF9E9DBD), - fontFamily: "Archivo", - fontWeight: FontWeight.bold))), + fontSize: 13, + fontFamily: "Inter", + fontWeight: FontWeight.w600))), ], - rows: plans.map((plan) { + rows: paginatedPlans.map((plan) { return DataRow(cells: [ DataCell(Text(plan.planId, style: TextStyle( @@ -549,27 +603,32 @@ class _ApprovalListState extends State { : plan.travellerName, style: TextStyle( fontSize: 13, - fontFamily: "Archivo", + fontFamily: "Inter", + ))), + DataCell(Text(plan.employeeCode ?? "no data", + style: TextStyle( + fontSize: 13, + fontFamily: "Inter", + ))), + DataCell(Text(plan.tripType, + style: TextStyle( + fontSize: 13, + fontFamily: "Inter", ))), DataCell(Text(plan.tripTitle, style: TextStyle( fontSize: 13, - fontFamily: "Archivo", + fontFamily: "Inter", ), softWrap: true, overflow: TextOverflow.ellipsis)), - DataCell(Text(plan.tripType, - style: TextStyle( - fontSize: 13, - fontFamily: "Archivo", - ))), DataCell(Text( // plan.createdOn, _formatDate(plan.createdOn), style: TextStyle( fontSize: 13, - fontFamily: "Archivo", + fontFamily: "Inter", ))), DataCell( Container( @@ -603,6 +662,7 @@ class _ApprovalListState extends State { ? Colors.white : Colors.grey, fontSize: 12, + fontFamily: "Inter", fontWeight: FontWeight.w400, ), ), @@ -646,6 +706,16 @@ class _ApprovalListState extends State { width: 20, height: 15), ), + + IconButton( + icon: const Icon( + Icons.download, + color: Color(0xFF475569), + size: 18, + ), + onPressed: () { + apiService.getPdfDownload(plan.planId); + }), ])), ]); }).toList(), @@ -655,15 +725,68 @@ class _ApprovalListState extends State { ); return Expanded( - child: isDesktop - ? table - : SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: SingleChildScrollView( - scrollDirection: Axis.vertical, - child: table, + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + isDesktop + ? table + : SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: SingleChildScrollView( + scrollDirection: Axis.vertical, + child: table, + ), + ), + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + // Previous button with arrow icon + IconButton( + icon: Icon( + Icons.arrow_back_ios_new, + size: 10, + ), + onPressed: currentPage > 0 + ? () { + setState(() { + currentPage--; + }); + } + : null, ), - ), + SizedBox(width: 2), + + // Page number text + Text( + 'Page ${currentPage + 1}', + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w400, + color: Colors.black87, + fontFamily: "Inter", + ), + ), + SizedBox(width: 2), + + // Next button with arrow icon + IconButton( + icon: Icon( + Icons.arrow_forward_ios, + size: 10, + ), + onPressed: (currentPage + 1) * itemsPerPage < + plans.length + ? () { + setState(() { + currentPage++; + }); + } + : null, + ), + ], + ), + ], + ), ); }, ) diff --git a/lib/Screens/itnerary/accomodations.dart b/lib/Screens/itnerary/accomodations.dart index 87e0b3e..5763cf5 100644 --- a/lib/Screens/itnerary/accomodations.dart +++ b/lib/Screens/itnerary/accomodations.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; import 'package:responsive_builder/responsive_builder.dart'; +import '../../services/apiService.dart'; import '../../widgets/custom_text_field.dart'; import '../../widgets/custom_text_itnerary_sub.dart'; @@ -24,8 +25,14 @@ class AccomodationScreen extends StatefulWidget { } class _AccomodationScreenState extends State { + ApiService apiService = ApiService(); + + // late Map countryMap; + Map countryMap = {}; + late ValueNotifier flightFirstTripDateNotifier; late ValueNotifier flightLastTripDateNotifier; + late ValueNotifier flightFirstToDestinationNotifier; final GlobalKey _formKey = GlobalKey(); @@ -95,6 +102,7 @@ class _AccomodationScreenState extends State { @override void initState() { super.initState(); + _addFocusListener( _destinationFocusNode, (focus) => _destinationFocused = focus); _addFocusListener( @@ -124,11 +132,21 @@ class _AccomodationScreenState extends State { flightFirstTripDateNotifier = ValueNotifier(null); flightLastTripDateNotifier = ValueNotifier(null); + flightFirstToDestinationNotifier = ValueNotifier(null); WidgetsBinding.instance.addPostFrameCallback((_) { + loadCountryList(); + final result = getFlightTripDateRange(widget.flightData); + + print("Rs: $result"); + flightFirstTripDateNotifier.value = result['firstTripDate']; flightLastTripDateNotifier.value = result['lastTripDate']; + flightFirstToDestinationNotifier.value = result['firstToDestination']; + + print( + "flightfirstToDestinationNotifier: $flightFirstToDestinationNotifier.value "); // ✅ Only set controller after value is updated final parsedDate = @@ -190,10 +208,14 @@ class _AccomodationScreenState extends State { final firstTrip = allTrips.first; final lastTrip = allTrips.last; + final firstTripToDestination = allTrips.first; + + print("allTrips - $allTrips"); return { 'firstTripDate': firstTrip['date'], 'lastTripDate': lastTrip['date'], + 'firstToDestination': firstTripToDestination['to_place'] }; } @@ -269,6 +291,37 @@ class _AccomodationScreenState extends State { return errorMessages.isEmpty; // Valid if there are no errors } + Future loadCountryList() async { + final result = await apiService.fetchFlightsCountryList(); + + print("ResultCountry : $result"); + + // Create a map: Country_Code -> "City, Airport" + Map tempCountryMap = {}; + + for (var country in result) { + String city = country['City'] ?? ''; + String airport = country['Airport'] ?? ''; + String displayName = '${country['City']}'; + // String displayName = '${country['City']} | ${country['Airport']}'; + + tempCountryMap[country['Code']] = displayName; + } + + setState(() { + countryMap = tempCountryMap; // Update the map + _updateDestinationCity(); + }); + } + + void _updateDestinationCity() { + final toDestination = flightFirstToDestinationNotifier.value ?? ''; + final toDestinationCity = countryMap[toDestination] ?? ""; + if (toDestinationCity != '') { + _destinationController.text = toDestinationCity; + } + } + void handleSave() { print("Handle Save accomadationData $accomadationData"); diff --git a/lib/Screens/itnerary/flights.dart b/lib/Screens/itnerary/flights.dart index e056631..29e6217 100644 --- a/lib/Screens/itnerary/flights.dart +++ b/lib/Screens/itnerary/flights.dart @@ -603,7 +603,7 @@ class _FlightScreenState extends State { child: Column( children: [ Padding( - padding: const EdgeInsets.all(28.0), + padding: const EdgeInsets.all(20.0), child: Center( child: Column(children: _buildAccomadtionForm(isDesktop)), ), @@ -1126,7 +1126,7 @@ class _FlightScreenState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - "Class $index *", + selectedTripType == "Oneway" ? "Class *" : "Class $index *", style: TextStyle( fontSize: 12, fontWeight: FontWeight.w600, @@ -1203,12 +1203,27 @@ class _FlightScreenState extends State { ? countryMap[selectedFrom[index]] : null, popupProps: PopupProps.menu( - showSearchBox: true, + fit: FlexFit.loose, + constraints: BoxConstraints(maxHeight: 220), + showSearchBox: true, // Enables search functionality searchFieldProps: TextFieldProps( - decoration: InputDecoration( - hintText: "Search ...", - contentPadding: - EdgeInsets.symmetric(horizontal: 10), + decoration: InputDecoration( + hintText: "Search...", + contentPadding: EdgeInsets.symmetric( + horizontal: 10, vertical: 1), + ), + style: TextStyle(fontSize: 12)), + menuProps: MenuProps( + backgroundColor: Colors.white, + ), + itemBuilder: (context, item, isSelected) => Padding( + padding: const EdgeInsets.symmetric( + horizontal: 8.0, vertical: 6.0), + child: Text( + item, + style: TextStyle( + fontSize: + 13), // 👈 Set your desired text size here ), ), ), @@ -1297,12 +1312,27 @@ class _FlightScreenState extends State { ? countryMap[selectedTo[index]] : null, popupProps: PopupProps.menu( - showSearchBox: true, + fit: FlexFit.loose, + constraints: BoxConstraints(maxHeight: 220), + showSearchBox: true, // Enables search functionality searchFieldProps: TextFieldProps( - decoration: InputDecoration( - hintText: "Search ...", - contentPadding: - EdgeInsets.symmetric(horizontal: 10), + decoration: InputDecoration( + hintText: "Search...", + contentPadding: EdgeInsets.symmetric( + horizontal: 10, vertical: 1), + ), + style: TextStyle(fontSize: 12)), + menuProps: MenuProps( + backgroundColor: Colors.white, + ), + itemBuilder: (context, item, isSelected) => Padding( + padding: const EdgeInsets.symmetric( + horizontal: 8.0, vertical: 6.0), + child: Text( + item, + style: TextStyle( + fontSize: + 13), // 👈 Set your desired text size here ), ), ), diff --git a/lib/Screens/itnerary/forex.dart b/lib/Screens/itnerary/forex.dart index f7bd9d2..391ca55 100644 --- a/lib/Screens/itnerary/forex.dart +++ b/lib/Screens/itnerary/forex.dart @@ -8,6 +8,7 @@ import 'package:responsive_builder/responsive_builder.dart'; import 'package:shared_preferences/shared_preferences.dart'; import '../../config/apiUrl.dart'; +import '../../utils/auth_utils.dart'; import '../../widgets/custom_text_field.dart'; import '../../widgets/custom_text_forex.dart'; import '../../widgets/custom_text_itnerary_sub.dart'; @@ -41,6 +42,8 @@ class _ForexScreenState extends State { late ValueNotifier flightFirstTripDateNotifier; late ValueNotifier flightLastTripDateNotifier; + late String? userCardNumber; + Map selectedValues = {}; bool isChecked = false; // State variable for checkbox @@ -214,6 +217,8 @@ class _ForexScreenState extends State { bool isCardChecked = data["have_card"] == "1"; if (isCardChecked) { requiredFields.add("delivery_location"); + } else { + requiredFields.add("card_number"); } // Check validation for each field @@ -341,8 +346,18 @@ class _ForexScreenState extends State { }); } - void handleUpdatedField() { + void handleUpdatedField() async { // Set the selected value if available + userCardNumber = await getForexCardNumber(); + // userCardNumber = "CD7909043"; + print("userCardNumber - $userCardNumber"); + + if (widget.selectedItem == null && + textControllers["_cardNumber"]?.text == "") { + print("userCardNumber11 - $userCardNumber"); + textControllers["_cardNumber"]?.text = userCardNumber ?? ""; + } + if (widget.selectedItem != null) { print("UPDATAED SELECTION"); @@ -365,6 +380,12 @@ class _ForexScreenState extends State { selectedPerdiemAmount = widget.selectedItem!["perdiem_amount"] as String?; isChecked = widget.selectedItem!["have_card"] == "1"; // Convert string to bool + + // if (textControllers["_cardNumber"] != null) { + // print("userCardNumber11 - $userCardNumber"); + // textControllers["_cardNumber"]!.text = userCardNumber ?? ''; + // } + _onFieldChangedForOthers(); setState(() {}); // Update the UI @@ -604,8 +625,8 @@ class _ForexScreenState extends State { ), ...buildResponsiveRow(_buildSecondRow(isDesktop)), ...buildResponsiveRow(_buildCardDetailsRow(isDesktop)), - ...buildResponsiveRow(_buildFprexCard(isDesktop)), - ...buildResponsiveRow(_buildThirdRow(isDesktop)), + ...buildResponsiveRow(_buildForexCard(isDesktop)), + ...buildResponsiveRow(_buildCommetsRow(isDesktop)), ]; } @@ -1361,7 +1382,9 @@ class _ForexScreenState extends State { ), if (isDesktop) - Spacer() + SizedBox( + width: MediaQuery.of(context).size.width * 0.035, + ) else SizedBox( height: 8, @@ -1413,7 +1436,9 @@ class _ForexScreenState extends State { ), if (isDesktop) - Spacer() + SizedBox( + width: MediaQuery.of(context).size.width * 0.035, + ) else SizedBox( height: 8, @@ -1453,12 +1478,6 @@ class _ForexScreenState extends State { ], ), - if (isDesktop) - Spacer() - else - SizedBox( - height: 8, - ), // Column( // crossAxisAlignment: CrossAxisAlignment.start, // children: [ @@ -1499,96 +1518,6 @@ class _ForexScreenState extends State { // ), // ], // ), - - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "Card Number", - style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.w600, - color: Color(0xFF575A74)), - ), - SizedBox(height: 5), - CustomTextFieldItnerarySubWrapper( - isFocused: focusStates["_cardNumber"] ?? false, - isDesktop: isDesktop, - child: SizedBox( - height: 40, - child: TextField( - enabled: !isChecked, - focusNode: focusNodes["_cardNumber"], - controller: textControllers["_cardNumber"], - style: const TextStyle(fontSize: 12), - decoration: const InputDecoration( - labelText: "Card Number", - labelStyle: TextStyle(fontSize: 12, color: Colors.grey), - floatingLabelBehavior: FloatingLabelBehavior.never, - border: InputBorder.none, - contentPadding: EdgeInsets.symmetric(vertical: 16), - ), - ), - ), - ), - if (errorMessages["card_number"] != null) ...[ - SizedBox(height: 5), // Space before error message - Text( - "Required", - style: TextStyle(color: Colors.red, fontSize: 12), - ), - ], - ], - ), - ]; - } - - List _buildThirdRow(bool isDesktop) { - return [ - isChecked - ? Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "Delivery Location", - style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.w600, - color: Color(0xFF575A74)), - ), - SizedBox(height: 5), - CustomTextFieldWrapper( - isFocused: focusStates["_deliveryLocation"] ?? - false, // Dropdown doesn't use focus - isDesktop: isDesktop, - width: isDesktop - ? MediaQuery.of(context).size.width * 0.330 - : MediaQuery.of(context).size.width * 0.66, - child: TextField( - focusNode: focusNodes["_deliveryLocation"], - controller: textControllers["_deliveryLocation"], - maxLines: 3, - keyboardType: TextInputType.multiline, - style: TextStyle(fontSize: 12), - decoration: InputDecoration( - labelText: "Location", - labelStyle: TextStyle(fontSize: 12, color: Colors.grey), - floatingLabelBehavior: FloatingLabelBehavior.never, - border: InputBorder.none, - contentPadding: EdgeInsets.symmetric(vertical: 4), - ), - ), - ), - if (errorMessages["delivery_location"] != null) ...[ - SizedBox(height: 5), // Space before error message - Text( - "Required", - style: TextStyle(color: Colors.red, fontSize: 12), - ), - ], - ], - ) - : SizedBox.shrink() ]; } @@ -1644,36 +1573,141 @@ class _ForexScreenState extends State { ]; } - List _buildFprexCard(bool isDesktop) { + List _buildForexCard(bool isDesktop) { return [ - Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Checkbox( - value: isChecked, - side: BorderSide( - color: Colors.grey, // Change border color - width: 1, // Adjust thickness + !isChecked + ? Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Card Number", + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74)), + ), + SizedBox(height: 5), + CustomTextFieldItnerarySubWrapper( + isFocused: focusStates["_cardNumber"] ?? false, + isDesktop: isDesktop, + child: SizedBox( + height: 40, + child: TextField( + enabled: !isChecked, + focusNode: focusNodes["_cardNumber"], + controller: textControllers["_cardNumber"], + style: const TextStyle(fontSize: 12), + decoration: const InputDecoration( + labelText: "Card Number", + labelStyle: TextStyle(fontSize: 12, color: Colors.grey), + floatingLabelBehavior: FloatingLabelBehavior.never, + border: InputBorder.none, + contentPadding: EdgeInsets.symmetric(vertical: 16), + ), + ), + ), + ), + if (errorMessages["card_number"] != null) ...[ + SizedBox(height: 5), // Space before error message + Text( + "Required", + style: TextStyle(color: Colors.red, fontSize: 12), + ), + ], + ], + ) + : Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Delivery Location", + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74)), + ), + SizedBox(height: 5), + CustomTextFieldWrapper( + isFocused: focusStates["_deliveryLocation"] ?? + false, // Dropdown doesn't use focus + isDesktop: isDesktop, + width: isDesktop + ? MediaQuery.of(context).size.width * 0.34 + : null, + child: SizedBox( + height: 35, + child: TextField( + focusNode: focusNodes["_deliveryLocation"], + controller: textControllers["_deliveryLocation"], + style: TextStyle(fontSize: 12), + decoration: InputDecoration( + labelText: "Location", + labelStyle: TextStyle(fontSize: 12, color: Colors.grey), + floatingLabelBehavior: FloatingLabelBehavior.never, + border: InputBorder.none, + contentPadding: EdgeInsets.symmetric(vertical: 16), + ), + ), + ), + ), + if (errorMessages["delivery_location"] != null) ...[ + SizedBox(height: 5), // Space before error message + Text( + "Required", + style: TextStyle(color: Colors.red, fontSize: 12), + ), + ], + ], ), - onChanged: (bool? value) { - setState(() { - isChecked = value!; - if (isChecked) { - textControllers["_cardNumber"] - ?.clear(); // Clear the value when isChecked is true - } - }); - }, - ), - Text( - "Check If You Don't Have a forex Account", - style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.w600, - color: Color(0xFF575A74)), + if (isDesktop) + SizedBox( + width: MediaQuery.of(context).size.width * 0.035, + ) + else + SizedBox( + height: 8, + ), + Column( + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + alignment: Alignment.bottomLeft, + child: Transform.scale( + scale: 0.8, + child: Checkbox( + value: isChecked, + activeColor: Color(0xFF114D8B), + // checkColor: Color(0xFF114D8B), + side: BorderSide( + color: Colors.grey, // Change border color + width: 1, // Adjust thickness + ), + onChanged: (bool? value) { + setState(() { + isChecked = value!; + if (isChecked) { + textControllers["_cardNumber"] + ?.clear(); // Clear the value when isChecked is true + } + }); + }, + ), + ), + ), + Text( + "Check If You Don't Have a forex Account", + style: TextStyle( + fontSize: 10, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74)), + ), + ], ), ], - ) + ), ]; } diff --git a/lib/Screens/itnerary/taxi.dart b/lib/Screens/itnerary/taxi.dart index 70ab797..496858f 100644 --- a/lib/Screens/itnerary/taxi.dart +++ b/lib/Screens/itnerary/taxi.dart @@ -506,7 +506,7 @@ class _TaxiScreenState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - "Destination", + "City", style: TextStyle( fontSize: 12, fontWeight: FontWeight.w600, diff --git a/lib/Screens/itnerary/train.dart b/lib/Screens/itnerary/train.dart index 022ecbc..7d9ed7a 100644 --- a/lib/Screens/itnerary/train.dart +++ b/lib/Screens/itnerary/train.dart @@ -247,7 +247,7 @@ class _TrainScreenState extends State { isCountryLoading = true; }); - final result = await apiService.fetchFlightsCountryList(); + final result = await apiService.fetchTrainCountryList(); print("ResultCountry : $result"); @@ -255,11 +255,9 @@ class _TrainScreenState extends State { Map tempCountryMap = {}; for (var country in result) { - String city = country['City'] ?? ''; - String airport = country['Airport'] ?? ''; - String displayName = '${country['City']} - ${country['Airport']}'; + String displayName = '${country['Station_Name']} '; - tempCountryMap[country['Code']] = displayName; + tempCountryMap[country['Station_Code']] = displayName; } setState(() { diff --git a/lib/Screens/itnerary/visa.dart b/lib/Screens/itnerary/visa.dart index a045342..b6f5446 100644 --- a/lib/Screens/itnerary/visa.dart +++ b/lib/Screens/itnerary/visa.dart @@ -3,6 +3,7 @@ import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; import 'package:responsive_builder/responsive_builder.dart'; +import '../../services/apiService.dart'; import '../../widgets/custom_text_field.dart'; import '../../widgets/custom_text_itnerary_sub.dart'; @@ -32,8 +33,14 @@ class VisaScreen extends StatefulWidget { class _VisaScreenState extends State { final GlobalKey _formKey = GlobalKey(); + ApiService apiService = ApiService(); + + // late Map countryMap; + Map countryMap = {}; + late ValueNotifier flightFirstTripDateNotifier; late ValueNotifier flightLastTripDateNotifier; + late ValueNotifier flightFirstToDestinationNotifier; Map selectedValues = {}; @@ -136,6 +143,40 @@ class _VisaScreenState extends State { }); } + // Future loadCountryList() async { + // final result = await apiService.fetchFlightsCountryList(); + // + // print("ResultCountry : $result"); + // + // // Create a map: Country_Code -> "City, Airport" + // Map tempCountryMap = {}; + // + // for (var country in result) { + // String city = country['City'] ?? ''; + // String airport = country['Airport'] ?? ''; + // String displayName = '${country['City']}'; + // // String displayName = '${country['City']} | ${country['Airport']}'; + // + // tempCountryMap[country['Code']] = displayName; + // } + // + // setState(() { + // countryMap = tempCountryMap; // Update the map + // _updateDestinationCity(); + // }); + // } + + void _updateDestinationCity() { + final toDestination = flightFirstToDestinationNotifier.value ?? ''; + + // final toDestinationCity = countryMap[toDestination] ?? ""; + if (toDestination != '') { + // _destinationController.text = toDestinationCity; + + selectedCountry = widget.selectedItem!["toDestination"] as String?; + } + } + Map getFlightTripDateRange( List> flightData) { final allTrips = flightData diff --git a/lib/Screens/itnerary_list/flight_list.dart b/lib/Screens/itnerary_list/flight_list.dart index 434deee..b60b8cf 100644 --- a/lib/Screens/itnerary_list/flight_list.dart +++ b/lib/Screens/itnerary_list/flight_list.dart @@ -279,7 +279,7 @@ class _FlightListWidgetState extends State { Expanded( flex: 2, child: Text( - "Planned Trips", + "Sector", style: TextStyle(fontSize: 11, fontFamily: "Archivo"), )), Expanded( diff --git a/lib/Screens/itnerary_list/taxi_list.dart b/lib/Screens/itnerary_list/taxi_list.dart index 9d8d88a..bae6245 100644 --- a/lib/Screens/itnerary_list/taxi_list.dart +++ b/lib/Screens/itnerary_list/taxi_list.dart @@ -231,7 +231,7 @@ class TaxiListWidget extends StatelessWidget { Expanded( flex: 2, child: Text( - "Destination", + "City", style: TextStyle( fontSize: 11, fontFamily: "Archivo"), )), diff --git a/lib/Screens/itnerary_list/train_list.dart b/lib/Screens/itnerary_list/train_list.dart index b9b23d6..73d9ac3 100644 --- a/lib/Screens/itnerary_list/train_list.dart +++ b/lib/Screens/itnerary_list/train_list.dart @@ -71,7 +71,8 @@ class _TrainListWidgetState extends State { } Future loadCountryList() async { - final result = await apiService.fetchFlightsCountryList(); + // final result = await apiService.fetchFlightsCountryList(); + final result = await apiService.fetchTrainCountryList(); print("ResultCountry : $result"); @@ -79,12 +80,11 @@ class _TrainListWidgetState extends State { Map tempCountryMap = {}; for (var country in result) { - String city = country['City'] ?? ''; - String airport = country['Airport'] ?? ''; - String displayName = '${country['City']} - ${country['Airport']}'; + String displayName = '${country['Station_Name']}'; // String displayName = '${country['City']} | ${country['Airport']}'; - tempCountryMap[country['Code']] = displayName; + // tempCountryMap[country['Code']] = displayName; + tempCountryMap[country['Station_Code']] = displayName; } setState(() { @@ -361,8 +361,7 @@ class _TrainListWidgetState extends State { SizedBox(width: 10), Expanded( flex: 2, - child: Text( - "$fromPlaceCountry (from) - (to) $toPlaceCountry", + child: Text("$fromPlaceCountry - $toPlaceCountry", // "${item["from_station"]!} - ${(item["to_station"])}", style: TextStyle( diff --git a/lib/Screens/plans/create_plans.dart b/lib/Screens/plans/create_plans.dart index d9525bd..0774bb4 100644 --- a/lib/Screens/plans/create_plans.dart +++ b/lib/Screens/plans/create_plans.dart @@ -1,8 +1,6 @@ import 'dart:convert'; -import 'dart:ui' as html; - import 'dart:async'; -import 'dart:html' as html; + import 'dart:typed_data'; import 'package:dropdown_search/dropdown_search.dart'; import 'package:web/web.dart' as web; @@ -14,6 +12,7 @@ import 'package:go_router/go_router.dart'; import 'package:responsive_builder/responsive_builder.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:http/http.dart' as http; +import 'package:universal_html/html.dart' as html; import '../../config/apiUrl.dart'; import '../../data/models/plan.dart'; @@ -556,6 +555,69 @@ class CreateNewPlansState extends State { }); } + Future getPdfDownload() async { + final String apiUrldata = + '$apiUrl/api/plans/download?plan_id=$selectedPlanId'; + + // final String apiUrldata = '$apiUrl/auth/googlelogin'; + + final token = await getToken(); + + if (token == null) { + throw Exception('Token not found. Please log in.'); + } + + final response = await http.get( + Uri.parse(apiUrldata), + headers: { + 'Authorization': 'Bearer $token', + 'Content-Type': 'application/json', + }, + ); + + if (response.statusCode == 200) { + try { + print("PDf Dowloaded"); + + // Create a blob from the response body + final blob = html.Blob([response.bodyBytes]); + +// Generate a download URL for the blob + final url = html.Url.createObjectUrlFromBlob(blob); + + // Create a link element to trigger the download + final anchor = html.AnchorElement(href: url) + ..setAttribute('download', 'trip_plan_$selectedPlanId.pdf') + ..click(); + + // Revoke the download URL to free up resources + html.Url.revokeObjectUrl(url); + } catch (e) { + throw Exception('Error parsing response: $e'); + } + } else if (response.statusCode == 404) { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + title: Text('File not found.'), + // content: Text('File not found.'), + actions: [ + TextButton( + child: Text('OK'), + onPressed: () { + Navigator.of(context).pop(); // Close the dialog + }, + ), + ], + ); + }, + ); + } else { + throw Exception('Failed to load plans'); + } + } + void getSelectedPlanFor() { if (!mounted) return; @@ -1031,80 +1093,105 @@ class CreateNewPlansState extends State { DeviceScreenType.desktop; return Container( - padding: const EdgeInsets.all(10), + padding: const EdgeInsets.all(5), child: isDesktop ? Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: _buildApproverControls(), + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ..._buildApproverControls(isDesktop), + Spacer(), + Text( + widget.isViewMode + ? "View Plan" + : (selectedPlanId != null && + selectedPlanId!.isNotEmpty + ? "Update Plan" + : "New Plan"), + style: TextStyle(fontSize: 18), + ), + Spacer(), + ..._buildPlanPdf() + ], ) : Column( crossAxisAlignment: CrossAxisAlignment.start, - children: _buildApproverControls(), + children: [ + ..._buildApproverControls(isDesktop), + Text( + widget.isViewMode + ? "View Plan" + : (planData.isNotEmpty + ? "Update Plan" + : "New Plan"), + style: TextStyle(fontSize: 18), + ), + ..._buildPlanPdf() + ], ), ); }), - if (isStatusExpanded) - Container( - margin: isDesktop - ? const EdgeInsets.only(left: 60, top: 0) - : const EdgeInsets.only(left: 5, top: 2), - padding: const EdgeInsets.all(12), - width: isDesktop - ? MediaQuery.of(context).size.width * 0.2 - : MediaQuery.of(context).size.width, - decoration: BoxDecoration( - color: Color(0xFFF5F5F5), - border: Border.all( - // color: Colors.grey.shade300, - color: Colors.white, - width: 0.2), - borderRadius: BorderRadius.circular(8), - // boxShadow: [ - // BoxShadow( - // // color: Colors.grey.withAlpha(20), - // color: Colors.grey.withAlpha(20), - // spreadRadius: 1.5, - // blurRadius: 7, - // offset: Offset(0, 4), // shadow direction: bottom - // ), - // ], - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - if (!hasApprovals) - Center( - child: Text( - "--- No Approvals ---", - style: TextStyle( - fontFamily: "Archivo", - fontSize: 11, - fontWeight: FontWeight.w500, - color: Colors.black87, - ), - )), - for (int i = 0; i < planStatusList.length; i++) ...[ - if (planStatusList[i].entries.any((entry) => - entry.key.contains('status') && - entry.value != null && - entry.value.toString().isNotEmpty)) ...[ - _buildApprovalItem( - "Approver ${i + 1}", - planStatusList[i] - .entries - .firstWhere( - (entry) => entry.key.contains('status'), - orElse: () => MapEntry('', ''), - ) - .value - .toString(), - ), - SizedBox(height: 6), - ], - ], - ], - ), - ), + // if (isStatusExpanded) + // Container( + // margin: isDesktop + // ? const EdgeInsets.only(left: 0, top: 0) + // : const EdgeInsets.only(left: 5, top: 2), + // padding: const EdgeInsets.all(12), + // width: isDesktop + // ? MediaQuery.of(context).size.width * 0.2 + // : MediaQuery.of(context).size.width, + // decoration: BoxDecoration( + // color: Color(0xFFF5F5F5), + // border: Border.all( + // // color: Colors.grey.shade300, + // color: Colors.white, + // width: 0.2), + // borderRadius: BorderRadius.circular(8), + // // boxShadow: [ + // // BoxShadow( + // // // color: Colors.grey.withAlpha(20), + // // color: Colors.grey.withAlpha(20), + // // spreadRadius: 1.5, + // // blurRadius: 7, + // // offset: Offset(0, 4), // shadow direction: bottom + // // ), + // // ], + // ), + // child: Column( + // crossAxisAlignment: CrossAxisAlignment.start, + // children: [ + // if (!hasApprovals) + // Center( + // child: Text( + // "--- No Approvals ---", + // style: TextStyle( + // fontFamily: "Archivo", + // fontSize: 11, + // fontWeight: FontWeight.w500, + // color: Colors.black87, + // ), + // )), + // for (int i = 0; i < planStatusList.length; i++) ...[ + // if (planStatusList[i].entries.any((entry) => + // entry.key.contains('status') && + // entry.value != null && + // entry.value.toString().isNotEmpty)) ...[ + // _buildApprovalItem( + // "Approver ${i + 1}", + // planStatusList[i] + // .entries + // .firstWhere( + // (entry) => entry.key.contains('status'), + // orElse: () => MapEntry('', ''), + // ) + // .value + // .toString(), + // ), + // SizedBox(height: 6), + // ], + // ], + // ], + // ), + // ), if (isApproverRejected) Row( crossAxisAlignment: CrossAxisAlignment.start, @@ -1140,11 +1227,13 @@ class CreateNewPlansState extends State { ), ], ), - if (widget.isApprover || isStatusExpanded) - Divider( - thickness: 0.1, - color: Colors.blueGrey, - ), + + // if (widget.isApprover || isStatusExpanded) + + Divider( + thickness: 0.1, + color: Colors.blueGrey, + ), if (widget.isApprover) SizedBox( height: 5, @@ -2283,55 +2372,217 @@ class CreateNewPlansState extends State { ); } - List _buildApproverControls() { + Color getStatusColor(String status) { + if (status == "Partially Approved") return Colors.yellow; + if (status == "Approved") return Colors.green; + if (status == "Completed") return Colors.green; + if (status == "Rejected") return Colors.red; + return Colors.grey; + } + + List _buildApproverControls(bool isDesktop) { + final statusText = isApproverApproved + ? "Approved" + : isApproverRejected + ? "Rejected" + : (statusValue ?? ""); + + bool hasApprovals = planStatusList.any( + (item) => item.entries.any( + (entry) => + entry.key.contains('status') && + entry.value != null && + entry.value.toString().isNotEmpty, + ), + ); + return [ - Row( - mainAxisSize: MainAxisSize.min, + Stack( + clipBehavior: Clip.none, // allow tooltip to overflow children: [ - Text("Status : "), - if (!isApproverApproved && !isApproverRejected) - Text(statusValue ?? "", - style: TextStyle( - fontFamily: "Archivo", - fontWeight: FontWeight.bold, - color: widget.layoutColor ?? Colors.grey, - )), - if (isApproverApproved) - Text("Approved", - style: TextStyle( - fontFamily: "Archivo", - fontWeight: FontWeight.bold, - color: widget.layoutColor ?? Colors.grey, - )), - if (isApproverRejected) - Text("Rejected", - style: TextStyle( - fontFamily: "Archivo", - fontWeight: FontWeight.bold, - color: widget.layoutColor ?? Colors.grey, - )), - SizedBox( - width: 5, - ), - MouseRegion( - onEnter: (_) { - setState(() { - isStatusExpanded = true; - }); - }, - onExit: (_) { - setState(() { - isStatusExpanded = false; - }); - }, - child: Icon( - Icons.approval_outlined, - size: 18, - color: isStatusExpanded ? Colors.green : Colors.grey, + if (statusValue != "") + MouseRegion( + onEnter: (_) => setState(() => isStatusExpanded = true), + onExit: (_) => setState(() => isStatusExpanded = false), + child: Container( + decoration: BoxDecoration( + border: Border.all(color: getStatusColor(statusText)), + borderRadius: BorderRadius.circular(8), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: const EdgeInsets.only( + top: 8.0, bottom: 8.0, left: 15, right: 15), + child: Text( + statusText, + style: TextStyle( + fontFamily: "Roboto", + fontWeight: FontWeight.w400, + fontSize: 12, + color: getStatusColor( + isApproverApproved + ? "Approved" + : isApproverRejected + ? "Rejected" + : (statusValue ?? ""), + ), + ), + ), + ), + ], + ), + ), ), - ), + + // Tooltip + if (isStatusExpanded) + Positioned( + top: -20, // Ensure the tooltip is above the container + left: + MediaQuery.of(context).size.width * 0.06, // align with label + child: Material( + // important: avoid clipping, give elevation + elevation: 4, + borderRadius: BorderRadius.circular(8), + child: SizedBox( + child: Container( + width: isDesktop + ? MediaQuery.of(context).size.width * 0.25 + : MediaQuery.of(context).size.width, + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: Colors.white, + // color: Colors.transparent, + border: Border.all(color: Colors.grey.shade300), + borderRadius: BorderRadius.circular(8), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (!hasApprovals) + Center( + child: Text( + "--- No Approvals ---", + style: TextStyle( + fontFamily: "Archivo", + fontSize: 11, + fontWeight: FontWeight.w500, + color: Colors.black87, + ), + )), + for (int i = 0; i < planStatusList.length; i++) ...[ + if (planStatusList[i].entries.any((entry) => + entry.key.contains('status') && + entry.value != null && + entry.value.toString().isNotEmpty)) ...[ + _buildApprovalItem( + "Approver ${i + 1}", + planStatusList[i] + .entries + .firstWhere( + (entry) => entry.key.contains('status'), + orElse: () => MapEntry('', ''), + ) + .value + .toString(), + ), + SizedBox(height: 6), + ], + ], + ], + ), + ), + ), + ), + ), + + // if (isStatusExpanded) + // Positioned( + // top: 20, // adjust how much above you want + // left: 100, + // child: Container( + // margin: isDesktop + // ? const EdgeInsets.only(left: 0, top: 0) + // : const EdgeInsets.only(left: 5, top: 2), + // padding: const EdgeInsets.all(12), + // width: isDesktop + // ? MediaQuery.of(context).size.width * 0.2 + // : MediaQuery.of(context).size.width, + // decoration: BoxDecoration( + // // color: Color(0xFFF5F5F5), + // color: Colors.white, + // border: Border.all(color: Colors.white, width: 0.2), + // borderRadius: BorderRadius.circular(8), + // ), + // child: Column( + // crossAxisAlignment: CrossAxisAlignment.start, + // children: [ + // if (!hasApprovals) + // Center( + // child: Text( + // "--- No Approvals ---", + // style: TextStyle( + // fontFamily: "Archivo", + // fontSize: 11, + // fontWeight: FontWeight.w500, + // color: Colors.black87, + // ), + // )), + // for (int i = 0; i < planStatusList.length; i++) ...[ + // if (planStatusList[i].entries.any((entry) => + // entry.key.contains('status') && + // entry.value != null && + // entry.value.toString().isNotEmpty)) ...[ + // _buildApprovalItem( + // "Approver ${i + 1}", + // planStatusList[i] + // .entries + // .firstWhere( + // (entry) => entry.key.contains('status'), + // orElse: () => MapEntry('', ''), + // ) + // .value + // .toString(), + // ), + // SizedBox(height: 6), + // ], + // ], + // ], + // ), + // ), + // ), ], ), + + // Row( + // mainAxisSize: MainAxisSize.min, + // children: [ + // Text( + // isApproverApproved + // ? "Approved" + // : isApproverRejected + // ? "Rejected" + // : (statusValue ?? ""), + // style: TextStyle( + // fontFamily: "Archivo", + // fontWeight: FontWeight.bold, + // color: widget.layoutColor ?? Colors.grey, + // ), + // ), + // SizedBox(width: 5), + // MouseRegion( + // onEnter: (_) => setState(() => isStatusExpanded = true), + // onExit: (_) => setState(() => isStatusExpanded = false), + // child: Icon( + // Icons.approval_outlined, + // size: 18, + // color: isStatusExpanded ? Colors.green : Colors.grey, + // ), + // ), + // ], + // ), const SizedBox(height: 10, width: 10), if (widget.isApprover) GestureDetector( @@ -2461,6 +2712,38 @@ class CreateNewPlansState extends State { ]; } + List _buildPlanPdf() { + return [ + Column( + children: [ + InkWell( + hoverColor: Colors.white, + onTap: () { + print('📄 PDF icon clicked!'); + getPdfDownload(); + }, + child: Row( + children: [ + Text("Download PDF"), + SizedBox( + width: 10, + ), + Transform.scale( + scale: 1.5, // 1.0 = normal, 1.5 = 50% bigger + child: Image.asset( + 'assets/images/IconsImg/planPdf_icon.png', + width: 25, + height: 25, // keep the real height small + ), + ), + ], + )), + // SizedBox(height: 10), // small spacing + ], + ), + ]; + } + void _showInputDialog(String title) { showDialog( context: context, diff --git a/lib/Screens/plans/dynamic_itinerary_stepper.dart b/lib/Screens/plans/dynamic_itinerary_stepper.dart index d6790c9..5337a7e 100644 --- a/lib/Screens/plans/dynamic_itinerary_stepper.dart +++ b/lib/Screens/plans/dynamic_itinerary_stepper.dart @@ -154,7 +154,7 @@ class DynamicItineraryState extends State { List getAllowedServiceNames() { if (widget.tripType == "1") { - return ["flight", "accomodation", "train", "bus"]; + return ["flight", "accomodation", "train", "bus", "taxi"]; } else if (widget.tripType == "2") { return [ "flight", @@ -162,7 +162,8 @@ class DynamicItineraryState extends State { "forex", "insurance", "visa", - "miscellaneous" + "miscellaneous", + "taxi" ]; } else { // tripType is null or not 1/2, allow everything diff --git a/lib/Screens/plans/list_plans.dart b/lib/Screens/plans/list_plans.dart index 867b691..4d49138 100644 --- a/lib/Screens/plans/list_plans.dart +++ b/lib/Screens/plans/list_plans.dart @@ -27,7 +27,6 @@ class _ListPlansState extends State { int currentPage = 0; int itemsPerPage = 8; - late Future> futurePlans; String? userId; String? orgId; String? token; @@ -35,7 +34,7 @@ class _ListPlansState extends State { Color? layoutColor; Color? bodyColor; - + late Future> futurePlans; List allPlans = []; List filteredPlans = []; TextEditingController searchController = TextEditingController(); @@ -49,22 +48,24 @@ class _ListPlansState extends State { initializeData(); loadInitialData(); - futurePlans.then((plans) { - setState(() { - allPlans = plans; - filteredPlans = plans; - }); - }); + // futurePlans.then((plans) { + // setState(() { + // allPlans = plans; + // filteredPlans = plans; + // }); + // }); }); // futurePlans = fetchPlans(); } void filterPlans(String query) { + print("allPlans before filtering: $allPlans"); final lowerQuery = query.toLowerCase(); setState(() { filteredPlans = allPlans.where((plan) { return (plan.planId?.toLowerCase().contains(lowerQuery) ?? false) || + (plan.employeeCode?.toLowerCase().contains(lowerQuery) ?? false) || (plan.tripTitle?.toLowerCase().contains(lowerQuery) ?? false) || (plan.userName?.toLowerCase().contains(lowerQuery) ?? false) || (plan.travellerName?.toLowerCase().contains(lowerQuery) ?? false) || @@ -73,6 +74,7 @@ class _ListPlansState extends State { (plan.statusValue?.toLowerCase().contains(lowerQuery) ?? false); }).toList(); }); + print("filteredPlans: $filteredPlans"); } void loadInitialData() async { @@ -103,6 +105,15 @@ class _ListPlansState extends State { setState(() { futurePlans = fetchPlans(); }); + + // Wait for futurePlans to be fetched and update allPlans + futurePlans.then((plans) { + setState(() { + allPlans = plans; // Set allPlans after the fetch is complete + filteredPlans = + allPlans; // You can update filteredPlans too if needed + }); + }); } } @@ -350,7 +361,7 @@ class _ListPlansState extends State { children: [ const Text('Trip List', style: TextStyle( - fontFamily: "Archivo", + fontFamily: "Inter", fontSize: 16, fontWeight: FontWeight.w600, color: Color(0xFF212121))), @@ -358,31 +369,43 @@ class _ListPlansState extends State { ), Spacer(), + Container( width: MediaQuery.of(context).size.width * 0.2, + height: 40, child: TextField( controller: searchController, onChanged: filterPlans, decoration: InputDecoration( - hintText: "Search for a plan", + hintText: "Search...", hintStyle: - TextStyle(fontSize: 14, color: Color(0xFF9E9DBD)), - prefixIcon: - Icon(Icons.search, color: Color(0xFF9E9DBD)), + TextStyle(fontSize: 12, color: Color(0xFF9E9DBD)), + prefixIcon: Icon( + Icons.search, + color: Color(0xFF9E9DBD), + size: 18, + ), border: OutlineInputBorder( - borderRadius: BorderRadius.circular(8), + borderRadius: BorderRadius.circular(12), ), enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), borderSide: BorderSide( - color: Colors.grey.shade300, width: 0.5), + color: Colors.grey.shade200, width: 0.5), ), focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), borderSide: - BorderSide(color: Colors.blueAccent, width: 1), + BorderSide(color: Colors.grey.shade300, width: 1), ), ), + style: TextStyle( + fontSize: 12, + fontFamily: "Inter", + ), ), ), + // SizedBox(width: 16), Spacer(), @@ -511,7 +534,10 @@ class _ListPlansState extends State { ); } - List plans = snapshot.data!; + // List plans = snapshot.data!; + + List plans = + filteredPlans.isNotEmpty ? filteredPlans : allPlans; plans.sort((a, b) => int.parse(b.planId).compareTo(int.parse(a.planId))); @@ -537,58 +563,71 @@ class _ListPlansState extends State { DataColumn( label: Text('Trip Id', style: TextStyle( - color: Color(0xFF9E9DBD), - fontSize: 14, - fontFamily: "Archivo", - fontWeight: FontWeight.bold))), + // color: Colors.grey, + // color: Color(0xFF9E9DBD), + fontSize: 13, + fontFamily: "Inter", + fontWeight: FontWeight.w600))), + DataColumn( + label: Text('Employee Code', + style: TextStyle( + // color: Color(0xFF9E9DBD), + fontSize: 13, + fontFamily: "Inter", + fontWeight: FontWeight.w600))), DataColumn( label: Text('Trip Name', style: TextStyle( - color: Color(0xFF9E9DBD), - fontFamily: "Archivo", - fontWeight: FontWeight.bold))), + fontSize: 13, + fontFamily: "Inter", + fontWeight: FontWeight.w600))), DataColumn( label: Text('Traveller', style: TextStyle( - color: Color(0xFF9E9DBD), - fontFamily: "Archivo", - fontWeight: FontWeight.bold))), + fontSize: 13, + fontFamily: "Inter", + fontWeight: FontWeight.w600))), DataColumn( label: Text('Trip Type', style: TextStyle( - color: Color(0xFF9E9DBD), - fontFamily: "Archivo", - fontWeight: FontWeight.bold))), + fontSize: 13, + fontFamily: "Inter", + fontWeight: FontWeight.w600))), DataColumn( label: Text('Created On', style: TextStyle( - color: Color(0xFF9E9DBD), - fontFamily: "Archivo", - fontWeight: FontWeight.bold))), + fontSize: 13, + fontFamily: "Inter", + fontWeight: FontWeight.w600))), DataColumn( label: Text('Status', style: TextStyle( - color: Color(0xFF9E9DBD), - fontFamily: "Archivo", - fontWeight: FontWeight.bold))), + fontSize: 13, + fontFamily: "Inter", + fontWeight: FontWeight.w600))), DataColumn( label: Text('Actions', style: TextStyle( - color: Color(0xFF9E9DBD), - fontFamily: "Archivo", - fontWeight: FontWeight.bold))), + fontSize: 13, + fontFamily: "Inter", + fontWeight: FontWeight.w600))), ], rows: paginatedPlans.map((plan) { return DataRow(cells: [ DataCell(Text(plan.planId, style: TextStyle( fontSize: 13, - fontFamily: "Archivo", + fontFamily: "Inter", + ))), + DataCell(Text(plan.employeeCode ?? "no data", + style: TextStyle( + fontSize: 13, + fontFamily: "Inter", ))), DataCell(Text(plan.tripTitle, style: TextStyle( fontSize: 13, - fontFamily: "Archivo", + fontFamily: "Inter", ), softWrap: true, overflow: TextOverflow.ellipsis)), @@ -598,18 +637,18 @@ class _ListPlansState extends State { : plan.travellerName, style: TextStyle( fontSize: 13, - fontFamily: "Archivo", + fontFamily: "Inter", ))), DataCell(Text(plan.tripType, style: TextStyle( fontSize: 13, - fontFamily: "Archivo", + fontFamily: "Inter", ))), DataCell(Text(_formatDate(plan.createdOn), // plan.createdOn, style: TextStyle( fontSize: 13, - fontFamily: "Archivo", + fontFamily: "Inter", ))), DataCell( Container( @@ -646,6 +685,7 @@ class _ListPlansState extends State { // ? Colors.white // : Colors.grey, fontSize: 12, + fontFamily: "Inter", fontWeight: FontWeight.w400, ), ), @@ -668,7 +708,7 @@ class _ListPlansState extends State { isViewMode: false), child: Image.asset( 'assets/images/IconsImg/edit.png', - width: 20, + width: 15, height: 15), ), @@ -688,6 +728,15 @@ class _ListPlansState extends State { width: 20, height: 15), ), + IconButton( + icon: const Icon( + Icons.download, + color: Color(0xFF475569), + size: 18, + ), + onPressed: () { + apiService.getPdfDownload(plan.planId); + }), ])), ]); }).toList(), @@ -698,6 +747,7 @@ class _ListPlansState extends State { return Expanded( child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ isDesktop ? table @@ -734,6 +784,7 @@ class _ListPlansState extends State { fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black87, + fontFamily: "Inter", ), ), SizedBox(width: 2), diff --git a/lib/Screens/policy/policy.dart b/lib/Screens/policy/policy.dart index 898805d..0f7fc5b 100644 --- a/lib/Screens/policy/policy.dart +++ b/lib/Screens/policy/policy.dart @@ -111,15 +111,18 @@ class _PolicyState extends State { WidgetsBinding.instance.addPostFrameCallback((_) { loadinitializeData(); - if (widget.policy != null) { - final details = - List>.from(widget.policy!['policy_details']); - policyCriteriaKey.currentState?.loadPolicyDetails(details); - } updateSelectedServices(); updateData(); loadInitialData(); + + if (widget.policy != null) { + final details = + List>.from(widget.policy!['policy_details']); + policyCriteriaKey.currentState?.loadPolicyDetails(details); + + policyCriteriaKey.currentState?.fetchTrainFlightClass(); + } }); } @@ -233,6 +236,7 @@ class _PolicyState extends State { String firstServiceName = ServicesChoosed?.first['name']; print("✅ First service name selected for filter: $firstServiceName"); selectedService = firstServiceName; + policyCriteriaKey.currentState?.fetchTrainFlightClass(); } } } @@ -538,89 +542,25 @@ class _PolicyState extends State { ), isDesktop ? SizedBox(height: 0) : SizedBox(height: 5), Container( - // color: Colors.amber, - // color: isDesktop ? Color(0xFFF7F7FB) : Colors.white, - padding: isDesktop ? const EdgeInsets.only(left: 35) : null, - child: Column( - children: [ - Row( + padding: isDesktop ? const EdgeInsets.only(left: 35) : null, + child: isDesktop + ? Row( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text("Policy Name", - style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.w200, - color: Colors.black)), - SizedBox(height: 5), - CustomTextFieldUserWrapper( - isFocused: false, - isDesktop: isDesktop, - child: SizedBox( - height: 40, - child: TextField( - style: TextStyle(fontSize: 12), - controller: _policyController, - // enabled: !isViewMode, - onChanged: (value) { - _clearError("name"); - }, - decoration: InputDecoration( - labelText: "Policy Name", - labelStyle: TextStyle( - fontSize: 12, color: Colors.grey), - floatingLabelBehavior: - FloatingLabelBehavior.never, - border: InputBorder.none, - contentPadding: - EdgeInsets.symmetric(vertical: 16), - ), - ), - ), - ), - if (errorMessages["name"] != null) ...[ - SizedBox(height: 5), // Space before error message - Text( - errorMessages["name"]!, - style: TextStyle(color: Colors.red, fontSize: 12), - ), - ], - ], - ), + Expanded(child: _buildPolicyNameField(isDesktop)), + Spacer(), + Expanded(child: _buildPolicyTypeField(isDesktop)), + ], + ) + : Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildPolicyNameField(isDesktop), + SizedBox(height: 20), + _buildPolicyTypeField(isDesktop), ], ), - SizedBox( - height: 5, - ), - Row( - children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text("Policy Type", - style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.w200, - color: Colors.black)), - SizedBox(height: 5), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: _buildTripType(isDesktop), - ), - if (errorMessages["trip_type"] != null) ...[ - SizedBox(height: 5), // Space before error message - Text( - errorMessages["trip_type"]!, - style: TextStyle(color: Colors.red, fontSize: 12), - ), - ], - ], - ) - ], - ), - ], - )), + ), SizedBox( height: 10, ), @@ -657,6 +597,67 @@ class _PolicyState extends State { ); } + Widget _buildPolicyNameField(bool isDesktop) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text("Policy Name", + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w200, + color: Colors.black)), + SizedBox(height: 5), + CustomTextFieldUserWrapper( + isFocused: false, + isDesktop: isDesktop, + child: SizedBox( + height: 40, + child: TextField( + style: TextStyle(fontSize: 12), + controller: _policyController, + onChanged: (value) => _clearError("name"), + decoration: InputDecoration( + labelText: "Policy Name", + labelStyle: TextStyle(fontSize: 12, color: Colors.grey), + floatingLabelBehavior: FloatingLabelBehavior.never, + border: InputBorder.none, + contentPadding: EdgeInsets.symmetric(vertical: 16), + ), + ), + ), + ), + if (errorMessages["name"] != null) ...[ + SizedBox(height: 5), + Text(errorMessages["name"]!, + style: TextStyle(color: Colors.red, fontSize: 12)), + ], + ], + ); + } + + Widget _buildPolicyTypeField(bool isDesktop) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text("Policy Type", + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w200, + color: Colors.black)), + SizedBox(height: 5), + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: _buildTripType(isDesktop), + ), + if (errorMessages["trip_type"] != null) ...[ + SizedBox(height: 5), + Text(errorMessages["trip_type"]!, + style: TextStyle(color: Colors.red, fontSize: 12)), + ], + ], + ); + } + Widget _buildPolicyCategoryList(bool isDesktop) { return Container( color: Colors.white, @@ -727,6 +728,8 @@ class _PolicyState extends State { selectedService == "Train") { showClass = true; showCost = true; + int serviceCode = selectedService == "Flight" ? 1 : 2; + policyCriteriaKey.currentState?.fetchTrainFlightClass(); } else if (selectedService == "Accommodation") { showClass = true; showCost = false; @@ -825,23 +828,6 @@ class _PolicyState extends State { fontWeight: _selectedTripType == "1" ? FontWeight.w600 : null, fontSize: 13), ), - - // Radio( - // activeColor: Colors.blueAccent, - // // contentPadding: EdgeInsets.zero, - // visualDensity: VisualDensity.compact, - // // dense: true, - // value: "1", - // groupValue: _selectedTripType, - // onChanged: widget.isViewMode - // ? null - // : (value) { - // setState(() { - // _selectedTripType = value!; - // }); - // }, - // ), - GestureDetector( onTap: () { setState(() { diff --git a/lib/Screens/policy/policyCriteria.dart b/lib/Screens/policy/policyCriteria.dart index f62403b..f2bf5f2 100644 --- a/lib/Screens/policy/policyCriteria.dart +++ b/lib/Screens/policy/policyCriteria.dart @@ -1,6 +1,12 @@ +import 'dart:convert'; + import 'package:dropdown_search/dropdown_search.dart'; import 'package:flutter/material.dart'; +import 'package:http/http.dart' as http; +import '../../config/apiUrl.dart'; +import '../../services/apiService.dart'; +import '../../utils/auth_utils.dart'; import '../../widgets/custom_text_field.dart'; import '../../widgets/custom_user_form.dart'; @@ -30,6 +36,12 @@ class PolicyCriteria extends StatefulWidget { class PolicyCriteriaState extends State { String? ServiceId = "1"; + String? orgId; + String? userId; + + Map? apiDataForClass; + bool isLoading = true; + // final TextEditingController _costController = TextEditingController(); // final TextEditingController _classController = TextEditingController(); // String? FirstApproverAction; @@ -37,12 +49,17 @@ class PolicyCriteriaState extends State { // String? ThirdApproverAction; // String? SelectedParallelProcess = "3"; + final ApiService apiService = ApiService(); + Map costController = {}; - Map classController = {}; + // Map classController = {}; + Map classAction = {}; Map FirstApproverAction = {}; Map SecondApproverAction = {}; Map ThirdApproverAction = {}; Map SelectedParallelProcess = {}; + Map policyDetailsIdMap = {}; // new + Map policyIdMap = {}; // new Map validationErrors = {}; @@ -72,41 +89,72 @@ class PolicyCriteriaState extends State { widget.selectedTabNotifier.addListener(() { print("selectedTab changed: ${widget.selectedTabNotifier.value}"); fieldForPolicy(); + fetchTrainFlightClass(); }); } + void loadinitializeData() async { + orgId = await getOrgId(); + userId = await getUserId(); + } + void saveCurrentPolicy() { if (ServiceId != null) { addOrUpdatePolicy(ServiceId!); } } + // To set the data (update) void loadPolicyDetails(List> details) { for (var item in details) { final serviceId = item['service_id'].toString(); costController[serviceId] = TextEditingController(text: item['cost'] ?? ''); - classController[serviceId] = - TextEditingController(text: item['class'] ?? ''); + + // classAction[serviceId] = classAction[serviceId]?.toString(); + + classAction[serviceId] = item['class']?.toString() ?? ''; FirstApproverAction[serviceId] = item['a1_action']?.toString(); SecondApproverAction[serviceId] = item['a2_action']?.toString(); ThirdApproverAction[serviceId] = item['a3_action']?.toString(); SelectedParallelProcess[serviceId] = item['parallel_process_from']?.toString() ?? "3"; + + if (item['policy_details_id'] != null) { + policyDetailsIdMap[serviceId] = item['policy_details_id'].toString(); + } + if (item['policy_id'] != null) { + policyIdMap[serviceId] = item['policy_id'].toString(); + } } policyData = details; widget.onPolicyDataChanged(policyData); + + if (details.isNotEmpty) { + final firstServiceId = details.first['service_id'].toString(); + print('First Service ID is: $firstServiceId'); + // firstServiceId == "1" + // ? fetchTrainFlightClass(1) + // : fetchTrainFlightClass(2); + } setState(() {}); + + print("detailsdetails- $details"); } void addOrUpdatePolicy(String serviceId) { + // 1. First, find existing item if any + final existingIndex = + policyData!.indexWhere((item) => item["service_id"] == serviceId); + Map data = { "service_id": serviceId, "cost": costController[serviceId]?.text, - "class": classController[serviceId]?.text, + // "class": classController[serviceId]?.text, + "class": classAction[serviceId], "a1_action": FirstApproverAction[serviceId], "a2_action": SecondApproverAction[serviceId], "a3_action": ThirdApproverAction[serviceId], @@ -115,12 +163,15 @@ class PolicyCriteriaState extends State { }; final cost = costController[serviceId]?.text ?? ""; - final travelClass = classController[serviceId]?.text ?? ""; + + // final travelClass = classController[serviceId]?.text ?? ""; + final travelClass = classAction[serviceId]; final a1 = FirstApproverAction[serviceId]; final a2 = SecondApproverAction[serviceId]; final a3 = ThirdApproverAction[serviceId]; - bool hasValue = cost.trim().isNotEmpty || travelClass.trim().isNotEmpty; + bool hasValue = cost.trim().isNotEmpty; + // bool hasValue = cost.trim().isNotEmpty || travelClass.trim().isNotEmpty; bool allActionsNull = a1 == null && a2 == null && a3 == null; bool someActionsMissing = [a1, a2, a3].where((a) => a != null).length > 0 && [a1, a2, a3].where((a) => a == null).length > 0; @@ -137,19 +188,103 @@ class PolicyCriteriaState extends State { validationErrors.remove(serviceId); - int index = - policyData!.indexWhere((item) => item["service_id"] == serviceId); - if (index != -1) { - policyData![index] = data; // Replace existing entry + // 2. ⚡ If updating an existing item, also **keep its IDs** + if (existingIndex != -1) { + final existingItem = policyData![existingIndex]; + + if (existingItem.containsKey("policy_details_id")) { + data["policy_details_id"] = existingItem["policy_details_id"]; + } + if (existingItem.containsKey("policy_id")) { + data["policy_id"] = existingItem["policy_id"]; + } + + // 3. Now update it + policyData![existingIndex] = data; print("🔁 Updated policy for ServiceId: $serviceId"); } else { - policyData!.add(data); // Add new entry + // 4. Else add as new + policyData!.add(data); print("➕ Added policy for ServiceId: $serviceId"); } + // int index = + // policyData!.indexWhere((item) => item["service_id"] == serviceId); + // if (index != -1) { + // policyData![index] = data; // Replace existing entry + // print("🔁 Updated policy for ServiceId: $serviceId"); + // } else { + // policyData!.add(data); // Add new entry + // print("➕ Added policy for ServiceId: $serviceId"); + // } + print("📋 policyData - $policyData"); } + // Future fetchTrainFlightClass(int tripId) async { + // // final String apiUrldata = '$apiUrl/api/getDropdownMaster'; + // + // // final String apiUrldata = + // // '$apiUrl/api/getFlightAndTrainClass?user_id=${widget.userId}&trip_type=tripId'; + // final String apiUrldata = await apiService.fetchMasterDropdown(); + // + // final token = await getToken(); + // + // if (token == null) { + // throw Exception('Token not found. Please log in.'); + // } + // + // final response = await http.get( + // Uri.parse(apiUrldata), + // headers: { + // 'Authorization': 'Bearer $token', + // 'Content-Type': 'application/json', + // }, + // ); + // + // if (response.statusCode == 200) { + // try { + // final data = json.decode(response.body); + // print(data); + // + // if (!data.containsKey('data') || data['data'] is! Map) { + // throw Exception( + // "Invalid response format: 'data' field is missing or not a Map"); + // } + // + // Map plansJson = + // data['data']; // 'data' is a Map, not a List + // setState(() { + // apiDataForClass = plansJson; // Store API response in state + // isLoading = false; + // }); + // } catch (e) { + // throw Exception('Error parsing response: $e'); + // } + // } else { + // throw Exception('Failed to load plans'); + // } + // } + + Future fetchTrainFlightClass() async { + try { + final data = await apiService.fetchMasterDropdown(); + + print("fetchTrainFlightClass - $data"); + + if (data is! Map) { + throw Exception("Invalid response format: expected a Map"); + } + + setState(() { + apiDataForClass = data; // Store API response in state + isLoading = false; + }); + } catch (e) { + print('Error fetching role list: $e'); + } + } + void fieldForPolicy() { print("fieldForPolicy - ${widget.selectedTabNotifier.value}"); @@ -162,7 +297,8 @@ class PolicyCriteriaState extends State { ServiceId = widget.selectedTabNotifier.value ?? "1"; // Initialize controllers and variables if not present costController.putIfAbsent(ServiceId!, () => TextEditingController()); - classController.putIfAbsent(ServiceId!, () => TextEditingController()); + classAction.putIfAbsent(ServiceId!, () => null); + // classController.putIfAbsent(ServiceId!, () => TextEditingController()); FirstApproverAction.putIfAbsent(ServiceId!, () => null); SecondApproverAction.putIfAbsent(ServiceId!, () => null); @@ -210,7 +346,7 @@ class PolicyCriteriaState extends State { ? Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - if (widget.isClass!) + if (widget.isClass) buildClassWidget(widget.isDesktop), SizedBox(height: 10), if (widget.isCost!) @@ -372,6 +508,7 @@ class PolicyCriteriaState extends State { items: [ "Approval", "Notification", + "None" ], dropdownDecoratorProps: DropDownDecoratorProps( @@ -500,6 +637,7 @@ class PolicyCriteriaState extends State { items: [ "Approval", "Notification", + "None" ], dropdownDecoratorProps: DropDownDecoratorProps( @@ -633,6 +771,7 @@ class PolicyCriteriaState extends State { items: [ "Approval", "Notification", + "None" ], dropdownDecoratorProps: DropDownDecoratorProps( @@ -740,6 +879,58 @@ class PolicyCriteriaState extends State { } Widget buildClassWidget(isDesktop) { + List purposeList = []; + if (ServiceId != null) { + purposeList = (ServiceId == "1") + ? (apiDataForClass?['flight_class'] ?? []) + : (apiDataForClass?['train_class'] ?? []); + } + + print("CLASS1 - "); + + List> dropdownItems = purposeList + .map((item) => DropdownMenuItem( + value: item['dropdown_key'], + child: Text(item['dropdown_value']), + )) + .toList(); + + print("CLASS 2 - "); + + if (dropdownItems.isEmpty) { + dropdownItems.add( + DropdownMenuItem( + value: null, + child: Text("No options available", + style: TextStyle(color: Colors.grey)), + ), + ); + } + + // if (ServiceId != null) { + // if (classAction[ServiceId!] == null && dropdownItems.isNotEmpty) { + // classAction[ServiceId!] = dropdownItems.first.value!; + // } + // } + + // if (ServiceId != null && + // classAction[ServiceId!] == null && + // dropdownItems.isNotEmpty) { + // classAction[ServiceId!] = dropdownItems.first.value!; + // } + + // 1. First, when dropdownItems are ready, assign default value: + if (ServiceId != null && + classAction[ServiceId!] == null && + dropdownItems.isNotEmpty) { + // Important to wrap inside setState if async + WidgetsBinding.instance.addPostFrameCallback((_) { + setState(() { + classAction[ServiceId!] = dropdownItems.first.value!; + }); + }); + } + return Row( children: [ Column( @@ -754,21 +945,48 @@ class PolicyCriteriaState extends State { CustomTextFieldUserWrapper( isFocused: false, isDesktop: widget.isDesktop, + // child: SizedBox( + // height: 40, + // child: TextField( + // style: TextStyle(fontSize: 12), + // // controller: classController[ServiceId], + // // enabled: !isViewMode, + // onChanged: (value) {}, + // decoration: InputDecoration( + // labelText: "Class", + // labelStyle: TextStyle(fontSize: 12, color: Colors.grey), + // floatingLabelBehavior: FloatingLabelBehavior.never, + // border: InputBorder.none, + // contentPadding: EdgeInsets.symmetric(vertical: 16), + // ), + // ), + // ), + child: SizedBox( height: 40, - child: TextField( - style: TextStyle(fontSize: 12), - controller: classController[ServiceId], - // enabled: !isViewMode, - onChanged: (value) {}, - decoration: InputDecoration( - labelText: "Class", - labelStyle: TextStyle(fontSize: 12, color: Colors.grey), - floatingLabelBehavior: FloatingLabelBehavior.never, - border: InputBorder.none, - contentPadding: EdgeInsets.symmetric(vertical: 16), - ), - ), + child: ServiceId != null && (isLoading || dropdownItems.isEmpty) + ? const Center(child: CircularProgressIndicator()) + : DropdownButtonFormField( + // Assign the correct focus node + // controller: _hotelNameController, + value: classAction[ServiceId], + + style: TextStyle(fontSize: 12), + decoration: InputDecoration( + border: InputBorder.none, + contentPadding: EdgeInsets.symmetric( + horizontal: 10), // Proper padding + ), + onChanged: (newValue) { + if (ServiceId != null && newValue != null) { + setState(() { + classAction[ServiceId!] = newValue; + }); + } + }, + + items: dropdownItems, + ), ), ), ], diff --git a/lib/Screens/policy/policy_list.dart b/lib/Screens/policy/policy_list.dart index 11e995d..34357cd 100644 --- a/lib/Screens/policy/policy_list.dart +++ b/lib/Screens/policy/policy_list.dart @@ -52,6 +52,14 @@ class _PolicyListState extends State { Future loadAllGroups() async { try { final result = await apiService.fetchAllPolicy(); + + // Sort by policy_id descending (latest first) + result.sort((a, b) { + int idA = int.tryParse(a['policy_id'].toString()) ?? 0; + int idB = int.tryParse(b['policy_id'].toString()) ?? 0; + return idB.compareTo(idA); // latest first + }); + setState(() { apiAllGroups = result; }); diff --git a/lib/Screens/userManagement/user_List.dart b/lib/Screens/userManagement/user_List.dart index 2518942..1d02459 100644 --- a/lib/Screens/userManagement/user_List.dart +++ b/lib/Screens/userManagement/user_List.dart @@ -24,11 +24,24 @@ class _UserListScreenState extends State { Color? layoutColor; Color? bodyColor; + List allUsers = []; + List filteredUsers = []; + TextEditingController searchController = TextEditingController(); + + int currentPage = 0; + int itemsPerPage = 8; + @override void initState() { super.initState(); futureUsers = fetchUsers(); + futureUsers.then((users) { + setState(() { + allUsers = users; + }); + }); + WidgetsBinding.instance.addPostFrameCallback((_) { fetchCountryList(); loadInitialData(); @@ -57,6 +70,35 @@ class _UserListScreenState extends State { return prefs.getString('auth_token'); } + // Future> fetchSingleUsers(userId) async { + // orgId = await getOrgId(); + // final String apiUrlData = '$apiUrl/api/users/find/$userId'; + // + // final String? token = await getToken(); + // + // print("Fetch Users"); + // print("TOEKRWE: $token"); + // + // if (token == null) { + // throw Exception('Token not found. Please log in.'); + // } + // + // final response = await http.get( + // Uri.parse(apiUrlData), + // headers: { + // 'Authorization': 'Bearer $token', + // 'Content-Type': 'application/json', + // }, + // ); + // + // if (response.statusCode == 200) { + // final data = json.decode(response.body); + // return data['data']; // Returning raw JSON list + // } else { + // throw Exception('Failed to load users'); + // } + // } + Future> fetchUsers() async { orgId = await getOrgId(); final String apiUrlData = '$apiUrl/api/users?org_id=$orgId'; @@ -236,9 +278,25 @@ class _UserListScreenState extends State { void refreshUserList() { setState(() { futureUsers = fetchUsers(); // Re-fetch users after status update + // Wait for futurePlans to be fetched and update allPlans }); } + void filterUsers(String query) { + print("allUsers before filtering: $query"); + final lowerQuery = query.toLowerCase(); + setState(() { + filteredUsers = allUsers.where((user) { + return (user['first_name']?.toLowerCase().contains(lowerQuery) ?? + false) || + (user['last_name']?.toLowerCase().contains(lowerQuery) ?? false) || + (user['email']?.toLowerCase().contains(lowerQuery) ?? false) || + (user['role_value']?.toLowerCase().contains(lowerQuery) ?? false); + }).toList(); + }); + print("filteredPlans: $filteredUsers"); + } + @override Widget build(BuildContext context) { return ResponsiveBuilder(builder: (context, sizingInfo) { @@ -294,19 +352,7 @@ class _UserListScreenState extends State { 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, - // // color: Color(0xFFF7F7FB), - // ) - // : null, - // color: Colors.white, - // // color: Color(0xFFF7F7FB), - // - // // color: Colors.amber, - // ), + child: Padding( padding: const EdgeInsets.all(10.0), child: Container( @@ -326,7 +372,7 @@ class _UserListScreenState extends State { children: [ const Text('User List', style: TextStyle( - fontFamily: "Archivo", + fontFamily: "Inter", fontSize: 16, fontWeight: FontWeight.w600, color: Color(0xFF212121))), @@ -334,29 +380,38 @@ class _UserListScreenState extends State { ), Spacer(), Container( - width: MediaQuery.of(context).size.width * 0.3, - // or use Flexible + width: MediaQuery.of(context).size.width * 0.2, + height: 40, child: TextField( - onChanged: (query) {}, + controller: searchController, + onChanged: filterUsers, decoration: InputDecoration( - hintText: "Search for a plan", + hintText: "Search for a User", hintStyle: TextStyle( - fontSize: 14, color: Color(0xFF9E9DBD)), - prefixIcon: - Icon(Icons.search, color: Color(0xFF9E9DBD)), + fontSize: 12, color: Color(0xFF9E9DBD)), + prefixIcon: Icon( + Icons.search, + color: Color(0xFF9E9DBD), + size: 18, + ), border: OutlineInputBorder( - borderRadius: BorderRadius.circular(8), + borderRadius: BorderRadius.circular(12), ), enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), borderSide: BorderSide( - color: Colors.grey.shade300, width: 0.5), + color: Colors.grey.shade200, width: 0.5), ), focusedBorder: OutlineInputBorder( - // borderRadius: BorderRadius.circular(8), + borderRadius: BorderRadius.circular(12), borderSide: BorderSide( - color: Colors.blueAccent, width: 1), + color: Colors.grey.shade300, width: 1), ), ), + style: TextStyle( + fontSize: 12, + fontFamily: "Inter", + ), ), ), // SizedBox(width: 16), @@ -451,9 +506,21 @@ class _UserListScreenState extends State { ); } - List users = snapshot.data!; - // users.sort((a, b) => - // int.parse(b.planId).compareTo(int.parse(a.planId))); + List users = + filteredUsers.isNotEmpty ? filteredUsers : allUsers; + + users.sort((a, b) { + DateTime dateA = DateTime.parse(a['created_on']); + DateTime dateB = DateTime.parse(b['created_on']); + + return dateB + .compareTo(dateA); // Descending: newest first + }); + + List paginatedUser = users + .skip(currentPage * itemsPerPage) + .take(itemsPerPage) + .toList(); Widget table = LayoutBuilder( builder: (context, constraints) { @@ -474,36 +541,36 @@ class _UserListScreenState extends State { label: Text('UserName', style: TextStyle( // color: Color(0xFF9E9DBD), - color: Color(0xFF9E9DBD), - fontSize: 14, - fontFamily: "Archivo", - fontWeight: FontWeight.w500))), + // color: Color(0xFF9E9DBD), + fontSize: 13, + fontFamily: "Inter", + fontWeight: FontWeight.w600))), DataColumn( label: Text('Email Id', style: TextStyle( - color: Color(0xFF9E9DBD), - fontFamily: "Archivo", - fontWeight: FontWeight.bold))), + fontSize: 13, + fontFamily: "Inter", + fontWeight: FontWeight.w600))), DataColumn( label: Text('Role', style: TextStyle( - color: Color(0xFF9E9DBD), - fontFamily: "Archivo", - fontWeight: FontWeight.bold))), + fontSize: 13, + fontFamily: "Inter", + fontWeight: FontWeight.w600))), DataColumn( label: Text('Status', style: TextStyle( - color: Color(0xFF9E9DBD), - fontFamily: "Archivo", - fontWeight: FontWeight.bold))), + fontSize: 13, + fontFamily: "Inter", + fontWeight: FontWeight.w600))), DataColumn( label: Text('Actions', style: TextStyle( - color: Color(0xFF9E9DBD), - fontFamily: "Archivo", - fontWeight: FontWeight.bold))), + fontSize: 13, + fontFamily: "Inter", + fontWeight: FontWeight.w600))), ], - rows: users.map((user) { + rows: paginatedUser.map((user) { String userId = user['user_id'].toString(); // Get user ID bool isSelected = selectedUserId == userId; @@ -513,17 +580,17 @@ class _UserListScreenState extends State { "${user['first_name'] ?? ''} ${user['last_name'] ?? ''}", style: TextStyle( fontSize: 13, - fontFamily: "Archivo", + fontFamily: "Inter", ))), DataCell(Text(user['email'] ?? '', style: TextStyle( fontSize: 13, - fontFamily: "Archivo", + fontFamily: "Inter", ))), - DataCell(Text(user['role_id'] ?? 'N/A', + DataCell(Text(user['role_value'] ?? 'N/A', style: TextStyle( fontSize: 13, - fontFamily: "Archivo", + fontFamily: "Inter", ), softWrap: true, overflow: TextOverflow.ellipsis)), @@ -540,6 +607,7 @@ class _UserListScreenState extends State { color: user['is_active'] == "1" ? Colors.green : Colors.grey, + fontFamily: "Inter", fontWeight: FontWeight.w400), ))), DataCell( @@ -640,463 +708,70 @@ class _UserListScreenState extends State { ); return Expanded( - child: isDesktop - ? table - : SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: SingleChildScrollView( - scrollDirection: Axis.vertical, - child: table, + child: Column( + children: [ + isDesktop + ? table + : SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: SingleChildScrollView( + scrollDirection: Axis.vertical, + child: table, + ), + ), + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + // Previous button with arrow icon + IconButton( + icon: Icon( + Icons.arrow_back_ios_new, + size: 10, + ), + onPressed: currentPage > 0 + ? () { + setState(() { + currentPage--; + }); + } + : null, ), - ), + SizedBox(width: 2), + + // Page number text + Text( + 'Page ${currentPage + 1}', + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w400, + color: Colors.black87, + fontFamily: "Inter", + ), + ), + SizedBox(width: 2), + + // Next button with arrow icon + IconButton( + icon: Icon( + Icons.arrow_forward_ios, + size: 10, + ), + onPressed: (currentPage + 1) * itemsPerPage < + users.length + ? () { + setState(() { + currentPage++; + }); + } + : null, + ), + ], + ), + ], + ), ); }, ) - - // FutureBuilder>( - // future: futureUsers, - // builder: (context, snapshot) { - // if (snapshot.connectionState == ConnectionState.waiting) { - // return Center(child: CircularProgressIndicator()); - // } else if (snapshot.hasError) { - // return Center( - // child: Padding( - // padding: const EdgeInsets.all(16.0), - // child: Column( - // mainAxisSize: MainAxisSize.min, - // children: [ - // Icon( - // Icons.error_outline, - // color: Colors.redAccent, - // size: 60, - // ), - // SizedBox(height: 16), - // Text( - // "Oops!", - // style: TextStyle( - // fontSize: 22, - // fontWeight: FontWeight.bold, - // color: Colors.redAccent, - // ), - // ), - // SizedBox(height: 8), - // Text( - // "No User Available", - // textAlign: TextAlign.center, - // style: TextStyle( - // fontSize: 20, - // fontWeight: FontWeight.bold, - // color: Colors.grey, - // ), - // ), - // SizedBox(height: 20), - // Text( - // " Please Create NewUser", - // textAlign: TextAlign.center, - // style: TextStyle( - // fontSize: 16, - // color: Colors.grey[700], - // ), - // ), - // SizedBox(height: 20), - // // ElevatedButton.icon( - // // onPressed: () { - // // // Optional: retry logic or navigation - // // }, - // // icon: Icon(Icons.refresh), - // // label: Text("Try Again"), - // // style: ElevatedButton.styleFrom( - // // backgroundColor: Colors.blueAccent, - // // ), - // // ), - // ], - // ), - // ), - // ); - // } else if (!snapshot.hasData || snapshot.data!.isEmpty) { - // return Center(child: Text("No users found")); - // } - // - // List users = snapshot.data!; - // Color borderColor = Color(0xFF9E9DBD); - // - // return Expanded( - // child: SingleChildScrollView( - // scrollDirection: Axis.vertical, - // child: SizedBox( - // width: MediaQuery.of(context).size.width * 1.5, - // child: SingleChildScrollView( - // scrollDirection: Axis - // .horizontal, // Inner wrapper for vertical scrolling - // - // child: ConstrainedBox( - // constraints: BoxConstraints( - // minWidth: - // MediaQuery.of(context).size.width * - // 0.8), - // // constraints: BoxConstraints(minWidth: 1300), - // // width: MediaQuery.of(context).size.width , - // - // child: Container( - // // color: Colors.grey, - // // color: Colors.amber, - // child: DataTable( - // columnSpacing: - // 20.0, // Adjust spacing between columns - // dividerThickness: 0.5, - // dataRowMinHeight: - // 60.0, // Minimum row height - // dataRowMaxHeight: 100.0, - // border: TableBorder( - // horizontalInside: BorderSide( - // width: 0.5, - // color: Colors.grey.shade200), - // ), - // columns: const [ - // DataColumn( - // label: Text( - // 'User Details', - // style: TextStyle( - // color: Color(0xFF9E9DBD), - // fontSize: 15, - // fontWeight: FontWeight.bold), - // )), - // DataColumn( - // label: Text( - // 'Role', - // style: TextStyle( - // color: Color(0xFF9E9DBD), - // fontSize: 15, - // fontWeight: FontWeight.bold), - // )), - // DataColumn( - // label: Text( - // 'Level', - // style: TextStyle( - // color: Color(0xFF9E9DBD), - // fontSize: 15, - // fontWeight: FontWeight.bold), - // )), - // DataColumn( - // label: Text( - // 'Status', - // style: TextStyle( - // color: Color(0xFF9E9DBD), - // fontSize: 15, - // fontWeight: FontWeight.bold), - // )), - // DataColumn( - // label: Text( - // 'Actions', - // style: TextStyle( - // color: Color(0xFF9E9DBD), - // fontSize: 15, - // fontWeight: FontWeight.bold), - // )), - // ], - // - // rows: users.map((user) { - // String userId = user['user_id'] - // .toString(); // Get user ID - // bool isSelected = - // selectedUserId == userId; - // - // return DataRow(cells: [ - // // DataCell(Text(user['user_id'].toString())), - // DataCell(Row( - // mainAxisAlignment: - // MainAxisAlignment.start, - // crossAxisAlignment: - // CrossAxisAlignment.start, - // children: [ - // Align( - // alignment: Alignment.center, - // child: GestureDetector( - // onTap: () { - // setState(() { - // selectedUserId = - // userId; // Store clicked user ID - // }); - // }, - // child: Container( - // width: 15, // Adjust size - // height: 15, - // decoration: BoxDecoration( - // // Background color - // shape: BoxShape.rectangle, - // border: Border.all( - // color: isSelected - // ? Colors.blueAccent - // : Color(0xFF9E9DBD), - // // color: Color(0xFF9E9DBD), - // // color: Color.fromRGBO(128, 128, 128, 0.6), - // width: isSelected - // ? 2 - // : 1), // Grey outline - // ), - // ), - // ), - // ), - // SizedBox( - // width: 50, - // ), - // Align( - // alignment: Alignment.center, - // child: Container( - // decoration: BoxDecoration( - // shape: BoxShape.circle, - // border: Border.all( - // color: - // Color(0xFF9E9DBD), - // width: - // 1), // Grey outline - // ), - // child: Padding( - // padding: - // const EdgeInsets.all( - // 2.0), - // child: Container( - // width: 40, // Adjust size - // height: 40, - // decoration: BoxDecoration( - // color: Colors - // .amber, // Inner circle background - // shape: BoxShape.circle, - // ), - // child: Column( - // mainAxisAlignment: - // MainAxisAlignment - // .center, - // crossAxisAlignment: - // CrossAxisAlignment - // .center, - // children: [ - // Text( - // (user['first_name'] != - // null && - // user['first_name']! - // .isNotEmpty) - // ? user['first_name']![ - // 0] - // .toUpperCase() - // : "?", - // style: TextStyle( - // fontSize: 18, - // fontWeight: - // FontWeight - // .bold, - // color: - // Colors.white, - // ), - // ), - // ], - // ), - // ), - // )), - // ), - // SizedBox( - // width: 20, - // ), - // Column( - // mainAxisAlignment: - // MainAxisAlignment.center, - // crossAxisAlignment: - // CrossAxisAlignment.start, - // children: [ - // Row( - // children: [ - // Text( - // "${user['first_name'] ?? ''} ${user['last_name'] ?? ''}", - // style: TextStyle( - // color: - // Colors.blueAccent, - // fontSize: 16), - // ), - // ], - // ), - // SizedBox(height: 5), - // Row( - // children: [ - // Icon(Icons.mail_outline, - // size: 15, - // color: - // Color(0xFF9E9EBE)), - // SizedBox( - // width: 10, - // ), - // Text(user['email'] ?? '') - // ], - // ), - // SizedBox(height: 5), - // Row( - // children: [ - // Icon( - // Icons - // .account_tree_outlined, - // size: 15, - // color: - // Color(0xFF9E9EBE)), - // SizedBox( - // width: 10, - // ), - // Text( - // user['user_type'] ?? '') - // ], - // ), - // ], - // ), - // ], - // )), - // - // DataCell(Text( - // user['role_id'] ?? 'N/A', - // style: TextStyle( - // color: user['is_active'] == "1" - // ? Colors.black - // : Colors.grey, - // fontWeight: FontWeight.bold), - // )), - // DataCell(Text( - // user['level_id'] ?? 'N/A', - // style: TextStyle( - // color: user['is_active'] == "1" - // ? Colors.black - // : Colors.grey, - // fontWeight: FontWeight.bold), - // )), - // DataCell(GestureDetector( - // onTap: () { - // handleToggleUserStatus( - // user['user_id'], - // user['is_active'], - // user); - // }, - // child: Text( - // user['is_active'] == "1" - // ? "Active" - // : "Inactive", - // style: TextStyle( - // color: - // user['is_active'] == "1" - // ? Colors.lightGreen - // : Colors.grey, - // fontWeight: FontWeight.bold), - // ))), - // - // DataCell( - // Row( - // children: [ - // MouseRegion( - // cursor: user['is_active'] == "0" - // ? SystemMouseCursors - // .forbidden - // : SystemMouseCursors.click, - // child: IconButton( - // icon: Icon( - // Icons.remove_red_eye, - // size: 18, - // color: - // user['is_active'] == - // "0" - // ? Colors.grey - // : Color( - // 0xFF475569)), - // onPressed: - // user['is_active'] == "0" - // ? null - // : () { - // context.go( - // "/CreateUserDetails", - // extra: { - // "selectedUser": - // user, - // "isViewMode": - // true - // }, - // ); - // }, - // ), - // ), - // - // MouseRegion( - // cursor: user['is_active'] == "0" - // ? SystemMouseCursors - // .forbidden - // : SystemMouseCursors.click, - // child: GestureDetector( - // onTap: - // user['is_active'] == "0" - // ? null - // : () { - // context.go( - // "/CreateUserDetails", - // extra: { - // "selectedUser": - // user, - // "isViewMode": - // false - // }, - // ); - // }, - // child: Image.asset( - // 'assets/images/IconsImg/edit.png', - // width: 20, - // height: 15), - // ), - // ), - // - // // MouseRegion( - // // cursor: user['is_active'] == "0" - // // ? SystemMouseCursors - // // .forbidden - // // : SystemMouseCursors.click, - // // child: IconButton( - // // icon: Icon(Icons.edit, - // // color: - // // user['is_active'] == - // // "0" - // // ? Colors.grey - // // : Colors.green), - // // onPressed: - // // user['is_active'] == "0" - // // ? null - // // : () { - // // print( - // // "USER: $user"); - // // - // // // final userJson = jsonEncode( - // // // user); // Convert user map to string - // // // final encodedUser = - // // // Uri.encodeComponent( - // // // userJson); - // // - // // context.go( - // // "/CreateUserDetails", - // // extra: { - // // "selectedUser": - // // user, - // // "isViewMode": - // // false - // // }, - // // ); - // // }, - // // ), - // // ), - // ], - // ), - // ), - // ]); - // }).toList(), - // ), - // ), - // ), - // ), - // ), - // ), - // ); - // }, - // ), - // ]), )), ); diff --git a/lib/data/models/plan.dart b/lib/data/models/plan.dart index f182e12..8ac57f5 100644 --- a/lib/data/models/plan.dart +++ b/lib/data/models/plan.dart @@ -1,5 +1,6 @@ class Plan { final String planId; + final String? employeeCode; final String tripTitle; final String tripType; final String status; @@ -17,6 +18,7 @@ class Plan { Plan({ required this.planId, + this.employeeCode, required this.tripTitle, required this.tripType, required this.status, @@ -33,8 +35,10 @@ class Plan { }); factory Plan.fromJson(Map json) { + print('Parsing Plan from JSON: $json'); return Plan( planId: json['plan_id'], + employeeCode: json['employee_code'], tripTitle: json['trip_title'], tripType: json['trip_type_value'], status: json['status'] == "0" ? "Inactive" : "Active", diff --git a/lib/routes/custom_appBar.dart b/lib/routes/custom_appBar.dart index 9b775a0..0098d86 100644 --- a/lib/routes/custom_appBar.dart +++ b/lib/routes/custom_appBar.dart @@ -38,6 +38,8 @@ class _CustomAppBarState extends State { Color? bodyColor; Color _myTravelRequestColor = Color(0xFF475569); // Default color + EdgeInsets _myTravelRequestPadding = + EdgeInsets.symmetric(horizontal: 8, vertical: 4); Color _myApprovalsColor = Color(0xFF475569); // Default color void initState() { @@ -183,45 +185,88 @@ class _CustomAppBarState extends State { ), ), ), - const SizedBox(width: 8), + SizedBox( + width: MediaQuery.of(context).size.width * 0.25, + ), Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const SizedBox(width: 15), + if (userData?["role"] == "Org Admin" || + userData?["role"] == "Travel Admin") + MouseRegion( + cursor: SystemMouseCursors.click, + onEnter: (_) { + setState(() { + // _myTravelRequestPadding = + // EdgeInsets.symmetric(horizontal: 12, vertical: 78); + // _myTravelRequestColor = + // Color(0xFF114D8B); // Change color on hover + }); + }, + onExit: (_) { + setState(() { + // _myTravelRequestPadding = EdgeInsets.symmetric( + // horizontal: 8, vertical: 4); // Normal padding + // _myTravelRequestColor = + // Color(0xFF475569); // Revert color when hover ends + }); + }, + child: InkWell( + onTap: () { + context.go('/listAllPlan'); + }, + child: Text( + "All Trips", + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w600, + // color: Color(0xFF475569), + color: _myTravelRequestColor, + fontFamily: "Inter", + ), + )), + ), + const SizedBox(width: 20), MouseRegion( cursor: SystemMouseCursors.click, onEnter: (_) { setState(() { - _myTravelRequestColor = - Colors.blue; // Change color on hover + // _myTravelRequestPadding = + // EdgeInsets.symmetric(horizontal: 12, vertical: 78); + // _myTravelRequestColor = + // Color(0xFF114D8B); // Change color on hover }); }, onExit: (_) { setState(() { - _myTravelRequestColor = - Color(0xFF475569); // Revert color when hover ends + // _myTravelRequestPadding = EdgeInsets.symmetric( + // horizontal: 8, vertical: 4); // Normal padding + // _myTravelRequestColor = + // Color(0xFF475569); // Revert color when hover ends }); }, - child: GestureDetector( + child: InkWell( onTap: () { context.go('/listPlan'); }, child: Text( "My Travel Request", style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.w600, - // color: Color(0xFF475569), - color: _myTravelRequestColor, - fontFamily: "Archivo"), + fontSize: 12, + fontWeight: FontWeight.w600, + // color: Color(0xFF475569), + color: _myTravelRequestColor, + fontFamily: "Inter", + ), )), ), - const SizedBox(width: 25), + const SizedBox(width: 20), MouseRegion( cursor: SystemMouseCursors.click, onEnter: (_) { setState(() { - _myApprovalsColor = Colors.blue; // Change color on hover + _myApprovalsColor = + Color(0xFF114D8B); // Change color on hover }); }, onExit: (_) { @@ -230,18 +275,19 @@ class _CustomAppBarState extends State { Color(0xFF475569); // Revert color when hover ends }); }, - child: GestureDetector( + child: InkWell( onTap: () { context.go('/ApprovalList'); }, child: Text( "My Approvals", style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.w600, - color: _myApprovalsColor, - // color: Color(0xFF475569), - fontFamily: "Archivo"), + fontSize: 12, + fontWeight: FontWeight.w600, + color: _myApprovalsColor, + // color: Color(0xFF475569), + fontFamily: "Inter", + ), )), ), ], diff --git a/lib/routes/custom_router.dart b/lib/routes/custom_router.dart index c1d9370..87e328c 100644 --- a/lib/routes/custom_router.dart +++ b/lib/routes/custom_router.dart @@ -14,6 +14,7 @@ import 'package:frontend/Screens/userManagement/create_user/create_user.dart'; import 'package:frontend/Screens/userManagement/user_List.dart'; import 'package:go_router/go_router.dart'; +import '../Screens/allTrips/list_all_plans.dart'; import '../Screens/approvals/approval_list.dart'; import '../Screens/group/group.dart'; import '../Screens/group/groupList.dart'; @@ -28,6 +29,10 @@ final GoRouter router = GoRouter( path: '/home', builder: (context, state) => HomePage(), ), + GoRoute( + path: '/listAllPlan', + builder: (context, state) => ListAllPlans(), + ), GoRoute( path: '/listPlan', builder: (context, state) => ListPlans(), diff --git a/lib/services/apiService.dart b/lib/services/apiService.dart index 23b6857..a63f6ee 100644 --- a/lib/services/apiService.dart +++ b/lib/services/apiService.dart @@ -1,8 +1,11 @@ import 'dart:convert'; import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; import 'package:frontend/utils/auth_utils.dart'; import 'package:go_router/go_router.dart'; import 'package:http/http.dart' as http; +import 'package:universal_html/html.dart' as html; +import 'package:universal_html/js.dart'; import '../../config/apiUrl.dart'; class ApiService { @@ -486,4 +489,102 @@ class ApiService { throw Exception('Failed to load country list'); } } + + Future> fetchTrainCountryList() async { + final String apiUrldata = '$apiUrl/api/getTrainCodeMaster'; + + final token = await getToken(); + + if (token == null) { + throw Exception('Token not found. Please log in.'); + } + + final response = await http.get( + Uri.parse(apiUrldata), + headers: { + 'Authorization': 'Bearer $token', + 'Content-Type': 'application/json', + }, + ); + + if (response.statusCode == 200) { + try { + final data = json.decode(response.body); + print("Country - $data"); + + if (!data.containsKey('data') || data['data'] is! List) { + throw Exception( + "Invalid response format: 'data' field is missing or not a List"); + } + + return data['data']; + } catch (e) { + throw Exception('Error parsing response: $e'); + } + } else { + throw Exception('Failed to load country list'); + } + } + + Future getPdfDownload(planId) async { + final String apiUrldata = '$apiUrl/api/plans/download?plan_id=$planId'; + + // final String apiUrldata = '$apiUrl/auth/googlelogin'; + + final token = await getToken(); + + if (token == null) { + throw Exception('Token not found. Please log in.'); + } + + final response = await http.get( + Uri.parse(apiUrldata), + headers: { + 'Authorization': 'Bearer $token', + 'Content-Type': 'application/json', + }, + ); + + if (response.statusCode == 200) { + try { + print("PDf Dowloaded"); + + // Create a blob from the response body + final blob = html.Blob([response.bodyBytes]); + +// Generate a download URL for the blob + final url = html.Url.createObjectUrlFromBlob(blob); + + // Create a link element to trigger the download + final anchor = html.AnchorElement(href: url) + ..setAttribute('download', 'trip_plan_$planId.pdf') + ..click(); + + // Revoke the download URL to free up resources + html.Url.revokeObjectUrl(url); + } catch (e) { + throw Exception('Error parsing response: $e'); + } + } else if (response.statusCode == 404) { + // showDialog( + // context: context, + // builder: (BuildContext context) { + // return AlertDialog( + // title: Text('File not found.'), + // // content: Text('File not found.'), + // actions: [ + // TextButton( + // child: Text('OK'), + // onPressed: () { + // Navigator.of(context).pop(); // Close the dialog + // }, + // ), + // ], + // ); + // }, + // ); + } else { + throw Exception('Failed to load plans'); + } + } } diff --git a/lib/utils/auth_utils.dart b/lib/utils/auth_utils.dart index 23d7a82..f4cf5d6 100644 --- a/lib/utils/auth_utils.dart +++ b/lib/utils/auth_utils.dart @@ -47,6 +47,36 @@ Future getOrgId() async { return null; } +Future getRoleUser() async { + final prefs = await SharedPreferences.getInstance(); + final String? userDataString = prefs.getString('user_data'); + + if (userDataString != null) { + try { + final Map userData = jsonDecode(userDataString); + return userData["role"]?.toString(); + } catch (e) { + return null; + } + } + return null; +} + +Future getForexCardNumber() async { + final prefs = await SharedPreferences.getInstance(); + final String? userDataString = prefs.getString('user_data'); + + if (userDataString != null) { + try { + final Map userData = jsonDecode(userDataString); + return userData["forex_pre_paid_card_number"]?.toString(); + } catch (e) { + return null; + } + } + return null; +} + Future getTripPlanAction() async { final prefs = await SharedPreferences.getInstance(); final String? userDataString = prefs.getString('user_data'); diff --git a/pubspec.lock b/pubspec.lock index 8f7bd5f..9d45aca 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -41,6 +41,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.0" + charcode: + dependency: transitive + description: + name: charcode + sha256: fb0f1107cac15a5ea6ef0a6ef71a807b9e4267c713bb93e00e92d737cc8dbd8a + url: "https://pub.dev" + source: hosted + version: "1.4.0" clock: dependency: transitive description: @@ -73,6 +81,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.6" + csslib: + dependency: transitive + description: + name: csslib + sha256: "09bad715f418841f976c77db72d5398dc1253c21fb9c0c7f0b0b985860b2d58e" + url: "https://pub.dev" + source: hosted + version: "1.0.2" cupertino_icons: dependency: "direct main" description: @@ -200,6 +216,14 @@ packages: url: "https://pub.dev" source: hosted version: "14.8.1" + html: + dependency: transitive + description: + name: html + sha256: "6d1264f2dffa1b1101c25a91dff0dc2daee4c18e87cd8538729773c073dbf602" + url: "https://pub.dev" + source: hosted + version: "0.15.6" http: dependency: "direct main" description: @@ -565,6 +589,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.0" + universal_html: + dependency: "direct main" + description: + name: universal_html + sha256: "56536254004e24d9d8cfdb7dbbf09b74cf8df96729f38a2f5c238163e3d58971" + url: "https://pub.dev" + source: hosted + version: "2.2.4" + universal_io: + dependency: transitive + description: + name: universal_io + sha256: "1722b2dcc462b4b2f3ee7d188dad008b6eb4c40bbd03a3de451d82c78bba9aad" + url: "https://pub.dev" + source: hosted + version: "2.2.2" vector_math: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index c9c14fb..b0d328f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -46,6 +46,7 @@ dependencies: http_parser: ^4.1.2 image_picker: ^1.1.2 web: ^1.1.0 + universal_html: ^2.2.4 dev_dependencies: @@ -76,6 +77,7 @@ flutter: - assets/images/login/VectorG.png - assets/images/IconsImg/delete.png - assets/images/IconsImg/edit.png + - assets/images/IconsImg/planPdf_icon.png # To add assets to your application, add an assets section, like this: