ts-tat/lib/Screens/plans/dynamic_itinerary_stepper.dart
venbaittech cf23f4f936 BUG_FIX
2025-07-22 14:46:43 +05:30

1380 lines
45 KiB
Dart

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 '../../services/apiService.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<String, dynamic>? apiData;
final Map<String, dynamic>? apiDataForClass;
final List<dynamic>? apiCountryData;
final String? loginUser;
final Function(String, List<Map<String, dynamic>>)
onItineraryUpdate; // Updated Signature
final void Function(bool)? onExeptionalClass;
final Map<String, dynamic> selectedPlanData;
final bool isViewMode;
final GlobalKey<FlightScreenState> flightScreenKey;
final ValueNotifier<String?> 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<DynamicItinerary> {
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<String, dynamic>? selectedItem;
int? selectedIndex;
List<dynamic>? selectedAllServices;
List<Map<String, String>> selectedOrgServiceIds = [];
List<dynamic>? ServicesChoosed;
List<String> filledItineraryKeys = [];
// List<Map<String, dynamic>> miscellaneousList = [];
Map<String, List<Map<String, dynamic>>> itineraryData = {
"Train": [],
"Bus": [],
"Taxi": [],
"Miscellaneous": [],
"Flight": [],
"Accomodation": [],
"Insurance": [],
"Visa": [],
"Forex": [],
};
@override
void initState() {
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;
// });
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
}
}
Future<void> loadAllServices() async {
try {
final result = await apiService.fetchAllServices();
setState(() {
selectedAllServices = result;
});
print("Fetched services Order: $selectedAllServices");
} catch (e) {
print('Error fetching role list: $e');
}
}
Future<void> loadOrgSelectedAlServices() async {
try {
final result = await apiService.fetchOrganization();
if (result != null && result is Map<String, dynamic>) {
final rawServices = result['services_ids'];
if (rawServices != null && rawServices is String) {
try {
List<dynamic> decoded = json.decode(rawServices);
List<Map<String, String>> 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');
}
}
List<String> 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 [];
}
}
// Future<void> 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<void> updateSelectedServices() async {
await loadAllServices();
await loadOrgSelectedAlServices();
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() {
// Check if selectedPlanData has itinerary data
if (hasAnyItineraryData()) {
print("selectedPlanData HAS DATA");
setState(() {
itineraryData = {
"Train": List<Map<String, dynamic>>.from(
widget.selectedPlanData['train'] ?? [],
),
"Bus": List<Map<String, dynamic>>.from(
widget.selectedPlanData['bus'] ?? [],
),
"Taxi": List<Map<String, dynamic>>.from(
widget.selectedPlanData['taxi'] ?? [],
),
"Miscellaneous": List<Map<String, dynamic>>.from(
widget.selectedPlanData['miscellaneous'] ?? [],
),
"Flight": List<Map<String, dynamic>>.from(
widget.selectedPlanData['flight'] ?? [],
),
"Accomodation": List<Map<String, dynamic>>.from(
widget.selectedPlanData['accomodation'] ?? [],
),
"Insurance": List<Map<String, dynamic>>.from(
widget.selectedPlanData['insurance'] ?? [],
),
"Visa": List<Map<String, dynamic>>.from(
widget.selectedPlanData['visa'] ?? [],
),
"Forex": List<Map<String, dynamic>>.from(
widget.selectedPlanData['forex'] ?? [],
),
};
});
} else {
print("No itinerary data available");
}
}
bool hasAnyItineraryData() {
List<String> 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<String, dynamic> newData) {
setState(() {
if (!itineraryData.containsKey(type)) {
itineraryData[type] = []; // Initialize if null
}
List<Map<String, dynamic>> 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<String, dynamic> data) {
setState(() {
// if(!itineraryData.containsKey(type)){
// return;
// }
if (!itineraryData.containsKey(type)) {
itineraryData[type] = []; // Initialize if null
}
List<Map<String, dynamic>> 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<String, dynamic> 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.all(12),
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<Widget> _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<String, dynamic> 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<String, dynamic> 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<Widget> _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,
// ),
// ),
// ]),
// );
// }
}