ts-tat/lib/Screens/itnerary/flights.dart
2025-05-03 17:59:17 +05:30

1749 lines
59 KiB
Dart

import 'package:dropdown_search/dropdown_search.dart';
import 'package:flutter/material.dart';
import 'package:frontend/services/apiService.dart';
import 'package:intl/intl.dart';
import 'package:responsive_builder/responsive_builder.dart';
import '../../widgets/custom_radio_button.dart';
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;
final Map<String, dynamic>? selectedItem;
FlightScreen(
{required this.apiData,
required this.loginUser,
required this.onClose,
required this.onSaveFlight,
required this.selectedItem,
required this.flightData,
required this.hasAction,
this.tripType,
this.apiDataForClass});
@override
_FlightScreenState createState() => _FlightScreenState();
}
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;
String? selectedCountry;
List<Map<String, dynamic>> countryList = [];
Map<String, String?> selectedValues = {};
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;
List<String> dataHeader = [
"_tripType",
"_class",
"_from",
"_to",
"_date",
"_visa",
"_time",
"_comments"
];
Map<String, FocusNode> focusNodes = {};
Map<String, bool> focusStates = {};
Map<String, TextEditingController> textControllers = {};
Map<String, String> fieldErrors = {}; // Holds error messages
List<List<Widget>> rowBuilders = []; // Holds row widgets
List<TextEditingController> controllers = []; // Dynamic controllers
Map<String, String> errorMessages = {};
// forex_pre_paid_card_number
@override
void initState() {
super.initState();
// _initializeRows();
// List<dynamic> purposeList = widget.apiData?['flight_trip_type'] ?? [];
// selectedTripType ??= purposeList.isNotEmpty ? purposeList.first['dropdown_value'] : null;
// Get trip type from widget.selectedItem
selectedTripType = widget.selectedItem?["trip_type"] as String?;
// If null, set it to the first available value from purposeList
List<dynamic> purposeList = widget.apiData?['flight_trip_type'] ?? [];
if (selectedTripType == null && purposeList.isNotEmpty) {
selectedTripType = purposeList.first['dropdown_value'] as String?;
}
_initializeFields();
getRowCount();
print("Focus Nodes Keys: ${focusNodes.keys.toList()}");
print("Focus States Keys: ${focusStates.keys.toList()}");
print("Text Controllers Keys: ${textControllers.keys.toList()}");
for (var key in focusNodes.keys) {
_addFocusListener(focusNodes[key]!, (focus) {
setState(() {
focusStates[key.replaceFirst("FocusNode", "Focused")] = focus;
});
});
}
handleUpdateField();
int rowCount = 1; // Default row count for One-way
if (selectedTripType == "Roundtrip") {
rowCount = 2; // Fixed for Roundtrip
} else if (selectedTripType == "Multitrip") {
rowCount = multiTripRowCount; // Dynamic row count for Multitrip
}
// Loop through each row and add listeners to clear errors
for (int i = 1; i <= rowCount; i++) {
textControllers["_from${i}Controller"]
?.addListener(() => _clearError("from_place_$i"));
textControllers["_to${i}Controller"]
?.addListener(() => _clearError("to_place_$i"));
textControllers["_date${i}Controller"]
?.addListener(() => _clearError("date_$i"));
textControllers["_time${i}Controller"]
?.addListener(() => _clearError("time_$i"));
}
loadCountryList();
flightFirstTripDateNotifier = ValueNotifier<String?>(null);
flightLastTripDateNotifier = ValueNotifier<String?>(null);
WidgetsBinding.instance.addPostFrameCallback((_) {
final result = getFlightTripDateRange(widget.flightData);
flightFirstTripDateNotifier.value = result['firstTripDate'];
flightLastTripDateNotifier.value = result['lastTripDate'];
});
}
Map<String, String?> getFlightTripDateRange(
List<Map<String, dynamic>> flightData) {
final allTrips = flightData
.expand((flight) => flight['trips'] ?? [])
.whereType<Map<String, dynamic>>()
.toList();
if (allTrips.isEmpty) {
return {
'firstTripDate': null,
'lastTripDate': null,
};
}
allTrips.sort((a, b) {
final aDate = DateTime.tryParse(a['date'] ?? '') ?? DateTime(1900);
final bDate = DateTime.tryParse(b['date'] ?? '') ?? DateTime(1900);
return aDate.compareTo(bDate);
});
final firstTrip = allTrips.first;
final lastTrip = allTrips.last;
return {
'firstTripDate': firstTrip['date'],
'lastTripDate': lastTrip['date'],
};
}
//
// 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();
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() {
if (selectedTripType == "RoundTrip") {
return 2;
} else if (selectedTripType == "Multitrip") {
return multiTripRowCount;
}
return 1; // Default for Oneway
}
void _initializeFields() {
print("_initializeFields-----------------");
// Dispose and clear previous controllers and focus nodes
for (var key in List.from(textControllers.keys)) {
textControllers[key]?.dispose();
}
textControllers.clear();
for (var key in List.from(focusNodes.keys)) {
focusNodes[key]?.dispose();
}
focusNodes.clear();
textControllers.clear();
focusNodes.clear();
focusStates.clear();
print("Focus Nodes KeysII: ${focusNodes.keys.toList()}");
print("Focus States KeysII: ${focusStates.keys.toList()}");
print("Text Controllers KeysII: ${textControllers.keys.toList()}");
// Determine the row count based on selectedTripType
int rowCount = selectedTripType == "Roundtrip"
? 2
: selectedTripType == "Multitrip"
? multiTripRowCount
: 1;
// Initialize fields dynamically
for (var field in dataHeader) {
for (int i = 1; i <= rowCount; i++) {
textControllers["${field}${i}Controller"] = TextEditingController();
focusNodes["${field}${i}FocusNode"] = FocusNode();
focusStates["${field}${i}Focused"] = false;
}
}
// Add focus listeners after reinitialization
for (var key in focusNodes.keys) {
_addFocusListener(focusNodes[key]!, (focus) {
setState(() {
focusStates[key.replaceFirst("FocusNode", "Focused")] = focus;
});
});
}
setState(() {}); // Ensure UI updates
}
void addMultiTripRow() {
setState(() {
multiTripRowCount++; // Increment row count
_initializeFields(); // Reinitialize fields with updated count
});
}
void _addFocusListener(FocusNode node, Function(bool) updateState) {
node.addListener(() {
setState(() {
updateState(node.hasFocus);
});
});
}
void _clearError(String field) {
if (mounted && errorMessages.containsKey(field)) {
setState(() {
errorMessages.remove(field);
});
}
}
@override
void dispose() {
// _tripTypeFocusNode.dispose();
// Dispose all dynamically created FocusNodes
for (var node in focusNodes.values) {
node.dispose();
}
// Dispose all dynamically created TextEditingControllers
for (var controller in textControllers.values) {
controller.dispose();
}
super.dispose();
}
Map<String, dynamic> get flightsData {
List<Map<String, dynamic>> trips = [];
int rowCount = 1; // Default for One-way
if (selectedTripType == "Roundtrip") {
rowCount = 2; // Fixed count for Roundtrip
} else if (selectedTripType == "Multitrip") {
rowCount = multiTripRowCount; // Use dynamic count for Multitrip
}
for (int i = 1; i <= rowCount; i++) {
final trip = {
"class": selectedClasses[i],
// "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,
"updated_by": widget.loginUser,
};
// Check if editing and flight_trip_id exists for this trip
// 🛠 Fix index offset (i - 1)
if (widget.selectedItem != null &&
widget.selectedItem?["trips"] != null &&
widget.selectedItem!["trips"] is List &&
(i - 1) < widget.selectedItem!["trips"].length) {
final existingTrip = widget.selectedItem!["trips"][i - 1];
if (existingTrip["flight_trip_id"] != null) {
trip["flight_trip_id"] = existingTrip["flight_trip_id"];
}
}
trips.add(trip);
// trips.add({
// "class": selectedClasses[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,
// "updated_by": widget.loginUser,
// });
}
Map<String, dynamic> data = {
"trip_type": selectedTripType,
"comments": textControllers["_comments1Controller"]?.text ?? "",
"visa_available": selectedvisa_available,
"created_by": widget.loginUser,
"updated_by": widget.loginUser,
"trips": trips,
};
if (widget.selectedItem != null) {
if (widget.selectedItem?["indx"] != null &&
widget.selectedItem?["indx"] != 0) {
data["indx"] = widget.selectedItem!["indx"];
} else if (widget.selectedItem?["flight_id"] != null &&
widget.selectedItem?["flight_id"] != 0) {
data["flight_id"] = widget.selectedItem!["flight_id"];
}
}
return data;
}
TextEditingController initController(String key) {
return TextEditingController(text: widget.selectedItem?[key] ?? "");
}
void handleUpdateField() {
if (widget.selectedItem != null) {
textControllers["_comments1Controller"] = initController("comments");
// selectedTripType = widget.selectedItem!["trip_type"] as String?;
// selectedvisa_available = widget.selectedItem!["visa_available"].toString();
if (widget.selectedItem!["trip_type"] != null) {
selectedTripType = widget.selectedItem!["trip_type"].toString();
}
if (widget.selectedItem!["visa_available"] != null) {
selectedvisa_available =
widget.selectedItem!["visa_available"].toString();
}
// Extract trips from selectedItem
List<dynamic> selectedTrips = widget.selectedItem!["trips"] ?? [];
// Ensure the selectedClasses map and textControllers are cleared before populating
// selectedClasses.clear();
// textControllers.clear();
// Set row count dynamically for Multitrip
if (selectedTripType == "Multitrip") {
multiTripRowCount = selectedTrips.length;
}
// Loop through selected trips and populate text controllers
for (int i = 0; i < selectedTrips.length; i++) {
var trip = selectedTrips[i] as Map<String, dynamic>;
int index = i + 1; // Use 1-based indexing to match the form
selectedClasses[index] = trip["class"].toString();
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"] =
TextEditingController(text: trip["time"]);
// Check if editing and flight_trip_id exists for this trip
if (widget.selectedItem != null &&
widget.selectedItem?["trips"] != null &&
widget.selectedItem!["trips"] is List &&
i < widget.selectedItem!["trips"].length) {
final existingTrip = widget.selectedItem!["trips"][i];
if (existingTrip["flight_trip_id"] != null) {
trip["flight_trip_id"] = existingTrip["flight_trip_id"];
}
}
}
print("Selected ITEM - ${widget.selectedItem}");
print("Total Trips Loaded: ${selectedTrips.length}");
print("Controllers Set: ${textControllers.keys}");
}
}
void handleSave() {
print("Controllers Key-Value Pairs:");
if (!validateFields()) {
print("Validation failed. Please fill all required fields.");
return;
}
widget.onSaveFlight(flightsData); // Send object to parent
widget.onClose(false);
print("FlightData - $flightsData");
}
bool validateFields() {
errorMessages.clear(); // Reset errors
int rowCount = 1; // Default row count for One-way
if (selectedTripType == "Roundtrip") {
rowCount = 2; // Fixed for Roundtrip
} else if (selectedTripType == "Multitrip") {
rowCount = multiTripRowCount; // Dynamic row count for Multitrip
}
// 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) {
// errorMessages["from_place_$i"] = "Required";
// }
if (selectedFrom[i] == null) {
errorMessages["from_place_$i"] = "Required";
}
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";
}
if (textControllers["_time${i}Controller"]?.text.trim().isEmpty ?? true) {
errorMessages["time_$i"] = "Required";
}
}
setState(() {}); // Update UI to show error messages
return errorMessages
.isEmpty; // Returns true if all required fields are filled
}
void removeTrip(int index) {
if (selectedTripType == "Multitrip" && multiTripRowCount > 1) {
print("Delete Index - $index");
setState(() {
multiTripRowCount--; // Reduce trip count
// Remove corresponding text controllers
textControllers.remove("_from${index}Controller");
textControllers.remove("_to${index}Controller");
textControllers.remove("_date${index}Controller");
textControllers.remove("_time${index}Controller");
// Step 2: Shift remaining textControllers keys
Map<String, TextEditingController> updatedTextControllers = {};
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["_to${newIndex}Controller"] =
textControllers["_to${i}Controller"]!;
updatedTextControllers["_date${newIndex}Controller"] =
textControllers["_date${i}Controller"]!;
updatedTextControllers["_time${newIndex}Controller"] =
textControllers["_time${i}Controller"]!;
newIndex++;
}
textControllers = updatedTextControllers;
// Shift the selectedClasses map BEFORE removing the index
Map<int, String?> updatedClasses = {};
newIndex = 1;
for (int i = 1; i <= selectedClasses.length; i++) {
if (i == index) continue; // Skip the one being deleted
updatedClasses[newIndex] = selectedClasses[i];
newIndex++;
}
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");
});
}
}
@override
Widget build(BuildContext context) {
return ResponsiveBuilder(builder: (context, sizingInfo) {
bool isMobile = sizingInfo.isMobile;
bool isDesktop = sizingInfo.deviceScreenType == DeviceScreenType.desktop;
return Container(
// color: Color(0xFFF4F4FB),
// color: Color(0xFFF9F9F9), // Slightly lighter than white
child: Form(
key: _formKey,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
Padding(
padding: const EdgeInsets.all(20.0),
child: Center(
child: Column(children: _buildAccomadtionForm(isDesktop)),
),
)
],
),
),
),
);
});
}
List<Widget> _buildAccomadtionForm(bool isDesktop) {
List<Widget> buildResponsiveRow(List<Widget> children) {
return [
isDesktop ? Row(children: children) : Column(children: children),
SizedBox(height: 10),
];
}
List<List<Widget>> rowBuilders = [
// _builClassType(isDesktop, 1),
_buildSecondRow(isDesktop, 1)
];
List<List<Widget>> rowRoundBuilders = [
// _builClassType(isDesktop, 1),
_buildSecondRow(isDesktop, 1),
// _builClassType(isDesktop, 2),
_buildSecondRow(isDesktop, 2)
];
print("Trip Type Selected: $selectedTripType");
return [
...buildResponsiveRow(_buildFirstRow(isDesktop)),
// Iterate over rowBuilders based on selectedTripType
if (selectedTripType == "Oneway")
...rowBuilders.expand((row) => buildResponsiveRow(row)),
if (selectedTripType == "Roundtrip")
...rowRoundBuilders.expand((row) => buildResponsiveRow(row)),
if (selectedTripType == "Multitrip")
...List.generate(multiTripRowCount, (index) {
// List<Widget> firstRow = _builClassType(isDesktop, index + 1);
List<Widget> secondRow = _buildSecondRow(isDesktop, index + 1);
return [
// ...buildResponsiveRow(firstRow), // Row 1
...buildResponsiveRow(secondRow), // Row 2
];
}).expand((row) => row),
// if (selectedTripType == "Multitrip")
// ...List.generate(multiTripRowCount, (index) =>
// buildResponsiveRow(_builClassType(isDesktop, index + 1) + _buildSecondRow(isDesktop, index + 1))
// ).expand((row) => row),
if (selectedTripType == "Multitrip")
Align(
alignment: Alignment.centerRight,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.green,
foregroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
),
onPressed: () {
setState(() {
multiTripRowCount++; // Increase the row count
});
// Dynamically create controllers and focus nodes for new trip fields
for (var field in dataHeader) {
String keyController = "${field}${multiTripRowCount}Controller";
String keyFocusNode = "${field}${multiTripRowCount}FocusNode";
String keyFocusState = "${field}${multiTripRowCount}Focused";
// Create TextEditingController if it doesn't exist
if (!textControllers.containsKey(keyController)) {
textControllers[keyController] = TextEditingController();
}
// Create FocusNode if it doesn't exist
if (!focusNodes.containsKey(keyFocusNode)) {
focusNodes[keyFocusNode] = FocusNode();
// Attach focus listener for dynamic fields
focusNodes[keyFocusNode]!.addListener(() {
setState(() {
focusStates[keyFocusState] =
focusNodes[keyFocusNode]!.hasFocus;
});
});
}
// Initialize focus state
focusStates[keyFocusState] = false;
}
// _initializeFields();
},
child: Text(
"Add Trip",
style: TextStyle(color: Colors.white, fontSize: 12),
),
),
),
// ...buildResponsiveRow(_buildvisa(isDesktop)),
...buildResponsiveRow(_buildThirdRow(isDesktop)),
// Actions row remains a Row
];
}
List<Widget> _buildFirstRow(isDesktop) {
return [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Trip Type",
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74)),
),
SizedBox(height: 5),
isDesktop
? Row(children: _buildTripType(isDesktop))
: Column(children: _buildTripType(isDesktop))
],
),
if (isDesktop)
Spacer()
else
SizedBox(
height: 8,
),
];
}
List<Widget> _buildTripType(bool isDesktop) {
List<dynamic> purposeList = widget.apiData?['flight_trip_type'] ?? [];
List<DropdownMenuItem<String>> dropdownItems = purposeList
.map((item) => DropdownMenuItem<String>(
value: item['dropdown_value'],
child: Text(item['dropdown_value']),
))
.toList();
if (dropdownItems.isEmpty) {
dropdownItems.add(
DropdownMenuItem<String>(
value: null,
child: Text("No options available",
style: TextStyle(color: Colors.grey)),
),
);
}
return [
CustomTextFieldWrapper(
// isFocused: _tripTypeFocused,
isFocused: focusStates["_tripType1Focused"] ?? false,
isDesktop: isDesktop,
width: isDesktop ? MediaQuery.of(context).size.width * 0.32 : null,
child: SizedBox(
height: 40,
width: double.infinity,
child: DropdownSearch<String>(
items: purposeList
.map((item) => item['dropdown_value'] as String)
.toList(),
dropdownDecoratorProps: DropDownDecoratorProps(
dropdownSearchDecoration: InputDecoration(
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(horizontal: 1),
),
),
onChanged: (newValue) {
setState(() {
selectedTripType = newValue;
if (selectedTripType != "Multitrip") {
multiTripRowCount = 1;
}
errorMessages.clear();
});
print(
"Updating form data: Flight -> trip_type -> $selectedTripType");
_initializeFields();
},
selectedItem: selectedTripType,
dropdownBuilder: (context, selectedItem) => Align(
alignment: Alignment.centerLeft,
child: Text(
selectedItem ?? "Select",
style: TextStyle(fontSize: 12),
),
),
popupProps: PopupProps.menu(
constraints: BoxConstraints(maxHeight: 100),
menuProps: MenuProps(backgroundColor: Colors.white),
itemBuilder: (context, item, isSelected) => Padding(
padding:
const EdgeInsets.symmetric(horizontal: 8.0, vertical: 6.0),
child: Text(
item,
style: TextStyle(
fontSize: 13), // Custom text size for dropdown items
),
),
),
),
// DropdownButtonFormField<String>(
// isExpanded: true,
// // focusNode: _tripTypeFocusNode, // Assign the correct focus node
// focusNode: focusNodes["_tripType1FocusNode"],
// value: selectedTripType,
// style: TextStyle(fontSize: 12),
// isDense: true,
// dropdownColor: Colors.white,
// decoration: InputDecoration(
// border: InputBorder.none,
// contentPadding:
// EdgeInsets.symmetric(horizontal: 10), // Proper padding
// ),
// onChanged: purposeList.isNotEmpty
// ? (newValue) {
// setState(() {
// selectedTripType = newValue;
// // selectedTripType = "Oneway";
// // Reset `multiTripRowCount` when switching away from Multitrip
// if (selectedTripType != "Multitrip") {
// multiTripRowCount = 1;
// }
// errorMessages.clear();
// });
// print(
// "Updating form data: Flight -> trip_type -> $selectedTripType");
// _initializeFields();
//
// // _initializeRows();
// }
// : null,
//
// items: dropdownItems,
// ),
),
),
];
}
// Widget _buildDelete(bool isDesktop, int index) {
// return Container(
// color: Colors.blueAccent,
// child: Row(
// mainAxisAlignment: MainAxisAlignment.start,
// children: [
// Text(
// "Trip ${index}",
// style: TextStyle(
// fontSize: 14,
// fontWeight: FontWeight.w600,
// color: Color(0xFF575A74),
// ),
// ),
// SizedBox(
// width: isDesktop
// ? MediaQuery.of(context).size.width * 0.38
// : MediaQuery.of(context).size.width * 0.3,
// child: Stack(
// alignment: Alignment.center, // Centers the icon
// children: [
// Divider(
// color: Color(0xFF8B8FB2),
// thickness: 0.5,
// height: 20,
// ),
// Container(
// // padding: EdgeInsets.all(4),
// color: Colors.white, // Background to avoid overlapping
// child: Row(
// mainAxisSize:
// MainAxisSize.min, // Prevents row from taking full width
// children: [
// Icon(Icons.add_circle_sharp,
// color: Colors.blue, size: 28),
// ],
// ),
// ),
// ],
// ),
// ),
// IconButton(
// onPressed: () {
// removeTrip(index);
// },
// icon: Icon(Icons.delete),
// color: Colors.red,
// iconSize: 20,
// )
// ],
// ),
// );
// }
List<Widget> _buildDelete(bool isDesktop, int index) {
return [
Container(
padding: const EdgeInsets.all(10),
// padding: const EdgeInsets.only(left: 10, right: 10),
// color: Colors.white,
child: Text(
"Trip ${index}",
style: TextStyle(
fontSize: 14,
color: Colors.blueAccent,
fontWeight: FontWeight.w600,
),
),
),
SizedBox(
width: isDesktop
? MediaQuery.of(context).size.width * 0.58
: 80, // Ensure full width
child: Stack(
alignment: Alignment.center, // Centers the icon
children: [
Divider(
color: Color(0xFF8B8FB2),
thickness: 0.5,
height: 20,
),
Container(
// padding: EdgeInsets.all(4),
color: Colors.white, // Background to avoid overlapping
child: Row(
mainAxisSize:
MainAxisSize.min, // Prevents row from taking full width
children: [
Icon(Icons.add_circle_sharp, color: Colors.blue, size: 28),
],
),
),
],
),
),
// SizedBox(
// width: isDesktop
// ? MediaQuery.of(context).size.width * 0.29
// : MediaQuery.of(context).size.width * 0.3,
// child: Stack(
// alignment: Alignment.center, // Centers the icon
// children: [
// Divider(
// color: Color(0xFF8B8FB2),
// thickness: 0.5,
// height: 20,
// ),
// Container(
// // padding: EdgeInsets.all(4),
// color: Colors.white, // Background to avoid overlapping
// child: Row(
// mainAxisSize:
// MainAxisSize.min, // Prevents row from taking full width
// children: [
// Icon(Icons.add_circle_sharp, color: Colors.blue, size: 28),
// ],
// ),
// ),
// ],
// ),
// ),
Container(
// color: Colors.white,
// padding: const EdgeInsets.only(left: 10, right: 10),
child: IconButton(
onPressed: () {
removeTrip(index);
},
icon: Icon(Icons.delete),
color: Colors.blueAccent,
iconSize: 20,
),
)
];
}
List<Widget> _buildSecondRow(bool isDesktop, int index) {
List<dynamic> purposeList = widget.apiDataForClass?['flight_class'] ?? [];
List<DropdownMenuItem<String>> dropdownItems = purposeList
.map((item) => DropdownMenuItem<String>(
value: item['dropdown_key'],
child: Text(item['dropdown_value']),
))
.toList();
if (dropdownItems.isEmpty) {
dropdownItems.add(
DropdownMenuItem<String>(
value: null,
child: Text("No options available",
style: TextStyle(color: Colors.grey)),
),
);
}
// Default selected value
selectedClasses[index] ??=
dropdownItems.isNotEmpty ? dropdownItems.first.value : null;
// -------------------------------------------------
DateTime? _selectedCheckOutDate;
TimeOfDay? _selectedCheckOutTime;
Future<void> _selectCheckOutDate(BuildContext context) async {
DateTime now = DateTime.now();
DateTime today = DateTime(now.year, now.month, now.day);
// Determine the minimum date (firstDate) based on previous index if available
DateTime firstDate = today;
if (index == 1 &&
flightLastTripDateNotifier.value != null &&
flightLastTripDateNotifier.value!.isNotEmpty) {
try {
final tripDate = DateFormat('yyyy-MM-dd')
.parseStrict(flightLastTripDateNotifier.value!);
if (tripDate.isAfter(today)) {
firstDate = tripDate;
}
} catch (_) {
// handle parse error if needed
}
} else if (index > 1) {
final previousDateString =
textControllers["_date${index - 1}Controller"]?.text;
if (previousDateString != null && previousDateString.isNotEmpty) {
try {
final previousDate =
DateFormat('yyyy-MM-dd').parseStrict(previousDateString);
if (previousDate.isAfter(today)) {
firstDate = previousDate;
}
} catch (_) {
// handle parse error if necessary
}
}
}
DateTime initialDate = _selectedCheckOutDate != null &&
_selectedCheckOutDate!.isAfter(firstDate)
? _selectedCheckOutDate!
: firstDate;
DateTime? pickedDate = await showDatePicker(
context: context,
initialDate: initialDate,
firstDate: firstDate,
lastDate: DateTime(2100),
);
if (pickedDate != null && pickedDate != _selectedCheckOutDate) {
setState(() {
_selectedCheckOutDate = pickedDate;
// _dateController.text = DateFormat('yyyy-MM-dd').format(pickedDate);
textControllers["_date${index}Controller"]?.text =
DateFormat('yyyy-MM-dd').format(pickedDate);
});
}
}
Future<void> _selectCheckOutTime(BuildContext context) async {
TimeOfDay? pickedTime = await showTimePicker(
context: context,
initialTime: _selectedCheckOutTime ?? TimeOfDay.now(),
);
if (pickedTime != null && pickedTime != _selectedCheckOutTime) {
setState(() {
_selectedCheckOutTime = pickedTime;
// Formatting time to HH:mm (24-hour format)
final now = DateTime.now();
final formattedTime = DateFormat('HH:mm').format(
DateTime(now.year, now.month, now.day, pickedTime.hour,
pickedTime.minute),
);
// _timeController.text = formattedTime;
textControllers["_time${index}Controller"]?.text = formattedTime;
});
}
}
// 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
// };
// // Extract only country codes for processing
// countryCodes = countryMap.keys.toList();
//
// selectedCountry ??= null;
return [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
selectedTripType == "Oneway" ? "Class *" : "Class $index *",
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74)),
),
SizedBox(height: 5),
CustomTextFieldItnerarySubWrapper(
isFocused: focusStates["_class${index}Focused"] ?? false,
isDesktop: isDesktop,
// width: isDesktop
// ? MediaQuery.of(context).size.width * 0.34
// : MediaQuery.of(context).size.width * 0.66,
child: SizedBox(
height: 40,
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],
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,
),
),
),
],
),
if (isDesktop)
SizedBox(
width: 20,
)
else
SizedBox(
height: 8,
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"From",
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74)),
),
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: isCountryLoading
? Center(child: CircularProgressIndicator())
: DropdownSearch<String>(
selectedItem: selectedFrom[index] != null
? countryMap[selectedFrom[index]]
: null,
popupProps: PopupProps.menu(
fit: FlexFit.loose,
constraints: BoxConstraints(maxHeight: 220),
showSearchBox: true, // Enables search functionality
searchFieldProps: TextFieldProps(
decoration: InputDecoration(
hintText: "Search...",
contentPadding: EdgeInsets.symmetric(
horizontal: 10, vertical: 1),
),
style: TextStyle(fontSize: 12)),
menuProps: MenuProps(
backgroundColor: Colors.white,
),
itemBuilder: (context, item, isSelected) => Padding(
padding: const EdgeInsets.symmetric(
horizontal: 8.0, vertical: 6.0),
child: Text(
item,
style: TextStyle(
fontSize:
13), // 👈 Set your desired text size here
),
),
),
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[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(
"Required",
style: TextStyle(color: Colors.red, fontSize: 12),
),
],
],
),
if (isDesktop)
Spacer()
else
SizedBox(
height: 8,
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"To",
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74)),
),
SizedBox(height: 5),
CustomTextFieldItnerarySubWrapper(
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(
fit: FlexFit.loose,
constraints: BoxConstraints(maxHeight: 220),
showSearchBox: true, // Enables search functionality
searchFieldProps: TextFieldProps(
decoration: InputDecoration(
hintText: "Search...",
contentPadding: EdgeInsets.symmetric(
horizontal: 10, vertical: 1),
),
style: TextStyle(fontSize: 12)),
menuProps: MenuProps(
backgroundColor: Colors.white,
),
itemBuilder: (context, item, isSelected) => Padding(
padding: const EdgeInsets.symmetric(
horizontal: 8.0, vertical: 6.0),
child: Text(
item,
style: TextStyle(
fontSize:
13), // 👈 Set your desired text size here
),
),
),
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(
"Required",
style: TextStyle(color: Colors.red, fontSize: 12),
),
],
],
),
if (isDesktop)
Spacer()
else
SizedBox(
height: 8,
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Date",
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74)),
),
SizedBox(height: 5),
CustomTextFieldItnerarySubWrapper(
isFocused: focusStates["_date${index}Focused"] ?? false,
isDesktop: isDesktop,
width: isDesktop ? MediaQuery.of(context).size.width * 0.11 : null,
child: SizedBox(
height: 40,
child: GestureDetector(
onTap: () => _selectCheckOutDate(context),
child: AbsorbPointer(
child: TextField(
focusNode: focusNodes["_date${index}FocusNode"],
// controller: _dateController,
controller: textControllers["_date${index}Controller"],
style: const TextStyle(fontSize: 12),
decoration: const InputDecoration(
labelText: "Select Date",
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16),
suffixIcon: Icon(Icons.calendar_today,
size: 16, color: Colors.grey),
),
),
),
),
),
),
if (errorMessages["date_$index"] != null) ...[
SizedBox(height: 5), // Space before error message
Text(
"Required",
style: TextStyle(color: Colors.red, fontSize: 12),
),
],
],
),
if (isDesktop)
Spacer()
else
SizedBox(
height: 8,
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Time",
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74)),
),
SizedBox(height: 5),
CustomTextFieldItnerarySubWrapper(
isFocused: focusStates["_timeFocused"] ?? false,
isDesktop: isDesktop,
width: isDesktop ? MediaQuery.of(context).size.width * 0.1 : null,
child: SizedBox(
height: 40,
child: GestureDetector(
onTap: () => _selectCheckOutTime(context),
child: AbsorbPointer(
child: TextField(
focusNode: focusNodes["_time${index}FocusNode"],
// controller: _timeController,
controller: textControllers["_time${index}Controller"],
style: const TextStyle(fontSize: 12),
decoration: const InputDecoration(
labelText: "Time",
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16),
suffixIcon:
Icon(Icons.access_time, size: 16, color: Colors.grey),
),
),
),
),
),
),
if (errorMessages["time_$index"] != null) ...[
SizedBox(height: 5), // Space before error message
Text(
"Required",
style: TextStyle(color: Colors.red, fontSize: 12),
),
],
],
),
if (selectedTripType == "Multitrip")
Container(
// color: Colors.blueGrey,
// padding: const EdgeInsets.only(top: 50, bottom: 50),
child: IconButton(
onPressed: () {
removeTrip(index);
},
icon: Icon(
Icons.close,
color: Colors.redAccent,
size: 20,
),
),
)
];
}
List<Widget> _buildThirdRow(bool isDesktop) {
List<dynamic> visa_available =
widget.apiData?['flight_visa_available'] ?? [];
// Default selected value
List<DropdownMenuItem<String>> dropdownItems = visa_available
.map((item) => DropdownMenuItem<String>(
value: item['dropdown_key'],
child: Text(item['dropdown_value']),
))
.toList();
selectedvisa_available ??=
dropdownItems.isNotEmpty ? dropdownItems.first.value : null;
if (dropdownItems.isEmpty) {
dropdownItems.add(
DropdownMenuItem<String>(
value: null,
child: Text("No options available",
style: TextStyle(color: Colors.grey)),
),
);
}
return [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Visa Required",
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74)),
),
SizedBox(height: 5),
CustomTextFieldItnerarySubWrapper(
// isFocused: _tripTypeFocused,
isFocused: focusStates["_visa1Focused"] ?? false,
isDesktop: isDesktop,
// width: isDesktop
// ? MediaQuery.of(context).size.width * 0.34
// : MediaQuery.of(context).size.width * 0.66,
child: SizedBox(
height: 40,
child: DropdownButtonFormField<String>(
// focusNode: _tripTypeFocusNode, // Assign the correct focus node
focusNode: focusNodes["_visa1FocusNode"],
value: selectedvisa_available,
style: TextStyle(fontSize: 12),
decoration: InputDecoration(
border: InputBorder.none,
contentPadding:
EdgeInsets.symmetric(horizontal: 10), // Proper padding
),
onChanged: visa_available.isNotEmpty
? (newValue) {
setState(() {
selectedvisa_available = newValue;
// selectedTripType = "Oneway";
// Reset `multiTripRowCount` when switching away from Multitrip
});
print(
"Updating form data: Flight -> trip_type -> $selectedvisa_available");
// _initializeRows();
}
: null,
items: dropdownItems,
),
),
),
],
),
if (isDesktop)
SizedBox(
width: 20,
),
SizedBox(
height: 5,
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Comments",
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74)),
),
SizedBox(height: 5),
CustomTextFieldWrapper(
isFocused: focusStates["_comments1Focused"] ?? false,
isDesktop: isDesktop,
width: isDesktop ? MediaQuery.of(context).size.width * 0.32 : null,
child: TextField(
focusNode: focusNodes["_comments1FocusNode"],
// controller: _commentsController,
controller: textControllers["_comments1Controller"],
// maxLines: 6,
// keyboardType: TextInputType.multiline,
style: TextStyle(fontSize: 12),
decoration: InputDecoration(
labelText: "Comments",
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
// contentPadding: EdgeInsets.symmetric(vertical: 1),
),
),
),
],
),
if (isDesktop) Spacer(),
SizedBox(
height: 5,
),
Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: _handleAction(isDesktop),
),
],
),
];
}
List<Widget> _buildvisa(bool isDesktop) {
List<dynamic> visa_available =
widget.apiData?['flight_visa_available'] ?? [];
// Default selected value
List<DropdownMenuItem<String>> dropdownItems = visa_available
.map((item) => DropdownMenuItem<String>(
value: item['dropdown_key'],
child: Text(item['dropdown_value']),
))
.toList();
selectedvisa_available ??=
dropdownItems.isNotEmpty ? dropdownItems.first.value : null;
if (dropdownItems.isEmpty) {
dropdownItems.add(
DropdownMenuItem<String>(
value: null,
child: Text("No options available",
style: TextStyle(color: Colors.grey)),
),
);
}
return [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Visa Required",
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74)),
),
SizedBox(height: 5),
CustomTextFieldWrapper(
// isFocused: _tripTypeFocused,
isFocused: focusStates["_visa1Focused"] ?? false,
isDesktop: isDesktop,
width: isDesktop
? MediaQuery.of(context).size.width * 0.34
: MediaQuery.of(context).size.width * 0.66,
child: SizedBox(
height: 40,
child: DropdownButtonFormField<String>(
// focusNode: _tripTypeFocusNode, // Assign the correct focus node
focusNode: focusNodes["_visa1FocusNode"],
value: selectedvisa_available,
style: TextStyle(fontSize: 12),
decoration: InputDecoration(
border: InputBorder.none,
contentPadding:
EdgeInsets.symmetric(horizontal: 10), // Proper padding
),
onChanged: visa_available.isNotEmpty
? (newValue) {
setState(() {
selectedvisa_available = newValue;
// selectedTripType = "Oneway";
// Reset `multiTripRowCount` when switching away from Multitrip
});
print(
"Updating form data: Flight -> trip_type -> $selectedvisa_available");
// _initializeRows();
}
: null,
items: dropdownItems,
),
),
),
],
),
];
}
List<Widget> _handleAction(bool isDesktop) {
return [
// Close Button
ElevatedButton(
onPressed: () {
widget.onClose(false); // Close the dialog or screen
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.grey[400], // Light grey color
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
),
child: Text(
"Close",
style: TextStyle(color: Colors.white, fontSize: 14),
),
),
SizedBox(width: 10), // Space between buttons
// Save Changes Button
ElevatedButton(
onPressed: () {
handleSave();
},
style: ElevatedButton.styleFrom(
backgroundColor: Color(0xFF114D8B), // Primary color for save
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
),
child: Text(
"Save Changes",
style: TextStyle(color: Colors.white, fontSize: 14),
),
),
];
}
}