From 75592f98796ace0b289dcc11fbb99dd2930c9a83 Mon Sep 17 00:00:00 2001 From: venbaittech Date: Tue, 13 May 2025 19:42:23 +0530 Subject: [PATCH] user management -TravelDetails --- lib/Screens/allTrips/list_all_plans.dart | 197 +- lib/Screens/allTrips/travel_agent_list.dart | 190 +- lib/Screens/approvals/approval_list.dart | 191 +- .../authentication/login/login_widget.dart | 22 +- lib/Screens/dialog/user_selection_dialog.dart | 4 +- lib/Screens/itnerary/forex.dart | 63 +- lib/Screens/itnerary_list/forex_list.dart | 26 +- lib/Screens/plans/list_plans.dart | 178 +- .../create_user/create_user.dart | 75 +- .../create_user/office_details.dart | 92 +- .../create_user/personal_details.dart | 483 +++- .../create_user/traveller_details.dart | 2302 ++++++++++++++++- lib/Screens/userManagement/user_List.dart | 312 ++- lib/routes/custom_appBar.dart | 63 +- lib/services/apiService.dart | 107 + lib/utils/travelAgent_remarks.dart | 267 ++ lib/widgets/custom_user_travel.dart | 61 + lib/widgets/popup_userList_action.dart | 77 + pubspec.lock | 8 + pubspec.yaml | 1 + 20 files changed, 4095 insertions(+), 624 deletions(-) create mode 100644 lib/utils/travelAgent_remarks.dart create mode 100644 lib/widgets/custom_user_travel.dart create mode 100644 lib/widgets/popup_userList_action.dart diff --git a/lib/Screens/allTrips/list_all_plans.dart b/lib/Screens/allTrips/list_all_plans.dart index b7e62c2..f5fedae 100644 --- a/lib/Screens/allTrips/list_all_plans.dart +++ b/lib/Screens/allTrips/list_all_plans.dart @@ -15,6 +15,7 @@ import '../../routes/custom_drawer.dart'; import '../../services/apiService.dart'; import '../../utils/auth_utils.dart'; import '../../utils/pagination.dart'; +import '../../utils/travelAgent_remarks.dart'; import '../../widgets/custom_popup.dart'; import '../../widgets/popup_listPlan_action.dart'; @@ -530,20 +531,17 @@ class _ListAllPlansState extends State { child: Column( mainAxisSize: MainAxisSize.min, children: [ - const Icon(Icons.error_outline, - color: Colors.redAccent, size: 60), - const SizedBox(height: 1), - Text( - "Oops!", - style: GoogleFonts.poppins( - fontSize: 12, - fontWeight: FontWeight.bold, - color: Colors.redAccent), - // style: TextStyle( - // fontSize: 22, - // fontWeight: FontWeight.bold, - // color: Colors.redAccent) - ), + // const Icon(Icons.error_outline, + // color: Colors.redAccent, size: 60), + // const SizedBox(height: 1), + // Text( + // "Oops!", + // style: GoogleFonts.poppins( + // fontSize: 12, + // fontWeight: FontWeight.bold, + // color: Colors.redAccent), + // + // ), const SizedBox(height: 15), Text( "No Trips", @@ -703,73 +701,95 @@ class _ListAllPlansState extends State { ), ), DataCell( - PopupMenuButton( - color: Colors.white, - padding: EdgeInsets.zero, - offset: Offset(0, 30), - icon: Icon( - Icons.more_vert, - color: Color(0xFF475569), - size: 14, - ), - itemBuilder: (context) => [ - CustomPopupMenuEntry( - child: Container( - padding: EdgeInsets.symmetric( - horizontal: 8, vertical: 8), - child: Row( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: - MainAxisAlignment.center, - children: [ - IconButton( - icon: Icon(Icons.remove_red_eye, - color: Color(0xFF475569), - size: 18), - onPressed: () { - Navigator.pop( - context); // Close popup manually - ApiService.viewPlan( - context, plan.planId, - isViewMode: true); - }, - ), - IconButton( - icon: Image.asset( - 'assets/images/IconsImg/edit.png', - width: 20, - height: 15), - onPressed: () { - Navigator.pop(context); - ApiService.viewPlan( - context, plan.planId, - isViewMode: false); - }, - ), - IconButton( - icon: Image.asset( - 'assets/images/IconsImg/delete.png', - width: 20, - height: 15), - onPressed: () { - Navigator.pop(context); - deletePlan(plan.planId); - }, - ), - IconButton( - icon: Icon(Icons.download, - color: Color(0xFF114D8B), - size: 18), - onPressed: () { - Navigator.pop(context); - apiService.getPdfDownload( - plan.planId); - }, - ), - ], - ), + Row( + children: [ + PopupMenuButton( + color: Colors.white, + padding: EdgeInsets.zero, + offset: Offset(0, 30), + icon: Icon( + Icons.more_vert, + color: Color(0xFF475569), + size: 14, ), + itemBuilder: (context) => [ + CustomPopupMenuEntry( + child: Container( + padding: EdgeInsets.symmetric( + horizontal: 8, vertical: 8), + child: Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: + MainAxisAlignment.center, + children: [ + IconButton( + icon: Icon( + Icons.remove_red_eye, + color: Color(0xFF475569), + size: 18), + onPressed: () { + Navigator.pop( + context); // Close popup manually + ApiService.viewPlan( + context, plan.planId, + isViewMode: true); + }, + ), + IconButton( + icon: Image.asset( + 'assets/images/IconsImg/edit.png', + width: 20, + height: 15), + onPressed: () { + Navigator.pop(context); + ApiService.viewPlan( + context, plan.planId, + isViewMode: false); + }, + ), + IconButton( + icon: Image.asset( + 'assets/images/IconsImg/delete.png', + width: 20, + height: 15), + onPressed: () { + Navigator.pop(context); + deletePlan(plan.planId); + }, + ), + IconButton( + icon: Icon(Icons.download, + color: Color(0xFF114D8B), + size: 18), + onPressed: () { + Navigator.pop(context); + apiService.getPdfDownload( + plan.planId); + }, + ), + ], + ), + ), + ), + ], ), + IconButton( + icon: const Icon( + Icons.comment, + color: Color(0xFF475569), + size: 11, + ), + onPressed: () { + showDialog( + context: context, + builder: (context) => CommentModal( + // planId: plan.planId, + planId: plan.planId.toString(), + layoutColorForUser: + layoutColor!, + role: "Admin"), + ); + }), ], ), ), @@ -839,6 +859,25 @@ class _ListAllPlansState extends State { deletePlan: deletePlan, apiService: apiService, ), + IconButton( + icon: const Icon( + Icons.comment, + color: Color(0xFF475569), + size: 11, + ), + onPressed: () { + showDialog( + context: context, + builder: (context) => + CommentModal( + // planId: plan.planId, + planId: plan.planId + .toString(), + layoutColorForUser: + layoutColor!, + role: "Admin"), + ); + }), ], ), ], diff --git a/lib/Screens/allTrips/travel_agent_list.dart b/lib/Screens/allTrips/travel_agent_list.dart index f84a910..bc1a7f4 100644 --- a/lib/Screens/allTrips/travel_agent_list.dart +++ b/lib/Screens/allTrips/travel_agent_list.dart @@ -1,6 +1,7 @@ import 'dart:convert'; import 'dart:core'; import 'package:frontend/data/models/plan.dart'; +import 'package:frontend/utils/travelAgent_remarks.dart'; import 'package:go_router/go_router.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:http/http.dart' as http; @@ -456,17 +457,24 @@ class _TravelAgentListPlansState extends State { padding: const EdgeInsets.all(16.0), child: Column( mainAxisSize: MainAxisSize.min, - children: const [ + children: [ SizedBox(height: 20), - Icon(Icons.error_outline, - color: Colors.redAccent, size: 60), - SizedBox(height: 1), - Text("Oops!", - style: TextStyle( - fontSize: 22, - fontWeight: FontWeight.bold, - color: Colors.redAccent)), + // Icon(Icons.error_outline, + // color: Colors.redAccent, size: 60), + // SizedBox(height: 1), + // Text("Oops!", + // style: TextStyle( + // fontSize: 22, + // fontWeight: FontWeight.bold, + // color: Colors.redAccent)), SizedBox(height: 20), + isDesktop + ? SizedBox( + width: + MediaQuery.of(context).size.width * 5.5, + ) + : SizedBox.shrink(), + Text("No Trips Pending For Your Approvals", textAlign: TextAlign.center, style: TextStyle( @@ -605,6 +613,9 @@ class _TravelAgentListPlansState extends State { ))), DataCell( Container( + padding: const EdgeInsets.all(3), + // width: + // 150, // Set your desired fixed size (equal width and height) width: double .infinity, // Set your desired fixed size (equal width and height) height: 25, @@ -628,70 +639,92 @@ class _TravelAgentListPlansState extends State { ), DataCell( - PopupMenuButton( - color: Colors.white, - padding: EdgeInsets.zero, - offset: Offset(0, 30), - icon: Icon(Icons.more_vert, - color: Color(0xFF475569)), - itemBuilder: (context) => [ - CustomPopupMenuEntry( - child: Container( - padding: EdgeInsets.symmetric( - horizontal: 8, vertical: 8), - child: Row( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: - MainAxisAlignment.center, - children: [ - IconButton( - icon: Icon(Icons.remove_red_eye, - color: Color(0xFF475569), - size: 14), - onPressed: () { - Navigator.pop( - context); // Close popup manually - ApiService.viewPlan( - context, plan.planId, - isViewMode: true); - }, + Row( + children: [ + PopupMenuButton( + color: Colors.white, + padding: EdgeInsets.zero, + offset: Offset(0, 30), + icon: Icon(Icons.more_vert, + color: Color(0xFF475569)), + itemBuilder: (context) => [ + CustomPopupMenuEntry( + child: Container( + padding: EdgeInsets.symmetric( + horizontal: 8, vertical: 8), + child: Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: + MainAxisAlignment.center, + children: [ + IconButton( + icon: Icon( + Icons.remove_red_eye, + color: Color(0xFF475569), + size: 16), + onPressed: () { + Navigator.pop( + context); // Close popup manually + ApiService.viewPlan( + context, plan.planId, + isViewMode: true); + }, + ), + IconButton( + icon: Image.asset( + 'assets/images/IconsImg/edit.png', + width: 20, + height: 15), + onPressed: () { + Navigator.pop(context); + ApiService.viewPlan( + context, plan.planId, + isViewMode: false); + }, + ), + IconButton( + icon: Image.asset( + 'assets/images/IconsImg/delete.png', + width: 20, + height: 15), + onPressed: () { + Navigator.pop(context); + deletePlan(plan.planId); + }, + ), + IconButton( + icon: Icon(Icons.download, + color: Color(0xFF114D8B), + size: 18), + onPressed: () { + Navigator.pop(context); + apiService.getPdfDownload( + plan.planId); + }, + ), + ], ), - IconButton( - icon: Image.asset( - 'assets/images/IconsImg/edit.png', - width: 20, - height: 15), - onPressed: () { - Navigator.pop(context); - ApiService.viewPlan( - context, plan.planId, - isViewMode: false); - }, - ), - IconButton( - icon: Image.asset( - 'assets/images/IconsImg/delete.png', - width: 20, - height: 15), - onPressed: () { - Navigator.pop(context); - deletePlan(plan.planId); - }, - ), - IconButton( - icon: Icon(Icons.download, - color: Color(0xFF114D8B), - size: 18), - onPressed: () { - Navigator.pop(context); - apiService.getPdfDownload( - plan.planId); - }, - ), - ], + ), ), - ), + ], ), + IconButton( + icon: const Icon( + Icons.comment, + color: Color(0xFF475569), + size: 11, + ), + onPressed: () { + showDialog( + context: context, + builder: (context) => CommentModal( + // planId: plan.planId, + planId: plan.planId.toString(), + layoutColorForUser: + layoutColor!, + role: "Travel Agent"), + ); + }), ], ), ), @@ -787,6 +820,23 @@ class _TravelAgentListPlansState extends State { deletePlan: deletePlan, apiService: apiService, ), + IconButton( + icon: const Icon( + Icons.comment, + color: Color(0xFF475569), + size: 11, + ), + onPressed: () { + showDialog( + context: context, + builder: (context) => CommentModal( + // planId: plan.planId, + planId: plan.planId.toString(), + layoutColorForUser: + layoutColor!, + role: "Travel Agent"), + ); + }), ], ), diff --git a/lib/Screens/approvals/approval_list.dart b/lib/Screens/approvals/approval_list.dart index ec5f626..896e7f5 100644 --- a/lib/Screens/approvals/approval_list.dart +++ b/lib/Screens/approvals/approval_list.dart @@ -15,6 +15,7 @@ import '../../routes/custom_drawer.dart'; import '../../services/apiService.dart'; import '../../utils/auth_utils.dart'; import '../../utils/pagination.dart'; +import '../../utils/travelAgent_remarks.dart'; import '../../widgets/custom_popup.dart'; import '../../widgets/popup_listPlan_action.dart'; @@ -505,14 +506,14 @@ class _ApprovalListState extends State { child: Column( mainAxisSize: MainAxisSize.min, children: const [ - Icon(Icons.error_outline, - color: Colors.redAccent, size: 60), - SizedBox(height: 1), - Text("Oops!", - style: TextStyle( - fontSize: 22, - fontWeight: FontWeight.bold, - color: Colors.redAccent)), + // Icon(Icons.error_outline, + // color: Colors.redAccent, size: 60), + // SizedBox(height: 1), + // Text("Oops!", + // style: TextStyle( + // fontSize: 22, + // fontWeight: FontWeight.bold, + // color: Colors.redAccent)), SizedBox(height: 15), Text("No Trips Pending For Your Approvals", textAlign: TextAlign.center, @@ -670,74 +671,99 @@ class _ApprovalListState extends State { ), DataCell( - PopupMenuButton( - color: Colors.white, - padding: EdgeInsets.zero, - offset: Offset(0, 30), - icon: Icon( - Icons.more_vert, - color: Color(0xFF475569), - size: 14, - ), - itemBuilder: (context) => [ - CustomPopupMenuEntry( - child: Container( - padding: EdgeInsets.symmetric( - horizontal: 8, vertical: 8), - child: Row( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: - MainAxisAlignment.center, - children: [ - IconButton( - icon: Icon(Icons.remove_red_eye, - color: Color(0xFF475569), - size: 18), - onPressed: () { - Navigator.pop( - context); // Close popup manually - viewPlanforApprover( - plan.planId, - isViewMode: true, - isApprover: true); - }), - IconButton( - icon: Image.asset( - 'assets/images/IconsImg/edit.png', - width: 20, - height: 15), - onPressed: () { - Navigator.pop(context); - ApiService.viewPlanForApprover( - context, plan.planId, - isViewMode: false, - isApprover: true); - }, - ), - IconButton( - icon: Image.asset( - 'assets/images/IconsImg/delete.png', - width: 20, - height: 15), - onPressed: () { - Navigator.pop(context); - deletePlan(plan.planId); - }, - ), - IconButton( - icon: Icon(Icons.download, - color: Color(0xFF114D8B), - size: 18), - onPressed: () { - Navigator.pop(context); - apiService.getPdfDownload( - plan.planId); - }, - ), - ], - ), + Row( + children: [ + PopupMenuButton( + color: Colors.white, + padding: EdgeInsets.zero, + offset: Offset(0, 30), + icon: Icon( + Icons.more_vert, + color: Color(0xFF475569), + size: 14, ), + itemBuilder: (context) => [ + CustomPopupMenuEntry( + child: Container( + padding: EdgeInsets.symmetric( + horizontal: 8, vertical: 8), + child: Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: + MainAxisAlignment.center, + children: [ + IconButton( + icon: Icon( + Icons.remove_red_eye, + color: + Color(0xFF475569), + size: 18), + onPressed: () { + Navigator.pop( + context); // Close popup manually + viewPlanforApprover( + plan.planId, + isViewMode: true, + isApprover: true); + }), + IconButton( + icon: Image.asset( + 'assets/images/IconsImg/edit.png', + width: 20, + height: 15), + onPressed: () { + Navigator.pop(context); + ApiService + .viewPlanForApprover( + context, + plan.planId, + isViewMode: false, + isApprover: true); + }, + ), + IconButton( + icon: Image.asset( + 'assets/images/IconsImg/delete.png', + width: 20, + height: 15), + onPressed: () { + Navigator.pop(context); + deletePlan(plan.planId); + }, + ), + IconButton( + icon: Icon(Icons.download, + color: Color(0xFF114D8B), + size: 18), + onPressed: () { + Navigator.pop(context); + apiService.getPdfDownload( + plan.planId); + }, + ), + ], + ), + ), + ), + ], ), + IconButton( + icon: const Icon( + Icons.comment, + color: Color(0xFF475569), + size: 11, + ), + onPressed: () { + showDialog( + context: context, + builder: (context) => CommentModal( + // planId: plan.planId, + planId: plan.planId.toString(), + layoutColorForUser: + layoutColor!, + role: "Approver"), + ); + }), ], ), ), @@ -911,6 +937,23 @@ class _ApprovalListState extends State { ), ], ), + IconButton( + icon: const Icon( + Icons.comment, + color: Color(0xFF475569), + size: 11, + ), + onPressed: () { + showDialog( + context: context, + builder: (context) => CommentModal( + // planId: plan.planId, + planId: plan.planId.toString(), + layoutColorForUser: + layoutColor!, + role: "Approver"), + ); + }), ], ), diff --git a/lib/Screens/authentication/login/login_widget.dart b/lib/Screens/authentication/login/login_widget.dart index 4bc9c19..ec9f1d7 100644 --- a/lib/Screens/authentication/login/login_widget.dart +++ b/lib/Screens/authentication/login/login_widget.dart @@ -1,5 +1,6 @@ import 'dart:convert'; import 'package:flutter/material.dart'; +import 'package:fluttertoast/fluttertoast.dart'; import 'package:frontend/config/apiUrl.dart'; import 'package:http/http.dart' as http; import 'package:go_router/go_router.dart'; @@ -115,11 +116,28 @@ class _LoginWidgetState extends State { context.go('/listPlan'); } } else { + final body = jsonDecode(response.body); + final messages = body['messages']; + + String errorMessage = 'Unknown error'; + + if (messages is Map && messages.isNotEmpty) { + errorMessage = messages.values.first.toString(); + } ScaffoldMessenger.of(context).showSnackBar( SnackBar( - content: Text( - "Login Failed: ${jsonDecode(response.body)['message']}")), + content: Text("Login Failed: $errorMessage"), + backgroundColor: Colors.red, + ), ); + // Fluttertoast.showToast( + // msg: "Login Failed: $errorMessage", + // toastLength: Toast.LENGTH_LONG, + // gravity: ToastGravity.TOP_RIGHT, + // backgroundColor: Colors.red, + // textColor: Colors.white, + // fontSize: 16.0, + // ); } } catch (e) { ScaffoldMessenger.of(context).showSnackBar( diff --git a/lib/Screens/dialog/user_selection_dialog.dart b/lib/Screens/dialog/user_selection_dialog.dart index a43e277..515e5bc 100644 --- a/lib/Screens/dialog/user_selection_dialog.dart +++ b/lib/Screens/dialog/user_selection_dialog.dart @@ -369,8 +369,8 @@ class _UserSelectionDialogState extends State { return ListTile( title: Text( "${user.firstName ?? "Unknown"} ${user.lastName ?? ""}"), - subtitle: Text( - "ID: ${userType == "user" ? user.userId : user.travellerId}"), + // subtitle: Text( + // "ID: ${userType == "user" ? user.userId : user.travellerId}"), onTap: () { String selectedUser = "${user.firstName ?? "Unknown"} ${user.lastName ?? ""}"; diff --git a/lib/Screens/itnerary/forex.dart b/lib/Screens/itnerary/forex.dart index 435c8d1..e784833 100644 --- a/lib/Screens/itnerary/forex.dart +++ b/lib/Screens/itnerary/forex.dart @@ -423,6 +423,7 @@ class _ForexScreenState extends State { // Handle field changes void _onFieldChanged() { if (_isForexDataDurationComplete()) { + print("Calculate 1"); CalculateDuration(); } @@ -432,16 +433,24 @@ class _ForexScreenState extends State { } void CalculateDuration() { + print("Calculate 2"); final data = getForexData; final startDateString = data["start_date"]; final endDateString = data["end_date"]; if (startDateString == null || endDateString == null) return; + print( + "Calculate 3 - StarrtDAte: $startDateString --EndDate: $endDateString"); try { // Parse the dates from string - final startDate = DateFormat('d/M/yyyy').parse(startDateString); - final endDate = DateFormat('d/M/yyyy').parse(endDateString); + // final startDate = DateFormat('dd/mm/yyyy').parse(startDateString); + // final endDate = DateFormat('dd/mm/yyyy').parse(endDateString); + + final startDate = DateFormat('dd-MM-yyyy').parse(startDateString); + final endDate = DateFormat('dd-MM-yyyy').parse(endDateString); + + print("Calculate 4"); // Calculate difference final durationInDays = @@ -450,8 +459,10 @@ class _ForexScreenState extends State { // You can now use durationInDays however you want: print("Duration: $durationInDays days"); - // Optionally, you might want to update some data: - selectedDuration = durationInDays as String?; + // Convert int to String + setState(() { + selectedDuration = durationInDays.toString(); + }); } catch (e) { print('Error parsing dates: $e'); } @@ -501,8 +512,23 @@ class _ForexScreenState extends State { (quotedAmount * 15) ~/ 100; // Calculate 15% (integer division) remainingAmount = quotedAmount - fifteenPercent; // Subtract from total - textControllers["_cash"]?.text = fifteenPercent.toString(); - textControllers["_card"]?.text = remainingAmount.toString(); + // Only set text if the field is empty (user hasn't typed) + if (textControllers["_cash"] != null && + textControllers["_cash"]!.text.trim().isEmpty) { + textControllers["_cash"]!.text = fifteenPercent.toString(); + } else { + print("_cash already has user input, not overwriting"); + } + + if (textControllers["_card"] != null && + textControllers["_card"]!.text.trim().isEmpty) { + textControllers["_card"]!.text = remainingAmount.toString(); + } else { + print("_card already has user input, not overwriting"); + } + + // textControllers["_cash"]?.text = fifteenPercent.toString(); + // textControllers["_card"]?.text = remainingAmount.toString(); print("15% Amount: $fifteenPercent"); print("Remaining Amount: $remainingAmount"); } else { @@ -518,11 +544,19 @@ class _ForexScreenState extends State { int? qouteAmount = int.tryParse(selectedQuotedAmount ?? "0"); int? calculateAmnt = cashAmount! + enteredAmount!; + int checkValidAmount = cashAmount + enteredAmount; + + print( + "checkValidAmount - $checkValidAmount - $enteredAmount - $cashAmount"); + print( "CAsh - $cashAmount- CaRd- $enteredAmount - $calculateAmnt - selectedQuotedAmount - $qouteAmount"); if (enteredAmount == null || calculateAmnt > qouteAmount!) { errorMessages["deposit_on_card"] = "Amount cannot exceed $qouteAmount"; + } else if (checkValidAmount < qouteAmount) { + errorMessages["deposit_on_card"] = + "Enter Valid Amount"; // Clear error if valid } else { errorMessages["deposit_on_card"] = ""; // Clear error if valid } @@ -532,18 +566,29 @@ class _ForexScreenState extends State { } void _validateCashAmount(String value) { + errorMessages["deposit_on_card"] = " "; print("_validateCashAmount - $value - $fifteenPercent"); int? enteredAmount = int.tryParse(value); int? quotedAmount = int.tryParse(selectedQuotedAmount!); int? difference = quotedAmount! - enteredAmount!; + int? cashAmount = int.tryParse(textControllers["_cash"]!.text ?? "0"); + int? cardAmount = int.tryParse(textControllers["_card"]!.text ?? "0"); + + int checkValidAmount = cardAmount! + enteredAmount; + + print( + "checkValidAmountCash - $checkValidAmount -cash- $enteredAmount -Card - $cardAmount - quotedAmount- $quotedAmount"); print("Difference - $difference"); + print('CardAmount - $cardAmount'); textControllers["_card"]?.text = difference.toString(); if (enteredAmount == null || enteredAmount > fifteenPercent) { errorMessages["deposit_on_cash"] = "Amount cannot exceed $fifteenPercent"; + } else if (checkValidAmount == quotedAmount) { + errorMessages["deposit_on_card"] = " "; // Clear error if valid } else { errorMessages["deposit_on_cash"] = ""; // Clear error if valid } @@ -682,8 +727,8 @@ class _ForexScreenState extends State { if (flightFirstTripDateNotifier.value != null) { try { initialDate = DateTime.parse(flightFirstTripDateNotifier.value!); - textControllers["_forexStartDate"]?.text = - DateFormat('yyyy-MM-dd').format(initialDate); + // textControllers["_forexStartDate"]?.text = + // DateFormat('yyyy-MM-dd').format(initialDate); textControllers["_forexStartDate"]?.text = DateFormat('dd-MM-yyyy').format(initialDate); } catch (e) { @@ -938,7 +983,7 @@ class _ForexScreenState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - "Durartion", + "Duration", style: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w600, diff --git a/lib/Screens/itnerary_list/forex_list.dart b/lib/Screens/itnerary_list/forex_list.dart index 0b7e899..04ab33d 100644 --- a/lib/Screens/itnerary_list/forex_list.dart +++ b/lib/Screens/itnerary_list/forex_list.dart @@ -4,6 +4,8 @@ import 'package:http/http.dart' as http; import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; +import '../../services/apiService.dart'; + class ForexListWidget extends StatelessWidget { final List> forexList; final Function(bool, Map, String) onOpen; @@ -27,6 +29,7 @@ class ForexListWidget extends StatelessWidget { @override Widget build(BuildContext context) { final isDesktop = MediaQuery.of(context).size.width > 1024; + final ApiService apiService = ApiService(); return Padding( padding: const EdgeInsets.all(16.0), @@ -133,7 +136,11 @@ class ForexListWidget extends StatelessWidget { ); } - Widget _buildData(BuildContext context, bool isDesktop) { + Widget _buildData( + BuildContext context, + bool isDesktop, + ) { + final ApiService apiService = ApiService(); List> filteredList = forexList.where((item) => item["is_active"] == "1").toList(); print("filteredList- $filteredList"); @@ -227,6 +234,23 @@ class ForexListWidget extends StatelessWidget { child: Image.asset('assets/images/IconsImg/delete.png', width: 20, height: 15), ), + item['forex_id'] != null + ? IconButton( + icon: Icon(Icons.download, + color: Color(0xFF114D8B), size: 18), + onPressed: () { + print("PDF- ${item['forex_id']}"); + final forexIdString = item['forex_id']; + final id = int.tryParse(forexIdString ?? ''); + + if (id != null) { + apiService.getForexPdfDownload(id); + } else { + print("Invalid forex_id: $forexIdString"); + } + }, + ) + : SizedBox.shrink(), // or Container() ], ), Divider( diff --git a/lib/Screens/plans/list_plans.dart b/lib/Screens/plans/list_plans.dart index 88ddcf9..a5b4a26 100644 --- a/lib/Screens/plans/list_plans.dart +++ b/lib/Screens/plans/list_plans.dart @@ -15,6 +15,7 @@ import '../../routes/custom_drawer.dart'; import '../../services/apiService.dart'; import '../../utils/auth_utils.dart'; import '../../utils/pagination.dart'; +import '../../utils/travelAgent_remarks.dart'; import '../../widgets/custom_popup.dart'; import '../../widgets/popup_listPlan_action.dart'; @@ -557,14 +558,14 @@ class _ListPlansState extends State { child: Column( mainAxisSize: MainAxisSize.min, children: const [ - Icon(Icons.error_outline, - color: Colors.redAccent, size: 60), - SizedBox(height: 1), - Text("Oops!", - style: TextStyle( - fontSize: 22, - fontWeight: FontWeight.bold, - color: Colors.redAccent)), + // Icon(Icons.error_outline, + // color: Colors.redAccent, size: 60), + // SizedBox(height: 1), + // Text("Oops!", + // style: TextStyle( + // fontSize: 22, + // fontWeight: FontWeight.bold, + // color: Colors.redAccent)), SizedBox(height: 15), Text("No Trips", textAlign: TextAlign.center, @@ -743,70 +744,92 @@ class _ListPlansState extends State { ), ), DataCell( - PopupMenuButton( - color: Colors.white, - padding: EdgeInsets.zero, - offset: Offset(0, 30), - icon: Icon(Icons.more_vert, - size: 14, color: Color(0xFF475569)), - itemBuilder: (context) => [ - CustomPopupMenuEntry( - child: Container( - padding: EdgeInsets.symmetric( - horizontal: 8, vertical: 8), - child: Row( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: - MainAxisAlignment.center, - children: [ - IconButton( - icon: Icon(Icons.remove_red_eye, - color: Color(0xFF475569), - size: 18), - onPressed: () { - Navigator.pop( - context); // Close popup manually - ApiService.viewPlan( - context, plan.planId, - isViewMode: true); - }, + Row( + children: [ + PopupMenuButton( + color: Colors.white, + padding: EdgeInsets.zero, + offset: Offset(0, 30), + icon: Icon(Icons.more_vert, + size: 14, color: Color(0xFF475569)), + itemBuilder: (context) => [ + CustomPopupMenuEntry( + child: Container( + padding: EdgeInsets.symmetric( + horizontal: 8, vertical: 8), + child: Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: + MainAxisAlignment.center, + children: [ + IconButton( + icon: Icon( + Icons.remove_red_eye, + color: Color(0xFF475569), + size: 18), + onPressed: () { + Navigator.pop( + context); // Close popup manually + ApiService.viewPlan( + context, plan.planId, + isViewMode: true); + }, + ), + IconButton( + icon: Image.asset( + 'assets/images/IconsImg/edit.png', + width: 20, + height: 15), + onPressed: () { + Navigator.pop(context); + ApiService.viewPlan( + context, plan.planId, + isViewMode: false); + }, + ), + IconButton( + icon: Image.asset( + 'assets/images/IconsImg/delete.png', + width: 20, + height: 15), + onPressed: () { + Navigator.pop(context); + deletePlan(plan.planId); + }, + ), + IconButton( + icon: Icon(Icons.download, + color: Color(0xFF114D8B), + size: 18), + onPressed: () { + Navigator.pop(context); + apiService.getPdfDownload( + plan.planId); + }, + ), + ], ), - IconButton( - icon: Image.asset( - 'assets/images/IconsImg/edit.png', - width: 20, - height: 15), - onPressed: () { - Navigator.pop(context); - ApiService.viewPlan( - context, plan.planId, - isViewMode: false); - }, - ), - IconButton( - icon: Image.asset( - 'assets/images/IconsImg/delete.png', - width: 20, - height: 15), - onPressed: () { - Navigator.pop(context); - deletePlan(plan.planId); - }, - ), - IconButton( - icon: Icon(Icons.download, - color: Color(0xFF114D8B), - size: 18), - onPressed: () { - Navigator.pop(context); - apiService.getPdfDownload( - plan.planId); - }, - ), - ], + ), ), - ), + ], ), + IconButton( + icon: const Icon( + Icons.comment, + color: Color(0xFF475569), + size: 11, + ), + onPressed: () { + showDialog( + context: context, + builder: (context) => CommentModal( + // planId: plan.planId, + planId: plan.planId.toString(), + layoutColorForUser: + layoutColor!, + role: "User"), + ); + }), ], ), ), @@ -910,6 +933,23 @@ class _ListPlansState extends State { deletePlan: deletePlan, apiService: apiService, ), + IconButton( + icon: const Icon( + Icons.comment, + color: Color(0xFF475569), + size: 11, + ), + onPressed: () { + showDialog( + context: context, + builder: (context) => CommentModal( + // planId: plan.planId, + planId: plan.planId.toString(), + layoutColorForUser: + layoutColor!, + role: "User"), + ); + }), ], ), diff --git a/lib/Screens/userManagement/create_user/create_user.dart b/lib/Screens/userManagement/create_user/create_user.dart index df40e86..c664468 100644 --- a/lib/Screens/userManagement/create_user/create_user.dart +++ b/lib/Screens/userManagement/create_user/create_user.dart @@ -41,6 +41,10 @@ class _CreateUserFormDetialsState extends State { final GlobalKey personalDetailsKey = GlobalKey(); + final GlobalKey travellerDetailsKey = + GlobalKey(); + + Map? travelDetailsData; late TabController _tabController; @@ -124,6 +128,8 @@ class _CreateUserFormDetialsState extends State { Color? layoutColor; Color? bodyColor; + bool setSelectesUserType = false; + Map get userDetials { Map data = { "first_name": controllers["Fname"]?.text, @@ -159,11 +165,18 @@ class _CreateUserFormDetialsState extends State { "created_by": userId, "is_active": "1", "org_id": orgId, + "travel_details": travelDetailsData ?? {}, // "passport_fileData": passportFile, }; return data; } + void handleUserTypeChange(bool isTravelAgent) { + setState(() { + setSelectesUserType = isTravelAgent; + }); + } + Future updateData() async { // Ensure apiselectedUser is not null before printing if (apiselectedUser != null) { @@ -474,8 +487,14 @@ class _CreateUserFormDetialsState extends State { print("USR Detail Submit"); printFormData(); + travelDetailsData = travellerDetailsKey.currentState?.travel_Detials; + + print("TRAVEL DETAILS FROM CHILD: $travelDetailsData"); + Map data = userDetials; + print("USERDETAILS : $data"); + if (!isValidData(data)) { print("USERDETAILS : $userDetials"); print("Validation Failed: Required fields are missing."); @@ -487,7 +506,7 @@ class _CreateUserFormDetialsState extends State { final tabs = { "personal": "Personal Details", "office": "Office Details", - // "travel": "Travel Details", + "travel": "Travel Details", }; final tabKeys = tabs.keys.toList(); // ["personal", "office", "travel"] @@ -511,8 +530,16 @@ class _CreateUserFormDetialsState extends State { print("USR Detail Submit"); printFormData(); + travelDetailsData = travellerDetailsKey.currentState?.travel_Detials; + + print("TRAVEL DETAILS FROM CHILD: $travelDetailsData"); + Map data = userDetials; + print("USERDETAILS : $data"); + + // Map data = userDetials; + if (!isValidData(data)) { print("USERDETAILS : $userDetials"); print("Validation Failed: Required fields are missing."); @@ -760,11 +787,9 @@ class _CreateUserFormDetialsState extends State { height: isDesktop ? MediaQuery.of(context).size.height * 0.98 : MediaQuery.of(context).size.height, - child: SingleChildScrollView( - child: Padding( - padding: EdgeInsets.all(0.0), - child: _buildUserDetails(isDesktop), - ), + child: Padding( + padding: EdgeInsets.all(0.0), + child: _buildUserDetails(isDesktop), ), ), ), @@ -775,13 +800,15 @@ class _CreateUserFormDetialsState extends State { child: isDesktop ? Row( mainAxisAlignment: MainAxisAlignment.end, - children: selectedTab == "office" + children: (selectedTab == "travel" || + setSelectesUserType == true) ? _buildSubmit(isDesktop, layoutColor!) : _buildNext(isDesktop, layoutColor!), ) : Row( mainAxisAlignment: MainAxisAlignment.center, - children: selectedTab == "office" + children: (selectedTab == "travel" || + setSelectesUserType == true) ? _buildSubmit(isDesktop, layoutColor!) : _buildNext(isDesktop, layoutColor!), )), @@ -819,7 +846,8 @@ class _CreateUserFormDetialsState extends State { ), Container( // color: Colors.yellow.shade50, - height: MediaQuery.of(context).size.height * 0.67, + + height: MediaQuery.of(context).size.height * 0.64, child: Row( children: [ Expanded(child: buildTabContents(isDesktop, isViewMode)), @@ -846,6 +874,7 @@ class _CreateUserFormDetialsState extends State { selectedCountry: selectedCountry, selectedRole: selectedRole, apiselectedUser: isapiselectedUser, + onUserTypeChanged: handleUserTypeChange, onGenderChanged: (gender) { setState(() { selectedGender = gender; @@ -900,17 +929,25 @@ class _CreateUserFormDetialsState extends State { }, ); case "travel": - return TravellerDetails(); + return TravellerDetails( + key: travellerDetailsKey, + isDesktop: isDesktop, + isViewMode: isViewMode, + controllers: controllers, + errorMessages: errorMessages, + ); default: return PersonalDetails( - personalDetailsKey: personalDetailsKey, - isDesktop: isDesktop, // pass isDesktop as a named argument - isViewMode: isViewMode, - controllers: controllers, - errorMessages: errorMessages, - selectedGender: selectedGender, - selectedCountry: selectedCountry, - apiselectedUser: isapiselectedUser); + personalDetailsKey: personalDetailsKey, + isDesktop: isDesktop, // pass isDesktop as a named argument + isViewMode: isViewMode, + controllers: controllers, + errorMessages: errorMessages, + selectedGender: selectedGender, + selectedCountry: selectedCountry, + apiselectedUser: isapiselectedUser, + onUserTypeChanged: handleUserTypeChange, + ); } } @@ -918,7 +955,7 @@ class _CreateUserFormDetialsState extends State { final tabs = { "personal": "Personal Details", "office": "Office Details", - // "travel": "Travel Details", + "travel": "Travel Details", }; return Row( diff --git a/lib/Screens/userManagement/create_user/office_details.dart b/lib/Screens/userManagement/create_user/office_details.dart index 02fe444..64007ae 100644 --- a/lib/Screens/userManagement/create_user/office_details.dart +++ b/lib/Screens/userManagement/create_user/office_details.dart @@ -4,6 +4,7 @@ import 'package:dropdown_search/dropdown_search.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:google_fonts/google_fonts.dart'; import 'package:intl/intl.dart'; import '../../../services/apiService.dart'; @@ -294,11 +295,11 @@ class _OfficeDetailsState extends State { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text("Employee Code", - style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.w200, - color: Colors.black)), + Text( + "Employee Code", + style: GoogleFonts.poppins( + fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black), + ), SizedBox(height: 5), CustomTextFieldUserWrapper( isFocused: false, @@ -306,12 +307,13 @@ class _OfficeDetailsState extends State { child: SizedBox( height: 40, child: TextField( - style: TextStyle(fontSize: 12, color: Colors.black), + style: GoogleFonts.poppins(fontSize: 12, color: Colors.black), controller: widget.controllers["employeeCode"], enabled: !widget.isViewMode, decoration: InputDecoration( labelText: "Employee Code", - labelStyle: TextStyle(fontSize: 12, color: Colors.grey), + labelStyle: + GoogleFonts.poppins(fontSize: 12, color: Colors.grey), floatingLabelBehavior: FloatingLabelBehavior.never, border: InputBorder.none, contentPadding: EdgeInsets.symmetric(vertical: 16), @@ -327,11 +329,11 @@ class _OfficeDetailsState extends State { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text("Department", - style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.w200, - color: Colors.black)), + Text( + "Department", + style: GoogleFonts.poppins( + fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black), + ), SizedBox(height: 5), CustomTextFieldUserWrapper( isFocused: false, // Dropdown doesn't use focus @@ -340,7 +342,7 @@ class _OfficeDetailsState extends State { height: 45, // Set appropriate height child: DropdownButtonFormField( value: widget.isViewMode ? null : selectedDepartment, - style: TextStyle(fontSize: 12, color: Colors.black), + style: GoogleFonts.poppins(fontSize: 12, color: Colors.black), decoration: InputDecoration( border: InputBorder.none, contentPadding: @@ -363,7 +365,7 @@ class _OfficeDetailsState extends State { hint: Text("Select"), disabledHint: Text( selectedDepartment ?? "Select Department", - style: TextStyle(fontSize: 12, color: Colors.black), + style: GoogleFonts.poppins(fontSize: 12, color: Colors.black), ), ), ), @@ -376,11 +378,11 @@ class _OfficeDetailsState extends State { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text("Group", - style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.w200, - color: Colors.black)), + Text( + "Group", + style: GoogleFonts.poppins( + fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black), + ), SizedBox(height: 5), // CustomTextFieldUserWrapper( // isFocused: false, @@ -474,7 +476,9 @@ class _OfficeDetailsState extends State { alignment: Alignment.centerLeft, child: Text( selectedItem ?? "Select", - style: TextStyle(fontSize: 12), + style: GoogleFonts.poppins( + fontSize: 12, + ), ), ), @@ -512,11 +516,13 @@ class _OfficeDetailsState extends State { Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text("First Approver", - style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.w200, - color: Colors.black)), + Text( + "First Approver", + style: GoogleFonts.poppins( + fontSize: 12, + fontWeight: FontWeight.w400, + color: Colors.black), + ), SizedBox(height: 5), CustomTextFieldUserWrapper( isFocused: false, @@ -561,7 +567,9 @@ class _OfficeDetailsState extends State { alignment: Alignment.centerLeft, child: Text( selectedItem ?? "Select", - style: TextStyle(fontSize: 12), + style: GoogleFonts.poppins( + fontSize: 12, + ), ), ), // onChanged: (String? newValue) { @@ -620,11 +628,13 @@ class _OfficeDetailsState extends State { Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text("Second Approver", - style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.w200, - color: Colors.black)), + Text( + "Second Approver", + style: GoogleFonts.poppins( + fontSize: 12, + fontWeight: FontWeight.w400, + color: Colors.black), + ), SizedBox(height: 5), CustomTextFieldUserWrapper( isFocused: false, @@ -669,7 +679,9 @@ class _OfficeDetailsState extends State { alignment: Alignment.centerLeft, child: Text( selectedItem ?? "Select", - style: TextStyle(fontSize: 12), + style: GoogleFonts.poppins( + fontSize: 12, + ), ), ), @@ -727,11 +739,13 @@ class _OfficeDetailsState extends State { Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text("Third Approver", - style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.w200, - color: Colors.black)), + Text( + "Third Approver", + style: GoogleFonts.poppins( + fontSize: 12, + fontWeight: FontWeight.w400, + color: Colors.black), + ), SizedBox(height: 5), CustomTextFieldUserWrapper( isFocused: false, @@ -776,7 +790,9 @@ class _OfficeDetailsState extends State { alignment: Alignment.centerLeft, child: Text( selectedItem ?? "Select", - style: TextStyle(fontSize: 12), + style: GoogleFonts.poppins( + fontSize: 12, + ), ), ), onChanged: (String? newValue) { diff --git a/lib/Screens/userManagement/create_user/personal_details.dart b/lib/Screens/userManagement/create_user/personal_details.dart index 04f3d66..5e7ae03 100644 --- a/lib/Screens/userManagement/create_user/personal_details.dart +++ b/lib/Screens/userManagement/create_user/personal_details.dart @@ -1,12 +1,15 @@ +import 'dart:convert'; import 'dart:io' as html; import 'package:dropdown_search/dropdown_search.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:google_fonts/google_fonts.dart'; import 'package:intl/intl.dart'; import '../../../services/apiService.dart'; +import '../../../utils/auth_utils.dart'; import '../../../widgets/custom_user_form.dart'; class PersonalDetails extends StatefulWidget { @@ -20,27 +23,29 @@ class PersonalDetails extends StatefulWidget { final ValueChanged? onGenderChanged; final ValueChanged? onCountryChanged; final ValueChanged? onRoleChanged; + final void Function(bool)? onUserTypeChanged; final String? selectedGender; final String? selectedCountry; final String? selectedRole; // const PersonalDetails(this.isDesktop, this.isViewMode, {super.key},); - const PersonalDetails({ - Key? key, - this.selectedGender, - this.selectedCountry, - this.selectedRole, - required this.personalDetailsKey, - required this.controllers, - required this.errorMessages, - required this.isDesktop, - required this.isViewMode, - required this.apiselectedUser, - this.onGenderChanged, - this.onCountryChanged, - this.onRoleChanged, - }) : super(key: key); + const PersonalDetails( + {Key? key, + this.selectedGender, + this.selectedCountry, + this.selectedRole, + required this.personalDetailsKey, + required this.controllers, + required this.errorMessages, + required this.isDesktop, + required this.isViewMode, + required this.apiselectedUser, + this.onGenderChanged, + this.onCountryChanged, + this.onRoleChanged, + this.onUserTypeChanged}) + : super(key: key); @override PersonalDetailsState createState() => PersonalDetailsState(); @@ -53,11 +58,16 @@ class PersonalDetailsState extends State { late List countryCodes; // List of country codes late List? apiCountryData; late List? apiRoleData; + List? apiAllServices; + Map? selectedOrg; + List> selectedServiceIds = []; String? get selectedGenderValue => selectedGender; String? get selectedCountryValue => selectedCountry; String? get selectedRoleValue => selectedRole; + late bool isTravelAgent = false; + String? selectedTab; String? selectedCountry; @@ -121,6 +131,11 @@ class PersonalDetailsState extends State { selectedRole = widget.selectedRole; fetchRoles(); fetchCountries(); + + WidgetsBinding.instance.addPostFrameCallback((_) { + loadAllServices(); + getOrganizationData(); + }); } void _clearError(String field) { @@ -158,6 +173,47 @@ class PersonalDetailsState extends State { } } + Future getOrganizationData() async { + try { + print("getUpdatedServices"); + + final result = await apiService.fetchOrganization(); + print("UUPdatedServices - $result"); + setState(() { + final servicesRaw = selectedOrg?['services_ids']; + + List services; + + if (servicesRaw is String) { + try { + services = jsonDecode(servicesRaw); + } catch (e) { + print('❌ Failed to decode services_ids: $e'); + services = []; + } + } else if (servicesRaw is List) { + services = servicesRaw; + } else { + services = []; + } + + selectedServiceIds = services.map>((item) { + // force cast or copy to a regular map + final map = Map.from(item); + return { + "service_id": map['service_id'].toString(), + }; + }).toList(); + }); + + // orgId = await getOrgId(); + + print("selectedOrg - $selectedOrg"); + } catch (e) { + print('Error fetching updatedServices list: $e'); + } + } + @override Widget build(BuildContext context) { return Container( @@ -169,30 +225,215 @@ class PersonalDetailsState extends State { ), ), ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox( - height: 10, - ), - _buildFirstRow(widget.isDesktop), - SizedBox( - height: 10, - ), - _buildSecondRow(widget.isDesktop), - SizedBox( - height: 10, - ), - _buildThirdRow(widget.isDesktop), - SizedBox( - height: 10, - ), - _buildForthRow(widget.isDesktop), - ], + child: SingleChildScrollView( + scrollDirection: Axis.vertical, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + height: 10, + ), + _buildFirstRow(widget.isDesktop), + SizedBox( + height: 10, + ), + _buildSecondRow(widget.isDesktop), + SizedBox( + height: 10, + ), + _buildThirdRow(widget.isDesktop), + SizedBox( + height: 10, + ), + _buildForthRow(widget.isDesktop), + SizedBox( + height: 10, + ), + if (selectedRole == "5") _buildServices(widget.isDesktop) + ], + ), ), ); } + Future loadAllServices() async { + try { + final result = await apiService.fetchAllServices(); + setState(() { + apiAllServices = result; + }); + print("Fetched services: $apiAllServices"); + } catch (e) { + print('Error fetching role list: $e'); + } + } + + Widget _buildServices(bool isDesktop) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Services", + + style: GoogleFonts.poppins( + fontSize: 14, fontWeight: FontWeight.w400, color: Colors.black), + // style: TextStyle( + // fontFamily: "Archivo", + // fontSize: 14, + // fontWeight: FontWeight.w600, + // color: Color(0xFF212121)), + ), + SizedBox( + height: 10, + ), + Container( + decoration: BoxDecoration( + border: Border.all(color: Color(0xFFF4F4FB)), + borderRadius: BorderRadius.circular(1), + // color: bodyColor, + // color: Color(0xFFF5F5F5), + color: Colors.white), + padding: EdgeInsets.only(left: 5, right: 5, top: 15, bottom: 5), + child: isDesktop + ? Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + // mainAxisSize: MainAxisSize.min, + children: _buildOptions(), + ) + : Expanded( + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row( + children: _buildOptions(), + ), + ), + ), + ), + ], + ); + } + + List _buildOptions() { + if (apiAllServices == null) return []; + + return apiAllServices!.map((service) { + return Padding( + padding: const EdgeInsets.only(right: 20.0), + child: _buildOption(service), + ); + }).toList(); + } + + Widget _buildOption(Map service) { + String name = service['name']; + String iconUrl = service['icon']; // Can be empty string + + IconData fallbackIcon = _getLocalIconForService(name); + + String serviceId = service['service_id'].toString(); + // bool isSelected = selectedServiceIds.contains(serviceId); + bool isSelected = + selectedServiceIds.any((item) => item["service_id"] == serviceId); + + return GestureDetector( + onTap: () { + setState(() { + String serviceId = service['service_id'].toString(); + + // Check if already selected + int existingIndex = selectedServiceIds + .indexWhere((item) => item["service_id"] == serviceId); + + if (existingIndex != -1) { + selectedServiceIds.removeAt(existingIndex); + } else { + selectedServiceIds.add({"service_id": serviceId}); + } + }); + }, + child: Row(children: [ + iconUrl.isNotEmpty + ? Image.network( + iconUrl, + width: 18, + height: 18, + errorBuilder: (context, error, stackTrace) { + return Icon(fallbackIcon, + size: 18, + color: isSelected == name + ? Color(0xFF114D8B) + : Color(0xFF475569)); + }, + ) + : Icon(fallbackIcon, + size: 18, + color: + isSelected == name ? Color(0xFF114D8B) : Color(0xFF475569)), + + SizedBox(width: 5), + + SizedBox(width: 5), + Text( + name, + style: GoogleFonts.poppins( + fontSize: 12, + color: isSelected == name ? Color(0xFF114D8B) : Color(0xFF475569), + fontWeight: + isSelected == name ? FontWeight.bold : FontWeight.w500), + // style: TextStyle( + // fontSize: 13, + // color: isSelected == name ? Color(0xFF114D8B) : Color(0xFF475569), + // fontFamily: "Archivo", + // fontWeight: + // isSelected == name ? FontWeight.bold : FontWeight.w500), + // fontWeight: selectedListOption == title ? FontWeight.bold : FontWeight.normal,)), + ), + + SizedBox(width: 5), + // if (selectedListOption == title && widget.isViewMode == false) + Container( + height: 15, + width: 15, + decoration: BoxDecoration( + shape: BoxShape.circle, + border: Border.all( + color: isSelected ? Colors.green : Colors.grey, width: 1), + ), + child: Icon( + Icons.check_circle, + size: 10, + color: isSelected ? Colors.green : Colors.grey, + // color: Colors.grey, + )), + ]), + ); + } + + IconData _getLocalIconForService(String name) { + switch (name.toLowerCase()) { + case 'flight': + return Icons.flight_takeoff_outlined; + case 'train': + return Icons.train_outlined; + case 'bus': + return Icons.bus_alert_outlined; + case 'taxi': + return Icons.local_taxi_outlined; + case 'accomodation': + return Icons.local_hotel_outlined; + case 'forex': + return Icons.attach_money_outlined; + case 'insurance': + return Icons.list_alt_outlined; + case 'visa': + return Icons.badge_outlined; + case 'miscellaneous': + return Icons.card_giftcard_outlined; + default: + return Icons.circle_notifications; + } + } + Widget _buildFirstRow(bool isDesktop) { return Container( color: Colors.white, @@ -227,11 +468,6 @@ class PersonalDetailsState extends State { } Widget _buildSecondRow(bool isDesktop) { - List fields = [ - buildEmailField(), - buildMobileField(), - buildAlternateMobileField(), - ]; return Container( color: Colors.white, child: widget.isDesktop @@ -318,11 +554,11 @@ class PersonalDetailsState extends State { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text("First Name", - style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.w200, - color: Colors.black)), + Text( + "First Name", + style: GoogleFonts.poppins( + fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black), + ), SizedBox(height: 5), CustomTextFieldUserWrapper( isFocused: false, @@ -330,7 +566,7 @@ class PersonalDetailsState extends State { child: SizedBox( height: 40, child: TextField( - style: TextStyle(fontSize: 12, color: Colors.black), + style: GoogleFonts.poppins(fontSize: 12, color: Colors.black), controller: widget.controllers["Fname"], enabled: !widget.isViewMode, onChanged: (value) { @@ -338,7 +574,10 @@ class PersonalDetailsState extends State { }, decoration: InputDecoration( labelText: "FirstName", - labelStyle: TextStyle(fontSize: 12, color: Colors.grey), + labelStyle: GoogleFonts.poppins( + fontSize: 12, + fontWeight: FontWeight.w400, + color: Colors.grey), floatingLabelBehavior: FloatingLabelBehavior.never, border: InputBorder.none, contentPadding: EdgeInsets.symmetric(vertical: 16), @@ -404,11 +643,11 @@ class PersonalDetailsState extends State { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text("Gender", - style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.w200, - color: Colors.black)), + Text( + "Gender", + style: GoogleFonts.poppins( + fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black), + ), SizedBox(height: 5), CustomTextFieldUserWrapper( width: widget.isDesktop @@ -435,18 +674,18 @@ class PersonalDetailsState extends State { border: InputBorder.none, enabled: !widget.isViewMode, // Disables input when in view mode ), - style: TextStyle(fontSize: 12, color: Colors.black), + style: GoogleFonts.poppins(fontSize: 12, color: Colors.black), items: [ DropdownMenuItem(value: "Male", child: Text("Male")), DropdownMenuItem(value: "Female", child: Text("Female")), ], hint: Text( selectedGender ?? "Select Gender", - style: TextStyle(fontSize: 12, color: Colors.black), + style: GoogleFonts.poppins(fontSize: 12, color: Colors.black), ), disabledHint: Text( selectedGender ?? "Select Gender", - style: TextStyle(fontSize: 12, color: Colors.black), + style: GoogleFonts.poppins(fontSize: 12, color: Colors.black), ), ), @@ -522,11 +761,11 @@ class PersonalDetailsState extends State { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text("Date of Birth", - style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.w200, - color: Colors.black)), + Text( + "Date of Birth", + style: GoogleFonts.poppins( + fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black), + ), SizedBox(height: 5), CustomTextFieldUserWrapper( width: widget.isDesktop @@ -550,10 +789,13 @@ class PersonalDetailsState extends State { child: AbsorbPointer( child: TextField( controller: widget.controllers["dob"], - style: const TextStyle(fontSize: 12, color: Colors.black), - decoration: const InputDecoration( + style: GoogleFonts.poppins(fontSize: 12, color: Colors.black), + decoration: InputDecoration( labelText: "Select Date", - labelStyle: TextStyle(fontSize: 12, color: Colors.grey), + labelStyle: GoogleFonts.poppins( + fontSize: 12, + fontWeight: FontWeight.w400, + color: Colors.grey), floatingLabelBehavior: FloatingLabelBehavior.never, border: InputBorder.none, contentPadding: EdgeInsets.symmetric(vertical: 16), @@ -573,11 +815,11 @@ class PersonalDetailsState extends State { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text("Email", - style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.w200, - color: Colors.black)), + Text( + "Email", + style: GoogleFonts.poppins( + fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black), + ), SizedBox(height: 5), CustomTextFieldUserWrapper( isFocused: false, @@ -585,7 +827,7 @@ class PersonalDetailsState extends State { child: SizedBox( height: 40, child: TextField( - style: TextStyle(fontSize: 12, color: Colors.black), + style: GoogleFonts.poppins(fontSize: 12, color: Colors.black), controller: widget.controllers["email"], onChanged: (value) { _clearError("email"); @@ -593,7 +835,8 @@ class PersonalDetailsState extends State { enabled: !widget.isViewMode, decoration: InputDecoration( labelText: "Email", - labelStyle: TextStyle(fontSize: 12, color: Colors.grey), + labelStyle: + GoogleFonts.poppins(fontSize: 12, color: Colors.grey), floatingLabelBehavior: FloatingLabelBehavior.never, border: InputBorder.none, contentPadding: EdgeInsets.symmetric(vertical: 16), @@ -616,11 +859,11 @@ class PersonalDetailsState extends State { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text("Mobile Number", - style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.w200, - color: Colors.black)), + Text( + "Mobile Number", + style: GoogleFonts.poppins( + fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black), + ), SizedBox(height: 5), CustomTextFieldUserWrapper( isFocused: false, @@ -628,7 +871,7 @@ class PersonalDetailsState extends State { child: SizedBox( height: 40, child: TextField( - style: TextStyle(fontSize: 12, color: Colors.black), + style: GoogleFonts.poppins(fontSize: 12, color: Colors.black), controller: widget.controllers["mobileNumber"], enabled: !widget.isViewMode, onChanged: (value) { @@ -641,7 +884,8 @@ class PersonalDetailsState extends State { ], decoration: InputDecoration( labelText: "Mobile Number", - labelStyle: TextStyle(fontSize: 12, color: Colors.grey), + labelStyle: + GoogleFonts.poppins(fontSize: 12, color: Colors.grey), floatingLabelBehavior: FloatingLabelBehavior.never, border: InputBorder.none, contentPadding: EdgeInsets.symmetric(vertical: 16), @@ -665,9 +909,9 @@ class PersonalDetailsState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text("Alternate Mobile Number", - style: TextStyle( + style: GoogleFonts.poppins( fontSize: 12, - fontWeight: FontWeight.w200, + fontWeight: FontWeight.w400, color: Colors.black)), SizedBox(height: 5), CustomTextFieldUserWrapper( @@ -676,7 +920,7 @@ class PersonalDetailsState extends State { child: SizedBox( height: 40, child: TextField( - style: TextStyle(fontSize: 12, color: Colors.black), + style: GoogleFonts.poppins(fontSize: 12, color: Colors.black), controller: widget.controllers["alternateMobile"], enabled: !widget.isViewMode, keyboardType: TextInputType.numberWithOptions(decimal: true), @@ -686,7 +930,8 @@ class PersonalDetailsState extends State { ], decoration: InputDecoration( labelText: "Alternate Number", - labelStyle: TextStyle(fontSize: 12, color: Colors.grey), + labelStyle: + GoogleFonts.poppins(fontSize: 12, color: Colors.grey), floatingLabelBehavior: FloatingLabelBehavior.never, border: InputBorder.none, contentPadding: EdgeInsets.symmetric(vertical: 16), @@ -726,11 +971,11 @@ class PersonalDetailsState extends State { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text("Country", - style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.w200, - color: Colors.black)), + Text( + "Country", + style: GoogleFonts.poppins( + fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black), + ), SizedBox(height: 5), CustomTextFieldUserWrapper( isFocused: false, @@ -765,7 +1010,7 @@ class PersonalDetailsState extends State { alignment: Alignment.centerLeft, child: Text( selectedItem ?? "Select Country", - style: TextStyle(fontSize: 12, color: Colors.black), + style: GoogleFonts.poppins(fontSize: 12, color: Colors.black), ), ), // onChanged: widget.isViewMode @@ -811,11 +1056,11 @@ class PersonalDetailsState extends State { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text("Postal Code", - style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.w200, - color: Colors.black)), + Text( + "Postal Code", + style: GoogleFonts.poppins( + fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black), + ), SizedBox(height: 5), CustomTextFieldUserWrapper( isFocused: false, @@ -823,7 +1068,7 @@ class PersonalDetailsState extends State { child: SizedBox( height: 40, child: TextField( - style: TextStyle(fontSize: 12, color: Colors.black), + style: GoogleFonts.poppins(fontSize: 12, color: Colors.black), controller: widget.controllers["postalCode"], enabled: !widget.isViewMode, keyboardType: TextInputType.numberWithOptions(decimal: true), @@ -833,7 +1078,10 @@ class PersonalDetailsState extends State { ], decoration: InputDecoration( labelText: "Postal Code", - labelStyle: TextStyle(fontSize: 12, color: Colors.grey), + labelStyle: GoogleFonts.poppins( + fontSize: 12, + fontWeight: FontWeight.w400, + color: Colors.grey), floatingLabelBehavior: FloatingLabelBehavior.never, border: InputBorder.none, contentPadding: EdgeInsets.symmetric(vertical: 16), @@ -849,11 +1097,11 @@ class PersonalDetailsState extends State { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text("Role ", - style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.w200, - color: Colors.black)), + Text( + "Role ", + style: GoogleFonts.poppins( + fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black), + ), SizedBox(height: 5), CustomTextFieldUserWrapper( isFocused: false, // Dropdown doesn't use focus @@ -862,7 +1110,7 @@ class PersonalDetailsState extends State { height: 45, // Set appropriate height child: DropdownButtonFormField( value: widget.isViewMode ? null : selectedRole, - style: TextStyle(fontSize: 12, color: Colors.black), + style: GoogleFonts.poppins(fontSize: 12, color: Colors.black), decoration: InputDecoration( border: InputBorder.none, contentPadding: @@ -873,6 +1121,9 @@ class PersonalDetailsState extends State { : (newValue) { setState(() { selectedRole = newValue; + isTravelAgent = selectedRole == "Travel Agent"; + // Pass the result back to parent + widget.onUserTypeChanged?.call(isTravelAgent); }); widget.onRoleChanged?.call(newValue); }, @@ -884,7 +1135,7 @@ class PersonalDetailsState extends State { hint: Text("Select"), disabledHint: Text( selectedRole ?? "Select Role", - style: TextStyle(fontSize: 12, color: Colors.black), + style: GoogleFonts.poppins(fontSize: 12, color: Colors.black), ), ), ), @@ -897,11 +1148,11 @@ class PersonalDetailsState extends State { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text("Address", - style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.w200, - color: Colors.black)), + Text( + "Address", + style: GoogleFonts.poppins( + fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black), + ), SizedBox(height: 5), CustomTextFieldUserWrapper( isFocused: false, @@ -909,14 +1160,15 @@ class PersonalDetailsState extends State { child: SizedBox( height: 80, child: TextField( - style: TextStyle(fontSize: 12, color: Colors.black), + style: GoogleFonts.poppins(fontSize: 12, color: Colors.black), controller: widget.controllers["address"], enabled: !widget.isViewMode, maxLines: 2, keyboardType: TextInputType.multiline, decoration: InputDecoration( labelText: "Address", - labelStyle: TextStyle(fontSize: 12, color: Colors.grey), + labelStyle: + GoogleFonts.poppins(fontSize: 12, color: Colors.grey), floatingLabelBehavior: FloatingLabelBehavior.never, border: InputBorder.none, // contentPadding: EdgeInsets.symmetric(vertical: 16), @@ -933,11 +1185,11 @@ class PersonalDetailsState extends State { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text("Password", - style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.w200, - color: Colors.black)), + Text( + "Password", + style: GoogleFonts.poppins( + fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black), + ), SizedBox(height: 5), CustomTextFieldUserWrapper( isFocused: false, @@ -945,7 +1197,7 @@ class PersonalDetailsState extends State { child: SizedBox( height: 40, child: TextField( - style: TextStyle(fontSize: 12, color: Colors.black), + style: GoogleFonts.poppins(fontSize: 12, color: Colors.black), controller: widget.controllers["password"], enabled: !widget.isViewMode, onChanged: (value) { @@ -953,7 +1205,8 @@ class PersonalDetailsState extends State { }, decoration: InputDecoration( labelText: "Password", - labelStyle: TextStyle(fontSize: 12, color: Colors.grey), + labelStyle: + GoogleFonts.poppins(fontSize: 12, color: Colors.grey), floatingLabelBehavior: FloatingLabelBehavior.never, border: InputBorder.none, contentPadding: EdgeInsets.symmetric(vertical: 16), diff --git a/lib/Screens/userManagement/create_user/traveller_details.dart b/lib/Screens/userManagement/create_user/traveller_details.dart index aea541e..7d481a6 100644 --- a/lib/Screens/userManagement/create_user/traveller_details.dart +++ b/lib/Screens/userManagement/create_user/traveller_details.dart @@ -1,13 +1,153 @@ +// import 'dart:io' as html; +import 'dart:io' as io; + +import 'package:dropdown_search/dropdown_search.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:intl/intl.dart'; +import 'package:universal_html/html.dart' as html; + +import 'dart:async'; + +import 'dart:typed_data'; // Import for Uint8List + +import 'package:flutter/foundation.dart'; + +import '../../../widgets/custom_user_form.dart'; + +import '../../../widgets/custom_user_form.dart'; +import '../../../widgets/custom_user_travel.dart'; class TravellerDetails extends StatefulWidget { + final bool isDesktop; + final Map controllers; + final Map errorMessages; + + final bool isViewMode; + + const TravellerDetails({ + Key? key, + required this.controllers, + required this.errorMessages, + required this.isDesktop, + required this.isViewMode, + }) : super(key: key); @override - _TravellerDetailsState createState() => _TravellerDetailsState(); + TravellerDetailsState createState() => TravellerDetailsState(); } -class _TravellerDetailsState extends State { +class TravellerDetailsState extends State { + html.File? passportFile; + late List isExpandedList; + int? expandedIndex; + + String? selectedFileNames; + Uint8List? passportDocumentBytes; + String? passportFileUrlFromApi; + + String? selectedPurpose; + String? selectedCountry; + String? selectedLocalIdType; + List countryList = []; + + String? selectedDomesticSeat; + String? selectedIntenationalSeat; + String? SelectedIdType; + + List frequentFlierRowIds = []; + int _rowFlierCounter = 1; + List hotelLoyalityRowIds = []; + int _rowhotelCounter = 1; + List visaRowIds = []; + int _rowVisaCounter = 1; + + Map countryMap = {}; + final Map controllers = {}; + + final List headers = [ + "Passport Details", + // "Local ID Details", + "Visa Details", + "Frequent Flier Information", + "Hotel Loyalty Membership", + "Preferences Domestic", + "Preferences International", + "Other Details", + "Forex Details" + ]; + + List dataHeader = [ + "Fname", + "Lname", + "nationality", + "d_meal_pref", + "i_meal_pref", + "d_additonal_Info", + "i_additonal_Info", + "passportNumber", + "emergency_contact", + "placeOfIssue", + "passportDoc", + "dateOfIssue", + "dateOfExpiry", + "forex_card_num", + "full_name_as_id", + "local_id_num", + ]; + + @override + void initState() { + super.initState(); + + for (var field in dataHeader) { + controllers[field] = TextEditingController(); + } + // Delay adding the row until after the first frame + WidgetsBinding.instance.addPostFrameCallback((_) { + setState(() { + frequentFlierRowIds.add(_rowFlierCounter++); + hotelLoyalityRowIds.add(_rowhotelCounter++); + visaRowIds.add(_rowVisaCounter++); + }); + }); + } + + void _clearError(String field) { + setState(() { + widget.errorMessages.remove(field); + }); + } + + Map get travel_Detials { + Map data = { + "given_Name": controllers["Fname"]?.text, + "last_Name": controllers["Lname"]?.text, + "nationality": controllers["nationality"]?.text, + "passport_number": controllers["passportNumber"]?.text, + "place_of_issue": controllers["placeOfIssue"]?.text, + "passport_document": passportFile, + "date_of_issue": controllers["dateOfIssue"]?.text, + "date_of_expiry": controllers["dateOfExpiry"]?.text, + "d_meal_preference": controllers["d_meal_pref"]?.text, + "d_seat_preference": selectedDomesticSeat, + "d_additonalInfo": controllers["d_additonal_Info"]?.text, + "i_meal_preference": controllers["i_meal_pref"]?.text, + "i_seat_preference": selectedIntenationalSeat, + "i_additonalInfo": controllers["i_additonal_Info"]?.text, + "emergency_contact_number": controllers["emergency_contact"]?.text, + "forex_pre_paid_card_number": controllers["forex_card_num"]?.text, + "frequent_flier_information": [], + "visa_details": [], + "hotel_membership": [], + "local_id_type": selectedLocalIdType, + "local_id_num": controllers["local_id_num"]?.text, + "full_name_as_id": controllers["full_name_as_id"]?.text, + }; + return data; + } + @override Widget build(BuildContext context) { return Container( @@ -19,8 +159,2162 @@ class _TravellerDetailsState extends State { ), ), ), - child: Column( - children: [Text("Traveller ...........")], + child: SingleChildScrollView( + scrollDirection: Axis.vertical, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ...List.generate(headers.length, (index) { + final isExpanded = expandedIndex == index; + + return Card( + color: isExpanded ? Color(0xFFFFFDF3) : Colors.white, + elevation: isExpanded ? 0.9 : 0, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + margin: const EdgeInsets.symmetric(vertical: 5), + child: InkWell( + focusColor: Color(0xFFFFFDF3), + hoverColor: Color(0xFFFFFDF3), + highlightColor: Color(0xFFFFFDF3), // removes grey highlight + splashColor: Colors.white, + borderRadius: BorderRadius.circular(12), + onTap: () { + setState(() { + expandedIndex = isExpanded ? null : index; + }); + }, + child: Padding( + padding: + const EdgeInsets.symmetric(horizontal: 12, vertical: 8), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + headers[index], + style: GoogleFonts.poppins( + fontSize: widget.isDesktop ? 12 : 11, + fontWeight: FontWeight.w500, + color: Colors.black87, + ), + ), + Icon( + isExpanded + ? Icons.keyboard_arrow_up + : Icons.keyboard_arrow_down, + color: isExpanded + ? Colors.black87 + : Colors.grey[600], + ) + ], + ), + AnimatedSize( + duration: const Duration(milliseconds: 250), + curve: Curves.easeInOut, + child: ConstrainedBox( + constraints: isExpanded + ? const BoxConstraints() + : const BoxConstraints(maxHeight: 0), + child: Padding( + padding: const EdgeInsets.only(top: 8), + child: Container( + width: double.infinity, + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8), + ), + child: _buildExpandedContent(index), + ), + ), + ), + ), + ], + ), + ), + ), + ); + }), + ], + ), + ), + ); + } + + Widget _buildExpandedContent(int index) { + switch (headers[index]) { + case "Passport Details": + return _buildPassportDatas(widget.isDesktop); + + // case "Local ID Details": + // return _buildLocalIdDetails(widget.isDesktop); + + case "Visa Details": + return _buildVisaDetails(widget.isDesktop); + + case "Frequent Flier Information": + return _buildFrequentFlierInfo(widget.isDesktop); + + case "Hotel Loyalty Membership": + return _buildHotelLoyalty(widget.isDesktop); + + case "Preferences Domestic": + return _buildPreferencesDomestic(widget.isDesktop); + // + case "Preferences International": + return _buildPreferencesInternational(widget.isDesktop); + + case "Other Details": + return _buildOtherDetails(widget.isDesktop); + + case "Forex Details": + return _buildForexDetails(widget.isDesktop); + + default: + return Text( + "Form fields for ${headers[index]} go here", + style: GoogleFonts.poppins(fontSize: 12), + ); + } + } + + //------------------------------------Passport Details----------------------------------- + + Widget _buildPassportDatas(bool isDesktop) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // SizedBox( + // height: 10, + // ), + // _buildPassportDataRow1(widget.isDesktop), + SizedBox( + height: 10, + ), + _buildPassportDataRow2(widget.isDesktop), + SizedBox( + height: 10, + ), + _buildPassportDataRow3(widget.isDesktop), + ], + ); + } + + Widget _buildPassportDataRow1(bool isDesktop) { + return Container( + color: Colors.white, + child: widget.isDesktop + ? Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + buildFirstNameField(), + Spacer(), + buildLastNameField(), + Spacer(), // Space after Last Name + buildNationality(), + ], + ) + : Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + buildFirstNameField(), + SizedBox(height: 8), // Vertical space + buildLastNameField(), + SizedBox(height: 8), + buildNationality(), + ], + ), + ); + } + + Widget _buildPassportDataRow2(bool isDesktop) { + return Container( + color: Colors.white, + child: widget.isDesktop + ? Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + buildPassportNumber(), + Spacer(), + buildPlaceOfIssue(), + Spacer(), + + /// Space after Last Name + buildDateOfIssue(), + + SizedBox(width: 25), + buildDateOfExpiry() + ], + ) + : Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + buildPassportNumber(), + SizedBox(height: 8), // Vertical space + buildPlaceOfIssue(), + SizedBox(height: 8), + buildDateOfIssue(), + SizedBox(height: 8), + buildDateOfExpiry() + ], + ), + ); + } + + Widget _buildPassportDataRow3(bool isDesktop) { + return Container( + color: Colors.white, + child: widget.isDesktop + ? Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + buildPassportDocument(), + ], + ) + : Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + buildPassportDocument(), + ], + ), + ); + } + + Widget buildFirstNameField() { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "First Name", + style: GoogleFonts.poppins( + fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black), + ), + SizedBox(height: 5), + CustomTextFieldUserTravellerWrapper( + isFocused: false, + isDesktop: widget.isDesktop, + child: SizedBox( + height: 40, + child: TextField( + style: GoogleFonts.poppins(fontSize: 12, color: Colors.black), + controller: controllers["Fname"], + enabled: !widget.isViewMode, + onChanged: (value) { + _clearError("first_name"); + }, + decoration: InputDecoration( + labelText: "FirstName", + labelStyle: + GoogleFonts.poppins(fontSize: 12, color: Colors.grey), + floatingLabelBehavior: FloatingLabelBehavior.never, + border: InputBorder.none, + contentPadding: EdgeInsets.symmetric(vertical: 16), + ), + ), + ), + ), + if (widget.errorMessages["first_name"] != null) ...[ + SizedBox(height: 5), // Space before error message + Text( + widget.errorMessages["first_name"]!, + style: TextStyle(color: Colors.red, fontSize: 12), + ), + ], + ], + ); + } + + Widget buildLastNameField() { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Last Name", + style: GoogleFonts.poppins( + fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black), + ), + SizedBox(height: 5), + CustomTextFieldUserTravellerWrapper( + isFocused: false, + isDesktop: widget.isDesktop, + child: SizedBox( + height: 40, + child: TextField( + style: GoogleFonts.poppins(fontSize: 12, color: Colors.black), + controller: controllers["Lname"], + enabled: !widget.isViewMode, + onChanged: (value) { + _clearError("last_name"); + }, + decoration: InputDecoration( + labelText: "LastName", + labelStyle: + GoogleFonts.poppins(fontSize: 12, color: Colors.grey), + floatingLabelBehavior: FloatingLabelBehavior.never, + border: InputBorder.none, + contentPadding: EdgeInsets.symmetric(vertical: 16), + ), + ), + ), + ), + if (widget.errorMessages["last_name"] != null) ...[ + SizedBox(height: 5), // Space before error message + Text( + widget.errorMessages["last_name"]!, + style: TextStyle(color: Colors.red, fontSize: 12), + ), + ], + ], + ); + } + + Widget buildNationality() { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Nationality", + style: GoogleFonts.poppins( + fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black), + ), + SizedBox(height: 5), + CustomTextFieldUserTravellerWrapper( + isFocused: false, + isDesktop: widget.isDesktop, + child: SizedBox( + height: 40, + child: TextField( + style: GoogleFonts.poppins(fontSize: 12, color: Colors.black), + controller: controllers["nationality"], + enabled: !widget.isViewMode, + onChanged: (value) { + _clearError("last_name"); + }, + decoration: InputDecoration( + labelText: "Nationality", + labelStyle: + GoogleFonts.poppins(fontSize: 12, color: Colors.grey), + floatingLabelBehavior: FloatingLabelBehavior.never, + border: InputBorder.none, + contentPadding: EdgeInsets.symmetric(vertical: 16), + ), + ), + ), + ), + if (widget.errorMessages["last_name"] != null) ...[ + SizedBox(height: 5), // Space before error message + Text( + widget.errorMessages["last_name"]!, + style: TextStyle(color: Colors.red, fontSize: 12), + ), + ], + ], + ); + } + + Widget buildPassportNumber() { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Passport Number", + style: GoogleFonts.poppins( + fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black), + ), + SizedBox(height: 5), + CustomTextFieldUserTravellerWrapper( + isFocused: false, + isDesktop: widget.isDesktop, + child: SizedBox( + height: 40, + child: TextField( + style: GoogleFonts.poppins(fontSize: 12, color: Colors.black), + controller: controllers["passportNumber"], + enabled: !widget.isViewMode, + onChanged: (value) { + _clearError("last_name"); + }, + decoration: InputDecoration( + labelText: "Passport Number", + labelStyle: + GoogleFonts.poppins(fontSize: 12, color: Colors.grey), + floatingLabelBehavior: FloatingLabelBehavior.never, + border: InputBorder.none, + contentPadding: EdgeInsets.symmetric(vertical: 16), + ), + ), + ), + ), + ], + ); + } + + Widget buildPlaceOfIssue() { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Place of Issue", + style: GoogleFonts.poppins( + fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black), + ), + SizedBox(height: 5), + CustomTextFieldUserTravellerWrapper( + isFocused: false, + isDesktop: widget.isDesktop, + child: SizedBox( + height: 40, + child: TextField( + style: GoogleFonts.poppins(fontSize: 12, color: Colors.black), + controller: controllers["placeOfIssue"], + enabled: !widget.isViewMode, + onChanged: (value) { + _clearError("placeOfIssue"); + }, + decoration: InputDecoration( + labelText: "Place of Issue", + labelStyle: + GoogleFonts.poppins(fontSize: 12, color: Colors.grey), + floatingLabelBehavior: FloatingLabelBehavior.never, + border: InputBorder.none, + contentPadding: EdgeInsets.symmetric(vertical: 16), + ), + ), + ), + ), + ], + ); + } + + Widget buildDateOfIssue() { + DateTime? _selectedDateOfIssue; + + Future _selectCheckDateOfIssue(BuildContext context) async { + DateTime now = DateTime.now(); + DateTime today = DateTime(now.year, now.month, now.day); + + DateTime? pickedDate = await showDatePicker( + context: context, + initialDate: + _selectedDateOfIssue != null && _selectedDateOfIssue!.isAfter(today) + ? _selectedDateOfIssue! + : today, + firstDate: today, + lastDate: DateTime(2100), + ); + + if (pickedDate != null && pickedDate != _selectedDateOfIssue) { + setState(() { + _selectedDateOfIssue = pickedDate; + controllers["dateOfIssue"]?.text = + DateFormat('yyyy-MM-dd').format(pickedDate); + }); + } + } + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Date of Issue", + style: GoogleFonts.poppins( + fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black), + ), + SizedBox(height: 5), + CustomTextFieldUserTravellerWrapper( + width: widget.isDesktop + ? MediaQuery.of(context).size.width * 0.11 + : null, + isFocused: false, + isDesktop: widget.isDesktop, + child: SizedBox( + height: 40, + child: GestureDetector( + onTap: widget.isViewMode + ? null + : () async { + await _selectCheckDateOfIssue(context); + if (controllers["dateOfIssue"]!.text.isNotEmpty) { + setState(() { + // errorMessages.remove("start_date"); + }); + } + }, + child: AbsorbPointer( + child: TextField( + controller: controllers["dateOfIssue"], + style: GoogleFonts.poppins(fontSize: 12, color: Colors.black), + decoration: InputDecoration( + labelText: "Select Date", + labelStyle: + GoogleFonts.poppins(fontSize: 12, color: Colors.grey), + floatingLabelBehavior: FloatingLabelBehavior.never, + border: InputBorder.none, + contentPadding: const EdgeInsets.symmetric(vertical: 16), + suffixIcon: const Icon(Icons.calendar_today, + size: 16, color: Color(0xFF8B8FB2)), + ), + ), + ), + ), + ), + ), + ], + ); + } + + Widget buildDateOfExpiry() { + DateTime? _selectedDateOfExpiry; + + Future _selectCheckDateOfExpiry(BuildContext context) async { + DateTime now = DateTime.now(); + DateTime today = DateTime(now.year, now.month, now.day); + + DateTime? pickedDate = await showDatePicker( + context: context, + initialDate: _selectedDateOfExpiry != null && + _selectedDateOfExpiry!.isAfter(today) + ? _selectedDateOfExpiry! + : today, + firstDate: today, + lastDate: DateTime(2100), + ); + + if (pickedDate != null && pickedDate != _selectedDateOfExpiry) { + setState(() { + _selectedDateOfExpiry = pickedDate; + controllers["dateOfExpiry"]?.text = + DateFormat('yyyy-MM-dd').format(pickedDate); + }); + } + } + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Date of Expiry", + style: GoogleFonts.poppins( + fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black), + ), + SizedBox(height: 5), + CustomTextFieldUserTravellerWrapper( + isFocused: false, + isDesktop: widget.isDesktop, + width: widget.isDesktop + ? MediaQuery.of(context).size.width * 0.11 + : null, + child: SizedBox( + height: 40, + child: GestureDetector( + onTap: widget.isViewMode + ? null + : () async { + await _selectCheckDateOfExpiry(context); + if (controllers["dateOfExpiry"]!.text.isNotEmpty) { + setState(() { + // errorMessages.remove("start_date"); + }); + } + }, + child: AbsorbPointer( + child: TextField( + controller: controllers["dateOfExpiry"], + style: GoogleFonts.poppins(fontSize: 12, color: Colors.black), + decoration: InputDecoration( + labelText: "Select Date", + labelStyle: + GoogleFonts.poppins(fontSize: 12, color: Colors.grey), + floatingLabelBehavior: FloatingLabelBehavior.never, + border: InputBorder.none, + contentPadding: const EdgeInsets.symmetric(vertical: 16), + suffixIcon: const Icon(Icons.calendar_today, + size: 16, color: Color(0xFF8B8FB2)), + ), + ), + ), + ), + ), + ), + ], + ); + } + + Widget buildPassportDocument() { + void pickPDFWeb() { + html.FileUploadInputElement uploadInput = html.FileUploadInputElement(); + uploadInput.accept = '.pdf'; + uploadInput.click(); + + uploadInput.onChange.listen((e) { + final file = uploadInput.files!.first; + + // Ensure the file is a PDF + if (!file.type.contains("pdf")) { + print("Error: Not a PDF file"); + return; + } + + // 🔹 File size check: Ensure it does not exceed 3MB + int maxFileSize = 3 * 1024 * 1024; // 3MB in bytes + if (file.size > maxFileSize) { + print('Error: File size exceeds 3MB'); + return; + } + + setState(() { + selectedFileNames = file.name; + passportFile = file; + passportFileUrlFromApi = null; + }); + + print('PDF File selected: ${file.name}'); + }); + } + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Passport Document", + style: GoogleFonts.poppins( + fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black), + ), + SizedBox(height: 5), + CustomTextFieldUserTravellerWrapper( + isFocused: false, + isDesktop: widget.isDesktop, + child: SizedBox( + height: 40, + child: GestureDetector( + onTap: pickPDFWeb, + child: Container( + padding: EdgeInsets.symmetric(horizontal: 10), + alignment: Alignment.centerLeft, + child: Text( + selectedFileNames ?? "Select Document", + style: GoogleFonts.poppins(fontSize: 12, color: Colors.black), + overflow: TextOverflow.ellipsis, // Prevents overflow issues + ), + ), + ), + ), + ), + SizedBox(height: 10), + if (passportFile != null || passportFileUrlFromApi != null) + + // Centers the text + Container( + color: Colors.white, + width: widget.isDesktop + ? MediaQuery.of(context).size.width * 0.25 + : MediaQuery.of(context).size.width * 0.8, + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + GestureDetector( + onTap: () { + print('DOWNLOAD - $passportFile'); + + if (passportFile != null) { + try { + // ✅ Step 1: Create a Blob directly from the file + final blob = + html.Blob([passportFile!], 'application/pdf'); + + // ✅ Step 2: Generate a download URL from the Blob + final url = html.Url.createObjectUrlFromBlob(blob); + + // ✅ Step 3: Create an invisible anchor to trigger download + final anchor = html.AnchorElement(href: url) + ..setAttribute( + "download", selectedFileNames ?? "document.pdf") + ..style.display = "none"; + + // ✅ Step 4: Add anchor to DOM and click it + html.document.body!.append(anchor); + anchor.click(); + + // ✅ Step 5: Clean up + anchor.remove(); + html.Url.revokeObjectUrl(url); + + print("Download triggered successfully!"); + } catch (e) { + print("Error during download: $e"); + } + } else if (passportFileUrlFromApi != null) { + // Trigger file download from the server path + final anchor = + html.AnchorElement(href: passportFileUrlFromApi!) + ..target = 'blank' + ..download = selectedFileNames ?? "document.pdf" + ..click(); + } else { + print("No file available to download."); + } + }, + child: Container( + padding: const EdgeInsets.all(5), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(5), + color: Colors.green.shade300, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Text( + "Download", + style: GoogleFonts.poppins( + fontSize: 12, + fontWeight: FontWeight.w200, + color: Colors.white), + overflow: TextOverflow.ellipsis, + textAlign: TextAlign + .end, // Ensures text is centered within the Text widget + ), + SizedBox(width: 5), + Icon( + Icons.download, + size: 13, + color: Colors.white, + ) + ], + ), + ), + ) + ], + ), + ), + ], + ); + } + + //------------------------Local Details---------------------------------------- + + Widget _buildLocalIdDetails(bool isDesktop) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + height: 10, + ), + _buildLocalDetailDataRow1(widget.isDesktop), + ], + ); + } + + Widget _buildLocalDetailDataRow1(bool isDesktop) { + return Container( + color: Colors.white, + child: widget.isDesktop + ? Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + buildLocalIdType(), + Spacer(), + buildIdNumber(), + Spacer(), + buildFullNameAsId(), + ], + ) + : Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + buildLocalIdType(), + SizedBox(height: 8), + buildIdNumber(), + SizedBox(height: 8), // Vertical space + + buildFullNameAsId(), + ], + ), + ); + } + + Widget buildIdNumber() { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Id Number", + style: GoogleFonts.poppins( + fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black), + ), + SizedBox(height: 5), + CustomTextFieldUserTravellerWrapper( + isFocused: false, + isDesktop: widget.isDesktop, + child: SizedBox( + height: 40, + child: TextField( + style: GoogleFonts.poppins(fontSize: 12, color: Colors.black), + controller: controllers["local_id_num"], + enabled: !widget.isViewMode, + onChanged: (value) { + _clearError("local_id_num"); + }, + decoration: InputDecoration( + labelText: "Id Number", + labelStyle: + GoogleFonts.poppins(fontSize: 12, color: Colors.grey), + floatingLabelBehavior: FloatingLabelBehavior.never, + border: InputBorder.none, + contentPadding: EdgeInsets.symmetric(vertical: 16), + ), + ), + ), + ), + ], + ); + } + + Widget buildLocalIdType() { + final List> localIdOptions = [ + {"dropdown_value": "Licence"}, + {"dropdown_value": "PAN"}, + {"dropdown_value": "Voter Card"}, + {"dropdown_value": "Aadhar Card"}, + ]; + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Id Type", + style: GoogleFonts.poppins( + fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black), + ), + SizedBox(height: 5), + CustomTextFieldUserTravellerWrapper( + isFocused: false, + isDesktop: widget.isDesktop, + child: SizedBox( + height: 35, + width: double.infinity, + child: DropdownSearch>( + popupProps: PopupProps.menu( + showSearchBox: false, + fit: FlexFit.loose, + menuProps: const MenuProps( + backgroundColor: Colors.white, + ), + itemBuilder: (context, item, isSelected) { + return Padding( + padding: + const EdgeInsets.symmetric(horizontal: 10, vertical: 8), + child: Text(item['dropdown_value'] ?? '', + style: GoogleFonts.poppins( + fontSize: 12, + color: Colors.black, + )), + ); + }, + ), + dropdownDecoratorProps: const DropDownDecoratorProps( + dropdownSearchDecoration: InputDecoration( + border: InputBorder.none, + contentPadding: + EdgeInsets.symmetric(horizontal: 10, vertical: 5), + ), + ), + dropdownButtonProps: const DropdownButtonProps( + icon: Icon(Icons.arrow_drop_down), + ), + dropdownBuilder: (context, selectedItem) { + if (selectedItem == null || selectedItem.isEmpty) { + return Text("Select Seat", // fallback text + style: GoogleFonts.poppins( + fontSize: 12, color: Colors.grey)); + } + return Text(selectedItem['dropdown_value'] ?? '', + style: + GoogleFonts.poppins(fontSize: 12, color: Colors.black)); + }, + items: localIdOptions, + selectedItem: selectedLocalIdType == null + ? null + : {"dropdown_value": selectedLocalIdType}, + onChanged: widget.isViewMode + ? null + : (Map? newItem) { + if (newItem != null) { + setState(() { + selectedLocalIdType = newItem['dropdown_value']; + }); + } + }, + ), + ), + ), + ], + ); + } + + Widget buildFullNameAsId() { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Full Name As ID", + style: GoogleFonts.poppins( + fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black), + ), + SizedBox(height: 5), + CustomTextFieldUserTravellerWrapper( + isFocused: false, + isDesktop: widget.isDesktop, + child: SizedBox( + height: 40, + child: TextField( + style: GoogleFonts.poppins(fontSize: 12, color: Colors.black), + controller: controllers["full_name_as_id"], + enabled: !widget.isViewMode, + onChanged: (value) { + _clearError("full_name_as_id"); + }, + decoration: InputDecoration( + labelText: "FullName As ID", + labelStyle: + GoogleFonts.poppins(fontSize: 12, color: Colors.grey), + floatingLabelBehavior: FloatingLabelBehavior.never, + border: InputBorder.none, + contentPadding: EdgeInsets.symmetric(vertical: 16), + ), + ), + ), + ), + ], + ); + } + +//------------------------Preferences Domestic --------------------------------------- + + Widget _buildPreferencesDomestic(bool isDesktop) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + height: 10, + ), + _buildDomesticDetailDataRow1(widget.isDesktop), + ], + ); + } + + Widget _buildDomesticDetailDataRow1(bool isDesktop) { + return Container( + color: Colors.white, + child: widget.isDesktop + ? Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + buildDomesticSeatType(), + SizedBox(width: 15), + buildDomesticMealPref(), + SizedBox(width: 15), + buildDomesticAddtnlPref(), + ], + ) + : Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + buildDomesticSeatType(), + SizedBox(height: 8), + buildDomesticMealPref(), + SizedBox(height: 8), // Vertical space + + buildDomesticAddtnlPref(), + ], + ), + ); + } + + Widget buildDomesticSeatType() { + final List> seatOptions = [ + {"dropdown_value": "Aisle"}, + {"dropdown_value": "Window"}, + {"dropdown_value": "Middle"}, + ]; + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Seat Preference", + style: GoogleFonts.poppins( + fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black), + ), + SizedBox(height: 5), + CustomTextFieldUserTravellerWrapper( + width: widget.isDesktop + ? MediaQuery.of(context).size.width * 0.15 + : null, + isFocused: false, + isDesktop: widget.isDesktop, + child: SizedBox( + height: 35, + width: double.infinity, + child: DropdownSearch>( + popupProps: PopupProps.menu( + showSearchBox: false, + fit: FlexFit.loose, + menuProps: const MenuProps( + backgroundColor: Colors.white, + ), + itemBuilder: (context, item, isSelected) { + return Padding( + padding: + const EdgeInsets.symmetric(horizontal: 10, vertical: 8), + child: Text(item['dropdown_value'] ?? '', + style: GoogleFonts.poppins( + fontSize: 12, + color: Colors.black, + )), + ); + }, + ), + dropdownDecoratorProps: const DropDownDecoratorProps( + dropdownSearchDecoration: InputDecoration( + border: InputBorder.none, + contentPadding: + EdgeInsets.symmetric(horizontal: 10, vertical: 5), + ), + ), + dropdownButtonProps: const DropdownButtonProps( + icon: Icon(Icons.arrow_drop_down), + ), + dropdownBuilder: (context, selectedItem) { + if (selectedItem == null || selectedItem.isEmpty) { + return Text("Select Seat", // fallback text + style: GoogleFonts.poppins( + fontSize: 12, color: Colors.grey)); + } + return Text(selectedItem['dropdown_value'] ?? '', + style: + GoogleFonts.poppins(fontSize: 12, color: Colors.black)); + }, + items: seatOptions, + selectedItem: selectedDomesticSeat == null + ? null + : {"dropdown_value": selectedDomesticSeat}, + onChanged: widget.isViewMode + ? null + : (Map? newItem) { + if (newItem != null) { + setState(() { + selectedDomesticSeat = newItem['dropdown_value']; + }); + } + }, + ), + ), + ), + ], + ); + } + + Widget buildDomesticMealPref() { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Meal Preference", + style: GoogleFonts.poppins( + fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black), + ), + SizedBox(height: 5), + CustomTextFieldUserTravellerWrapper( + width: widget.isDesktop + ? MediaQuery.of(context).size.width * 0.15 + : null, + isFocused: false, + isDesktop: widget.isDesktop, + child: SizedBox( + height: 40, + child: TextField( + style: GoogleFonts.poppins(fontSize: 12, color: Colors.black), + controller: controllers["d_meal_pref"], + enabled: !widget.isViewMode, + onChanged: (value) { + _clearError("d_meal_pref"); + }, + decoration: InputDecoration( + labelText: "Meal Preference", + labelStyle: + GoogleFonts.poppins(fontSize: 12, color: Colors.grey), + floatingLabelBehavior: FloatingLabelBehavior.never, + border: InputBorder.none, + contentPadding: EdgeInsets.symmetric(vertical: 16), + ), + ), + ), + ), + ], + ); + } + + Widget buildDomesticAddtnlPref() { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Additional Information", + style: GoogleFonts.poppins( + fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black), + ), + SizedBox(height: 5), + CustomTextFieldUserTravellerWrapper( + width: + widget.isDesktop ? MediaQuery.of(context).size.width * 0.4 : null, + isFocused: false, + isDesktop: widget.isDesktop, + child: SizedBox( + height: 40, + child: TextField( + style: GoogleFonts.poppins(fontSize: 12, color: Colors.black), + controller: controllers["d_additonal_Info"], + enabled: !widget.isViewMode, + onChanged: (value) { + _clearError("d_additonal_Info"); + }, + decoration: InputDecoration( + labelText: "Please Enter", + labelStyle: + GoogleFonts.poppins(fontSize: 12, color: Colors.grey), + floatingLabelBehavior: FloatingLabelBehavior.never, + border: InputBorder.none, + contentPadding: EdgeInsets.symmetric(vertical: 16), + ), + ), + ), + ), + ], + ); + } + + //------------------------Preferences International --------------------------------------- + + Widget _buildPreferencesInternational(bool isDesktop) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + height: 10, + ), + _buildInternationalDetailDataRow1(widget.isDesktop), + ], + ); + } + + Widget _buildInternationalDetailDataRow1(bool isDesktop) { + return Container( + color: Colors.white, + child: widget.isDesktop + ? Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + buildInternationalSeatType(), + SizedBox(width: 15), + buildInternationalMealPref(), + SizedBox(width: 15), + buildInternationalAddtnlPref(), + ], + ) + : Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + buildInternationalSeatType(), + SizedBox(height: 8), + buildInternationalMealPref(), + SizedBox(height: 8), // Vertical space + + buildInternationalAddtnlPref(), + ], + ), + ); + } + + Widget buildInternationalSeatType() { + final List> seatOptionsInt = [ + {"dropdown_value": "Aisle"}, + {"dropdown_value": "Window"}, + {"dropdown_value": "Middle"}, + ]; + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Seat Preference", + style: GoogleFonts.poppins( + fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black), + ), + SizedBox(height: 5), + CustomTextFieldUserTravellerWrapper( + width: widget.isDesktop + ? MediaQuery.of(context).size.width * 0.15 + : null, + isFocused: false, + isDesktop: widget.isDesktop, + child: SizedBox( + height: 35, + width: double.infinity, + child: DropdownSearch>( + popupProps: PopupProps.menu( + showSearchBox: false, + fit: FlexFit.loose, + menuProps: const MenuProps( + backgroundColor: Colors.white, + ), + itemBuilder: (context, item, isSelected) { + return Padding( + padding: + const EdgeInsets.symmetric(horizontal: 10, vertical: 8), + child: Text(item['dropdown_value'] ?? '', + style: GoogleFonts.poppins( + fontSize: 12, + color: Colors.black, + )), + ); + }, + ), + dropdownDecoratorProps: const DropDownDecoratorProps( + dropdownSearchDecoration: InputDecoration( + border: InputBorder.none, + contentPadding: + EdgeInsets.symmetric(horizontal: 10, vertical: 5), + ), + ), + // selectedItem: purposeList.cast>().firstWhere( + // (item) => item['dropdown_key'] == _selectedIsBillable, + // orElse: () => {}, + // ), + dropdownButtonProps: const DropdownButtonProps( + icon: Icon(Icons.arrow_drop_down), + ), + + dropdownBuilder: (context, selectedItem) { + if (selectedItem == null || selectedItem.isEmpty) { + return Text("Select Seat", // fallback text + style: GoogleFonts.poppins( + fontSize: 12, color: Colors.grey)); + } + return Text(selectedItem['dropdown_value'] ?? '', + style: + GoogleFonts.poppins(fontSize: 12, color: Colors.black)); + }, + items: seatOptionsInt, + selectedItem: selectedIntenationalSeat == null + ? null + : {"dropdown_value": selectedIntenationalSeat}, + onChanged: widget.isViewMode + ? null + : (Map? newItem) { + if (newItem != null) { + setState(() { + selectedIntenationalSeat = newItem['dropdown_value']; + }); + } + }, + ), + ), + ), + ], + ); + } + + Widget buildInternationalMealPref() { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Meal Preference", + style: GoogleFonts.poppins( + fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black), + ), + SizedBox(height: 5), + CustomTextFieldUserTravellerWrapper( + width: widget.isDesktop + ? MediaQuery.of(context).size.width * 0.15 + : null, + isFocused: false, + isDesktop: widget.isDesktop, + child: SizedBox( + height: 40, + child: TextField( + style: GoogleFonts.poppins(fontSize: 12, color: Colors.black), + controller: controllers["i_meal_pref"], + enabled: !widget.isViewMode, + onChanged: (value) { + _clearError("i_meal_pref"); + }, + decoration: InputDecoration( + labelText: "Meal Preference", + labelStyle: + GoogleFonts.poppins(fontSize: 12, color: Colors.grey), + floatingLabelBehavior: FloatingLabelBehavior.never, + border: InputBorder.none, + contentPadding: EdgeInsets.symmetric(vertical: 16), + ), + ), + ), + ), + ], + ); + } + + Widget buildInternationalAddtnlPref() { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Additional Information", + style: GoogleFonts.poppins( + fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black), + ), + SizedBox(height: 5), + CustomTextFieldUserTravellerWrapper( + width: + widget.isDesktop ? MediaQuery.of(context).size.width * 0.4 : null, + isFocused: false, + isDesktop: widget.isDesktop, + child: SizedBox( + height: 40, + child: TextField( + style: GoogleFonts.poppins(fontSize: 12, color: Colors.black), + controller: controllers["i_additonal_Info"], + enabled: !widget.isViewMode, + onChanged: (value) { + _clearError("i_additonal_Info"); + }, + decoration: InputDecoration( + labelText: "Please Enter", + labelStyle: + GoogleFonts.poppins(fontSize: 12, color: Colors.grey), + floatingLabelBehavior: FloatingLabelBehavior.never, + border: InputBorder.none, + contentPadding: EdgeInsets.symmetric(vertical: 16), + ), + ), + ), + ), + ], + ); + } + + //--------------------------Other Details------------------------------- + + Widget _buildOtherDetails(bool isDesktop) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + height: 10, + ), + buildEmergencyContactNum(), + ], + ); + } + + Widget buildEmergencyContactNum() { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Emergency Contact Number", + style: GoogleFonts.poppins( + fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black), + ), + SizedBox(height: 5), + CustomTextFieldUserTravellerWrapper( + isFocused: false, + isDesktop: widget.isDesktop, + child: SizedBox( + height: 40, + child: TextField( + style: GoogleFonts.poppins(fontSize: 12, color: Colors.black), + controller: controllers["emergency_contact"], + enabled: !widget.isViewMode, + onChanged: (value) { + _clearError("emergency_contact"); + }, + decoration: InputDecoration( + labelText: "Contact Number", + labelStyle: + GoogleFonts.poppins(fontSize: 12, color: Colors.grey), + floatingLabelBehavior: FloatingLabelBehavior.never, + border: InputBorder.none, + contentPadding: EdgeInsets.symmetric(vertical: 16), + ), + ), + ), + ), + ], + ); + } + //-------------------------Forex Prepaid Card Number------------------------ + + Widget _buildForexDetails(bool isDesktop) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + height: 10, + ), + buildForexCardNumber(), + ], + ); + } + + Widget buildForexCardNumber() { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Forex Pre-Paid Card Number", + style: GoogleFonts.poppins( + fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black), + ), + SizedBox(height: 5), + CustomTextFieldUserTravellerWrapper( + isFocused: false, + isDesktop: widget.isDesktop, + child: SizedBox( + height: 40, + child: TextField( + style: GoogleFonts.poppins(fontSize: 12, color: Colors.black), + controller: controllers["forex_card_num"], + enabled: !widget.isViewMode, + onChanged: (value) { + _clearError("forex_card_num"); + }, + decoration: InputDecoration( + labelText: "Card Number", + labelStyle: + GoogleFonts.poppins(fontSize: 12, color: Colors.grey), + floatingLabelBehavior: FloatingLabelBehavior.never, + border: InputBorder.none, + contentPadding: EdgeInsets.symmetric(vertical: 16), + ), + ), + ), + ), + ], + ); + } + + //-------------------------Frequent Flier Information ------------------------------- + + Widget _buildFrequentFlierInfo(bool isDesktop) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ...frequentFlierRowIds.map( + (id) => _buildFrequentAirlineDataRow1(id, isDesktop), + ), + IconForAirlineMem(), + ], + ); + } + + Widget _buildFrequentAirlineDataRow1(int id, bool isDesktop) { + return Container( + color: Colors.white, + padding: const EdgeInsets.only(top: 8.0), + child: widget.isDesktop + ? Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + buildAirline(), + SizedBox(width: 25), + buildFrequentFlierInformation(), + SizedBox(width: 15), + IconButton( + icon: Icon( + Icons.cancel, + color: Colors.red, + size: 15, + ), + onPressed: () { + setState(() { + frequentFlierRowIds.remove(id); + }); + }, + ), + ], + ) + : Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + buildAirline(), + SizedBox(height: 8), + buildFrequentFlierInformation(), + SizedBox(width: 15), + IconButton( + icon: Icon( + Icons.cancel, + color: Colors.red, + size: 15, + ), + onPressed: () { + setState(() { + frequentFlierRowIds.remove(id); + }); + }, + ), + ], + ), + ); + } + + Widget buildAirline() { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Airline", + style: GoogleFonts.poppins( + fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black), + ), + SizedBox(height: 5), + CustomTextFieldUserTravellerWrapper( + width: widget.isDesktop + ? MediaQuery.of(context).size.width * 0.15 + : null, + isFocused: false, + isDesktop: widget.isDesktop, + child: SizedBox( + height: 40, + child: TextField( + style: GoogleFonts.poppins(fontSize: 12, color: Colors.black), + controller: widget.controllers["Fname"], + enabled: !widget.isViewMode, + onChanged: (value) { + _clearError("first_name"); + }, + decoration: InputDecoration( + labelText: "Airline", + labelStyle: + GoogleFonts.poppins(fontSize: 12, color: Colors.grey), + floatingLabelBehavior: FloatingLabelBehavior.never, + border: InputBorder.none, + contentPadding: EdgeInsets.symmetric(vertical: 16), + ), + ), + ), + ), + ], + ); + } + + Widget buildFrequentFlierInformation() { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Frequent Flier Information", + style: GoogleFonts.poppins( + fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black), + ), + SizedBox(height: 5), + CustomTextFieldUserTravellerWrapper( + isFocused: false, + isDesktop: widget.isDesktop, + child: SizedBox( + height: 40, + child: TextField( + style: GoogleFonts.poppins(fontSize: 12, color: Colors.black), + controller: widget.controllers["Fname"], + enabled: !widget.isViewMode, + onChanged: (value) { + _clearError("first_name"); + }, + decoration: InputDecoration( + labelText: "Frequent Flier Information", + labelStyle: + GoogleFonts.poppins(fontSize: 12, color: Colors.grey), + floatingLabelBehavior: FloatingLabelBehavior.never, + border: InputBorder.none, + contentPadding: EdgeInsets.symmetric(vertical: 16), + ), + ), + ), + ), + ], + ); + } + + Widget IconForAirlineMem() { + return Container( + // height: 60, // must define height + alignment: Alignment.bottomRight, + child: IconButton( + icon: Icon( + Icons.add_circle, + color: Color(0xFF114D8B), + size: 30, + ), + onPressed: () { + setState(() { + frequentFlierRowIds.add(_rowFlierCounter++); + }); + }, + tooltip: "Hotel Loyalty Membership", + ), + ); + } + +//-------------------------Frequent Loyalty MemberShip ------------------------------- + + Widget _buildHotelLoyalty(bool isDesktop) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ...hotelLoyalityRowIds.map( + (id) => _buildFrequentLoyaltyDataRow1(id, isDesktop), + ), + IconForLoyaltyMem(), + ], + ); + } + + Widget _buildFrequentLoyaltyDataRow1(int id, bool isDesktop) { + return Container( + padding: const EdgeInsets.only(top: 8.0), + color: Colors.white, + child: widget.isDesktop + ? Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + buildHotel(), + SizedBox(width: 25), + buildHotelMembershipNum(), + IconButton( + icon: Icon( + Icons.cancel, + color: Colors.red, + size: 15, + ), + onPressed: () { + setState(() { + hotelLoyalityRowIds.remove(id); + }); + }, + ), + ], + ) + : Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + buildHotel(), + SizedBox(height: 8), + buildHotelMembershipNum(), + IconButton( + icon: Icon( + Icons.cancel, + color: Colors.red, + size: 15, + ), + onPressed: () { + setState(() { + hotelLoyalityRowIds.remove(id); + }); + }, + ), + ], + ), + ); + } + + Widget buildHotel() { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Hotel", + style: GoogleFonts.poppins( + fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black), + ), + SizedBox(height: 5), + CustomTextFieldUserTravellerWrapper( + width: widget.isDesktop + ? MediaQuery.of(context).size.width * 0.15 + : null, + isFocused: false, + isDesktop: widget.isDesktop, + child: SizedBox( + height: 40, + child: TextField( + style: GoogleFonts.poppins(fontSize: 12, color: Colors.black), + controller: widget.controllers["Fname"], + enabled: !widget.isViewMode, + onChanged: (value) { + _clearError("first_name"); + }, + decoration: InputDecoration( + labelText: "Hotel", + labelStyle: + GoogleFonts.poppins(fontSize: 12, color: Colors.grey), + floatingLabelBehavior: FloatingLabelBehavior.never, + border: InputBorder.none, + contentPadding: EdgeInsets.symmetric(vertical: 16), + ), + ), + ), + ), + ], + ); + } + + Widget buildHotelMembershipNum() { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Hotel Membership Number", + style: GoogleFonts.poppins( + fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black), + ), + SizedBox(height: 5), + CustomTextFieldUserTravellerWrapper( + isFocused: false, + isDesktop: widget.isDesktop, + child: SizedBox( + height: 40, + child: TextField( + style: GoogleFonts.poppins(fontSize: 12, color: Colors.black), + controller: widget.controllers["Fname"], + enabled: !widget.isViewMode, + onChanged: (value) { + _clearError("first_name"); + }, + decoration: InputDecoration( + labelText: "Hotel Membership Number", + labelStyle: + GoogleFonts.poppins(fontSize: 12, color: Colors.grey), + floatingLabelBehavior: FloatingLabelBehavior.never, + border: InputBorder.none, + contentPadding: EdgeInsets.symmetric(vertical: 16), + ), + ), + ), + ), + ], + ); + } + + Widget IconForLoyaltyMem() { + return Container( + height: 60, // must define height + alignment: Alignment.bottomRight, + child: IconButton( + icon: Icon( + Icons.add_circle, + color: Color(0xFF114D8B), + size: 30, + ), + onPressed: () { + setState(() { + hotelLoyalityRowIds.add(_rowhotelCounter++); + }); + }, + tooltip: "Hotel Loyalty Membership", + ), + ); + } + + //-------------------------Visa Details ----------------------------------------- + + Widget _buildVisaDetails(bool isDesktop) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + height: 10, + ), + ...visaRowIds.map( + (id) => _buildVisaDataRow1(id, isDesktop), + ), + IconForVisa() + ], + ); + } + + Widget _buildVisaDataRow1(int id, bool isDesktop) { + return Container( + padding: const EdgeInsets.only(top: 8), + color: Colors.white, + child: widget.isDesktop + ? Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + buildVisaCountry(), + SizedBox(width: 25), + buildVisaType(), + SizedBox(width: 15), + buildVisaValidFrom(), + SizedBox(width: 15), + buildVisaValidUpTo(), + IconButton( + icon: Icon( + Icons.cancel, + color: Colors.red, + size: 15, + ), + onPressed: () { + setState(() { + visaRowIds.remove(id); + }); + }, + ), + ], + ) + : Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + buildVisaCountry(), + SizedBox(height: 8), + buildVisaType(), + SizedBox(height: 8), + buildVisaValidFrom(), + SizedBox(height: 8), + buildVisaValidUpTo(), + IconButton( + icon: Icon( + Icons.cancel, + color: Colors.red, + size: 15, + ), + onPressed: () { + setState(() { + visaRowIds.remove(id); + }); + }, + ), + ], + ), + ); + } + + Widget buildVisaCountry() { + late Map countryMap; // Mapping country_code -> country_name + late List countryCodes; // List of country codes + + countryList = []; + // countryList = apiCountryData ?? []; + + // Map country codes to country names + countryMap = { + for (var item in countryList) + item['country_code'] as String: item['country_name'] as String + }; + + // Extract only country codes for processing + countryCodes = countryMap.keys.toList(); + + selectedCountry ??= null; + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Country", + style: GoogleFonts.poppins( + fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black), + ), + SizedBox(height: 5), + CustomTextFieldUserTravellerWrapper( + width: widget.isDesktop + ? MediaQuery.of(context).size.width * 0.15 + : null, + isFocused: false, + isDesktop: widget.isDesktop, + child: SizedBox( + height: 40, + child: DropdownSearch( + selectedItem: countryMap[selectedCountry], + popupProps: PopupProps.menu( + showSearchBox: true, // Enables search functionality + searchFieldProps: TextFieldProps( + decoration: InputDecoration( + hintText: "Search Country...", + contentPadding: EdgeInsets.symmetric(horizontal: 10), + ), + ), + ), + items: countryMap.values.toList(), + dropdownDecoratorProps: DropDownDecoratorProps( + dropdownSearchDecoration: InputDecoration( + border: InputBorder.none, + contentPadding: EdgeInsets.symmetric( + horizontal: 1, + ), + ), + ), + dropdownBuilder: (context, selectedItem) => Align( + // Center-align selected item + alignment: Alignment.centerLeft, + child: Text( + selectedItem ?? "Select Country", + style: TextStyle(fontSize: 12), + ), + ), + onChanged: (String? newValue) { + setState(() { + // Find the country_code based on selected country_name + selectedCountry = countryMap.entries + .firstWhere((entry) => entry.value == newValue) + .key; + }); + }, + ), + ), + ), + ], + ); + } + + Widget buildVisaType() { + // List purposeList = widget.apiData?['visa_type_of_visa'] ?? []; + List purposeList = []; + + List> dropdownItems = purposeList + .map((item) => DropdownMenuItem( + value: item['dropdown_key'], + child: Text(item['dropdown_value']), + )) + .toList(); + + if (dropdownItems.isEmpty) { + dropdownItems.add( + DropdownMenuItem( + value: null, + child: Text("No options available", + style: TextStyle(color: Colors.grey)), + ), + ); + } + + // Default selected value + selectedPurpose ??= + dropdownItems.isNotEmpty ? dropdownItems.first.value : null; + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Visa Type", + style: GoogleFonts.poppins( + fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black), + ), + SizedBox(height: 5), + CustomTextFieldUserTravellerWrapper( + width: widget.isDesktop + ? MediaQuery.of(context).size.width * 0.15 + : null, + isFocused: false, + isDesktop: widget.isDesktop, + child: SizedBox( + height: 40, + child: DropdownButtonFormField( + value: selectedPurpose, + style: TextStyle(fontSize: 12), + decoration: InputDecoration( + border: InputBorder.none, + contentPadding: + EdgeInsets.symmetric(horizontal: 10), // Proper padding + ), + onChanged: purposeList.isNotEmpty + ? (newValue) { + setState(() { + selectedPurpose = newValue; + }); + print( + "Updating form data: Flight -> trip_type -> ${newValue ?? ""}"); + } + : null, + items: dropdownItems, + ), + ), + ), + ], + ); + } + + Widget buildVisaValidFrom() { + DateTime? _selectedCheckOutDate; + TimeOfDay? _selectedCheckOutTime; + + Future _selectValidFromDate(BuildContext context) async { + DateTime now = DateTime.now(); + DateTime today = DateTime(now.year, now.month, now.day); + + // Parse date from notifier if available, else use today + DateTime initialDate = today; + + // Use previously selected date if valid + if (_selectedCheckOutDate != null && + _selectedCheckOutDate!.isAfter(today)) { + initialDate = _selectedCheckOutDate!; + } + + final pickedDate = await showDatePicker( + context: context, + initialDate: initialDate, + firstDate: initialDate, + lastDate: DateTime(2100), + ); + + if (pickedDate != null && pickedDate != _selectedCheckOutDate) { + setState(() { + _selectedCheckOutDate = pickedDate; + // _dateController.text = DateFormat('yyyy-MM-dd').format(pickedDate); + }); + } + } + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "ValidFrom", + style: GoogleFonts.poppins( + fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black), + ), + SizedBox(height: 5), + CustomTextFieldUserTravellerWrapper( + width: widget.isDesktop + ? MediaQuery.of(context).size.width * 0.15 + : null, + isFocused: false, + isDesktop: widget.isDesktop, + child: SizedBox( + height: 40, + child: GestureDetector( + onTap: () async { + await _selectValidFromDate(context); + }, + child: AbsorbPointer( + child: TextField( + // focusNode: _dateFocusNode, + // controller: _dateController, + style: const TextStyle(fontSize: 12), + decoration: const InputDecoration( + labelText: "Select Date", + labelStyle: TextStyle(fontSize: 12, color: Colors.grey), + floatingLabelBehavior: FloatingLabelBehavior.never, + border: InputBorder.none, + contentPadding: EdgeInsets.symmetric(vertical: 16), + suffixIcon: Icon(Icons.calendar_today, + size: 16, color: Colors.grey), + ), + ), + ), + ), + ), + ), + ], + ); + } + + Widget buildVisaValidUpTo() { + DateTime? _selectedCheckOutDate; + TimeOfDay? _selectedCheckOutTime; + + Future _selectValidUpToDate(BuildContext context) async { + DateTime now = DateTime.now(); + DateTime today = DateTime(now.year, now.month, now.day); + + // Parse date from notifier if available, else use today + DateTime initialDate = today; + + // Use previously selected date if valid + if (_selectedCheckOutDate != null && + _selectedCheckOutDate!.isAfter(today)) { + initialDate = _selectedCheckOutDate!; + } + + final pickedDate = await showDatePicker( + context: context, + initialDate: initialDate, + firstDate: initialDate, + lastDate: DateTime(2100), + ); + + if (pickedDate != null && pickedDate != _selectedCheckOutDate) { + setState(() { + _selectedCheckOutDate = pickedDate; + // _dateController.text = DateFormat('yyyy-MM-dd').format(pickedDate); + }); + } + } + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Valid UpTo", + style: GoogleFonts.poppins( + fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black), + ), + SizedBox(height: 5), + CustomTextFieldUserTravellerWrapper( + width: widget.isDesktop + ? MediaQuery.of(context).size.width * 0.15 + : null, + isFocused: false, + isDesktop: widget.isDesktop, + child: SizedBox( + height: 40, + child: GestureDetector( + onTap: () async { + await _selectValidUpToDate(context); + }, + child: AbsorbPointer( + child: TextField( + // focusNode: _dateFocusNode, + // controller: _dateController, + style: const TextStyle(fontSize: 12), + decoration: const InputDecoration( + labelText: "Select Date", + labelStyle: TextStyle(fontSize: 12, color: Colors.grey), + floatingLabelBehavior: FloatingLabelBehavior.never, + border: InputBorder.none, + contentPadding: EdgeInsets.symmetric(vertical: 16), + suffixIcon: Icon(Icons.calendar_today, + size: 16, color: Colors.grey), + ), + ), + ), + ), + ), + ), + ], + ); + } + + Widget IconForVisa() { + return Container( + height: 60, // must define height + alignment: Alignment.bottomRight, + child: IconButton( + icon: Icon( + Icons.add_circle, + color: Color(0xFF114D8B), + size: 30, + ), + onPressed: () { + setState(() { + visaRowIds.add(_rowVisaCounter++); + }); + }, + tooltip: "Hotel Loyalty Membership", ), ); } diff --git a/lib/Screens/userManagement/user_List.dart b/lib/Screens/userManagement/user_List.dart index 5fbe978..c3b7b2e 100644 --- a/lib/Screens/userManagement/user_List.dart +++ b/lib/Screens/userManagement/user_List.dart @@ -10,8 +10,10 @@ import 'package:shared_preferences/shared_preferences.dart'; import '../../config/apiUrl.dart'; import '../../routes/custom_appBar.dart'; import '../../routes/custom_drawer.dart'; +import '../../services/apiService.dart'; import '../../utils/pagination.dart'; import '../../widgets/custom_popup.dart'; +import '../../widgets/popup_userList_action.dart'; class UserListScreen extends StatefulWidget { @override @@ -19,7 +21,10 @@ class UserListScreen extends StatefulWidget { } class _UserListScreenState extends State { + final ApiService apiService = ApiService(); late Future> futureUsers; + + late Map userSingleData; List? apiCountryData; String? selectedUserId; String? orgId; @@ -542,16 +547,16 @@ class _UserListScreenState extends State { child: Column( mainAxisSize: MainAxisSize.min, children: [ - const Icon(Icons.error_outline, - color: Colors.redAccent, size: 60), - const SizedBox(height: 16), - Text( - "Oops!", - style: GoogleFonts.poppins( - fontSize: 20, - fontWeight: FontWeight.bold, - color: Colors.redAccent), - ), + // const Icon(Icons.error_outline, + // color: Colors.redAccent, size: 60), + // const SizedBox(height: 16), + // Text( + // "Oops!", + // style: GoogleFonts.poppins( + // fontSize: 20, + // fontWeight: FontWeight.bold, + // color: Colors.redAccent), + // ), const SizedBox(height: 8), Text( "No Plans Available For This User", @@ -683,61 +688,104 @@ class _UserListScreenState extends State { fontWeight: FontWeight.w400), ))), DataCell( - PopupMenuButton( - color: Colors.white, - padding: EdgeInsets.zero, - offset: Offset(0, 30), - icon: Icon( - Icons.more_vert, - color: Color(0xFF475569), - size: 14, - ), - itemBuilder: (context) => [ - CustomPopupMenuEntry( - child: Container( - padding: EdgeInsets.symmetric( - horizontal: 8, vertical: 8), - child: Row( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: - MainAxisAlignment.center, - children: [ - IconButton( - icon: Icon( - Icons.remove_red_eye, - color: - Color(0xFF475569), - size: 18), - onPressed: () { - context.go( - "/CreateUserDetails", - extra: { - "selectedUser": user, - "isViewMode": true - }, - ); - }), - IconButton( - icon: Image.asset( - 'assets/images/IconsImg/edit.png', - width: 20, - height: 15), - onPressed: () { - context.go( - "/CreateUserDetails", - extra: { - "selectedUser": user, - "isViewMode": false - }, - ); - }, - ), - ], - ), - ), - ), - ], + UserActionsMenu( + user: user, + getUserDetails: (id) => + apiService.getSingleUser(id), ), + // PopupMenuButton( + // color: Colors.white, + // padding: EdgeInsets.zero, + // offset: Offset(0, 30), + // icon: Icon( + // Icons.more_vert, + // color: Color(0xFF475569), + // size: 14, + // ), + // itemBuilder: (context) => [ + // CustomPopupMenuEntry( + // child: Container( + // padding: EdgeInsets.symmetric( + // horizontal: 8, vertical: 8), + // child: Row( + // mainAxisSize: MainAxisSize.min, + // mainAxisAlignment: + // MainAxisAlignment.center, + // children: [ + // IconButton( + // icon: Icon( + // Icons.remove_red_eye, + // color: + // Color(0xFF475569), + // size: 18), + // onPressed: () async { + // print( + // "USerDAta1 - $user"); + // // Fetch the user data properly with await + // Map + // usersData = + // await apiService + // .getSingleUser(user[ + // 'user_id'] + // is String + // ? int.parse(user[ + // 'user_id']) + // : user[ + // 'user_id']); + // + // print( + // "USerDAta2 - $usersData"); + // + // // userSingleData = + // // await apiService + // // .getSingleUser(user[ + // // 'user_id']); + // + // context.go( + // "/CreateUserDetails", + // extra: { + // "selectedUser": + // usersData, + // "isViewMode": true + // }, + // ); + // }), + // IconButton( + // icon: Image.asset( + // 'assets/images/IconsImg/edit.png', + // width: 20, + // height: 15), + // onPressed: () async { + // // Fetch the user data properly with await + // Map + // usersData = + // await apiService + // .getSingleUser(user[ + // 'user_id'] + // is String + // ? int.parse(user[ + // 'user_id']) + // : user[ + // 'user_id']); + // + // print( + // "USerDAta2 - $usersData"); + // context.go( + // "/CreateUserDetails", + // extra: { + // "selectedUser": + // usersData, + // "isViewMode": false + // }, + // ); + // }, + // ), + // ], + // ), + // ), + // ), + // ], + // ), // Row( // children: [ // MouseRegion( @@ -858,65 +906,85 @@ class _UserListScreenState extends State { color: Colors.black87, fontWeight: FontWeight.w700), ), - PopupMenuButton( - color: Colors.white, - padding: EdgeInsets.zero, - offset: Offset(0, 30), - icon: Icon( - Icons.more_vert, - color: Color(0xFF475569), - size: 14, - ), - itemBuilder: (context) => [ - CustomPopupMenuEntry( - child: Container( - padding: EdgeInsets.symmetric( - horizontal: 8, vertical: 8), - child: Row( - mainAxisSize: - MainAxisSize.min, - mainAxisAlignment: - MainAxisAlignment.center, - children: [ - IconButton( - icon: Icon( - Icons - .remove_red_eye, - color: Color( - 0xFF475569), - size: 18), - onPressed: () { - context.go( - "/CreateUserDetails", - extra: { - "selectedUser": - user, - "isViewMode": true - }, - ); - }), - IconButton( - icon: Image.asset( - 'assets/images/IconsImg/edit.png', - width: 20, - height: 15), - onPressed: () { - context.go( - "/CreateUserDetails", - extra: { - "selectedUser": - user, - "isViewMode": false - }, - ); - }, - ), - ], - ), - ), - ), - ], + + UserActionsMenu( + user: user, + getUserDetails: (id) => + apiService.getSingleUser(id), ), + // PopupMenuButton( + // color: Colors.white, + // padding: EdgeInsets.zero, + // offset: Offset(0, 30), + // icon: Icon( + // Icons.more_vert, + // color: Color(0xFF475569), + // size: 14, + // ), + // itemBuilder: (context) => [ + // CustomPopupMenuEntry( + // child: Container( + // padding: EdgeInsets.symmetric( + // horizontal: 8, vertical: 8), + // child: Row( + // mainAxisSize: + // MainAxisSize.min, + // mainAxisAlignment: + // MainAxisAlignment.center, + // children: [ + // IconButton( + // icon: Icon( + // Icons + // .remove_red_eye, + // color: Color( + // 0xFF475569), + // size: 18), + // onPressed: () { + // print( + // "USerDAta - $user"); + // // dynamic usersData = apiService + // // .getSingleUser(user[ + // // 'user_id'] + // // is String + // // ? int.parse(user[ + // // 'user_id']) + // // : user[ + // // 'user_id']); + // // + // // print( + // // "USerDAta - $usersData"); + // + // context.go( + // "/CreateUserDetails", + // extra: { + // "selectedUser": + // user, + // "isViewMode": true + // }, + // ); + // }), + // IconButton( + // icon: Image.asset( + // 'assets/images/IconsImg/edit.png', + // width: 20, + // height: 15), + // onPressed: () { + // context.go( + // "/CreateUserDetails", + // extra: { + // "selectedUser": + // user, + // "isViewMode": false + // }, + // ); + // }, + // ), + // ], + // ), + // ), + // ), + // ], + // ), ], ), diff --git a/lib/routes/custom_appBar.dart b/lib/routes/custom_appBar.dart index 82b7fff..1902194 100644 --- a/lib/routes/custom_appBar.dart +++ b/lib/routes/custom_appBar.dart @@ -394,31 +394,54 @@ class _CustomAppBarState extends State { buildNavItem("All Trips", _myTravelRequestColor, () => context.go('/listAllPlan'), isSelected: selectedTab == "All Trips"), - if (userDetails["role"] == "Travel Agent") - const SizedBox(width: 20), - if (userDetails["role"] == "Travel Agent") - buildNavItem("My Trips", _myTravelRequestColor, - () => context.go('/listTravelAgentPlan'), - isSelected: selectedTab == "My Trips"), - ...[ - const SizedBox(width: 20), - buildNavItem("My Trips", _myTravelRequestColor, - () => context.go('/listPlan'), - isSelected: selectedTab == "My Trips"), - ], + if (userDetails["role"] != "Travel Agent") const SizedBox(width: 20), - buildNavItem("My Approvals", _myApprovalsColor, () { - if (userData?["role"] == "Travel Agent") { - isSelected: - selectedTab == "My Approvals"; - context.go('/listTravelAgentPlan'); - } else { + + if (userData?["role"] == "Travel Agent") + buildNavItem( + "My Trips", + _myTravelRequestColor, + () => context.go('/listTravelAgentPlan'), + isSelected: selectedTab == "My Trips", + ), + + if (userData?["role"] != "Travel Agent") // for others + buildNavItem( + "My Trips", + _myTravelRequestColor, + () => context.go('/listPlan'), + isSelected: selectedTab == "My Trips", + ), + // if (userDetails["role"] == "Travel Agent") + // buildNavItem("My Trips", _myTravelRequestColor, + // () => context.go('/listTravelAgentPlan'), + // isSelected: selectedTab == "My Trips"), + // ...[ + // const SizedBox(width: 20), + // buildNavItem("My Trips", _myTravelRequestColor, + // () => context.go('/listPlan'), + // isSelected: selectedTab == "My Trips"), + // ], + // + + const SizedBox(width: 20), + if (userData?["role"] != "Travel Agent") + buildNavItem("My Approvals", _myApprovalsColor, () { isSelected: selectedTab == "My Approvals"; context.go('/ApprovalList'); - } - }), + }) + + // if (userData?["role"] == "Travel Agent") { + // isSelected: + // selectedTab == "My Approvals"; + // context.go('/listTravelAgentPlan'); + // } else { + // isSelected: + // selectedTab == "My Approvals"; + // context.go('/ApprovalList'); + // } ], ), ), diff --git a/lib/services/apiService.dart b/lib/services/apiService.dart index 5fe8db3..82a0688 100644 --- a/lib/services/apiService.dart +++ b/lib/services/apiService.dart @@ -623,4 +623,111 @@ class ApiService { throw Exception('Failed to load plans'); } } + + Future getForexPdfDownload(forexId) async { + final String apiUrldata = + '$apiUrl/api/plans/forexDownload?forex_id=$forexId'; + + // 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', 'Forex_$forexId.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'); + } + } + + //---------------------------------------User Management----------------------------------- + + Future> getSingleUser(int userId) async { + print('Single USer 1 - $userId'); + + final String apiUrldata = '$apiUrl/api/users/find/$userId'; + + 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"); + } + print('Single USer 1'); + + Map plansJson = + data['data']; // 'data' is a Map, not a List + + print('Single USer 2'); + return plansJson; + } catch (e) { + throw Exception('Error parsing response: $e'); + } + } else { + throw Exception('Failed to load plans'); + } + } } diff --git a/lib/utils/travelAgent_remarks.dart b/lib/utils/travelAgent_remarks.dart new file mode 100644 index 0000000..0c3e7fa --- /dev/null +++ b/lib/utils/travelAgent_remarks.dart @@ -0,0 +1,267 @@ +import 'dart:convert'; + +import 'package:flutter/material.dart'; +import 'package:frontend/utils/auth_utils.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:http/http.dart' as http; + +import '../config/apiUrl.dart'; + +class CommentModal extends StatefulWidget { + final String planId; + final Color layoutColorForUser; + final String role; + const CommentModal({ + Key? key, + required this.planId, + required this.layoutColorForUser, + required this.role, + }) : super(key: key); + + @override + CommentModalState createState() => CommentModalState(); +} + +class CommentModalState extends State { + Map? remarksData; + int? remarksId; + dynamic userId; + bool isLoading = true; + String? errorMessage; + bool editRemarks = false; + TextEditingController commentController = TextEditingController(); + + Map getData() { + final map = { + "plan_id": int.parse(widget.planId), + "remarks": commentController.text, + "created_by": userId, + "is_active": 1, + }; + + if (remarksId != null) { + map["id"] = remarksId; // Add 'id' only if available + } + + return map; + } + + @override + void initState() { + super.initState(); + loadRemarks(); + } + + Future loadRemarks() async { + try { + final userIdString = await getUserId(); // getUserId returns String? + if (userIdString == null) { + throw Exception('User ID not found.'); + } + + userId = int.tryParse(userIdString); + if (userId == null) { + throw Exception('Invalid user ID format.'); + } + + final data = await getRemarks(userId); + + setState(() { + remarksData = data; + + isLoading = false; + }); + print("Remarks -"); + print("Remarks - ${jsonEncode(remarksData)}"); + } catch (e) { + setState(() { + errorMessage = e.toString(); + isLoading = false; + }); + } + } + + Future> getRemarks(int userId) async { + // final String apiUrldata = + // '$apiUrl/getRemarksByPlanId?plan_id=${widget.planId}&user_id=$userId'; + + final String apiUrldata = + '$apiUrl/api/plans/getRemarksByPlanId?plan_id=${widget.planId}&user_id=$userId'; + + 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) { + print("Res1 ...."); + final data = json.decode(response.body); + + // Check if 'data' is empty or doesn't contain valid remarks + if (!data.containsKey('data') || + data['data'] == null || + data['data'].isEmpty) { + editRemarks = true; // Set the flag to true if data is empty + } else { + print("DAta available "); + updateData(data); + editRemarks = false; // Otherwise, no need to create remarks + } + + print("Res1 - $data"); + if (!data.containsKey('data') || data['data'] is! Map) { + throw Exception( + "Invalid response format: 'data' field is missing or not a Map"); + } + return data['data']; + } else { + throw Exception('Failed to load remarks'); + } + } + + Future postRemarksData() async { + final remarksData = getData(); + + print("Remarks Data - remarksData"); + + final String apiUrldata = '$apiUrl/api/plans/addOrEditPlanRemark'; + // final String apiUrldata = '$apiUrl/api/plans/createOrEditPlan'; + + final token = await getToken(); // Fetch token + + if (token == null) { + throw Exception('Token not found. Please log in.'); + } + + // if (selectedPlanId != null && selectedPlanId!.isNotEmpty) { + // planData['plan_id'] = selectedPlanId; // Add plan_id for update + // } + + try { + final response = await http.post( + Uri.parse(apiUrldata), + headers: { + 'Authorization': 'Bearer $token', + 'Content-Type': 'application/json', + }, + body: jsonEncode(remarksData), // Convert map to JSON + ); + + if (response.statusCode == 200) { + print("Plan submitted successfully!"); + print("Response: ${response.body}"); + } else { + print("Failed to submit plan. Status: ${response.statusCode}"); + print("Error: ${response.body}"); + } + } catch (e) { + print(" Error submitting plan: $e"); + } + } + + void updateData(dynamic data) { + if (data['data'] != null && data['data'].isNotEmpty) { + final remarksData = data['data'][0]; // Take the first item from the list + + // Now update your local fields + commentController.text = remarksData['remarks'] ?? ''; + + // If you need to update other fields like created_by, you can do that too + userId = remarksData['created_by'] ?? userId; + remarksId = remarksData['id']; + // If plan_id needs to be updated (usually it doesn't change), you can do it too + // widget.planId = remarksData['plan_id'].toString(); // If widget.planId is mutable + + setState(() {}); // Refresh the UI if needed + } + } + + Widget build(BuildContext context) { + // You can also make a TextEditingController if you want to collect input + + return AlertDialog( + contentPadding: const EdgeInsets.fromLTRB(24, 20, 24, 10), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + // Row 1: Title + Edit + Delete buttons + Row( + children: [ + Text( + 'Comment', + style: GoogleFonts.poppins(fontSize: 18, color: Colors.black), + ), + const Spacer(), + if (widget.role == "Travel Agent") + IconButton( + icon: const Icon(Icons.edit, size: 20), + onPressed: () { + setState(() { + editRemarks = !editRemarks; + }); + }, + ), + IconButton( + icon: const Icon(Icons.delete, size: 20), + onPressed: () { + // Handle delete pressed + }, + ), + ], + ), + const SizedBox(height: 10), + + // Row 2: TextField for comment + TextField( + controller: commentController, + maxLines: 5, + enabled: editRemarks, + decoration: InputDecoration( + hintText: 'Enter your comment...', + hintStyle: GoogleFonts.poppins(fontSize: 12, color: Colors.grey), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(8), + ), + ), + style: GoogleFonts.poppins(fontSize: 12, color: Colors.black), + ), + const SizedBox(height: 20), + + // Row 3: OK button + + editRemarks + ? SizedBox( + width: double.infinity, + child: ElevatedButton( + onPressed: () { + postRemarksData(); + // You can get text from commentController.text + Navigator.of(context).pop(); // Close the modal + }, + style: ElevatedButton.styleFrom( + backgroundColor: widget.layoutColorForUser, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + ), + child: Text('OK', + style: GoogleFonts.poppins( + fontSize: 13, color: Colors.white)), + ), + ) + : SizedBox.shrink(), + ], + ), + ); + } +} diff --git a/lib/widgets/custom_user_travel.dart b/lib/widgets/custom_user_travel.dart new file mode 100644 index 0000000..d01f6c1 --- /dev/null +++ b/lib/widgets/custom_user_travel.dart @@ -0,0 +1,61 @@ +import 'package:flutter/material.dart'; + +class CustomTextFieldUserTravellerWrapper extends StatefulWidget { + final Widget child; + final bool isFocused; + final bool isDesktop; + final double? width; + final Color? color; + final VoidCallback? onFocusChange; // Callback for focus handling + final EdgeInsetsGeometry padding; + + const CustomTextFieldUserTravellerWrapper({ + super.key, + required this.child, + required this.isFocused, + required this.isDesktop, + this.width, + this.color = Colors.white, + this.onFocusChange, + this.padding = const EdgeInsets.symmetric(horizontal: 12), + }); + + @override + _CustomTextFieldUserTravellerWrapperState createState() => + _CustomTextFieldUserTravellerWrapperState(); +} + +class _CustomTextFieldUserTravellerWrapperState + extends State { + @override + Widget build(BuildContext context) { + return Container( + width: widget.width ?? // Use custom width if provided, else default + (widget.isDesktop + ? MediaQuery.of(context).size.width * 0.24 + : MediaQuery.of(context).size.width * 0.8), + padding: widget.padding, + decoration: BoxDecoration( + // color: widget.color, + // color: Color(0xFFF7F7FB), + borderRadius: BorderRadius.circular(10), + border: Border.all( + color: widget.isFocused ? Color(0xFF78B4FC) : Color(0xFFD6D5E6), + // color: widget.isFocused ? Color(0xFF78B4FC) : Color(0xFFD6D5E6), + width: widget.isFocused ? 2.0 : 0.5, + ), + boxShadow: widget.isFocused + ? [ + BoxShadow( + color: Color.fromRGBO(120, 180, 252, 0.3), + blurRadius: 10, + spreadRadius: 2, + offset: Offset(0, 4), + ), + ] + : [], + ), + child: widget.child, + ); + } +} diff --git a/lib/widgets/popup_userList_action.dart b/lib/widgets/popup_userList_action.dart new file mode 100644 index 0000000..e2f461f --- /dev/null +++ b/lib/widgets/popup_userList_action.dart @@ -0,0 +1,77 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; + +import '../services/apiService.dart'; +import 'custom_popup.dart'; + +class UserActionsMenu extends StatelessWidget { + final ApiService apiService = ApiService(); + final Map user; + final Future> Function(int userId) getUserDetails; + + UserActionsMenu({ + super.key, + required this.user, + required this.getUserDetails, + }); + + int getUserId(dynamic value) { + if (value is String) return int.parse(value); + return value; + } + + @override + Widget build(BuildContext context) { + return PopupMenuButton( + color: Colors.white, + padding: EdgeInsets.zero, + offset: Offset(0, 30), + icon: Icon( + Icons.more_vert, + color: Color(0xFF475569), + size: 14, + ), + itemBuilder: (context) => [ + CustomPopupMenuEntry( + child: Container( + padding: EdgeInsets.symmetric(horizontal: 8, vertical: 8), + child: Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + GestureDetector( + child: Icon(Icons.remove_red_eye, + color: Color(0xFF475569), size: 18), + onTap: () async { + Navigator.pop(context); // Close the menu + final userId = getUserId(user['user_id']); + final usersData = await getUserDetails(userId); + context.go("/CreateUserDetails", extra: { + "selectedUser": usersData, + "isViewMode": true, + }); + }, + ), + SizedBox(width: 8), + GestureDetector( + child: Image.asset('assets/images/IconsImg/edit.png', + width: 20, height: 15), + onTap: () async { + Navigator.pop(context); // Close the menu + final userId = getUserId(user['user_id']); + final usersData = await getUserDetails(userId); + context.go("/CreateUserDetails", extra: { + "selectedUser": usersData, + "isViewMode": false, + }); + }, + ), + ], + ), + ), + ), + ], + ); + } +} diff --git a/pubspec.lock b/pubspec.lock index 0e32a0f..ac3cb8d 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -208,6 +208,14 @@ packages: description: flutter source: sdk version: "0.0.0" + fluttertoast: + dependency: "direct main" + description: + name: fluttertoast + sha256: "25e51620424d92d3db3832464774a6143b5053f15e382d8ffbfd40b6e795dcf1" + url: "https://pub.dev" + source: hosted + version: "8.2.12" go_router: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 757b4b4..4e3115c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -49,6 +49,7 @@ dependencies: universal_html: ^2.2.4 super_tooltip: ^2.0.9 google_fonts: ^6.2.1 + fluttertoast: ^8.2.12 dev_dependencies: