ts-tat/lib/Screens/userManagement/create_user/traveller_details.dart
2025-05-23 14:52:28 +05:30

3226 lines
107 KiB
Dart

// import 'dart:io' as html;
import 'dart:io' as io;
import 'package:dropdown_search/dropdown_search.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:frontend/utils/auth_utils.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:intl/intl.dart';
import 'package:universal_html/html.dart' as html;
import 'dart:async';
import 'dart:typed_data'; // Import for Uint8List
import 'package:flutter/foundation.dart';
import '../../../config/apiUrl.dart';
import '../../../services/apiService.dart';
import '../../../widgets/custom_user_form.dart';
import '../../../widgets/custom_user_form.dart';
import '../../../widgets/custom_user_travel.dart';
class TravellerDetails extends StatefulWidget {
final bool isDesktop;
final Map<String, TextEditingController> controllers;
final Map<String, String> errorMessages;
final bool isViewMode;
final Map<String, dynamic>? travelDetails;
final String? passportFileUrlFromApi;
const TravellerDetails({
Key? key,
required this.controllers,
required this.errorMessages,
required this.isDesktop,
required this.isViewMode,
this.travelDetails,
this.passportFileUrlFromApi,
}) : super(key: key);
@override
TravellerDetailsState createState() => TravellerDetailsState();
}
class TravellerDetailsState extends State<TravellerDetails> {
final ApiService apiService = ApiService();
html.File? passportFile;
late List<bool> isExpandedList;
int? expandedIndex;
bool isCountryLoading = true;
late final userId;
String? selectedFileNames;
Uint8List? passportDocumentBytes;
String? passportFileUrlFromApi;
// String? selectedPurpose;
// String? selectedCountry;
String? selectedLocalIdType;
List<dynamic> countryList = [];
String? selectedDomesticSeat;
String? selectedIntenationalSeat;
String? SelectedIdType;
Map<int, TextEditingController> airlineControllers = {};
Map<int, TextEditingController> flierNumberControllers = {};
List<Map<String, dynamic>> hotelLoyaltyEntries = [];
int _rowhotelCounter = 1;
List<Map<String, dynamic>> frequentFlierEntries = [];
int _rowFlierCounter = 1;
List<Map<String, dynamic>> visaEntries = [];
int _rowVisaCounter = 1;
Map<String, String> countryMap = {};
late List<dynamic>? apiCountryData;
late List<dynamic>? apiHotelsData;
late List<dynamic>? apiAirlineCountryData;
Map<String, dynamic>? apiData;
final Map<String, TextEditingController> controllers = {};
final List<String> headers = [
"Passport Details",
// "Local ID Details",
"Visa Details",
"Frequent Flyer Information",
"Hotel Loyalty Membership",
"Preferences Domestic",
"Preferences International",
"Other Details",
"Forex Details"
];
//To Create Controllers
List<String> dataHeader = [
"Fname",
"Lname",
"nationality",
"d_meal_pref",
"i_meal_pref",
"d_additonal_Info",
"i_additonal_Info",
"passportNumber",
"emergency_contact",
"placeOfIssue",
"passportDoc",
"dateOfIssue",
"dateOfExpiry",
"forex_card_num",
"full_name_as_id",
"local_id_num",
"forex_expiry_date"
];
@override
void initState() {
super.initState();
// userId = getUserId();
apiCountryData = null;
apiHotelsData = [];
apiAirlineCountryData = [];
apiData = null;
for (var field in dataHeader) {
controllers[field] = TextEditingController();
}
fetchCountries();
fetchHotels();
loadCountryList();
fetchApiData();
// Delay adding the row until after the first frame
WidgetsBinding.instance.addPostFrameCallback((_) {
setState(() {
addHotelLoyaltyEntry();
addFrequentFlierEntry();
addVisaEntry();
});
updateTravel();
});
}
void _clearError(String field) {
setState(() {
widget.errorMessages.remove(field);
});
}
//-------------------------Hotel Loyalty --------------------------------
void addHotelLoyaltyEntry() {
final localId = _rowhotelCounter++;
hotelLoyaltyEntries.add({
"local_id": localId,
"id": null, // to be set when backend responds
"hotel_name": null,
// "controller_hotel": TextEditingController(),
"controller_membership": TextEditingController(),
"is_active": "1"
});
setState(() {});
}
void removeHotelLoyaltyEntry(Map<String, dynamic> entry) {
print("removeHotelLoyaltyEntry- $entry");
setState(() {
entry["is_active"] = "0";
});
}
//-------------------------Frequent Flier--------------------------------
void addFrequentFlierEntry() {
final localId = _rowFlierCounter++;
frequentFlierEntries.add({
"local_id": localId,
"id": null, // to be set when backend responds
// "controller_airline": null,
"airline": null,
"controller_flier_number": TextEditingController(),
"is_active": "1"
});
setState(() {});
}
void removeFrequentFlierEntry(Map<String, dynamic> entry) {
print("removeFrequentFlierEntrydd- $entry");
setState(() {
entry["is_active"] = "0";
});
}
//-------------------------Visa --------------------------------
void addVisaEntry() {
final localId = _rowVisaCounter++;
visaEntries.add({
"local_id": localId,
"id": null, // to be set when backend responds
"country_code": null,
"visa_type_id": null,
"controller_valid_from": TextEditingController(),
"controller_valid_upto": TextEditingController(),
"is_active": "1"
});
setState(() {});
}
void removeVisaEntry(Map<String, dynamic> entry) {
print("removeFrequentFlierEntrydd- $entry");
setState(() {
entry["is_active"] = "0";
});
}
@override
void dispose() {
for (var entry in hotelLoyaltyEntries) {
// entry["controller_hotel"].dispose();
entry["controller_membership"].dispose();
}
for (var entry in frequentFlierEntries) {
// entry["controller_airline"].dispose();
entry["controller_flier_number"].dispose();
}
for (var entry in visaEntries) {
entry["controller_valid_from"].dispose();
entry["controller_valid_upto"].dispose();
}
super.dispose();
}
void disposeController(dynamic controller) {
if (controller is TextEditingController) {
controller.dispose();
}
}
Map<String, dynamic> get travel_Detials {
Map<String, dynamic> data = {
// "given_Name": controllers["Fname"]?.text,
// "last_Name": controllers["Lname"]?.text,
// "nationality": controllers["nationality"]?.text,
"passport_number": controllers["passportNumber"]?.text ?? '',
"place_of_issue": controllers["placeOfIssue"]?.text,
"date_of_issue": controllers["dateOfIssue"]?.text,
"date_of_expiry": controllers["dateOfExpiry"]?.text,
"d_meal_preference": controllers["d_meal_pref"]?.text,
"d_seat_preference": selectedDomesticSeat,
"d_additonalInfo": controllers["d_additonal_Info"]?.text,
"i_meal_preference": controllers["i_meal_pref"]?.text,
"i_seat_preference": selectedIntenationalSeat,
"i_additonalInfo": controllers["i_additonal_Info"]?.text,
"emergency_contact_number": controllers["emergency_contact"]?.text,
"forex_pre_paid_card_number": controllers["forex_card_num"]?.text,
"forex_expiry_date": controllers["forex_expiry_date"]?.text,
// "frequent_flier_information": [],
"frequent_flier_information": frequentFlierList,
"hotel_membership": hotelMembershipList,
"visa_details": visaDetailsList,
// "local_id_type": selectedLocalIdType,
// "local_id_num": controllers["local_id_num"]?.text,
// "full_name_as_id": controllers["full_name_as_id"]?.text,
};
return data;
}
// List<Map<String, dynamic>> get hotelMembershipList {
// final mappedList = hotelLoyaltyEntries.map((entry) {
// return {
// "id": entry["id"],
// "hotel_name": entry["controller_hotel"].text ?? '',
// "membership_number": entry["controller_membership"].text ?? '',
// "created_by": null,
// "updated_by": null,
// "is_active": entry["is_active"] ?? "1",
// };
// }).toList();
//
// final allEntriesEmpty = mappedList.every((entry) =>
// (entry["hotel_name"] as String).trim().isEmpty &&
// (entry["membership_number"] as String).trim().isEmpty);
//
// return allEntriesEmpty ? [] : mappedList;
// }
List<Map<String, dynamic>> get hotelMembershipList {
final mappedList = hotelLoyaltyEntries.map((entry) {
final membership_number = entry["controller_membership"];
// final hotelName = entry["controller_hotel"];
print("membership_number- ${membership_number}");
return {
"id": entry["id"],
"hotel_id": entry["hotel_id"] ?? "",
// "hotel_name": entry["hotel_id"] ?? "",
"hotel_name": entry["hotel_name"] ?? "",
// "hotel_name": hotelName is TextEditingController ? hotelName.text : "",
"membership_number": membership_number is TextEditingController
? membership_number.text
: "",
"created_by": null,
"updated_by": null,
"is_active": entry["is_active"] ?? "1",
};
}).toList();
final allEntriesEmpty = mappedList.every((entry) =>
(entry["hotel_id"] as String).trim().isEmpty &&
(entry["membership_number"] as String).trim().isEmpty);
return allEntriesEmpty ? [] : mappedList;
}
// List<Map<String, dynamic>> get frequentFlierList {
// return frequentFlierEntries
// // .where((entry) => entry["is_active"] != "0") // ✅ Only active entries
// .map((entry) {
// return {
// "id": entry["id"],
// "airline": entry["airline"],
// "frequent_flier_number": entry["controller_flier_number"].text,
// "created_by": null,
// "updated_by": null,
// "is_active": entry["is_active"] ?? "1",
// };
// }).toList();
// }
List<Map<String, dynamic>> get frequentFlierList {
final mappedList = frequentFlierEntries.map((entry) {
final frequent_flier_number = entry["controller_flier_number"];
print("frequent_flier_number- $frequent_flier_number");
return {
"id": entry["id"],
"airline": entry["airline"] ?? "",
"frequent_flier_number": frequent_flier_number is TextEditingController
? frequent_flier_number.text
: "",
"created_by": null,
"updated_by": null,
"is_active": entry["is_active"] ?? "1",
};
}).toList();
final allEntriesEmpty = mappedList.every((entry) =>
(entry["airline"] as String).trim().isEmpty &&
(entry["frequent_flier_number"] as String).trim().isEmpty);
return allEntriesEmpty ? [] : mappedList;
}
// List<Map<String, dynamic>> get visaDetailsList {
// return visaEntries
// // .where((entry) => entry["is_active"] != "0") // ✅ Only active entries
// .map((entry) {
// return {
// "id": entry["id"],
// "country_code": entry["country_code"],
// "visa_type_id": entry["visa_type_id"],
// "valid_from": entry["controller_valid_from"].text,
// "valid_upto": entry["controller_valid_upto"].text,
// // "valid_from": entry["controller_valid_from"] != null
// // ? entry["controller_valid_from"].text
// // : "",
// // "valid_upto": entry["controller_valid_upto"] != null
// // ? entry["controller_valid_upto"].text
// // : "",
// "created_by": null,
// "updated_by": null,
// "is_active": entry["is_active"] ?? "1",
// };
// }).toList();
// }
List<Map<String, dynamic>> get visaDetailsList {
final mappedList = visaEntries.map((entry) {
final fromController = entry["controller_valid_from"];
final uptoController = entry["controller_valid_upto"];
return {
"id": entry["id"],
"country_code": entry["country_code"], // ✅ string value
"visa_type_id": entry["visa_type_id"], // ✅ string value
"valid_from":
fromController is TextEditingController ? fromController.text : "",
"valid_upto":
uptoController is TextEditingController ? uptoController.text : "",
"created_by": null,
"updated_by": null,
"is_active": entry["is_active"] ?? "1",
};
}).toList();
final allEntriesEmpty = mappedList.every((entry) =>
(entry["country_code"] == null ||
entry["country_code"].toString().trim().isEmpty) &&
(entry["visa_type_id"] == null ||
entry["visa_type_id"].toString().trim().isEmpty) &&
(entry["valid_from"] == null ||
entry["valid_from"].toString().trim().isEmpty) &&
(entry["valid_upto"] == null ||
entry["valid_upto"].toString().trim().isEmpty));
return allEntriesEmpty ? [] : mappedList;
}
void updateTravel() {
print("widget.travelDetails");
print(widget.travelDetails);
if (widget.travelDetails != null) {
print("Updatewidget.travelDetails");
print(widget.travelDetails);
controllers["passportNumber"]?.text =
widget.travelDetails?["passport_number"] ?? "";
controllers["placeOfIssue"]?.text =
widget.travelDetails?["place_of_issue"] ?? "";
controllers["dateOfIssue"]?.text =
widget.travelDetails?["date_of_issue"] ?? "";
controllers["dateOfExpiry"]?.text =
widget.travelDetails?["date_of_expiry"] ?? "";
controllers["d_meal_pref"]?.text =
widget.travelDetails?["d_meal_preference"] ?? "";
controllers["i_meal_pref"]?.text =
widget.travelDetails?["i_meal_preference"] ?? "";
controllers["d_additonal_Info"]?.text =
widget.travelDetails?["d_additonalInfo"] ?? "";
controllers["i_additonal_Info"]?.text =
widget.travelDetails?["i_additonalInfo"] ?? "";
selectedIntenationalSeat =
widget.travelDetails?["i_seat_preference"] ?? "";
selectedDomesticSeat = widget.travelDetails?["d_seat_preference"] ?? "";
controllers["emergency_contact"]?.text =
widget.travelDetails?["emergency_contact_number"] ?? "";
controllers["forex_card_num"]?.text =
widget.travelDetails?["forex_pre_paid_card_number"] ?? "";
controllers["forex_expiry_date"]?.text =
widget.travelDetails?["forex_expiry_date"] ?? "";
String? apiDocPath = widget.travelDetails?["passport_document"];
if (apiDocPath != null && apiDocPath.isNotEmpty) {
passportFileUrlFromApi = apiDocPath;
selectedFileNames = apiDocPath.split('/').last;
passportFile = null;
} else {
passportFileUrlFromApi = null;
selectedFileNames = null;
passportFile = null;
}
//----------------------------------------------------
// 🚨 Hotel Membership Logic (pre-fill for edit)
// for (var entry in hotelLoyaltyEntries) {
// // entry["controller_hotel"].dispose();
// entry["controller_membership"].dispose();
// }
// hotelLoyaltyEntries.clear();
for (var entry in hotelLoyaltyEntries) {
disposeController(entry["controller_membership"]);
// (entry["controller_membership"] as TextEditingController?)?.dispose();
}
if (!mounted) return; // ✅ avoids context errors
print(MediaQuery.of(context).size);
//
// for (var entry in hotelLoyaltyEntries) {
// (entry["controller_membership"] as TextEditingController?)?.dispose();
// }
hotelLoyaltyEntries.clear();
final hotelMembershipList = widget.travelDetails?['hotel_membership'];
if (hotelMembershipList != null && hotelMembershipList is List) {
for (var item in hotelMembershipList) {
if (item["is_active"]?.toString() == "0") continue;
final localId = _rowhotelCounter++;
final hotelDataId = item["hotel_id"];
hotelLoyaltyEntries.add({
"local_id": localId,
"id": item["id"],
// "controller_hotel":
// TextEditingController(text: item["hotel_name"] ?? ""),
"hotel_name": item["hotel_name"] ?? "",
"hotel_id": hotelDataId ?? "",
// "controller_membership":
// TextEditingController(text: item["membership_number"] ?? ""),
"controller_membership":
TextEditingController(text: item["membership_number"] ?? ""),
});
}
if (hotelLoyaltyEntries.isEmpty) {
addHotelLoyaltyEntry();
}
}
//----------------------------------------------------
// 🚨 Visa Logic (pre-fill for edit)
for (var entry in visaEntries) {
entry["controller_valid_from"]?.dispose();
entry["controller_valid_upto"]?.dispose();
}
visaEntries.clear();
final visaDetailsList = widget.travelDetails?['visa_details'];
if (visaDetailsList != null && visaDetailsList is List) {
for (var item in visaDetailsList) {
if (item["is_active"]?.toString() == "0") continue;
final localId = _rowVisaCounter++;
visaEntries.add({
"local_id": localId,
"id": item["id"],
"country_code": item["country_code"],
"visa_type_id": item["visa_type_id"],
"controller_valid_from":
TextEditingController(text: item["valid_from"] ?? ""),
"controller_valid_upto":
TextEditingController(text: item["valid_upto"] ?? ""),
});
}
if (visaEntries.isEmpty) {
addVisaEntry();
}
}
// ----------------------------------------------------
// 🚨 Frequent Flier Logic (pre-fill for edit)
// for (var entry in frequentFlierEntries) {
// // entry["controller_airline"].dispose();
// // entry["controller_flier_number"].dispose();
// (entry["controller_flier_number"] as TextEditingController?)?.dispose();
// }
for (var entry in frequentFlierEntries) {
entry["controller_flier_number"]?.dispose();
}
frequentFlierEntries.clear();
print(MediaQuery.of(context).size);
final frequentFlierList =
widget.travelDetails?['frequent_flier_information'];
if (frequentFlierList != null && frequentFlierList is List) {
for (var item in frequentFlierList) {
if (item["is_active"]?.toString() == "0") continue;
final localId = _rowFlierCounter++;
final airlineCode = item["airline"];
frequentFlierEntries.add({
"local_id": localId,
"id": item["id"],
"airline": airlineCode ?? "",
"controller_flier_number": TextEditingController(
text: item["frequent_flier_number"] ?? ""),
});
}
if (frequentFlierEntries.isEmpty) {
addFrequentFlierEntry();
}
}
//----------------------------------------------------
setState(() {});
}
}
Future<void> fetchCountries() async {
try {
List<dynamic> countries = await apiService.fetchCountryList();
setState(() {
apiCountryData = countries;
});
} catch (e) {
print('Error fetching country list: $e');
}
}
Future<void> fetchHotels() async {
try {
List<dynamic> countries = await apiService.fetchHotelsList();
setState(() {
apiHotelsData = countries;
});
} catch (e) {
print('Error fetching country list: $e');
}
}
Future<void> loadCountryList() async {
final newTripType = "2";
final result = await apiService.fetchFlightsCountryList(newTripType);
print("resultFlight: $result ");
setState(() {
apiAirlineCountryData = result;
});
// print("ResultCountry : $result");
//
// // Create a map: Country_Code -> "City, Airport"
// Map<String, String> tempCountryMap = {};
//
// for (var country in result) {
// String city = country['City'] ?? '';
// String airport = country['Airport'] ?? '';
// String displayName = '${country['City']} - ${country['Airport']}';
//
// tempCountryMap[country['Code']] = displayName;
// }
//
// setState(() {
// countryMap = tempCountryMap; // Update the map
// isCountryLoading = false;
// });
}
Future<void> fetchApiData() async {
try {
final data = await apiService.fetchMasterDropdown();
print("fetchApiData - $data");
if (data is! Map) {
throw Exception("Invalid response format: expected a Map");
}
if (!mounted) return;
setState(() {
apiData = data;
});
} catch (e) {
print('Error fetching role list: $e');
}
}
@override
Widget build(BuildContext context) {
return Container(
height: MediaQuery.of(context).size.height,
decoration: BoxDecoration(
border: Border(
top: BorderSide(
color: Colors.blueGrey.shade300, // Set your desired color
width: 0.09, // Set border thickness
),
),
),
child: SingleChildScrollView(
scrollDirection: Axis.vertical,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
...List.generate(headers.length, (index) {
final isExpanded = expandedIndex == index;
return Card(
color: isExpanded ? Color(0xFFFFFDF3) : Colors.white,
elevation: isExpanded ? 0.9 : 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
margin: const EdgeInsets.symmetric(vertical: 5),
child: InkWell(
focusColor: Color(0xFFFFFDF3),
hoverColor: Color(0xFFFFFDF3),
highlightColor: Color(0xFFFFFDF3), // removes grey highlight
splashColor: Colors.white,
borderRadius: BorderRadius.circular(12),
onTap: () {
setState(() {
expandedIndex = isExpanded ? null : index;
});
},
child: Padding(
padding:
const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
headers[index],
style: GoogleFonts.poppins(
fontSize: widget.isDesktop ? 12 : 11,
fontWeight: FontWeight.w500,
color: Colors.black87,
),
),
Icon(
isExpanded
? Icons.keyboard_arrow_up
: Icons.keyboard_arrow_down,
color: isExpanded
? Colors.black87
: Colors.grey[600],
)
],
),
AnimatedSize(
duration: const Duration(milliseconds: 250),
curve: Curves.easeInOut,
child: ConstrainedBox(
constraints: isExpanded
? const BoxConstraints()
: const BoxConstraints(maxHeight: 0),
child: Padding(
padding: const EdgeInsets.only(top: 8),
child: Container(
width: double.infinity,
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
),
child: _buildExpandedContent(index),
),
),
),
),
],
),
),
),
);
}),
],
),
),
);
}
Widget _buildExpandedContent(int index) {
switch (headers[index]) {
case "Passport Details":
return _buildPassportDatas(widget.isDesktop);
// case "Local ID Details":
// return _buildLocalIdDetails(widget.isDesktop);
case "Visa Details":
return _buildVisaDetails(widget.isDesktop);
case "Frequent Flyer Information":
return _buildFrequentFlierInfo(widget.isDesktop);
case "Hotel Loyalty Membership":
return _buildHotelLoyalty(widget.isDesktop);
case "Preferences Domestic":
return _buildPreferencesDomestic(widget.isDesktop);
//
case "Preferences International":
return _buildPreferencesInternational(widget.isDesktop);
case "Other Details":
return _buildOtherDetails(widget.isDesktop);
case "Forex Details":
return _buildForexDetails(widget.isDesktop);
default:
return Text(
"Form fields for ${headers[index]} go here",
style: GoogleFonts.poppins(fontSize: 12),
);
}
}
//------------------------------------Passport Details-----------------------------------
Widget _buildPassportDatas(bool isDesktop) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// SizedBox(
// height: 10,
// ),
// _buildPassportDataRow1(widget.isDesktop),
SizedBox(
height: 10,
),
_buildPassportDataRow2(widget.isDesktop),
SizedBox(
height: 10,
),
_buildPassportDataRow3(widget.isDesktop),
],
);
}
Widget _buildPassportDataRow1(bool isDesktop) {
return Container(
color: Colors.white,
child: widget.isDesktop
? Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
buildFirstNameField(),
Spacer(),
buildLastNameField(),
Spacer(), // Space after Last Name
buildNationality(),
],
)
: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
buildFirstNameField(),
SizedBox(height: 8), // Vertical space
buildLastNameField(),
SizedBox(height: 8),
buildNationality(),
],
),
);
}
Widget _buildPassportDataRow2(bool isDesktop) {
return Container(
color: Colors.white,
child: widget.isDesktop
? Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
buildPassportNumber(),
Spacer(),
buildPlaceOfIssue(),
Spacer(),
/// Space after Last Name
buildDateOfIssue(),
SizedBox(width: 25),
buildDateOfExpiry()
],
)
: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
buildPassportNumber(),
SizedBox(height: 8), // Vertical space
buildPlaceOfIssue(),
SizedBox(height: 8),
buildDateOfIssue(),
SizedBox(height: 8),
buildDateOfExpiry()
],
),
);
}
Widget _buildPassportDataRow3(bool isDesktop) {
return Container(
color: Colors.white,
child: widget.isDesktop
? Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
buildPassportDocument(),
],
)
: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
buildPassportDocument(),
],
),
);
}
Widget buildFirstNameField() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"First Name",
style: GoogleFonts.poppins(
fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black),
),
SizedBox(height: 5),
CustomTextFieldUserTravellerWrapper(
isFocused: false,
isDesktop: widget.isDesktop,
child: SizedBox(
height: 40,
child: TextField(
style: GoogleFonts.poppins(fontSize: 12, color: Colors.black),
controller: controllers["Fname"],
enabled: !widget.isViewMode,
onChanged: (value) {
_clearError("first_name");
},
decoration: InputDecoration(
labelText: "FirstName",
labelStyle:
GoogleFonts.poppins(fontSize: 12, color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16),
),
),
),
),
if (widget.errorMessages["first_name"] != null) ...[
SizedBox(height: 5), // Space before error message
Text(
widget.errorMessages["first_name"]!,
style: TextStyle(color: Colors.red, fontSize: 12),
),
],
],
);
}
Widget buildLastNameField() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Last Name",
style: GoogleFonts.poppins(
fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black),
),
SizedBox(height: 5),
CustomTextFieldUserTravellerWrapper(
isFocused: false,
isDesktop: widget.isDesktop,
child: SizedBox(
height: 40,
child: TextField(
style: GoogleFonts.poppins(fontSize: 12, color: Colors.black),
controller: controllers["Lname"],
enabled: !widget.isViewMode,
onChanged: (value) {
_clearError("last_name");
},
decoration: InputDecoration(
labelText: "LastName",
labelStyle:
GoogleFonts.poppins(fontSize: 12, color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16),
),
),
),
),
if (widget.errorMessages["last_name"] != null) ...[
SizedBox(height: 5), // Space before error message
Text(
widget.errorMessages["last_name"]!,
style: TextStyle(color: Colors.red, fontSize: 12),
),
],
],
);
}
Widget buildNationality() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Nationality",
style: GoogleFonts.poppins(
fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black),
),
SizedBox(height: 5),
CustomTextFieldUserTravellerWrapper(
isFocused: false,
isDesktop: widget.isDesktop,
child: SizedBox(
height: 40,
child: TextField(
style: GoogleFonts.poppins(fontSize: 12, color: Colors.black),
controller: controllers["nationality"],
enabled: !widget.isViewMode,
onChanged: (value) {
_clearError("last_name");
},
decoration: InputDecoration(
labelText: "Nationality",
labelStyle:
GoogleFonts.poppins(fontSize: 12, color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16),
),
),
),
),
if (widget.errorMessages["last_name"] != null) ...[
SizedBox(height: 5), // Space before error message
Text(
widget.errorMessages["last_name"]!,
style: TextStyle(color: Colors.red, fontSize: 12),
),
],
],
);
}
Widget buildPassportNumber() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Passport Number",
style: GoogleFonts.poppins(
fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black),
),
SizedBox(height: 5),
CustomTextFieldUserTravellerWrapper(
isFocused: false,
isDesktop: widget.isDesktop,
child: SizedBox(
height: 40,
child: TextField(
style: GoogleFonts.poppins(fontSize: 12, color: Colors.black),
controller: controllers["passportNumber"],
enabled: !widget.isViewMode,
onChanged: (value) {
_clearError("last_name");
},
decoration: InputDecoration(
labelText: "Passport Number",
labelStyle:
GoogleFonts.poppins(fontSize: 12, color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16),
),
),
),
),
],
);
}
Widget buildPlaceOfIssue() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Place of Issue",
style: GoogleFonts.poppins(
fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black),
),
SizedBox(height: 5),
CustomTextFieldUserTravellerWrapper(
isFocused: false,
isDesktop: widget.isDesktop,
child: SizedBox(
height: 40,
child: TextField(
style: GoogleFonts.poppins(fontSize: 12, color: Colors.black),
controller: controllers["placeOfIssue"],
enabled: !widget.isViewMode,
onChanged: (value) {
_clearError("placeOfIssue");
},
decoration: InputDecoration(
labelText: "Place of Issue",
labelStyle:
GoogleFonts.poppins(fontSize: 12, color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16),
),
),
),
),
],
);
}
Widget buildDateOfIssue() {
DateTime? _selectedDateOfIssue;
Future<void> _selectCheckDateOfIssue(BuildContext context) async {
DateTime now = DateTime.now();
DateTime today = DateTime(now.year, now.month, now.day);
DateTime? pickedDate = await showDatePicker(
context: context,
initialDate:
_selectedDateOfIssue != null && _selectedDateOfIssue!.isAfter(today)
? _selectedDateOfIssue!
: today,
firstDate: today,
lastDate: DateTime(2100),
);
if (pickedDate != null && pickedDate != _selectedDateOfIssue) {
setState(() {
_selectedDateOfIssue = pickedDate;
controllers["dateOfIssue"]?.text =
DateFormat('dd-MM-yyyy').format(pickedDate);
});
}
}
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Date of Issue",
style: GoogleFonts.poppins(
fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black),
),
SizedBox(height: 5),
CustomTextFieldUserTravellerWrapper(
width: widget.isDesktop
? MediaQuery.of(context).size.width * 0.13
: null,
isFocused: false,
isDesktop: widget.isDesktop,
child: SizedBox(
height: 40,
child: GestureDetector(
onTap: widget.isViewMode
? null
: () async {
await _selectCheckDateOfIssue(context);
if (controllers["dateOfIssue"]!.text.isNotEmpty) {
setState(() {
// errorMessages.remove("start_date");
});
}
},
child: AbsorbPointer(
child: TextField(
controller: controllers["dateOfIssue"],
style: GoogleFonts.poppins(fontSize: 12, color: Colors.black),
decoration: InputDecoration(
labelText: "Select Date",
labelStyle:
GoogleFonts.poppins(fontSize: 12, color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: const EdgeInsets.symmetric(vertical: 16),
suffixIcon: const Icon(Icons.calendar_today,
size: 16, color: Color(0xFF8B8FB2)),
),
),
),
),
),
),
],
);
}
Widget buildDateOfExpiry() {
DateTime? _selectedDateOfExpiry;
Future<void> _selectCheckDateOfExpiry(BuildContext context) async {
DateTime now = DateTime.now();
DateTime today = DateTime(now.year, now.month, now.day);
// DateTime today = DateTime(now.year now.day, now.month,);
DateTime? pickedDate = await showDatePicker(
context: context,
initialDate: _selectedDateOfExpiry != null &&
_selectedDateOfExpiry!.isAfter(today)
? _selectedDateOfExpiry!
: today,
firstDate: today,
lastDate: DateTime(2100),
);
if (pickedDate != null && pickedDate != _selectedDateOfExpiry) {
setState(() {
_selectedDateOfExpiry = pickedDate;
controllers["dateOfExpiry"]?.text =
DateFormat('dd-MM-yyyy').format(pickedDate);
// controllers["dateOfExpiry"]?.text =
// DateFormat('dd-MM-yyyy').format(pickedDate);
});
}
}
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Date of Expiry",
style: GoogleFonts.poppins(
fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black),
),
SizedBox(height: 5),
CustomTextFieldUserTravellerWrapper(
isFocused: false,
isDesktop: widget.isDesktop,
width: widget.isDesktop
? MediaQuery.of(context).size.width * 0.13
: null,
child: SizedBox(
height: 40,
child: GestureDetector(
onTap: widget.isViewMode
? null
: () async {
await _selectCheckDateOfExpiry(context);
if (controllers["dateOfExpiry"]!.text.isNotEmpty) {
setState(() {
// errorMessages.remove("start_date");
});
}
},
child: AbsorbPointer(
child: TextField(
controller: controllers["dateOfExpiry"],
style: GoogleFonts.poppins(fontSize: 12, color: Colors.black),
decoration: InputDecoration(
labelText: "Select Date",
labelStyle:
GoogleFonts.poppins(fontSize: 12, color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: const EdgeInsets.symmetric(vertical: 16),
suffixIcon: const Icon(Icons.calendar_today,
size: 16, color: Color(0xFF8B8FB2)),
),
),
),
),
),
),
],
);
}
Widget buildPassportDocument() {
void pickPDFWeb() {
html.FileUploadInputElement uploadInput = html.FileUploadInputElement();
uploadInput.accept = '.pdf';
uploadInput.click();
uploadInput.onChange.listen((e) {
final file = uploadInput.files!.first;
// Ensure the file is a PDF
if (!file.type.contains("pdf")) {
print("Error: Not a PDF file");
return;
}
// 🔹 File size check: Ensure it does not exceed 3MB
int maxFileSize = 3 * 1024 * 1024; // 3MB in bytes
if (file.size > maxFileSize) {
print('Error: File size exceeds 3MB');
return;
}
setState(() {
selectedFileNames = file.name;
passportFile = file;
passportFileUrlFromApi = null;
});
print('PDF File selected: ${file.name}');
});
}
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Passport Document",
style: GoogleFonts.poppins(
fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black),
),
SizedBox(height: 5),
CustomTextFieldUserTravellerWrapper(
isFocused: false,
isDesktop: widget.isDesktop,
child: SizedBox(
height: 40,
child: GestureDetector(
onTap: pickPDFWeb,
child: Container(
padding: EdgeInsets.symmetric(horizontal: 10),
alignment: Alignment.centerLeft,
child: Text(
selectedFileNames ?? "Select Document",
style: GoogleFonts.poppins(fontSize: 12, color: Colors.black),
overflow: TextOverflow.ellipsis, // Prevents overflow issues
),
),
),
),
),
SizedBox(height: 10),
if (passportFile != null || passportFileUrlFromApi != null)
// Centers the text
Container(
color: Colors.white,
width: widget.isDesktop
? MediaQuery.of(context).size.width * 0.25
: MediaQuery.of(context).size.width * 0.8,
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.end,
children: [
GestureDetector(
onTap: () {
print('DOWNLOAD - $passportFile');
if (passportFile != null) {
try {
// ✅ Step 1: Create a Blob directly from the file
// final blob =
// html.Blob([passportFile!], 'application/pdf');
//
// // ✅ Step 2: Generate a download URL from the Blob
// final url = html.Url.createObjectUrlFromBlob(blob);
//
// // ✅ Step 3: Create an invisible anchor to trigger download
// final anchor = html.AnchorElement(href: url)
// ..setAttribute(
// "download", selectedFileNames ?? "document.pdf")
// ..style.display = "none";
//
// // ✅ Step 4: Add anchor to DOM and click it
// html.document.body!.append(anchor);
// anchor.click();
//
// // ✅ Step 5: Clean up
// anchor.remove();
// html.Url.revokeObjectUrl(url);
// Create a blob from the response body
final blob = html.Blob([passportFile!]);
// Generate a download URL for the blob
final url = html.Url.createObjectUrlFromBlob(blob);
// Create a link element to trigger the download
final anchor = html.AnchorElement(href: url)
..setAttribute(
'download', selectedFileNames ?? "document.pdf")
..click();
// Revoke the download URL to free up resources
html.Url.revokeObjectUrl(url);
print("Download triggered successfully!");
} catch (e) {
print("Error during download: $e");
}
} else if (passportFileUrlFromApi != null) {
print("Raw file path: $passportFileUrlFromApi");
// Extract public part of the path starting from "/assets"
String cleanedPath = passportFileUrlFromApi!;
final index = passportFileUrlFromApi!.indexOf("/assets");
if (index != -1) {
cleanedPath = passportFileUrlFromApi!.substring(index);
}
final fullUrl =
'https://apitest.tripapprovaltool.com$cleanedPath';
print("Final download URL: $fullUrl");
final anchor = html.AnchorElement(href: fullUrl)
..target = '_blank'
..download = selectedFileNames ?? "document.pdf"
..click();
} else {
print("No file available to download.");
}
},
child: Container(
padding: const EdgeInsets.all(5),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(5),
color: Colors.green.shade300,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
"Download",
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w200,
color: Colors.white),
overflow: TextOverflow.ellipsis,
textAlign: TextAlign
.end, // Ensures text is centered within the Text widget
),
SizedBox(width: 5),
Icon(
Icons.download,
size: 13,
color: Colors.white,
)
],
),
),
)
],
),
),
],
);
}
//------------------------Local Details----------------------------------------
Widget _buildLocalIdDetails(bool isDesktop) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
height: 10,
),
_buildLocalDetailDataRow1(widget.isDesktop),
],
);
}
Widget _buildLocalDetailDataRow1(bool isDesktop) {
return Container(
color: Colors.white,
child: widget.isDesktop
? Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
buildLocalIdType(),
Spacer(),
buildIdNumber(),
Spacer(),
buildFullNameAsId(),
],
)
: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
buildLocalIdType(),
SizedBox(height: 8),
buildIdNumber(),
SizedBox(height: 8), // Vertical space
buildFullNameAsId(),
],
),
);
}
Widget buildIdNumber() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Id Number",
style: GoogleFonts.poppins(
fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black),
),
SizedBox(height: 5),
CustomTextFieldUserTravellerWrapper(
isFocused: false,
isDesktop: widget.isDesktop,
child: SizedBox(
height: 40,
child: TextField(
style: GoogleFonts.poppins(fontSize: 12, color: Colors.black),
controller: controllers["local_id_num"],
enabled: !widget.isViewMode,
onChanged: (value) {
_clearError("local_id_num");
},
decoration: InputDecoration(
labelText: "Id Number",
labelStyle:
GoogleFonts.poppins(fontSize: 12, color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16),
),
),
),
),
],
);
}
Widget buildLocalIdType() {
final List<Map<String, dynamic>> localIdOptions = [
{"dropdown_value": "Licence"},
{"dropdown_value": "PAN"},
{"dropdown_value": "Voter Card"},
{"dropdown_value": "Aadhar Card"},
];
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Id Type",
style: GoogleFonts.poppins(
fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black),
),
SizedBox(height: 5),
CustomTextFieldUserTravellerWrapper(
isFocused: false,
isDesktop: widget.isDesktop,
child: SizedBox(
height: 35,
width: double.infinity,
child: DropdownSearch<Map<String, dynamic>>(
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: const DropDownDecoratorProps(
dropdownSearchDecoration: InputDecoration(
border: InputBorder.none,
contentPadding:
EdgeInsets.symmetric(horizontal: 10, vertical: 5),
),
),
dropdownButtonProps: const DropdownButtonProps(
icon: Icon(Icons.arrow_drop_down),
),
dropdownBuilder: (context, selectedItem) {
if (selectedItem == null || selectedItem.isEmpty) {
return Text("Select Seat", // fallback text
style: GoogleFonts.poppins(
fontSize: 12, color: Colors.grey));
}
return Text(selectedItem['dropdown_value'] ?? '',
style:
GoogleFonts.poppins(fontSize: 12, color: Colors.black));
},
items: localIdOptions,
selectedItem: selectedLocalIdType == null
? null
: {"dropdown_value": selectedLocalIdType},
onChanged: widget.isViewMode
? null
: (Map<String, dynamic>? newItem) {
if (newItem != null) {
setState(() {
selectedLocalIdType = newItem['dropdown_value'];
});
}
},
),
),
),
],
);
}
Widget buildFullNameAsId() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Full Name As ID",
style: GoogleFonts.poppins(
fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black),
),
SizedBox(height: 5),
CustomTextFieldUserTravellerWrapper(
isFocused: false,
isDesktop: widget.isDesktop,
child: SizedBox(
height: 40,
child: TextField(
style: GoogleFonts.poppins(fontSize: 12, color: Colors.black),
controller: controllers["full_name_as_id"],
enabled: !widget.isViewMode,
onChanged: (value) {
_clearError("full_name_as_id");
},
decoration: InputDecoration(
labelText: "FullName As ID",
labelStyle:
GoogleFonts.poppins(fontSize: 12, color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16),
),
),
),
),
],
);
}
//------------------------Preferences Domestic ---------------------------------------
Widget _buildPreferencesDomestic(bool isDesktop) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
height: 10,
),
_buildDomesticDetailDataRow1(widget.isDesktop),
],
);
}
Widget _buildDomesticDetailDataRow1(bool isDesktop) {
return Container(
color: Colors.white,
child: widget.isDesktop
? Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
buildDomesticSeatType(),
SizedBox(width: 15),
buildDomesticMealPref(),
SizedBox(width: 15),
buildDomesticAddtnlPref(),
],
)
: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
buildDomesticSeatType(),
SizedBox(height: 8),
buildDomesticMealPref(),
SizedBox(height: 8), // Vertical space
buildDomesticAddtnlPref(),
],
),
);
}
Widget buildDomesticSeatType() {
final List<Map<String, dynamic>> seatOptions = [
{"dropdown_value": "Aisle"},
{"dropdown_value": "Window"},
{"dropdown_value": "Middle"},
];
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Seat Preference",
style: GoogleFonts.poppins(
fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black),
),
SizedBox(height: 5),
CustomTextFieldUserTravellerWrapper(
width: widget.isDesktop
? MediaQuery.of(context).size.width * 0.15
: null,
isFocused: false,
isDesktop: widget.isDesktop,
child: SizedBox(
height: 35,
width: double.infinity,
child: DropdownSearch<Map<String, dynamic>>(
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: const DropDownDecoratorProps(
dropdownSearchDecoration: InputDecoration(
border: InputBorder.none,
contentPadding:
EdgeInsets.symmetric(horizontal: 10, vertical: 5),
),
),
dropdownButtonProps: const DropdownButtonProps(
icon: Icon(Icons.arrow_drop_down),
),
dropdownBuilder: (context, selectedItem) {
if (selectedItem == null || selectedItem.isEmpty) {
return Text("Select Seat", // fallback text
style: GoogleFonts.poppins(
fontSize: 12, color: Colors.grey));
}
return Text(selectedItem['dropdown_value'] ?? '',
style:
GoogleFonts.poppins(fontSize: 12, color: Colors.black));
},
items: seatOptions,
selectedItem: selectedDomesticSeat == null
? null
: {"dropdown_value": selectedDomesticSeat},
onChanged: widget.isViewMode
? null
: (Map<String, dynamic>? newItem) {
if (newItem != null) {
setState(() {
selectedDomesticSeat = newItem['dropdown_value'];
});
}
},
),
),
),
],
);
}
Widget buildDomesticMealPref() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Meal Preference",
style: GoogleFonts.poppins(
fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black),
),
SizedBox(height: 5),
CustomTextFieldUserTravellerWrapper(
width: widget.isDesktop
? MediaQuery.of(context).size.width * 0.15
: null,
isFocused: false,
isDesktop: widget.isDesktop,
child: SizedBox(
height: 40,
child: TextField(
style: GoogleFonts.poppins(fontSize: 12, color: Colors.black),
controller: controllers["d_meal_pref"],
enabled: !widget.isViewMode,
onChanged: (value) {
_clearError("d_meal_pref");
},
decoration: InputDecoration(
labelText: "Meal Preference",
labelStyle:
GoogleFonts.poppins(fontSize: 12, color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16),
),
),
),
),
],
);
}
Widget buildDomesticAddtnlPref() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Additional Information",
style: GoogleFonts.poppins(
fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black),
),
SizedBox(height: 5),
CustomTextFieldUserTravellerWrapper(
width:
widget.isDesktop ? MediaQuery.of(context).size.width * 0.4 : null,
isFocused: false,
isDesktop: widget.isDesktop,
child: SizedBox(
height: 40,
child: TextField(
style: GoogleFonts.poppins(fontSize: 12, color: Colors.black),
controller: controllers["d_additonal_Info"],
enabled: !widget.isViewMode,
onChanged: (value) {
_clearError("d_additonal_Info");
},
decoration: InputDecoration(
labelText: "Please Enter",
labelStyle:
GoogleFonts.poppins(fontSize: 12, color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16),
),
),
),
),
],
);
}
//------------------------Preferences International ---------------------------------------
Widget _buildPreferencesInternational(bool isDesktop) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
height: 10,
),
_buildInternationalDetailDataRow1(widget.isDesktop),
],
);
}
Widget _buildInternationalDetailDataRow1(bool isDesktop) {
return Container(
color: Colors.white,
child: widget.isDesktop
? Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
buildInternationalSeatType(),
SizedBox(width: 15),
buildInternationalMealPref(),
SizedBox(width: 15),
buildInternationalAddtnlPref(),
],
)
: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
buildInternationalSeatType(),
SizedBox(height: 8),
buildInternationalMealPref(),
SizedBox(height: 8), // Vertical space
buildInternationalAddtnlPref(),
],
),
);
}
Widget buildInternationalSeatType() {
final List<Map<String, dynamic>> seatOptionsInt = [
{"dropdown_value": "Aisle"},
{"dropdown_value": "Window"},
{"dropdown_value": "Middle"},
];
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Seat Preference",
style: GoogleFonts.poppins(
fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black),
),
SizedBox(height: 5),
CustomTextFieldUserTravellerWrapper(
width: widget.isDesktop
? MediaQuery.of(context).size.width * 0.15
: null,
isFocused: false,
isDesktop: widget.isDesktop,
child: SizedBox(
height: 35,
width: double.infinity,
child: DropdownSearch<Map<String, dynamic>>(
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: const DropDownDecoratorProps(
dropdownSearchDecoration: InputDecoration(
border: InputBorder.none,
contentPadding:
EdgeInsets.symmetric(horizontal: 10, vertical: 5),
),
),
// selectedItem: purposeList.cast<Map<String, dynamic>>().firstWhere(
// (item) => item['dropdown_key'] == _selectedIsBillable,
// orElse: () => {},
// ),
dropdownButtonProps: const DropdownButtonProps(
icon: Icon(Icons.arrow_drop_down),
),
dropdownBuilder: (context, selectedItem) {
if (selectedItem == null || selectedItem.isEmpty) {
return Text("Select Seat", // fallback text
style: GoogleFonts.poppins(
fontSize: 12, color: Colors.grey));
}
return Text(selectedItem['dropdown_value'] ?? '',
style:
GoogleFonts.poppins(fontSize: 12, color: Colors.black));
},
items: seatOptionsInt,
selectedItem: selectedIntenationalSeat == null
? null
: {"dropdown_value": selectedIntenationalSeat},
onChanged: widget.isViewMode
? null
: (Map<String, dynamic>? newItem) {
if (newItem != null) {
setState(() {
selectedIntenationalSeat = newItem['dropdown_value'];
});
}
},
),
),
),
],
);
}
Widget buildInternationalMealPref() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Meal Preference",
style: GoogleFonts.poppins(
fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black),
),
SizedBox(height: 5),
CustomTextFieldUserTravellerWrapper(
width: widget.isDesktop
? MediaQuery.of(context).size.width * 0.15
: null,
isFocused: false,
isDesktop: widget.isDesktop,
child: SizedBox(
height: 40,
child: TextField(
style: GoogleFonts.poppins(fontSize: 12, color: Colors.black),
controller: controllers["i_meal_pref"],
enabled: !widget.isViewMode,
onChanged: (value) {
_clearError("i_meal_pref");
},
decoration: InputDecoration(
labelText: "Meal Preference",
labelStyle:
GoogleFonts.poppins(fontSize: 12, color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16),
),
),
),
),
],
);
}
Widget buildInternationalAddtnlPref() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Additional Information",
style: GoogleFonts.poppins(
fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black),
),
SizedBox(height: 5),
CustomTextFieldUserTravellerWrapper(
width:
widget.isDesktop ? MediaQuery.of(context).size.width * 0.4 : null,
isFocused: false,
isDesktop: widget.isDesktop,
child: SizedBox(
height: 40,
child: TextField(
style: GoogleFonts.poppins(fontSize: 12, color: Colors.black),
controller: controllers["i_additonal_Info"],
enabled: !widget.isViewMode,
onChanged: (value) {
_clearError("i_additonal_Info");
},
decoration: InputDecoration(
labelText: "Please Enter",
labelStyle:
GoogleFonts.poppins(fontSize: 12, color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16),
),
),
),
),
],
);
}
//--------------------------Other Details-------------------------------
Widget _buildOtherDetails(bool isDesktop) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
height: 10,
),
buildEmergencyContactNum(),
],
);
}
Widget buildEmergencyContactNum() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Emergency Contact Number",
style: GoogleFonts.poppins(
fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black),
),
SizedBox(height: 5),
CustomTextFieldUserTravellerWrapper(
isFocused: false,
isDesktop: widget.isDesktop,
child: SizedBox(
height: 40,
child: TextField(
style: GoogleFonts.poppins(fontSize: 12, color: Colors.black),
controller: controllers["emergency_contact"],
inputFormatters: [
FilteringTextInputFormatter.allow(RegExp(r'[0-9\s]')),
],
// maxLength: 10,
enabled: !widget.isViewMode,
onChanged: (value) {
_clearError("emergency_contact");
},
decoration: InputDecoration(
// counterText: '',
labelText: "Contact Number",
labelStyle:
GoogleFonts.poppins(fontSize: 12, color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16),
),
),
),
),
],
);
}
//-------------------------Forex Prepaid Card Number------------------------
Widget _buildForexDetails(bool isDesktop) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
height: 10,
),
_buildForexDetailDataRow1(isDesktop),
],
);
}
Widget _buildForexDetailDataRow1(bool isDesktop) {
return Container(
color: Colors.white,
child: widget.isDesktop
? Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
buildForexCardNumber(),
SizedBox(width: 15),
buildForexExpiryDate(),
],
)
: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
buildForexCardNumber(),
SizedBox(height: 8),
buildForexExpiryDate()
],
),
);
}
Widget buildForexCardNumber() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Forex Pre-Paid Card Number",
style: GoogleFonts.poppins(
fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black),
),
SizedBox(height: 5),
CustomTextFieldUserTravellerWrapper(
isFocused: false,
isDesktop: widget.isDesktop,
child: SizedBox(
height: 40,
child: TextField(
style: GoogleFonts.poppins(fontSize: 12, color: Colors.black),
controller: controllers["forex_card_num"],
enabled: !widget.isViewMode,
onChanged: (value) {
_clearError("forex_card_num");
},
decoration: InputDecoration(
labelText: "Card Number",
labelStyle:
GoogleFonts.poppins(fontSize: 12, color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16),
),
),
),
),
],
);
}
Widget buildForexExpiryDate() {
DateTime? _selectedExpiryDate;
Future<void> _selectExpiryDate(BuildContext context) async {
DateTime now = DateTime.now();
DateTime today = DateTime(now.year, now.month, now.day);
DateTime? pickedDate = await showDatePicker(
context: context,
initialDate:
_selectedExpiryDate != null && _selectedExpiryDate!.isAfter(today)
? _selectedExpiryDate!
: today,
firstDate: DateTime(1900),
lastDate: DateTime(2100),
);
if (pickedDate != null && pickedDate != _selectedExpiryDate) {
setState(() {
_selectedExpiryDate = pickedDate;
// controllers["forex_expiry_date"]?.text =
// DateFormat('yyyy-MM-dd').format(pickedDate);
controllers["forex_expiry_date"]?.text =
DateFormat('dd-MM-yyyy').format(pickedDate);
});
}
}
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Forex Expiry Date",
style: GoogleFonts.poppins(
fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black),
),
SizedBox(height: 5),
CustomTextFieldUserTravellerWrapper(
isFocused: false,
isDesktop: widget.isDesktop,
child: SizedBox(
height: 40,
child: GestureDetector(
onTap: widget.isViewMode
? null
: () async {
await _selectExpiryDate(context);
},
child: AbsorbPointer(
child: TextField(
controller: controllers["forex_expiry_date"],
style: GoogleFonts.poppins(fontSize: 12, color: Colors.black),
decoration: InputDecoration(
labelText: "Select Date",
labelStyle: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w400,
color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16),
suffixIcon: Icon(Icons.calendar_today,
size: 16, color: Color(0xFF8B8FB2)),
),
),
),
),
),
),
],
);
}
//-------------------------Frequent Flier Information -------------------------------
Widget _buildFrequentFlierInfo(bool isDesktop) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
...frequentFlierEntries
.where((e) => e["is_active"] != "0")
.map((e) => _buildFrequentAirlineDataRow1(e, isDesktop)),
IconForAirlineMem(),
],
);
}
Widget _buildFrequentAirlineDataRow1(
Map<String, dynamic> entry, bool isDesktop) {
int localId = entry["local_id"];
return Container(
color: Colors.white,
padding: const EdgeInsets.only(top: 8.0),
child: widget.isDesktop
? Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
buildAirline(entry),
SizedBox(width: 25),
buildFrequentFlierInformation(entry),
SizedBox(width: 15),
IconButton(
icon: Icon(
Icons.cancel,
color: Colors.red,
size: 15,
),
onPressed: () {
setState(() {
removeFrequentFlierEntry(entry);
});
},
),
],
)
: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
buildAirline(entry),
SizedBox(height: 8),
buildFrequentFlierInformation(entry),
SizedBox(width: 15),
IconButton(
icon: Icon(
Icons.cancel,
color: Colors.red,
size: 15,
),
onPressed: () {
setState(() {
removeFrequentFlierEntry(entry);
});
},
),
],
),
);
}
Widget buildAirline(Map<String, dynamic> entry) {
late Map<String, String> countryMap; // Mapping country_code -> country_name
late List<String> countryCodes; // List of country codes
// countryList = [];
bool hasAirlineCountryData =
apiAirlineCountryData == null || apiAirlineCountryData!.isEmpty;
countryList = apiAirlineCountryData!;
print("TestcountryList2 - $countryList");
countryMap = {
for (var country in countryList)
country['Code'] as String: '${country['City']} - ${country['Airport']}'
};
countryCodes = countryMap.keys.toList();
// setState(() {
// countryMap = tempCountryMap; // Update the map
// isCountryLoading = false;
// });
// Map country codes to country names
// countryMap = {
//
//
// for (var item in countryList)
// item['country_code'] as String: item['country_name'] as String
// };
// Extract only country codes for processing
//--------------------------
// String? selectedCountry = entry['airline'];
String? selectedCode = entry['airline'];
String? selectedText =
selectedCode != null ? countryMap[selectedCode] : null;
print("selectedCode: $selectedCode");
print("selectedText: $selectedText");
print(
"items.contains(selectedText): ${countryMap.values.contains(selectedText)}");
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Airline",
style: GoogleFonts.poppins(
fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black),
),
SizedBox(height: 5),
CustomTextFieldUserTravellerWrapper(
width: widget.isDesktop
? MediaQuery.of(context).size.width * 0.17
: null,
isFocused: false,
isDesktop: widget.isDesktop,
child: SizedBox(
height: 40,
child: hasAirlineCountryData
? CircularProgressIndicator()
: DropdownSearch<String>(
// selectedItem: countryMap[selectedCountry],
// selectedItem: entry['airline'] != null
// ? countryMap[entry['airline']]
// : null,
// selectedItem: countryMap.containsKey(entry['airline'])
// ? countryMap[entry['airline']]
// : null,
// selectedItem: countryMap[selectedCode],
// selectedItem: countryMap.containsKey(selectedCode)
// ? countryMap[selectedCode]
// : null,
selectedItem: (entry["airline"] != null &&
countryMap.containsKey(entry["airline"]))
? countryMap[entry["airline"]]
: null,
popupProps: PopupProps.menu(
showSearchBox: true, // Enables search functionality
menuProps: const MenuProps(
backgroundColor: Colors.white,
),
constraints: BoxConstraints(maxHeight: 250),
itemBuilder: (context, item, isSelected) => Padding(
padding: const EdgeInsets.symmetric(
horizontal: 8.0, vertical: 6.0),
child: Text(
item,
style: GoogleFonts.poppins(fontSize: 11.5),
),
),
searchFieldProps: TextFieldProps(
decoration: InputDecoration(
hintText: "Search Country...",
hintStyle: GoogleFonts.poppins(fontSize: 11.5),
contentPadding: EdgeInsets.symmetric(horizontal: 10),
),
),
),
items: countryMap.values.toList(),
dropdownDecoratorProps: DropDownDecoratorProps(
dropdownSearchDecoration: InputDecoration(
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(
horizontal: 1,
),
),
),
dropdownBuilder: (context, selectedItem) => Align(
// Center-align selected item
alignment: Alignment.centerLeft,
child: Text(
selectedItem ?? "Select Country",
style: TextStyle(fontSize: 12),
),
),
onChanged: (String? newValue) {
setState(() {
// Find the country_code based on selected country_name
// selectedCountry = countryMap.entries
// .firstWhere((entry) => entry.value == newValue)
// .key;
final selectedCode = countryMap.entries
.firstWhere((entry) => entry.value == newValue)
.key;
entry['airline'] = selectedCode;
// final selectedCode = countryMap.entries
// .firstWhere((entry) => entry.value == newValue)
// .key;
// entry['airline'] = selectedCode;
});
},
),
),
),
// CustomTextFieldUserTravellerWrapper(
// width: widget.isDesktop
// ? MediaQuery.of(context).size.width * 0.15
// : null,
// isFocused: false,
// isDesktop: widget.isDesktop,
// child: SizedBox(
// height: 40,
// child: TextField(
// style: GoogleFonts.poppins(fontSize: 12, color: Colors.black),
// controller: entry["controller_airline"],
// enabled: !widget.isViewMode,
// onChanged: (value) {
// _clearError("first_name");
// },
// decoration: InputDecoration(
// labelText: "Airline",
// labelStyle:
// GoogleFonts.poppins(fontSize: 12, color: Colors.grey),
// floatingLabelBehavior: FloatingLabelBehavior.never,
// border: InputBorder.none,
// contentPadding: EdgeInsets.symmetric(vertical: 16),
// ),
// ),
// ),
// ),
],
);
}
Widget buildFrequentFlierInformation(
Map<String, dynamic> entry,
) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Frequent Flyer Information",
style: GoogleFonts.poppins(
fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black),
),
SizedBox(height: 5),
CustomTextFieldUserTravellerWrapper(
isFocused: false,
isDesktop: widget.isDesktop,
child: SizedBox(
height: 40,
child: TextField(
style: GoogleFonts.poppins(fontSize: 12, color: Colors.black),
controller: entry["controller_flier_number"],
enabled: !widget.isViewMode,
onChanged: (value) {
_clearError("first_name");
},
decoration: InputDecoration(
labelText: "Frequent Flyer Information",
labelStyle:
GoogleFonts.poppins(fontSize: 12, color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16),
),
),
),
),
],
);
}
Widget IconForAirlineMem() {
return Container(
// height: 60, // must define height
alignment: Alignment.bottomRight,
child: IconButton(
icon: Icon(
Icons.add_circle,
color: Color(0xFF114D8B),
size: 30,
),
onPressed: () => addFrequentFlierEntry(),
tooltip: "Frequent Flyer Information",
),
);
}
//-------------------------Frequent Loyalty MemberShip Hotels -------------------------------
Widget _buildHotelLoyalty(bool isDesktop) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// ...hotelLoyaltyEntries.map((entry) {
// return _buildFrequentLoyaltyDataRow1(entry, isDesktop);
// }),
...hotelLoyaltyEntries
.where((e) => e["is_active"] != "0")
.map((e) => _buildFrequentLoyaltyDataRow1(e, isDesktop)),
IconForLoyaltyMem(),
],
);
}
Widget _buildFrequentLoyaltyDataRow1(
Map<String, dynamic> entry, bool isDesktop) {
int localId = entry["local_id"];
return Container(
padding: const EdgeInsets.only(top: 8.0),
color: Colors.white,
child: widget.isDesktop
? Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
buildHotel(entry),
SizedBox(width: 25),
buildHotelMembershipNum(entry),
IconButton(
icon: Icon(
Icons.cancel,
color: Colors.red,
size: 15,
),
onPressed: () {
setState(() {
// removeHotelLoyaltyEntry(localId);
removeHotelLoyaltyEntry(entry);
});
},
),
],
)
: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
buildHotel(entry),
SizedBox(height: 8),
buildHotelMembershipNum(entry),
IconButton(
icon: Icon(
Icons.cancel,
color: Colors.red,
size: 15,
),
onPressed: () {
setState(() {
// removeHotelLoyaltyEntry(localId);
removeHotelLoyaltyEntry(entry);
});
},
),
],
),
);
}
Widget buildHotel(Map<String, dynamic> entry) {
late Map<String, String> countryMap; // Mapping country_code -> country_name
late List<String> countryCodes; // List of country codes
bool hasAirlineCountryData =
apiHotelsData == null || apiHotelsData!.isEmpty;
countryList = apiHotelsData!;
print("TestcountryList2 - $countryList");
countryMap = {
for (var country in countryList)
country['hotel_id'] as String: '${country['hotel_name']}'
};
countryCodes = countryMap.keys.toList();
String? selectedCode = entry['hotel_id'];
// String? selectedCode = entry['hotel_id'];
String? selectedText =
selectedCode != null ? countryMap[selectedCode] : null;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Hotel",
style: GoogleFonts.poppins(
fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black),
),
SizedBox(height: 5),
CustomTextFieldUserTravellerWrapper(
// width: widget.isDesktop
// ? MediaQuery.of(context).size.width * 0.15
// : null,
isFocused: false,
isDesktop: widget.isDesktop,
child: SizedBox(
height: 40,
child: hasAirlineCountryData
? CircularProgressIndicator()
: DropdownSearch<String>(
selectedItem: (entry["hotel_id"] != null &&
countryMap.containsKey(entry["hotel_id"]))
? countryMap[entry["hotel_id"]]
: null,
popupProps: PopupProps.menu(
showSearchBox: true, // Enables search functionality
menuProps: const MenuProps(
backgroundColor: Colors.white,
),
constraints: BoxConstraints(maxHeight: 250),
itemBuilder: (context, item, isSelected) => Padding(
padding: const EdgeInsets.symmetric(
horizontal: 8.0, vertical: 6.0),
child: Text(
item,
style: GoogleFonts.poppins(fontSize: 11.5),
),
),
searchFieldProps: TextFieldProps(
decoration: InputDecoration(
hintText: "Search Hotels...",
hintStyle: GoogleFonts.poppins(fontSize: 11.5),
contentPadding: EdgeInsets.symmetric(horizontal: 10),
),
),
),
items: countryMap.values.toList(),
dropdownDecoratorProps: DropDownDecoratorProps(
dropdownSearchDecoration: InputDecoration(
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(
horizontal: 1,
),
),
),
dropdownBuilder: (context, selectedItem) => Align(
// Center-align selected item
alignment: Alignment.centerLeft,
child: Text(
selectedItem ?? "Select Hotels",
style: GoogleFonts.poppins(fontSize: 11),
),
),
onChanged: (String? newValue) {
setState(() {
// Find the country_code based on selected country_name
// selectedCountry = countryMap.entries
// .firstWhere((entry) => entry.value == newValue)
// .key;
final selectedCode = countryMap.entries
.firstWhere((entry) => entry.value == newValue)
.key;
print("SelectedHotelId - $selectedCode");
entry['hotel_id'] = selectedCode;
// entry["controller_hotel"] = newValue;
entry['hotel_name'] = newValue;
// final selectedCode = countryMap.entries
// .firstWhere((entry) => entry.value == newValue)
// .key;
// entry['airline'] = selectedCode;
});
},
),
),
),
],
);
}
Widget buildHotelMembershipNum(Map<String, dynamic> entry) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Hotel Membership Number",
style: GoogleFonts.poppins(
fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black),
),
SizedBox(height: 5),
CustomTextFieldUserTravellerWrapper(
isFocused: false,
isDesktop: widget.isDesktop,
child: SizedBox(
height: 40,
child: TextField(
style: GoogleFonts.poppins(fontSize: 12, color: Colors.black),
controller: entry["controller_membership"],
enabled: !widget.isViewMode,
onChanged: (value) {
_clearError("first_name");
},
decoration: InputDecoration(
labelText: "Hotel Membership Number",
labelStyle:
GoogleFonts.poppins(fontSize: 12, color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16),
),
),
),
),
],
);
}
Widget IconForLoyaltyMem() {
return Container(
height: 60, // must define height
alignment: Alignment.bottomRight,
child: IconButton(
icon: Icon(
Icons.add_circle,
color: Color(0xFF114D8B),
size: 30,
),
onPressed: () => addHotelLoyaltyEntry(),
tooltip: "Add Hotel Loyalty Membership",
),
);
}
//-------------------------Visa Details -----------------------------------------
Widget _buildVisaDetails(bool isDesktop) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
height: 10,
),
...visaEntries
.where((e) => e["is_active"] != "0")
.map((e) => _buildVisaDataRow1(e, isDesktop)),
// _buildVisaDataRow1(isDesktop),
IconForVisa()
],
);
}
Widget _buildVisaDataRow1(Map<String, dynamic> entry, bool isDesktop) {
return Container(
padding: const EdgeInsets.only(top: 8),
color: Colors.white,
child: widget.isDesktop
? Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
buildVisaCountry(entry),
SizedBox(width: 25),
buildVisaType(entry),
SizedBox(width: 15),
buildVisaValidFrom(entry),
SizedBox(width: 15),
buildVisaValidUpTo(entry),
IconButton(
icon: Icon(
Icons.cancel,
color: Colors.red,
size: 15,
),
onPressed: () {
setState(() {
removeVisaEntry(entry);
});
},
),
],
)
: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
buildVisaCountry(entry),
SizedBox(height: 8),
buildVisaType(entry),
SizedBox(height: 8),
buildVisaValidFrom(entry),
SizedBox(height: 8),
buildVisaValidUpTo(entry),
IconButton(
icon: Icon(
Icons.cancel,
color: Colors.red,
size: 15,
),
onPressed: () {
setState(() {
// visaRowIds.remove(id);
removeVisaEntry(entry);
});
},
),
],
),
);
}
Widget buildVisaCountry(entry) {
late Map<String, String> countryMap; // Mapping country_code -> country_name
late List<String> countryCodes; // List of country codes
// countryList = [];
countryList = apiCountryData ?? [];
print("TestcountryList1 - $countryList");
// Map country codes to country names
countryMap = {
for (var item in countryList)
item['country_code'] as String: item['country_name'] as String
};
// Extract only country codes for processing
countryCodes = countryMap.keys.toList();
// selectedCountry ??= null;
String? selectedCountry = entry['country_code'];
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Country",
style: GoogleFonts.poppins(
fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black),
),
SizedBox(height: 5),
CustomTextFieldUserTravellerWrapper(
width: widget.isDesktop
? MediaQuery.of(context).size.width * 0.17
: null,
isFocused: false,
isDesktop: widget.isDesktop,
child: SizedBox(
height: 40,
child: DropdownSearch<String>(
selectedItem: countryMap[selectedCountry],
popupProps: PopupProps.menu(
showSearchBox: true, // Enables search functionality
menuProps: const MenuProps(
backgroundColor: Colors.white,
),
// constraints: BoxConstraints(maxHeight: 250),
itemBuilder: (context, item, isSelected) => Padding(
padding: const EdgeInsets.symmetric(
horizontal: 8.0, vertical: 6.0),
child: Text(
item,
style: GoogleFonts.poppins(fontSize: 11.5),
),
),
searchFieldProps: TextFieldProps(
decoration: InputDecoration(
hintText: "Search Country...",
hintStyle: GoogleFonts.poppins(fontSize: 11.5),
contentPadding: EdgeInsets.symmetric(horizontal: 10),
),
),
),
items: countryMap.values.toList(),
dropdownDecoratorProps: DropDownDecoratorProps(
dropdownSearchDecoration: InputDecoration(
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(
horizontal: 1,
),
),
),
dropdownBuilder: (context, selectedItem) => Align(
// Center-align selected item
alignment: Alignment.centerLeft,
child: Text(
selectedItem ?? "Select Country",
style: TextStyle(fontSize: 12),
),
),
onChanged: (String? newValue) {
setState(() {
// Find the country_code based on selected country_name
// selectedCountry = countryMap.entries
// .firstWhere((entry) => entry.value == newValue)
// .key;
final selectedCode = countryMap.entries
.firstWhere((entry) => entry.value == newValue)
.key;
entry['country_code'] = selectedCode;
});
},
),
),
),
],
);
}
Widget buildVisaType(entry) {
if (apiData == null || apiData?['visa_type_of_visa'] == null) {
return Center(
child: Transform.scale(
scale: 0.5,
child: CircularProgressIndicator(),
),
);
}
// List<dynamic> purposeList = apiData?['visa_type_of_visa'] ?? [];
// List<dynamic> purposeList = [];
List<dynamic> purposeList = apiData?['visa_type_of_visa'];
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
// selectedPurpose ??=
// dropdownItems.isNotEmpty ? dropdownItems.first.value : null;
// String? selectedPurpose = entry['visa_type_id'];
// 3. Selected value
String? selectedPurpose = entry['visa_type_id']?.toString();
// 4. Avoid crash if selected value doesn't match any item
if (!dropdownItems.any((item) => item.value == selectedPurpose)) {
selectedPurpose = null;
}
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Visa Type",
style: GoogleFonts.poppins(
fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black),
),
SizedBox(height: 5),
CustomTextFieldUserTravellerWrapper(
width: widget.isDesktop
? MediaQuery.of(context).size.width * 0.17
: null,
isFocused: false,
isDesktop: widget.isDesktop,
child: SizedBox(
height: 40,
child: DropdownButtonFormField<String>(
value: selectedPurpose,
style: TextStyle(fontSize: 12),
decoration: InputDecoration(
border: InputBorder.none,
contentPadding:
EdgeInsets.symmetric(horizontal: 10), // Proper padding
),
hint: Text("Select"),
onChanged: purposeList.isNotEmpty
? (newValue) {
setState(() {
// selectedPurpose = newValue;
entry['visa_type_id'] = newValue;
});
print(
"Updating form data: Flight -> trip_type -> ${newValue ?? ""}");
}
: null,
items: dropdownItems,
),
),
),
],
);
}
Widget buildVisaValidFrom(Map<String, dynamic> entry) {
DateTime? _selectedCheckOutDate;
TimeOfDay? _selectedCheckOutTime;
Future<void> _selectValidFromDate(BuildContext context) async {
DateTime now = DateTime.now();
DateTime today = DateTime(now.year, now.month, now.day);
// Parse date from notifier if available, else use today
DateTime initialDate = today;
// Use previously selected date if valid
if (_selectedCheckOutDate != null &&
_selectedCheckOutDate!.isAfter(today)) {
initialDate = _selectedCheckOutDate!;
}
final pickedDate = await showDatePicker(
context: context,
initialDate: initialDate,
firstDate: initialDate,
lastDate: DateTime(2100),
);
if (pickedDate != null && pickedDate != _selectedCheckOutDate) {
setState(() {
_selectedCheckOutDate = pickedDate;
// _dateController.text = DateFormat('yyyy-MM-dd').format(pickedDate);
entry["controller_valid_from"].text =
DateFormat('dd-MM-yyyy').format(pickedDate);
});
}
}
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"ValidFrom",
style: GoogleFonts.poppins(
fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black),
),
SizedBox(height: 5),
CustomTextFieldUserTravellerWrapper(
width: widget.isDesktop
? MediaQuery.of(context).size.width * 0.15
: null,
isFocused: false,
isDesktop: widget.isDesktop,
child: SizedBox(
height: 40,
child: GestureDetector(
onTap: () async {
await _selectValidFromDate(context);
},
child: AbsorbPointer(
child: TextField(
// focusNode: _dateFocusNode,
controller: entry["controller_valid_from"],
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),
),
),
),
),
),
),
],
);
}
Widget buildVisaValidUpTo(Map<String, dynamic> entry) {
DateTime? _selectedCheckOutDate;
TimeOfDay? _selectedCheckOutTime;
Future<void> _selectValidUpToDate(BuildContext context) async {
DateTime now = DateTime.now();
DateTime today = DateTime(now.year, now.month, now.day);
// Parse date from notifier if available, else use today
DateTime initialDate = today;
// Use previously selected date if valid
if (_selectedCheckOutDate != null &&
_selectedCheckOutDate!.isAfter(today)) {
initialDate = _selectedCheckOutDate!;
}
final pickedDate = await showDatePicker(
context: context,
initialDate: initialDate,
firstDate: initialDate,
lastDate: DateTime(2100),
);
if (pickedDate != null && pickedDate != _selectedCheckOutDate) {
setState(() {
_selectedCheckOutDate = pickedDate;
// _dateController.text = DateFormat('yyyy-MM-dd').format(pickedDate);
entry["controller_valid_upto"].text =
DateFormat('dd-MM-yyyy').format(pickedDate);
});
}
}
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Valid UpTo",
style: GoogleFonts.poppins(
fontSize: 12, fontWeight: FontWeight.w400, color: Colors.black),
),
SizedBox(height: 5),
CustomTextFieldUserTravellerWrapper(
width: widget.isDesktop
? MediaQuery.of(context).size.width * 0.15
: null,
isFocused: false,
isDesktop: widget.isDesktop,
child: SizedBox(
height: 40,
child: GestureDetector(
onTap: () async {
await _selectValidUpToDate(context);
},
child: AbsorbPointer(
child: TextField(
// focusNode: _dateFocusNode,
// controller: _dateController,
controller: entry["controller_valid_upto"],
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),
),
),
),
),
),
),
],
);
}
Widget IconForVisa() {
return Container(
height: 60, // must define height
alignment: Alignment.bottomRight,
child: IconButton(
icon: Icon(
Icons.add_circle,
color: Color(0xFF114D8B),
size: 30,
),
onPressed: () => addVisaEntry(),
tooltip: "Add Visa Details",
),
);
}
}