3226 lines
107 KiB
Dart
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",
|
|
),
|
|
);
|
|
}
|
|
}
|