import 'dart:convert'; import 'package:dotted_border/dotted_border.dart'; import 'package:easy_stepper/easy_stepper.dart'; import 'package:flutter/material.dart'; import 'package:frontend/Screens/itnerary_list/accomodation_list.dart'; import 'package:frontend/Screens/itnerary_list/bus_list.dart'; import 'package:frontend/Screens/itnerary_list/flight_list.dart'; import 'package:frontend/Screens/itnerary_list/taxi_list.dart'; import 'package:frontend/Screens/itnerary_list/train_list.dart'; import 'package:frontend/utils/auth_utils.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:responsive_builder/responsive_builder.dart'; import 'package:shared_preferences/shared_preferences.dart'; import '../../services/apiService.dart'; import '../../utils/auth_utils.dart'; import '../itnerary/accomodations.dart'; import '../itnerary/bus.dart'; import '../itnerary/flights.dart'; import '../itnerary/forex.dart'; import '../itnerary/insurance.dart'; import '../itnerary/miscellaneous.dart'; import '../itnerary/taxi.dart'; import '../itnerary/train.dart'; import '../itnerary/visa.dart'; import '../itnerary_list/forex_list.dart'; import '../itnerary_list/insurance_list.dart'; 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>) onItineraryUpdate; // Updated Signature final void Function(bool)? onExeptionalClass; final Map selectedPlanData; final bool isViewMode; final GlobalKey flightScreenKey; final ValueNotifier tripTypeNotifier; const DynamicItinerary({ super.key, required this.apiData, required this.onItineraryUpdate, required this.apiCountryData, required this.loginUser, required this.selectedPlanData, required this.isViewMode, required this.hasAction, this.onExeptionalClass, this.tripType, this.apiDataForClass, required this.tripTypeNotifier, required this.flightScreenKey, }); @override DynamicItineraryState createState() => DynamicItineraryState(); } class DynamicItineraryState extends State { final ApiService apiService = ApiService(); late ScrollController _scrollController; bool _canScrollLeft = false; bool _canScrollRight = true; Color? layoutColor; Color? secondColor; Color? thridColor; String? _tripType; String selectedOption = ""; String selectedListOption = ""; String selectedActiveTab = ""; bool isSelected = false; Map? selectedItem; int? selectedIndex; List? selectedAllServices; // List> selectedOrgServiceIds = []; List> selectedOrgServiceIds = []; List? ServicesChoosed; List filledItineraryKeys = []; // List> miscellaneousList = []; Map>> itineraryData = { "Train": [], "Bus": [], "Taxi": [], "Miscellaneous": [], "Flight": [], "Accomodation": [], "Insurance": [], "Visa": [], "Forex": [], }; @override void initState() { print("DynamicItinerary"); super.initState(); handleSelectedPlan(); updateSelectedServices(); _tripType = widget.tripType; loadInitialData(); _scrollController = ScrollController(); _scrollController.addListener(_updateScrollButtonState); // Delay check after layout is built WidgetsBinding.instance.addPostFrameCallback((_) { _updateScrollButtonState(); // Initial scroll check }); } @override void didChangeDependencies() { super.didChangeDependencies(); if (widget.tripType != _tripType) { // setState(() { // selectedOption = ""; // isSelected = false; // selectedIndex = null; // selectedItem = null; // }); print("updateTripType - $_tripType"); updateTripType(widget.tripType); } } @override void dispose() { _scrollController.removeListener(_updateScrollButtonState); _scrollController.dispose(); super.dispose(); } void _updateScrollButtonState() { if (!_scrollController.hasClients) return; final max = _scrollController.position.maxScrollExtent; final offset = _scrollController.offset; setState(() { _canScrollLeft = offset > 0; _canScrollRight = offset < max; }); } void loadInitialData() async { String? layoutString = await getLayoutColor(); String? secondString = await getSecondaryColor(); String? thridString = await getTernaryColor(); // String? bodyStringColor = await getBodyColor(); setState(() { layoutColor = layoutString != null ? Color(int.parse(layoutString)) : Colors.redAccent; secondColor = layoutString != null ? Color(int.parse(secondString!)) : Colors.orange; thridColor = layoutString != null ? Color(int.parse(thridString!)) : Colors.orangeAccent; // bodyColor = // bodyStringColor != null // ? Color(int.parse(bodyStringColor)) // : Colors.white; }); } void updateTripType(String? newTripType) { if (_tripType != newTripType) { setState(() { _tripType = newTripType; }); updateSelectedServices(); // Refresh services based on new tripType print("updateTripType2 - $newTripType"); handleSelectedPlan(); } } Future loadAllServices() async { try { final result = await apiService.fetchAllServices(context); setState(() { selectedAllServices = result; }); print("Fetched services Order: $selectedAllServices"); } catch (e) { print('Error fetching role list: $e'); } } /// Org token based services Future loadOrgSelectedAlServices() async { print('loadOrgSelectedAlServices'); try { final result = await apiService.fetchOrganization(context); if (result != null && result is Map) { final rawServices = result['services_ids']; if (rawServices != null && rawServices is String) { try { List decoded = json.decode(rawServices); List> formatted = decoded .map((e) => {"service_id": e['service_id'].toString()}) .toList(); setState(() { selectedOrgServiceIds = formatted; }); print("selectedOrgServiceIds: ${selectedOrgServiceIds}"); for (var service in selectedOrgServiceIds) { print("service_id: ${service['service_id']}"); } } catch (e) { print("Failed to decode services_ids: $e"); } } } } catch (e) { print('Error fetching role list: $e'); } } Future saveSelectedOrgServices( List> services, ) async { final prefs = await SharedPreferences.getInstance(); final jsonString = jsonEncode(services); await prefs.setString("selectedOrgServiceName", jsonString); } Future OrgServices() async { print('loadOrgSelectedAlServices'); try { List> formattedName = selectedOrgServiceIds.map((e) { final id = e['service_id'].toString(); // find matching service from master list final match = selectedAllServices?.firstWhere( (service) => service['service_id'].toString() == id, orElse: () => {}, ); return { "service_id": id, "name": match.isNotEmpty ? match['name'].toString() : "", }; }).toList(); await saveSelectedOrgServices(formattedName); print("ServiceformattedName: $formattedName"); } catch (e) { print('Error fetching role list: $e'); } } List getAllowedServiceNames() { print( "DID Updatee changee - $selectedOption $selectedListOption $isSelected $selectedItem $selectedIndex", ); setState(() { selectedOption = ""; selectedListOption = ""; isSelected = false; selectedIndex = null; selectedItem = null; }); print( "DID Updatee changee - $selectedOption $isSelected $selectedItem $selectedIndex", ); if (widget.tripType == "1") { return ["flight", "accomodation", "train", "bus", "taxi"]; } else if (widget.tripType == "2") { return [ "flight", "accomodation", "forex", "insurance", "visa", "miscellaneous", "taxi", ]; } else { // tripType is null or not 1/2, allow everything return []; } } void clearAllServiceLists() async { print("clearAllServiceLists"); setState(() { itineraryData.updateAll((key, value) => []); }); } // 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(); await OrgServices(); final allowedServiceNames = getAllowedServiceNames(); if (hasAnyItineraryData()) { print("SELCTSplanChhose: $filledItineraryKeys"); final selectedIds = selectedOrgServiceIds.map((e) => e['service_id']).toSet(); final additionalServices = selectedAllServices!.where((service) { final name = (service['name'] ?? "").toString().toLowerCase(); final id = service['service_id'].toString(); final isNameAllowed = allowedServiceNames.isEmpty || allowedServiceNames.contains(name); return filledItineraryKeys.contains(name) && !selectedIds.contains(id) && isNameAllowed; }).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] ..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) { 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)); }); print("Filtered Selected Services Chosen: $ServicesChoosed"); } } void handleSelectedPlan() { print("handleSelectedPlan"); // Check if selectedPlanData has itinerary data if (hasAnyItineraryData()) { print("selectedPlanData HAS DATA"); setState(() { itineraryData = { "Train": List>.from( widget.selectedPlanData['train'] ?? [], ), "Bus": List>.from( widget.selectedPlanData['bus'] ?? [], ), "Taxi": List>.from( widget.selectedPlanData['taxi'] ?? [], ), "Miscellaneous": List>.from( widget.selectedPlanData['miscellaneous'] ?? [], ), "Flight": List>.from( widget.selectedPlanData['flight'] ?? [], ), "Accomodation": List>.from( widget.selectedPlanData['accomodation'] ?? [], ), "Insurance": List>.from( widget.selectedPlanData['insurance'] ?? [], ), "Visa": List>.from( widget.selectedPlanData['visa'] ?? [], ), "Forex": List>.from( widget.selectedPlanData['forex'] ?? [], ), }; }); } else { print("No itinerary data available"); } } bool hasAnyItineraryData() { List keys = [ "train", "bus", "taxi", "miscellaneous", "flight", "accomodation", "insurance", "visa", "forex", ]; // for (String key in keys) { // if (widget.selectedPlanData.containsKey(key) && // widget.selectedPlanData[key] is List && // (widget.selectedPlanData[key] as List).isNotEmpty) { // print("SelectedPLANDFDDSF- ${widget.selectedPlanData}"); // return true; // At least one list has data // } // } filledItineraryKeys.clear(); // Clear previous results for (String key in keys) { if (widget.selectedPlanData.containsKey(key) && widget.selectedPlanData[key] is List && (widget.selectedPlanData[key] as List).isNotEmpty) { filledItineraryKeys.add(key); // Store key with data } } if (filledItineraryKeys.isNotEmpty) { print("Selected keys with data: $filledItineraryKeys"); return true; } return false; // No itinerary data available } void handleMClose(bool value) { print("handleClos- $isSelected"); setState(() { selectedOption = ""; isSelected = value; selectedIndex = null; selectedItem = null; }); } 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; isSelected = value; this.selectedItem = selectedItem; }); print(selectedItem); } void handlecreateNewPlan(name, bool value) { print("handlecreateNewPlan- $name , $value"); setState(() { selectedOption = name; isSelected = value; this.selectedItem = null; }); } Widget build(BuildContext context) { Widget selectedWidget; Widget selectedListWidget; void handleItineraryUpdate(String type, Map newData) { setState(() { if (!itineraryData.containsKey(type)) { itineraryData[type] = []; // Initialize if null } List> itemList = itineraryData[type]!; print("newData - $newData"); // int? existingId = newData["id"]; // String? existingId = newData["id"]; String? idKey = "${type.toLowerCase()}_id"; String? existingId = newData[idKey]; print("Looking for ID using key: $idKey, Found ID: $existingId"); int? existingIndex = newData["indx"]; print("existingIndex - $existingIndex, existingId - $existingId"); // CASE 2: Update using id if available if (existingId != null && existingId != 0) { // int itemId = itemList.indexWhere((item) => item["id"] == existingId); int itemId = itemList.indexWhere( (item) => item[idKey]?.toString() == existingId.toString(), ); if (itemId != -1) { print(" Updating existing item with id: $existingId"); newData["is_active"] = "1"; itemList[itemId] = newData; return; } } // CASE 1: Update if indx exists in list if (existingIndex != null && existingIndex != 0) { int itemIndex = itemList.indexWhere( (item) => item["indx"] == existingIndex, ); if (itemIndex != -1) { print("Updating existing item with indx: $existingIndex"); newData["is_active"] = "1"; itemList[itemIndex] = newData; // Update the item return; } } // CASE 3: New Entry (Assign new indx) print(" Creating new entry"); newData["indx"] = itemList.length + 1; // Assign a new indx newData["is_active"] = "1"; itemList.add(newData); print(" Updated $type List: ${itineraryData[type]}"); }); print(" onItineraryUpdate - $type - ${itineraryData[type]!} "); widget.onItineraryUpdate(type, itineraryData[type]!); // Notify parent } void handleItinerarydelete(String type, Map data) { setState(() { // if(!itineraryData.containsKey(type)){ // return; // } if (!itineraryData.containsKey(type)) { itineraryData[type] = []; // Initialize if null } List> itemList = itineraryData[type]!; String? idKey = "${type.toLowerCase()}_id"; String? existingId = data[idKey]; // int? existingId = data["id"]; // String? existingId = data["id"]; int? existingIndex = data["indx"]; print("🗑️ Deleting item -> ID: $existingId, Index: $existingIndex"); // Delete by ID if (existingId != null && existingId != 0) { // itemList.removeWhere((item) => item[idKey]?.toString() == existingId.toString()); for (var item in itemList) { if (item[idKey]?.toString() == existingId.toString()) { item["is_active"] = 0; // Soft delete print("Updated is_active to 0 for ID: $existingId"); } } itineraryData[type] = List.from(itemList); print("Deleted by ID: $existingId"); } //Delete by Index else if (existingIndex != null && existingId != 0) { itemList.removeWhere((item) => item["indx"] == existingIndex); print("Deleted by ID: $existingIndex"); } else { print("No Valid Deletion"); } itineraryData[type] = List.from(itemList); }); print(" onItineraryUpdate - $type - ${itineraryData[type]!} "); widget.onItineraryUpdate(type, itineraryData[type]!); // Notify parent print("ItienreayDATE - $itineraryData"); } // void handleItineraryUpdate(String type, Map newData) { // setState(() { // if (!itineraryData.containsKey(type)) { // itineraryData[type] = []; // Initialize if null // } // // // Assign an index based on the current list length // int newIndex = itineraryData[type]!.length + 1; // newData["indx"] = newIndex; // Add an ID field // // itineraryData[type]!.add(newData); // Append new object // }); // // widget.onItineraryUpdate(type, itineraryData[type]!); // Notify parent // print("Updated $type List: ${itineraryData[type]}"); // } switch (selectedListOption) { case "Train": selectedListWidget = TrainListWidget( hasAction: widget.hasAction, tripType: widget.tripType, trainList: itineraryData["Train"]!, onOpen: handleEdit, onAddNew: handlecreateNewPlan, isViewMode: widget.isViewMode, apiData: widget.apiData, onDeleteTrain: (data) => handleItinerarydelete("Train", data), ); break; case "Taxi": selectedListWidget = TaxiListWidget( taxiList: itineraryData["Taxi"]!, onOpen: handleEdit, onAddNew: handlecreateNewPlan, isViewMode: widget.isViewMode, apiData: widget.apiData, onDeleteTaxi: (data) => handleItinerarydelete("Taxi", data), ); break; case "Bus": selectedListWidget = BusListWidget( busList: itineraryData["Bus"]!, onOpen: handleEdit, onAddNew: handlecreateNewPlan, isViewMode: widget.isViewMode, onDeleteBus: (data) => handleItinerarydelete("Bus", data), ); break; case "Insurance": selectedListWidget = InsuranceListWidget( insuranceList: itineraryData["Insurance"]!, onOpen: handleEdit, onAddNew: handlecreateNewPlan, isViewMode: widget.isViewMode, apiData: widget.apiData, onDeleteInsurance: (data) => handleItinerarydelete("Insurance", data), ); break; case "Visa": selectedListWidget = VisaListWidget( visaList: itineraryData["Visa"]!, apiData: widget.apiData, apiCountryData: widget.apiCountryData, onOpen: handleEdit, onAddNew: handlecreateNewPlan, isViewMode: widget.isViewMode, onDeleteMiscellaneous: (data) => handleItinerarydelete("Visa", data), ); break; case "Forex": selectedListWidget = ForexListWidget( forexList: itineraryData["Forex"]!, apiCountryData: widget.apiCountryData, onOpen: handleEdit, onAddNew: handlecreateNewPlan, isViewMode: widget.isViewMode, apiData: widget.apiData, onDeleteForex: (data) => handleItinerarydelete("Forex", data), ); break; case "Accomodation": selectedListWidget = AccomodationListWidget( accommodationList: itineraryData["Accomodation"]!, onOpen: handleEdit, onAddNew: handlecreateNewPlan, isViewMode: widget.isViewMode, onDeleteAccommodation: (data) => handleItinerarydelete("Accomodation", data), ); break; case "Miscellaneous": selectedListWidget = MiscellaneousListWidget( miscellaneousList: itineraryData["Miscellaneous"]!, onOpen: handleEdit, isViewMode: widget.isViewMode, onAddNew: handlecreateNewPlan, apiData: widget.apiData, onDeleteMiscellaneous: (data) => handleItinerarydelete("Miscellaneous", data), ); break; case "Flight": default: selectedListWidget = FlightListWidget( hasAction: widget.hasAction, tripType: widget.tripType, flightList: itineraryData["Flight"]!, onOpen: handleEdit, onAddNew: handlecreateNewPlan, isViewMode: widget.isViewMode, apiData: widget.apiData, onDeleteFlight: (data) => handleItinerarydelete("Flight", data), ); break; } switch (selectedOption) { case "Train": selectedWidget = TrainScreen( onClose: handleClose, apiData: widget.apiData, apiDataForClass: widget.apiDataForClass, loginUser: widget.loginUser, onSavetrain: (data) => handleItineraryUpdate("Train", data), tripType: widget.tripType, selectedItem: selectedItem, ); break; case "Taxi": selectedWidget = TaxiScreen( onClose: handleClose, apiData: widget.apiData, loginUser: widget.loginUser, onSavetaxi: (data) => handleItineraryUpdate("Taxi", data), selectedItem: selectedItem, ); break; case "Bus": selectedWidget = BusScreen( onClose: handleClose, apiData: widget.apiData, loginUser: widget.loginUser, onSaveBus: (data) => handleItineraryUpdate("Bus", data), selectedItem: selectedItem, ); break; case "Insurance": selectedWidget = InsuranceScreen( onClose: handleClose, apiData: widget.apiData, loginUser: widget.loginUser, onSaveInsurance: (data) => handleItineraryUpdate("Insurance", data), selectedItem: selectedItem, flightData: itineraryData["Flight"]!, ); break; case "Visa": selectedWidget = VisaScreen( onClose: handleClose, apiData: widget.apiData, apiCountryData: widget.apiCountryData, loginUser: widget.loginUser, onSaveVisa: (data) => handleItineraryUpdate("Visa", data), selectedItem: selectedItem, flightData: itineraryData["Flight"]!, ); break; case "Miscellaneous": selectedWidget = MiscellaneousScreen( // onClose: handleClose, onClose: handleClose, apiData: widget.apiData, loginUser: widget.loginUser, onSaveMiscellaneous: (data) => handleItineraryUpdate("Miscellaneous", data), selectedItem: selectedItem, selectedIndex: selectedIndex, ); break; case "Accomodation": 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": selectedWidget = ForexScreen( onClose: handleClose, apiData: widget.apiData, loginUser: widget.loginUser, apiCountryData: widget.apiCountryData, onSaveForex: (data) => handleItineraryUpdate("Forex", data), selectedItem: selectedItem, flightData: itineraryData["Flight"]!, ); break; case "Flight": default: selectedWidget = FlightScreen( key: widget.flightScreenKey, tripTypeNotifier: widget.tripTypeNotifier, hasAction: widget.hasAction, tripType: widget.tripType, // onClose: handleMClose, onClose: handleClose, loginUser: widget.loginUser, onSaveFlight: (data) => handleItineraryUpdate("Flight", data), apiData: widget.apiData, apiDataForClass: widget.apiDataForClass, selectedItem: selectedItem, flightData: itineraryData["Flight"]!, ); break; } return ResponsiveBuilder( builder: (context, sizingInfo) { bool isMobile = sizingInfo.isMobile; return Stack( clipBehavior: Clip.none, children: [ // Second container (yellow box) Container( margin: EdgeInsets.only( top: 40, ), // Push it down to make room for the tab bar padding: EdgeInsets.only(top: 12, bottom: 12, left: 3, right: 3), decoration: BoxDecoration( color: Colors.white, // Card background // color: Colors.yellow.shade50, // Card background // color: Color(0xFFF9F9F9), // Slightly lighter than white // border: Border.all(color: Color(0xFFE6E7F5), width: 1.3), border: Border.all(color: Color(0xFFE6E7F5), width: 1.2), borderRadius: BorderRadius.circular(8), boxShadow: [ BoxShadow( // color: Color(0x0D000000), // 5% opacity black color: Colors.black12, // 5% opacity black blurRadius: 5, offset: Offset(0, 0.2), ), ], ), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ SizedBox(height: 2), isSelected ? selectedWidget : selectedListWidget, ], ), ), // First container (tab bar) — positioned above Positioned( top: 0, left: MediaQuery.of(context).size.width * 0.05, right: MediaQuery.of(context).size.width * 0.05, child: Container( padding: EdgeInsets.symmetric(vertical: 2, horizontal: 2), decoration: BoxDecoration( color: Colors.white, // Card background // color: thridColor, // Card background borderRadius: BorderRadius.circular(40), // color: Color(0xFFE6E7F5) // border: Border.all(color: Colors.black12, width: 1.3), border: Border.all(color: Color(0xFFE6E7F5), width: 1.2), boxShadow: [ BoxShadow( color: Colors.black12, // color: Color(0x0D000000), // 5% opacity black blurRadius: 10, offset: Offset(0, 0.2), ), ], ), child: Row( children: [ isMobile ? IconButton( icon: Icon( Icons.arrow_back_ios, color: Colors.black54, // color: _canScrollLeft ? Colors.black : Colors.grey, ), onPressed: _canScrollLeft ? () { _scrollBy(-100); } : null, ) : SizedBox.shrink(), Expanded( child: isMobile ? SingleChildScrollView( controller: _scrollController, scrollDirection: Axis.horizontal, child: Row(children: _buildOptions()), ) : Row( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: _buildOptions(), ), ), isMobile ? IconButton( icon: Icon( Icons.arrow_forward_ios, color: Colors.black54, // color: _canScrollRight ? Colors.black : Colors.grey, ), onPressed: _canScrollRight ? () { _scrollBy(100); } : null, ) : SizedBox.shrink(), ], ), ), ), ], ); }, ); } void _scrollBy(double offset) { if (!_scrollController.hasClients) return; final targetOffset = (_scrollController.offset + offset).clamp( _scrollController.position.minScrollExtent, _scrollController.position.maxScrollExtent, ); _scrollController.animateTo( targetOffset, duration: Duration(milliseconds: 300), curve: Curves.easeInOut, ); } bool hasValidItineraryEntries() { if (ServicesChoosed == null || ServicesChoosed!.isEmpty) return false; for (var service in ServicesChoosed!) { final serviceName = service['name']; final entries = itineraryData[serviceName]; // Check if there is at least one active entry (is_active == 1) final hasActive = entries?.any((entry) => entry['is_active'] == 1) ?? false; if (!hasActive) { return false; // Fail fast if any one service has no active entries } } return true; // All selected services have at least one active entry } List _buildOptions() { if (ServicesChoosed == null && !hasValidItineraryEntries()) return []; if (ServicesChoosed != null && ServicesChoosed!.isNotEmpty && selectedListOption == "") { WidgetsBinding.instance.addPostFrameCallback((_) { setState(() { selectedListOption = ServicesChoosed!.first['name']; isSelected = false; }); }); } // if (ServicesChoosed != null && // ServicesChoosed!.isNotEmpty && // selectedListOption == "") { // selectedListOption = ServicesChoosed!.first['name']; // isSelected = false; // } return ServicesChoosed!.map((service) { final serviceName = service['name']; final serviceEntries = itineraryData[serviceName]; final hasActive = serviceEntries?.any((entry) => entry['is_active'] == "1") ?? false; print("Service1: $serviceName"); print("Entries1: $serviceEntries"); print("Has Active1: $hasActive"); return Padding( padding: const EdgeInsets.only(right: 20.0), child: _buildOption( service, hasActive, // itineraryData[service['name']]?.isNotEmpty ?? false, ), ); }).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 = selectedActiveTab == name; // bool isOptionSelected = // hasData ? selectedListOption == name : selectedOption == name; return GestureDetector( onTap: () { 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( // border: Border( // bottom: BorderSide( // color: isOptionSelected ? Color(0xFF114D8B) : Colors.transparent, // width: 2, // ), // ), ), padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 3), child: Column( children: [ DottedBorder( options: RoundedRectDottedBorderOptions( dashPattern: [1, 3], strokeWidth: 1.5, radius: Radius.circular(46), color: isOptionSelected ? secondColor! : Colors.transparent, padding: EdgeInsets.all(3.5), ), child: Container( decoration: BoxDecoration( color: isOptionSelected ? secondColor : Colors.white, borderRadius: BorderRadius.circular(20.0), ), padding: const EdgeInsets.all(3.0), child: iconUrl.isNotEmpty ? Image.network( iconUrl, width: 15, height: 15, errorBuilder: (context, error, stackTrace) { return Icon( fallbackIcon, size: 20, color: isOptionSelected ? Colors.white : Colors.black87, // color: // isOptionSelected // ? Color(0xFF114D8B) // : Color(0xFF475569), ); }, ) : Icon( fallbackIcon, size: 20, color: isOptionSelected ? Colors.white : Colors.black87, // color: // isOptionSelected // ? Color(0xFF114D8B) // : Color(0xFF475569), ), ), ), SizedBox(height: 1), Row( children: [ Text( name, // style: GoogleFonts.poppins( fontSize: 12, // fontWeight: FontWeight.w600, // color: Color(0xFF575A74)) style: GoogleFonts.poppins( fontSize: 12, color: Colors.black, // color: // isOptionSelected // ? Color(0xFF114D8B) // : Color(0xFF475569), // fontFamily: "Inter", fontWeight: isOptionSelected ? FontWeight.w700 : FontWeight.w400, ), ), SizedBox(width: 4), if (hasData) Icon(Icons.circle, size: 8, color: Colors.green), ], ), ], ), ), ); } // Widget _buildOption1( // Map service, // bool hasData, // ) // { // String name = service['name']; // String iconUrl = service['icon']; // Can be empty string // // Optional: define local icon fallback if iconUrl is empty // IconData fallbackIcon = _getLocalIconForService(name); // // final idMap = {"service_id": service['service_id'].toString()}; // // final isSelected = selectedServiceIds.contains(idMap); // // String serviceId = service['service_id'].toString(); // // bool isSelected = selectedServiceIds.contains(serviceId); // // bool isSelected = // // selectedServiceIds.any((item) => item["service_id"] == serviceId); // // return GestureDetector( // onTap: () { // setState(() { // selectedListOption = name; // isSelected = false; // }); // }, // child: Row(children: [ // iconUrl.isNotEmpty // ? Image.network( // iconUrl, // width: 18, // height: 18, // errorBuilder: (context, error, stackTrace) { // return Icon( // fallbackIcon, // size: 18, // color: selectedListOption == name // ? Color(0xFF114D8B) // : Color(0xFF475569), // ); // }, // ) // : Icon( // fallbackIcon, // size: 18, // color: selectedListOption == name // ? Color(0xFF114D8B) // : Color(0xFF475569), // ), // // SizedBox(width: 2), // Text( // name, // style: TextStyle( // fontSize: 14, // // color: selectedListOption == title ? Colors.blueAccent : Color(0xFF575A74), // color: selectedListOption == name // ? Color(0xFF114D8B) // : Color(0xFF475569), // fontFamily: "Archivo", // fontWeight: selectedListOption == name // ? FontWeight.bold // : FontWeight.w500), // // fontWeight: selectedListOption == title ? FontWeight.bold : FontWeight.normal,)), // ), // // SizedBox(width: 2), // // if (selectedListOption == title && widget.isViewMode == false) // if (hasData) // Icon(Icons.circle, size: 8, color: Colors.green // // color: Colors.grey, // ) // // Container( // // height: 10, // // width: 10, // // // decoration: BoxDecoration( // // // shape: BoxShape.circle, // // // border: Border.all(color: Colors.green, width: 1.5), // // // ), // // child:), // ]), // ); // } IconData _getLocalIconForService(String name) { switch (name.toLowerCase()) { case 'flight': return Icons.flight_takeoff_outlined; case 'train': return Icons.train_outlined; case 'bus': return Icons.directions_bus_rounded; case 'taxi': return Icons.local_taxi_outlined; case 'accomodation': return Icons.local_hotel_outlined; case 'forex': return Icons.attach_money_outlined; case 'insurance': return Icons.list_alt_outlined; case 'visa': return Icons.badge_outlined; case 'miscellaneous': return Icons.card_giftcard_outlined; default: return Icons.circle_notifications; } } // // List _buildOptions1() { // return [ // _buildOption("Flight", itineraryData["Flight"]?.isNotEmpty ?? false), // SizedBox(width: 20), // _buildOption("Taxi", itineraryData["Taxi"]?.isNotEmpty ?? false), // SizedBox(width: 20), // _buildOption("Train", itineraryData["Train"]?.isNotEmpty ?? false), // SizedBox(width: 20), // _buildOption("Bus", itineraryData["Bus"]?.isNotEmpty ?? false), // SizedBox(width: 20), // _buildOption( // "Accomodation", itineraryData["Accomodation"]?.isNotEmpty ?? false), // SizedBox(width: 20), // _buildOption("Forex", itineraryData["Forex"]?.isNotEmpty ?? false), // SizedBox(width: 20), // _buildOption( // "Insurance", itineraryData["Insurance"]?.isNotEmpty ?? false), // SizedBox(width: 20), // _buildOption("Visa", itineraryData["Visa"]?.isNotEmpty ?? false), // SizedBox(width: 20), // _buildOption( // "Miscellaneous", itineraryData["Miscellaneous"]?.isNotEmpty ?? false), // ]; // } // // // Widget _buildOption1(String title, bool hasData) { // return GestureDetector( // onTap: () { // setState(() { // selectedListOption = title; // isSelected = false; // }); // }, // child: Row(children: [ // if (hasData) // Icon( // Icons.circle_notifications, // color: Colors.green, // size: 10, // ), // SizedBox(width: 5), // Text(title, // style: TextStyle( // fontSize: 13, // color: selectedListOption == title // ? Colors.blueAccent // : Color(0xFF575A74), // fontWeight: selectedListOption == title // ? FontWeight.bold // : FontWeight.normal, // )), // SizedBox(width: 5), // SizedBox(width: 5), // if (selectedListOption == title && widget.isViewMode == false) // GestureDetector( // onTap: () { // setState(() { // selectedOption = title; // isSelected = true; // selectedItem = null; // }); // }, // child: Icon( // Icons.add_circle, // color: Colors.blueAccent, // size: 18, // ), // ), // ]), // ); // } }