diff --git a/lib/Screens/allTrips/travel_agent_list.dart b/lib/Screens/allTrips/travel_agent_list.dart index 66243f3..aa31d87 100644 --- a/lib/Screens/allTrips/travel_agent_list.dart +++ b/lib/Screens/allTrips/travel_agent_list.dart @@ -817,7 +817,7 @@ class _TravelAgentListPlansState extends State { Navigator.pop( context, ); // Close popup manually - ApiService.viewPlan( + ApiService.viewPlanTravelAgent( context, plan.planId, isViewMode: diff --git a/lib/Screens/itnerary/accomodations.dart b/lib/Screens/itnerary/accomodations.dart index 6187eb9..9cef823 100644 --- a/lib/Screens/itnerary/accomodations.dart +++ b/lib/Screens/itnerary/accomodations.dart @@ -1,16 +1,20 @@ +import 'package:dropdown_search/dropdown_search.dart'; import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:intl/intl.dart'; import 'package:responsive_builder/responsive_builder.dart'; import '../../services/apiService.dart'; +import '../../widgets/custom_confirmation_dialog.dart'; import '../../widgets/custom_text_field.dart'; import '../../widgets/custom_text_itnerary_sub.dart'; class AccomodationScreen extends StatefulWidget { final List> flightData; - final Function(bool) onClose; // Callback function + final Map? apiDataForClass; + final Function(String, bool) onClose; // Callback function final Function(Map) onSaveAccomadation; + final void Function(bool)? onExceptionalClass; final Map? selectedItem; final String? loginUser; final String? tripType; @@ -22,6 +26,8 @@ class AccomodationScreen extends StatefulWidget { required this.loginUser, required this.flightData, this.tripType, + this.apiDataForClass, + this.onExceptionalClass, }); @override @@ -34,6 +40,10 @@ class _AccomodationScreenState extends State { // late Map countryMap; Map countryMap = {}; + String? selectedClass; + String? exceptionalClass = "0"; + bool hasExceptionalClass = false; + late ValueNotifier flightFirstTripDateNotifier; late ValueNotifier flightLastTripDateNotifier; late ValueNotifier flightFirstToDestinationNotifier; @@ -42,6 +52,7 @@ class _AccomodationScreenState extends State { final FocusNode _destinationFocusNode = FocusNode(); final FocusNode _hotelNameFocusNode = FocusNode(); + final FocusNode _CategoryFocusNode = FocusNode(); final FocusNode _checkInFocusNode = FocusNode(); final FocusNode _checkInTimeFocusNode = FocusNode(); final FocusNode _checkOutFocusNode = FocusNode(); @@ -58,6 +69,7 @@ class _AccomodationScreenState extends State { bool _destinationFocused = false; bool _isHotelNameFocused = false; + bool _CategoryFocused = false; bool _checkInFocus = false; bool _checkInTimeFocus = false; bool _checkOutFocus = false; @@ -78,11 +90,13 @@ class _AccomodationScreenState extends State { Map data = { "destination_city": _destinationController.text, "hotel_name": _hotelNameController.text, + "class": selectedClass, "checkin_date": _checkInController.text, "checkin_time": _checkInTimeController.text, "checkout_date": _checkOutController.text, "checkout_time": _checkOutTimeController.text, "comments": _commentsController.text, + "is_this_exceptional": exceptionalClass, "created_by": widget.loginUser, "updated_by": widget.loginUser, }; @@ -191,6 +205,16 @@ class _AccomodationScreenState extends State { _checkOutTimeController.text = '12:00'; // 12 PM } }); + + if (widget.selectedItem != null && + widget.selectedItem!["is_this_exceptional"] != null) { + exceptionalClass = widget.selectedItem!["is_this_exceptional"].toString(); + } + + // Set the selected value if available + if (widget.selectedItem != null && widget.selectedItem!["class"] != null) { + selectedClass = widget.selectedItem!["class"].toString(); + } } @override @@ -365,9 +389,13 @@ class _AccomodationScreenState extends State { return; // Stop execution if validation fails } else { widget.onSaveAccomadation(accomadationData); + + // if (hasExceptionalClass) { + // widget.onExceptionalClass?.call(true); // or false + // } } - widget.onClose(false); // Close screen after saving + widget.onClose("Accomodation", false); // Close screen after saving } @override @@ -421,6 +449,34 @@ class _AccomodationScreenState extends State { } List _buildFirstRow(isDesktop) { + List purposeList = widget.apiDataForClass?['hotel_class'] ?? []; + + 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 + // selectedClass ??= + // dropdownItems.isNotEmpty ? dropdownItems.first.value : null; + return [ // Column( // children: [ @@ -493,13 +549,13 @@ class _AccomodationScreenState extends State { ), ), SizedBox(height: 5), - CustomTextFieldWrapper( + CustomTextFieldItnerarySubWrapper( isFocused: _isHotelNameFocused, isDesktop: isDesktop, - width: - isDesktop - ? MediaQuery.of(context).size.width * 0.34 - : MediaQuery.of(context).size.width * 0.66, + // width: + // isDesktop + // ? MediaQuery.of(context).size.width * 0.34 + // : MediaQuery.of(context).size.width * 0.66, child: SizedBox( height: 40, child: TextField( @@ -525,6 +581,147 @@ class _AccomodationScreenState extends State { ], ], ), + if (isDesktop) Spacer() else SizedBox(height: 8), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Category *", + style: GoogleFonts.poppins( + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74), + ), + ), + SizedBox(height: 5), + CustomTextFieldItnerarySubWrapper( + // isFocused: focusStates["_class${index}Focused"] ?? false, + isFocused: false, + padding: const EdgeInsets.symmetric(horizontal: 0), + isDesktop: isDesktop, + child: SizedBox( + height: 40, + width: double.infinity, + child: DropdownSearch>( + key: + selectedClass == null + ? UniqueKey() + : ValueKey(selectedClass), + + items: purposeList.cast>(), + // selectedItem: purposeList.firstWhere( + // (item) => item['dropdown_key'] == selectedClass, + // orElse: () => {}, + // ), + selectedItem: + selectedClass != null + ? purposeList.firstWhere( + (item) => item['dropdown_key'] == selectedClass, + orElse: () => {}, + ) + : null, + + itemAsString: (item) => item['dropdown_value'] ?? '', + popupProps: PopupProps.menu( + showSearchBox: false, + fit: FlexFit.loose, + menuProps: const MenuProps(backgroundColor: Colors.white), + itemBuilder: (context, item, isSelected) { + print("categotey - $item "); + final bool isNotAllowed = item['is_allowed'] == 'No'; + return Padding( + padding: const EdgeInsets.symmetric( + horizontal: 10, + vertical: 5, + ), + child: Text( + item['dropdown_value'] ?? '', + style: GoogleFonts.poppins( + fontSize: 12, + color: isNotAllowed ? Colors.redAccent : Colors.black, + ), + ), + ); + }, + ), + dropdownDecoratorProps: const DropDownDecoratorProps( + dropdownSearchDecoration: InputDecoration( + border: InputBorder.none, + contentPadding: EdgeInsets.symmetric( + horizontal: 10, + vertical: 10, + ), + ), + ), + dropdownBuilder: (context, selectedItem) { + if (selectedItem == null || selectedItem.isEmpty) { + return Text( + "Select ", + style: GoogleFonts.poppins( + color: Colors.grey, + fontSize: 13, + ), + ); + } + return Text( + selectedItem['dropdown_value'] ?? '', + style: GoogleFonts.poppins( + fontSize: 12, + // color: Colors.red, + color: Colors.black, + ), + ); + }, + onChanged: + purposeList.isNotEmpty + ? (Map? newValue) async { + if (newValue != null && + newValue['is_allowed'] == 'No') { + final confirm = await showNotAllowedDialog( + context, + message: + "You are not entitled for this service. If you wish to continue with this service, you may require another approval.", + ); + + if (confirm) { + setState(() { + exceptionalClass = "1"; + selectedClass = newValue['dropdown_key']; + }); + print("Selected Purpose: ${selectedClass}"); + } else { + setState(() { + exceptionalClass = "0"; + selectedClass = + null; // ❌ Clear selection if user cancels + }); + print( + "selectedItem resolved to: ${selectedClass}", + ); + } + + return; + } else if (newValue != null && + newValue['is_allowed'] == 'yes') { + setState(() { + exceptionalClass = "0"; + selectedClass = newValue?['dropdown_key']; + print("Selected Purpose: ${selectedClass}"); + }); + } + } + : null, + ), + ), + ), + if (errorMessages["class"] != null) ...[ + SizedBox(height: 5), // Space before error message + Text("Required", style: TextStyle(color: Colors.red, fontSize: 12)), + ], + ], + ), + + // if (isDesktop) Spacer() else SizedBox(height: 8), ]; } @@ -1025,7 +1222,8 @@ class _AccomodationScreenState extends State { // Close Button ElevatedButton( onPressed: () { - widget.onClose(false); // Close the dialog or screen + widget.onClose("Accomodation", false); + // widget.onClose(false); // Close the dialog or screen }, style: ElevatedButton.styleFrom( backgroundColor: Colors.grey[400], // Light grey color diff --git a/lib/Screens/itnerary/bus.dart b/lib/Screens/itnerary/bus.dart index e36337f..faadf5e 100644 --- a/lib/Screens/itnerary/bus.dart +++ b/lib/Screens/itnerary/bus.dart @@ -8,7 +8,7 @@ import '../../widgets/custom_text_itnerary_sub.dart'; class BusScreen extends StatefulWidget { final Map? apiData; - final Function(bool) onClose; + final Function(String, bool) onClose; final Function(Map) onSaveBus; final Map? selectedItem; final String? loginUser; @@ -190,8 +190,8 @@ class _BusScreenState extends State { } else { widget.onSaveBus(busData); } - - widget.onClose(false); // Close screen after saving + widget.onClose("Bus", false); + // widget.onClose(false); // Close screen after saving } @override @@ -678,7 +678,8 @@ class _BusScreenState extends State { // Close Button ElevatedButton( onPressed: () { - widget.onClose(false); // Close the dialog or screen + widget.onClose("Bus", false); + // widget.onClose(false); // Close the dialog or screen }, style: ElevatedButton.styleFrom( backgroundColor: Colors.grey[400], // Light grey color diff --git a/lib/Screens/itnerary/flights.dart b/lib/Screens/itnerary/flights.dart index 7e9d9b6..8c858e2 100644 --- a/lib/Screens/itnerary/flights.dart +++ b/lib/Screens/itnerary/flights.dart @@ -6,6 +6,7 @@ import 'package:intl/intl.dart'; import 'package:responsive_builder/responsive_builder.dart'; import '../../utils/auth_utils.dart'; +import '../../widgets/custom_confirmation_dialog.dart'; import '../../widgets/custom_radio_button.dart'; import '../../widgets/custom_text_field.dart'; import '../../widgets/custom_text_itnerary_sub.dart'; @@ -17,7 +18,7 @@ class FlightScreen extends StatefulWidget { final Map? apiData; final Map? apiDataForClass; final String? loginUser; - final Function(bool) onClose; + final Function(String, bool) onClose; final Function(Map) onSaveFlight; final Map? selectedItem; final ValueNotifier tripTypeNotifier; @@ -60,6 +61,8 @@ class FlightScreenState extends State { String? selectedTripType; Map selectedClasses = {}; // Store class selection for each trip + Map exceptionalClass = + {}; // 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; @@ -409,6 +412,7 @@ class FlightScreenState extends State { for (int i = 1; i <= rowCount; i++) { final trip = { "class": selectedClasses[i], + "is_this_exceptional": exceptionalClass[i], // "from_place": countryMap[selectedFrom[i]], "from_place": selectedFrom[i], "to_place": selectedTo[i], @@ -505,6 +509,7 @@ class FlightScreenState extends State { int index = i + 1; // Use 1-based indexing to match the form selectedClasses[index] = trip["class"].toString(); + exceptionalClass[index] = trip["is_this_exceptional"].toString(); selectedFrom[index] = trip["from_place"].toString(); selectedTo[index] = trip["to_place"].toString(); // textControllers["_from${index}Controller"] = @@ -545,7 +550,8 @@ class FlightScreenState extends State { } widget.onSaveFlight(flightsData); // Send object to parent - widget.onClose(false); + // widget.onClose(false); + widget.onClose("Flight", false); print("FlightData - $flightsData"); } @@ -789,6 +795,16 @@ class FlightScreenState extends State { selectedClasses = updatedClasses; // Update the map + Map updatedExptionalClasses = {}; + newIndex = 1; + + for (int i = 1; i <= exceptionalClass.length; i++) { + if (i == index) continue; // Skip the one being deleted + updatedExptionalClasses[newIndex] = exceptionalClass[i]; + newIndex++; + } + exceptionalClass = updatedExptionalClasses; + // Shift the selectedFrom map BEFORE removing the index Map updatedFrom = {}; newIndex = 1; @@ -1320,8 +1336,8 @@ class FlightScreenState extends State { } // Default selected value - selectedClasses[index] ??= - dropdownItems.isNotEmpty ? dropdownItems.first.value : null; + // selectedClasses[index] ??= + // dropdownItems.isNotEmpty ? dropdownItems.first.value : null; // ------------------------------------------------- DateTime? _selectedCheckOutDate; @@ -2046,16 +2062,30 @@ class FlightScreenState extends State { }, child: DropdownSearch>( items: purposeList.cast>(), - selectedItem: purposeList.firstWhere( - (item) => item['dropdown_key'] == selectedClasses[index], - orElse: () => {}, - ), + key: + selectedClasses[index] == null + ? UniqueKey() + : ValueKey(selectedClasses[index]), + // selectedItem: purposeList.firstWhere( + // (item) => item['dropdown_key'] == selectedClasses[index], + // orElse: () => {}, + // ), + selectedItem: + selectedClasses[index] != null + ? purposeList.firstWhere( + (item) => + item['dropdown_key'] == + selectedClasses[index], + orElse: () => {}, + ) + : null, itemAsString: (item) => item['dropdown_value'] ?? '', popupProps: PopupProps.menu( showSearchBox: false, fit: FlexFit.loose, menuProps: const MenuProps(backgroundColor: Colors.white), itemBuilder: (context, item, isSelected) { + final bool isNotAllowed = item['is_allowed'] == 'No'; return Padding( padding: const EdgeInsets.symmetric( horizontal: 10, @@ -2065,7 +2095,10 @@ class FlightScreenState extends State { item['dropdown_value'] ?? '', style: GoogleFonts.poppins( fontSize: 12, - color: Colors.black, + color: + isNotAllowed + ? Colors.redAccent + : Colors.black, ), ), ); @@ -2124,14 +2157,67 @@ class FlightScreenState extends State { }, onChanged: purposeList.isNotEmpty - ? (Map? newValue) { - setState(() { - selectedClasses[index] = - newValue?['dropdown_key']; - print( - "Selected Class: ${selectedClasses[index]}", + ? (Map? newValue) async { + // if (newValue != null && + // newValue['is_allowed'] == 'No') { + // print('not allowed'); + // + // exceptionalClass[index] = "1"; + // } else if (newValue != null && + // newValue['is_allowed'] == 'yes') { + // print('not allowed'); + // + // exceptionalClass[index] = "0"; + // } + // + // setState(() { + // selectedClasses[index] = + // newValue?['dropdown_key']; + // print( + // "Selected Class: ${selectedClasses[index]}", + // ); + // }); + + if (newValue != null && + newValue['is_allowed'] == 'No') { + final confirm = await showNotAllowedDialog( + context, + message: + "You are not entitled for this service. If you wish to continue with this service, you may require another approval.", ); - }); + + if (confirm) { + setState(() { + exceptionalClass[index] = "1"; + selectedClasses[index] = + newValue['dropdown_key']; + }); + print( + "Selected Purpose: ${selectedClasses[index]}", + ); + } else { + setState(() { + exceptionalClass[index] = "0"; + selectedClasses[index] = + null; // ❌ Clear selection if user cancels + }); + print( + "selectedItem resolved to: ${selectedClasses[index]}", + ); + } + + return; + } else if (newValue != null && + newValue['is_allowed'] == 'yes') { + setState(() { + exceptionalClass[index] = "0"; + selectedClasses[index] = + newValue?['dropdown_key']; + print( + "Selected Purpose: ${selectedClasses[index]}", + ); + }); + } } : null, ), @@ -2690,7 +2776,8 @@ class FlightScreenState extends State { // Close Button ElevatedButton( onPressed: () { - widget.onClose(false); // Close the dialog or screen + widget.onClose("Flight", false); + // widget.onClose(false); // Close the dialog or screen }, style: ElevatedButton.styleFrom( backgroundColor: Colors.grey[400], // Light grey color diff --git a/lib/Screens/itnerary/forex.dart b/lib/Screens/itnerary/forex.dart index 5a8c4f7..a174c32 100644 --- a/lib/Screens/itnerary/forex.dart +++ b/lib/Screens/itnerary/forex.dart @@ -18,7 +18,7 @@ import 'package:http/http.dart' as http; class ForexScreen extends StatefulWidget { final List> flightData; final Map? apiData; - final Function(bool) onClose; + final Function(String, bool) onClose; final Map? selectedItem; final List? apiCountryData; final Function(Map) onSaveForex; @@ -366,8 +366,8 @@ class _ForexScreenState extends State { } else { widget.onSaveForex(forexData); // Send object to parent } - - widget.onClose(false); // Close screen after saving + widget.onClose("Forex", false); + // widget.onClose(false); // Close screen after saving } DateTime? _parseDate(String date) { @@ -2294,7 +2294,7 @@ class _ForexScreenState extends State { // Close Button ElevatedButton( onPressed: () { - widget.onClose(false); // Close the dialog or screen + widget.onClose("Forex", false); // Close the dialog or screen }, style: ElevatedButton.styleFrom( backgroundColor: Colors.grey[400], // Light grey color diff --git a/lib/Screens/itnerary/insurance.dart b/lib/Screens/itnerary/insurance.dart index 3e19de0..f2e9a92 100644 --- a/lib/Screens/itnerary/insurance.dart +++ b/lib/Screens/itnerary/insurance.dart @@ -11,7 +11,7 @@ import '../../widgets/custom_text_itnerary_sub.dart'; class InsuranceScreen extends StatefulWidget { final List> flightData; final Map? apiData; - final Function(bool) onClose; + final Function(String, bool) onClose; final Function(Map) onSaveInsurance; final Map? selectedItem; final String? loginUser; @@ -290,8 +290,8 @@ class _InsuranceScreenState extends State { } else { widget.onSaveInsurance(InsuranceData); } - - widget.onClose(false); // Close screen after saving + widget.onClose("Insurance", false); + // widget.onClose(false); // Close screen after saving } DateTime? _parseDate(String date) { @@ -1371,7 +1371,7 @@ class _InsuranceScreenState extends State { // Close Button ElevatedButton( onPressed: () { - widget.onClose(false); // Close the dialog or screen + widget.onClose("Insurance", false); // Close the dialog or screen }, style: ElevatedButton.styleFrom( backgroundColor: Colors.grey[400], // Light grey color diff --git a/lib/Screens/itnerary/miscellaneous.dart b/lib/Screens/itnerary/miscellaneous.dart index 409bd3d..201fb0d 100644 --- a/lib/Screens/itnerary/miscellaneous.dart +++ b/lib/Screens/itnerary/miscellaneous.dart @@ -9,7 +9,8 @@ import '../../widgets/custom_text_itnerary_sub.dart'; class MiscellaneousScreen extends StatefulWidget { final Map? apiData; - final Function(bool) onClose; + final Function(String, bool) onClose; + final Function(Map) onSaveMiscellaneous; final Map? selectedItem; final int? selectedIndex; @@ -137,7 +138,8 @@ class _MiscellaneousScreenState extends State { widget.onSaveMiscellaneous(miscellaneousData); // Send object to parent } - widget.onClose(false); // Close screen after saving + widget.onClose("Miscellaneous", false); // Close screen after saving + // widget.onClose(false); // Close screen after saving // Clear only if this is a new entry // if (widget.selectedItem == null) { // _commentsController.clear(); @@ -281,9 +283,10 @@ class _MiscellaneousScreenState extends State { CustomTextFieldItnerarySubWrapper( isFocused: _isHotelNameFocused, isDesktop: isDesktop, - width: isDesktop - ? MediaQuery.of(context).size.width * 0.34 - : MediaQuery.of(context).size.width * 0.66, + width: + isDesktop + ? MediaQuery.of(context).size.width * 0.34 + : MediaQuery.of(context).size.width * 0.66, child: SizedBox( height: 40, width: double.infinity, @@ -291,7 +294,7 @@ class _MiscellaneousScreenState extends State { // focusNode: _taxiReqFocusNode, items: purposeList.cast>(), selectedItem: purposeList.firstWhere( - (item) => item['dropdown_key'] == selectedSpecialType, + (item) => item['dropdown_key'] == selectedSpecialType, orElse: () => {}, ), itemAsString: (item) => item['dropdown_value'] ?? '', @@ -328,31 +331,23 @@ class _MiscellaneousScreenState extends State { if (selectedItem == null || selectedItem.isEmpty) { return Text( "Select ", - style: GoogleFonts.poppins( - color: Colors.grey, - fontSize: 13, - ), + style: GoogleFonts.poppins(color: Colors.grey, fontSize: 13), ); } return Text( selectedItem['dropdown_value'] ?? '', - style: GoogleFonts.poppins( - fontSize: 12, - color: Colors.black, - ), + style: GoogleFonts.poppins(fontSize: 12, color: Colors.black), ); }, onChanged: - purposeList.isNotEmpty - ? (newValue) { - setState(() { - selectedSpecialType = newValue as String?; - }); - print( - "Updating form data: -> ${newValue ?? ""}", - ); - } - : null, + purposeList.isNotEmpty + ? (newValue) { + setState(() { + selectedSpecialType = newValue as String?; + }); + print("Updating form data: -> ${newValue ?? ""}"); + } + : null, ), ), ), @@ -421,7 +416,8 @@ class _MiscellaneousScreenState extends State { ElevatedButton( onPressed: () { _commentsController.clear(); - widget.onClose(false); // Close the dialog or screen + widget.onClose("Miscellaneous", false); + // widget.onClose(false); // Close the dialog or screen }, style: ElevatedButton.styleFrom( backgroundColor: Colors.grey[400], // Light grey color diff --git a/lib/Screens/itnerary/taxi.dart b/lib/Screens/itnerary/taxi.dart index 573561c..c26516a 100644 --- a/lib/Screens/itnerary/taxi.dart +++ b/lib/Screens/itnerary/taxi.dart @@ -10,7 +10,7 @@ import '../../widgets/custom_text_itnerary_sub.dart'; class TaxiScreen extends StatefulWidget { final Map? apiData; - final Function(bool) onClose; + final Function(String, bool) onClose; final Function(Map) onSavetaxi; final Map? selectedItem; final String? loginUser; @@ -199,8 +199,8 @@ class _TaxiScreenState extends State { } else { widget.onSavetaxi(taxiData); } - - widget.onClose(false); // Close screen after saving + widget.onClose("Taxi", false); + // widget.onClose(false); // Close screen after saving } @override @@ -404,7 +404,7 @@ class _TaxiScreenState extends State { // focusNode: _taxiReqFocusNode, items: purposeList.cast>(), selectedItem: purposeList.firstWhere( - (item) => item['dropdown_key'] == selectedCarType, + (item) => item['dropdown_key'] == selectedCarType, orElse: () => {}, ), itemAsString: (item) => item['dropdown_value'] ?? '', @@ -456,16 +456,14 @@ class _TaxiScreenState extends State { ); }, onChanged: - purposeList.isNotEmpty - ? (newValue) { - setState(() { - selectedCarType = newValue as String?; - }); - print( - "Updating form data: -> ${newValue ?? ""}", - ); - } - : null, + purposeList.isNotEmpty + ? (newValue) { + setState(() { + selectedCarType = newValue as String?; + }); + print("Updating form data: -> ${newValue ?? ""}"); + } + : null, ), ), ), @@ -544,9 +542,9 @@ class _TaxiScreenState extends State { isFocused: _taxiReqFocused, isDesktop: isDesktop, width: - isDesktop - ? MediaQuery.of(context).size.width * 0.34 - : MediaQuery.of(context).size.width * 0.66, + isDesktop + ? MediaQuery.of(context).size.width * 0.34 + : MediaQuery.of(context).size.width * 0.66, child: SizedBox( height: 40, width: double.infinity, @@ -554,7 +552,7 @@ class _TaxiScreenState extends State { // focusNode: _taxiReqFocusNode, items: purposeList.cast>(), selectedItem: purposeList.firstWhere( - (item) => item['dropdown_key'] == selectedReqTaxi, + (item) => item['dropdown_key'] == selectedReqTaxi, orElse: () => {}, ), itemAsString: (item) => item['dropdown_value'] ?? '', @@ -591,31 +589,23 @@ class _TaxiScreenState extends State { if (selectedItem == null || selectedItem.isEmpty) { return Text( "Select ", - style: GoogleFonts.poppins( - color: Colors.grey, - fontSize: 13, - ), + style: GoogleFonts.poppins(color: Colors.grey, fontSize: 13), ); } return Text( selectedItem['dropdown_value'] ?? '', - style: GoogleFonts.poppins( - fontSize: 12, - color: Colors.black, - ), + style: GoogleFonts.poppins(fontSize: 12, color: Colors.black), ); }, onChanged: - purposeList.isNotEmpty - ? (newValue) { - setState(() { - selectedReqTaxi = newValue as String?; - }); - print( - "Updating form data: -> ${newValue ?? ""}", - ); - } - : null, + purposeList.isNotEmpty + ? (newValue) { + setState(() { + selectedReqTaxi = newValue as String?; + }); + print("Updating form data: -> ${newValue ?? ""}"); + } + : null, ), ), ), @@ -974,7 +964,7 @@ class _TaxiScreenState extends State { // Close Button ElevatedButton( onPressed: () { - widget.onClose(false); // Close the dialog or screen + widget.onClose("Taxi", false); // Close the dialog or screen }, style: ElevatedButton.styleFrom( backgroundColor: Colors.grey[400], // Light grey color diff --git a/lib/Screens/itnerary/train.dart b/lib/Screens/itnerary/train.dart index 723d18f..2b3e806 100644 --- a/lib/Screens/itnerary/train.dart +++ b/lib/Screens/itnerary/train.dart @@ -5,6 +5,7 @@ import 'package:intl/intl.dart'; import 'package:responsive_builder/responsive_builder.dart'; import '../../services/apiService.dart'; +import '../../widgets/custom_confirmation_dialog.dart'; import '../../widgets/custom_text_field.dart'; import '../../widgets/custom_text_itnerary_sub.dart'; @@ -12,7 +13,7 @@ class TrainScreen extends StatefulWidget { final Map? apiData; final Map? apiDataForClass; final Function(Map) onSavetrain; - final Function(bool) onClose; + final Function(String, bool) onClose; final Map? selectedItem; final String? loginUser; final String? tripType; @@ -67,6 +68,7 @@ class _TrainScreenState extends State { bool _commentsFocus = false; String? selectedClass; + String? exceptionalClass = "0"; String? selectedFrom; String? selectedTo; @@ -80,6 +82,7 @@ class _TrainScreenState extends State { // "from_station": _fromController.text, "to_station": selectedTo, // "to_station": _toController.text, + "is_this_exceptional": exceptionalClass, "date": _dateController.text, "time": _timeController.text, "comments": _trainCommentsController.text, @@ -151,6 +154,11 @@ class _TrainScreenState extends State { _dateController = initController("date"); _timeController = initController("time"); + if (widget.selectedItem != null && + widget.selectedItem!["is_this_exceptional"] != null) { + exceptionalClass = widget.selectedItem!["is_this_exceptional"].toString(); + } + // Set the selected value if available if (widget.selectedItem != null && widget.selectedItem!["class"] != null) { selectedClass = widget.selectedItem!["class"].toString(); @@ -288,8 +296,8 @@ class _TrainScreenState extends State { } else { widget.onSavetrain(trainData); } - - widget.onClose(false); // Close screen after saving + widget.onClose("Train", false); + // widget.onClose(false); // Close screen after saving } @override @@ -576,8 +584,8 @@ class _TrainScreenState extends State { } // Default selected value - selectedClass ??= - dropdownItems.isNotEmpty ? dropdownItems.first.value : null; + // selectedClass ??= + // dropdownItems.isNotEmpty ? dropdownItems.first.value : null; return [ Column( @@ -631,17 +639,30 @@ class _TrainScreenState extends State { height: 40, width: double.infinity, child: DropdownSearch>( + key: + selectedClass == null + ? UniqueKey() + : ValueKey(selectedClass), items: purposeList.cast>(), - selectedItem: purposeList.firstWhere( - (item) => item['dropdown_key'] == selectedClass, - orElse: () => {}, - ), + + // selectedItem: purposeList.firstWhere( + // (item) => item['dropdown_key'] == selectedClass, + // orElse: () => {}, + // ), + selectedItem: + selectedClass != null + ? purposeList.firstWhere( + (item) => item['dropdown_key'] == selectedClass, + orElse: () => {}, + ) + : null, itemAsString: (item) => item['dropdown_value'] ?? '', popupProps: PopupProps.menu( showSearchBox: false, fit: FlexFit.loose, menuProps: const MenuProps(backgroundColor: Colors.white), itemBuilder: (context, item, isSelected) { + final bool isNotAllowed = item['is_allowed'] == 'No'; return Padding( padding: const EdgeInsets.symmetric( horizontal: 10, @@ -651,7 +672,7 @@ class _TrainScreenState extends State { item['dropdown_value'] ?? '', style: GoogleFonts.poppins( fontSize: 12, - color: Colors.black, + color: isNotAllowed ? Colors.redAccent : Colors.black, ), ), ); @@ -660,7 +681,10 @@ class _TrainScreenState extends State { dropdownDecoratorProps: const DropDownDecoratorProps( dropdownSearchDecoration: InputDecoration( border: InputBorder.none, - contentPadding: EdgeInsets.symmetric(horizontal: 10,vertical: 10,), + contentPadding: EdgeInsets.symmetric( + horizontal: 10, + vertical: 10, + ), ), ), dropdownBuilder: (context, selectedItem) { @@ -682,14 +706,59 @@ class _TrainScreenState extends State { ); }, onChanged: - purposeList.isNotEmpty - ? (Map? newValue) { - setState(() { - selectedClass= newValue?['dropdown_key']; - print("Selected Purpose: ${selectedClass}"); - }); - } - : null, + purposeList.isNotEmpty + ? (Map? newValue) async { + if (newValue != null && + newValue['is_allowed'] == 'No') { + final confirm = await showNotAllowedDialog( + context, + message: + "You are not entitled for this service. If you wish to continue with this service, you may require another approval.", + ); + + if (confirm) { + setState(() { + exceptionalClass = "1"; + selectedClass = newValue['dropdown_key']; + }); + print("Selected Purpose: ${selectedClass}"); + } else { + setState(() { + exceptionalClass = "0"; + selectedClass = + null; // ❌ Clear selection if user cancels + }); + print( + "selectedItem resolved to: ${selectedClass}", + ); + } + + return; + } else if (newValue != null && + newValue['is_allowed'] == 'yes') { + setState(() { + exceptionalClass = "0"; + selectedClass = newValue?['dropdown_key']; + print("Selected Purpose: ${selectedClass}"); + }); + } + + // if (newValue != null && + // newValue['is_allowed'] == 'No') { + // print('not allowed'); + // + // setState(() { + // exceptionalClass = "1"; + // // hasExceptionalClass = true; + // }); + // } + + setState(() { + selectedClass = newValue?['dropdown_key']; + print("Selected Purpose: ${selectedClass}"); + }); + } + : null, ), ), ), @@ -817,16 +886,16 @@ class _TrainScreenState extends State { menuProps: const MenuProps(backgroundColor: Colors.white), itemBuilder: (context, item, isSelected) => Container( - color: Colors.white, - padding: EdgeInsets.symmetric( - horizontal: 10, - vertical: 6, - ), - child: Text( - item, - style: GoogleFonts.poppins(fontSize: 11.5), - ), - ), + color: Colors.white, + padding: EdgeInsets.symmetric( + horizontal: 10, + vertical: 6, + ), + child: Text( + item, + style: GoogleFonts.poppins(fontSize: 11.5), + ), + ), searchFieldProps: TextFieldProps( decoration: InputDecoration( hintText: "Search ...", @@ -843,22 +912,20 @@ class _TrainScreenState extends State { ), dropdownBuilder: (context, selectedItem) => Align( - alignment: Alignment.centerLeft, - child: Text( - selectedItem ?? "Select ", - style: GoogleFonts.poppins( - fontSize: 12, - color: Colors.black, + alignment: Alignment.centerLeft, + child: Text( + selectedItem ?? "Select ", + style: GoogleFonts.poppins( + fontSize: 12, + color: Colors.black, + ), + ), ), - ), - ), onChanged: (String? newValue) { setState(() { selectedFrom = countryMap.entries - .firstWhere( - (entry) => entry.value == newValue, - ) + .firstWhere((entry) => entry.value == newValue) .key; }); }, @@ -952,16 +1019,16 @@ class _TrainScreenState extends State { menuProps: const MenuProps(backgroundColor: Colors.white), itemBuilder: (context, item, isSelected) => Container( - color: Colors.white, - padding: EdgeInsets.symmetric( - horizontal: 10, - vertical: 6, - ), - child: Text( - item, - style: GoogleFonts.poppins(fontSize: 11.5), - ), - ), + color: Colors.white, + padding: EdgeInsets.symmetric( + horizontal: 10, + vertical: 6, + ), + child: Text( + item, + style: GoogleFonts.poppins(fontSize: 11.5), + ), + ), searchFieldProps: TextFieldProps( decoration: InputDecoration( hintText: "Search ...", @@ -978,22 +1045,20 @@ class _TrainScreenState extends State { ), dropdownBuilder: (context, selectedItem) => Align( - alignment: Alignment.centerLeft, - child: Text( - selectedItem ?? "Select ", - style: GoogleFonts.poppins( - fontSize: 12, - color: Colors.black, + alignment: Alignment.centerLeft, + child: Text( + selectedItem ?? "Select ", + style: GoogleFonts.poppins( + fontSize: 12, + color: Colors.black, + ), + ), ), - ), - ), onChanged: (String? newValue) { setState(() { selectedTo = countryMap.entries - .firstWhere( - (entry) => entry.value == newValue, - ) + .firstWhere((entry) => entry.value == newValue) .key; }); }, @@ -1174,7 +1239,8 @@ class _TrainScreenState extends State { // Close Button ElevatedButton( onPressed: () { - widget.onClose(false); + // widget.onClose(false); + widget.onClose("Train", false); }, style: ElevatedButton.styleFrom( backgroundColor: Colors.grey[400], // Light grey color diff --git a/lib/Screens/itnerary/visa.dart b/lib/Screens/itnerary/visa.dart index 30b98c0..b841abd 100644 --- a/lib/Screens/itnerary/visa.dart +++ b/lib/Screens/itnerary/visa.dart @@ -13,7 +13,7 @@ class VisaScreen extends StatefulWidget { final Map? apiData; final List? apiCountryData; - final Function(bool) onClose; + final Function(String, bool) onClose; final Function(Map) onSaveVisa; final Map? selectedItem; final String? loginUser; @@ -259,8 +259,8 @@ class _VisaScreenState extends State { } else { widget.onSaveVisa(visaData); } - - widget.onClose(false); // Close screen after saving + widget.onClose("Visa", false); + // widget.onClose(false); // Close screen after saving } @override @@ -513,9 +513,10 @@ class _VisaScreenState extends State { CustomTextFieldItnerarySubWrapper( isFocused: _isHotelNameFocused, isDesktop: isDesktop, - width: isDesktop - ? MediaQuery.of(context).size.width * 0.34 - : MediaQuery.of(context).size.width * 0.66, + width: + isDesktop + ? MediaQuery.of(context).size.width * 0.34 + : MediaQuery.of(context).size.width * 0.66, child: SizedBox( height: 40, child: DropdownSearch( @@ -527,16 +528,16 @@ class _VisaScreenState extends State { menuProps: const MenuProps(backgroundColor: Colors.white), itemBuilder: (context, item, isSelected) => Container( - color: Colors.white, - padding: EdgeInsets.symmetric( - horizontal: 10, - vertical: 6, - ), - child: Text( - item, - style: GoogleFonts.poppins(fontSize: 11.5), - ), - ), + color: Colors.white, + padding: EdgeInsets.symmetric( + horizontal: 10, + vertical: 6, + ), + child: Text( + item, + style: GoogleFonts.poppins(fontSize: 11.5), + ), + ), searchFieldProps: TextFieldProps( decoration: InputDecoration( hintText: "Search ...", @@ -553,18 +554,21 @@ class _VisaScreenState extends State { ), dropdownBuilder: (context, selectedItem) => Align( - alignment: Alignment.centerLeft, - child: Text( - selectedItem ?? "Select ", - style: GoogleFonts.poppins(fontSize: 12,color: Colors.black,), - ), - ), + alignment: Alignment.centerLeft, + child: Text( + selectedItem ?? "Select ", + style: GoogleFonts.poppins( + fontSize: 12, + color: Colors.black, + ), + ), + ), onChanged: (String? newValue) { setState(() { selectedCountry = - countryMap.entries.firstWhere( - (entry) => entry.value == newValue, - ).key; + countryMap.entries + .firstWhere((entry) => entry.value == newValue) + .key; }); }, ), @@ -635,7 +639,7 @@ class _VisaScreenState extends State { child: DropdownSearch>( items: purposeList.cast>(), selectedItem: purposeList.firstWhere( - (item) => item['dropdown_key'] == selectedPurpose, + (item) => item['dropdown_key'] == selectedPurpose, orElse: () => {}, ), itemAsString: (item) => item['dropdown_value'] ?? '', @@ -662,7 +666,10 @@ class _VisaScreenState extends State { dropdownDecoratorProps: const DropDownDecoratorProps( dropdownSearchDecoration: InputDecoration( border: InputBorder.none, - contentPadding: EdgeInsets.symmetric(horizontal: 10,vertical: 10,), + contentPadding: EdgeInsets.symmetric( + horizontal: 10, + vertical: 10, + ), ), ), dropdownBuilder: (context, selectedItem) { @@ -692,14 +699,14 @@ class _VisaScreenState extends State { // EdgeInsets.symmetric(horizontal: 10), // Proper padding // ), onChanged: - purposeList.isNotEmpty - ? (Map? newValue) { - setState(() { - selectedPurpose= newValue?['dropdown_key']; - print("Selected Purpose: ${selectedPurpose}"); - }); - } - : null, + purposeList.isNotEmpty + ? (Map? newValue) { + setState(() { + selectedPurpose = newValue?['dropdown_key']; + print("Selected Purpose: ${selectedPurpose}"); + }); + } + : null, ), ), ), @@ -826,7 +833,8 @@ class _VisaScreenState extends State { // Close Button ElevatedButton( onPressed: () { - widget.onClose(false); // Close the dialog or screen + widget.onClose("Visa", false); + // widget.onClose(false); // Close the dialog or screen }, style: ElevatedButton.styleFrom( backgroundColor: Colors.grey[400], // Light grey color diff --git a/lib/Screens/itnerary_list/flight_list.dart b/lib/Screens/itnerary_list/flight_list.dart index 54919a5..46b07bb 100644 --- a/lib/Screens/itnerary_list/flight_list.dart +++ b/lib/Screens/itnerary_list/flight_list.dart @@ -133,7 +133,8 @@ class _FlightListWidgetState extends State { widget.isViewMode ? null : () { - checkClass(); + widget.onAddNew("Flight", true); + // checkClass(); print("New data"); }, child: Row( diff --git a/lib/Screens/plans/create_plans.dart b/lib/Screens/plans/create_plans.dart index 8218b5d..2661552 100644 --- a/lib/Screens/plans/create_plans.dart +++ b/lib/Screens/plans/create_plans.dart @@ -298,6 +298,8 @@ class _CreatePlansState extends State { context.go('/listPlan'); } else if (currentUri == "/approver/plans") { context.go('/approvallist'); + } else if (currentUri == "/travelagent/trips") { + context.go('/listTravelAgentPlan'); } else { isApprover ? context.go('/approvallist') : context.go('/listPlan'); } @@ -381,6 +383,8 @@ class CreateNewPlansState extends State { final TextEditingController _tripTitleController = TextEditingController(); final TextEditingController _descriptionController = TextEditingController(); + final TextEditingController _excepntldescriptionController = + TextEditingController(); final TextEditingController _remarksController = TextEditingController(); // final FocusNode _tripTitleFocusNode = FocusNode(); @@ -416,6 +420,7 @@ class CreateNewPlansState extends State { bool showDomestic = false; bool showInternational = false; bool hasAction = true; + bool hasExceptionalClass = false; late Map costCenterMap; List costCenterIds = []; @@ -445,10 +450,10 @@ class CreateNewPlansState extends State { List> insuranceList = []; List> accommodationList = []; List> trainList = []; + List> flightList = []; List> busList = []; List> taxiList = []; List> forexList = []; - List> flightList = []; List> planStatusList = []; @@ -471,6 +476,7 @@ class CreateNewPlansState extends State { "is_billable": _selectedIsBillable, "purpose_of_travel": selectedPurpose, "description": _descriptionController.text, + "exceptional_plan_reason": _excepntldescriptionController.text, "functional_department": selectedFuncDept, "so_number": "12345", "created_by": selfId, @@ -495,6 +501,7 @@ class CreateNewPlansState extends State { "is_billable", "purpose_of_travel", "description", + "excepntldescription", "functional_department", ]; @@ -535,6 +542,13 @@ class CreateNewPlansState extends State { print("Updated $type List: $newList"); } + void handleExcentionalClass(bool val) { + print("handleExcentionalClass - $val"); + setState(() { + hasExceptionalClass = val; + }); + } + @override void initState() { super.initState(); @@ -652,6 +666,13 @@ class CreateNewPlansState extends State { _descriptionController.text = widget.selectedPlanData['description'] ?? ''; + _excepntldescriptionController.text = + widget.selectedPlanData['exceptional_plan_reason'] ?? ''; + + if (_excepntldescriptionController.text != "") { + hasExceptionalClass = true; + } + _selectedTripType = widget.selectedPlanData['trip_type']; flightTripTypeNotifier.value = widget.selectedPlanData['trip_type']; _selectedIsBillable = @@ -1136,6 +1157,8 @@ class CreateNewPlansState extends State { "cost_center_id": selectedCostCenterId, "functional_department": selectedFuncDept, "purpose_of_travel": selectedPurpose, + if (hasExceptionalClass) + "exceptional_plan_reason": _excepntldescriptionController.text, }; print("validateForm.....3"); for (var entry in requiredFields.entries) { @@ -1268,8 +1291,55 @@ class CreateNewPlansState extends State { } } + Future checkExceptionalClass() async { + // pretend we fetch something + await Future.delayed(Duration(seconds: 1)); // Example async operation + + int exceptionalCount = 0; + + bool hasExceptional(List> list) { + return list.any( + (item) => + item['is_this_exceptional']?.toString() == '1' && + item['is_active']?.toString() == '1', + ); + } + + bool hasExceptionalInFlights(List> flights) { + for (var flight in flights) { + if (flight['is_active']?.toString() == '1') { + final trips = flight['trips']; + if (trips is List) { + final exceptionalTrip = trips.any( + (trip) => trip['is_this_exceptional']?.toString() == '1', + ); + if (exceptionalTrip) return true; + } + } + } + return false; + } + + if (hasExceptionalInFlights(flightList)) exceptionalCount++; + if (hasExceptional(trainList)) exceptionalCount++; + if (hasExceptional(accommodationList)) exceptionalCount++; + + setState(() { + hasExceptionalClass = exceptionalCount >= 1; + if (!hasExceptionalClass) { + _excepntldescriptionController.text = ''; + } + }); + + print("hasExceptionalClass: $hasExceptionalClass"); + } + void handleSubmit() { setState(() async { + await checkExceptionalClass(); + + print("Handle Submit....after check"); + print("Hansle Submit....11"); if (validateForm() && temporaryMessage == null) { print("Hansle Submit....11222"); @@ -1690,17 +1760,18 @@ class CreateNewPlansState extends State { ? Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ - // _buildNonDescriptionColumn(), - // SizedBox(width: 25), + // _buildDescriptionColumn(isDesktop), + SizedBox(width: 25), + if (hasExceptionalClass) _buildNonDescriptionColumn(), ], ) : Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - // _buildNonDescriptionColumn(), - // SizedBox(height: 15), _buildDescriptionColumn(isDesktop), + SizedBox(height: 15), + _buildNonDescriptionColumn(), ], ), @@ -1743,6 +1814,7 @@ class CreateNewPlansState extends State { apiDataForClass: apiDataForClass, apiCountryData: apiCountryData, onItineraryUpdate: handleItineraryUpdate, + onExeptionalClass: handleExcentionalClass, loginUser: selfId, selectedPlanData: planData, isViewMode: widget.isViewMode, @@ -3248,7 +3320,7 @@ class CreateNewPlansState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - "Trip Name", // Your label + "Reason For Exceptional Class *", // Your label style: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w600, @@ -3256,32 +3328,54 @@ class CreateNewPlansState extends State { ), ), SizedBox(height: 5), + CustomTextFieldWrapper( - isFocused: false, - // isFocused: _isTripTitleFocused, + isFocused: focusStates["excepntldescriptionFocused"] ?? false, + // isFocused: _isdescriptionFocused, + width: + widget.isDesktop + ? MediaQuery.of(context).size.width * 0.42 + : null, isDesktop: widget.isDesktop, child: SizedBox( - height: 35, + height: 55, child: TextField( - // focusNode: _tripTitleFocusNode, - controller: _tripTitleController, + focusNode: focusNodes["excepntldescriptionFocusNode"], + controller: _excepntldescriptionController, + maxLines: 2, + keyboardType: TextInputType.multiline, style: TextStyle(fontSize: 12), enabled: !widget.isViewMode, decoration: InputDecoration( - labelText: "Trip Name", - labelStyle: TextStyle(fontSize: 12, color: Colors.grey), + labelText: "Exceptional Description", + labelStyle: GoogleFonts.poppins( + fontSize: 12, + color: Colors.grey, + ), + // labelStyle: TextStyle(fontSize: 12, color: Colors.grey), floatingLabelBehavior: FloatingLabelBehavior.never, border: InputBorder.none, - contentPadding: EdgeInsets.symmetric(vertical: 16), + contentPadding: EdgeInsets.symmetric(vertical: 1), ), ), ), ), + + if (validationErrors["exceptional_plan_reason"] != null) + Padding( + padding: EdgeInsets.only(top: 4), + child: Text( + validationErrors["exceptional_plan_reason"]!, + style: GoogleFonts.poppins( + fontSize: 12, + color: Colors.red, + ), + ), + ), ], ), ], ), - SizedBox(height: 8), ], ); } diff --git a/lib/Screens/plans/dynamic_itinerary_stepper.dart b/lib/Screens/plans/dynamic_itinerary_stepper.dart index 3b73751..af38ab1 100644 --- a/lib/Screens/plans/dynamic_itinerary_stepper.dart +++ b/lib/Screens/plans/dynamic_itinerary_stepper.dart @@ -34,6 +34,8 @@ class DynamicItinerary extends StatefulWidget { final String? loginUser; final Function(String, List>) onItineraryUpdate; // Updated Signature + + final void Function(bool)? onExeptionalClass; final Map selectedPlanData; final bool isViewMode; final GlobalKey flightScreenKey; @@ -48,6 +50,7 @@ class DynamicItinerary extends StatefulWidget { required this.selectedPlanData, required this.isViewMode, required this.hasAction, + this.onExeptionalClass, this.tripType, this.apiDataForClass, required this.tripTypeNotifier, @@ -64,6 +67,7 @@ class DynamicItineraryState extends State { String? _tripType; String selectedOption = ""; String selectedListOption = ""; + String selectedActiveTab = ""; bool isSelected = false; Map? selectedItem; @@ -400,7 +404,8 @@ class DynamicItineraryState extends State { return false; // No itinerary data available } - void handleClose(bool value) { + void handleMClose(bool value) { + print("handleClos- $isSelected"); setState(() { selectedOption = ""; isSelected = value; @@ -409,6 +414,17 @@ class DynamicItineraryState extends State { }); } + void handleClose(name, bool value) { + print("handleMClos- $name, $isSelected"); + setState(() { + selectedListOption = name; + selectedOption = ""; + isSelected = value; + selectedIndex = null; + selectedItem = null; + }); + } + void handleEdit(bool value, selectedItem, title) { setState(() { selectedOption = title; @@ -420,6 +436,7 @@ class DynamicItineraryState extends State { } void handlecreateNewPlan(name, bool value) { + print("handlecreateNewPlan- $name , $value"); setState(() { selectedOption = name; isSelected = value; @@ -710,6 +727,7 @@ class DynamicItineraryState extends State { break; case "Miscellaneous": selectedWidget = MiscellaneousScreen( + // onClose: handleClose, onClose: handleClose, apiData: widget.apiData, loginUser: widget.loginUser, @@ -723,10 +741,12 @@ class DynamicItineraryState extends State { selectedWidget = AccomodationScreen( onClose: handleClose, loginUser: widget.loginUser, + apiDataForClass: widget.apiDataForClass, onSaveAccomadation: (data) => handleItineraryUpdate("Accomodation", data), selectedItem: selectedItem, flightData: itineraryData["Flight"]!, + onExceptionalClass: widget.onExeptionalClass, ); break; case "Forex": @@ -747,6 +767,7 @@ class DynamicItineraryState extends State { tripTypeNotifier: widget.tripTypeNotifier, hasAction: widget.hasAction, tripType: widget.tripType, + // onClose: handleMClose, onClose: handleClose, loginUser: widget.loginUser, onSaveFlight: (data) => handleItineraryUpdate("Flight", data), @@ -758,71 +779,6 @@ class DynamicItineraryState extends State { break; } - // return ResponsiveBuilder(builder: (context, sizingInfo) { - // bool isMobile = sizingInfo.isMobile; - // bool isDesktop = sizingInfo.deviceScreenType == DeviceScreenType.desktop; - // - // return Column( - // // mainAxisSize: MainAxisSize.min, - // children: [ - // Transform.translate( - // offset: Offset(0, 20), - // child: Container( - // decoration: BoxDecoration( - // border: Border( - // bottom: BorderSide(color: Color(0xFFF4F4FB), width: 2)), - // borderRadius: BorderRadius.circular(1), - // color: Color(0xFFF4F4FB), - // ), - // margin: EdgeInsets.symmetric( - // horizontal: MediaQuery.of(context).size.width * - // 0.05, // 30% of screen width as horizontal padding - // vertical: MediaQuery.of(context).size.height * - // 0, // 5% of screen height as vertical padding - // ), - // child: isMobile - // ? Expanded( - // child: SingleChildScrollView( - // scrollDirection: Axis.horizontal, - // child: Row( - // children: _buildOptions(), - // ), - // ), - // ) - // : Row( - // mainAxisAlignment: MainAxisAlignment.spaceEvenly, - // // mainAxisSize: MainAxisSize.min, - // children: _buildOptions(), - // ), - // ), - // ), - // - // - // Container( - // decoration: BoxDecoration( - // // border: - // // Border(bottom: BorderSide(color: Colors.black, width: 2)), - // - // border: Border.all(color: Colors.green, width: 1.5), - // borderRadius: BorderRadius.circular(1), - // color: Colors.yellowAccent.shade100, - // ), - // margin: EdgeInsets.only(top: 0), - // child: Column( - // // mainAxisSize: MainAxisSize.min, - // crossAxisAlignment: CrossAxisAlignment.stretch, - // children: [ - // SizedBox(height: 2), - // isSelected ? selectedWidget : selectedListWidget, - // // TrainScreen() - // ], - // ), - // ), - // - // ], - // ); - // }); - return ResponsiveBuilder( builder: (context, sizingInfo) { bool isMobile = sizingInfo.isMobile; @@ -964,19 +920,69 @@ class DynamicItineraryState extends State { }).toList(); } + bool checkClass() { + if (widget.hasAction) { + if (widget.tripType?.isNotEmpty == true) { + print("Teppp - ${widget.tripType}"); + return true; + } else { + showDialog( + context: context, + builder: (context) { + return AlertDialog( + title: const Text('Select Trip Type'), + content: const Text( + 'Please select a trip type before selecting services.', + ), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(), + child: const Text('OK'), + ), + ], + ); + }, + ); + return false; + } + } else { + print("Teppp No - ${widget.tripType}"); + return true; // still allow it when `hasAction` is false + } + } + Widget _buildOption(Map service, bool hasData) { String name = service['name']; String iconUrl = service['icon']; // Can be empty string IconData fallbackIcon = _getLocalIconForService(name); - bool isOptionSelected = selectedListOption == name; + // bool isOptionSelected = selectedListOption == name; + bool isOptionSelected = selectedActiveTab == name; + // bool isOptionSelected = + // hasData ? selectedListOption == name : selectedOption == name; return GestureDetector( onTap: () { - setState(() { - selectedListOption = name; - isSelected = false; - }); + if (checkClass()) { + setState(() { + if (hasData) { + selectedListOption = name; + selectedActiveTab = name; + isSelected = false; + } else { + selectedOption = name; + selectedActiveTab = name; + isSelected = true; + selectedItem = null; + } + }); + } + // setState(() { + // selectedListOption = name; + // isSelected = false; + // // selectedOption = name; + // // isSelected = true; + // }); }, child: Container( decoration: BoxDecoration( diff --git a/lib/config/apiUrl.dart b/lib/config/apiUrl.dart index 7139bea..eb751b8 100644 --- a/lib/config/apiUrl.dart +++ b/lib/config/apiUrl.dart @@ -1,3 +1,3 @@ //api url -// const String apiUrl = 'http://apitest.tripapprovaltool.com/tstat_be'; -const String apiUrl = 'https://uat.tripapprovaltool.com/tstat_be'; +const String apiUrl = 'http://apitest.tripapprovaltool.com/tstat_be'; +// const String apiUrl = 'https://uat.tripapprovaltool.com/tstat_be'; diff --git a/lib/routes/custom_appBar.dart b/lib/routes/custom_appBar.dart index 0809fb6..214b426 100644 --- a/lib/routes/custom_appBar.dart +++ b/lib/routes/custom_appBar.dart @@ -680,11 +680,7 @@ final List> menuItems = [ // 'label': 'Template List' // }, // {'value': '/template', 'icon': Icons.ac_unit_sharp, 'label': 'Template'}, - { - 'value': '/report', - 'icon': Icons.auto_graph, - 'label': 'Reports', - }, + {'value': '/report', 'icon': Icons.auto_graph, 'label': 'Reports'}, { 'value': '/CreateUserDetails', 'icon': Icons.account_circle, diff --git a/lib/routes/custom_router.dart b/lib/routes/custom_router.dart index 6bc6568..fcd7843 100644 --- a/lib/routes/custom_router.dart +++ b/lib/routes/custom_router.dart @@ -81,6 +81,10 @@ final GoRouter router = GoRouter( path: '/approver/plans', builder: (context, state) => CreatePlan(), ), + GoRoute( + path: '/travelagent/trips', + builder: (context, state) => CreatePlan(), + ), GoRoute( path: '/listUser', builder: (context, state) => UserListScreen(), diff --git a/lib/services/apiService.dart b/lib/services/apiService.dart index 4d88d2c..64fa3e8 100644 --- a/lib/services/apiService.dart +++ b/lib/services/apiService.dart @@ -643,6 +643,25 @@ class ApiService { } } + static Future viewPlanTravelAgent( + BuildContext context, + String planId, { + bool isViewMode = false, + bool isMyTrips = false, + }) async { + try { + Map planData = await getViewPlanEdit(planId); + print("ViewAAA - $planData"); + + context.go( + '/travelagent/trips', + extra: {'planData': planData, 'isViewMode': isViewMode}, + ); + } catch (e) { + print("Error fetching plan: $e"); + } + } + Future> fetchUserApprovalList() async { String? orgId = await getOrgId(); String? userId = await getUserId(); @@ -899,8 +918,8 @@ class ApiService { // download the Template for bulk upload the user Future getDownloadUserTemplateForUpload() async { - - final String apiUrldata = '$apiUrl/api/user/userUploadTemplate'; // download the Template for bulk upload user => api + final String apiUrldata = + '$apiUrl/api/user/userUploadTemplate'; // download the Template for bulk upload user => api // final String apiUrldata = '$apiUrl/auth/googlelogin'; @@ -930,9 +949,9 @@ class ApiService { // Create a link element to trigger the download final anchor = - html.AnchorElement(href: url) - ..setAttribute('download', 'user_file.xlsx') - ..click(); + html.AnchorElement(href: url) + ..setAttribute('download', 'user_file.xlsx') + ..click(); // Revoke the download URL to free up resources html.Url.revokeObjectUrl(url); @@ -962,7 +981,6 @@ class ApiService { } } - //---------------------------------------User Management----------------------------------- Future> getSingleUser(int userId) async { @@ -1511,7 +1529,7 @@ class ApiService { // ----------------------- User Management - CheckDuplicate end here ---------------------------------- // ----------------------- Report ------------------------- - Future> CallReports(String apiRoute,String body) async { + Future> CallReports(String apiRoute, String body) async { String apiUrldata = '$apiUrl/api/$apiRoute'; final token = await getToken(); @@ -1521,8 +1539,11 @@ class ApiService { final response = await http.post( Uri.parse(apiUrldata), - headers: {'Authorization': 'Bearer $token','Content-Type': 'application/json'}, - body: body + headers: { + 'Authorization': 'Bearer $token', + 'Content-Type': 'application/json', + }, + body: body, ); if (response.statusCode == 200) { @@ -1539,14 +1560,20 @@ class ApiService { } } - Future reportExcelDownload(String apiRoute,String body,String name) async { + Future reportExcelDownload( + String apiRoute, + String body, + String name, + ) async { final String apiUrldata = '$apiUrl/api/$apiRoute'; // Sanitize name for filename (optional) - final safeName = name.replaceAll(RegExp(r'[^\w\s-]'), ''); // remove any special chars if needed + final safeName = name.replaceAll( + RegExp(r'[^\w\s-]'), + '', + ); // remove any special chars if needed final excelName = '$safeName.xlsx'; - final token = await getToken(); if (token == null) { @@ -1554,9 +1581,12 @@ class ApiService { } final response = await http.post( - Uri.parse(apiUrldata), - headers: {'Authorization': 'Bearer $token','Content-Type': 'application/json'}, - body: body + Uri.parse(apiUrldata), + headers: { + 'Authorization': 'Bearer $token', + 'Content-Type': 'application/json', + }, + body: body, ); if (response.statusCode == 200) { @@ -1571,9 +1601,9 @@ class ApiService { // Create a link element to trigger the download final anchor = - html.AnchorElement(href: url) - ..setAttribute('download', excelName) - ..click(); + html.AnchorElement(href: url) + ..setAttribute('download', excelName) + ..click(); // Revoke the download URL to free up resources html.Url.revokeObjectUrl(url); @@ -1587,5 +1617,6 @@ class ApiService { throw Exception('Failed to Download'); } } -// ----------------------- Report ------------------------- + + // ----------------------- Report ------------------------- } diff --git a/lib/widgets/custom_confirmation_dialog.dart b/lib/widgets/custom_confirmation_dialog.dart new file mode 100644 index 0000000..164f128 --- /dev/null +++ b/lib/widgets/custom_confirmation_dialog.dart @@ -0,0 +1,30 @@ +import 'package:flutter/material.dart'; + +Future showNotAllowedDialog( + BuildContext context, { + String? message, +}) async { + return await showDialog( + context: context, + barrierDismissible: false, // Force user to choose + builder: (context) { + return AlertDialog( + title: const Text('Notice'), + content: Text( + message ?? 'This class is not allowed. Do you want to continue?', + ), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(false), // Cancel + child: const Text('Cancel'), + ), + ElevatedButton( + onPressed: () => Navigator.of(context).pop(true), // OK + child: const Text('OK'), + ), + ], + ); + }, + ) ?? + false; // Default to false if dialog is dismissed +}