4026 lines
176 KiB
Dart
4026 lines
176 KiB
Dart
import 'package:dropdown_search/dropdown_search.dart';
|
||
import 'package:flutter/material.dart';
|
||
import 'package:frontend/services/apiService.dart';
|
||
import 'package:google_fonts/google_fonts.dart';
|
||
import 'package:intl/intl.dart';
|
||
import 'package:responsive_builder/responsive_builder.dart';
|
||
|
||
import '../../utils/auth_utils.dart';
|
||
import '../../widgets/custom_confirmation_dialog.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(String, bool) onClose;
|
||
final Function(Map<String, dynamic>) onSaveFlight;
|
||
final Map<String, dynamic>? selectedItem;
|
||
final ValueNotifier<String?> tripTypeNotifier;
|
||
|
||
FlightScreen({
|
||
Key? key,
|
||
required this.apiData,
|
||
required this.loginUser,
|
||
required this.onClose,
|
||
required this.onSaveFlight,
|
||
required this.selectedItem,
|
||
required this.flightData,
|
||
required this.hasAction,
|
||
this.tripType,
|
||
required this.tripTypeNotifier,
|
||
this.apiDataForClass,
|
||
}) : super(key: key);
|
||
|
||
@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 Map<String, String> countryCodeMap;
|
||
late List<String> countryCodes;
|
||
|
||
late ValueNotifier<String?> flightFirstTripDateNotifier;
|
||
late ValueNotifier<String?> flightLastTripDateNotifier;
|
||
|
||
String? selectedCountry;
|
||
List<Map<String, dynamic>> countryList = [];
|
||
|
||
Map<String, String?> selectedValues = {};
|
||
|
||
int rowCount = 1;
|
||
|
||
String? selectedTripType;
|
||
Map<int, String?> selectedClasses = {}; // Store class selection for each trip
|
||
Map<int, String?> exceptionalClass =
|
||
{}; // 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;
|
||
|
||
// Color? layoutColor;
|
||
Color layoutColor = Colors.grey;
|
||
|
||
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
|
||
bool _hasInitialTripTypeLoaded = false;
|
||
Map<String, String> errorMessages = {};
|
||
// forex_pre_paid_card_number
|
||
@override
|
||
void initState() {
|
||
super.initState();
|
||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||
loadInitialData();
|
||
});
|
||
print("TripType - ${widget.tripType}");
|
||
|
||
// final newTripType = widget.tripTypeNotifier.value;
|
||
// print("TripType1 - $newTripType");
|
||
|
||
// // Listen immediately
|
||
// widget.tripTypeNotifier.addListener(() {
|
||
// final newTripType = widget.tripTypeNotifier.value;
|
||
//
|
||
// if (newTripType != null && newTripType.isNotEmpty) {
|
||
// if (_hasInitialTripTypeLoaded) {
|
||
// print("🚀 FlightScreen reacting to new trip type: $newTripType");
|
||
// loadCountryList(newTripType);
|
||
// } else {
|
||
// print("🟡 Skipping initial tripTypeNotifier value: $newTripType");
|
||
// _hasInitialTripTypeLoaded = true;
|
||
// }
|
||
// }
|
||
// });
|
||
|
||
// _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'];
|
||
final tripTypeValue = widget.tripTypeNotifier.value;
|
||
|
||
if (tripTypeValue != null && tripTypeValue.isNotEmpty) {
|
||
print("🚀 Initial loadCountryList for tripType: $tripTypeValue");
|
||
loadCountryList(tripTypeValue);
|
||
} else {
|
||
print("⚠️ tripType is null or empty, skipping loadCountryList");
|
||
}
|
||
});
|
||
}
|
||
|
||
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(newTripType) async {
|
||
print("🔄 loadCountryList called with tripType: ${newTripType}");
|
||
|
||
setState(() {
|
||
isCountryLoading = true;
|
||
});
|
||
|
||
final result = await apiService.fetchFlightsCountryList(newTripType);
|
||
|
||
print("ResultCountry : $result");
|
||
|
||
// Create a map: Country_Code -> "City, Airport"
|
||
Map<String, String> tempCountryMap = {};
|
||
Map<String, String> tempCountryCode = {};
|
||
|
||
for (var country in result) {
|
||
// String city = country['City'] ?? '';
|
||
// String airport = country['Airport'] ?? '';
|
||
// String displayName = '${country['City']} - ${country['Airport']}';
|
||
|
||
final code = country['Code'] ?? '';
|
||
final city = country['City'] ?? '';
|
||
final airport = country['Airport'] ?? '';
|
||
final countyCode = country['Country_Code'] ?? '';
|
||
final displayName = '$city ($code)\n$airport';
|
||
|
||
tempCountryMap[country['Code']] = displayName;
|
||
tempCountryCode[country['Code']] = countyCode;
|
||
}
|
||
|
||
setState(() {
|
||
countryMap = tempCountryMap; // Update the map
|
||
countryCodeMap = tempCountryCode;
|
||
isCountryLoading = false;
|
||
});
|
||
}
|
||
|
||
int getRowCount() {
|
||
if (selectedTripType == "RoundTrip") {
|
||
return 2;
|
||
} else if (selectedTripType == "Multitrip") {
|
||
return multiTripRowCount;
|
||
}
|
||
return 1; // Default for Oneway
|
||
}
|
||
|
||
void loadInitialData() async {
|
||
String? layoutString = await getLayoutColor();
|
||
|
||
// setState(() {
|
||
// layoutColor =
|
||
// layoutString != null
|
||
// ? Color(int.parse(layoutString))
|
||
// : Colors.redAccent;
|
||
// });
|
||
|
||
setState(() {
|
||
layoutColor =
|
||
layoutString != null
|
||
? Color(int.parse(layoutString))
|
||
: Colors.redAccent;
|
||
});
|
||
}
|
||
|
||
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],
|
||
"is_this_exceptional": exceptionalClass[i],
|
||
// "from_place": countryMap[selectedFrom[i]],
|
||
"from_place": selectedFrom[i],
|
||
"to_place": selectedTo[i],
|
||
"from_country_code": countryCodeMap[selectedFrom[i]],
|
||
"to_country_code": countryCodeMap[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();
|
||
exceptionalClass[index] = trip["is_this_exceptional"].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);
|
||
widget.onClose("Flight", false);
|
||
|
||
print("FlightData - $flightsData");
|
||
}
|
||
|
||
void validateTimeDifference(int index) {
|
||
if (index <= 1) return; // Skip validation for the first row
|
||
|
||
String? prevDateStr = textControllers["_date${index - 1}Controller"]?.text;
|
||
String? prevTimeStr = textControllers["_time${index - 1}Controller"]?.text;
|
||
String? currDateStr = textControllers["_date${index}Controller"]?.text;
|
||
String? currTimeStr = textControllers["_time${index}Controller"]?.text;
|
||
|
||
print("prevDateTime - $prevTimeStr");
|
||
print("currDateTime - $currTimeStr");
|
||
if (prevDateStr != null &&
|
||
prevDateStr.isNotEmpty &&
|
||
prevTimeStr != null &&
|
||
prevTimeStr.isNotEmpty &&
|
||
currDateStr != null &&
|
||
currDateStr.isNotEmpty &&
|
||
currTimeStr != null &&
|
||
currTimeStr.isNotEmpty) {
|
||
try {
|
||
final format = DateFormat("dd-MM-yyyy HH:mm");
|
||
|
||
// final prevDateTime = DateTime.parse("$prevDateStr $prevTimeStr");
|
||
// final currDateTime = DateTime.parse("$currDateStr $currTimeStr");
|
||
|
||
final prevDateTime = format.parse("$prevDateStr $prevTimeStr");
|
||
final currDateTime = format.parse("$currDateStr $currTimeStr");
|
||
|
||
if (!currDateTime.isAfter(prevDateTime)) {
|
||
errorMessages["time_$index"] = "30 mins gap required";
|
||
} else if (currDateTime.difference(prevDateTime).inMinutes < 30) {
|
||
errorMessages["time_$index"] = "30 mins gap required";
|
||
} else {
|
||
errorMessages.remove("time_$index");
|
||
}
|
||
} catch (e) {
|
||
print(" Invalid time format");
|
||
errorMessages["time_$index"] = "Invalid time format";
|
||
}
|
||
}
|
||
}
|
||
|
||
bool validateFields() {
|
||
errorMessages.clear(); // Reset errors
|
||
|
||
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";
|
||
// }else if (
|
||
// selectedFrom[i] == selectedTo[i]) {
|
||
// errorMessages["to_place_$i"] = "Change Destination";
|
||
// }
|
||
|
||
if (selectedTo[i] == null) {
|
||
errorMessages["to_place_$i"] = "Required";
|
||
print("Error: to_place_$i -> Required (selectedTo[$i] is null)");
|
||
} else if (selectedFrom[i] == selectedTo[i]) {
|
||
if (selectedTripType == "Roundtrip") {
|
||
errorMessages["to_place_1"] = "Change Destination";
|
||
} else {
|
||
errorMessages["to_place_$i"] = "Change Destination";
|
||
}
|
||
|
||
print(
|
||
"Error: to_place_$i -> Change Destination (selectedFrom[$i] == selectedTo[$i])",
|
||
);
|
||
}
|
||
|
||
// 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["_date${i}Controller"]?.text.trim().isEmpty ?? true) {
|
||
errorMessages["date_$i"] = "Required";
|
||
}
|
||
|
||
if( selectedClasses[i] == null ) {
|
||
errorMessages["class_$i"] = "Required";
|
||
}
|
||
if (textControllers["_time${i}Controller"]?.text.trim().isEmpty ?? true) {
|
||
errorMessages["time_$i"] = "Required";
|
||
}
|
||
}
|
||
//selectedFrom For every trips should n't same
|
||
Set<String?> seenFrom = {};
|
||
Set<String?> seenTo = {};
|
||
|
||
if (selectedTripType == "Roundtrip") {
|
||
for (int i = 1; i <= rowCount; i++) {
|
||
final fromValue = selectedFrom[i];
|
||
final toValue = selectedTo[i];
|
||
if (fromValue != null) {
|
||
if (seenFrom.contains(fromValue)) {
|
||
errorMessages["from_place_$i"] = "Duplicate Departure ";
|
||
print("Error: from_place_$i duplicates a previous departure");
|
||
} else {
|
||
seenFrom.add(fromValue);
|
||
|
||
// errorMessages.remove("from_place_$i");
|
||
}
|
||
}
|
||
if (toValue != null) {
|
||
if (seenTo.contains(toValue)) {
|
||
errorMessages["to_place_$i"] = "Duplicate Destination ";
|
||
print("Error: to_place_$i duplicates a previous departure");
|
||
} else {
|
||
// errorMessages.remove("to_place_$i");
|
||
seenTo.add(toValue);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// Step 3: Sequential Date Comparison
|
||
DateFormat format = DateFormat("dd-MM-yyyy"); // Assumes "12 Jun" format
|
||
DateTime now = DateTime.now();
|
||
List<DateTime> parsedDates = [];
|
||
|
||
for (int i = 1; i <= rowCount; i++) {
|
||
String? dateStr = textControllers["_date${i}Controller"]?.text.trim();
|
||
String? timeStr = textControllers["_time${i}Controller"]?.text.trim();
|
||
|
||
print("VAlidationDATE - $dateStr ");
|
||
if (dateStr != null && dateStr.isNotEmpty) {
|
||
try {
|
||
DateTime date = format.parseStrict(dateStr);
|
||
date = DateTime(
|
||
date.year,
|
||
date.month,
|
||
date.day,
|
||
); // Assume current year
|
||
parsedDates.add(date);
|
||
} catch (e) {
|
||
errorMessages["date_$i"] = "Invalid format";
|
||
}
|
||
}
|
||
}
|
||
|
||
for (int i = 1; i < parsedDates.length; i++) {
|
||
if (parsedDates[i].isAtSameMomentAs(parsedDates[i - 1])) {
|
||
//Here check time[i] and time[i-1] if same error 30 mins gap reeuired
|
||
print("Ckecking Same DAte");
|
||
// Same date - check time gap
|
||
// Combine parsedDates and times into DateTime objects
|
||
String prevDateStr =
|
||
textControllers["_date${i}Controller"]!.text.trim();
|
||
String prevTimeStr =
|
||
textControllers["_time${i}Controller"]!.text.trim();
|
||
String currDateStr =
|
||
textControllers["_date${i + 1}Controller"]!.text.trim();
|
||
String currTimeStr =
|
||
textControllers["_time${i + 1}Controller"]!.text.trim();
|
||
|
||
final dtFormat = DateFormat("dd-MM-yyyy HH:mm");
|
||
final prevDT = dtFormat.parse("$prevDateStr $prevTimeStr");
|
||
final currDT = dtFormat.parse("$currDateStr $currTimeStr");
|
||
|
||
int diffMins = currDT.difference(prevDT).inMinutes;
|
||
print("Time difference between row ${i} and ${i + 1}: $diffMins mins");
|
||
|
||
// If same day (diff >= 0 & < 1440 minutes), enforce 30‑min minimum gap
|
||
if (diffMins < 0) {
|
||
errorMessages["time_${i + 1}"] = "Must be after previous";
|
||
} else if (diffMins < 30) {
|
||
errorMessages["time_${i + 1}"] = "30 mins gap required";
|
||
} else {
|
||
errorMessages.remove("time_${i + 1}");
|
||
}
|
||
|
||
print("Ckecking Same DAte...1");
|
||
} else if (!parsedDates[i].isAfter(parsedDates[i - 1])) {
|
||
errorMessages["date_${i + 1}"] = "Must be after date_${i}";
|
||
print("Error: date_${i + 1} is not after date_${i}");
|
||
} else {
|
||
errorMessages.remove("date_${i + 1}");
|
||
}
|
||
}
|
||
|
||
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
|
||
|
||
Map<int, String?> updatedExptionalClasses = {};
|
||
newIndex = 1;
|
||
|
||
for (int i = 1; i <= exceptionalClass.length; i++) {
|
||
if (i == index) continue; // Skip the one being deleted
|
||
updatedExptionalClasses[newIndex] = exceptionalClass[i];
|
||
newIndex++;
|
||
}
|
||
exceptionalClass = updatedExptionalClasses;
|
||
|
||
// 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.only(top: 30.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(
|
||
minimumSize: !isDesktop
|
||
? const Size.fromHeight(48)
|
||
: null, // No min height on desktop // height
|
||
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<String> filterAndSortCountryList(List<String> items, String filter) {
|
||
final lowerFilter = filter.toLowerCase();
|
||
|
||
int getMatchScore(String item) {
|
||
final parts = item.split('\n');
|
||
final cityAndCode = parts[0];
|
||
final airport = parts.length > 1 ? parts[1].toLowerCase() : '';
|
||
|
||
final match = RegExp(r'^(.*)\s+\(([^)]+)\)$').firstMatch(cityAndCode);
|
||
final city = match?.group(1)?.toLowerCase() ?? '';
|
||
final code = match?.group(2)?.toLowerCase() ?? '';
|
||
|
||
if (code == lowerFilter) return 0;
|
||
if (code.startsWith(lowerFilter)) return 1;
|
||
if (code.contains(lowerFilter)) return 2;
|
||
if (city == lowerFilter) return 3;
|
||
if (city.startsWith(lowerFilter)) return 4;
|
||
if (city.contains(lowerFilter)) return 5;
|
||
if (airport.contains(lowerFilter)) return 6;
|
||
return 999;
|
||
}
|
||
|
||
return items.where((item) {
|
||
final parts = item.split('\n');
|
||
final cityAndCode = parts[0];
|
||
final airport = parts.length > 1 ? parts[1].toLowerCase() : '';
|
||
|
||
final match = RegExp(r'^(.*)\s+\(([^)]+)\)$').firstMatch(cityAndCode);
|
||
final city = match?.group(1)?.toLowerCase() ?? '';
|
||
final code = match?.group(2)?.toLowerCase() ?? '';
|
||
|
||
return code.contains(lowerFilter) ||
|
||
city.contains(lowerFilter) ||
|
||
airport.contains(lowerFilter);
|
||
}).toList()
|
||
..sort((a, b) => getMatchScore(a).compareTo(getMatchScore(b)));
|
||
}
|
||
|
||
List<Widget> _buildFirstRow(isDesktop) {
|
||
return [
|
||
Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Text(
|
||
"Trip Type",
|
||
style: GoogleFonts.poppins(
|
||
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,
|
||
isFocused: false,
|
||
padding: const EdgeInsets.symmetric(horizontal: 0),
|
||
isDesktop: isDesktop,
|
||
width: isDesktop ? MediaQuery.of(context).size.width * 0.32 : null,
|
||
child: SizedBox(
|
||
height: 40,
|
||
width: double.infinity,
|
||
child: Focus(
|
||
focusNode: focusNodes["_tripType1FocusNode"],
|
||
onFocusChange: (hasFocus) {
|
||
setState(() {
|
||
focusStates["_tripType1Focused"] = hasFocus;
|
||
});
|
||
},
|
||
child: GestureDetector(
|
||
//
|
||
onTap: () {
|
||
// Request focus when user taps
|
||
focusNodes["_tripType1FocusNode"]?.requestFocus();
|
||
},
|
||
child: DropdownSearch<String>(
|
||
items:
|
||
purposeList
|
||
.map((item) => item['dropdown_value'] as String)
|
||
.toList(),
|
||
dropdownDecoratorProps: DropDownDecoratorProps(
|
||
dropdownSearchDecoration: InputDecoration(
|
||
// border: InputBorder.none,
|
||
border: OutlineInputBorder(
|
||
borderRadius: BorderRadius.circular(8),
|
||
borderSide: BorderSide(
|
||
color:
|
||
(focusStates["_tripType1Focused"] ?? false)
|
||
? layoutColor!
|
||
: Colors.white,
|
||
width: 0.5,
|
||
),
|
||
),
|
||
enabledBorder: OutlineInputBorder(
|
||
borderSide: BorderSide(
|
||
color:
|
||
(focusStates["_tripType1Focused"] ?? false)
|
||
? layoutColor
|
||
: Colors.white,
|
||
// : const Color(0xFFD6D5E6),
|
||
width: 0.5,
|
||
// const Color(0xFFD6D5E6),
|
||
),
|
||
),
|
||
focusedBorder: OutlineInputBorder(
|
||
borderSide: BorderSide(color: layoutColor, width: 1),
|
||
),
|
||
contentPadding: EdgeInsets.symmetric(
|
||
horizontal: 10,
|
||
vertical: 5,
|
||
),
|
||
),
|
||
),
|
||
onChanged: (newValue) {
|
||
setState(() {
|
||
selectedTripType = newValue;
|
||
if (selectedTripType != "Multitrip") {
|
||
multiTripRowCount = 1;
|
||
}
|
||
errorMessages.clear();
|
||
|
||
for (int i = 1; i <= rowCount; i++) {
|
||
textControllers["_date${i}Controller"]?.clear();
|
||
textControllers["_time${i}Controller"]?.clear();
|
||
|
||
// Also clear the actual selected data (not just the text in controllers)
|
||
selectedFrom[i] =
|
||
null; // Assuming null means no selection
|
||
selectedTo[i] = null;
|
||
}
|
||
});
|
||
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) {
|
||
bool isFlightClassLoading = widget.apiDataForClass?['flight_class'] == null;
|
||
|
||
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(
|
||
'dd-MM-yyyy',
|
||
).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(
|
||
'dd-MM-yyyy',
|
||
).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),
|
||
initialEntryMode: DatePickerEntryMode.calendarOnly,
|
||
);
|
||
|
||
if (pickedDate != null && pickedDate != _selectedCheckOutDate) {
|
||
setState(() {
|
||
_selectedCheckOutDate = pickedDate;
|
||
// _dateController.text = DateFormat('yyyy-MM-dd').format(pickedDate);
|
||
textControllers["_date${index}Controller"]?.text = DateFormat(
|
||
'dd-MM-yyyy',
|
||
).format(pickedDate);
|
||
});
|
||
}
|
||
}
|
||
|
||
Future<void> _selectCheckOutTime(
|
||
BuildContext context,
|
||
int index,
|
||
VoidCallback onPicked,
|
||
) async {
|
||
TimeOfDay? pickedTime = await showTimePicker(
|
||
context: context,
|
||
initialTime: _selectedCheckOutTime ?? TimeOfDay.now(),
|
||
);
|
||
|
||
if (pickedTime != null) {
|
||
final now = DateTime.now();
|
||
|
||
// Parse the selected date
|
||
final dateText = textControllers["_date${index}Controller"]?.text ?? "";
|
||
final selectedDate = DateFormat(
|
||
'dd-MM-yyyy',
|
||
).parse(dateText); // or 'yyyy-MM-dd' depending on your format
|
||
|
||
final selectedDateTime = DateTime(
|
||
selectedDate.year,
|
||
selectedDate.month,
|
||
selectedDate.day,
|
||
pickedTime.hour,
|
||
pickedTime.minute,
|
||
);
|
||
|
||
// ✅ Only validate past time if date is today
|
||
final isToday =
|
||
selectedDate.year == now.year &&
|
||
selectedDate.month == now.month &&
|
||
selectedDate.day == now.day;
|
||
|
||
bool isPastTime = selectedDateTime.isBefore(now);
|
||
|
||
if (isToday && isPastTime) {
|
||
setState(() {
|
||
errorMessages["time_$index"] = "You can't select a past time.";
|
||
});
|
||
return;
|
||
}
|
||
|
||
// ✅ Valid time selection
|
||
setState(() {
|
||
_selectedCheckOutTime = pickedTime;
|
||
|
||
final formattedTime = DateFormat('HH:mm').format(selectedDateTime);
|
||
textControllers["_time${index}Controller"]?.text = formattedTime;
|
||
// errorMessages["time_$index"] = ""; // clear previous error
|
||
errorMessages.remove("time_$index");
|
||
onPicked(); // Trigger callback
|
||
});
|
||
}
|
||
|
||
// 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;
|
||
//
|
||
// onPicked();
|
||
// });
|
||
// }
|
||
}
|
||
|
||
return isDesktop
|
||
? [
|
||
Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Text(
|
||
"From*",
|
||
style: GoogleFonts.poppins(
|
||
fontSize: 12,
|
||
fontWeight: FontWeight.w600,
|
||
color: Color(0xFF575A74),
|
||
),
|
||
),
|
||
SizedBox(height: 5),
|
||
CustomTextFieldItnerarySubWrapper(
|
||
// isFocused: _fromFocus,
|
||
// isFocused: focusStates["_from${index}Focused"] ?? false,
|
||
isFocused: false,
|
||
padding: const EdgeInsets.symmetric(horizontal: 0),
|
||
// isFocused: focusStates["_from${fieldIndex}Focused"] ?? false,
|
||
isDesktop: isDesktop,
|
||
child: SizedBox(
|
||
height: 40,
|
||
child:
|
||
isCountryLoading
|
||
? Center(child: CircularProgressIndicator())
|
||
: Focus(
|
||
focusNode: focusNodes["_from${index}FocusNode"],
|
||
onFocusChange: (hasFocus) {
|
||
setState(() {
|
||
focusStates["_from${index}Focused"] = hasFocus;
|
||
});
|
||
},
|
||
child: GestureDetector(
|
||
//
|
||
onTap: () {
|
||
// Request focus when user taps
|
||
focusNodes["_from${index}FocusNode"]
|
||
?.requestFocus();
|
||
},
|
||
child: DropdownSearch<String>(
|
||
enabled:
|
||
!(selectedTripType == "Roundtrip" &&
|
||
index == 2),
|
||
selectedItem:
|
||
selectedFrom[index] != null
|
||
? countryMap[selectedFrom[index]]
|
||
: null,
|
||
items: countryMap.values.toList(),
|
||
// asyncItems: (String filter) async {
|
||
// final allItems = countryMap.values.toSet().toList();
|
||
// print("Original items: ${allItems.length}");
|
||
// final filtered = filterAndSortCountryList(allItems, filter);
|
||
// // print("Filtered items: ${filtered.length} -> $filtered");
|
||
// return filtered;
|
||
// },
|
||
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) {
|
||
final parts = item.split('\n');
|
||
final cityAndCode = parts[0];
|
||
final airport =
|
||
parts.length > 1 ? parts[1] : '';
|
||
|
||
// Extract city and code from "City (CODE)"
|
||
final cityMatch = RegExp(
|
||
r'^(.*)\s+\(([^)]+)\)$',
|
||
).firstMatch(cityAndCode);
|
||
final city = cityMatch?.group(1) ?? '';
|
||
final code = cityMatch?.group(2) ?? '';
|
||
|
||
return Padding(
|
||
padding: const EdgeInsets.symmetric(
|
||
horizontal: 8.0,
|
||
vertical: 6.0,
|
||
),
|
||
child: Column(
|
||
crossAxisAlignment:
|
||
CrossAxisAlignment.start,
|
||
children: [
|
||
Row(
|
||
mainAxisAlignment:
|
||
MainAxisAlignment.spaceBetween,
|
||
children: [
|
||
Text(
|
||
city,
|
||
style: const TextStyle(
|
||
fontSize: 12,
|
||
fontWeight: FontWeight.bold,
|
||
),
|
||
),
|
||
Text(
|
||
code,
|
||
style: const TextStyle(
|
||
fontSize: 12,
|
||
fontWeight: FontWeight.bold,
|
||
),
|
||
),
|
||
],
|
||
),
|
||
const SizedBox(height: 1),
|
||
Text(
|
||
airport,
|
||
style: const TextStyle(
|
||
fontSize: 11,
|
||
color: Colors.grey,
|
||
overflow: TextOverflow.ellipsis,
|
||
),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
},
|
||
|
||
// 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(),
|
||
// filterFn: (item, filter) {
|
||
// final lowerFilter = filter.toLowerCase();
|
||
//
|
||
// final parts = item.split('\n');
|
||
// final cityAndCode = parts[0];
|
||
// final airport = parts.length > 1 ? parts[1].toLowerCase() : '';
|
||
//
|
||
// // Extract city and code from "City (CODE)"
|
||
// final cityMatch = RegExp(r'^(.*)\s+\(([^)]+)\)$').firstMatch(cityAndCode);
|
||
// final city = cityMatch?.group(1)?.toLowerCase() ?? '';
|
||
// final code = cityMatch?.group(2)?.toLowerCase() ?? '';
|
||
//
|
||
// // Priority: Code > City > Airport
|
||
// return code.contains(lowerFilter) ||
|
||
// city.contains(lowerFilter) ||
|
||
// airport.contains(lowerFilter);
|
||
// },
|
||
dropdownDecoratorProps: DropDownDecoratorProps(
|
||
dropdownSearchDecoration: InputDecoration(
|
||
border: OutlineInputBorder(
|
||
borderRadius: BorderRadius.circular(8),
|
||
borderSide: BorderSide(
|
||
color:
|
||
(focusStates["_from${index}Focused"] ??
|
||
false)
|
||
? layoutColor!
|
||
: Colors.white,
|
||
width: 0.5,
|
||
),
|
||
),
|
||
enabledBorder: OutlineInputBorder(
|
||
borderSide: BorderSide(
|
||
color:
|
||
(focusStates["_from${index}Focused"] ??
|
||
false)
|
||
? layoutColor
|
||
: Colors.white,
|
||
// : const Color(0xFFD6D5E6),
|
||
width: 0.5,
|
||
// const Color(0xFFD6D5E6),
|
||
),
|
||
),
|
||
focusedBorder: OutlineInputBorder(
|
||
borderSide: BorderSide(
|
||
color: layoutColor,
|
||
width: 1,
|
||
),
|
||
),
|
||
contentPadding: EdgeInsets.symmetric(
|
||
horizontal: 10,
|
||
vertical: 5,
|
||
),
|
||
),
|
||
),
|
||
dropdownBuilder: (context, selectedItem) {
|
||
if (selectedItem == null) {
|
||
return Align(
|
||
alignment: Alignment.centerLeft,
|
||
child: const Text(
|
||
"Select",
|
||
style: TextStyle(fontSize: 12),
|
||
),
|
||
);
|
||
}
|
||
|
||
final parts = selectedItem.split('\n');
|
||
final cityAndCode = parts[0];
|
||
final airport = parts.length > 1 ? parts[1] : '';
|
||
|
||
return Align(
|
||
alignment: Alignment.centerLeft,
|
||
child: Text(
|
||
cityAndCode ?? "Select",
|
||
style: const TextStyle(fontSize: 12),
|
||
),
|
||
);
|
||
},
|
||
|
||
// dropdownBuilder:
|
||
// (context, selectedItem) => Align(
|
||
// alignment: Alignment.centerLeft,
|
||
// child: Text(
|
||
// selectedItem ?? "Select",
|
||
// style: TextStyle(fontSize: 12),
|
||
// ),
|
||
// ),
|
||
onChanged: (String? newValue) {
|
||
setState(() {
|
||
errorMessages.remove("from_place_$index");
|
||
// selectedFrom[index] = countryMap.entries
|
||
// .firstWhere((entry) => entry.value == newValue)
|
||
// .key;
|
||
|
||
selectedFrom[index] =
|
||
countryMap.entries
|
||
.firstWhere(
|
||
(entry) => entry.value == newValue,
|
||
)
|
||
.key;
|
||
if (selectedTripType == "Roundtrip") {
|
||
print('rounfTo${selectedFrom[index]}');
|
||
// textControllers["_to${index+1}Controller"]?.text = selectedFrom[index]!;
|
||
selectedTo[2] = 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(
|
||
errorMessages["from_place_$index"]!,
|
||
style: TextStyle(color: Colors.red, fontSize: 12),
|
||
),
|
||
],
|
||
],
|
||
),
|
||
if (isDesktop) SizedBox(width: 20) else SizedBox(height: 8),
|
||
Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Text(
|
||
"To*",
|
||
style: GoogleFonts.poppins(
|
||
fontSize: 12,
|
||
fontWeight: FontWeight.w600,
|
||
color: Color(0xFF575A74),
|
||
),
|
||
),
|
||
SizedBox(height: 5),
|
||
CustomTextFieldItnerarySubWrapper(
|
||
// isFocused: focusStates["_to${index}Focused"] ?? false,
|
||
isFocused: false,
|
||
padding: const EdgeInsets.symmetric(horizontal: 0),
|
||
isDesktop: isDesktop,
|
||
child: SizedBox(
|
||
height: 40,
|
||
child:
|
||
isCountryLoading
|
||
? Center(child: CircularProgressIndicator())
|
||
: Focus(
|
||
focusNode: focusNodes["_to${index}FocusNode"],
|
||
onFocusChange: (hasFocus) {
|
||
setState(() {
|
||
focusStates["_to${index}Focused"] = hasFocus;
|
||
});
|
||
},
|
||
child: GestureDetector(
|
||
//
|
||
onTap: () {
|
||
// Request focus when user taps
|
||
focusNodes["_to${index}FocusNode"]?.requestFocus();
|
||
},
|
||
child: DropdownSearch<String>(
|
||
enabled:
|
||
!(selectedTripType == "Roundtrip" &&
|
||
index == 2),
|
||
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) {
|
||
final parts = item.split('\n');
|
||
final cityAndCode = parts[0];
|
||
final airport =
|
||
parts.length > 1 ? parts[1] : '';
|
||
|
||
// Extract city and code from "City (CODE)"
|
||
final cityMatch = RegExp(
|
||
r'^(.*)\s+\(([^)]+)\)$',
|
||
).firstMatch(cityAndCode);
|
||
final city = cityMatch?.group(1) ?? '';
|
||
final code = cityMatch?.group(2) ?? '';
|
||
|
||
return Padding(
|
||
padding: const EdgeInsets.symmetric(
|
||
horizontal: 8.0,
|
||
vertical: 6.0,
|
||
),
|
||
child: Column(
|
||
crossAxisAlignment:
|
||
CrossAxisAlignment.start,
|
||
children: [
|
||
Row(
|
||
mainAxisAlignment:
|
||
MainAxisAlignment.spaceBetween,
|
||
children: [
|
||
Text(
|
||
city,
|
||
style: const TextStyle(
|
||
fontSize: 12,
|
||
fontWeight: FontWeight.bold,
|
||
),
|
||
),
|
||
Text(
|
||
code,
|
||
style: const TextStyle(
|
||
fontSize: 12,
|
||
fontWeight: FontWeight.bold,
|
||
),
|
||
),
|
||
],
|
||
),
|
||
const SizedBox(height: 1),
|
||
Text(
|
||
airport,
|
||
style: const TextStyle(
|
||
fontSize: 11,
|
||
color: Colors.grey,
|
||
overflow: TextOverflow.ellipsis,
|
||
),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
},
|
||
// 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: OutlineInputBorder(
|
||
borderRadius: BorderRadius.circular(8),
|
||
borderSide: BorderSide(
|
||
color:
|
||
(focusStates["_to${index}Focused"] ??
|
||
false)
|
||
? layoutColor!
|
||
: Colors.white,
|
||
width: 0.5,
|
||
),
|
||
),
|
||
enabledBorder: OutlineInputBorder(
|
||
borderSide: BorderSide(
|
||
color:
|
||
(focusStates["_to${index}Focused"] ??
|
||
false)
|
||
? layoutColor
|
||
: Colors.white,
|
||
// : const Color(0xFFD6D5E6),
|
||
width: 0.5,
|
||
// const Color(0xFFD6D5E6),
|
||
),
|
||
),
|
||
focusedBorder: OutlineInputBorder(
|
||
borderSide: BorderSide(
|
||
color: layoutColor,
|
||
width: 1,
|
||
),
|
||
),
|
||
contentPadding: EdgeInsets.symmetric(
|
||
horizontal: 10,
|
||
vertical: 5,
|
||
),
|
||
),
|
||
),
|
||
|
||
// dropdownBuilder:
|
||
// (context, selectedItem) => Align(
|
||
// alignment: Alignment.centerLeft,
|
||
// child: Text(
|
||
// selectedItem ?? "Select Country",
|
||
// style: TextStyle(fontSize: 12),
|
||
// ),
|
||
// ),
|
||
dropdownBuilder: (context, selectedItem) {
|
||
if (selectedItem == null) {
|
||
return Align(
|
||
alignment: Alignment.centerLeft,
|
||
child: const Text(
|
||
"Select",
|
||
style: TextStyle(fontSize: 12),
|
||
),
|
||
);
|
||
}
|
||
|
||
final parts = selectedItem.split('\n');
|
||
final cityAndCode = parts[0];
|
||
final airport = parts.length > 1 ? parts[1] : '';
|
||
|
||
return Align(
|
||
alignment: Alignment.centerLeft,
|
||
child: Text(
|
||
cityAndCode ?? "Select",
|
||
style: const TextStyle(fontSize: 12),
|
||
),
|
||
);
|
||
},
|
||
onChanged: (String? newValue) {
|
||
setState(() {
|
||
errorMessages.remove("to_place_$index");
|
||
selectedTo[index] =
|
||
countryMap.entries
|
||
.firstWhere(
|
||
(entry) => entry.value == newValue,
|
||
)
|
||
.key;
|
||
|
||
print(selectedTo[index]);
|
||
|
||
if (selectedTripType == "Roundtrip") {
|
||
print('rounfTo${selectedTo[index]}');
|
||
// textControllers["_to${index+1}Controller"]?.text = selectedFrom[index]!;
|
||
selectedFrom[2] = selectedTo[index]!;
|
||
}
|
||
});
|
||
},
|
||
),
|
||
),
|
||
),
|
||
),
|
||
),
|
||
if (errorMessages["to_place_$index"] != null) ...[
|
||
SizedBox(height: 5), // Space before error message
|
||
Text(
|
||
errorMessages["to_place_$index"]!,
|
||
style: TextStyle(color: Colors.red, fontSize: 12),
|
||
),
|
||
],
|
||
],
|
||
),
|
||
if (isDesktop) Spacer() else SizedBox(height: 8),
|
||
|
||
// Column(
|
||
// crossAxisAlignment: CrossAxisAlignment.start,
|
||
// children: [
|
||
// Text(
|
||
// "Class *",
|
||
// style: GoogleFonts.poppins(
|
||
// 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:
|
||
// isFlightClassLoading
|
||
// ? 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,
|
||
// ),
|
||
// ),
|
||
// ),
|
||
// ],
|
||
// ),
|
||
Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Text(
|
||
"Class *",
|
||
style: GoogleFonts.poppins(
|
||
fontSize: 12,
|
||
fontWeight: FontWeight.w600,
|
||
color: Color(0xFF575A74),
|
||
),
|
||
),
|
||
SizedBox(height: 5),
|
||
CustomTextFieldItnerarySubWrapper(
|
||
// isFocused: focusStates["_class${index}Focused"] ?? false,
|
||
isFocused: false,
|
||
padding: const EdgeInsets.symmetric(horizontal: 0),
|
||
isDesktop: isDesktop,
|
||
child: SizedBox(
|
||
height: 40,
|
||
width: double.infinity,
|
||
child: Focus(
|
||
focusNode: focusNodes["_class${index}FocusNode"],
|
||
|
||
onFocusChange: (hasFocus) {
|
||
setState(() {
|
||
focusStates["_class${index}Focused"] = hasFocus;
|
||
});
|
||
},
|
||
child: GestureDetector(
|
||
//
|
||
onTap: () {
|
||
// Request focus when user taps
|
||
focusNodes["_class${index}FocusNode"]?.requestFocus();
|
||
},
|
||
child: DropdownSearch<Map<String, dynamic>>(
|
||
items: purposeList.cast<Map<String, dynamic>>(),
|
||
key:
|
||
selectedClasses[index] == null
|
||
? UniqueKey()
|
||
: ValueKey(selectedClasses[index]),
|
||
// selectedItem: purposeList.firstWhere(
|
||
// (item) => item['dropdown_key'] == selectedClasses[index],
|
||
// orElse: () => {},
|
||
// ),
|
||
selectedItem:
|
||
selectedClasses[index] != null
|
||
? purposeList.firstWhere(
|
||
(item) =>
|
||
item['dropdown_key'] ==
|
||
selectedClasses[index],
|
||
orElse: () => {},
|
||
)
|
||
: null,
|
||
itemAsString: (item) => item['dropdown_value'] ?? '',
|
||
popupProps: PopupProps.menu(
|
||
showSearchBox: false,
|
||
fit: FlexFit.loose,
|
||
menuProps: const MenuProps(backgroundColor: Colors.white),
|
||
itemBuilder: (context, item, isSelected) {
|
||
final bool isNotAllowed = item['is_allowed'] == 'No';
|
||
return Padding(
|
||
padding: const EdgeInsets.symmetric(
|
||
horizontal: 10,
|
||
vertical: 8,
|
||
),
|
||
child: Text(
|
||
item['dropdown_value'] ?? '',
|
||
style: GoogleFonts.poppins(
|
||
fontSize: 12,
|
||
color:
|
||
isNotAllowed
|
||
? Colors.redAccent
|
||
: Colors.black,
|
||
),
|
||
),
|
||
);
|
||
},
|
||
),
|
||
dropdownDecoratorProps: DropDownDecoratorProps(
|
||
dropdownSearchDecoration: InputDecoration(
|
||
// border: InputBorder.none,
|
||
border: OutlineInputBorder(
|
||
borderRadius: BorderRadius.circular(8),
|
||
borderSide: BorderSide(
|
||
color:
|
||
(focusStates["_class${index}Focused"] ?? false)
|
||
? layoutColor!
|
||
: Colors.white,
|
||
width: 0.5,
|
||
),
|
||
),
|
||
enabledBorder: OutlineInputBorder(
|
||
borderSide: BorderSide(
|
||
color:
|
||
(focusStates["_class${index}Focused"] ?? false)
|
||
? layoutColor
|
||
: Colors.white,
|
||
// : const Color(0xFFD6D5E6),
|
||
width: 0.5,
|
||
// const Color(0xFFD6D5E6),
|
||
),
|
||
),
|
||
focusedBorder: OutlineInputBorder(
|
||
borderSide: BorderSide(color: layoutColor, width: 1),
|
||
),
|
||
contentPadding: const EdgeInsets.symmetric(
|
||
horizontal: 10,
|
||
vertical: 5,
|
||
),
|
||
),
|
||
),
|
||
dropdownBuilder: (context, selectedItem) {
|
||
if (selectedItem == null || selectedItem.isEmpty) {
|
||
return Text(
|
||
"Select Class",
|
||
style: GoogleFonts.poppins(
|
||
color: Colors.grey,
|
||
fontSize: 13,
|
||
),
|
||
);
|
||
}
|
||
return Text(
|
||
selectedItem['dropdown_value'] ?? '',
|
||
style: GoogleFonts.poppins(
|
||
fontSize: 12,
|
||
color: Colors.black,
|
||
),
|
||
);
|
||
},
|
||
onChanged:
|
||
purposeList.isNotEmpty
|
||
? (Map<String, dynamic>? newValue) async {
|
||
// if (newValue != null &&
|
||
// newValue['is_allowed'] == 'No') {
|
||
// print('not allowed');
|
||
//
|
||
// exceptionalClass[index] = "1";
|
||
// } else if (newValue != null &&
|
||
// newValue['is_allowed'] == 'yes') {
|
||
// print('not allowed');
|
||
//
|
||
// exceptionalClass[index] = "0";
|
||
// }
|
||
//
|
||
// setState(() {
|
||
// selectedClasses[index] =
|
||
// newValue?['dropdown_key'];
|
||
// print(
|
||
// "Selected Class: ${selectedClasses[index]}",
|
||
// );
|
||
// });
|
||
|
||
if (newValue != null &&
|
||
newValue['is_allowed'] == 'No') {
|
||
final confirm = await showNotAllowedDialog(
|
||
context,
|
||
message:
|
||
"You are not entitled for this service. If you wish to continue with this service, you may require another approval.",
|
||
);
|
||
|
||
if (confirm) {
|
||
setState(() {
|
||
exceptionalClass[index] = "1";
|
||
selectedClasses[index] =
|
||
newValue['dropdown_key'];
|
||
});
|
||
print(
|
||
"Selected Purpose: ${selectedClasses[index]}",
|
||
);
|
||
} else {
|
||
setState(() {
|
||
exceptionalClass[index] = "0";
|
||
selectedClasses[index] =
|
||
null; // ❌ Clear selection if user cancels
|
||
});
|
||
print(
|
||
"selectedItem resolved to: ${selectedClasses[index]}",
|
||
);
|
||
}
|
||
|
||
return;
|
||
} else if (newValue != null &&
|
||
newValue['is_allowed'] == 'yes') {
|
||
setState(() {
|
||
exceptionalClass[index] = "0";
|
||
selectedClasses[index] =
|
||
newValue?['dropdown_key'];
|
||
print(
|
||
"Selected Purpose: ${selectedClasses[index]}",
|
||
);
|
||
});
|
||
}
|
||
}
|
||
: null,
|
||
),
|
||
),
|
||
),
|
||
),
|
||
),
|
||
if (errorMessages["class_$index"] != null)
|
||
Padding(
|
||
padding: const EdgeInsets.only(top: 4),
|
||
child: Text(
|
||
errorMessages["class_$index"]!,
|
||
style: GoogleFonts.poppins(fontSize: 12, color: Colors.red),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
if (isDesktop) Spacer() else SizedBox(height: 8),
|
||
Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Text(
|
||
"Date*",
|
||
style: GoogleFonts.poppins(
|
||
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: () {
|
||
focusNodes["_date${index}FocusNode"]?.requestFocus();
|
||
_selectCheckOutDate(context);
|
||
},
|
||
child: AbsorbPointer(
|
||
child: TextField(
|
||
focusNode: focusNodes["_date${index}FocusNode"],
|
||
// controller: _dateController,
|
||
controller: textControllers["_date${index}Controller"],
|
||
readOnly: true,
|
||
style: const TextStyle(fontSize: 12),
|
||
decoration: InputDecoration(
|
||
labelText: "Select Date",
|
||
labelStyle: const TextStyle(
|
||
fontSize: 12,
|
||
color: Colors.grey,
|
||
),
|
||
floatingLabelBehavior: FloatingLabelBehavior.never,
|
||
border: InputBorder.none,
|
||
contentPadding: EdgeInsets.symmetric(vertical: 16),
|
||
// border: OutlineInputBorder(
|
||
// borderRadius: BorderRadius.circular(8),
|
||
// borderSide: BorderSide(
|
||
// color:
|
||
// (focusStates["_date${index}Focused"] ?? false)
|
||
// ? layoutColor!
|
||
// : Colors.white,
|
||
// width: 0.5,
|
||
// ),
|
||
// ),
|
||
// enabledBorder: OutlineInputBorder(
|
||
// borderSide: BorderSide(
|
||
// color:
|
||
// (focusStates["_date${index}Focused"] ?? false)
|
||
// ? layoutColor
|
||
// : Colors.white,
|
||
// // : const Color(0xFFD6D5E6),
|
||
// width: 0.5,
|
||
// // const Color(0xFFD6D5E6),
|
||
// ),
|
||
// ),
|
||
// focusedBorder: OutlineInputBorder(
|
||
// borderSide: BorderSide(color: layoutColor, width: 1),
|
||
// ),
|
||
// contentPadding: const EdgeInsets.symmetric(
|
||
// horizontal: 10,
|
||
// vertical: 5,
|
||
// ),
|
||
suffixIcon: const Icon(
|
||
Icons.calendar_today,
|
||
size: 16,
|
||
color: Colors.grey,
|
||
),
|
||
),
|
||
),
|
||
),
|
||
),
|
||
),
|
||
),
|
||
if (errorMessages["date_$index"] != null) ...[
|
||
SizedBox(height: 5), // Space before error message
|
||
Text(
|
||
errorMessages["date_$index"]!,
|
||
style: TextStyle(color: Colors.red, fontSize: 12),
|
||
),
|
||
],
|
||
],
|
||
),
|
||
if (isDesktop) Spacer() else SizedBox(height: 8),
|
||
Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Text(
|
||
"Time*",
|
||
style: GoogleFonts.poppins(
|
||
fontSize: 12,
|
||
fontWeight: FontWeight.w600,
|
||
color: Color(0xFF575A74),
|
||
),
|
||
),
|
||
SizedBox(height: 5),
|
||
CustomTextFieldItnerarySubWrapper(
|
||
isFocused: focusStates["_time${index}Focused"] ?? false,
|
||
isDesktop: isDesktop,
|
||
width: isDesktop ? MediaQuery.of(context).size.width * 0.1 : null,
|
||
child: SizedBox(
|
||
height: 40,
|
||
child: GestureDetector(
|
||
onTap: () {
|
||
focusNodes["_time${index}FocusNode"]?.requestFocus();
|
||
textControllers["_time${index}Controller"]?.text = "";
|
||
_clearError("time_$index");
|
||
// validateTimeDifference(index);
|
||
// _selectCheckOutTime(context);
|
||
|
||
_selectCheckOutTime(context, index, () {
|
||
validateTimeDifference(index);
|
||
setState(
|
||
() {},
|
||
); // ✅ Force rebuild to show the error immediately
|
||
});
|
||
},
|
||
|
||
child: AbsorbPointer(
|
||
child: TextField(
|
||
focusNode: focusNodes["_time${index}FocusNode"],
|
||
// controller: _timeController,
|
||
controller: textControllers["_time${index}Controller"],
|
||
readOnly: true,
|
||
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,
|
||
),
|
||
),
|
||
),
|
||
),
|
||
// Focus(
|
||
// focusNode: focusNodes["_time${index}FocusNode"],
|
||
// onFocusChange: (hasFocus) {
|
||
// setState(() {
|
||
// focusStates["_time${index}Focused"] = hasFocus;
|
||
// });
|
||
// },
|
||
// child: GestureDetector(
|
||
// //
|
||
// onTap: () {
|
||
// // Request focus when user taps
|
||
// focusNodes["_time${index}FocusNode"]?.requestFocus();
|
||
// },
|
||
),
|
||
),
|
||
),
|
||
if (errorMessages["time_$index"] != null) ...[
|
||
SizedBox(height: 5), // Space before error message
|
||
Text(
|
||
errorMessages["time_$index"]!,
|
||
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),
|
||
),
|
||
),
|
||
]
|
||
: [
|
||
// For mobile, wrap inside a card with padding and grey background
|
||
Card(
|
||
elevation: 1,
|
||
margin: const EdgeInsets.symmetric(horizontal: 10, vertical: 8),
|
||
color: const Color(0xFFF5F5F5), // Light grey
|
||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||
child: Padding(
|
||
padding: const EdgeInsets.all(12),
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Text(
|
||
"From*",
|
||
style: GoogleFonts.poppins(
|
||
fontSize: 12,
|
||
fontWeight: FontWeight.w600,
|
||
color: Color(0xFF575A74),
|
||
),
|
||
),
|
||
SizedBox(height: 5),
|
||
CustomTextFieldItnerarySubWrapper(
|
||
// isFocused: _fromFocus,
|
||
// isFocused: focusStates["_from${index}Focused"] ?? false,
|
||
isFocused: false,
|
||
padding: const EdgeInsets.symmetric(horizontal: 0),
|
||
// isFocused: focusStates["_from${fieldIndex}Focused"] ?? false,
|
||
isDesktop: isDesktop,
|
||
child: SizedBox(
|
||
height: 40,
|
||
child:
|
||
isCountryLoading
|
||
? Center(child: CircularProgressIndicator())
|
||
: Focus(
|
||
focusNode: focusNodes["_from${index}FocusNode"],
|
||
onFocusChange: (hasFocus) {
|
||
setState(() {
|
||
focusStates["_from${index}Focused"] = hasFocus;
|
||
});
|
||
},
|
||
child: GestureDetector(
|
||
//
|
||
onTap: () {
|
||
// Request focus when user taps
|
||
focusNodes["_from${index}FocusNode"]
|
||
?.requestFocus();
|
||
},
|
||
child: DropdownSearch<String>(
|
||
enabled:
|
||
!(selectedTripType == "Roundtrip" &&
|
||
index == 2),
|
||
selectedItem:
|
||
selectedFrom[index] != null
|
||
? countryMap[selectedFrom[index]]
|
||
: null,
|
||
items: countryMap.values.toList(),
|
||
// asyncItems: (String filter) async {
|
||
// final allItems = countryMap.values.toSet().toList();
|
||
// print("Original items: ${allItems.length}");
|
||
// final filtered = filterAndSortCountryList(allItems, filter);
|
||
// // print("Filtered items: ${filtered.length} -> $filtered");
|
||
// return filtered;
|
||
// },
|
||
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) {
|
||
final parts = item.split('\n');
|
||
final cityAndCode = parts[0];
|
||
final airport =
|
||
parts.length > 1 ? parts[1] : '';
|
||
|
||
// Extract city and code from "City (CODE)"
|
||
final cityMatch = RegExp(
|
||
r'^(.*)\s+\(([^)]+)\)$',
|
||
).firstMatch(cityAndCode);
|
||
final city = cityMatch?.group(1) ?? '';
|
||
final code = cityMatch?.group(2) ?? '';
|
||
|
||
return Padding(
|
||
padding: const EdgeInsets.symmetric(
|
||
horizontal: 8.0,
|
||
vertical: 6.0,
|
||
),
|
||
child: Column(
|
||
crossAxisAlignment:
|
||
CrossAxisAlignment.start,
|
||
children: [
|
||
Row(
|
||
mainAxisAlignment:
|
||
MainAxisAlignment.spaceBetween,
|
||
children: [
|
||
Text(
|
||
city,
|
||
style: const TextStyle(
|
||
fontSize: 12,
|
||
fontWeight: FontWeight.bold,
|
||
),
|
||
),
|
||
Text(
|
||
code,
|
||
style: const TextStyle(
|
||
fontSize: 12,
|
||
fontWeight: FontWeight.bold,
|
||
),
|
||
),
|
||
],
|
||
),
|
||
const SizedBox(height: 1),
|
||
Text(
|
||
airport,
|
||
style: const TextStyle(
|
||
fontSize: 11,
|
||
color: Colors.grey,
|
||
overflow: TextOverflow.ellipsis,
|
||
),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
},
|
||
|
||
// 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(),
|
||
// filterFn: (item, filter) {
|
||
// final lowerFilter = filter.toLowerCase();
|
||
//
|
||
// final parts = item.split('\n');
|
||
// final cityAndCode = parts[0];
|
||
// final airport = parts.length > 1 ? parts[1].toLowerCase() : '';
|
||
//
|
||
// // Extract city and code from "City (CODE)"
|
||
// final cityMatch = RegExp(r'^(.*)\s+\(([^)]+)\)$').firstMatch(cityAndCode);
|
||
// final city = cityMatch?.group(1)?.toLowerCase() ?? '';
|
||
// final code = cityMatch?.group(2)?.toLowerCase() ?? '';
|
||
//
|
||
// // Priority: Code > City > Airport
|
||
// return code.contains(lowerFilter) ||
|
||
// city.contains(lowerFilter) ||
|
||
// airport.contains(lowerFilter);
|
||
// },
|
||
dropdownDecoratorProps: DropDownDecoratorProps(
|
||
dropdownSearchDecoration: InputDecoration(
|
||
border: OutlineInputBorder(
|
||
borderRadius: BorderRadius.circular(8),
|
||
borderSide: BorderSide(
|
||
color:
|
||
(focusStates["_from${index}Focused"] ??
|
||
false)
|
||
? layoutColor!
|
||
: Colors.white,
|
||
width: 0.5,
|
||
),
|
||
),
|
||
enabledBorder: OutlineInputBorder(
|
||
borderSide: BorderSide(
|
||
color:
|
||
(focusStates["_from${index}Focused"] ??
|
||
false)
|
||
? layoutColor
|
||
: Colors.white,
|
||
// : const Color(0xFFD6D5E6),
|
||
width: 0.5,
|
||
// const Color(0xFFD6D5E6),
|
||
),
|
||
),
|
||
focusedBorder: OutlineInputBorder(
|
||
borderSide: BorderSide(
|
||
color: layoutColor,
|
||
width: 1,
|
||
),
|
||
),
|
||
contentPadding: EdgeInsets.symmetric(
|
||
horizontal: 10,
|
||
vertical: 5,
|
||
),
|
||
),
|
||
),
|
||
dropdownBuilder: (context, selectedItem) {
|
||
if (selectedItem == null) {
|
||
return Align(
|
||
alignment: Alignment.centerLeft,
|
||
child: const Text(
|
||
"Select",
|
||
style: TextStyle(fontSize: 12),
|
||
),
|
||
);
|
||
}
|
||
|
||
final parts = selectedItem.split('\n');
|
||
final cityAndCode = parts[0];
|
||
final airport = parts.length > 1 ? parts[1] : '';
|
||
|
||
return Align(
|
||
alignment: Alignment.centerLeft,
|
||
child: Text(
|
||
cityAndCode ?? "Select",
|
||
style: const TextStyle(fontSize: 12),
|
||
),
|
||
);
|
||
},
|
||
|
||
// dropdownBuilder:
|
||
// (context, selectedItem) => Align(
|
||
// alignment: Alignment.centerLeft,
|
||
// child: Text(
|
||
// selectedItem ?? "Select",
|
||
// style: TextStyle(fontSize: 12),
|
||
// ),
|
||
// ),
|
||
onChanged: (String? newValue) {
|
||
setState(() {
|
||
errorMessages.remove("from_place_$index");
|
||
// selectedFrom[index] = countryMap.entries
|
||
// .firstWhere((entry) => entry.value == newValue)
|
||
// .key;
|
||
|
||
selectedFrom[index] =
|
||
countryMap.entries
|
||
.firstWhere(
|
||
(entry) => entry.value == newValue,
|
||
)
|
||
.key;
|
||
if (selectedTripType == "Roundtrip") {
|
||
print('rounfTo${selectedFrom[index]}');
|
||
// textControllers["_to${index+1}Controller"]?.text = selectedFrom[index]!;
|
||
selectedTo[2] = 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(
|
||
errorMessages["from_place_$index"]!,
|
||
style: TextStyle(color: Colors.red, fontSize: 12),
|
||
),
|
||
],
|
||
],
|
||
),
|
||
if (isDesktop) SizedBox(width: 20) else SizedBox(height: 8),
|
||
Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Text(
|
||
"To*",
|
||
style: GoogleFonts.poppins(
|
||
fontSize: 12,
|
||
fontWeight: FontWeight.w600,
|
||
color: Color(0xFF575A74),
|
||
),
|
||
),
|
||
SizedBox(height: 5),
|
||
CustomTextFieldItnerarySubWrapper(
|
||
// isFocused: focusStates["_to${index}Focused"] ?? false,
|
||
isFocused: false,
|
||
padding: const EdgeInsets.symmetric(horizontal: 0),
|
||
isDesktop: isDesktop,
|
||
child: SizedBox(
|
||
height: 40,
|
||
child:
|
||
isCountryLoading
|
||
? Center(child: CircularProgressIndicator())
|
||
: Focus(
|
||
focusNode: focusNodes["_to${index}FocusNode"],
|
||
onFocusChange: (hasFocus) {
|
||
setState(() {
|
||
focusStates["_to${index}Focused"] = hasFocus;
|
||
});
|
||
},
|
||
child: GestureDetector(
|
||
//
|
||
onTap: () {
|
||
// Request focus when user taps
|
||
focusNodes["_to${index}FocusNode"]?.requestFocus();
|
||
},
|
||
child: DropdownSearch<String>(
|
||
enabled:
|
||
!(selectedTripType == "Roundtrip" &&
|
||
index == 2),
|
||
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) {
|
||
final parts = item.split('\n');
|
||
final cityAndCode = parts[0];
|
||
final airport =
|
||
parts.length > 1 ? parts[1] : '';
|
||
|
||
// Extract city and code from "City (CODE)"
|
||
final cityMatch = RegExp(
|
||
r'^(.*)\s+\(([^)]+)\)$',
|
||
).firstMatch(cityAndCode);
|
||
final city = cityMatch?.group(1) ?? '';
|
||
final code = cityMatch?.group(2) ?? '';
|
||
|
||
return Padding(
|
||
padding: const EdgeInsets.symmetric(
|
||
horizontal: 8.0,
|
||
vertical: 6.0,
|
||
),
|
||
child: Column(
|
||
crossAxisAlignment:
|
||
CrossAxisAlignment.start,
|
||
children: [
|
||
Row(
|
||
mainAxisAlignment:
|
||
MainAxisAlignment.spaceBetween,
|
||
children: [
|
||
Text(
|
||
city,
|
||
style: const TextStyle(
|
||
fontSize: 12,
|
||
fontWeight: FontWeight.bold,
|
||
),
|
||
),
|
||
Text(
|
||
code,
|
||
style: const TextStyle(
|
||
fontSize: 12,
|
||
fontWeight: FontWeight.bold,
|
||
),
|
||
),
|
||
],
|
||
),
|
||
const SizedBox(height: 1),
|
||
Text(
|
||
airport,
|
||
style: const TextStyle(
|
||
fontSize: 11,
|
||
color: Colors.grey,
|
||
overflow: TextOverflow.ellipsis,
|
||
),
|
||
),
|
||
],
|
||
),
|
||
);
|
||
},
|
||
// 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: OutlineInputBorder(
|
||
borderRadius: BorderRadius.circular(8),
|
||
borderSide: BorderSide(
|
||
color:
|
||
(focusStates["_to${index}Focused"] ??
|
||
false)
|
||
? layoutColor!
|
||
: Colors.white,
|
||
width: 0.5,
|
||
),
|
||
),
|
||
enabledBorder: OutlineInputBorder(
|
||
borderSide: BorderSide(
|
||
color:
|
||
(focusStates["_to${index}Focused"] ??
|
||
false)
|
||
? layoutColor
|
||
: Colors.white,
|
||
// : const Color(0xFFD6D5E6),
|
||
width: 0.5,
|
||
// const Color(0xFFD6D5E6),
|
||
),
|
||
),
|
||
focusedBorder: OutlineInputBorder(
|
||
borderSide: BorderSide(
|
||
color: layoutColor,
|
||
width: 1,
|
||
),
|
||
),
|
||
contentPadding: EdgeInsets.symmetric(
|
||
horizontal: 10,
|
||
vertical: 5,
|
||
),
|
||
),
|
||
),
|
||
|
||
// dropdownBuilder:
|
||
// (context, selectedItem) => Align(
|
||
// alignment: Alignment.centerLeft,
|
||
// child: Text(
|
||
// selectedItem ?? "Select Country",
|
||
// style: TextStyle(fontSize: 12),
|
||
// ),
|
||
// ),
|
||
dropdownBuilder: (context, selectedItem) {
|
||
if (selectedItem == null) {
|
||
return Align(
|
||
alignment: Alignment.centerLeft,
|
||
child: const Text(
|
||
"Select",
|
||
style: TextStyle(fontSize: 12),
|
||
),
|
||
);
|
||
}
|
||
|
||
final parts = selectedItem.split('\n');
|
||
final cityAndCode = parts[0];
|
||
final airport = parts.length > 1 ? parts[1] : '';
|
||
|
||
return Align(
|
||
alignment: Alignment.centerLeft,
|
||
child: Text(
|
||
cityAndCode ?? "Select",
|
||
style: const TextStyle(fontSize: 12),
|
||
),
|
||
);
|
||
},
|
||
onChanged: (String? newValue) {
|
||
setState(() {
|
||
errorMessages.remove("to_place_$index");
|
||
selectedTo[index] =
|
||
countryMap.entries
|
||
.firstWhere(
|
||
(entry) => entry.value == newValue,
|
||
)
|
||
.key;
|
||
|
||
print(selectedTo[index]);
|
||
|
||
if (selectedTripType == "Roundtrip") {
|
||
print('rounfTo${selectedTo[index]}');
|
||
// textControllers["_to${index+1}Controller"]?.text = selectedFrom[index]!;
|
||
selectedFrom[2] = selectedTo[index]!;
|
||
}
|
||
});
|
||
},
|
||
),
|
||
),
|
||
),
|
||
),
|
||
),
|
||
if (errorMessages["to_place_$index"] != null) ...[
|
||
SizedBox(height: 5), // Space before error message
|
||
Text(
|
||
errorMessages["to_place_$index"]!,
|
||
style: TextStyle(color: Colors.red, fontSize: 12),
|
||
),
|
||
],
|
||
],
|
||
),
|
||
if (isDesktop) Spacer() else SizedBox(height: 8),
|
||
Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Text(
|
||
"Class *",
|
||
style: GoogleFonts.poppins(
|
||
fontSize: 12,
|
||
fontWeight: FontWeight.w600,
|
||
color: Color(0xFF575A74),
|
||
),
|
||
),
|
||
SizedBox(height: 5),
|
||
CustomTextFieldItnerarySubWrapper(
|
||
// isFocused: focusStates["_class${index}Focused"] ?? false,
|
||
isFocused: false,
|
||
padding: const EdgeInsets.symmetric(horizontal: 0),
|
||
isDesktop: isDesktop,
|
||
child: SizedBox(
|
||
height: 40,
|
||
width: double.infinity,
|
||
child: Focus(
|
||
focusNode: focusNodes["_class${index}FocusNode"],
|
||
|
||
onFocusChange: (hasFocus) {
|
||
setState(() {
|
||
focusStates["_class${index}Focused"] = hasFocus;
|
||
});
|
||
},
|
||
child: GestureDetector(
|
||
//
|
||
onTap: () {
|
||
// Request focus when user taps
|
||
focusNodes["_class${index}FocusNode"]?.requestFocus();
|
||
},
|
||
child: DropdownSearch<Map<String, dynamic>>(
|
||
items: purposeList.cast<Map<String, dynamic>>(),
|
||
key:
|
||
selectedClasses[index] == null
|
||
? UniqueKey()
|
||
: ValueKey(selectedClasses[index]),
|
||
// selectedItem: purposeList.firstWhere(
|
||
// (item) => item['dropdown_key'] == selectedClasses[index],
|
||
// orElse: () => {},
|
||
// ),
|
||
selectedItem:
|
||
selectedClasses[index] != null
|
||
? purposeList.firstWhere(
|
||
(item) =>
|
||
item['dropdown_key'] ==
|
||
selectedClasses[index],
|
||
orElse: () => {},
|
||
)
|
||
: null,
|
||
itemAsString: (item) => item['dropdown_value'] ?? '',
|
||
popupProps: PopupProps.menu(
|
||
showSearchBox: false,
|
||
fit: FlexFit.loose,
|
||
menuProps: const MenuProps(backgroundColor: Colors.white),
|
||
itemBuilder: (context, item, isSelected) {
|
||
final bool isNotAllowed = item['is_allowed'] == 'No';
|
||
return Padding(
|
||
padding: const EdgeInsets.symmetric(
|
||
horizontal: 10,
|
||
vertical: 8,
|
||
),
|
||
child: Text(
|
||
item['dropdown_value'] ?? '',
|
||
style: GoogleFonts.poppins(
|
||
fontSize: 12,
|
||
color:
|
||
isNotAllowed
|
||
? Colors.redAccent
|
||
: Colors.black,
|
||
),
|
||
),
|
||
);
|
||
},
|
||
),
|
||
dropdownDecoratorProps: DropDownDecoratorProps(
|
||
dropdownSearchDecoration: InputDecoration(
|
||
// border: InputBorder.none,
|
||
border: OutlineInputBorder(
|
||
borderRadius: BorderRadius.circular(8),
|
||
borderSide: BorderSide(
|
||
color:
|
||
(focusStates["_class${index}Focused"] ?? false)
|
||
? layoutColor!
|
||
: Colors.white,
|
||
width: 0.5,
|
||
),
|
||
),
|
||
enabledBorder: OutlineInputBorder(
|
||
borderSide: BorderSide(
|
||
color:
|
||
(focusStates["_class${index}Focused"] ?? false)
|
||
? layoutColor
|
||
: Colors.white,
|
||
// : const Color(0xFFD6D5E6),
|
||
width: 0.5,
|
||
// const Color(0xFFD6D5E6),
|
||
),
|
||
),
|
||
focusedBorder: OutlineInputBorder(
|
||
borderSide: BorderSide(color: layoutColor, width: 1),
|
||
),
|
||
contentPadding: const EdgeInsets.symmetric(
|
||
horizontal: 10,
|
||
vertical: 5,
|
||
),
|
||
),
|
||
),
|
||
dropdownBuilder: (context, selectedItem) {
|
||
if (selectedItem == null || selectedItem.isEmpty) {
|
||
return Text(
|
||
"Select Class",
|
||
style: GoogleFonts.poppins(
|
||
color: Colors.grey,
|
||
fontSize: 13,
|
||
),
|
||
);
|
||
}
|
||
return Text(
|
||
selectedItem['dropdown_value'] ?? '',
|
||
style: GoogleFonts.poppins(
|
||
fontSize: 12,
|
||
color: Colors.black,
|
||
),
|
||
);
|
||
},
|
||
onChanged:
|
||
purposeList.isNotEmpty
|
||
? (Map<String, dynamic>? newValue) async {
|
||
// if (newValue != null &&
|
||
// newValue['is_allowed'] == 'No') {
|
||
// print('not allowed');
|
||
//
|
||
// exceptionalClass[index] = "1";
|
||
// } else if (newValue != null &&
|
||
// newValue['is_allowed'] == 'yes') {
|
||
// print('not allowed');
|
||
//
|
||
// exceptionalClass[index] = "0";
|
||
// }
|
||
//
|
||
// setState(() {
|
||
// selectedClasses[index] =
|
||
// newValue?['dropdown_key'];
|
||
// print(
|
||
// "Selected Class: ${selectedClasses[index]}",
|
||
// );
|
||
// });
|
||
|
||
if (newValue != null &&
|
||
newValue['is_allowed'] == 'No') {
|
||
final confirm = await showNotAllowedDialog(
|
||
context,
|
||
message:
|
||
"You are not entitled for this service. If you wish to continue with this service, you may require another approval.",
|
||
);
|
||
|
||
if (confirm) {
|
||
setState(() {
|
||
exceptionalClass[index] = "1";
|
||
selectedClasses[index] =
|
||
newValue['dropdown_key'];
|
||
});
|
||
print(
|
||
"Selected Purpose: ${selectedClasses[index]}",
|
||
);
|
||
} else {
|
||
setState(() {
|
||
exceptionalClass[index] = "0";
|
||
selectedClasses[index] =
|
||
null; // ❌ Clear selection if user cancels
|
||
});
|
||
print(
|
||
"selectedItem resolved to: ${selectedClasses[index]}",
|
||
);
|
||
}
|
||
|
||
return;
|
||
} else if (newValue != null &&
|
||
newValue['is_allowed'] == 'yes') {
|
||
setState(() {
|
||
exceptionalClass[index] = "0";
|
||
selectedClasses[index] =
|
||
newValue?['dropdown_key'];
|
||
print(
|
||
"Selected Purpose: ${selectedClasses[index]}",
|
||
);
|
||
});
|
||
}
|
||
}
|
||
: null,
|
||
),
|
||
),
|
||
),
|
||
),
|
||
),
|
||
if (errorMessages["class_$index"] != null)
|
||
Padding(
|
||
padding: const EdgeInsets.only(top: 4),
|
||
child: Text(
|
||
errorMessages["class_$index"]!,
|
||
style: GoogleFonts.poppins(fontSize: 12, color: Colors.red),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
if (isDesktop) Spacer() else SizedBox(height: 8),
|
||
Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Text(
|
||
"Date*",
|
||
style: GoogleFonts.poppins(
|
||
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: () {
|
||
focusNodes["_date${index}FocusNode"]?.requestFocus();
|
||
_selectCheckOutDate(context);
|
||
},
|
||
child: AbsorbPointer(
|
||
child: TextField(
|
||
focusNode: focusNodes["_date${index}FocusNode"],
|
||
// controller: _dateController,
|
||
controller: textControllers["_date${index}Controller"],
|
||
readOnly: true,
|
||
style: const TextStyle(fontSize: 12),
|
||
decoration: InputDecoration(
|
||
labelText: "Select Date",
|
||
labelStyle: const TextStyle(
|
||
fontSize: 12,
|
||
color: Colors.grey,
|
||
),
|
||
floatingLabelBehavior: FloatingLabelBehavior.never,
|
||
border: InputBorder.none,
|
||
contentPadding: EdgeInsets.symmetric(vertical: 16),
|
||
// border: OutlineInputBorder(
|
||
// borderRadius: BorderRadius.circular(8),
|
||
// borderSide: BorderSide(
|
||
// color:
|
||
// (focusStates["_date${index}Focused"] ?? false)
|
||
// ? layoutColor!
|
||
// : Colors.white,
|
||
// width: 0.5,
|
||
// ),
|
||
// ),
|
||
// enabledBorder: OutlineInputBorder(
|
||
// borderSide: BorderSide(
|
||
// color:
|
||
// (focusStates["_date${index}Focused"] ?? false)
|
||
// ? layoutColor
|
||
// : Colors.white,
|
||
// // : const Color(0xFFD6D5E6),
|
||
// width: 0.5,
|
||
// // const Color(0xFFD6D5E6),
|
||
// ),
|
||
// ),
|
||
// focusedBorder: OutlineInputBorder(
|
||
// borderSide: BorderSide(color: layoutColor, width: 1),
|
||
// ),
|
||
// contentPadding: const EdgeInsets.symmetric(
|
||
// horizontal: 10,
|
||
// vertical: 5,
|
||
// ),
|
||
suffixIcon: const Icon(
|
||
Icons.calendar_today,
|
||
size: 16,
|
||
color: Colors.grey,
|
||
),
|
||
),
|
||
),
|
||
),
|
||
),
|
||
),
|
||
),
|
||
if (errorMessages["date_$index"] != null) ...[
|
||
SizedBox(height: 5), // Space before error message
|
||
Text(
|
||
errorMessages["date_$index"]!,
|
||
style: TextStyle(color: Colors.red, fontSize: 12),
|
||
),
|
||
],
|
||
],
|
||
),
|
||
if (isDesktop) Spacer() else SizedBox(height: 8),
|
||
Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Text(
|
||
"Time*",
|
||
style: GoogleFonts.poppins(
|
||
fontSize: 12,
|
||
fontWeight: FontWeight.w600,
|
||
color: Color(0xFF575A74),
|
||
),
|
||
),
|
||
SizedBox(height: 5),
|
||
CustomTextFieldItnerarySubWrapper(
|
||
isFocused: focusStates["_time${index}Focused"] ?? false,
|
||
isDesktop: isDesktop,
|
||
width: isDesktop ? MediaQuery.of(context).size.width * 0.1 : null,
|
||
child: SizedBox(
|
||
height: 40,
|
||
child: GestureDetector(
|
||
onTap: () {
|
||
focusNodes["_time${index}FocusNode"]?.requestFocus();
|
||
textControllers["_time${index}Controller"]?.text = "";
|
||
_clearError("time_$index");
|
||
// validateTimeDifference(index);
|
||
// _selectCheckOutTime(context);
|
||
|
||
_selectCheckOutTime(context, index, () {
|
||
validateTimeDifference(index);
|
||
setState(
|
||
() {},
|
||
); // ✅ Force rebuild to show the error immediately
|
||
});
|
||
},
|
||
|
||
child: AbsorbPointer(
|
||
child: TextField(
|
||
focusNode: focusNodes["_time${index}FocusNode"],
|
||
// controller: _timeController,
|
||
controller: textControllers["_time${index}Controller"],
|
||
readOnly: true,
|
||
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,
|
||
),
|
||
),
|
||
),
|
||
),
|
||
// Focus(
|
||
// focusNode: focusNodes["_time${index}FocusNode"],
|
||
// onFocusChange: (hasFocus) {
|
||
// setState(() {
|
||
// focusStates["_time${index}Focused"] = hasFocus;
|
||
// });
|
||
// },
|
||
// child: GestureDetector(
|
||
// //
|
||
// onTap: () {
|
||
// // Request focus when user taps
|
||
// focusNodes["_time${index}FocusNode"]?.requestFocus();
|
||
// },
|
||
),
|
||
),
|
||
),
|
||
if (errorMessages["time_$index"] != null) ...[
|
||
SizedBox(height: 5), // Space before error message
|
||
Text(
|
||
errorMessages["time_$index"]!,
|
||
style: TextStyle(color: Colors.red, fontSize: 12),
|
||
),
|
||
],
|
||
],
|
||
),
|
||
if (selectedTripType == "Multitrip")
|
||
if(multiTripRowCount > 1)
|
||
Padding(
|
||
padding: const EdgeInsets.only(top: 10),
|
||
child: SizedBox(
|
||
width: double.infinity, // ensure full width
|
||
child: ElevatedButton(
|
||
style: ElevatedButton.styleFrom(
|
||
minimumSize: const Size.fromHeight(48), // set height
|
||
backgroundColor: Colors.red,
|
||
foregroundColor: Colors.white,
|
||
shape: RoundedRectangleBorder(
|
||
borderRadius: BorderRadius.circular(8),
|
||
),
|
||
),
|
||
onPressed: () {
|
||
print("Pressed: $index");
|
||
removeTrip(index);
|
||
},
|
||
child: const Text(
|
||
"Remove",
|
||
style: TextStyle(fontSize: 14),
|
||
),
|
||
),
|
||
),
|
||
),
|
||
// 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),
|
||
),
|
||
),
|
||
);
|
||
}
|
||
// Column(
|
||
// crossAxisAlignment: CrossAxisAlignment.start,
|
||
// children: [
|
||
// Text(
|
||
// "Visa Required",
|
||
// style: GoogleFonts.poppins(
|
||
// 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,
|
||
// ),
|
||
// ),
|
||
// ),
|
||
// ],
|
||
// ),
|
||
//********//
|
||
return isDesktop
|
||
? [
|
||
Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Text(
|
||
"Visa Required",
|
||
style: GoogleFonts.poppins(
|
||
fontSize: 12,
|
||
fontWeight: FontWeight.w600,
|
||
color: Color(0xFF575A74),
|
||
),
|
||
),
|
||
SizedBox(height: 5),
|
||
CustomTextFieldItnerarySubWrapper(
|
||
// use same wrapper as class
|
||
// isFocused: focusStates["_visa1Focused"] ?? false,
|
||
isFocused: false,
|
||
padding: const EdgeInsets.symmetric(horizontal: 0),
|
||
isDesktop: isDesktop,
|
||
child: SizedBox(
|
||
height: 35,
|
||
width: double.infinity,
|
||
child: Focus(
|
||
focusNode: focusNodes["_visa1FocusNode"],
|
||
onFocusChange: (hasFocus) {
|
||
setState(() {
|
||
focusStates["_visa1Focused"] = hasFocus;
|
||
});
|
||
},
|
||
child: GestureDetector(
|
||
//
|
||
onTap: () {
|
||
// Request focus when user taps
|
||
focusNodes["_visa1FocusNode"]?.requestFocus();
|
||
},
|
||
child: DropdownSearch<Map<String, dynamic>>(
|
||
items: visa_available.cast<Map<String, dynamic>>(),
|
||
selectedItem: visa_available.firstWhere(
|
||
(item) => item['dropdown_key'] == selectedvisa_available,
|
||
orElse: () => {},
|
||
),
|
||
itemAsString: (item) => item['dropdown_value'] ?? '',
|
||
popupProps: PopupProps.menu(
|
||
showSearchBox: false,
|
||
fit: FlexFit.loose,
|
||
menuProps: const MenuProps(backgroundColor: Colors.white),
|
||
itemBuilder: (context, item, isSelected) {
|
||
return Padding(
|
||
padding: const EdgeInsets.symmetric(
|
||
horizontal: 10,
|
||
vertical: 8,
|
||
),
|
||
child: Text(
|
||
item['dropdown_value'] ?? '',
|
||
style: GoogleFonts.poppins(
|
||
fontSize: 12,
|
||
color: Colors.black,
|
||
),
|
||
),
|
||
);
|
||
},
|
||
),
|
||
dropdownDecoratorProps: DropDownDecoratorProps(
|
||
dropdownSearchDecoration: InputDecoration(
|
||
// border: InputBorder.none,
|
||
border: OutlineInputBorder(
|
||
borderRadius: BorderRadius.circular(8),
|
||
borderSide: BorderSide(
|
||
color:
|
||
(focusStates["_visa1Focused"] ?? false)
|
||
? layoutColor!
|
||
: Colors.white,
|
||
width: 0.5,
|
||
),
|
||
),
|
||
enabledBorder: OutlineInputBorder(
|
||
borderSide: BorderSide(
|
||
color:
|
||
(focusStates["_visa1Focused"] ?? false)
|
||
? layoutColor
|
||
: Colors.white,
|
||
// : const Color(0xFFD6D5E6),
|
||
width: 0.5,
|
||
// const Color(0xFFD6D5E6),
|
||
),
|
||
),
|
||
focusedBorder: OutlineInputBorder(
|
||
borderSide: BorderSide(color: layoutColor, width: 1),
|
||
),
|
||
contentPadding: const EdgeInsets.symmetric(
|
||
horizontal: 10,
|
||
vertical: 5,
|
||
),
|
||
),
|
||
),
|
||
dropdownBuilder: (context, selectedItem) {
|
||
if (selectedItem == null || selectedItem.isEmpty) {
|
||
return Text(
|
||
"Select Option",
|
||
style: GoogleFonts.poppins(
|
||
color: Colors.grey,
|
||
fontSize: 13,
|
||
),
|
||
);
|
||
}
|
||
return Text(
|
||
selectedItem['dropdown_value'] ?? '',
|
||
style: GoogleFonts.poppins(
|
||
fontSize: 12,
|
||
color: Colors.black,
|
||
),
|
||
);
|
||
},
|
||
onChanged:
|
||
visa_available.isNotEmpty
|
||
? (Map<String, dynamic>? newValue) {
|
||
setState(() {
|
||
selectedvisa_available =
|
||
newValue?['dropdown_key'];
|
||
print("Selected Visa: $selectedvisa_available");
|
||
});
|
||
}
|
||
: null,
|
||
),
|
||
),
|
||
),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
if (isDesktop) SizedBox(width: 20),
|
||
SizedBox(height: 5),
|
||
Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Text(
|
||
"Comments",
|
||
style: GoogleFonts.poppins(
|
||
fontSize: 12,
|
||
fontWeight: FontWeight.w600,
|
||
color: Color(0xFF575A74),
|
||
),
|
||
),
|
||
SizedBox(height: 5),
|
||
CustomTextFieldItnerarySubWrapper(
|
||
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),
|
||
),
|
||
],
|
||
),
|
||
]
|
||
: [
|
||
Card(
|
||
elevation: 1,
|
||
margin: const EdgeInsets.symmetric(horizontal: 10, vertical: 8),
|
||
color: const Color(0xFFF5F5F5), // Light grey
|
||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||
child: Padding(
|
||
padding: const EdgeInsets.all(12),
|
||
child: Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Text(
|
||
"Visa Required",
|
||
style: GoogleFonts.poppins(
|
||
fontSize: 12,
|
||
fontWeight: FontWeight.w600,
|
||
color: Color(0xFF575A74),
|
||
),
|
||
),
|
||
SizedBox(height: 5),
|
||
CustomTextFieldItnerarySubWrapper(
|
||
// use same wrapper as class
|
||
// isFocused: focusStates["_visa1Focused"] ?? false,
|
||
isFocused: false,
|
||
padding: const EdgeInsets.symmetric(horizontal: 0),
|
||
isDesktop: isDesktop,
|
||
child: SizedBox(
|
||
height: 35,
|
||
width: double.infinity,
|
||
child: Focus(
|
||
focusNode: focusNodes["_visa1FocusNode"],
|
||
onFocusChange: (hasFocus) {
|
||
setState(() {
|
||
focusStates["_visa1Focused"] = hasFocus;
|
||
});
|
||
},
|
||
child: GestureDetector(
|
||
//
|
||
onTap: () {
|
||
// Request focus when user taps
|
||
focusNodes["_visa1FocusNode"]?.requestFocus();
|
||
},
|
||
child: DropdownSearch<Map<String, dynamic>>(
|
||
items: visa_available.cast<Map<String, dynamic>>(),
|
||
selectedItem: visa_available.firstWhere(
|
||
(item) => item['dropdown_key'] == selectedvisa_available,
|
||
orElse: () => {},
|
||
),
|
||
itemAsString: (item) => item['dropdown_value'] ?? '',
|
||
popupProps: PopupProps.menu(
|
||
showSearchBox: false,
|
||
fit: FlexFit.loose,
|
||
menuProps: const MenuProps(backgroundColor: Colors.white),
|
||
itemBuilder: (context, item, isSelected) {
|
||
return Padding(
|
||
padding: const EdgeInsets.symmetric(
|
||
horizontal: 10,
|
||
vertical: 8,
|
||
),
|
||
child: Text(
|
||
item['dropdown_value'] ?? '',
|
||
style: GoogleFonts.poppins(
|
||
fontSize: 12,
|
||
color: Colors.black,
|
||
),
|
||
),
|
||
);
|
||
},
|
||
),
|
||
dropdownDecoratorProps: DropDownDecoratorProps(
|
||
dropdownSearchDecoration: InputDecoration(
|
||
// border: InputBorder.none,
|
||
border: OutlineInputBorder(
|
||
borderRadius: BorderRadius.circular(8),
|
||
borderSide: BorderSide(
|
||
color:
|
||
(focusStates["_visa1Focused"] ?? false)
|
||
? layoutColor!
|
||
: Colors.white,
|
||
width: 0.5,
|
||
),
|
||
),
|
||
enabledBorder: OutlineInputBorder(
|
||
borderSide: BorderSide(
|
||
color:
|
||
(focusStates["_visa1Focused"] ?? false)
|
||
? layoutColor
|
||
: Colors.white,
|
||
// : const Color(0xFFD6D5E6),
|
||
width: 0.5,
|
||
// const Color(0xFFD6D5E6),
|
||
),
|
||
),
|
||
focusedBorder: OutlineInputBorder(
|
||
borderSide: BorderSide(color: layoutColor, width: 1),
|
||
),
|
||
contentPadding: const EdgeInsets.symmetric(
|
||
horizontal: 10,
|
||
vertical: 5,
|
||
),
|
||
),
|
||
),
|
||
dropdownBuilder: (context, selectedItem) {
|
||
if (selectedItem == null || selectedItem.isEmpty) {
|
||
return Text(
|
||
"Select Option",
|
||
style: GoogleFonts.poppins(
|
||
color: Colors.grey,
|
||
fontSize: 13,
|
||
),
|
||
);
|
||
}
|
||
return Text(
|
||
selectedItem['dropdown_value'] ?? '',
|
||
style: GoogleFonts.poppins(
|
||
fontSize: 12,
|
||
color: Colors.black,
|
||
),
|
||
);
|
||
},
|
||
onChanged:
|
||
visa_available.isNotEmpty
|
||
? (Map<String, dynamic>? newValue) {
|
||
setState(() {
|
||
selectedvisa_available =
|
||
newValue?['dropdown_key'];
|
||
print("Selected Visa: $selectedvisa_available");
|
||
});
|
||
}
|
||
: null,
|
||
),
|
||
),
|
||
),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
if (isDesktop) SizedBox(width: 20),
|
||
SizedBox(height: 5),
|
||
Column(
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Text(
|
||
"Comments",
|
||
style: GoogleFonts.poppins(
|
||
fontSize: 12,
|
||
fontWeight: FontWeight.w600,
|
||
color: Color(0xFF575A74),
|
||
),
|
||
),
|
||
SizedBox(height: 5),
|
||
CustomTextFieldItnerarySubWrapper(
|
||
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: 10),
|
||
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),
|
||
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,
|
||
),
|
||
),
|
||
),
|
||
],
|
||
),
|
||
];
|
||
}
|
||
|
||
List<Widget> _handleAction(bool isDesktop) {
|
||
return [
|
||
// Close Button
|
||
ElevatedButton(
|
||
onPressed: () {
|
||
widget.onClose("Flight", false);
|
||
// 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: GoogleFonts.poppins(color: Colors.white, fontSize: 12),
|
||
),
|
||
),
|
||
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: GoogleFonts.poppins(color: Colors.white, fontSize: 12),
|
||
),
|
||
),
|
||
];
|
||
}
|
||
}
|