ui changes

This commit is contained in:
venbaittech 2025-04-30 19:46:53 +05:30
parent 5c628282c6
commit 36975e716f
9 changed files with 992 additions and 312 deletions

View File

@ -9,8 +9,11 @@ import '../../widgets/custom_text_field.dart';
import '../../widgets/custom_text_itnerary_sub.dart';
class FlightScreen extends StatefulWidget {
final bool hasAction;
final String? tripType;
final List<Map<String, dynamic>> flightData;
final Map<String, dynamic>? apiData;
final Map<String, dynamic>? apiDataForClass;
final String? loginUser;
final Function(bool) onClose;
final Function(Map<String, dynamic>) onSaveFlight;
@ -22,7 +25,10 @@ class FlightScreen extends StatefulWidget {
required this.onClose,
required this.onSaveFlight,
required this.selectedItem,
required this.flightData});
required this.flightData,
required this.hasAction,
this.tripType,
this.apiDataForClass});
@override
_FlightScreenState createState() => _FlightScreenState();
@ -32,6 +38,10 @@ class _FlightScreenState extends State<FlightScreen> {
ApiService apiService = ApiService();
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
bool isCountryLoading = true;
late Map<String, String> countryMap;
late List<String> countryCodes;
late ValueNotifier<String?> flightFirstTripDateNotifier;
late ValueNotifier<String?> flightLastTripDateNotifier;
@ -42,6 +52,8 @@ class _FlightScreenState extends State<FlightScreen> {
String? selectedTripType;
Map<int, String?> selectedClasses = {}; // Store class selection for each trip
Map<int, String?> selectedFrom = {}; // Store class selection for each trip
Map<int, String?> selectedTo = {}; // Store class selection for each trip
String? selectedvisa_available;
int multiTripRowCount = 1;
@ -66,7 +78,7 @@ class _FlightScreenState extends State<FlightScreen> {
List<TextEditingController> controllers = []; // Dynamic controllers
Map<String, String> errorMessages = {};
// forex_pre_paid_card_number
@override
void initState() {
super.initState();
@ -120,7 +132,7 @@ class _FlightScreenState extends State<FlightScreen> {
textControllers["_time${i}Controller"]
?.addListener(() => _clearError("time_$i"));
}
// loadCountryList();
loadCountryList();
flightFirstTripDateNotifier = ValueNotifier<String?>(null);
flightLastTripDateNotifier = ValueNotifier<String?>(null);
@ -161,15 +173,57 @@ class _FlightScreenState extends State<FlightScreen> {
};
}
//
// Future<void> loadCountryList() async {
// setState(() {
// isCountryLoading = true;
// });
//
// final result = await apiService.fetchFlightsCountryList();
//
// if (result is List) {
// countryList =
// result.map((item) => Map<String, dynamic>.from(item)).toList();
// countryMap = {
// for (var item in countryList)
// if (item['country_code'] != null && item['country_name'] != null)
// item['country_code'] as String: item['country_name'] as String
// };
// countryCodes = countryMap.keys.toList();
// } else {
// countryList = [];
// countryMap = {};
// countryCodes = [];
// }
//
// setState(() {
// isCountryLoading = false;
// });
// }
Future<void> loadCountryList() async {
setState(() {
isCountryLoading = true;
});
final result = await apiService.fetchFlightsCountryList();
if (result is List) {
countryList =
result.map((item) => Map<String, dynamic>.from(item)).toList();
} else {
countryList = []; // fallback or throw error
print("ResultCountry : $result");
// Create a map: Country_Code -> "City, Airport"
Map<String, String> tempCountryMap = {};
for (var country in result) {
String city = country['City'] ?? '';
String airport = country['Airport'] ?? '';
String displayName = '${country['City']} - ${country['Airport']}';
tempCountryMap[country['Code']] = displayName;
}
setState(() {
countryMap = tempCountryMap; // Update the map
isCountryLoading = false;
});
}
int getRowCount() {
@ -285,8 +339,12 @@ class _FlightScreenState extends State<FlightScreen> {
for (int i = 1; i <= rowCount; i++) {
final trip = {
"class": selectedClasses[i],
"from_place": textControllers["_from${i}Controller"]?.text ?? "",
"to_place": textControllers["_to${i}Controller"]?.text ?? "",
// "from_place": countryMap[selectedFrom[i]],
"from_place": selectedFrom[i],
"to_place": selectedTo[i],
// "from_place": textControllers["_from${i}Controller"]?.text ?? "",
// "to_place": textControllers["_to${i}Controller"]?.text ?? "",
"date": textControllers["_date${i}Controller"]?.text ?? "",
"time": textControllers["_time${i}Controller"]?.text ?? "",
"created_by": widget.loginUser,
@ -377,10 +435,12 @@ class _FlightScreenState extends State<FlightScreen> {
int index = i + 1; // Use 1-based indexing to match the form
selectedClasses[index] = trip["class"].toString();
textControllers["_from${index}Controller"] =
TextEditingController(text: trip["from_place"]);
textControllers["_to${index}Controller"] =
TextEditingController(text: trip["to_place"]);
selectedFrom[index] = trip["from_place"].toString();
selectedTo[index] = trip["to_place"].toString();
// textControllers["_from${index}Controller"] =
// TextEditingController(text: trip["from_place"]);
// textControllers["_to${index}Controller"] =
// TextEditingController(text: trip["to_place"]);
textControllers["_date${index}Controller"] =
TextEditingController(text: trip["date"]);
textControllers["_time${index}Controller"] =
@ -430,12 +490,19 @@ class _FlightScreenState extends State<FlightScreen> {
// Loop through each trip row and validate required fields
for (int i = 1; i <= rowCount; i++) {
if (textControllers["_from${i}Controller"]?.text.trim().isEmpty ?? true) {
// if (textControllers["_from${i}Controller"]?.text.trim().isEmpty ?? true) {
// errorMessages["from_place_$i"] = "Required";
// }
if (selectedFrom[i] == null) {
errorMessages["from_place_$i"] = "Required";
}
if (textControllers["_to${i}Controller"]?.text.trim().isEmpty ?? true) {
if (selectedTo[i] == null) {
errorMessages["to_place_$i"] = "Required";
}
// if (textControllers["_to${i}Controller"]?.text.trim().isEmpty ?? true) {
// errorMessages["to_place_$i"] = "Required";
// }
if (textControllers["_date${i}Controller"]?.text.trim().isEmpty ?? true) {
errorMessages["date_$i"] = "Required";
}
@ -468,8 +535,8 @@ class _FlightScreenState extends State<FlightScreen> {
int newIndex = 1;
for (int i = 1; i <= multiTripRowCount + 1; i++) {
if (i == index) continue; // Skip the deleted one
updatedTextControllers["_from${newIndex}Controller"] =
textControllers["_from${i}Controller"]!;
// updatedTextControllers["_from${newIndex}Controller"] =
// textControllers["_from${i}Controller"]!;
updatedTextControllers["_to${newIndex}Controller"] =
textControllers["_to${i}Controller"]!;
updatedTextControllers["_date${newIndex}Controller"] =
@ -491,6 +558,27 @@ class _FlightScreenState extends State<FlightScreen> {
}
selectedClasses = updatedClasses; // Update the map
// Shift the selectedFrom map BEFORE removing the index
Map<int, String?> updatedFrom = {};
newIndex = 1;
for (int i = 1; i <= selectedFrom.length; i++) {
if (i == index) continue; // Skip the one being deleted
updatedFrom[newIndex] = selectedFrom[i];
newIndex++;
}
selectedFrom = updatedFrom;
// Shift the selectedTo map BEFORE removing the index
Map<int, String?> updatedTo = {};
newIndex = 1;
for (int i = 1; i <= selectedTo.length; i++) {
if (i == index) continue; // Skip the one being deleted
updatedTo[newIndex] = selectedTo[i];
newIndex++;
}
selectedTo = updatedTo;
print("FlightData - $flightsData");
print("Updated Trips: $multiTripRowCount");
print("Updated Classes: $selectedClasses");
@ -916,7 +1004,7 @@ class _FlightScreenState extends State<FlightScreen> {
}
List<Widget> _buildSecondRow(bool isDesktop, int index) {
List<dynamic> purposeList = widget.apiData?['flight_class'] ?? [];
List<dynamic> purposeList = widget.apiDataForClass?['flight_class'] ?? [];
List<DropdownMenuItem<String>> dropdownItems = purposeList
.map((item) => DropdownMenuItem<String>(
@ -1020,18 +1108,18 @@ class _FlightScreenState extends State<FlightScreen> {
}
}
late Map<String, String> countryMap; // Mapping country_code -> country_name
late List<String> countryCodes; // List of country codes
// late Map<String, String> countryMap; // Mapping country_code -> country_name
// late List<String> countryCodes; // List of country codes
// countryMap = {
// for (var item in countryList)
// if (item['country_code'] != null && item['country_name'] != null)
// item['country_code'] as String: item['country_name'] as String
// };
countryMap = {
for (var item in countryList)
item['country_code'] as String: item['country_name'] as String
};
// Extract only country codes for processing
countryCodes = countryMap.keys.toList();
selectedCountry ??= null;
// // Extract only country codes for processing
// countryCodes = countryMap.keys.toList();
//
// selectedCountry ??= null;
return [
Column(
@ -1053,28 +1141,31 @@ class _FlightScreenState extends State<FlightScreen> {
// : MediaQuery.of(context).size.width * 0.66,
child: SizedBox(
height: 40,
child: DropdownButtonFormField<String>(
focusNode: focusNodes["_class${index}FocusNode"],
// focusNode: _tripTypeFocusNode, // Assign the correct focus node
// controller: _hotelNameController,
value: selectedClasses[index],
style: TextStyle(fontSize: 12),
decoration: InputDecoration(
border: InputBorder.none,
contentPadding:
EdgeInsets.symmetric(horizontal: 10), // Proper padding
),
onChanged: purposeList.isNotEmpty
? (newValue) {
setState(() {
selectedClasses[index] = newValue;
});
child: isCountryLoading
? const Center(child: CircularProgressIndicator())
: DropdownButtonFormField<String>(
focusNode: focusNodes["_class${index}FocusNode"],
// focusNode: _tripTypeFocusNode, // Assign the correct focus node
// controller: _hotelNameController,
value: selectedClasses[index],
print(selectedClasses[index]);
}
: null,
items: dropdownItems,
),
style: TextStyle(fontSize: 12),
decoration: InputDecoration(
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(
horizontal: 10), // Proper padding
),
onChanged: purposeList.isNotEmpty
? (newValue) {
setState(() {
selectedClasses[index] = newValue;
});
print(selectedClasses[index]);
}
: null,
items: dropdownItems,
),
),
),
],
@ -1099,72 +1190,75 @@ class _FlightScreenState extends State<FlightScreen> {
),
SizedBox(height: 5),
CustomTextFieldItnerarySubWrapper(
// isFocused: _fromFocus,
isFocused: focusStates["_from${index}Focused"] ?? false,
// isFocused: focusStates["_from${fieldIndex}Focused"] ?? false,
isDesktop: isDesktop,
// child: SizedBox(
// height: 40,
// child: DropdownSearch<String>(
// selectedItem: countryMap[selectedCountry],
// popupProps: PopupProps.menu(
// showSearchBox: true, // Enables search functionality
// searchFieldProps: TextFieldProps(
// decoration: InputDecoration(
// hintText: "Search Country...",
// contentPadding: EdgeInsets.symmetric(horizontal: 10),
// ),
// ),
// ),
// items: countryMap.values.toList(),
// dropdownDecoratorProps: DropDownDecoratorProps(
// dropdownSearchDecoration: InputDecoration(
// border: InputBorder.none,
// contentPadding: EdgeInsets.symmetric(
// horizontal: 1,
// ),
// ),
// ),
// dropdownBuilder: (context, selectedItem) => Align(
// // Center-align selected item
// alignment: Alignment.centerLeft,
// child: Text(
// selectedItem ?? "Select Country",
// style: TextStyle(fontSize: 12),
// ),
// ),
// onChanged: (String? newValue) {
// setState(() {
// // Find the country_code based on selected country_name
// selectedCountry = countryMap.entries
// .firstWhere((entry) => entry.value == newValue)
// .key;
//
// if (selectedCountry!.isNotEmpty) {
// errorMessages.remove("country_code");
// }
// });
// },
// ),
// )
// isFocused: _fromFocus,
isFocused: focusStates["_from${index}Focused"] ?? false,
// isFocused: focusStates["_from${fieldIndex}Focused"] ?? false,
isDesktop: isDesktop,
child: SizedBox(
height: 40,
child: isCountryLoading
? Center(child: CircularProgressIndicator())
: DropdownSearch<String>(
selectedItem: selectedFrom[index] != null
? countryMap[selectedFrom[index]]
: null,
popupProps: PopupProps.menu(
showSearchBox: true,
searchFieldProps: TextFieldProps(
decoration: InputDecoration(
hintText: "Search ...",
contentPadding:
EdgeInsets.symmetric(horizontal: 10),
),
),
),
items: countryMap.values.toList(),
dropdownDecoratorProps: DropDownDecoratorProps(
dropdownSearchDecoration: InputDecoration(
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(horizontal: 1),
),
),
dropdownBuilder: (context, selectedItem) => Align(
alignment: Alignment.centerLeft,
child: Text(
selectedItem ?? "Select",
style: TextStyle(fontSize: 12),
),
),
onChanged: (String? newValue) {
setState(() {
// selectedFrom[index] = countryMap.entries
// .firstWhere((entry) => entry.value == newValue)
// .key;
child: SizedBox(
height: 40,
child: TextField(
// focusNode: _fromFocusNode,
focusNode: focusNodes["_from${index}FocusNode"],
controller: textControllers["_from${index}Controller"],
style: const TextStyle(fontSize: 12),
decoration: const InputDecoration(
labelText: "From",
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16),
),
selectedFrom[index] = countryMap.entries
.firstWhere((entry) => entry.value == newValue)
.key;
print(selectedFrom[index]);
});
},
),
)
// child: SizedBox(
// height: 40,
// child: TextField(
// // focusNode: _fromFocusNode,
// focusNode: focusNodes["_from${index}FocusNode"],
// controller: textControllers["_from${index}Controller"],
// style: const TextStyle(fontSize: 12),
// decoration: const InputDecoration(
// labelText: "From",
// labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
// floatingLabelBehavior: FloatingLabelBehavior.never,
// border: InputBorder.none,
// contentPadding: EdgeInsets.symmetric(vertical: 16),
// ),
// ),
// ),
),
),
),
if (errorMessages["from_place_$index"] != null) ...[
SizedBox(height: 5), // Space before error message
Text(
@ -1192,24 +1286,51 @@ class _FlightScreenState extends State<FlightScreen> {
),
SizedBox(height: 5),
CustomTextFieldItnerarySubWrapper(
isFocused: focusStates["_to${index}Focused"] ?? false,
isDesktop: isDesktop,
child: SizedBox(
height: 40,
child: TextField(
focusNode: focusNodes["_to${index}FocusNode"],
controller: textControllers["_to${index}Controller"],
style: const TextStyle(fontSize: 12),
decoration: const InputDecoration(
labelText: "To",
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16),
),
),
),
),
isFocused: focusStates["_to${index}Focused"] ?? false,
isDesktop: isDesktop,
child: SizedBox(
height: 40,
child: isCountryLoading
? Center(child: CircularProgressIndicator())
: DropdownSearch<String>(
selectedItem: selectedTo[index] != null
? countryMap[selectedTo[index]]
: null,
popupProps: PopupProps.menu(
showSearchBox: true,
searchFieldProps: TextFieldProps(
decoration: InputDecoration(
hintText: "Search ...",
contentPadding:
EdgeInsets.symmetric(horizontal: 10),
),
),
),
items: countryMap.values.toList(),
dropdownDecoratorProps: DropDownDecoratorProps(
dropdownSearchDecoration: InputDecoration(
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(horizontal: 1),
),
),
dropdownBuilder: (context, selectedItem) => Align(
alignment: Alignment.centerLeft,
child: Text(
selectedItem ?? "Select Country",
style: TextStyle(fontSize: 12),
),
),
onChanged: (String? newValue) {
setState(() {
selectedTo[index] = countryMap.entries
.firstWhere((entry) => entry.value == newValue)
.key;
print(selectedTo[index]);
});
},
),
)),
if (errorMessages["to_place_$index"] != null) ...[
SizedBox(height: 5), // Space before error message
Text(
@ -1293,7 +1414,7 @@ class _FlightScreenState extends State<FlightScreen> {
CustomTextFieldItnerarySubWrapper(
isFocused: focusStates["_timeFocused"] ?? false,
isDesktop: isDesktop,
width: isDesktop ? MediaQuery.of(context).size.width * 0.08 : null,
width: isDesktop ? MediaQuery.of(context).size.width * 0.1 : null,
child: SizedBox(
height: 40,
child: GestureDetector(

View File

@ -1,12 +1,15 @@
import 'package:dropdown_search/dropdown_search.dart';
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:responsive_builder/responsive_builder.dart';
import '../../services/apiService.dart';
import '../../widgets/custom_text_field.dart';
import '../../widgets/custom_text_itnerary_sub.dart';
class TrainScreen extends StatefulWidget {
final Map<String, dynamic>? apiData;
final Map<String, dynamic>? apiDataForClass;
final Function(Map<String, dynamic>) onSavetrain;
final Function(bool) onClose;
final Map<String, dynamic>? selectedItem;
@ -17,7 +20,8 @@ class TrainScreen extends StatefulWidget {
this.apiData,
required this.onSavetrain,
required this.selectedItem,
required this.loginUser});
required this.loginUser,
this.apiDataForClass});
@override
_TrainScreenState createState() => _TrainScreenState();
@ -26,6 +30,12 @@ class TrainScreen extends StatefulWidget {
class _TrainScreenState extends State<TrainScreen> {
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
ApiService apiService = ApiService();
bool isCountryLoading = true;
Map<String, String> countryMap = {}; // <--- instead of late
late List<String> countryCodes;
Map<String, String?> selectedValues = {};
final FocusNode _trainNoFocusNode = FocusNode();
@ -53,6 +63,8 @@ class _TrainScreenState extends State<TrainScreen> {
bool _commentsFocus = false;
String? selectedClass;
String? selectedFrom;
String? selectedTo;
Map<String, String> errorMessages = {};
@ -60,8 +72,10 @@ class _TrainScreenState extends State<TrainScreen> {
Map<String, dynamic> data = {
"train_no": _trainNoController.text,
"class": selectedClass,
"from_station": _fromController.text,
"to_station": _toController.text,
"from_station": selectedFrom,
// "from_station": _fromController.text,
"to_station": selectedTo,
// "to_station": _toController.text,
"date": _dateController.text,
"time": _timeController.text,
"comments": _trainCommentsController.text,
@ -128,7 +142,7 @@ class _TrainScreenState extends State<TrainScreen> {
_trainCommentsController = initController("comments");
_trainNoController = initController("train_no");
_fromController = initController("from_station");
// _fromController = initController("from_station");
_toController = initController("to_station");
_dateController = initController("date");
_timeController = initController("time");
@ -138,11 +152,50 @@ class _TrainScreenState extends State<TrainScreen> {
selectedClass = widget.selectedItem!["class"].toString();
}
// if (widget.selectedItem != null &&
// widget.selectedItem!["from_station"] != null) {
// final fromCode = widget.selectedItem!["from_station"].toString();
// if (countryMap.containsKey(fromCode)) {
// selectedFrom = fromCode;
// }
// }
if (widget.selectedItem != null &&
widget.selectedItem!["from_station"] != null) {
selectedFrom = widget.selectedItem!["from_station"].toString();
print("Selected From CODE = $selectedFrom");
}
if (widget.selectedItem != null &&
widget.selectedItem!["to_station"] != null) {
selectedTo = widget.selectedItem!["to_station"].toString();
print("Selected To CODE = $selectedTo");
}
// if (widget.selectedItem != null &&
// widget.selectedItem!["from_station"] != null) {
// String fromPlaceDisplay = widget.selectedItem!["from_station"].toString();
//
// selectedFrom = countryMap.entries
// .firstWhere((entry) => entry.value == fromPlaceDisplay,
// orElse: () => MapEntry('', '')) // avoid crash if not found
// .key;
//
// print("Selected From CODE = $selectedFrom");
// }
// if (widget.selectedItem != null &&
// widget.selectedItem!["to_station"] != null) {
// selectedClass = widget.selectedItem!["to_station"].toString();
// }
_trainNoController.addListener(() => _clearError("train_no"));
_fromController.addListener(() => _clearError("from_station"));
// _fromController.addListener(() => _clearError("from_station"));
_toController.addListener(() => _clearError("to_station"));
_dateController.addListener(() => _clearError("date"));
_timeController.addListener(() => _clearError("time"));
loadCountryList();
}
@override
@ -189,6 +242,32 @@ class _TrainScreenState extends State<TrainScreen> {
return errorMessages.isEmpty; // Valid if there are no errors
}
Future<void> loadCountryList() async {
setState(() {
isCountryLoading = true;
});
final result = await apiService.fetchFlightsCountryList();
print("ResultCountry : $result");
// Create a map: Country_Code -> "City, Airport"
Map<String, String> tempCountryMap = {};
for (var country in result) {
String city = country['City'] ?? '';
String airport = country['Airport'] ?? '';
String displayName = '${country['City']} - ${country['Airport']}';
tempCountryMap[country['Code']] = displayName;
}
setState(() {
countryMap = tempCountryMap; // Update the map
isCountryLoading = false;
});
}
void handleSave() {
print("Handle Save accomadationData $trainData");
@ -413,7 +492,7 @@ class _TrainScreenState extends State<TrainScreen> {
//----------------------------------------------
List<dynamic> purposeList = widget.apiData?['train_class'] ?? [];
List<dynamic> purposeList = widget.apiDataForClass?['train_class'] ?? [];
List<DropdownMenuItem<String>> dropdownItems = purposeList
.map((item) => DropdownMenuItem<String>(
@ -504,24 +583,84 @@ class _TrainScreenState extends State<TrainScreen> {
),
SizedBox(height: 5),
CustomTextFieldItnerarySubWrapper(
isFocused: _fromFocus,
isDesktop: isDesktop,
child: SizedBox(
height: 40,
child: TextField(
focusNode: _fromFocusNode,
controller: _fromController,
style: const TextStyle(fontSize: 12),
decoration: const InputDecoration(
labelText: "From",
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16),
),
isFocused: _fromFocus,
isDesktop: isDesktop,
child: SizedBox(
height: 40,
child: isCountryLoading
? Center(child: CircularProgressIndicator())
: DropdownSearch<String>(
// selectedItem: selectedFrom != null
// ? countryMap[selectedFrom]
// : null,
selectedItem: selectedFrom != null
? countryMap[
selectedFrom] // get the display value from code
: null,
popupProps: PopupProps.menu(
showSearchBox: true,
searchFieldProps: TextFieldProps(
decoration: InputDecoration(
hintText: "Search ...",
contentPadding:
EdgeInsets.symmetric(horizontal: 10),
),
),
),
items: countryMap.values.toList(),
dropdownDecoratorProps: DropDownDecoratorProps(
dropdownSearchDecoration: InputDecoration(
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(horizontal: 1),
),
),
dropdownBuilder: (context, selectedItem) => Align(
alignment: Alignment.centerLeft,
child: Text(
selectedItem ?? "Select",
style: TextStyle(fontSize: 12),
),
),
// onChanged: (String? newValue) {
// setState(() {
// // selectedFrom[index] = countryMap.entries
// // .firstWhere((entry) => entry.value == newValue)
// // .key;
//
// selectedFrom = countryMap.entries
// .firstWhere((entry) => entry.value == newValue)
// .key;
//
// print(selectedFrom);
// });
// },
onChanged: (String? newValue) {
setState(() {
selectedFrom = countryMap.entries
.firstWhere((entry) => entry.value == newValue)
.key;
});
},
),
)
// child: SizedBox(
// height: 40,
// child: TextField(
// focusNode: _fromFocusNode,
// controller: _fromController,
// style: const TextStyle(fontSize: 12),
// decoration: const InputDecoration(
// labelText: "From",
// labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
// floatingLabelBehavior: FloatingLabelBehavior.never,
// border: InputBorder.none,
// contentPadding: EdgeInsets.symmetric(vertical: 16),
// ),
// ),
// ),
),
),
),
if (errorMessages["from_station"] != null) ...[
SizedBox(height: 5), // Space before error message
Text(
@ -553,18 +692,45 @@ class _TrainScreenState extends State<TrainScreen> {
isDesktop: isDesktop,
child: SizedBox(
height: 40,
child: TextField(
focusNode: _toFocusNode,
controller: _toController,
style: const TextStyle(fontSize: 12),
decoration: const InputDecoration(
labelText: "To",
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16),
),
),
child: isCountryLoading
? Center(child: CircularProgressIndicator())
: DropdownSearch<String>(
selectedItem: selectedTo != null
? countryMap[
selectedTo] // get the display value from code
: null,
popupProps: PopupProps.menu(
showSearchBox: true,
searchFieldProps: TextFieldProps(
decoration: InputDecoration(
hintText: "Search ...",
contentPadding:
EdgeInsets.symmetric(horizontal: 10),
),
),
),
items: countryMap.values.toList(),
dropdownDecoratorProps: DropDownDecoratorProps(
dropdownSearchDecoration: InputDecoration(
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(horizontal: 1),
),
),
dropdownBuilder: (context, selectedItem) => Align(
alignment: Alignment.centerLeft,
child: Text(
selectedItem ?? "Select",
style: TextStyle(fontSize: 12),
),
),
onChanged: (String? newValue) {
setState(() {
selectedTo = countryMap.entries
.firstWhere((entry) => entry.value == newValue)
.key;
});
},
),
),
),
if (errorMessages["to_station"] != null) ...[
@ -649,7 +815,7 @@ class _TrainScreenState extends State<TrainScreen> {
CustomTextFieldItnerarySubWrapper(
isFocused: _timeFocus,
isDesktop: isDesktop,
width: isDesktop ? MediaQuery.of(context).size.width * 0.08 : null,
width: isDesktop ? MediaQuery.of(context).size.width * 0.1 : null,
child: SizedBox(
height: 40,
child: GestureDetector(

View File

@ -1,28 +1,106 @@
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
class FlightListWidget extends StatelessWidget {
import '../../services/apiService.dart';
class FlightListWidget extends StatefulWidget {
final bool hasAction;
final String? tripType;
final List<Map<String, dynamic>> flightList;
final Function(bool, Map<String, dynamic>, String) onOpen;
final Function(Map<String, dynamic>) onDeleteFlight;
final Map<String, dynamic>? apiData;
final Function(String, bool) onAddNew;
final bool isViewMode;
const FlightListWidget(
{super.key,
required this.flightList,
required this.onOpen,
required this.onDeleteFlight,
required this.onAddNew,
required this.apiData,
required this.isViewMode});
const FlightListWidget({
Key? key,
required this.flightList,
required this.onOpen,
required this.onDeleteFlight,
required this.onAddNew,
required this.apiData,
required this.isViewMode,
required this.hasAction,
this.tripType,
}) : super(key: key);
@override
_FlightListWidgetState createState() => _FlightListWidgetState();
}
class _FlightListWidgetState extends State<FlightListWidget> {
ApiService apiService = ApiService();
// late Map<String, String> countryMap;
Map<String, String> countryMap = {};
@override
void initState() {
super.initState();
loadCountryList(); // Call your method here
}
Future<void> loadCountryList() async {
final result = await apiService.fetchFlightsCountryList();
print("ResultCountry : $result");
// Create a map: Country_Code -> "City, Airport"
Map<String, String> tempCountryMap = {};
for (var country in result) {
String city = country['City'] ?? '';
String airport = country['Airport'] ?? '';
String displayName = '${country['City']} - ${country['Airport']}';
// String displayName = '${country['City']} | ${country['Airport']}';
tempCountryMap[country['Code']] = displayName;
}
setState(() {
countryMap = tempCountryMap; // Update the map
});
}
@override
Widget build(BuildContext context) {
final isDesktop = MediaQuery.of(context).size.width > 1024;
ApiService apiService = ApiService();
late Map<String, String> countryMap;
late List<String> countryCodes;
void checkClass() {
if (widget.hasAction) {
if (widget.tripType?.isNotEmpty == true) {
print("Teppp - $widget.tripType");
widget.onAddNew("Flight", true);
} else {
showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: const Text('Select Trip Type'),
content: const Text(
'Please select a trip type before adding a flight.'),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: const Text('OK'),
),
],
);
},
);
}
} else {
print("Teppp No - $widget.tripType");
widget.onAddNew("Flight", true);
}
}
return Padding(
padding: const EdgeInsets.all(16.0),
child: Container(
@ -39,15 +117,15 @@ class FlightListWidget extends StatelessWidget {
// style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
// ),
MouseRegion(
cursor: isViewMode
cursor: widget.isViewMode
? SystemMouseCursors.forbidden
: SystemMouseCursors.click,
child: GestureDetector(
onTap: isViewMode
onTap: widget.isViewMode
? null
: () {
checkClass();
print("New data");
onAddNew("Flight", true);
},
child: Row(
mainAxisSize:
@ -81,10 +159,10 @@ class FlightListWidget extends StatelessWidget {
Widget _buildData(BuildContext context, bool isDesktop) {
List<Map<String, dynamic>> filteredList =
flightList.where((item) => item["is_active"] == "1").toList();
widget.flightList.where((item) => item["is_active"] == "1").toList();
print("filteredList- $filteredList");
List<dynamic> visatypeList = apiData?['flight_class'] ?? [];
List<dynamic> visatypeList = widget.apiData?['flight_class'] ?? [];
String getRequestForClass(String? specialRequestKey) {
if (specialRequestKey == null) return "N/A";
@ -174,13 +252,13 @@ class FlightListWidget extends StatelessWidget {
),
Spacer(),
GestureDetector(
onTap: () => onOpen(true, item, "Flight"),
onTap: () => widget.onOpen(true, item, "Flight"),
child: Image.asset('assets/images/IconsImg/edit.png',
width: 20, height: 15),
),
SizedBox(width: 10),
GestureDetector(
onTap: () => onDeleteFlight(item),
onTap: () => widget.onDeleteFlight(item),
child: Image.asset('assets/images/IconsImg/delete.png',
width: 20, height: 15),
),
@ -224,6 +302,13 @@ class FlightListWidget extends StatelessWidget {
if (isDesktop)
if (item["trips"] != null && item["trips"].isNotEmpty)
...item["trips"].map<Widget>((trip) {
String fromPlaceCountry =
countryMap[trip["from_place"]?.toString()] ??
"Unknown Country";
String toPlaceCountry =
countryMap[trip["to_place"]?.toString()] ??
"Unknown Country";
return Padding(
padding: const EdgeInsets.symmetric(vertical: 4.0),
child: Row(
@ -240,7 +325,8 @@ class FlightListWidget extends StatelessWidget {
Expanded(
flex: 2,
child: Text(
"${trip["from_place"]?.toString()} - ${trip["to_place"]?.toString()}",
"$fromPlaceCountry (from) - (to) $toPlaceCountry",
// "${trip["from_place"]?.toString()} - ${trip["to_place"]?.toString()}",
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,

View File

@ -1,7 +1,11 @@
import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
class TrainListWidget extends StatelessWidget {
import '../../services/apiService.dart';
class TrainListWidget extends StatefulWidget {
final bool hasAction;
final String? tripType;
final List<Map<String, dynamic>> trainList;
final Function(bool, Map<String, dynamic>, String) onOpen;
final Function(Map<String, dynamic>) onDeleteTrain;
@ -10,14 +14,83 @@ class TrainListWidget extends StatelessWidget {
final Map<String, dynamic>? apiData;
const TrainListWidget({
super.key,
Key? key,
required this.trainList,
required this.onOpen,
required this.onDeleteTrain,
required this.onAddNew,
required this.isViewMode,
required this.apiData,
});
required this.hasAction,
this.tripType,
}) : super(key: key);
@override
_TrainListWidgetState createState() => _TrainListWidgetState();
}
class _TrainListWidgetState extends State<TrainListWidget> {
ApiService apiService = ApiService();
// late Map<String, String> countryMap;
Map<String, String> countryMap = {};
@override
void initState() {
super.initState();
loadCountryList(); // Call your method here
}
void checkClass() {
if (widget.hasAction) {
if (widget.tripType?.isNotEmpty == true) {
print("Teppp - $widget.tripType");
widget.onAddNew("Train", true);
} else {
showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: const Text('Select Trip Type'),
content: const Text(
'Please select a trip type before adding a Train.'),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: const Text('OK'),
),
],
);
},
);
}
} else {
print("Teppp No - $widget.tripType");
widget.onAddNew("Flight", true);
}
}
Future<void> loadCountryList() async {
final result = await apiService.fetchFlightsCountryList();
print("ResultCountry : $result");
// Create a map: Country_Code -> "City, Airport"
Map<String, String> tempCountryMap = {};
for (var country in result) {
String city = country['City'] ?? '';
String airport = country['Airport'] ?? '';
String displayName = '${country['City']} - ${country['Airport']}';
// String displayName = '${country['City']} | ${country['Airport']}';
tempCountryMap[country['Code']] = displayName;
}
setState(() {
countryMap = tempCountryMap; // Update the map
});
}
@override
Widget build(BuildContext context) {
@ -35,20 +108,20 @@ class TrainListWidget extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
MouseRegion(
cursor: isViewMode
cursor: widget.isViewMode
? SystemMouseCursors.forbidden
: SystemMouseCursors.click,
child: MouseRegion(
cursor: isViewMode
cursor: widget.isViewMode
? SystemMouseCursors.forbidden
: SystemMouseCursors.click,
child: GestureDetector(
onTap: isViewMode
onTap: widget.isViewMode
? null
: () {
print("New data");
onAddNew("Train", true);
checkClass();
},
child: Row(
mainAxisSize:
@ -130,10 +203,10 @@ class TrainListWidget extends StatelessWidget {
Widget _buildData(BuildContext context, bool isDesktop) {
List<Map<String, dynamic>> filteredList =
trainList.where((item) => item["is_active"] == "1").toList();
widget.trainList.where((item) => item["is_active"] == "1").toList();
// print("filteredList- $filteredList");
List<dynamic> trainClassList = apiData?['train_class'] ?? [];
List<dynamic> trainClassList = widget.apiData?['train_class'] ?? [];
String getRequestForTrainClass(String? specialRequestKey) {
if (specialRequestKey == null) return "N/A";
@ -180,6 +253,11 @@ class TrainListWidget extends StatelessWidget {
itemBuilder: (context, index) {
final item = filteredList[index];
String fromPlaceCountry =
countryMap[item["from_station"].toString()] ?? "Unknown Country";
String toPlaceCountry =
countryMap[item["to_station"].toString()] ?? "Unknown Country";
return Container(
margin: EdgeInsets.symmetric(horizontal: 8, vertical: 6),
decoration: BoxDecoration(
@ -217,13 +295,13 @@ class TrainListWidget extends StatelessWidget {
),
Spacer(),
GestureDetector(
onTap: () => onOpen(true, item, "Train"),
onTap: () => widget.onOpen(true, item, "Train"),
child: Image.asset('assets/images/IconsImg/edit.png',
width: 20, height: 15),
),
SizedBox(width: 10),
GestureDetector(
onTap: () => onDeleteTrain(item),
onTap: () => widget.onDeleteTrain(item),
child: Image.asset('assets/images/IconsImg/delete.png',
width: 20, height: 15),
),
@ -284,7 +362,9 @@ class TrainListWidget extends StatelessWidget {
Expanded(
flex: 2,
child: Text(
"${item["from_station"]!} - ${(item["to_station"])}",
"$fromPlaceCountry (from) - (to) $toPlaceCountry",
// "${item["from_station"]!} - ${(item["to_station"])}",
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,

View File

@ -284,6 +284,9 @@ class CreateNewPlan extends StatefulWidget {
}
class CreateNewPlansState extends State<CreateNewPlan> {
final GlobalKey<DynamicItineraryState> dynamicItineraryKey =
GlobalKey<DynamicItineraryState>();
final ApiService apiService = ApiService();
final TextEditingController _tripTitleController = TextEditingController();
@ -311,12 +314,14 @@ class CreateNewPlansState extends State<CreateNewPlan> {
late Color layoutColorForUser;
Map<String, dynamic>? apiData; // Store API response here
Map<String, dynamic>? apiDataForClass; // Store API response here
List<dynamic>? apiCountryData;
List<dynamic>? apiCostData; // Store API response here
bool isLoading = true; // Track loading state
String? TripPlanAction;
bool showDomestic = false;
bool showInternational = false;
bool hasAction = true;
late Map<String, String> costCenterMap;
List<String> costCenterIds = [];
@ -511,6 +516,12 @@ class CreateNewPlansState extends State<CreateNewPlan> {
widget.selectedPlanData['miscellaneous'] ?? []);
});
if (widget.selectedPlanData['trip_type'] != null) {
int? tripId =
int.tryParse(widget.selectedPlanData['trip_type'].toString());
fetchTrainFlightClass(tripId!);
}
if (widget.selectedPlanData.containsKey('plan_id') &&
widget.selectedPlanData['plan_id'] != null) {
print("Plan ID exists: ${widget.selectedPlanData['plan_id']}");
@ -528,15 +539,19 @@ class CreateNewPlansState extends State<CreateNewPlan> {
if (TripPlanAction == "Plan Creation Not Allowed") {
showDomestic = false;
showInternational = false;
hasAction = false;
} else if (TripPlanAction == "Only Domestic Plan Creation Allowed") {
showDomestic = true;
showInternational = false;
hasAction = true;
} else if (TripPlanAction == "Only International Plan Creation Allowed") {
showDomestic = false;
showInternational = true;
hasAction = true;
} else if (TripPlanAction == "Both Type Plan Creation Allowed") {
showDomestic = true;
showInternational = true;
hasAction = true;
}
});
}
@ -764,6 +779,55 @@ class CreateNewPlansState extends State<CreateNewPlan> {
}
}
Future<void> fetchTrainFlightClass(int tripId) async {
final userId = (planUsrId?.toString().isNotEmpty == true)
? planUsrId.toString()
: (planTravlrId?.toString().isNotEmpty == true)
? planTravlrId.toString()
: '';
// final String apiUrldata = '$apiUrl/api/getDropdownMaster';
final String apiUrldata =
'$apiUrl/api/getFlightAndTrainClass?user_id=$userId&trip_type=$tripId';
final token = await getToken();
if (token == null) {
throw Exception('Token not found. Please log in.');
}
final response = await http.get(
Uri.parse(apiUrldata),
headers: {
'Authorization': 'Bearer $token',
'Content-Type': 'application/json',
},
);
if (response.statusCode == 200) {
try {
final data = json.decode(response.body);
print(data);
if (!data.containsKey('data') || data['data'] is! Map) {
throw Exception(
"Invalid response format: 'data' field is missing or not a Map");
}
Map<String, dynamic> plansJson =
data['data']; // 'data' is a Map, not a List
setState(() {
apiDataForClass = plansJson; // Store API response in state
isLoading = false;
});
} catch (e) {
throw Exception('Error parsing response: $e');
}
} else {
throw Exception('Failed to load plans');
}
}
// Handle Submit
bool validateForm() {
@ -1213,7 +1277,11 @@ class CreateNewPlansState extends State<CreateNewPlan> {
children: [
Expanded(
child: DynamicItinerary(
key: dynamicItineraryKey,
hasAction: hasAction,
tripType: _selectedTripType,
apiData: apiData,
apiDataForClass: apiDataForClass,
apiCountryData: apiCountryData,
onItineraryUpdate: handleItineraryUpdate,
loginUser: selfId,
@ -1588,7 +1656,7 @@ class CreateNewPlansState extends State<CreateNewPlan> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Purpose of Travel *", // Your label
"Purpose of Trip *", // Your label
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
@ -1887,6 +1955,9 @@ class CreateNewPlansState extends State<CreateNewPlan> {
: () {
setState(() {
_selectedTripType = "1";
fetchTrainFlightClass(1);
dynamicItineraryKey.currentState?.updateSelectedServices();
});
},
child: CustomTextFieldWrapper(
@ -1944,6 +2015,8 @@ class CreateNewPlansState extends State<CreateNewPlan> {
: () {
setState(() {
_selectedTripType = "2";
fetchTrainFlightClass(2);
dynamicItineraryKey.currentState?.updateSelectedServices();
});
},
child: CustomTextFieldWrapper(

View File

@ -25,7 +25,10 @@ import '../itnerary_list/miscellaneous_list.dart';
import '../itnerary_list/visa_list.dart';
class DynamicItinerary extends StatefulWidget {
final bool hasAction;
final String? tripType;
final Map<String, dynamic>? apiData;
final Map<String, dynamic>? apiDataForClass;
final List<dynamic>? apiCountryData;
final String? loginUser;
final Function(String, List<Map<String, dynamic>>)
@ -40,15 +43,19 @@ class DynamicItinerary extends StatefulWidget {
required this.apiCountryData,
required this.loginUser,
required this.selectedPlanData,
required this.isViewMode});
required this.isViewMode,
required this.hasAction,
this.tripType,
this.apiDataForClass});
@override
_DynamicItineraryState createState() => _DynamicItineraryState();
DynamicItineraryState createState() => DynamicItineraryState();
}
class _DynamicItineraryState extends State<DynamicItinerary> {
class DynamicItineraryState extends State<DynamicItinerary> {
final ApiService apiService = ApiService();
String? _tripType;
String selectedOption = "";
String selectedListOption = "";
@ -80,6 +87,25 @@ class _DynamicItineraryState extends State<DynamicItinerary> {
super.initState();
handleSelectedPlan();
updateSelectedServices();
_tripType = widget.tripType;
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
if (widget.tripType != _tripType) {
updateTripType(widget.tripType);
}
}
void updateTripType(String? newTripType) {
if (_tripType != newTripType) {
setState(() {
_tripType = newTripType;
});
updateSelectedServices(); // Refresh services based on new tripType
}
}
Future<void> loadAllServices() async {
@ -126,31 +152,108 @@ class _DynamicItineraryState extends State<DynamicItinerary> {
}
}
List<String> getAllowedServiceNames() {
if (widget.tripType == "1") {
return ["flight", "accomodation", "train", "bus"];
} else if (widget.tripType == "2") {
return [
"flight",
"accomodation",
"forex",
"insurance",
"visa",
"miscellaneous"
];
} else {
// tripType is null or not 1/2, allow everything
return [];
}
}
// Future<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}");
print("SELCTSplanChhose: $filledItineraryKeys");
final selectedIds =
selectedOrgServiceIds.map((e) => e['service_id']).toSet();
// Filter services that match filled keys (name match) and are not already selected
final additionalServices = selectedAllServices!.where((service) {
final name = (service['name'] ?? "").toString().toLowerCase();
final id = service['service_id'].toString();
return filledItineraryKeys.contains(name) && !selectedIds.contains(id);
final isNameAllowed =
allowedServiceNames.isEmpty || allowedServiceNames.contains(name);
return filledItineraryKeys.contains(name) &&
!selectedIds.contains(id) &&
isNameAllowed;
}).toList();
final originalFiltered = selectedAllServices!
.where((service) =>
selectedIds.contains(service['service_id'].toString()))
.toList();
final originalFiltered = selectedAllServices!.where((service) {
final name = (service['name'] ?? "").toString().toLowerCase();
final id = service['service_id'].toString();
final isNameAllowed =
allowedServiceNames.isEmpty || allowedServiceNames.contains(name);
return selectedIds.contains(id) && isNameAllowed;
}).toList();
// setState(() {
// ServicesChoosed = [...originalFiltered, ...additionalServices];
// });
setState(() {
ServicesChoosed = [...originalFiltered, ...additionalServices]
..sort((a, b) => (a['order'] ?? 0).compareTo(b['order'] ?? 0));
@ -162,21 +265,20 @@ class _DynamicItineraryState extends State<DynamicItinerary> {
final selectedIds =
selectedOrgServiceIds.map((e) => e['service_id']).toSet();
final filtered = selectedAllServices!
.where((service) =>
selectedIds.contains(service['service_id'].toString()))
.toList();
final filtered = selectedAllServices!.where((service) {
final name = (service['name'] ?? "").toString().toLowerCase();
final isNameAllowed =
allowedServiceNames.isEmpty || allowedServiceNames.contains(name);
return selectedIds.contains(service['service_id'].toString()) &&
isNameAllowed;
}).toList();
setState(() {
ServicesChoosed = filtered
..sort((a, b) => (a['order'] ?? 0).compareTo(b['order'] ?? 0));
});
// setState(() {
// ServicesChoosed = filtered;
// });
print("Filtered Selected Services Chooesed: $ServicesChoosed");
print("Filtered Selected Services Chosen: $ServicesChoosed");
}
}
@ -408,6 +510,8 @@ class _DynamicItineraryState extends State<DynamicItinerary> {
switch (selectedListOption) {
case "Train":
selectedListWidget = TrainListWidget(
hasAction: widget.hasAction,
tripType: widget.tripType,
trainList: itineraryData["Train"]!,
onOpen: handleEdit,
onAddNew: handlecreateNewPlan,
@ -491,6 +595,8 @@ class _DynamicItineraryState extends State<DynamicItinerary> {
case "Flight":
default:
selectedListWidget = FlightListWidget(
hasAction: widget.hasAction,
tripType: widget.tripType,
flightList: itineraryData["Flight"]!,
onOpen: handleEdit,
onAddNew: handlecreateNewPlan,
@ -505,6 +611,7 @@ class _DynamicItineraryState extends State<DynamicItinerary> {
selectedWidget = TrainScreen(
onClose: handleClose,
apiData: widget.apiData,
apiDataForClass: widget.apiDataForClass,
loginUser: widget.loginUser,
onSavetrain: (data) => handleItineraryUpdate("Train", data),
selectedItem: selectedItem);
@ -582,10 +689,13 @@ class _DynamicItineraryState extends State<DynamicItinerary> {
case "Flight":
default:
selectedWidget = FlightScreen(
hasAction: widget.hasAction,
tripType: widget.tripType,
onClose: handleClose,
loginUser: widget.loginUser,
onSaveFlight: (data) => handleItineraryUpdate("Flight", data),
apiData: widget.apiData,
apiDataForClass: widget.apiDataForClass,
selectedItem: selectedItem,
flightData: itineraryData["Flight"]!,
);

View File

@ -36,6 +36,10 @@ class _ListPlansState extends State<ListPlans> {
Color? layoutColor;
Color? bodyColor;
List<Plan> allPlans = [];
List<Plan> filteredPlans = [];
TextEditingController searchController = TextEditingController();
@override
void initState() {
super.initState();
@ -44,11 +48,33 @@ class _ListPlansState extends State<ListPlans> {
WidgetsBinding.instance.addPostFrameCallback((_) {
initializeData();
loadInitialData();
futurePlans.then((plans) {
setState(() {
allPlans = plans;
filteredPlans = plans;
});
});
});
// futurePlans = fetchPlans();
}
void filterPlans(String query) {
final lowerQuery = query.toLowerCase();
setState(() {
filteredPlans = allPlans.where((plan) {
return (plan.planId?.toLowerCase().contains(lowerQuery) ?? false) ||
(plan.tripTitle?.toLowerCase().contains(lowerQuery) ?? false) ||
(plan.userName?.toLowerCase().contains(lowerQuery) ?? false) ||
(plan.travellerName?.toLowerCase().contains(lowerQuery) ?? false) ||
(plan.tripType?.toLowerCase().contains(lowerQuery) ?? false) ||
(plan.createdOn?.toLowerCase().contains(lowerQuery) ?? false) ||
(plan.statusValue?.toLowerCase().contains(lowerQuery) ?? false);
}).toList();
});
}
void loadInitialData() async {
String? layoutString = await getLayoutColor();
String? bodyStringColor = await getBodyColor();
@ -322,7 +348,7 @@ class _ListPlansState extends State<ListPlans> {
children: [
Row(
children: [
const Text('Plans List',
const Text('Trip List',
style: TextStyle(
fontFamily: "Archivo",
fontSize: 16,
@ -334,9 +360,9 @@ class _ListPlansState extends State<ListPlans> {
Spacer(),
Container(
width: MediaQuery.of(context).size.width * 0.2,
// or use Flexible
child: TextField(
onChanged: (query) {},
controller: searchController,
onChanged: filterPlans,
decoration: InputDecoration(
hintText: "Search for a plan",
hintStyle:
@ -351,7 +377,6 @@ class _ListPlansState extends State<ListPlans> {
color: Colors.grey.shade300, width: 0.5),
),
focusedBorder: OutlineInputBorder(
// borderRadius: BorderRadius.circular(8),
borderSide:
BorderSide(color: Colors.blueAccent, width: 1),
),
@ -510,7 +535,7 @@ class _ListPlansState extends State<ListPlans> {
),
columns: const [
DataColumn(
label: Text('Plan Id',
label: Text('Trip Id',
style: TextStyle(
color: Color(0xFF9E9DBD),
fontSize: 14,
@ -523,7 +548,7 @@ class _ListPlansState extends State<ListPlans> {
fontFamily: "Archivo",
fontWeight: FontWeight.bold))),
DataColumn(
label: Text('UserName',
label: Text('Traveller',
style: TextStyle(
color: Color(0xFF9E9DBD),
fontFamily: "Archivo",

View File

@ -162,8 +162,8 @@ class _CustomAppBarState extends State<CustomAppBar> {
child: Image.network(
selectedOrg!['logo'],
width: 100,
height: 50,
fit: BoxFit.contain,
height: 80,
// fit: BoxFit.contain,
errorBuilder: (context, error, stackTrace) {
return const CircleAvatar(
radius: 20,
@ -253,102 +253,119 @@ class _CustomAppBarState extends State<CustomAppBar> {
Padding(
padding: EdgeInsets.symmetric(
horizontal: MediaQuery.of(context).size.width * 0.05),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
child: Row(
children: [
Text(
userData?["name"] ?? "N/A",
style: TextStyle(
fontSize: 13,
fontWeight: FontWeight.w600,
fontFamily: "Archivo",
// color: Color(0xFF12B24B),
color: layoutColor,
),
),
SizedBox(
height: 1,
),
Row(
children: [
// if (userData?["role"] != "User")
Builder(
builder: (context) => PopupMenuButton<String>(
color: Colors.white,
padding: EdgeInsets.zero,
// icon: const Icon(Icons.arrow_drop_down,
// size: 20, color: Colors.black87),
offset: const Offset(0, 50), // 👈 shift it 50 pixels down
onSelected: (String value) {
switch (value) {
case '/OrganizationSetup':
context.go('/OrganizationSetup');
break;
case '/listUser':
context.go('/listUser');
break;
case '/group':
context.go('/group');
break;
case '/PolicyList':
context.go('/PolicyList');
case '/CreateUserDetails':
context.go(
"/CreateUserDetails",
extra: {
"selectedUser": userDetails,
"isEditProfile": true,
"isViewMode": true
},
);
case '/logout':
context.go('/');
break;
}
},
// if (userData?["role"] != "User")
Builder(
builder: (context) => PopupMenuButton<String>(
color: Colors.white,
padding: EdgeInsets.zero,
// icon: const Icon(Icons.arrow_drop_down,
// size: 20, color: Colors.black87),
offset: const Offset(0, 50), // 👈 shift it 50 pixels down
onSelected: (String value) {
switch (value) {
case '/OrganizationSetup':
context.go('/OrganizationSetup');
break;
case '/listUser':
context.go('/listUser');
break;
case '/group':
context.go('/group');
break;
case '/PolicyList':
context.go('/PolicyList');
case '/CreateUserDetails':
context.go(
"/CreateUserDetails",
extra: {
"selectedUser": userDetails,
"isEditProfile": true,
"isViewMode": true
},
);
case '/logout':
context.go('/');
break;
}
},
// itemBuilder: (BuildContext context) =>
// menuItems.map(buildMenuItem).toList(),
// itemBuilder: (BuildContext context) =>
// menuItems.map(buildMenuItem).toList(),
itemBuilder: (BuildContext context) {
final isUser = userData?["role"] == "User";
final filteredItems = isUser
? menuItems
.where((item) =>
item['value'] == '/CreateUserDetails' ||
item['value'] == '/logout')
.toList()
: menuItems;
itemBuilder: (BuildContext context) {
final isUser = userData?["role"] == "User";
final filteredItems = isUser
? menuItems
.where((item) =>
item['value'] == '/CreateUserDetails' ||
item['value'] == '/logout')
.toList()
: menuItems;
return filteredItems.map(buildMenuItem).toList();
},
child: MouseRegion(
cursor: SystemMouseCursors.click,
child: Row(
mainAxisSize: MainAxisSize.min,
// Create a new list starting with role display and divider
return [
PopupMenuItem<String>(
enabled: false, // Not clickable
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
userData?["role"] ?? "Role",
userData?["role"] ?? '',
style: const TextStyle(
fontSize: 11,
fontWeight: FontWeight.w300,
fontFamily: "Archivo",
fontSize: 12,
fontWeight: FontWeight.bold,
color: Colors.black,
),
),
const Icon(
Icons.arrow_drop_down,
size: 20,
color: Colors.black87,
),
const Divider(), // 👈 Divider after role
],
),
),
...filteredItems
.map(buildMenuItem)
.toList(), // 👈 then normal items
];
},
// itemBuilder: (BuildContext context) {
// final isUser = userData?["role"] == "User";
// final filteredItems = isUser
// ? menuItems
// .where((item) =>
// item['value'] == '/CreateUserDetails' ||
// item['value'] == '/logout')
// .toList()
// : menuItems;
//
// return filteredItems.map(buildMenuItem).toList();
// },
child: MouseRegion(
cursor: SystemMouseCursors.click,
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(
userData?["name"] ?? "N/A",
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
fontFamily: "Roboto",
color: Colors.black,
),
),
const Icon(
Icons.arrow_drop_down,
size: 20,
color: Colors.black87,
),
],
),
),
const SizedBox(width: 8),
],
),
),
const SizedBox(width: 8),
],
),
),

View File

@ -451,7 +451,9 @@ class ApiService {
// Flight From - To
Future<List<dynamic>> fetchFlightsCountryList() async {
final String apiUrldata = '$apiUrl/api/getAirportCodeMaster';
final String apiUrldata =
'$apiUrl/api/getAirportCodeMaster?limit=1000&offset=0';
final token = await getToken();
if (token == null) {