// 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 controllers; final Map errorMessages; final bool isViewMode; final Map? travelDetails; final String? passportFileUrlFromApi; final String? userIdApi; final String fullName; const TravellerDetails({ Key? key, required this.controllers, required this.errorMessages, required this.isDesktop, required this.isViewMode, this.travelDetails, this.passportFileUrlFromApi, this.userIdApi, required this.fullName, }) : super(key: key); @override TravellerDetailsState createState() => TravellerDetailsState(); } class TravellerDetailsState extends State { final ApiService apiService = ApiService(); html.File? passportFile; late List isExpandedList; int? expandedIndex; bool isCountryLoading = true; Map errorMessages = {}; late final userId; String? selectedFileNames; Uint8List? passportDocumentBytes; String? passportFileUrlFromApi; String? _selectedTripType; // String? selectedPurpose; // String? selectedCountry; String? selectedLocalIdType; List countryList = []; String? selectedDomesticSeat; String? selectedIntenationalSeat; String? SelectedIdType; Map airlineControllers = {}; Map flierNumberControllers = {}; List> hotelLoyaltyEntries = []; int _rowhotelCounter = 1; List> frequentFlierEntries = []; int _rowFlierCounter = 1; List> visaEntries = []; int _rowVisaCounter = 1; Map countryMap = {}; late List? apiCountryData; late List? apiHotelsData; late List? apiAirlineCountryData; Map? apiData; final Map controllers = {}; Map focusNodes = {}; Map focusStates = {}; bool _iSeatPrefFocus = false; bool _dSeatPrefFocus = false; final FocusNode _iSeatPrefFocusNode = FocusNode(); final FocusNode _dSeatPrefFocusNode = FocusNode(); final List 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 dataHeader = [ "Fname", "Mname", "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", ]; // Color? layoutColor; Color layoutColor = Colors.grey; Color? bodyColor; @override void initState() { super.initState(); // userId = getUserId(); apiCountryData = null; apiHotelsData = []; apiAirlineCountryData = []; apiData = null; for (var field in dataHeader) { controllers[field] = TextEditingController(); focusNodes["${field}FocusNode"] = FocusNode(); focusStates["${field}Focused"] = false; } for (var key in focusNodes.keys) { _addFocusListener(focusNodes[key]!, (focus) { setState(() { focusStates[key.replaceFirst("FocusNode", "Focused")] = focus; }); }); } fetchCountries(); fetchHotels(); loadCountryList(); fetchApiData(); // Delay adding the row until after the first frame WidgetsBinding.instance.addPostFrameCallback((_) { // focusNodes["FnameFocusNode"]?.requestFocus(); setState(() { addHotelLoyaltyEntry(); addFrequentFlierEntry(); addVisaEntry(); loadInitialData(); }); updateTravel(); print("Trying to focus Fname..."); final node = focusNodes["FnameFocusNode"]; if (node != null) { print("Node exists, requesting focus"); node.requestFocus(); } }); _addFocusListener(_iSeatPrefFocusNode, (focus) => _iSeatPrefFocus = focus); _addFocusListener(_dSeatPrefFocusNode, (focus) => _dSeatPrefFocus = focus); // _addFocusListener(_groupFocusNode, (focus) => _groupFocus = focus); // _addFocusListener(_firstApprovalFocusNode, (focus) => _firstApprovalFocus = focus); // _addFocusListener(_secondApprovalFocusNode, (focus) => _secondApprovalFocus = focus); // _addFocusListener(_thirdApprovalFocusNode, (focus) => _thirdApprovalFocus = focus); // _addFocusListener(_exceptionalApprovalFocusNode, (focus) => _exceptionalApprovalFocus = focus); // _addFocusListener(_delegateFocusNode, (focus) => _delegateFocus = focus); // _addFocusListener(_startDateFocusNode, (focus) => _startDateFocus = focus); // _addFocusListener(_endDateFocusNode, (focus) => _endDateFocus = focus); } void _addFocusListener(FocusNode node, Function(bool) updateState) { node.addListener(() { setState(() { updateState(node.hasFocus); }); }); } void loadInitialData() async { String? layoutString = await getLayoutColor(); String? bodyStringColor = await getBodyColor(); setState(() { layoutColor = layoutString != null ? Color(int.parse(layoutString)) : Colors.redAccent; bodyColor = bodyStringColor != null ? Color(int.parse(bodyStringColor)) : Colors.white; }); } bool boolValidation() { print("travel validation start"); print("visaEntries - $visaEntries"); final passportText = controllers["passportNumber"]?.text ?? ""; if (_selectedTripType == "Indian") { // Match exactly 8 alphanumeric characters final isValid = RegExp(r'^[a-zA-Z0-9]{8}$').hasMatch(passportText); if (!isValid) { setState(() { errorMessages["passport_number"] = "Enter 8 characters"; }); return false; } else { setState(() { errorMessages.remove("passport_number"); }); } } final format = DateFormat("dd-MM-yyyy"); DateTime? start_Date; DateTime? end_Date; try { if (controllers["dateOfIssue"]?.text != null && controllers["dateOfIssue"]!.text.isNotEmpty) { start_Date = format.parse(controllers["dateOfIssue"]!.text); } if (controllers["dateOfExpiry"]?.text != null && controllers["dateOfExpiry"]!.text.isNotEmpty) { end_Date = format.parse(controllers["dateOfExpiry"]!.text); } if (start_Date != null && end_Date != null && end_Date.isBefore(start_Date)) { setState(() { errorMessages["date_of_expiry"] = "Invalid Date Range"; }); print("End date cannot be earlier than start date"); } } catch (e) { print("Invalid passport date format"); } // Additional validation starts - travelDetailsData passport // DateTime? start_Date = travelDetailsData?['date_of_issue']; // DateTime? end_Date = travelDetailsData?['date_of_expiry']; // // if (start_Date != null && // end_Date != null && // start_Date.toString().isNotEmpty && // end_Date.toString().isNotEmpty) { // try { // final format = DateFormat("dd-MM-yyyy"); // final checkStartDate = format.parse("$start_Date"); // final checkEndDate = format.parse("$end_Date"); // // if (checkEndDate.isBefore(checkStartDate)) { // // return "End date cannot be earlier than start date";; // return; // } // } catch (e) { // // return "End date cannot be earlier than start date"; // return; // // errorMessages["end_date"] = "Invalid date format"; // } // } // ✅ Visa entries: Validate valid_from < valid_upto for each entry for (var entry in visaEntries) { final localId = entry["local_id"]; final fromText = entry["controller_valid_from"]?.text.trim(); final toText = entry["controller_valid_upto"]?.text.trim(); if (fromText != null && toText != null && fromText.isNotEmpty && toText.isNotEmpty) { try { final fromDate = format.parseStrict(fromText); final toDate = format.parseStrict(toText); if (toDate.isBefore(fromDate)) { print("VisaErroer1"); errorMessages["valid_upto${entry['local_id']}"] = "Invalid date range"; // errorMessages["valid_upto_$localId"] = // "Valid Upto can't be before Valid From"; } else { print("VisaErroer2"); // errorMessages.remove("valid_upto$localId"); errorMessages.remove("valid_upto${entry['local_id']}"); } } catch (e) { print("VisaErroer3"); errorMessages["valid_upto${entry['local_id']}"] = "Invalid date format"; } } } if (errorMessages.isNotEmpty) { print("Validation failed with errors: $errorMessages"); setState(() {}); // Refresh UI to show errors return false; } print("travel validation end"); return true; } Future apiCheckDuplicate( String label, String field, String value, String? userId, ) async { try { final response = await apiService.CheckDuplicate( context, label, field, value, userId, ); if (response.isNotEmpty) { _clearError(field); errorMessages[field] = response['message'] ?? "$label Already Exists"; print("Duplicate found: ${response['message']}"); return; } else { _clearError(field); print("No duplicates found."); } } catch (e) { print("Error in checkDuplicate: $e"); } } // Future apiCheckDuplicate( // String label, // String field, // String value, // String? userId, // ) async { // var newField = ""; // try { // final response = await apiService.CheckDuplicate( // label, // newField, // value, // userId, // ); // if (response.isNotEmpty) { // _clearError(field); // errorMessages[field] = response['message'] ?? "$label already exists"; // print("Duplicate found: ${response['message']}"); // return; // } else { // _clearError(field); // print("No duplicates found."); // } // } catch (e) { // print("Error in checkDuplicate: $e"); // } // } void _clearError(String field) { setState(() { 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 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 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 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 get travel_Detials { Map data = { "passport_firstname": controllers["Fname"]?.text, "passport_middlename": controllers["Mname"]?.text, "passport_lastname": controllers["Lname"]?.text, "nationality": _selectedTripType, "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> 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> 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> 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> 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> 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> 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["Fname"]?.text = widget.travelDetails?["passport_firstname"] ?? ""; controllers["Mname"]?.text = widget.travelDetails?["passport_middlename"] ?? ""; controllers["Lname"]?.text = widget.travelDetails?["passport_lastname"] ?? ""; _selectedTripType = widget.travelDetails?["nationality"] ?? ""; 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 fetchCountries() async { try { List countries = await apiService.fetchCountryList(context); setState(() { apiCountryData = countries; }); } catch (e) { print('Error fetching country list: $e'); } } Future fetchHotels() async { try { List countries = await apiService.fetchHotelsList(context); setState(() { apiHotelsData = countries; }); } catch (e) { print('Error fetching country list: $e'); } } Future loadCountryList() async { final newTripType = "2"; final result = await apiService.fetchAirlineList(context); // final result = await apiService.fetchFlightsCountryList(newTripType); print("resultFlight: $result "); setState(() { apiAirlineCountryData = result; }); // print("ResultCountry : $result"); // // // Create a map: Country_Code -> "City, Airport" // Map 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 fetchApiData() async { try { final data = await apiService.fetchMasterDropdown(context); 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, padding: const EdgeInsets.only(top: 8), 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: Row( // children: [ // Text( // "Name as per passport : ", // style: GoogleFonts.poppins( // fontSize: 11, // fontStyle: FontStyle.italic, // letterSpacing: 0.5, // ), // ), // Text( // "${widget.fullName} ", // style: GoogleFonts.poppins( // fontSize: 10, // fontWeight: FontWeight.w600, // letterSpacing: 0.5, // color: Colors.black87, // // fontStyle: FontStyle.italic, // ), // ), // ], // ), child: widget.isDesktop ? Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ buildFirstNameField(), Spacer(), buildMiddleNameField(), Spacer(), buildLastNameField(), SizedBox(width: 25), // Space after Last Name buildNationality(), ], ) : Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ buildFirstNameField(), SizedBox(height: 8), buildMiddleNameField(), 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.w600, color: Color(0xFF575A74), ), ), SizedBox(height: 5), CustomTextFieldUserTravellerWrapper( // isFocused: false, isFocused: focusStates["FnameFocused"] ?? false, isDesktop: widget.isDesktop, child: SizedBox( height: 40, child: TextField( style: GoogleFonts.poppins(fontSize: 12, color: Colors.black), focusNode: focusNodes["FnameFocusNode"], 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), ), ), ), ), ], ); } Widget buildMiddleNameField() { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Middle Name", style: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w600, color: Color(0xFF575A74), ), ), SizedBox(height: 5), CustomTextFieldUserTravellerWrapper( isFocused: focusStates["MnameFocused"] ?? false, isDesktop: widget.isDesktop, child: SizedBox( height: 40, child: TextField( style: GoogleFonts.poppins(fontSize: 12, color: Colors.black), focusNode: focusNodes["MnameFocusNode"], controller: controllers["Mname"], enabled: !widget.isViewMode, onChanged: (value) { _clearError("middle_name"); }, decoration: InputDecoration( labelText: "MiddleName", labelStyle: GoogleFonts.poppins( fontSize: 12, color: Colors.grey, ), floatingLabelBehavior: FloatingLabelBehavior.never, border: InputBorder.none, contentPadding: EdgeInsets.symmetric(vertical: 16), ), ), ), ), ], ); } Widget buildLastNameField() { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Last Name", style: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w600, color: Color(0xFF575A74), ), ), SizedBox(height: 5), CustomTextFieldUserTravellerWrapper( isFocused: focusStates["LnameFocused"] ?? false, width: widget.isDesktop ? MediaQuery.of(context).size.width * 0.13 : null, isDesktop: widget.isDesktop, child: SizedBox( height: 40, child: TextField( style: GoogleFonts.poppins(fontSize: 12, color: Colors.black), focusNode: focusNodes["LnameFocusNode"], 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), ), ), ), ), ], ); } Widget buildNationality() { List nationalityOptions = [ "Indian", "International", "other nationality", ]; return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Nationality", style: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w600, color: Color(0xFF575A74), ), ), SizedBox(height: 5), CustomTextFieldUserTravellerWrapper( // isFocused: false, isFocused: focusStates["nationality"] ?? false, padding: const EdgeInsets.symmetric(horizontal: 0), width: widget.isDesktop ? MediaQuery.of(context).size.width * 0.13 : null, isDesktop: widget.isDesktop, child: SizedBox( height: 40, child: Focus( focusNode: focusNodes["nationalityFocusNode"], onFocusChange: (hasFocus) { setState(() { focusStates["nationalityFocused"] = hasFocus; }); }, child: GestureDetector( onTap: () { // Request focus when user taps focusNodes["nationalityFocusNode"]?.requestFocus(); }, child: DropdownSearch( selectedItem: _selectedTripType, enabled: !widget.isViewMode, popupProps: PopupProps.menu( showSearchBox: false, // Set true if you want search fit: FlexFit.loose, constraints: BoxConstraints(maxHeight: 200), itemBuilder: (context, item, isSelected) => Container( color: Colors.white, padding: EdgeInsets.symmetric( horizontal: 10, vertical: 6, ), child: Text( item, style: GoogleFonts.poppins(fontSize: 11.5), ), ), ), items: nationalityOptions, dropdownDecoratorProps: DropDownDecoratorProps( dropdownSearchDecoration: InputDecoration( // border: InputBorder.none, // contentPadding: EdgeInsets.symmetric(horizontal: 1), border: OutlineInputBorder( borderRadius: BorderRadius.circular(1), borderSide: BorderSide( color: (focusStates["nationalityFocused"] ?? false) ? (layoutColor) : Colors.white, width: 1, ), ), enabledBorder: OutlineInputBorder( borderSide: BorderSide( color: (focusStates["nationalityFocused"] ?? false) ? (layoutColor) : Colors.white, // : const Color(0xFFD6D5E6), width: 1, // const Color(0xFFD6D5E6), ), ), focusedBorder: OutlineInputBorder( borderSide: BorderSide( color: (layoutColor), width: 0.5, ), ), contentPadding: EdgeInsets.symmetric( horizontal: 8, vertical: 1, ), ), ), dropdownBuilder: (context, selectedItem) => Align( alignment: Alignment.centerLeft, child: Text( selectedItem ?? "Select", style: GoogleFonts.poppins( fontSize: 12, color: Colors.black, ), ), ), onChanged: widget.isViewMode ? null : (String? newValue) { setState(() { _selectedTripType = newValue; controllers["passportNumber"]?.clear(); print("_selectedTripType - $_selectedTripType"); }); }, ), ), ), ), ), ], ); } Widget buildPassportNumber() { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Passport Number", style: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w600, color: Color(0xFF575A74), ), ), SizedBox(height: 5), CustomTextFieldUserTravellerWrapper( isFocused: focusStates["passportNumberFocused"] ?? false, isDesktop: widget.isDesktop, child: SizedBox( height: 40, child: TextField( style: GoogleFonts.poppins(fontSize: 12, color: Colors.black), focusNode: focusNodes["passportNumberFocusNode"], controller: controllers["passportNumber"], enabled: !widget.isViewMode, inputFormatters: [ // limit to 8 characters FilteringTextInputFormatter.allow( RegExp(r'[a-zA-Z0-9]'), ), // allow only letters and digits if (_selectedTripType == "Indian") LengthLimitingTextInputFormatter(8), ], onChanged: (value) { _clearError("passport_number"); apiCheckDuplicate( "Passport Number", "passport_number", value, widget.userIdApi, ); }, decoration: InputDecoration( labelText: "Passport Number", labelStyle: GoogleFonts.poppins( fontSize: 12, color: Colors.grey, ), floatingLabelBehavior: FloatingLabelBehavior.never, border: InputBorder.none, contentPadding: EdgeInsets.symmetric(vertical: 16), ), ), ), ), if (errorMessages["passport_number"] != null) ...[ SizedBox(height: 5), // Space before error message Text( errorMessages["passport_number"]!, style: GoogleFonts.poppins(color: Colors.red, fontSize: 12), ), ], ], ); } Widget buildPlaceOfIssue() { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Place of Issue", style: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w600, color: Color(0xFF575A74), ), ), SizedBox(height: 5), CustomTextFieldUserTravellerWrapper( isFocused: focusStates["placeOfIssueFocused"] ?? false, isDesktop: widget.isDesktop, child: SizedBox( height: 40, child: TextField( style: GoogleFonts.poppins(fontSize: 12, color: Colors.black), focusNode: focusNodes["placeOfIssueFocusNode"], 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), ), ), ), ), ], ); } DateTime? _selectedDateOfIssue; DateTime? _selectedDateOfExpiry; Widget buildDateOfIssue() { Future _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: DateTime(1900), // lastDate: DateTime(2100), lastDate: today, initialEntryMode: DatePickerEntryMode.calendarOnly, ); if (pickedDate != null && pickedDate != _selectedDateOfIssue) { setState(() { _selectedDateOfIssue = pickedDate; controllers["dateOfIssue"]?.text = DateFormat( 'dd-MM-yyyy', ).format(pickedDate); // Revalidate expiry if (_selectedDateOfExpiry != null && _selectedDateOfExpiry!.isBefore(_selectedDateOfIssue!)) { errorMessages["date_of_expiry"] = "Invalid Date Range"; } else { errorMessages.remove("date_of_expiry"); } }); } } return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Date of Issue", style: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w600, color: Color(0xFF575A74), ), ), SizedBox(height: 5), CustomTextFieldUserTravellerWrapper( width: widget.isDesktop ? MediaQuery.of(context).size.width * 0.13 : null, isFocused: focusStates["dateOfIssueFocused"] ?? false, // 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"); }); } focusNodes["dateOfIssueFocusNode"]?.requestFocus(); }, child: AbsorbPointer( child: TextField( controller: controllers["dateOfIssue"], focusNode: focusNodes["dateOfIssueFocusNode"], readOnly: true, 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() { Future _selectCheckDateOfExpiry(BuildContext context) async { DateTime now = DateTime.now(); DateTime today = DateTime(now.year, now.month, now.day); // DateTime firstDate = today; DateTime? validFromDate; try { String? fromDateText = controllers["dateOfIssue"]?.text.trim(); print("dateOfIssue From Text: $fromDateText"); if (fromDateText != null && fromDateText.isNotEmpty) { validFromDate = DateFormat('dd-MM-yyyy').parseStrict(fromDateText); print("dateOfIssue Valid From: $validFromDate"); } } catch (e) { print("Error parsing valid from date: $e"); } // Use max(today, validFromDate) as firstDate // DateTime firstDate = today; DateTime firstDate = validFromDate ?? today; // Parse dateOfIssue safely // DateTime firstDate; // try { // firstDate = DateFormat( // "yyyy-MM-dd", // ).parse(controllers["dateOfIssue"]!.text); // } catch (e) { // firstDate = today; // fallback if parsing fails // } DateTime? pickedDate = await showDatePicker( context: context, // initialDate: // _selectedDateOfExpiry != null && // _selectedDateOfExpiry!.isAfter(firstDate) // ? _selectedDateOfExpiry! // : today, initialDate: firstDate, firstDate: firstDate, // firstDate: DateTime(1900), lastDate: DateTime(2100), initialEntryMode: DatePickerEntryMode.calendarOnly, ); if (pickedDate != null) { setState(() { _selectedDateOfExpiry = pickedDate; controllers["dateOfExpiry"]?.text = DateFormat( "dd-MM-yyyy", ).format(pickedDate); if (_selectedDateOfIssue != null && _selectedDateOfExpiry!.isBefore(_selectedDateOfIssue!)) { errorMessages["date_of_expiry"] = "Invalid Date Range"; } else { errorMessages.remove("date_of_expiry"); } }); } // 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.w600, color: Color(0xFF575A74), ), ), SizedBox(height: 5), CustomTextFieldUserTravellerWrapper( // isFocused: false, isFocused: focusStates["dateOfExpiryFocused"] ?? 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"); }); } focusNodes["dateOfExpiryFocusNode"]?.requestFocus(); }, child: AbsorbPointer( child: TextField( controller: controllers["dateOfExpiry"], focusNode: focusNodes["dateOfExpiryFocusNode"], readOnly: true, 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), ), ), ), ), ), ), ), if (errorMessages["date_of_expiry"] != null) ...[ SizedBox(height: 5), // Space before error message Text( errorMessages["date_of_expiry"]!, style: GoogleFonts.poppins(color: Colors.red, fontSize: 10), maxLines: 2, // Allow it to wrap onto two lines overflow: TextOverflow.ellipsis, // Add ellipsis if it still overflows ), ], ], ); } // Widget buildDateOfIssue() { // DateTime? _selectedDateOfIssue; // // Future _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, // firstDate: DateTime(1900), // lastDate: DateTime(2100), // initialEntryMode: DatePickerEntryMode.calendarOnly, // ); // // 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.w600, // color: Color(0xFF575A74), // ), // ), // 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 _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? validFromDate; // // try { // String? fromDateText = controllers["dateOfIssue"]?.text.trim(); // print("dateOfIssue From Text: $fromDateText"); // if (fromDateText!.isNotEmpty) { // validFromDate = DateFormat('dd-MM-yyyy').parseStrict(fromDateText); // print("dateOfIssue Valid From: $validFromDate"); // } // } catch (e) { // print("Error parsing valid from date: $e"); // } // // // Use max(today, validFromDate) as firstDate // // DateTime firstDate = today; // DateTime firstDate = validFromDate ?? today; // // // Parse dateOfIssue safely // // DateTime firstDate; // // try { // // firstDate = DateFormat( // // "yyyy-MM-dd", // // ).parse(controllers["dateOfIssue"]!.text); // // } catch (e) { // // firstDate = today; // fallback if parsing fails // // } // // DateTime? pickedDate = await showDatePicker( // context: context, // // // initialDate: // // _selectedDateOfExpiry != null && // // _selectedDateOfExpiry!.isAfter(firstDate) // // ? _selectedDateOfExpiry! // // : today, // initialDate: firstDate, // firstDate: firstDate, // // firstDate: DateTime(1900), // lastDate: DateTime(2100), // initialEntryMode: DatePickerEntryMode.calendarOnly, // ); // // if (pickedDate != null) { // _selectedDateOfExpiry = pickedDate; // controllers["dateOfExpiry"]?.text = DateFormat( // "dd-MM-yyyy", // ).format(pickedDate); // } // // // 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.w600, // color: Color(0xFF575A74), // ), // ), // 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}'); // }); // } void pickFileWeb() { html.FileUploadInputElement uploadInput = html.FileUploadInputElement(); // Accept PDFs and common image formats uploadInput.accept = '.pdf,.jpg,.jpeg,.png,.gif'; uploadInput.click(); uploadInput.onChange.listen((e) { final file = uploadInput.files!.first; // Ensure the file is PDF or image if (!(file.type.contains("pdf") || file.type.contains("image"))) { print("Error: Not a PDF or image 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('File selected: ${file.name}'); }); } return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Passport Document", style: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w600, color: Color(0xFF575A74), ), ), SizedBox(height: 5), CustomTextFieldUserTravellerWrapper( isFocused: false, isDesktop: widget.isDesktop, child: SizedBox( height: 40, child: GestureDetector( onTap: pickFileWeb, 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.w600, color: Color(0xFF575A74), ), ), 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> 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.w600, color: Color(0xFF575A74), ), ), SizedBox(height: 5), CustomTextFieldUserTravellerWrapper( isFocused: false, isDesktop: widget.isDesktop, child: SizedBox( height: 35, width: double.infinity, child: DropdownSearch>( 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'] ?? 'Select', style: GoogleFonts.poppins( fontSize: 12, color: Colors.black, ), ), ); }, ), dropdownDecoratorProps: const DropDownDecoratorProps( dropdownSearchDecoration: InputDecoration( border: InputBorder.none, hintText: "Select", 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 ", // fallback text style: GoogleFonts.poppins( fontSize: 12, color: Colors.grey, ), ); } return Text( selectedItem['dropdown_value'] ?? "Select ", style: GoogleFonts.poppins(fontSize: 12, color: Colors.black), ); }, items: localIdOptions, selectedItem: selectedLocalIdType == null ? null : {"dropdown_value": selectedLocalIdType}, onChanged: widget.isViewMode ? null : (Map? 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.w600, color: Color(0xFF575A74), ), ), 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> 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.w600, color: Color(0xFF575A74), ), ), SizedBox(height: 5), CustomTextFieldUserTravellerWrapper( width: widget.isDesktop ? MediaQuery.of(context).size.width * 0.15 : null, isFocused: _dSeatPrefFocus, padding: const EdgeInsets.symmetric(horizontal: 0), isDesktop: widget.isDesktop, child: SizedBox( height: 35, width: double.infinity, child: Focus( focusNode: _dSeatPrefFocusNode, onFocusChange: (hasFocus) { setState(() { _dSeatPrefFocus = hasFocus; }); }, child: GestureDetector( onTap: () { // Request focus when user taps _dSeatPrefFocusNode.requestFocus(); }, child: DropdownSearch>( 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'] ?? "Select Seat", style: GoogleFonts.poppins( fontSize: 12, color: Colors.black, ), ), ); }, ), dropdownDecoratorProps: DropDownDecoratorProps( dropdownSearchDecoration: InputDecoration( // border: InputBorder.none, border: OutlineInputBorder( borderRadius: BorderRadius.circular(8), borderSide: BorderSide( color: (_dSeatPrefFocus) ? (layoutColor) : Colors.white, width: 0.5, ), ), enabledBorder: OutlineInputBorder( borderSide: BorderSide( color: (_dSeatPrefFocus) ? (layoutColor) : Colors.white, width: 0.5, // const Color(0xFFD6D5E6), ), ), focusedBorder: OutlineInputBorder( borderSide: BorderSide( color: (layoutColor), width: 0.5, ), ), hintText: "Select Seat", hintStyle: GoogleFonts.poppins( fontSize: 11.5, color: Colors.grey, ), contentPadding: const EdgeInsets.symmetric( horizontal: 10, vertical: 5, ), ), ), dropdownButtonProps: const DropdownButtonProps( icon: Icon(Icons.arrow_drop_down), ), dropdownBuilder: (context, selectedItem) { if (selectedItem == null || selectedItem.isEmpty || selectedItem['dropdown_value'].toString().isEmpty) { return Text( "Select Seat", // fallback text style: GoogleFonts.poppins( fontSize: 12, color: Colors.grey, ), ); } return Text( selectedItem['dropdown_value'] ?? 'Select Seat', style: GoogleFonts.poppins( fontSize: 12, color: Colors.black, ), ); }, items: seatOptions, selectedItem: selectedDomesticSeat == null ? null : {"dropdown_value": selectedDomesticSeat}, onChanged: widget.isViewMode ? null : (Map? 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.w600, color: Color(0xFF575A74), ), ), SizedBox(height: 5), CustomTextFieldUserTravellerWrapper( width: widget.isDesktop ? MediaQuery.of(context).size.width * 0.15 : null, isFocused: focusStates["d_meal_prefFocused"] ?? false, isDesktop: widget.isDesktop, child: SizedBox( height: 40, child: TextField( style: GoogleFonts.poppins(fontSize: 12, color: Colors.black), focusNode: focusNodes["d_meal_prefFocusNode"], 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.w600, color: Color(0xFF575A74), ), ), SizedBox(height: 5), CustomTextFieldUserTravellerWrapper( width: widget.isDesktop ? MediaQuery.of(context).size.width * 0.4 : null, isFocused: focusStates["d_additonal_InfoFocused"] ?? false, isDesktop: widget.isDesktop, child: SizedBox( height: 40, child: TextField( style: GoogleFonts.poppins(fontSize: 12, color: Colors.black), controller: controllers["d_additonal_Info"], focusNode: focusNodes["d_additonal_InfoFocusNode"], 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> 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.w600, color: Color(0xFF575A74), ), ), SizedBox(height: 5), CustomTextFieldUserTravellerWrapper( width: widget.isDesktop ? MediaQuery.of(context).size.width * 0.15 : null, isFocused: _iSeatPrefFocus, isDesktop: widget.isDesktop, child: SizedBox( height: 35, width: double.infinity, child: Focus( focusNode: _iSeatPrefFocusNode, onFocusChange: (hasFocus) { setState(() { _iSeatPrefFocus = hasFocus; }); }, child: GestureDetector( onTap: () { // Request focus when user taps _iSeatPrefFocusNode.requestFocus(); }, child: DropdownSearch>( 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'] ?? 'Select Seat', style: GoogleFonts.poppins( fontSize: 12, color: Colors.black, ), ), ); }, ), dropdownDecoratorProps: DropDownDecoratorProps( dropdownSearchDecoration: InputDecoration( // border: InputBorder.none, border: OutlineInputBorder( borderRadius: BorderRadius.circular(8), borderSide: BorderSide( color: (_iSeatPrefFocus) ? (layoutColor) : Colors.white, width: 0.5, ), ), enabledBorder: OutlineInputBorder( borderSide: BorderSide( color: (_iSeatPrefFocus) ? (layoutColor) : Colors.white, width: 0.5, // const Color(0xFFD6D5E6), ), ), focusedBorder: OutlineInputBorder( borderSide: BorderSide( color: (layoutColor), width: 0.5, ), ), contentPadding: EdgeInsets.symmetric( horizontal: 10, vertical: 1, ), ), ), // selectedItem: purposeList.cast>().firstWhere( // (item) => item['dropdown_key'] == _selectedIsBillable, // orElse: () => {}, // ), dropdownButtonProps: const DropdownButtonProps( icon: Icon(Icons.arrow_drop_down), ), dropdownBuilder: (context, selectedItem) { if (selectedItem == null || selectedItem.isEmpty || selectedItem['dropdown_value'].toString().isEmpty) { return Text( "Select Seat", // fallback text style: GoogleFonts.poppins( fontSize: 12, color: Colors.grey, ), ); } return Text( selectedItem['dropdown_value'] ?? 'Select Seat', style: GoogleFonts.poppins( fontSize: 12, color: Colors.black, ), ); }, items: seatOptionsInt, selectedItem: selectedIntenationalSeat == null ? null : {"dropdown_value": selectedIntenationalSeat}, onChanged: widget.isViewMode ? null : (Map? 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.w600, color: Color(0xFF575A74), ), ), SizedBox(height: 5), CustomTextFieldUserTravellerWrapper( width: widget.isDesktop ? MediaQuery.of(context).size.width * 0.15 : null, isFocused: focusStates["i_meal_prefFocused"] ?? false, isDesktop: widget.isDesktop, child: SizedBox( height: 40, child: TextField( style: GoogleFonts.poppins(fontSize: 12, color: Colors.black), controller: controllers["i_meal_pref"], focusNode: focusNodes["i_meal_prefFocusNode"], 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.w600, color: Color(0xFF575A74), ), ), SizedBox(height: 5), CustomTextFieldUserTravellerWrapper( width: widget.isDesktop ? MediaQuery.of(context).size.width * 0.4 : null, isFocused: focusStates["i_additonal_InfoFocused"] ?? false, isDesktop: widget.isDesktop, child: SizedBox( height: 40, child: TextField( style: GoogleFonts.poppins(fontSize: 12, color: Colors.black), controller: controllers["i_additonal_Info"], focusNode: focusNodes["i_additonal_InfoFocusNode"], 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.w600, color: Color(0xFF575A74), ), ), SizedBox(height: 5), CustomTextFieldUserTravellerWrapper( // isFocused: false, isFocused: focusStates["emergency_contactFocused"] ?? false, isDesktop: widget.isDesktop, child: SizedBox( height: 40, child: TextField( style: GoogleFonts.poppins(fontSize: 12, color: Colors.black), controller: controllers["emergency_contact"], focusNode: focusNodes["emergency_contactFocusNode"], 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.w600, color: Color(0xFF575A74), ), ), SizedBox(height: 5), CustomTextFieldUserTravellerWrapper( isFocused: focusStates["forex_card_numFocused"] ?? false, isDesktop: widget.isDesktop, child: SizedBox( height: 40, child: TextField( style: GoogleFonts.poppins(fontSize: 12, color: Colors.black), controller: controllers["forex_card_num"], focusNode: focusNodes["forex_card_numFocusNode"], inputFormatters: [ FilteringTextInputFormatter.allow(RegExp(r'[0-9\s]')), LengthLimitingTextInputFormatter(16), ], enabled: !widget.isViewMode, onChanged: (value) { _clearError("forex_pre_paid_card_number"); apiCheckDuplicate( "Forex Pre-Paid Card Number", "forex_pre_paid_card_number", value, widget.userIdApi, ); }, decoration: InputDecoration( labelText: "Card Number", labelStyle: GoogleFonts.poppins( fontSize: 12, color: Colors.grey, ), floatingLabelBehavior: FloatingLabelBehavior.never, border: InputBorder.none, contentPadding: EdgeInsets.symmetric(vertical: 16), ), ), ), ), if (errorMessages["forex_pre_paid_card_number"] != null) ...[ SizedBox(height: 5), // Space before error message Text( errorMessages["forex_pre_paid_card_number"]!, style: GoogleFonts.poppins(color: Colors.red, fontSize: 12), ), ], ], ); } Widget buildForexExpiryDate() { DateTime? _selectedExpiryDate; Future _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), initialEntryMode: DatePickerEntryMode.calendarOnly, ); 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.w600, color: Color(0xFF575A74), ), ), SizedBox(height: 5), CustomTextFieldUserTravellerWrapper( isFocused: focusStates["forex_expiry_dateFocused"] ?? false, isDesktop: widget.isDesktop, child: SizedBox( height: 40, child: GestureDetector( onTap: widget.isViewMode ? null : () async { focusNodes["forex_expiry_dateFocusNode"] ?.requestFocus(); await _selectExpiryDate(context); }, child: AbsorbPointer( child: TextField( controller: controllers["forex_expiry_date"], focusNode: focusNodes["forex_expiry_dateFocusNode"], readOnly: true, 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 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), SizedBox( height: 60, child: Center( child: Padding( padding: EdgeInsets.only( top: 20, left: 8, ), // 👈 Add top padding here child: IconButton( icon: Icon( Icons.remove_circle_sharp, color: Colors.red, size: 15, ), tooltip: 'Cancel the Details', onPressed: () { setState(() { removeFrequentFlierEntry(entry); }); }, ), ), ), ), ], ) : Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ buildAirline(entry), SizedBox(height: 8), buildFrequentFlierInformation(entry), SizedBox(width: 15), IconButton( icon: Icon( Icons.remove_circle_sharp, color: Colors.red, size: 15, ), tooltip: 'Cancel the Details', onPressed: () { setState(() { removeFrequentFlierEntry(entry); }); }, ), ], ), ); } Widget buildAirline(Map entry) { late Map countryMap; // Mapping country_code -> country_name late List 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']}', // }; countryMap = { for (var country in countryList) country['airline'] as String: '${country['airline_name']} (${country['airline']})', // country['airline'] as String: '${country['airline_name']}', }; // airline 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.w600, color: Color(0xFF575A74), ), ), 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( // 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) { // Extract airline name and code from "AirlineName (CODE)" final match = RegExp( r'^(.*)\s+\(([^)]+)\)$', ).firstMatch(item); final name = match?.group(1) ?? ''; final code = match?.group(2) ?? ''; return Padding( padding: const EdgeInsets.symmetric( horizontal: 8.0, vertical: 6.0, ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Expanded( child: Text( name, style: const TextStyle( fontSize: 12, fontWeight: FontWeight.w500, ), overflow: TextOverflow.ellipsis, ), ), Text( code, style: const TextStyle( fontSize: 12, fontWeight: FontWeight.w500, color: Colors.grey, ), ), ], ), ); }, // 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 Airline...", 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 Airline", 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 entry) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Frequent Flyer Information", style: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w600, color: Color(0xFF575A74), ), ), 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 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), SizedBox( height: 60, child: Center( child: Padding( padding: EdgeInsets.only( top: 20, left: 8, ), // 👈 Add top padding here child: IconButton( icon: Icon( Icons.remove_circle_sharp, color: Colors.red, size: 15, ), tooltip: 'Cancel the Details', onPressed: () { setState(() { removeHotelLoyaltyEntry(entry); }); }, ), ), ), ), ], ) : Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ buildHotel(entry), SizedBox(height: 8), buildHotelMembershipNum(entry), IconButton( icon: Icon( Icons.remove_circle_sharp, color: Colors.red, size: 15, ), tooltip: 'Cancel the Details', onPressed: () { setState(() { // removeHotelLoyaltyEntry(localId); removeHotelLoyaltyEntry(entry); }); }, ), ], ), ); } Widget buildHotel(Map entry) { late Map countryMap; // Mapping country_code -> country_name late List 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.w600, color: Color(0xFF575A74), ), ), 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( 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 entry) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Hotel Membership Number", style: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w600, color: Color(0xFF575A74), ), ), 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), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( " * confirm your passport is valid for next 6 months", style: GoogleFonts.poppins(fontSize: 10), ), IconForVisa(), ], ), ], ); } Widget _buildVisaDataRow1(Map 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), SizedBox( height: 60, child: Center( child: Padding( padding: EdgeInsets.only( top: 20, left: 8, ), // 👈 Add top padding here child: IconButton( icon: Icon( Icons.remove_circle_sharp, color: Colors.red, size: 15, ), tooltip: 'Cancel the Details', 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.remove_circle_sharp, color: Colors.red, size: 15, ), tooltip: 'Cancel the Details', onPressed: () { setState(() { // visaRowIds.remove(id); removeVisaEntry(entry); }); }, ), ], ), ); } Widget buildVisaCountry(entry) { late Map countryMap; // Mapping country_code -> country_name late List 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.w600, color: Color(0xFF575A74), ), ), 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( 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 purposeList = apiData?['visa_type_of_visa']; // Map for id => name Map visaTypeMap = { for (var item in purposeList) item['dropdown_key'].toString(): item['dropdown_value'].toString(), }; // Selected value from entry String? selectedPurpose = entry['visa_type_id']?.toString(); if (!visaTypeMap.containsKey(selectedPurpose)) { selectedPurpose = null; } return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Visa Type", style: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w600, color: Color(0xFF575A74), ), ), 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( selectedItem: visaTypeMap[selectedPurpose], enabled: purposeList.isNotEmpty, popupProps: PopupProps.menu( showSearchBox: true, fit: FlexFit.loose, menuProps: const MenuProps(backgroundColor: Colors.white), constraints: BoxConstraints(maxHeight: 200), itemBuilder: (context, item, isSelected) => Container( padding: EdgeInsets.symmetric( horizontal: 10, vertical: 6, ), child: Text( item, style: GoogleFonts.poppins(fontSize: 11.5), ), ), searchFieldProps: TextFieldProps( decoration: InputDecoration( hintText: "Search Visa Type...", hintStyle: GoogleFonts.poppins(fontSize: 11.5), contentPadding: EdgeInsets.symmetric(horizontal: 10), ), ), ), items: visaTypeMap.values.toList(), dropdownDecoratorProps: DropDownDecoratorProps( dropdownSearchDecoration: InputDecoration( border: InputBorder.none, contentPadding: EdgeInsets.symmetric(horizontal: 1), ), ), dropdownBuilder: (context, selectedItem) => Align( alignment: Alignment.centerLeft, child: Text( selectedItem ?? "Select Visa Type", style: GoogleFonts.poppins( fontSize: 12, color: Colors.black, ), ), ), onChanged: (newValue) { if (newValue == null) return; final selectedId = visaTypeMap.entries .firstWhere((entry) => entry.value == newValue) .key; setState(() { entry['visa_type_id'] = selectedId; }); print("Updating form data: visa_type_id -> $selectedId"); }, ), ), ), ], ); } Widget buildVisaType3(entry) { if (apiData == null || apiData?['visa_type_of_visa'] == null) { return Center( child: Transform.scale(scale: 0.5, child: CircularProgressIndicator()), ); } // List purposeList = apiData?['visa_type_of_visa'] ?? []; // List purposeList = []; List purposeList = apiData?['visa_type_of_visa']; List> dropdownItems = purposeList .map( (item) => DropdownMenuItem( value: item['dropdown_key'], child: Text(item['dropdown_value']), ), ) .toList(); if (dropdownItems.isEmpty) { dropdownItems.add( DropdownMenuItem( 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.w600, color: Color(0xFF575A74), ), ), 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( 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 buildVisaType2(entry) { late Map visaTypeMap; // Mapping country_code -> country_name late List visaTypeCodes; // List of country codes // List purposeList = apiData?['visa_type_of_visa']; List purposeList = apiData?['visa_type_of_visa']; print("purposeList - $purposeList"); visaTypeMap = { for (var item in purposeList) item['visa_type_id'] as String: item['visa_type_of_visa'] as String, }; // Extract only country codes for processing visaTypeCodes = visaTypeMap.keys.toList(); // selectedPurpose ??= null; String? selectedPurpose = entry['visa_type_of_visa']; return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Visa Type", style: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w600, color: Color(0xFF575A74), ), ), 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( selectedItem: visaTypeMap[selectedPurpose], 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 Visa Type...", hintStyle: GoogleFonts.poppins(fontSize: 11.5), contentPadding: EdgeInsets.symmetric(horizontal: 10), ), ), ), items: visaTypeMap.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 Visa Type", 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 selectedPurpose = visaTypeMap.entries .firstWhere((entry) => entry.value == newValue) .key; entry['visa_type_of_visa'] = selectedPurpose; }); }, ), ), ), ], ); } DateTime? _selectedCheckOutDate; DateTime? _selectedCheckInDate; Widget buildVisaValidFrom(Map entry) { TimeOfDay? _selectedCheckOutTime; Future _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, firstDate: DateTime(1900), // lastDate: DateTime(2100), lastDate: today, initialEntryMode: DatePickerEntryMode.calendarOnly, ); print("CheckOut - $_selectedCheckOutDate"); print("CheckIn - $_selectedCheckInDate"); if (pickedDate != null && pickedDate != _selectedCheckOutDate) { setState(() { _selectedCheckInDate = pickedDate; entry["controller_valid_from"]?.text = DateFormat( 'dd-MM-yyyy', ).format(pickedDate); // Revalidate expiry if (_selectedCheckOutDate != null && _selectedCheckOutDate!.isBefore(_selectedCheckInDate!)) { print("Entry - $entry"); print("errorMessages - $errorMessages"); errorMessages["valid_upto${entry['local_id']}"] = "Invalid Date Range"; // errorMessages["valid_upto"] = "Invalid Date Range"; } else { errorMessages.remove("valid_upto${entry['local_id']}"); // errorMessages.remove("valid_upto"); } }); } } return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Valid From", style: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w600, color: Color(0xFF575A74), ), ), 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"], readOnly: true, 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 entry) { TimeOfDay? _selectedCheckOutTime; Future _selectValidUpToDate(BuildContext context) async { DateTime now = DateTime.now(); DateTime today = DateTime(now.year, now.month, now.day); print("buildVisaValidUpTo "); print("buildVisaValidUpTo - ${entry["controller_valid_from"].text}"); // 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!; // } DateTime? validFromDate; try { String fromDateText = entry["controller_valid_from"].text.trim(); print("Valid From Text: $fromDateText"); if (fromDateText.isNotEmpty) { validFromDate = DateFormat('dd-MM-yyyy').parseStrict(fromDateText); print("Parsed Valid From: $validFromDate"); } } catch (e) { print("Error parsing valid from date: $e"); } // Use max(today, validFromDate) as firstDate // DateTime firstDate = today; DateTime firstDate = validFromDate ?? today; if (validFromDate != null && validFromDate.isAfter(today)) { firstDate = validFromDate; } final pickedDate = await showDatePicker( context: context, // initialDate: initialDate, // firstDate: initialDate, // firstDate: DateTime(1900), initialDate: firstDate, firstDate: firstDate, lastDate: DateTime(2100), initialEntryMode: DatePickerEntryMode.calendarOnly, ); if (pickedDate != null && pickedDate != _selectedCheckOutDate) { setState(() { _selectedCheckOutDate = pickedDate; entry["controller_valid_upto"].text = DateFormat( "dd-MM-yyyy", ).format(pickedDate); if (_selectedCheckInDate != null && _selectedCheckOutDate!.isBefore(_selectedCheckInDate!)) { errorMessages["valid_upto${entry['local_id']}"] = "Invalid Date Range"; } else { errorMessages.remove("valid_upto${entry['local_id']}"); } }); } } return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Valid To", style: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w600, color: Color(0xFF575A74), ), ), 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 { errorMessages.remove("valid_upto${entry['local_id']}"); await _selectValidUpToDate(context); }, child: AbsorbPointer( child: TextField( // focusNode: _dateFocusNode, // controller: _dateController, controller: entry["controller_valid_upto"], readOnly: true, style: const TextStyle(fontSize: 12), decoration: const InputDecoration( labelText: "Select Date", labelStyle: TextStyle(fontSize: 12, color: Colors.grey), floatingLabelBehavior: FloatingLabelBehavior.never, border: InputBorder.none, contentPadding: EdgeInsets.symmetric(vertical: 16), suffixIcon: Icon( Icons.calendar_today, size: 16, color: Colors.grey, ), ), ), ), ), ), ), if (errorMessages["valid_upto${entry['local_id']}"] != null) ...[ SizedBox(height: 5), // Space before error message Text( errorMessages["valid_upto${entry['local_id']}"]!, style: GoogleFonts.poppins(color: Colors.red, fontSize: 10), maxLines: 2, // Allow it to wrap onto two lines overflow: TextOverflow.ellipsis, // Add ellipsis if it still overflows ), ], ], ); } // Widget buildVisaValidFrom(Map entry) { // DateTime? _selectedCheckOutDate; // TimeOfDay? _selectedCheckOutTime; // // Future _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, // firstDate: DateTime(1900), // lastDate: DateTime(2100), // initialEntryMode: DatePickerEntryMode.calendarOnly, // ); // // 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( // "Valid From", // style: GoogleFonts.poppins( // fontSize: 12, // fontWeight: FontWeight.w600, // color: Color(0xFF575A74), // ), // ), // 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 entry) { // DateTime? _selectedCheckOutDate; // TimeOfDay? _selectedCheckOutTime; // // Future _selectValidUpToDate(BuildContext context) async { // DateTime now = DateTime.now(); // DateTime today = DateTime(now.year, now.month, now.day); // print("buildVisaValidUpTo "); // print("buildVisaValidUpTo - ${entry["controller_valid_from"].text}"); // // // 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!; // // } // // DateTime? validFromDate; // // try { // String fromDateText = entry["controller_valid_from"].text.trim(); // print("Valid From Text: $fromDateText"); // if (fromDateText.isNotEmpty) { // validFromDate = DateFormat('dd-MM-yyyy').parseStrict(fromDateText); // print("Parsed Valid From: $validFromDate"); // } // } catch (e) { // print("Error parsing valid from date: $e"); // } // // // Use max(today, validFromDate) as firstDate // // DateTime firstDate = today; // DateTime firstDate = validFromDate ?? today; // if (validFromDate != null && validFromDate.isAfter(today)) { // firstDate = validFromDate; // } // final pickedDate = await showDatePicker( // context: context, // // initialDate: initialDate, // // firstDate: initialDate, // // firstDate: DateTime(1900), // initialDate: firstDate, // firstDate: firstDate, // lastDate: DateTime(2100), // initialEntryMode: DatePickerEntryMode.calendarOnly, // ); // // 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 To", // style: GoogleFonts.poppins( // fontSize: 12, // fontWeight: FontWeight.w600, // color: Color(0xFF575A74), // ), // ), // 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", ), ); } }