1749 lines
59 KiB
Dart
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),
|
|
),
|
|
),
|
|
];
|
|
}
|
|
}
|