1242 lines
38 KiB
Dart
1242 lines
38 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:intl/intl.dart';
|
|
import 'package:responsive_builder/responsive_builder.dart';
|
|
|
|
import '../../widgets/custom_radio_button.dart';
|
|
import '../../widgets/custom_text_field.dart';
|
|
import '../../widgets/custom_text_itnerary_sub.dart';
|
|
|
|
class FlightScreen extends StatefulWidget {
|
|
|
|
final Map<String, dynamic>? apiData;
|
|
final String? loginUser;
|
|
final Function(bool) onClose;
|
|
final Function(Map<String,dynamic>) onSaveFlight;
|
|
final Map<String,dynamic>? selectedItem;
|
|
|
|
FlightScreen({ required this.apiData,required this.loginUser,
|
|
required this.onClose, required this.onSaveFlight,required this.selectedItem});
|
|
|
|
@override
|
|
_FlightScreenState createState() => _FlightScreenState();
|
|
|
|
|
|
}
|
|
|
|
|
|
class _FlightScreenState extends State<FlightScreen> {
|
|
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
|
|
|
|
Map<String, String?> selectedValues = {};
|
|
|
|
String? selectedTripType;
|
|
Map<int, String?> selectedClasses = {}; // Store class selection for each trip
|
|
String? selectedvisa_available;
|
|
int multiTripRowCount = 1;
|
|
|
|
|
|
List<String> dataHeader = ["_tripType", "_class", "_from", "_to", "_date","_visa", "_time", "_comments"];
|
|
|
|
Map<String, FocusNode> focusNodes = {};
|
|
Map<String, bool> focusStates = {};
|
|
Map<String, TextEditingController> textControllers = {};
|
|
|
|
Map<String, String> fieldErrors = {}; // Holds error messages
|
|
|
|
List<List<Widget>> rowBuilders = []; // Holds row widgets
|
|
List<TextEditingController> controllers = []; // Dynamic controllers
|
|
|
|
Map<String, String> errorMessages = {};
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
// _initializeRows();
|
|
// List<dynamic> purposeList = widget.apiData?['flight_trip_type'] ?? [];
|
|
// selectedTripType ??= purposeList.isNotEmpty ? purposeList.first['dropdown_value'] : null;
|
|
|
|
|
|
// Get trip type from widget.selectedItem
|
|
selectedTripType = widget.selectedItem?["trip_type"] as String?;
|
|
|
|
// If null, set it to the first available value from purposeList
|
|
List<dynamic> purposeList = widget.apiData?['flight_trip_type'] ?? [];
|
|
|
|
if (selectedTripType == null && purposeList.isNotEmpty) {
|
|
selectedTripType = purposeList.first['dropdown_value'] as String?;
|
|
}
|
|
|
|
|
|
|
|
|
|
_initializeFields();
|
|
getRowCount();
|
|
|
|
print("Focus Nodes Keys: ${focusNodes.keys.toList()}");
|
|
print("Focus States Keys: ${focusStates.keys.toList()}");
|
|
print("Text Controllers Keys: ${textControllers.keys.toList()}");
|
|
|
|
|
|
for (var key in focusNodes.keys) {
|
|
_addFocusListener(focusNodes[key]!, (focus) {
|
|
setState(() {
|
|
focusStates[key.replaceFirst("FocusNode", "Focused")] = focus;
|
|
});
|
|
});
|
|
}
|
|
|
|
handleUpdateField();
|
|
|
|
|
|
int rowCount = 1; // Default row count for One-way
|
|
if (selectedTripType == "Roundtrip") {
|
|
rowCount = 2; // Fixed for Roundtrip
|
|
} else if (selectedTripType == "Multitrip") {
|
|
rowCount = multiTripRowCount; // Dynamic row count for Multitrip
|
|
}
|
|
|
|
// Loop through each row and add listeners to clear errors
|
|
for (int i = 1; i <= rowCount; i++) {
|
|
textControllers["_from${i}Controller"]?.addListener(() => _clearError("from_place_$i"));
|
|
textControllers["_to${i}Controller"]?.addListener(() => _clearError("to_place_$i"));
|
|
textControllers["_date${i}Controller"]?.addListener(() => _clearError("date_$i"));
|
|
textControllers["_time${i}Controller"]?.addListener(() => _clearError("time_$i"));
|
|
}
|
|
|
|
}
|
|
|
|
int getRowCount() {
|
|
if (selectedTripType == "RoundTrip") {
|
|
return 2;
|
|
} else if (selectedTripType == "Multitrip") {
|
|
return multiTripRowCount;
|
|
}
|
|
return 1; // Default for Oneway
|
|
}
|
|
|
|
void _initializeFields() {
|
|
|
|
print("_initializeFields-----------------");
|
|
|
|
// Dispose and clear previous controllers and focus nodes
|
|
for (var key in List.from(textControllers.keys)) {
|
|
textControllers[key]?.dispose();
|
|
}
|
|
textControllers.clear();
|
|
|
|
for (var key in List.from(focusNodes.keys)) {
|
|
focusNodes[key]?.dispose();
|
|
}
|
|
focusNodes.clear();
|
|
|
|
textControllers.clear();
|
|
focusNodes.clear();
|
|
focusStates.clear();
|
|
|
|
print("Focus Nodes KeysII: ${focusNodes.keys.toList()}");
|
|
print("Focus States KeysII: ${focusStates.keys.toList()}");
|
|
print("Text Controllers KeysII: ${textControllers.keys.toList()}");
|
|
|
|
// Determine the row count based on selectedTripType
|
|
int rowCount = selectedTripType == "Roundtrip"
|
|
? 2
|
|
: selectedTripType == "Multitrip"
|
|
? multiTripRowCount
|
|
: 1;
|
|
|
|
// Initialize fields dynamically
|
|
for (var field in dataHeader) {
|
|
for (int i = 1; i <= rowCount; i++) {
|
|
textControllers["${field}${i}Controller"] =
|
|
TextEditingController();
|
|
|
|
focusNodes["${field}${i}FocusNode"] = FocusNode();
|
|
focusStates["${field}${i}Focused"] = false;
|
|
}
|
|
}
|
|
|
|
// Add focus listeners after reinitialization
|
|
for (var key in focusNodes.keys) {
|
|
_addFocusListener(focusNodes[key]!, (focus) {
|
|
setState(() {
|
|
focusStates[key.replaceFirst("FocusNode", "Focused")] = focus;
|
|
});
|
|
});
|
|
}
|
|
|
|
setState(() {}); // Ensure UI updates
|
|
|
|
|
|
}
|
|
|
|
void addMultiTripRow() {
|
|
setState(() {
|
|
multiTripRowCount++; // Increment row count
|
|
_initializeFields(); // Reinitialize fields with updated count
|
|
});
|
|
}
|
|
|
|
void _addFocusListener(FocusNode node, Function(bool) updateState) {
|
|
node.addListener(() {
|
|
setState(() {
|
|
updateState(node.hasFocus);
|
|
});
|
|
});
|
|
}
|
|
|
|
void _clearError(String field) {
|
|
if (mounted && errorMessages.containsKey(field)) {
|
|
setState(() {
|
|
errorMessages.remove(field);
|
|
});
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
@override
|
|
void dispose() {
|
|
// _tripTypeFocusNode.dispose();
|
|
|
|
|
|
// Dispose all dynamically created FocusNodes
|
|
for (var node in focusNodes.values) {
|
|
node.dispose();
|
|
}
|
|
|
|
// Dispose all dynamically created TextEditingControllers
|
|
for (var controller in textControllers.values) {
|
|
controller.dispose();
|
|
}
|
|
super.dispose();
|
|
}
|
|
|
|
|
|
|
|
Map<String, dynamic> get flightsData{
|
|
|
|
List<Map<String, dynamic>> trips = [];
|
|
|
|
int rowCount = 1; // Default for One-way
|
|
|
|
if (selectedTripType == "Roundtrip") {
|
|
rowCount = 2; // Fixed count for Roundtrip
|
|
} else if (selectedTripType == "Multitrip") {
|
|
rowCount = multiTripRowCount; // Use dynamic count for Multitrip
|
|
}
|
|
|
|
|
|
for (int i = 1; i <= rowCount; i++) {
|
|
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();
|
|
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"]);
|
|
}
|
|
|
|
print("Selected ITEM - ${widget.selectedItem}");
|
|
print("Total Trips Loaded: ${selectedTrips.length}");
|
|
print("Controllers Set: ${textControllers.keys}");
|
|
}
|
|
}
|
|
|
|
void handleSave() {
|
|
print("Controllers Key-Value Pairs:");
|
|
|
|
if (!validateFields()) {
|
|
print("Validation failed. Please fill all required fields.");
|
|
return;
|
|
}
|
|
|
|
widget.onSaveFlight(flightsData); // Send object to parent
|
|
widget.onClose(false);
|
|
|
|
print("FlightData - $flightsData");
|
|
}
|
|
|
|
bool validateFields() {
|
|
errorMessages.clear(); // Reset errors
|
|
|
|
int rowCount = 1; // Default row count for One-way
|
|
if (selectedTripType == "Roundtrip") {
|
|
rowCount = 2; // Fixed for Roundtrip
|
|
} else if (selectedTripType == "Multitrip") {
|
|
rowCount = multiTripRowCount; // Dynamic row count for Multitrip
|
|
}
|
|
|
|
// Loop through each trip row and validate required fields
|
|
for (int i = 1; i <= rowCount; i++) {
|
|
if (textControllers["_from${i}Controller"]?.text.trim().isEmpty ?? true) {
|
|
errorMessages["from_place_$i"] = "Required";
|
|
}
|
|
if (textControllers["_to${i}Controller"]?.text.trim().isEmpty ?? true) {
|
|
errorMessages["to_place_$i"] = "Required";
|
|
}
|
|
if (textControllers["_date${i}Controller"]?.text.trim().isEmpty ?? true) {
|
|
errorMessages["date_$i"] = "Required";
|
|
}
|
|
if (textControllers["_time${i}Controller"]?.text.trim().isEmpty ?? true) {
|
|
errorMessages["time_$i"] = "Required";
|
|
}
|
|
}
|
|
|
|
setState(() {}); // Update UI to show error messages
|
|
|
|
return errorMessages.isEmpty; // Returns true if all required fields are filled
|
|
}
|
|
|
|
void removeTrip(int index) {
|
|
if (selectedTripType == "Multitrip" && multiTripRowCount > 1) {
|
|
print("Delete Index - $index");
|
|
|
|
setState(() {
|
|
multiTripRowCount--; // Reduce trip count
|
|
|
|
// Remove corresponding text controllers
|
|
textControllers.remove("_from${index}Controller");
|
|
textControllers.remove("_to${index}Controller");
|
|
textControllers.remove("_date${index}Controller");
|
|
textControllers.remove("_time${index}Controller");
|
|
|
|
|
|
|
|
// Step 2: Shift remaining textControllers keys
|
|
Map<String, TextEditingController> updatedTextControllers = {};
|
|
int newIndex = 1;
|
|
for (int i = 1; i <= multiTripRowCount + 1; i++) {
|
|
if (i == index) continue; // Skip the deleted one
|
|
updatedTextControllers["_from${newIndex}Controller"] = textControllers["_from${i}Controller"]!;
|
|
updatedTextControllers["_to${newIndex}Controller"] = textControllers["_to${i}Controller"]!;
|
|
updatedTextControllers["_date${newIndex}Controller"] = textControllers["_date${i}Controller"]!;
|
|
updatedTextControllers["_time${newIndex}Controller"] = textControllers["_time${i}Controller"]!;
|
|
newIndex++;
|
|
}
|
|
textControllers = updatedTextControllers;
|
|
|
|
|
|
// Shift the selectedClasses map BEFORE removing the index
|
|
Map<int, String?> updatedClasses = {};
|
|
newIndex = 1;
|
|
|
|
for (int i = 1; i <= selectedClasses.length; i++) {
|
|
if (i == index) continue; // Skip the one being deleted
|
|
updatedClasses[newIndex] = selectedClasses[i];
|
|
newIndex++;
|
|
}
|
|
|
|
selectedClasses = updatedClasses; // Update the map
|
|
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),
|
|
child: Form(
|
|
key: _formKey,
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(16.0),
|
|
child: Column(
|
|
children: [
|
|
Align(
|
|
alignment: Alignment.centerRight,
|
|
child: InkWell(
|
|
onTap: () {
|
|
widget.onClose(false);
|
|
},
|
|
child: Icon(
|
|
Icons.close,
|
|
size: 18,
|
|
color: Color(0xFF575A74),
|
|
),
|
|
),
|
|
),
|
|
Text("Flight Booking",
|
|
style:
|
|
TextStyle(fontSize: 18, fontWeight: FontWeight.bold,color: Color(0xFF575A74))),
|
|
SizedBox(
|
|
height: 6,
|
|
),
|
|
Padding(
|
|
padding: const EdgeInsets.all(28.0),
|
|
child: Center(
|
|
child: Column(children: _buildAccomadtionForm(isDesktop)),
|
|
),
|
|
)
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
});
|
|
}
|
|
|
|
// void _addNewRow() {
|
|
// setState(() {
|
|
// rowBuilders.add(_builClassType(false));
|
|
// controllers.add(TextEditingController());
|
|
// });
|
|
// }
|
|
|
|
|
|
List<Widget> _buildAccomadtionForm (bool isDesktop) {
|
|
List<Widget> buildResponsiveRow(List<Widget> children) {
|
|
return [
|
|
isDesktop ? Row(children: children) : Column(children: children),
|
|
SizedBox(height: 10),
|
|
];
|
|
}
|
|
|
|
List<List<Widget>> rowBuilders = [
|
|
_builClassType(isDesktop,1),
|
|
_buildSecondRow(isDesktop,1)
|
|
];
|
|
|
|
List<List<Widget>> rowRoundBuilders = [
|
|
_builClassType(isDesktop,1),
|
|
_buildSecondRow(isDesktop,1),
|
|
_builClassType(isDesktop,2),
|
|
_buildSecondRow(isDesktop,2)
|
|
];
|
|
|
|
|
|
print("Trip Type Selected: $selectedTripType");
|
|
|
|
return [
|
|
|
|
...buildResponsiveRow(_buildFirstRow(isDesktop)),
|
|
|
|
|
|
// Iterate over rowBuilders based on selectedTripType
|
|
if (selectedTripType == "Oneway")
|
|
...rowBuilders.expand((row) => buildResponsiveRow(row)),
|
|
|
|
|
|
if (selectedTripType == "Roundtrip")
|
|
...rowRoundBuilders.expand((row) => buildResponsiveRow(row)),
|
|
|
|
|
|
if (selectedTripType == "Multitrip")
|
|
...List.generate(multiTripRowCount, (index) {
|
|
List<Widget> firstRow = _builClassType(isDesktop, index + 1);
|
|
List<Widget> secondRow = _buildSecondRow(isDesktop, index + 1);
|
|
|
|
return [
|
|
...buildResponsiveRow(firstRow), // Row 1
|
|
...buildResponsiveRow(secondRow), // Row 2
|
|
];
|
|
}).expand((row) => row),
|
|
|
|
|
|
|
|
// if (selectedTripType == "Multitrip")
|
|
// ...List.generate(multiTripRowCount, (index) =>
|
|
// buildResponsiveRow(_builClassType(isDesktop, index + 1) + _buildSecondRow(isDesktop, index + 1))
|
|
// ).expand((row) => row),
|
|
|
|
|
|
|
|
if (selectedTripType == "Multitrip")
|
|
Align(
|
|
alignment: Alignment.centerRight,
|
|
child: ElevatedButton(
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: Colors.blueAccent,
|
|
foregroundColor: Colors.white,
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(8),
|
|
side: BorderSide(color: Colors.blueAccent, width: 2),
|
|
),
|
|
padding: EdgeInsets.symmetric(horizontal: 8, vertical: 2),
|
|
) ,
|
|
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(fontSize: 10,fontWeight:FontWeight.bold),),
|
|
),
|
|
),
|
|
|
|
|
|
...buildResponsiveRow(_buildvisa(isDesktop)),
|
|
...buildResponsiveRow(_buildThirdRow(isDesktop)),
|
|
// Actions row remains a Row
|
|
Row(
|
|
mainAxisAlignment: MainAxisAlignment.end,
|
|
children: _handleAction(isDesktop),
|
|
),
|
|
];
|
|
}
|
|
|
|
|
|
|
|
List<Widget> _buildFirstRow(isDesktop) {
|
|
|
|
return [
|
|
Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
"Trip Type",
|
|
style: TextStyle(
|
|
fontSize: 12,
|
|
fontWeight: FontWeight.w600,
|
|
color: Color(0xFF575A74)),
|
|
),
|
|
SizedBox(height: 5),
|
|
|
|
isDesktop ? Row(children: _buildTripType(isDesktop)
|
|
) :
|
|
Column(
|
|
children: _buildTripType(isDesktop)
|
|
)
|
|
|
|
|
|
],
|
|
),
|
|
if (isDesktop)
|
|
Spacer()
|
|
else
|
|
SizedBox(
|
|
height: 8,
|
|
),
|
|
|
|
];
|
|
}
|
|
|
|
|
|
|
|
List<Widget> _buildTripType(bool isDesktop){
|
|
|
|
List<dynamic> purposeList = widget.apiData?['flight_trip_type'] ?? [];
|
|
|
|
List<DropdownMenuItem<String>> dropdownItems = purposeList
|
|
.map((item)=>DropdownMenuItem<String>(
|
|
value: item['dropdown_value'],
|
|
child: Text(item['dropdown_value']),
|
|
)).toList();
|
|
|
|
if (dropdownItems.isEmpty) {
|
|
dropdownItems.add(
|
|
DropdownMenuItem<String>(
|
|
value: null,
|
|
child: Text("No options available", style: TextStyle(color: Colors.grey)),
|
|
),
|
|
);
|
|
}
|
|
|
|
|
|
|
|
return [
|
|
CustomTextFieldWrapper(
|
|
// isFocused: _tripTypeFocused,
|
|
isFocused: focusStates["_tripType1Focused"] ?? false,
|
|
isDesktop: isDesktop,
|
|
child: SizedBox(
|
|
height: 40,
|
|
|
|
child: DropdownButtonFormField<String>(
|
|
// focusNode: _tripTypeFocusNode, // Assign the correct focus node
|
|
focusNode: focusNodes["_tripType1FocusNode"],
|
|
value: selectedTripType,
|
|
style: TextStyle(fontSize: 12),
|
|
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,
|
|
),
|
|
|
|
|
|
),
|
|
),
|
|
];
|
|
}
|
|
|
|
List<Widget> _buildDelete(bool isDesktop, int index){
|
|
return [
|
|
|
|
Align(
|
|
alignment: Alignment.center,
|
|
child: Text(
|
|
"Trip ${index}",
|
|
style: TextStyle(
|
|
fontSize: 14,
|
|
fontWeight: FontWeight.w600,
|
|
color: Color(0xFF575A74),
|
|
),
|
|
),
|
|
),
|
|
SizedBox(
|
|
|
|
width: isDesktop? 1000 : 80, // Ensure full width
|
|
child: Divider(
|
|
|
|
thickness: 1, // Make it more visible
|
|
),
|
|
),
|
|
IconButton(onPressed: (){
|
|
removeTrip(index);
|
|
},
|
|
icon: Icon(Icons.delete),color: Colors.red,iconSize: 20,)
|
|
|
|
|
|
];
|
|
}
|
|
|
|
List<Widget> _builClassType(bool isDesktop, int index){
|
|
|
|
List<dynamic> purposeList = widget.apiData?['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;
|
|
|
|
|
|
return [
|
|
|
|
|
|
Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
|
|
if (selectedTripType == "Multitrip" )
|
|
isDesktop?
|
|
SizedBox(
|
|
width: MediaQuery.of(context).size.width * 0.89,
|
|
child: Row(mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [..._buildDelete(isDesktop, index)]),
|
|
)
|
|
: Row(mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [..._buildDelete(isDesktop, index)]),
|
|
|
|
SizedBox(height: 20),
|
|
|
|
|
|
Text(
|
|
"Class $index *",
|
|
style: TextStyle(
|
|
fontSize: 12,
|
|
fontWeight: FontWeight.w600,
|
|
color: Color(0xFF575A74)),
|
|
),
|
|
SizedBox(height: 5),
|
|
|
|
CustomTextFieldWrapper(
|
|
isFocused: focusStates["_class${index}Focused"] ?? false,
|
|
isDesktop: isDesktop,
|
|
child: SizedBox(
|
|
height: 40,
|
|
|
|
child: DropdownButtonFormField<String>(
|
|
focusNode: focusNodes["_class${index}FocusNode"],
|
|
// focusNode: _tripTypeFocusNode, // Assign the correct focus node
|
|
// controller: _hotelNameController,
|
|
value: selectedClasses[index] ,
|
|
style: TextStyle(fontSize: 12),
|
|
decoration: InputDecoration(
|
|
border: InputBorder.none,
|
|
contentPadding: EdgeInsets.symmetric(
|
|
horizontal: 10), // Proper padding
|
|
),
|
|
onChanged: purposeList.isNotEmpty
|
|
? (newValue) {
|
|
setState(() {
|
|
selectedClasses[index] = newValue;
|
|
});
|
|
|
|
print( selectedClasses[index] );
|
|
|
|
}
|
|
: null,
|
|
items: dropdownItems,
|
|
),
|
|
|
|
),
|
|
),
|
|
|
|
|
|
],
|
|
),
|
|
];
|
|
}
|
|
|
|
|
|
List<Widget> _buildSecondRow(bool isDesktop, int index) {
|
|
|
|
|
|
DateTime? _selectedCheckOutDate;
|
|
TimeOfDay? _selectedCheckOutTime;
|
|
|
|
Future<void> _selectCheckOutDate(BuildContext context) async {
|
|
DateTime now = DateTime.now();
|
|
DateTime today = DateTime(now.year, now.month, now.day);
|
|
|
|
DateTime? pickedDate = await showDatePicker(
|
|
context: context,
|
|
|
|
|
|
initialDate: _selectedCheckOutDate != null && _selectedCheckOutDate!.isAfter(today)
|
|
? _selectedCheckOutDate!
|
|
: today,
|
|
firstDate: today,
|
|
lastDate: DateTime(2100),
|
|
);
|
|
|
|
if (pickedDate != null && pickedDate != _selectedCheckOutDate) {
|
|
setState(() {
|
|
_selectedCheckOutDate = pickedDate;
|
|
// _dateController.text = DateFormat('yyyy-MM-dd').format(pickedDate);
|
|
textControllers["_date${index}Controller"]?.text = DateFormat('yyyy-MM-dd').format(pickedDate);
|
|
});
|
|
}
|
|
}
|
|
|
|
Future<void> _selectCheckOutTime(BuildContext context) async {
|
|
TimeOfDay? pickedTime = await showTimePicker(
|
|
context: context,
|
|
initialTime: _selectedCheckOutTime ?? TimeOfDay.now(),
|
|
);
|
|
|
|
if (pickedTime != null && pickedTime != _selectedCheckOutTime) {
|
|
setState(() {
|
|
_selectedCheckOutTime = pickedTime;
|
|
// Formatting time to HH:mm (24-hour format)
|
|
final now = DateTime.now();
|
|
final formattedTime = DateFormat('HH:mm').format(
|
|
DateTime(now.year, now.month, now.day, pickedTime.hour,
|
|
pickedTime.minute),
|
|
);
|
|
// _timeController.text = formattedTime;
|
|
textControllers["_time${index}Controller"]?.text = formattedTime;
|
|
});
|
|
}
|
|
}
|
|
|
|
return [
|
|
|
|
Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
"From",
|
|
style: TextStyle(
|
|
fontSize: 12,
|
|
fontWeight: FontWeight.w600,
|
|
color: Color(0xFF575A74)),
|
|
),
|
|
SizedBox(height: 5),
|
|
CustomTextFieldItnerarySubWrapper(
|
|
// isFocused: _fromFocus,
|
|
isFocused: focusStates["_from${index}Focused"] ?? false,
|
|
// isFocused: focusStates["_from${fieldIndex}Focused"] ?? false,
|
|
isDesktop: isDesktop,
|
|
child: SizedBox(
|
|
height: 40,
|
|
child: TextField(
|
|
// focusNode: _fromFocusNode,
|
|
focusNode: focusNodes["_from${index}FocusNode"],
|
|
controller: textControllers["_from${index}Controller"],
|
|
style: const TextStyle(fontSize: 12),
|
|
decoration: const InputDecoration(
|
|
labelText: "From",
|
|
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
|
|
floatingLabelBehavior: FloatingLabelBehavior.never,
|
|
border: InputBorder.none,
|
|
contentPadding: EdgeInsets.symmetric(vertical: 16),
|
|
|
|
),
|
|
),
|
|
),
|
|
),
|
|
|
|
|
|
|
|
if (errorMessages["from_place_$index"] != null) ...[
|
|
SizedBox(height: 5), // Space before error message
|
|
Text(
|
|
"Required",
|
|
style: TextStyle(color: Colors.red, fontSize: 12),
|
|
),
|
|
],
|
|
|
|
|
|
],
|
|
),
|
|
if (isDesktop)
|
|
Spacer()
|
|
else
|
|
SizedBox(
|
|
height: 8,
|
|
),
|
|
Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
"To",
|
|
style: TextStyle(
|
|
fontSize: 12,
|
|
fontWeight: FontWeight.w600,
|
|
color: Color(0xFF575A74)),
|
|
),
|
|
SizedBox(height: 5),
|
|
CustomTextFieldItnerarySubWrapper(
|
|
isFocused: focusStates["_to${index}Focused"] ?? false,
|
|
isDesktop: isDesktop,
|
|
child: SizedBox(
|
|
height: 40,
|
|
|
|
child: TextField(
|
|
focusNode: focusNodes["_to${index}FocusNode"],
|
|
controller: textControllers["_to${index}Controller"],
|
|
style: const TextStyle(fontSize: 12),
|
|
decoration: const InputDecoration(
|
|
labelText: "To",
|
|
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
|
|
floatingLabelBehavior: FloatingLabelBehavior.never,
|
|
border: InputBorder.none,
|
|
contentPadding: EdgeInsets.symmetric(vertical: 16),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
if (errorMessages["to_place_$index"] != null) ...[
|
|
SizedBox(height: 5), // Space before error message
|
|
Text(
|
|
"Required",
|
|
style: TextStyle(color: Colors.red, fontSize: 12),
|
|
),
|
|
],
|
|
],
|
|
),
|
|
if (isDesktop)
|
|
Spacer()
|
|
else
|
|
SizedBox(
|
|
height: 8,
|
|
),
|
|
Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
"Date",
|
|
style: TextStyle(
|
|
fontSize: 12,
|
|
fontWeight: FontWeight.w600,
|
|
color: Color(0xFF575A74)),
|
|
),
|
|
SizedBox(height: 5),
|
|
CustomTextFieldItnerarySubWrapper(
|
|
isFocused: focusStates["_date${index}Focused"] ?? false,
|
|
isDesktop: isDesktop,
|
|
child: SizedBox(
|
|
height: 40,
|
|
|
|
child: GestureDetector(
|
|
onTap: () => _selectCheckOutDate(context),
|
|
child: AbsorbPointer(
|
|
child: TextField(
|
|
focusNode: focusNodes["_date${index}FocusNode"],
|
|
// controller: _dateController,
|
|
controller: textControllers["_date${index}Controller"],
|
|
style: const TextStyle(fontSize: 12),
|
|
decoration: const InputDecoration(
|
|
labelText: "Select Date",
|
|
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
|
|
floatingLabelBehavior: FloatingLabelBehavior.never,
|
|
border: InputBorder.none,
|
|
contentPadding: EdgeInsets.symmetric(vertical: 16),
|
|
suffixIcon: Icon(Icons.calendar_today,
|
|
size: 16, color: Colors.grey),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
|
|
),
|
|
),
|
|
|
|
if (errorMessages["date_$index"] != null) ...[
|
|
SizedBox(height: 5), // Space before error message
|
|
Text(
|
|
"Required",
|
|
style: TextStyle(color: Colors.red, fontSize: 12),
|
|
),
|
|
],
|
|
|
|
|
|
|
|
|
|
],
|
|
),
|
|
if (isDesktop)
|
|
Spacer()
|
|
else
|
|
SizedBox(
|
|
height: 8,
|
|
),
|
|
Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
"Time",
|
|
style: TextStyle(
|
|
fontSize: 12,
|
|
fontWeight: FontWeight.w600,
|
|
color: Color(0xFF575A74)),
|
|
),
|
|
SizedBox(height: 5),
|
|
CustomTextFieldItnerarySubWrapper(
|
|
isFocused: focusStates["_timeFocused"] ?? false,
|
|
isDesktop: isDesktop,
|
|
child: SizedBox(
|
|
height: 40,
|
|
child: GestureDetector(
|
|
onTap: () => _selectCheckOutTime(context),
|
|
child: AbsorbPointer(
|
|
child: TextField(
|
|
focusNode: focusNodes["_time${index}FocusNode"],
|
|
// controller: _timeController,
|
|
controller: textControllers["_time${index}Controller"],
|
|
style: const TextStyle(fontSize: 12),
|
|
decoration: const InputDecoration(
|
|
labelText: "Select Time",
|
|
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
|
|
floatingLabelBehavior: FloatingLabelBehavior.never,
|
|
border: InputBorder.none,
|
|
contentPadding: EdgeInsets.symmetric(vertical: 16),
|
|
suffixIcon:
|
|
Icon(Icons.access_time, size: 16, color: Colors.grey),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
|
|
),
|
|
),
|
|
if (errorMessages["time_$index"] != null) ...[
|
|
SizedBox(height: 5), // Space before error message
|
|
Text(
|
|
"Required",
|
|
style: TextStyle(color: Colors.red, fontSize: 12),
|
|
),
|
|
],
|
|
|
|
],
|
|
),
|
|
|
|
];
|
|
}
|
|
|
|
|
|
List<Widget> _buildThirdRow(bool isDesktop) {
|
|
return [
|
|
Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
"Comments",
|
|
style: TextStyle(
|
|
fontSize: 12,
|
|
fontWeight: FontWeight.w600,
|
|
color: Color(0xFF575A74)),
|
|
),
|
|
SizedBox(height: 5),
|
|
CustomTextFieldWrapper(
|
|
isFocused: focusStates["_comments1Focused"] ?? false,
|
|
isDesktop: isDesktop,
|
|
width: isDesktop
|
|
? MediaQuery.of(context).size.width * 0.4
|
|
: MediaQuery.of(context).size.width * 0.66,
|
|
child: TextField(
|
|
focusNode: focusNodes["_comments1FocusNode"],
|
|
// controller: _commentsController,
|
|
controller: textControllers["_comments1Controller"],
|
|
maxLines: 6,
|
|
keyboardType: TextInputType.multiline,
|
|
style: TextStyle(fontSize: 12),
|
|
decoration: InputDecoration(
|
|
labelText: "Description",
|
|
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
|
|
floatingLabelBehavior: FloatingLabelBehavior.never,
|
|
border: InputBorder.none,
|
|
contentPadding: EdgeInsets.symmetric(vertical: 4),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
)
|
|
];
|
|
}
|
|
|
|
|
|
|
|
List<Widget> _buildvisa(bool isDesktop){
|
|
|
|
List<dynamic> visa_available = widget.apiData?['flight_visa_available'] ?? [];
|
|
// Default selected value
|
|
|
|
|
|
List<DropdownMenuItem<String>> dropdownItems = visa_available
|
|
.map((item)=>DropdownMenuItem<String>(
|
|
value: item['dropdown_key'],
|
|
child: Text(item['dropdown_value']),
|
|
)).toList();
|
|
|
|
selectedvisa_available ??= dropdownItems.isNotEmpty ? dropdownItems.first.value : null;
|
|
|
|
if (dropdownItems.isEmpty) {
|
|
dropdownItems.add(
|
|
DropdownMenuItem<String>(
|
|
value: null,
|
|
child: Text("No options available", style: TextStyle(color: Colors.grey)),
|
|
),
|
|
);
|
|
}
|
|
|
|
|
|
|
|
return [
|
|
Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
"Visa Required",
|
|
style: TextStyle(
|
|
fontSize: 12,
|
|
fontWeight: FontWeight.w600,
|
|
color: Color(0xFF575A74)),
|
|
),
|
|
SizedBox(height: 5),
|
|
CustomTextFieldWrapper(
|
|
// isFocused: _tripTypeFocused,
|
|
isFocused: focusStates["_visa1Focused"] ?? false,
|
|
isDesktop: isDesktop,
|
|
child: SizedBox(
|
|
height: 40,
|
|
|
|
child: DropdownButtonFormField<String>(
|
|
// focusNode: _tripTypeFocusNode, // Assign the correct focus node
|
|
focusNode: focusNodes["_visa1FocusNode"],
|
|
value: selectedvisa_available,
|
|
style: TextStyle(fontSize: 12),
|
|
decoration: InputDecoration(
|
|
border: InputBorder.none,
|
|
contentPadding: EdgeInsets.symmetric(
|
|
horizontal: 10), // Proper padding
|
|
),
|
|
onChanged: visa_available.isNotEmpty
|
|
? (newValue) {
|
|
setState(() {
|
|
selectedvisa_available = newValue;
|
|
// selectedTripType = "Oneway";
|
|
// Reset `multiTripRowCount` when switching away from Multitrip
|
|
});
|
|
print("Updating form data: Flight -> trip_type -> $selectedvisa_available");
|
|
|
|
|
|
// _initializeRows();
|
|
}
|
|
: null,
|
|
|
|
|
|
items: dropdownItems,
|
|
),
|
|
|
|
|
|
),
|
|
),
|
|
],
|
|
),
|
|
];
|
|
}
|
|
|
|
|
|
List<Widget> _handleAction(bool isDesktop) {
|
|
return [
|
|
// Close Button
|
|
ElevatedButton(
|
|
onPressed: () {
|
|
widget.onClose(false);// Close the dialog or screen
|
|
},
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: Colors.grey[400], // Light grey color
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(8),
|
|
),
|
|
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
|
|
),
|
|
child: Text(
|
|
"Close",
|
|
style: TextStyle(color: Colors.white, fontSize: 14),
|
|
),
|
|
),
|
|
SizedBox(width: 10), // Space between buttons
|
|
|
|
// Save Changes Button
|
|
ElevatedButton(
|
|
onPressed: () {
|
|
handleSave();
|
|
},
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: Colors.blue, // Primary color for save
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(8),
|
|
),
|
|
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
|
|
),
|
|
child: Text(
|
|
"Save Changes",
|
|
style: TextStyle(color: Colors.white, fontSize: 14),
|
|
),
|
|
),
|
|
];
|
|
}
|
|
}
|