diff --git a/lib/Screens/itnerary/flights.dart b/lib/Screens/itnerary/flights.dart index 4d5913a..e056631 100644 --- a/lib/Screens/itnerary/flights.dart +++ b/lib/Screens/itnerary/flights.dart @@ -9,8 +9,11 @@ import '../../widgets/custom_text_field.dart'; import '../../widgets/custom_text_itnerary_sub.dart'; class FlightScreen extends StatefulWidget { + final bool hasAction; + final String? tripType; final List> flightData; final Map? apiData; + final Map? apiDataForClass; final String? loginUser; final Function(bool) onClose; final Function(Map) onSaveFlight; @@ -22,7 +25,10 @@ class FlightScreen extends StatefulWidget { required this.onClose, required this.onSaveFlight, required this.selectedItem, - required this.flightData}); + required this.flightData, + required this.hasAction, + this.tripType, + this.apiDataForClass}); @override _FlightScreenState createState() => _FlightScreenState(); @@ -32,6 +38,10 @@ class _FlightScreenState extends State { ApiService apiService = ApiService(); final GlobalKey _formKey = GlobalKey(); + bool isCountryLoading = true; + late Map countryMap; + late List countryCodes; + late ValueNotifier flightFirstTripDateNotifier; late ValueNotifier flightLastTripDateNotifier; @@ -42,6 +52,8 @@ class _FlightScreenState extends State { String? selectedTripType; Map selectedClasses = {}; // Store class selection for each trip + Map selectedFrom = {}; // Store class selection for each trip + Map selectedTo = {}; // Store class selection for each trip String? selectedvisa_available; int multiTripRowCount = 1; @@ -66,7 +78,7 @@ class _FlightScreenState extends State { List controllers = []; // Dynamic controllers Map errorMessages = {}; - + // forex_pre_paid_card_number @override void initState() { super.initState(); @@ -120,7 +132,7 @@ class _FlightScreenState extends State { textControllers["_time${i}Controller"] ?.addListener(() => _clearError("time_$i")); } - // loadCountryList(); + loadCountryList(); flightFirstTripDateNotifier = ValueNotifier(null); flightLastTripDateNotifier = ValueNotifier(null); @@ -161,15 +173,57 @@ class _FlightScreenState extends State { }; } + // + // Future loadCountryList() async { + // setState(() { + // isCountryLoading = true; + // }); + // + // final result = await apiService.fetchFlightsCountryList(); + // + // if (result is List) { + // countryList = + // result.map((item) => Map.from(item)).toList(); + // countryMap = { + // for (var item in countryList) + // if (item['country_code'] != null && item['country_name'] != null) + // item['country_code'] as String: item['country_name'] as String + // }; + // countryCodes = countryMap.keys.toList(); + // } else { + // countryList = []; + // countryMap = {}; + // countryCodes = []; + // } + // + // setState(() { + // isCountryLoading = false; + // }); + // } Future loadCountryList() async { + setState(() { + isCountryLoading = true; + }); + final result = await apiService.fetchFlightsCountryList(); - if (result is List) { - countryList = - result.map((item) => Map.from(item)).toList(); - } else { - countryList = []; // fallback or throw error + print("ResultCountry : $result"); + + // Create a map: Country_Code -> "City, Airport" + Map tempCountryMap = {}; + + for (var country in result) { + String city = country['City'] ?? ''; + String airport = country['Airport'] ?? ''; + String displayName = '${country['City']} - ${country['Airport']}'; + + tempCountryMap[country['Code']] = displayName; } + + setState(() { + countryMap = tempCountryMap; // Update the map + isCountryLoading = false; + }); } int getRowCount() { @@ -285,8 +339,12 @@ class _FlightScreenState extends State { for (int i = 1; i <= rowCount; i++) { final trip = { "class": selectedClasses[i], - "from_place": textControllers["_from${i}Controller"]?.text ?? "", - "to_place": textControllers["_to${i}Controller"]?.text ?? "", + // "from_place": countryMap[selectedFrom[i]], + "from_place": selectedFrom[i], + "to_place": selectedTo[i], + + // "from_place": textControllers["_from${i}Controller"]?.text ?? "", + // "to_place": textControllers["_to${i}Controller"]?.text ?? "", "date": textControllers["_date${i}Controller"]?.text ?? "", "time": textControllers["_time${i}Controller"]?.text ?? "", "created_by": widget.loginUser, @@ -377,10 +435,12 @@ class _FlightScreenState extends State { int index = i + 1; // Use 1-based indexing to match the form selectedClasses[index] = trip["class"].toString(); - textControllers["_from${index}Controller"] = - TextEditingController(text: trip["from_place"]); - textControllers["_to${index}Controller"] = - TextEditingController(text: trip["to_place"]); + selectedFrom[index] = trip["from_place"].toString(); + selectedTo[index] = trip["to_place"].toString(); + // textControllers["_from${index}Controller"] = + // TextEditingController(text: trip["from_place"]); + // textControllers["_to${index}Controller"] = + // TextEditingController(text: trip["to_place"]); textControllers["_date${index}Controller"] = TextEditingController(text: trip["date"]); textControllers["_time${index}Controller"] = @@ -430,12 +490,19 @@ class _FlightScreenState extends State { // Loop through each trip row and validate required fields for (int i = 1; i <= rowCount; i++) { - if (textControllers["_from${i}Controller"]?.text.trim().isEmpty ?? true) { + // if (textControllers["_from${i}Controller"]?.text.trim().isEmpty ?? true) { + // errorMessages["from_place_$i"] = "Required"; + // } + + if (selectedFrom[i] == null) { errorMessages["from_place_$i"] = "Required"; } - if (textControllers["_to${i}Controller"]?.text.trim().isEmpty ?? true) { + if (selectedTo[i] == null) { errorMessages["to_place_$i"] = "Required"; } + // if (textControllers["_to${i}Controller"]?.text.trim().isEmpty ?? true) { + // errorMessages["to_place_$i"] = "Required"; + // } if (textControllers["_date${i}Controller"]?.text.trim().isEmpty ?? true) { errorMessages["date_$i"] = "Required"; } @@ -468,8 +535,8 @@ class _FlightScreenState extends State { int newIndex = 1; for (int i = 1; i <= multiTripRowCount + 1; i++) { if (i == index) continue; // Skip the deleted one - updatedTextControllers["_from${newIndex}Controller"] = - textControllers["_from${i}Controller"]!; + // updatedTextControllers["_from${newIndex}Controller"] = + // textControllers["_from${i}Controller"]!; updatedTextControllers["_to${newIndex}Controller"] = textControllers["_to${i}Controller"]!; updatedTextControllers["_date${newIndex}Controller"] = @@ -491,6 +558,27 @@ class _FlightScreenState extends State { } selectedClasses = updatedClasses; // Update the map + + // Shift the selectedFrom map BEFORE removing the index + Map updatedFrom = {}; + newIndex = 1; + for (int i = 1; i <= selectedFrom.length; i++) { + if (i == index) continue; // Skip the one being deleted + updatedFrom[newIndex] = selectedFrom[i]; + newIndex++; + } + selectedFrom = updatedFrom; + + // Shift the selectedTo map BEFORE removing the index + Map updatedTo = {}; + newIndex = 1; + for (int i = 1; i <= selectedTo.length; i++) { + if (i == index) continue; // Skip the one being deleted + updatedTo[newIndex] = selectedTo[i]; + newIndex++; + } + selectedTo = updatedTo; + print("FlightData - $flightsData"); print("Updated Trips: $multiTripRowCount"); print("Updated Classes: $selectedClasses"); @@ -916,7 +1004,7 @@ class _FlightScreenState extends State { } List _buildSecondRow(bool isDesktop, int index) { - List purposeList = widget.apiData?['flight_class'] ?? []; + List purposeList = widget.apiDataForClass?['flight_class'] ?? []; List> dropdownItems = purposeList .map((item) => DropdownMenuItem( @@ -1020,18 +1108,18 @@ class _FlightScreenState extends State { } } - late Map countryMap; // Mapping country_code -> country_name - late List countryCodes; // List of country codes + // late Map countryMap; // Mapping country_code -> country_name + // late List countryCodes; // List of country codes + // countryMap = { + // for (var item in countryList) + // if (item['country_code'] != null && item['country_name'] != null) + // item['country_code'] as String: item['country_name'] as String + // }; - 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; + // // Extract only country codes for processing + // countryCodes = countryMap.keys.toList(); + // + // selectedCountry ??= null; return [ Column( @@ -1053,28 +1141,31 @@ class _FlightScreenState extends State { // : MediaQuery.of(context).size.width * 0.66, child: SizedBox( height: 40, - child: DropdownButtonFormField( - focusNode: focusNodes["_class${index}FocusNode"], - // focusNode: _tripTypeFocusNode, // Assign the correct focus node - // controller: _hotelNameController, - value: selectedClasses[index], - style: TextStyle(fontSize: 12), - decoration: InputDecoration( - border: InputBorder.none, - contentPadding: - EdgeInsets.symmetric(horizontal: 10), // Proper padding - ), - onChanged: purposeList.isNotEmpty - ? (newValue) { - setState(() { - selectedClasses[index] = newValue; - }); + child: isCountryLoading + ? const Center(child: CircularProgressIndicator()) + : DropdownButtonFormField( + focusNode: focusNodes["_class${index}FocusNode"], + // focusNode: _tripTypeFocusNode, // Assign the correct focus node + // controller: _hotelNameController, + value: selectedClasses[index], - print(selectedClasses[index]); - } - : null, - items: dropdownItems, - ), + style: TextStyle(fontSize: 12), + decoration: InputDecoration( + border: InputBorder.none, + contentPadding: EdgeInsets.symmetric( + horizontal: 10), // Proper padding + ), + onChanged: purposeList.isNotEmpty + ? (newValue) { + setState(() { + selectedClasses[index] = newValue; + }); + + print(selectedClasses[index]); + } + : null, + items: dropdownItems, + ), ), ), ], @@ -1099,72 +1190,75 @@ class _FlightScreenState extends State { ), SizedBox(height: 5), CustomTextFieldItnerarySubWrapper( - // isFocused: _fromFocus, - isFocused: focusStates["_from${index}Focused"] ?? false, - // isFocused: focusStates["_from${fieldIndex}Focused"] ?? false, - isDesktop: 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; - // - // if (selectedCountry!.isNotEmpty) { - // errorMessages.remove("country_code"); - // } - // }); - // }, - // ), - // ) + // isFocused: _fromFocus, + isFocused: focusStates["_from${index}Focused"] ?? false, + // isFocused: focusStates["_from${fieldIndex}Focused"] ?? false, + isDesktop: isDesktop, + child: SizedBox( + height: 40, + child: isCountryLoading + ? Center(child: CircularProgressIndicator()) + : DropdownSearch( + selectedItem: selectedFrom[index] != null + ? countryMap[selectedFrom[index]] + : null, + popupProps: PopupProps.menu( + showSearchBox: true, + searchFieldProps: TextFieldProps( + decoration: InputDecoration( + hintText: "Search ...", + 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( + alignment: Alignment.centerLeft, + child: Text( + selectedItem ?? "Select", + style: TextStyle(fontSize: 12), + ), + ), + onChanged: (String? newValue) { + setState(() { + // selectedFrom[index] = countryMap.entries + // .firstWhere((entry) => entry.value == newValue) + // .key; - child: SizedBox( - height: 40, - child: TextField( - // focusNode: _fromFocusNode, - focusNode: focusNodes["_from${index}FocusNode"], - controller: textControllers["_from${index}Controller"], - style: const TextStyle(fontSize: 12), - decoration: const InputDecoration( - labelText: "From", - labelStyle: TextStyle(fontSize: 12, color: Colors.grey), - floatingLabelBehavior: FloatingLabelBehavior.never, - border: InputBorder.none, - contentPadding: EdgeInsets.symmetric(vertical: 16), - ), + selectedFrom[index] = countryMap.entries + .firstWhere((entry) => entry.value == newValue) + .key; + + print(selectedFrom[index]); + }); + }, + ), + ) + + // child: SizedBox( + // height: 40, + // child: TextField( + // // focusNode: _fromFocusNode, + // focusNode: focusNodes["_from${index}FocusNode"], + // controller: textControllers["_from${index}Controller"], + // style: const TextStyle(fontSize: 12), + // decoration: const InputDecoration( + // labelText: "From", + // labelStyle: TextStyle(fontSize: 12, color: Colors.grey), + // floatingLabelBehavior: FloatingLabelBehavior.never, + // border: InputBorder.none, + // contentPadding: EdgeInsets.symmetric(vertical: 16), + // ), + // ), + // ), ), - ), - ), if (errorMessages["from_place_$index"] != null) ...[ SizedBox(height: 5), // Space before error message Text( @@ -1192,24 +1286,51 @@ class _FlightScreenState extends State { ), SizedBox(height: 5), CustomTextFieldItnerarySubWrapper( - isFocused: focusStates["_to${index}Focused"] ?? false, - isDesktop: isDesktop, - child: SizedBox( - height: 40, - child: TextField( - focusNode: focusNodes["_to${index}FocusNode"], - controller: textControllers["_to${index}Controller"], - style: const TextStyle(fontSize: 12), - decoration: const InputDecoration( - labelText: "To", - labelStyle: TextStyle(fontSize: 12, color: Colors.grey), - floatingLabelBehavior: FloatingLabelBehavior.never, - border: InputBorder.none, - contentPadding: EdgeInsets.symmetric(vertical: 16), - ), - ), - ), - ), + isFocused: focusStates["_to${index}Focused"] ?? false, + isDesktop: isDesktop, + child: SizedBox( + height: 40, + child: isCountryLoading + ? Center(child: CircularProgressIndicator()) + : DropdownSearch( + selectedItem: selectedTo[index] != null + ? countryMap[selectedTo[index]] + : null, + popupProps: PopupProps.menu( + showSearchBox: true, + searchFieldProps: TextFieldProps( + decoration: InputDecoration( + hintText: "Search ...", + 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( + alignment: Alignment.centerLeft, + child: Text( + selectedItem ?? "Select Country", + style: TextStyle(fontSize: 12), + ), + ), + onChanged: (String? newValue) { + setState(() { + selectedTo[index] = countryMap.entries + .firstWhere((entry) => entry.value == newValue) + .key; + + print(selectedTo[index]); + }); + }, + ), + )), if (errorMessages["to_place_$index"] != null) ...[ SizedBox(height: 5), // Space before error message Text( @@ -1293,7 +1414,7 @@ class _FlightScreenState extends State { CustomTextFieldItnerarySubWrapper( isFocused: focusStates["_timeFocused"] ?? false, isDesktop: isDesktop, - width: isDesktop ? MediaQuery.of(context).size.width * 0.08 : null, + width: isDesktop ? MediaQuery.of(context).size.width * 0.1 : null, child: SizedBox( height: 40, child: GestureDetector( diff --git a/lib/Screens/itnerary/train.dart b/lib/Screens/itnerary/train.dart index bee4728..022ecbc 100644 --- a/lib/Screens/itnerary/train.dart +++ b/lib/Screens/itnerary/train.dart @@ -1,12 +1,15 @@ +import 'package:dropdown_search/dropdown_search.dart'; import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; import 'package:responsive_builder/responsive_builder.dart'; +import '../../services/apiService.dart'; import '../../widgets/custom_text_field.dart'; import '../../widgets/custom_text_itnerary_sub.dart'; class TrainScreen extends StatefulWidget { final Map? apiData; + final Map? apiDataForClass; final Function(Map) onSavetrain; final Function(bool) onClose; final Map? selectedItem; @@ -17,7 +20,8 @@ class TrainScreen extends StatefulWidget { this.apiData, required this.onSavetrain, required this.selectedItem, - required this.loginUser}); + required this.loginUser, + this.apiDataForClass}); @override _TrainScreenState createState() => _TrainScreenState(); @@ -26,6 +30,12 @@ class TrainScreen extends StatefulWidget { class _TrainScreenState extends State { final GlobalKey _formKey = GlobalKey(); + ApiService apiService = ApiService(); + bool isCountryLoading = true; + Map countryMap = {}; // <--- instead of late + + late List countryCodes; + Map selectedValues = {}; final FocusNode _trainNoFocusNode = FocusNode(); @@ -53,6 +63,8 @@ class _TrainScreenState extends State { bool _commentsFocus = false; String? selectedClass; + String? selectedFrom; + String? selectedTo; Map errorMessages = {}; @@ -60,8 +72,10 @@ class _TrainScreenState extends State { Map data = { "train_no": _trainNoController.text, "class": selectedClass, - "from_station": _fromController.text, - "to_station": _toController.text, + "from_station": selectedFrom, + // "from_station": _fromController.text, + "to_station": selectedTo, + // "to_station": _toController.text, "date": _dateController.text, "time": _timeController.text, "comments": _trainCommentsController.text, @@ -128,7 +142,7 @@ class _TrainScreenState extends State { _trainCommentsController = initController("comments"); _trainNoController = initController("train_no"); - _fromController = initController("from_station"); + // _fromController = initController("from_station"); _toController = initController("to_station"); _dateController = initController("date"); _timeController = initController("time"); @@ -138,11 +152,50 @@ class _TrainScreenState extends State { selectedClass = widget.selectedItem!["class"].toString(); } + // if (widget.selectedItem != null && + // widget.selectedItem!["from_station"] != null) { + // final fromCode = widget.selectedItem!["from_station"].toString(); + // if (countryMap.containsKey(fromCode)) { + // selectedFrom = fromCode; + // } + // } + + if (widget.selectedItem != null && + widget.selectedItem!["from_station"] != null) { + selectedFrom = widget.selectedItem!["from_station"].toString(); + print("Selected From CODE = $selectedFrom"); + } + + if (widget.selectedItem != null && + widget.selectedItem!["to_station"] != null) { + selectedTo = widget.selectedItem!["to_station"].toString(); + print("Selected To CODE = $selectedTo"); + } + + // if (widget.selectedItem != null && + // widget.selectedItem!["from_station"] != null) { + // String fromPlaceDisplay = widget.selectedItem!["from_station"].toString(); + // + // selectedFrom = countryMap.entries + // .firstWhere((entry) => entry.value == fromPlaceDisplay, + // orElse: () => MapEntry('', '')) // avoid crash if not found + // .key; + // + // print("Selected From CODE = $selectedFrom"); + // } + + // if (widget.selectedItem != null && + // widget.selectedItem!["to_station"] != null) { + // selectedClass = widget.selectedItem!["to_station"].toString(); + // } + _trainNoController.addListener(() => _clearError("train_no")); - _fromController.addListener(() => _clearError("from_station")); + // _fromController.addListener(() => _clearError("from_station")); _toController.addListener(() => _clearError("to_station")); _dateController.addListener(() => _clearError("date")); _timeController.addListener(() => _clearError("time")); + + loadCountryList(); } @override @@ -189,6 +242,32 @@ class _TrainScreenState extends State { return errorMessages.isEmpty; // Valid if there are no errors } + Future loadCountryList() async { + setState(() { + isCountryLoading = true; + }); + + final result = await apiService.fetchFlightsCountryList(); + + print("ResultCountry : $result"); + + // Create a map: Country_Code -> "City, Airport" + Map tempCountryMap = {}; + + for (var country in result) { + String city = country['City'] ?? ''; + String airport = country['Airport'] ?? ''; + String displayName = '${country['City']} - ${country['Airport']}'; + + tempCountryMap[country['Code']] = displayName; + } + + setState(() { + countryMap = tempCountryMap; // Update the map + isCountryLoading = false; + }); + } + void handleSave() { print("Handle Save accomadationData $trainData"); @@ -413,7 +492,7 @@ class _TrainScreenState extends State { //---------------------------------------------- - List purposeList = widget.apiData?['train_class'] ?? []; + List purposeList = widget.apiDataForClass?['train_class'] ?? []; List> dropdownItems = purposeList .map((item) => DropdownMenuItem( @@ -504,24 +583,84 @@ class _TrainScreenState extends State { ), SizedBox(height: 5), CustomTextFieldItnerarySubWrapper( - isFocused: _fromFocus, - isDesktop: isDesktop, - child: SizedBox( - height: 40, - child: TextField( - focusNode: _fromFocusNode, - controller: _fromController, - style: const TextStyle(fontSize: 12), - decoration: const InputDecoration( - labelText: "From", - labelStyle: TextStyle(fontSize: 12, color: Colors.grey), - floatingLabelBehavior: FloatingLabelBehavior.never, - border: InputBorder.none, - contentPadding: EdgeInsets.symmetric(vertical: 16), - ), + isFocused: _fromFocus, + isDesktop: isDesktop, + child: SizedBox( + height: 40, + child: isCountryLoading + ? Center(child: CircularProgressIndicator()) + : DropdownSearch( + // selectedItem: selectedFrom != null + // ? countryMap[selectedFrom] + // : null, + + selectedItem: selectedFrom != null + ? countryMap[ + selectedFrom] // get the display value from code + : null, + popupProps: PopupProps.menu( + showSearchBox: true, + searchFieldProps: TextFieldProps( + decoration: InputDecoration( + hintText: "Search ...", + 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( + alignment: Alignment.centerLeft, + child: Text( + selectedItem ?? "Select", + style: TextStyle(fontSize: 12), + ), + ), + // onChanged: (String? newValue) { + // setState(() { + // // selectedFrom[index] = countryMap.entries + // // .firstWhere((entry) => entry.value == newValue) + // // .key; + // + // selectedFrom = countryMap.entries + // .firstWhere((entry) => entry.value == newValue) + // .key; + // + // print(selectedFrom); + // }); + // }, + + onChanged: (String? newValue) { + setState(() { + selectedFrom = countryMap.entries + .firstWhere((entry) => entry.value == newValue) + .key; + }); + }, + ), + ) + // child: SizedBox( + // height: 40, + // child: TextField( + // focusNode: _fromFocusNode, + // controller: _fromController, + // style: const TextStyle(fontSize: 12), + // decoration: const InputDecoration( + // labelText: "From", + // labelStyle: TextStyle(fontSize: 12, color: Colors.grey), + // floatingLabelBehavior: FloatingLabelBehavior.never, + // border: InputBorder.none, + // contentPadding: EdgeInsets.symmetric(vertical: 16), + // ), + // ), + // ), ), - ), - ), if (errorMessages["from_station"] != null) ...[ SizedBox(height: 5), // Space before error message Text( @@ -553,18 +692,45 @@ class _TrainScreenState extends State { isDesktop: isDesktop, child: SizedBox( height: 40, - child: TextField( - focusNode: _toFocusNode, - controller: _toController, - style: const TextStyle(fontSize: 12), - decoration: const InputDecoration( - labelText: "To", - labelStyle: TextStyle(fontSize: 12, color: Colors.grey), - floatingLabelBehavior: FloatingLabelBehavior.never, - border: InputBorder.none, - contentPadding: EdgeInsets.symmetric(vertical: 16), - ), - ), + child: isCountryLoading + ? Center(child: CircularProgressIndicator()) + : DropdownSearch( + selectedItem: selectedTo != null + ? countryMap[ + selectedTo] // get the display value from code + : null, + popupProps: PopupProps.menu( + showSearchBox: true, + searchFieldProps: TextFieldProps( + decoration: InputDecoration( + hintText: "Search ...", + 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( + alignment: Alignment.centerLeft, + child: Text( + selectedItem ?? "Select", + style: TextStyle(fontSize: 12), + ), + ), + onChanged: (String? newValue) { + setState(() { + selectedTo = countryMap.entries + .firstWhere((entry) => entry.value == newValue) + .key; + }); + }, + ), ), ), if (errorMessages["to_station"] != null) ...[ @@ -649,7 +815,7 @@ class _TrainScreenState extends State { CustomTextFieldItnerarySubWrapper( isFocused: _timeFocus, isDesktop: isDesktop, - width: isDesktop ? MediaQuery.of(context).size.width * 0.08 : null, + width: isDesktop ? MediaQuery.of(context).size.width * 0.1 : null, child: SizedBox( height: 40, child: GestureDetector( diff --git a/lib/Screens/itnerary_list/flight_list.dart b/lib/Screens/itnerary_list/flight_list.dart index 04235ce..434deee 100644 --- a/lib/Screens/itnerary_list/flight_list.dart +++ b/lib/Screens/itnerary_list/flight_list.dart @@ -1,28 +1,106 @@ import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; -class FlightListWidget extends StatelessWidget { +import '../../services/apiService.dart'; + +class FlightListWidget extends StatefulWidget { + final bool hasAction; + final String? tripType; final List> flightList; final Function(bool, Map, String) onOpen; final Function(Map) onDeleteFlight; final Map? apiData; - final Function(String, bool) onAddNew; final bool isViewMode; - const FlightListWidget( - {super.key, - required this.flightList, - required this.onOpen, - required this.onDeleteFlight, - required this.onAddNew, - required this.apiData, - required this.isViewMode}); + const FlightListWidget({ + Key? key, + required this.flightList, + required this.onOpen, + required this.onDeleteFlight, + required this.onAddNew, + required this.apiData, + required this.isViewMode, + required this.hasAction, + this.tripType, + }) : super(key: key); + + @override + _FlightListWidgetState createState() => _FlightListWidgetState(); +} + +class _FlightListWidgetState extends State { + ApiService apiService = ApiService(); + + // late Map countryMap; + Map countryMap = {}; + + @override + void initState() { + super.initState(); + loadCountryList(); // Call your method here + } + + Future loadCountryList() async { + final result = await apiService.fetchFlightsCountryList(); + + print("ResultCountry : $result"); + + // Create a map: Country_Code -> "City, Airport" + Map tempCountryMap = {}; + + for (var country in result) { + String city = country['City'] ?? ''; + String airport = country['Airport'] ?? ''; + String displayName = '${country['City']} - ${country['Airport']}'; + // String displayName = '${country['City']} | ${country['Airport']}'; + + tempCountryMap[country['Code']] = displayName; + } + + setState(() { + countryMap = tempCountryMap; // Update the map + }); + } @override Widget build(BuildContext context) { final isDesktop = MediaQuery.of(context).size.width > 1024; + ApiService apiService = ApiService(); + + late Map countryMap; + late List countryCodes; + + void checkClass() { + if (widget.hasAction) { + if (widget.tripType?.isNotEmpty == true) { + print("Teppp - $widget.tripType"); + widget.onAddNew("Flight", true); + } else { + showDialog( + context: context, + builder: (context) { + return AlertDialog( + title: const Text('Select Trip Type'), + content: const Text( + 'Please select a trip type before adding a flight.'), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(), + child: const Text('OK'), + ), + ], + ); + }, + ); + } + } else { + print("Teppp No - $widget.tripType"); + widget.onAddNew("Flight", true); + } + } + return Padding( padding: const EdgeInsets.all(16.0), child: Container( @@ -39,15 +117,15 @@ class FlightListWidget extends StatelessWidget { // style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), // ), MouseRegion( - cursor: isViewMode + cursor: widget.isViewMode ? SystemMouseCursors.forbidden : SystemMouseCursors.click, child: GestureDetector( - onTap: isViewMode + onTap: widget.isViewMode ? null : () { + checkClass(); print("New data"); - onAddNew("Flight", true); }, child: Row( mainAxisSize: @@ -81,10 +159,10 @@ class FlightListWidget extends StatelessWidget { Widget _buildData(BuildContext context, bool isDesktop) { List> filteredList = - flightList.where((item) => item["is_active"] == "1").toList(); + widget.flightList.where((item) => item["is_active"] == "1").toList(); print("filteredList- $filteredList"); - List visatypeList = apiData?['flight_class'] ?? []; + List visatypeList = widget.apiData?['flight_class'] ?? []; String getRequestForClass(String? specialRequestKey) { if (specialRequestKey == null) return "N/A"; @@ -174,13 +252,13 @@ class FlightListWidget extends StatelessWidget { ), Spacer(), GestureDetector( - onTap: () => onOpen(true, item, "Flight"), + onTap: () => widget.onOpen(true, item, "Flight"), child: Image.asset('assets/images/IconsImg/edit.png', width: 20, height: 15), ), SizedBox(width: 10), GestureDetector( - onTap: () => onDeleteFlight(item), + onTap: () => widget.onDeleteFlight(item), child: Image.asset('assets/images/IconsImg/delete.png', width: 20, height: 15), ), @@ -224,6 +302,13 @@ class FlightListWidget extends StatelessWidget { if (isDesktop) if (item["trips"] != null && item["trips"].isNotEmpty) ...item["trips"].map((trip) { + String fromPlaceCountry = + countryMap[trip["from_place"]?.toString()] ?? + "Unknown Country"; + String toPlaceCountry = + countryMap[trip["to_place"]?.toString()] ?? + "Unknown Country"; + return Padding( padding: const EdgeInsets.symmetric(vertical: 4.0), child: Row( @@ -240,7 +325,8 @@ class FlightListWidget extends StatelessWidget { Expanded( flex: 2, child: Text( - "${trip["from_place"]?.toString()} - ${trip["to_place"]?.toString()}", + "$fromPlaceCountry (from) - (to) $toPlaceCountry", + // "${trip["from_place"]?.toString()} - ${trip["to_place"]?.toString()}", style: TextStyle( fontSize: 12, fontWeight: FontWeight.w600, diff --git a/lib/Screens/itnerary_list/train_list.dart b/lib/Screens/itnerary_list/train_list.dart index 9f65490..b9b23d6 100644 --- a/lib/Screens/itnerary_list/train_list.dart +++ b/lib/Screens/itnerary_list/train_list.dart @@ -1,7 +1,11 @@ import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; -class TrainListWidget extends StatelessWidget { +import '../../services/apiService.dart'; + +class TrainListWidget extends StatefulWidget { + final bool hasAction; + final String? tripType; final List> trainList; final Function(bool, Map, String) onOpen; final Function(Map) onDeleteTrain; @@ -10,14 +14,83 @@ class TrainListWidget extends StatelessWidget { final Map? apiData; const TrainListWidget({ - super.key, + Key? key, required this.trainList, required this.onOpen, required this.onDeleteTrain, required this.onAddNew, required this.isViewMode, required this.apiData, - }); + required this.hasAction, + this.tripType, + }) : super(key: key); + + @override + _TrainListWidgetState createState() => _TrainListWidgetState(); +} + +class _TrainListWidgetState extends State { + ApiService apiService = ApiService(); + + // late Map countryMap; + Map countryMap = {}; + + @override + void initState() { + super.initState(); + loadCountryList(); // Call your method here + } + + void checkClass() { + if (widget.hasAction) { + if (widget.tripType?.isNotEmpty == true) { + print("Teppp - $widget.tripType"); + widget.onAddNew("Train", true); + } else { + showDialog( + context: context, + builder: (context) { + return AlertDialog( + title: const Text('Select Trip Type'), + content: const Text( + 'Please select a trip type before adding a Train.'), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(), + child: const Text('OK'), + ), + ], + ); + }, + ); + } + } else { + print("Teppp No - $widget.tripType"); + widget.onAddNew("Flight", true); + } + } + + Future loadCountryList() async { + final result = await apiService.fetchFlightsCountryList(); + + print("ResultCountry : $result"); + + // Create a map: Country_Code -> "City, Airport" + Map tempCountryMap = {}; + + for (var country in result) { + String city = country['City'] ?? ''; + String airport = country['Airport'] ?? ''; + String displayName = '${country['City']} - ${country['Airport']}'; + // String displayName = '${country['City']} | ${country['Airport']}'; + + tempCountryMap[country['Code']] = displayName; + } + + setState(() { + countryMap = tempCountryMap; // Update the map + }); + } @override Widget build(BuildContext context) { @@ -35,20 +108,20 @@ class TrainListWidget extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ MouseRegion( - cursor: isViewMode + cursor: widget.isViewMode ? SystemMouseCursors.forbidden : SystemMouseCursors.click, child: MouseRegion( - cursor: isViewMode + cursor: widget.isViewMode ? SystemMouseCursors.forbidden : SystemMouseCursors.click, child: GestureDetector( - onTap: isViewMode + onTap: widget.isViewMode ? null : () { print("New data"); - onAddNew("Train", true); + checkClass(); }, child: Row( mainAxisSize: @@ -130,10 +203,10 @@ class TrainListWidget extends StatelessWidget { Widget _buildData(BuildContext context, bool isDesktop) { List> filteredList = - trainList.where((item) => item["is_active"] == "1").toList(); + widget.trainList.where((item) => item["is_active"] == "1").toList(); // print("filteredList- $filteredList"); - List trainClassList = apiData?['train_class'] ?? []; + List trainClassList = widget.apiData?['train_class'] ?? []; String getRequestForTrainClass(String? specialRequestKey) { if (specialRequestKey == null) return "N/A"; @@ -180,6 +253,11 @@ class TrainListWidget extends StatelessWidget { itemBuilder: (context, index) { final item = filteredList[index]; + String fromPlaceCountry = + countryMap[item["from_station"].toString()] ?? "Unknown Country"; + String toPlaceCountry = + countryMap[item["to_station"].toString()] ?? "Unknown Country"; + return Container( margin: EdgeInsets.symmetric(horizontal: 8, vertical: 6), decoration: BoxDecoration( @@ -217,13 +295,13 @@ class TrainListWidget extends StatelessWidget { ), Spacer(), GestureDetector( - onTap: () => onOpen(true, item, "Train"), + onTap: () => widget.onOpen(true, item, "Train"), child: Image.asset('assets/images/IconsImg/edit.png', width: 20, height: 15), ), SizedBox(width: 10), GestureDetector( - onTap: () => onDeleteTrain(item), + onTap: () => widget.onDeleteTrain(item), child: Image.asset('assets/images/IconsImg/delete.png', width: 20, height: 15), ), @@ -284,7 +362,9 @@ class TrainListWidget extends StatelessWidget { Expanded( flex: 2, child: Text( - "${item["from_station"]!} - ${(item["to_station"])}", + "$fromPlaceCountry (from) - (to) $toPlaceCountry", + + // "${item["from_station"]!} - ${(item["to_station"])}", style: TextStyle( fontSize: 12, fontWeight: FontWeight.w600, diff --git a/lib/Screens/plans/create_plans.dart b/lib/Screens/plans/create_plans.dart index 2ac5e9f..d9525bd 100644 --- a/lib/Screens/plans/create_plans.dart +++ b/lib/Screens/plans/create_plans.dart @@ -284,6 +284,9 @@ class CreateNewPlan extends StatefulWidget { } class CreateNewPlansState extends State { + final GlobalKey dynamicItineraryKey = + GlobalKey(); + final ApiService apiService = ApiService(); final TextEditingController _tripTitleController = TextEditingController(); @@ -311,12 +314,14 @@ class CreateNewPlansState extends State { late Color layoutColorForUser; Map? apiData; // Store API response here + Map? apiDataForClass; // Store API response here List? apiCountryData; List? apiCostData; // Store API response here bool isLoading = true; // Track loading state String? TripPlanAction; bool showDomestic = false; bool showInternational = false; + bool hasAction = true; late Map costCenterMap; List costCenterIds = []; @@ -511,6 +516,12 @@ class CreateNewPlansState extends State { widget.selectedPlanData['miscellaneous'] ?? []); }); + if (widget.selectedPlanData['trip_type'] != null) { + int? tripId = + int.tryParse(widget.selectedPlanData['trip_type'].toString()); + fetchTrainFlightClass(tripId!); + } + if (widget.selectedPlanData.containsKey('plan_id') && widget.selectedPlanData['plan_id'] != null) { print("Plan ID exists: ${widget.selectedPlanData['plan_id']}"); @@ -528,15 +539,19 @@ class CreateNewPlansState extends State { if (TripPlanAction == "Plan Creation Not Allowed") { showDomestic = false; showInternational = false; + hasAction = false; } else if (TripPlanAction == "Only Domestic Plan Creation Allowed") { showDomestic = true; showInternational = false; + hasAction = true; } else if (TripPlanAction == "Only International Plan Creation Allowed") { showDomestic = false; showInternational = true; + hasAction = true; } else if (TripPlanAction == "Both Type Plan Creation Allowed") { showDomestic = true; showInternational = true; + hasAction = true; } }); } @@ -764,6 +779,55 @@ class CreateNewPlansState extends State { } } + Future fetchTrainFlightClass(int tripId) async { + final userId = (planUsrId?.toString().isNotEmpty == true) + ? planUsrId.toString() + : (planTravlrId?.toString().isNotEmpty == true) + ? planTravlrId.toString() + : ''; + + // final String apiUrldata = '$apiUrl/api/getDropdownMaster'; + final String apiUrldata = + '$apiUrl/api/getFlightAndTrainClass?user_id=$userId&trip_type=$tripId'; + + final token = await getToken(); + + if (token == null) { + throw Exception('Token not found. Please log in.'); + } + + final response = await http.get( + Uri.parse(apiUrldata), + headers: { + 'Authorization': 'Bearer $token', + 'Content-Type': 'application/json', + }, + ); + + if (response.statusCode == 200) { + try { + final data = json.decode(response.body); + print(data); + + if (!data.containsKey('data') || data['data'] is! Map) { + throw Exception( + "Invalid response format: 'data' field is missing or not a Map"); + } + + Map plansJson = + data['data']; // 'data' is a Map, not a List + setState(() { + apiDataForClass = plansJson; // Store API response in state + isLoading = false; + }); + } catch (e) { + throw Exception('Error parsing response: $e'); + } + } else { + throw Exception('Failed to load plans'); + } + } + // Handle Submit bool validateForm() { @@ -1213,7 +1277,11 @@ class CreateNewPlansState extends State { children: [ Expanded( child: DynamicItinerary( + key: dynamicItineraryKey, + hasAction: hasAction, + tripType: _selectedTripType, apiData: apiData, + apiDataForClass: apiDataForClass, apiCountryData: apiCountryData, onItineraryUpdate: handleItineraryUpdate, loginUser: selfId, @@ -1588,7 +1656,7 @@ class CreateNewPlansState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - "Purpose of Travel *", // Your label + "Purpose of Trip *", // Your label style: TextStyle( fontSize: 12, fontWeight: FontWeight.w600, @@ -1887,6 +1955,9 @@ class CreateNewPlansState extends State { : () { setState(() { _selectedTripType = "1"; + fetchTrainFlightClass(1); + + dynamicItineraryKey.currentState?.updateSelectedServices(); }); }, child: CustomTextFieldWrapper( @@ -1944,6 +2015,8 @@ class CreateNewPlansState extends State { : () { setState(() { _selectedTripType = "2"; + fetchTrainFlightClass(2); + dynamicItineraryKey.currentState?.updateSelectedServices(); }); }, child: CustomTextFieldWrapper( diff --git a/lib/Screens/plans/dynamic_itinerary_stepper.dart b/lib/Screens/plans/dynamic_itinerary_stepper.dart index a1eacb0..d6790c9 100644 --- a/lib/Screens/plans/dynamic_itinerary_stepper.dart +++ b/lib/Screens/plans/dynamic_itinerary_stepper.dart @@ -25,7 +25,10 @@ import '../itnerary_list/miscellaneous_list.dart'; import '../itnerary_list/visa_list.dart'; class DynamicItinerary extends StatefulWidget { + final bool hasAction; + final String? tripType; final Map? apiData; + final Map? apiDataForClass; final List? apiCountryData; final String? loginUser; final Function(String, List>) @@ -40,15 +43,19 @@ class DynamicItinerary extends StatefulWidget { required this.apiCountryData, required this.loginUser, required this.selectedPlanData, - required this.isViewMode}); + required this.isViewMode, + required this.hasAction, + this.tripType, + this.apiDataForClass}); @override - _DynamicItineraryState createState() => _DynamicItineraryState(); + DynamicItineraryState createState() => DynamicItineraryState(); } -class _DynamicItineraryState extends State { +class DynamicItineraryState extends State { final ApiService apiService = ApiService(); + String? _tripType; String selectedOption = ""; String selectedListOption = ""; @@ -80,6 +87,25 @@ class _DynamicItineraryState extends State { super.initState(); handleSelectedPlan(); updateSelectedServices(); + _tripType = widget.tripType; + } + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + + if (widget.tripType != _tripType) { + updateTripType(widget.tripType); + } + } + + void updateTripType(String? newTripType) { + if (_tripType != newTripType) { + setState(() { + _tripType = newTripType; + }); + updateSelectedServices(); // Refresh services based on new tripType + } } Future loadAllServices() async { @@ -126,31 +152,108 @@ class _DynamicItineraryState extends State { } } + List getAllowedServiceNames() { + if (widget.tripType == "1") { + return ["flight", "accomodation", "train", "bus"]; + } else if (widget.tripType == "2") { + return [ + "flight", + "accomodation", + "forex", + "insurance", + "visa", + "miscellaneous" + ]; + } else { + // tripType is null or not 1/2, allow everything + return []; + } + } + + // Future updateSelectedServices() async { + // await loadAllServices(); + // await loadOrgSelectedAlServices(); + // + // if (hasAnyItineraryData()) { + // print("SELCTSplanChhose: ${filledItineraryKeys}"); + // + // final selectedIds = + // selectedOrgServiceIds.map((e) => e['service_id']).toSet(); + // + // // Filter services that match filled keys (name match) and are not already selected + // final additionalServices = selectedAllServices!.where((service) { + // final name = (service['name'] ?? "").toString().toLowerCase(); + // final id = service['service_id'].toString(); + // return filledItineraryKeys.contains(name) && !selectedIds.contains(id); + // }).toList(); + // + // final originalFiltered = selectedAllServices! + // .where((service) => + // selectedIds.contains(service['service_id'].toString())) + // .toList(); + // + // // setState(() { + // // ServicesChoosed = [...originalFiltered, ...additionalServices]; + // // }); + // setState(() { + // ServicesChoosed = [...originalFiltered, ...additionalServices] + // ..sort((a, b) => (a['order'] ?? 0).compareTo(b['order'] ?? 0)); + // }); + // + // print( + // "Services chosen based on filled keys + selected: $ServicesChoosed"); + // } else { + // final selectedIds = + // selectedOrgServiceIds.map((e) => e['service_id']).toSet(); + // + // final filtered = selectedAllServices! + // .where((service) => + // selectedIds.contains(service['service_id'].toString())) + // .toList(); + // + // setState(() { + // ServicesChoosed = filtered + // ..sort((a, b) => (a['order'] ?? 0).compareTo(b['order'] ?? 0)); + // }); + // + // // setState(() { + // // ServicesChoosed = filtered; + // // }); + // + // print("Filtered Selected Services Chooesed: $ServicesChoosed"); + // } + // } + Future updateSelectedServices() async { await loadAllServices(); await loadOrgSelectedAlServices(); + final allowedServiceNames = getAllowedServiceNames(); + if (hasAnyItineraryData()) { - print("SELCTSplanChhose: ${filledItineraryKeys}"); + print("SELCTSplanChhose: $filledItineraryKeys"); final selectedIds = selectedOrgServiceIds.map((e) => e['service_id']).toSet(); - // Filter services that match filled keys (name match) and are not already selected final additionalServices = selectedAllServices!.where((service) { final name = (service['name'] ?? "").toString().toLowerCase(); final id = service['service_id'].toString(); - return filledItineraryKeys.contains(name) && !selectedIds.contains(id); + final isNameAllowed = + allowedServiceNames.isEmpty || allowedServiceNames.contains(name); + return filledItineraryKeys.contains(name) && + !selectedIds.contains(id) && + isNameAllowed; }).toList(); - final originalFiltered = selectedAllServices! - .where((service) => - selectedIds.contains(service['service_id'].toString())) - .toList(); + final originalFiltered = selectedAllServices!.where((service) { + final name = (service['name'] ?? "").toString().toLowerCase(); + final id = service['service_id'].toString(); + final isNameAllowed = + allowedServiceNames.isEmpty || allowedServiceNames.contains(name); + return selectedIds.contains(id) && isNameAllowed; + }).toList(); - // setState(() { - // ServicesChoosed = [...originalFiltered, ...additionalServices]; - // }); setState(() { ServicesChoosed = [...originalFiltered, ...additionalServices] ..sort((a, b) => (a['order'] ?? 0).compareTo(b['order'] ?? 0)); @@ -162,21 +265,20 @@ class _DynamicItineraryState extends State { final selectedIds = selectedOrgServiceIds.map((e) => e['service_id']).toSet(); - final filtered = selectedAllServices! - .where((service) => - selectedIds.contains(service['service_id'].toString())) - .toList(); + final filtered = selectedAllServices!.where((service) { + final name = (service['name'] ?? "").toString().toLowerCase(); + final isNameAllowed = + allowedServiceNames.isEmpty || allowedServiceNames.contains(name); + return selectedIds.contains(service['service_id'].toString()) && + isNameAllowed; + }).toList(); setState(() { ServicesChoosed = filtered ..sort((a, b) => (a['order'] ?? 0).compareTo(b['order'] ?? 0)); }); - // setState(() { - // ServicesChoosed = filtered; - // }); - - print("Filtered Selected Services Chooesed: $ServicesChoosed"); + print("Filtered Selected Services Chosen: $ServicesChoosed"); } } @@ -408,6 +510,8 @@ class _DynamicItineraryState extends State { switch (selectedListOption) { case "Train": selectedListWidget = TrainListWidget( + hasAction: widget.hasAction, + tripType: widget.tripType, trainList: itineraryData["Train"]!, onOpen: handleEdit, onAddNew: handlecreateNewPlan, @@ -491,6 +595,8 @@ class _DynamicItineraryState extends State { case "Flight": default: selectedListWidget = FlightListWidget( + hasAction: widget.hasAction, + tripType: widget.tripType, flightList: itineraryData["Flight"]!, onOpen: handleEdit, onAddNew: handlecreateNewPlan, @@ -505,6 +611,7 @@ class _DynamicItineraryState extends State { selectedWidget = TrainScreen( onClose: handleClose, apiData: widget.apiData, + apiDataForClass: widget.apiDataForClass, loginUser: widget.loginUser, onSavetrain: (data) => handleItineraryUpdate("Train", data), selectedItem: selectedItem); @@ -582,10 +689,13 @@ class _DynamicItineraryState extends State { case "Flight": default: selectedWidget = FlightScreen( + hasAction: widget.hasAction, + tripType: widget.tripType, onClose: handleClose, loginUser: widget.loginUser, onSaveFlight: (data) => handleItineraryUpdate("Flight", data), apiData: widget.apiData, + apiDataForClass: widget.apiDataForClass, selectedItem: selectedItem, flightData: itineraryData["Flight"]!, ); diff --git a/lib/Screens/plans/list_plans.dart b/lib/Screens/plans/list_plans.dart index e7ef81f..867b691 100644 --- a/lib/Screens/plans/list_plans.dart +++ b/lib/Screens/plans/list_plans.dart @@ -36,6 +36,10 @@ class _ListPlansState extends State { Color? layoutColor; Color? bodyColor; + List allPlans = []; + List filteredPlans = []; + TextEditingController searchController = TextEditingController(); + @override void initState() { super.initState(); @@ -44,11 +48,33 @@ class _ListPlansState extends State { WidgetsBinding.instance.addPostFrameCallback((_) { initializeData(); loadInitialData(); + + futurePlans.then((plans) { + setState(() { + allPlans = plans; + filteredPlans = plans; + }); + }); }); // futurePlans = fetchPlans(); } + void filterPlans(String query) { + final lowerQuery = query.toLowerCase(); + setState(() { + filteredPlans = allPlans.where((plan) { + return (plan.planId?.toLowerCase().contains(lowerQuery) ?? false) || + (plan.tripTitle?.toLowerCase().contains(lowerQuery) ?? false) || + (plan.userName?.toLowerCase().contains(lowerQuery) ?? false) || + (plan.travellerName?.toLowerCase().contains(lowerQuery) ?? false) || + (plan.tripType?.toLowerCase().contains(lowerQuery) ?? false) || + (plan.createdOn?.toLowerCase().contains(lowerQuery) ?? false) || + (plan.statusValue?.toLowerCase().contains(lowerQuery) ?? false); + }).toList(); + }); + } + void loadInitialData() async { String? layoutString = await getLayoutColor(); String? bodyStringColor = await getBodyColor(); @@ -322,7 +348,7 @@ class _ListPlansState extends State { children: [ Row( children: [ - const Text('Plans List', + const Text('Trip List', style: TextStyle( fontFamily: "Archivo", fontSize: 16, @@ -334,9 +360,9 @@ class _ListPlansState extends State { Spacer(), Container( width: MediaQuery.of(context).size.width * 0.2, - // or use Flexible child: TextField( - onChanged: (query) {}, + controller: searchController, + onChanged: filterPlans, decoration: InputDecoration( hintText: "Search for a plan", hintStyle: @@ -351,7 +377,6 @@ class _ListPlansState extends State { color: Colors.grey.shade300, width: 0.5), ), focusedBorder: OutlineInputBorder( - // borderRadius: BorderRadius.circular(8), borderSide: BorderSide(color: Colors.blueAccent, width: 1), ), @@ -510,7 +535,7 @@ class _ListPlansState extends State { ), columns: const [ DataColumn( - label: Text('Plan Id', + label: Text('Trip Id', style: TextStyle( color: Color(0xFF9E9DBD), fontSize: 14, @@ -523,7 +548,7 @@ class _ListPlansState extends State { fontFamily: "Archivo", fontWeight: FontWeight.bold))), DataColumn( - label: Text('UserName', + label: Text('Traveller', style: TextStyle( color: Color(0xFF9E9DBD), fontFamily: "Archivo", diff --git a/lib/routes/custom_appBar.dart b/lib/routes/custom_appBar.dart index 1f608a5..9b775a0 100644 --- a/lib/routes/custom_appBar.dart +++ b/lib/routes/custom_appBar.dart @@ -162,8 +162,8 @@ class _CustomAppBarState extends State { child: Image.network( selectedOrg!['logo'], width: 100, - height: 50, - fit: BoxFit.contain, + height: 80, + // fit: BoxFit.contain, errorBuilder: (context, error, stackTrace) { return const CircleAvatar( radius: 20, @@ -253,102 +253,119 @@ class _CustomAppBarState extends State { Padding( padding: EdgeInsets.symmetric( horizontal: MediaQuery.of(context).size.width * 0.05), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + child: Row( children: [ - Text( - userData?["name"] ?? "N/A", - style: TextStyle( - fontSize: 13, - fontWeight: FontWeight.w600, - fontFamily: "Archivo", - // color: Color(0xFF12B24B), - color: layoutColor, - ), - ), - SizedBox( - height: 1, - ), - Row( - children: [ - // if (userData?["role"] != "User") - Builder( - builder: (context) => PopupMenuButton( - color: Colors.white, - padding: EdgeInsets.zero, - // icon: const Icon(Icons.arrow_drop_down, - // size: 20, color: Colors.black87), - offset: const Offset(0, 50), // 👈 shift it 50 pixels down - onSelected: (String value) { - switch (value) { - case '/OrganizationSetup': - context.go('/OrganizationSetup'); - break; - case '/listUser': - context.go('/listUser'); - break; - case '/group': - context.go('/group'); - break; - case '/PolicyList': - context.go('/PolicyList'); - case '/CreateUserDetails': - context.go( - "/CreateUserDetails", - extra: { - "selectedUser": userDetails, - "isEditProfile": true, - "isViewMode": true - }, - ); - case '/logout': - context.go('/'); - break; - } - }, + // if (userData?["role"] != "User") + Builder( + builder: (context) => PopupMenuButton( + color: Colors.white, + padding: EdgeInsets.zero, + // icon: const Icon(Icons.arrow_drop_down, + // size: 20, color: Colors.black87), + offset: const Offset(0, 50), // 👈 shift it 50 pixels down + onSelected: (String value) { + switch (value) { + case '/OrganizationSetup': + context.go('/OrganizationSetup'); + break; + case '/listUser': + context.go('/listUser'); + break; + case '/group': + context.go('/group'); + break; + case '/PolicyList': + context.go('/PolicyList'); + case '/CreateUserDetails': + context.go( + "/CreateUserDetails", + extra: { + "selectedUser": userDetails, + "isEditProfile": true, + "isViewMode": true + }, + ); + case '/logout': + context.go('/'); + break; + } + }, - // itemBuilder: (BuildContext context) => - // menuItems.map(buildMenuItem).toList(), + // itemBuilder: (BuildContext context) => + // menuItems.map(buildMenuItem).toList(), - itemBuilder: (BuildContext context) { - final isUser = userData?["role"] == "User"; - final filteredItems = isUser - ? menuItems - .where((item) => - item['value'] == '/CreateUserDetails' || - item['value'] == '/logout') - .toList() - : menuItems; + itemBuilder: (BuildContext context) { + final isUser = userData?["role"] == "User"; + final filteredItems = isUser + ? menuItems + .where((item) => + item['value'] == '/CreateUserDetails' || + item['value'] == '/logout') + .toList() + : menuItems; - return filteredItems.map(buildMenuItem).toList(); - }, - child: MouseRegion( - cursor: SystemMouseCursors.click, - child: Row( - mainAxisSize: MainAxisSize.min, + // Create a new list starting with role display and divider + return [ + PopupMenuItem( + enabled: false, // ❌ Not clickable + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - userData?["role"] ?? "Role", + userData?["role"] ?? '', style: const TextStyle( - fontSize: 11, - fontWeight: FontWeight.w300, - fontFamily: "Archivo", + fontSize: 12, + fontWeight: FontWeight.bold, color: Colors.black, ), ), - const Icon( - Icons.arrow_drop_down, - size: 20, - color: Colors.black87, - ), + const Divider(), // 👈 Divider after role ], ), ), + ...filteredItems + .map(buildMenuItem) + .toList(), // 👈 then normal items + ]; + }, + + // itemBuilder: (BuildContext context) { + // final isUser = userData?["role"] == "User"; + // final filteredItems = isUser + // ? menuItems + // .where((item) => + // item['value'] == '/CreateUserDetails' || + // item['value'] == '/logout') + // .toList() + // : menuItems; + // + // return filteredItems.map(buildMenuItem).toList(); + // }, + child: MouseRegion( + cursor: SystemMouseCursors.click, + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + userData?["name"] ?? "N/A", + style: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.w500, + fontFamily: "Roboto", + color: Colors.black, + ), + ), + const Icon( + Icons.arrow_drop_down, + size: 20, + color: Colors.black87, + ), + ], ), ), - const SizedBox(width: 8), - ], + ), ), + const SizedBox(width: 8), ], ), ), diff --git a/lib/services/apiService.dart b/lib/services/apiService.dart index cba8872..23b6857 100644 --- a/lib/services/apiService.dart +++ b/lib/services/apiService.dart @@ -451,7 +451,9 @@ class ApiService { // Flight From - To Future> fetchFlightsCountryList() async { - final String apiUrldata = '$apiUrl/api/getAirportCodeMaster'; + final String apiUrldata = + '$apiUrl/api/getAirportCodeMaster?limit=1000&offset=0'; + final token = await getToken(); if (token == null) {