import 'dart:convert'; import 'package:dropdown_search/dropdown_search.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:intl/intl.dart'; import 'package:responsive_builder/responsive_builder.dart'; import 'package:shared_preferences/shared_preferences.dart'; import '../../config/apiUrl.dart'; import '../../utils/auth_utils.dart'; import '../../widgets/custom_text_field.dart'; import '../../widgets/custom_text_forex.dart'; import '../../widgets/custom_text_itnerary_sub.dart'; import 'package:http/http.dart' as http; class ForexScreen extends StatefulWidget { final List> flightData; final Map? apiData; final Function(String, bool) onClose; final Map? selectedItem; final List? apiCountryData; final Function(Map) onSaveForex; final String? loginUser; ForexScreen({ required this.onClose, this.apiData, required this.selectedItem, required this.apiCountryData, required this.onSaveForex, required this.loginUser, required this.flightData, }); @override _ForexScreenState createState() => _ForexScreenState(); } class _ForexScreenState extends State { final GlobalKey _formKey = GlobalKey(); late ValueNotifier flightFirstTripDateNotifier; late ValueNotifier flightLastTripDateNotifier; late ValueNotifier flightFirstTripToPlaceNotifier; // late final tripuserId; String? tripuserId; // late String? userCardNumber; Map selectedValues = {}; bool isChecked = false; // State variable for checkbox int fifteenPercent = 0; int remainingAmount = 0; bool isWidget = false; Map focusNodes = {}; Map focusStates = {}; Map textControllers = {}; List countryList = []; List dataHeader = [ "_forexStartDate", "_forexEndDate", "_countries", "_duration", "_currency", "_perdiemAmount", "_transport", "_accomodation", "_telephone", "_otherExpenses", "_cardNumber", "_currency", "_card", "_cash", "_checkForex", "_deliveryLocation", "_comments", ]; String _formatDate(String? date) { if (date == null || date.isEmpty) return ""; try { // First try parsing as ISO format (YYYY-MM-DD) DateTime parsedDate; try { parsedDate = DateTime.parse(date); } catch (e) { // If ISO parse fails, try parsing as DD-MM-YYYY final parts = date.split('-'); if (parts.length == 3) { parsedDate = DateTime( int.parse(parts[2]), // year int.parse(parts[1]), // month int.parse(parts[0]), // day ); } else { throw FormatException("Unsupported date format"); } } return DateFormat("dd-MM-yyyy").format(parsedDate); } catch (e) { print("Error formatting date '$date': $e"); return ""; // Return empty string or handle differently } } // String _formatDate(String? date) { // if (date == null || date.isEmpty) return ""; // try { // DateTime parsedDate = DateTime.parse( // date, // ); // Assuming input is YYYY-MM-DD // return DateFormat( // "dd-MM-yyyy", // ).format(parsedDate); // Convert to DD-MM-YYYY // } catch (e) { // print("Error formatting date: $e"); // return date; // Return as is if parsing fails // } // } Map errorMessages = {}; String? selectedCountry; String? selectedCurrency; String? selectedDuration; String? selectedPerdiemAmount; String? CalculatedOtherExpenses; String? selectedQuotedAmount; int? selectedCashPercent; int? selectedCardPercent; bool userEdited = false; Map get forexData { Map data = { "start_date": textControllers["_forexStartDate"]?.text, "end_date": textControllers["_forexEndDate"]?.text, "country_code": selectedCountry, "duration": selectedDuration, "currency": selectedCurrency, "perdiem_amount": selectedPerdiemAmount, "transport": textControllers["_transport"]?.text, "accommodation": textControllers["_accomodation"]?.text, "telephone": textControllers["_telephone"]?.text, "have_card": isChecked ? "1" : "0", "card_number": textControllers["_cardNumber"]?.text, "deposit_on_card": textControllers["_card"]?.text, "deposit_on_cash": textControllers["_cash"]?.text, "delivery_location": textControllers["_deliveryLocation"]?.text, "comments": textControllers["_comments"]?.text, "total": selectedQuotedAmount, "card_percentage": selectedCardPercent, "cash_percentage": selectedCashPercent, "created_by": widget.loginUser, "updated_by": widget.loginUser, }; if (widget.selectedItem != null) { if (widget.selectedItem?["indx"] != null && widget.selectedItem?["indx"] != 0) { data["indx"] = widget.selectedItem!["indx"]; } else if (widget.selectedItem?["forex_id"] != null && widget.selectedItem?["forex_id"] != 0) { data["forex_id"] = widget.selectedItem!["forex_id"]; } } return data; } Map get getForexData { return { "country_code": selectedCountry, "start_date": _formatDate(textControllers["_forexStartDate"]?.text), "end_date": _formatDate(textControllers["_forexEndDate"]?.text), "user_id": tripuserId, // "currency": selectedCurrency ?? "", }; } Future getToken() async { final prefs = await SharedPreferences.getInstance(); return prefs.getString('auth_token'); } Future postgetForexData(Map forexData) async { final String apiUrldata = '$apiUrl/api/plans/getForexPerdiem'; print("getForexPerdiem api - Sending Data: ${jsonEncode(forexData)}"); final token = await getToken(); // Fetch token if (token == null) { throw Exception('Token not found. Please log in.'); } try { final response = await http.post( Uri.parse(apiUrldata), headers: { 'Authorization': 'Bearer $token', 'Content-Type': 'application/json', }, body: jsonEncode(forexData), // Convert map to JSON ); if (response.statusCode == 200) { print("Plan submitted successfully!"); print("Response: ${response.body}"); final Map responseData = jsonDecode(response.body); // Ensure the response contains the expected keys before updating state if (responseData.containsKey("currency") && responseData.containsKey("perdiem_amount") && responseData.containsKey("duration")) { setState(() { // Update only if data is valid selectedCurrency = responseData["currency"] ?? selectedCurrency; selectedPerdiemAmount = responseData["perdiem_amount"]?.toString() ?? ""; selectedDuration = responseData["duration"]?.toString() ?? ""; selectedQuotedAmount = responseData["perdiem_amount"]?.toString() ?? ""; selectedCardPercent = int.tryParse( responseData["card_percentage"]?.toString() ?? "", ); selectedCashPercent = int.tryParse( responseData["cash_percentage"]?.toString() ?? "", ); textControllers["_cardNumber"]?.text = responseData["forex_card_no"]?.toString() ?? ""; }); _onFieldChangedForOthers(); _divideQuotedAmount(); } else { print("Warning: Response does not contain expected fields."); } } else { print("Failed to submit plan. Status: ${response.statusCode}"); print("Error: ${response.body}"); } } catch (e) { print(" Error submitting plan: $e"); } } bool isValidForexData(Map data) { // Required fields that must not be empty List requiredFields = [ "start_date", "end_date", "country_code", "deposit_on_card", "deposit_on_cash", "currency", // "card_number", ]; // If have_card is "1", then delivery_location is required bool isCardChecked = data["have_card"] == "1"; if (isCardChecked) { requiredFields.add("delivery_location"); } else { requiredFields.add("card_number"); // Check if card_number has at least 16 digits } if (!isCardChecked) { final cardNumber = data["card_number"]?.toString() ?? ''; if (cardNumber.length < 15) { // Show error or return false print("Card number must be 15 digits"); errorMessages["card_number"] = "Card number must be atleast 15 digits"; } } // Check validation for each field for (String field in requiredFields) { if (data[field] == null || data[field].toString().trim().isEmpty) { errorMessages[field] = "Required"; } } // Additional validation starts final start_Date = data["start_date"]; final end_Date = data["end_date"]; 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)) { errorMessages["end_date"] = "End date cannot be earlier than start date"; ; } else if (checkEndDate.isAtSameMomentAs(checkStartDate)) { setState(() { errorMessages["end_date"] = "Start and end dates cannot be the same"; }); } } catch (e) { errorMessages["end_date"] = "Invalid date format"; } return errorMessages.isEmpty; } // Additional validation ends return errorMessages.values.every((msg) => msg.trim().isEmpty); // if (hasErrors) { // print("At least one error message is present."); // } // // return errorMessages.isEmpty; // Valid if there are no errors } Map getFlightTripDateRange( List> flightData, ) { final allTrips = flightData .expand((flight) => flight['trips'] ?? []) .whereType>() .toList(); print("ALlTrips- $allTrips"); if (allTrips.isEmpty) { return {'firstTripDate': null, 'lastTripDate': null, 'toTripPlace': null}; } allTrips.sort((a, b) { final aDate = DateTime.tryParse(a['date'] ?? '') ?? DateTime(1900); final bDate = DateTime.tryParse(b['date'] ?? '') ?? DateTime(1900); return aDate.compareTo(bDate); }); final firstTrip = allTrips.first; final lastTrip = allTrips.last; final toPlaceCode = allTrips.first['to_country_code']; return { 'firstTripDate': firstTrip['date'], 'lastTripDate': lastTrip['date'], 'toTripPlace': toPlaceCode, }; } void handleSave() { print("Handle Save forexData $forexData"); Map data = forexData; final card = data["deposit_on_card"]; final cash = data["deposit_on_cash"]; print("card - $card"); print("cash - $cash"); _validateCardAmount(card); _validateCashAmount(cash); if (!isValidForexData(data)) { // && errorMessages.isNotEmpty) { print("Validation Failed: Required fields are missing."); setState(() {}); return; // Stop execution if validation fails } else { widget.onSaveForex(forexData); // Send object to parent } widget.onClose("Forex", false); // widget.onClose(false); // Close screen after saving } DateTime? _parseDate(String date) { try { return DateFormat("dd-MM-yyyy").parse(date); // Change format if needed } catch (e) { return null; } } TextEditingController initController(String key) { return TextEditingController(text: widget.selectedItem?[key] ?? ""); } @override void initState() { super.initState(); // Initialize fields dynamically for (var field in dataHeader) { if (!textControllers.containsKey(field)) { textControllers[field] = TextEditingController(); } if (!focusNodes.containsKey(field)) { focusNodes[field] = FocusNode(); } if (!focusStates.containsKey(field)) { focusStates[field] = false; } } print("Focus States KeysII: ${focusStates.keys.toList()}"); print("Focus Nodes KeysII: ${focusNodes.keys.toList()}"); print("Text Controllers KeysII: ${textControllers.keys.toList()}"); // Add focus listeners focusNodes.forEach((key, focusNode) { _addFocusListener(focusNode, (focus) { setState(() { focusStates[key] = focus; }); }); }); // Add listeners to text fields textControllers["_forexStartDate"]?.addListener(_onFieldChanged); textControllers["_forexEndDate"]?.addListener(_onFieldChanged); textControllers["_countries"]?.addListener(_onFieldChanged); // Reattach flightFirstTripDateNotifier = ValueNotifier(null); flightLastTripDateNotifier = ValueNotifier(null); flightFirstTripToPlaceNotifier = ValueNotifier(null); WidgetsBinding.instance.addPostFrameCallback((_) async { tripuserId = await getTripUserId(); print("tripuserId - $tripuserId"); final result = getFlightTripDateRange(widget.flightData); print("result - $result"); flightFirstTripDateNotifier.value = result['firstTripDate']; flightLastTripDateNotifier.value = result['lastTripDate']; flightFirstTripToPlaceNotifier.value = result['toTripPlace']; // ✅ Only set controller after value is updated // final parsedDate = // DateTime.tryParse(flightFirstTripDateNotifier.value ?? ''); // ----------------------------------- // final parsedDate = DateFormat( // "dd-MM-yyyy", // ).parse(flightFirstTripDateNotifier.value ?? ''); // if (parsedDate != null) { // textControllers["_forexStartDate"]?.text = DateFormat( // 'dd-MM-yyyy', // ).format(parsedDate); // } final flightDateStr = flightFirstTripDateNotifier.value; if (flightDateStr != null && flightDateStr.trim().isNotEmpty) { try { final parsedDate = DateFormat( "dd-MM-yyyy", ).parse(flightDateStr.trim()); textControllers["_forexStartDate"]?.text = DateFormat( 'dd-MM-yyyy', ).format(parsedDate); } catch (e) { print("Error parsing flightFirstTripDate: $e"); // Optionally show an error message to the user or log it } } else { print("flightFirstTripDateNotifier is null or empty"); } final flightDateEnd = flightLastTripDateNotifier.value; if (flightDateEnd != null && flightDateEnd.trim().isNotEmpty) { try { final parsedDate = DateFormat( "dd-MM-yyyy", ).parse(flightDateEnd.trim()); textControllers["_forexEndDate"]?.text = DateFormat( 'dd-MM-yyyy', ).format(parsedDate); } catch (e) { print("Error parsing flightEndTripDate: $e"); // Optionally show an error message to the user or log it } } else { print("flightEndTripDateNotifier is null or empty"); } final toPlaceCountryCode = flightFirstTripToPlaceNotifier.value; print("toPlaceCode-$toPlaceCountryCode"); if (toPlaceCountryCode != null && toPlaceCountryCode.trim().isNotEmpty) { try { if (toPlaceCountryCode != null) { textControllers["_countries"]?.text = toPlaceCountryCode; // set country dropdown selectedCountry = toPlaceCountryCode; // textControllers["_countriesFocused"]?.addListener( _onFieldChanged ); // Reattach // _onFieldChanged; } else { print("No matching country found for code: $toPlaceCountryCode"); } } catch (e) { print("Error while mapping to_place to Country_Code: $e"); } } else { print("flightToPlaceTripNotifier is null or empty"); } // final parsedEndDate = DateFormat( // "dd-MM-yyyy", // ).parse(flightLastTripDateNotifier.value ?? ''); // if (parsedEndDate != null) { // textControllers["_forexEndDate"]?.text = DateFormat( // 'dd-MM-yyyy', // ).format(parsedEndDate); // } _onFieldChanged(); handleUpdatedField(); }); } void handleUpdatedField() async { // Set the selected value if available // // userCardNumber = await getForexCardNumber(); // // userCardNumber = "CD7909043"; // print("userCardNumber - $userCardNumber"); if (widget.selectedItem == null && textControllers["_cardNumber"]?.text == "") { // print("userCardNumber11 - $userCardNumber"); // textControllers["_cardNumber"]?.text = userCardNumber ?? ""; } if (widget.selectedItem != null) { print("UPDATAED SELECTION"); textControllers["_forexStartDate"] = initController("start_date"); textControllers["_forexStartDate"]?.addListener( _onFieldChanged, ); // Reattach textControllers["_forexEndDate"] = initController("end_date"); textControllers["_forexEndDate"]?.addListener( _onFieldChanged, ); // Reattach textControllers["_transport"] = initController("transport"); textControllers["_accomodation"] = initController("accommodation"); textControllers["_telephone"] = initController("telephone"); textControllers["_cardNumber"] = initController("card_number"); textControllers["_card"] = initController("deposit_on_card"); textControllers["_cash"] = initController("deposit_on_cash"); textControllers["_deliveryLocation"] = initController( "delivery_location", ); textControllers["_comments"] = initController("comments"); // Set dropdown values selectedCountry = widget.selectedItem!["country_code"] as String?; selectedCurrency = widget.selectedItem!["currency"] as String?; selectedDuration = widget.selectedItem!["duration"] as String?; selectedCardPercent = int.tryParse( widget.selectedItem!["card_percentage"]?.toString() ?? "", ); selectedCashPercent = int.tryParse( widget.selectedItem!["cash_percentage"]?.toString() ?? "", ); print("selectedCashPercentUPdae - $selectedCashPercent"); selectedPerdiemAmount = widget.selectedItem!["perdiem_amount"] as String?; isChecked = widget.selectedItem!["have_card"] == "1"; // Convert string to bool textControllers["_cardNumber"]?.text = widget.selectedItem!["card_number"]?.toString() ?? ""; isWidget = true; // if (textControllers["_cardNumber"] != null) { // print("userCardNumber11 - $userCardNumber"); // textControllers["_cardNumber"]!.text = userCardNumber ?? ''; // // } _onFieldChangedForOthers(); setState(() {}); // Update the UI // // Calculate other expenses (if applicable) // CalculatedOtherExpenses = calculateOtherExpenses(); } } void _addFocusListener(FocusNode node, Function(bool) updateState) { node.addListener(() { setState(() { updateState(node.hasFocus); }); }); } // ___________________________- // Check if all required fields have data bool _isForexDataComplete() { final data = getForexData; return (data["country_code"]?.isNotEmpty ?? false) && (data["start_date"]?.isNotEmpty ?? false) && (data["end_date"]?.isNotEmpty ?? false); } // Check if all required fields have data bool _isForexDataDurationComplete() { final data = getForexData; return (data["start_date"]?.isNotEmpty ?? false) && (data["end_date"]?.isNotEmpty ?? false); } // Handle field changes void _onFieldChanged() { if (_isForexDataDurationComplete()) { print("Calculate 1"); CalculateDuration(); } if (_isForexDataComplete()) { if (tripuserId != null) { postgetForexData(getForexData); } else { print("tripuserId is null"); } } } void CalculateDuration() { print("Calculate 2"); final data = getForexData; final startDateString = data["start_date"]; final endDateString = data["end_date"]; if (startDateString == null || endDateString == null) return; print( "Calculate 3 - StarrtDAte: $startDateString --EndDate: $endDateString", ); try { // Parse the dates from string // final startDate = DateFormat('dd/mm/yyyy').parse(startDateString); // final endDate = DateFormat('dd/mm/yyyy').parse(endDateString); final startDate = DateFormat('dd-MM-yyyy').parse(startDateString.trim()); final endDate = DateFormat('dd-MM-yyyy').parse(endDateString.trim()); print("Calculate 4"); // Calculate difference final durationInDays = endDate.difference(startDate).inDays; // +1 to include both days // You can now use durationInDays however you want: print("Duration: $durationInDays days"); // Convert int to String setState(() { selectedDuration = durationInDays.toString(); }); } catch (e) { print('Error parsing dates: $e'); } } // Handle dropdown change void _onCountryChanged(String? newCountry) { setState(() { selectedCountry = newCountry; }); if (_isForexDataComplete()) { postgetForexData(getForexData); } } // _____________ End Forex Details ______________- void _onFieldChangedForOthers() { setState(() { double transport = double.tryParse(textControllers["_transport"]?.text ?? "0") ?? 0; double accommodation = double.tryParse(textControllers["_accomodation"]?.text ?? "0") ?? 0; double telephone = double.tryParse(textControllers["_telephone"]?.text ?? "0") ?? 0; double calclateVal = (transport + accommodation + telephone); CalculatedOtherExpenses = (calclateVal).toStringAsFixed(0); // Convert selectedPerdiemAmount to double before performing the addition double perdiemAmount = double.tryParse(selectedPerdiemAmount ?? "0") ?? 0; print("calclateVal - $calclateVal"); selectedQuotedAmount = ((perdiemAmount + calclateVal).toString() ?? 0) as String?; if (isWidget) { print("IsWidget Update"); int? quotedAmount = int.tryParse(selectedQuotedAmount!); fifteenPercent = (quotedAmount! * selectedCashPercent!) ~/ 100; } }); if (userEdited) { _divideQuotedAmount(); } errorMessages.clear(); } void _divideQuotedAmount() { int? quotedAmount = int.tryParse(selectedQuotedAmount!); print("quotedAmount - $selectedQuotedAmount"); print("DIVIDREFD - $selectedQuotedAmount -$quotedAmount"); if (quotedAmount != null) { // fifteenPercent = (quotedAmount * 15) ~/ 100; print("selectedCardPercent - $selectedCashPercent"); fifteenPercent = (quotedAmount * selectedCashPercent!) ~/ 100; // Calculate 15% (integer division) remainingAmount = quotedAmount - fifteenPercent; // Subtract from total // // Only set text if the field is empty (user hasn't typed) // if (textControllers["_cash"] != null && // textControllers["_cash"]!.text.trim().isEmpty) { // textControllers["_cash"]!.text = fifteenPercent.toString(); // } else { // print("_cash already has user input, not overwriting"); // } // // if (textControllers["_card"] != null && // textControllers["_card"]!.text.trim().isEmpty) { // textControllers["_card"]!.text = remainingAmount.toString(); // } else { // print("_card already has user input, not overwriting"); // } textControllers["_cash"]?.text = fifteenPercent.toString(); textControllers["_card"]?.text = remainingAmount.toString(); print("15% Amount: $fifteenPercent"); print("Remaining Amount: $remainingAmount"); } else { print("Invalid number format in selectedQuotedAmount"); } } void _validateCardAmount(String value) { print("_validateCardAmount - $value - $remainingAmount"); int? enteredAmount = int.tryParse(value); int? cashAmount = int.tryParse(textControllers["_cash"]!.text ?? "0"); int? qouteAmount = int.tryParse(selectedQuotedAmount ?? "0"); int? calculateAmnt = cashAmount! + enteredAmount!; int checkValidAmount = cashAmount + enteredAmount; print( "checkValidAmount - $checkValidAmount - $enteredAmount - $cashAmount", ); print( "CAsh - $cashAmount- CaRd- $enteredAmount - $calculateAmnt - selectedQuotedAmount - $qouteAmount", ); if (enteredAmount == null || calculateAmnt > qouteAmount!) { errorMessages["deposit_on_card"] = "Sum of cash card cannot exceed $qouteAmount"; } else if (checkValidAmount < qouteAmount) { errorMessages["deposit_on_card"] = "Enter Valid Amount"; // Clear error if valid } else { errorMessages.remove("deposit_on_card"); // Clear error if valid } // Refresh UI if using StatefulWidget setState(() {}); } void _validateCashAmount(String value) { // errorMessages["deposit_on_card"] = " "; errorMessages.remove("deposit_on_cash"); print("_validateCashAmount - $value - $fifteenPercent"); int? enteredAmount = int.tryParse(value); int? quotedAmount = int.tryParse(selectedQuotedAmount!); int? difference = quotedAmount! - enteredAmount!; int? cashAmount = int.tryParse(textControllers["_cash"]!.text ?? "0"); int? cardAmount = int.tryParse(textControllers["_card"]!.text ?? "0"); int checkValidAmount = cardAmount! + enteredAmount; print( "checkValidAmountCash - $checkValidAmount -cash- $enteredAmount -Card - $cardAmount - quotedAmount- $quotedAmount", ); print("Difference - $difference"); print('CardAmount - $cardAmount'); textControllers["_card"]?.text = difference.toString(); if (enteredAmount > fifteenPercent) { if (checkValidAmount > 0) { textControllers["_card"]?.text = "0"; errorMessages["deposit_on_card"] = "Enter Valid Amount"; // Clear error if valid } print("CASHfifteenPercent - $fifteenPercent"); errorMessages["deposit_on_cash"] = "Amount cannot exceed $fifteenPercent"; } else if (checkValidAmount == quotedAmount) { errorMessages.remove("deposit_on_cash"); // errorMessages["deposit_on_card"] = " "; // Clear error if valid } else { // errorMessages["deposit_on_cash"] = ""; // Clear error if valid errorMessages.remove("deposit_on_cash"); } // Refresh UI if using StatefulWidget setState(() {}); } @override void dispose() { for (var node in focusNodes.values) { node.dispose(); } // Dispose all dynamically created TextEditingControllers for (var controller in textControllers.values) { controller.dispose(); } super.dispose(); } void _validateDates() { print("VALiDATING DATES"); DateTime? startDate = _parseDate( textControllers["_forexStartDate"]?.text ?? "", ); DateTime? endDate = _parseDate( textControllers["_forexEndDate"]?.text ?? "", ); if (startDate != null && endDate != null && endDate.isBefore(startDate)) { setState(() { errorMessages["end_date"] = "End date cannot be earlier than start date"; }); } else { setState(() { errorMessages.remove("end_date"); }); } } @override Widget build(BuildContext context) { return ResponsiveBuilder( builder: (context, sizingInfo) { bool isMobile = sizingInfo.isMobile; bool isDesktop = sizingInfo.deviceScreenType == DeviceScreenType.desktop; return Container( // color: Color(0xFFF4F4FB), child: Form( key: _formKey, child: Padding( padding: const EdgeInsets.all(16.0), child: Column( children: [ Padding( padding: const EdgeInsets.all(28.0), child: Center( child: Column(children: _buildAccomadtionForm(isDesktop)), ), ), ], ), ), ), ); }, ); } List _buildAccomadtionForm(bool isDesktop) { List buildResponsiveRow(List children) { return [ isDesktop ? Row(children: children) : Column(children: children), SizedBox(height: 10), ]; } // List> rowBuilders = [ // // _builClassType(isDesktop), // _buildSecondRow(isDesktop) // ]; return [ ...buildResponsiveRow(_buildFirstRow(isDesktop)), SizedBox(height: 5), // Align( // alignment: Alignment.centerLeft, // child: Text( // "Forex Details", // style: TextStyle( // fontSize: 13, // fontWeight: FontWeight.w600, // color: Color(0xFF575A74)), // ), // ), // SizedBox( // height: 8, // ), // Divider( // thickness: 0.3, // ), // SizedBox( // height: 3, // ), ...buildResponsiveRow(_builClassType(isDesktop)), // SizedBox( // height: 3, // ), // Divider( // thickness: 0.3, // ), SizedBox(height: 10), ...buildResponsiveRow(_buildSecondRow(isDesktop)), ...buildResponsiveRow(_buildCardDetailsRow(isDesktop)), ...buildResponsiveRow(_buildForexCard(isDesktop)), ...buildResponsiveRow(_buildCommetsRow(isDesktop)), ]; } List _buildFirstRow(isDesktop) { DateTime? _selectedCheckOutDate; DateTime? _selectedEndDate; Future _selectCheckOutDate(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; if (flightFirstTripDateNotifier.value != null) { try { initialDate = DateTime.parse(flightFirstTripDateNotifier.value!); // textControllers["_forexStartDate"]?.text = // DateFormat('yyyy-MM-dd').format(initialDate); textControllers["_forexStartDate"]?.text = DateFormat( 'dd-MM-yyyy', ).format(initialDate); } catch (e) { initialDate = today; } } else { initialDate = today; } // Use previously selected date if valid if (_selectedCheckOutDate != null && _selectedCheckOutDate!.isAfter(today)) { initialDate = _selectedCheckOutDate!; } final pickedDate = await showDatePicker( context: context, initialDate: initialDate, firstDate: initialDate, lastDate: DateTime(2100), initialEntryMode: DatePickerEntryMode.calendarOnly, ); // DateTime? pickedDate = await showDatePicker( // context: context, // initialDate: _selectedCheckOutDate != null && // _selectedCheckOutDate!.isAfter(today) // ? _selectedCheckOutDate! // : today, // firstDate: today, // lastDate: DateTime(2100), // ); if (pickedDate != null && pickedDate != _selectedCheckOutDate) { setState(() { _selectedCheckOutDate = pickedDate; textControllers["_forexStartDate"]?.text = DateFormat( 'dd-MM-yyyy', ).format(pickedDate); }); } } ; Future _selectForexEndDate(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; if (flightLastTripDateNotifier.value != null) { try { initialDate = DateTime.parse(flightLastTripDateNotifier.value!); } catch (e) { initialDate = today; } } else { initialDate = today; } // Use previously selected date if valid if (_selectedCheckOutDate != null && _selectedCheckOutDate!.isAfter(today)) { initialDate = _selectedCheckOutDate!; } final pickedDate = await showDatePicker( context: context, initialDate: initialDate, firstDate: initialDate, lastDate: DateTime(2100), initialEntryMode: DatePickerEntryMode.calendarOnly, ); // DateTime? pickedDate = await showDatePicker( // context: context, // initialDate: // _selectedEndDate != null && _selectedEndDate!.isAfter(today) // ? _selectedEndDate! // : today, // firstDate: today, // lastDate: DateTime(2100), // ); if (pickedDate != null && pickedDate != _selectedEndDate) { setState(() { _selectedEndDate = pickedDate; textControllers["_forexEndDate"]?.text = DateFormat( 'dd-MM-yyyy', ).format(pickedDate); // textControllers["_forexEndDate"]?.text = // DateFormat('dd-MM-yyyy').format(initialDate); }); } } return [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Start Date *", style: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w600, color: Color(0xFF575A74), ), ), SizedBox(height: 5), CustomTextFieldForexWrapper( isFocused: focusStates["_forexStartDate"] ?? false, isDesktop: isDesktop, child: SizedBox( height: 40, child: GestureDetector( // onTap: () async{ // _selectCheckOutDate(context); // // }, onTap: () async { focusNodes["_forexStartDate"]?.requestFocus(); await _selectCheckOutDate(context); if (textControllers["_forexEndDate"]!.text.isNotEmpty) { DateTime? startDate = _parseDate( textControllers["_forexStartDate"]!.text, ); DateTime? endDate = _parseDate( textControllers["_forexEndDate"]!.text, ); if (startDate != null && endDate != null) { if (endDate.isBefore(startDate)) { setState(() { errorMessages["end_date"] = "End date cannot be earlier than start date"; }); } else if (endDate.isAtSameMomentAs(startDate)) { setState(() { errorMessages["end_date"] = "Start and end dates cannot be the same"; }); } else { setState(() { errorMessages.remove("end_date"); }); } } // if (startDate != null && // endDate != null && // endDate.isBefore(startDate)) { // setState(() { // errorMessages["end_date"] = // "End date cannot be earlier than start date"; // }); // } else { // setState(() { // errorMessages.remove("end_date"); // }); // } } }, child: AbsorbPointer( child: TextField( focusNode: focusNodes["_forexStartDate"], controller: textControllers["_forexStartDate"], readOnly: true, style: const TextStyle(fontSize: 12), decoration: InputDecoration( labelText: "Select Date", labelStyle: const TextStyle( fontSize: 12, color: Colors.grey, ), floatingLabelBehavior: FloatingLabelBehavior.never, border: InputBorder.none, contentPadding: const EdgeInsets.symmetric(vertical: 16), suffixIcon: const Icon( Icons.calendar_today, size: 16, color: Colors.grey, ), ), ), ), ), ), ), if (errorMessages["start_date"] != null) ...[ SizedBox(height: 5), // Space before error message Text( errorMessages["start_date"]!, style: TextStyle(color: Colors.red, fontSize: 12), ), ], ], ), if (isDesktop) Spacer() else SizedBox(height: 8), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "End Date *", style: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w600, color: Color(0xFF575A74), ), ), SizedBox(height: 5), CustomTextFieldForexWrapper( isFocused: focusStates["_forexEndDate"] ?? false, isDesktop: isDesktop, child: SizedBox( height: 40, child: GestureDetector( // onTap: () => _selectForexEndDate(context), onTap: () async { focusNodes["_forexEndDate"]?.requestFocus(); await _selectForexEndDate(context); if (textControllers["_forexEndDate"]!.text.isNotEmpty) { DateTime? startDate = _parseDate( textControllers["_forexStartDate"]!.text, ); DateTime? endDate = _parseDate( textControllers["_forexEndDate"]!.text, ); if (startDate != null && endDate != null && endDate.isBefore(startDate)) { setState(() { errorMessages["end_date"] = "End date cannot be earlier than start date"; }); } else if (endDate!.isAtSameMomentAs(startDate!)) { setState(() { errorMessages["end_date"] = "Start and end dates cannot be the same"; }); } else { setState(() { errorMessages.remove("end_date"); }); } } }, child: AbsorbPointer( child: TextField( focusNode: focusNodes["_forexEndDate"], controller: textControllers["_forexEndDate"], 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["end_date"] != null) ...[ SizedBox(height: 5), // Space before error message Text( errorMessages["end_date"]!, style: const TextStyle(color: Colors.red, fontSize: 12), maxLines: 2, // Allow it to wrap onto two lines overflow: TextOverflow.ellipsis, // Add ellipsis if it still overflows ), ], ], ), if (isDesktop) Spacer() else SizedBox(height: 8), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Duration", style: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w600, color: Color(0xFF575A74), ), ), SizedBox(height: 5), CustomTextFieldForexWrapper( isFocused: focusStates["_durationFocused"] ?? false, isDesktop: isDesktop, color: Colors.transparent, child: SizedBox( height: 40, child: Padding( padding: const EdgeInsets.all(8.0), child: Text( // "dur", // selectedDuration?.isNotEmpty == true ? selectedDuration! : "Duration", selectedDuration ?? "Duration", style: const TextStyle( fontSize: 12, fontWeight: FontWeight.w600, color: Color(0xFF575A74), ), // decoration: const InputDecoration( // labelText: "To", // labelStyle: TextStyle(fontSize: 12, color: Colors.grey), // floatingLabelBehavior: FloatingLabelBehavior.never, // border: InputBorder.none, // contentPadding: EdgeInsets.symmetric(vertical: 16), // ), ), ), ), ), ], ), ]; } List _builClassType(bool isDesktop) { late Map countryMap; // Mapping country_code -> country_name late List countryCodes; // List of country codes countryList = widget.apiCountryData ?? []; // 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(); countryMap = { for (var country in countryList) (country['country_code'] ?? ''): '${country['country_name'] ?? ''} (${country['country_code'] ?? ''})', }; countryCodes = countryMap.keys.toList(); Map tempCountryMap = {}; selectedCountry ??= null; // // Set default selected value // if (selectedCountry == null && countryCodes.isNotEmpty) { // selectedCountry = countryCodes.first; // } // -------------------------- End Selected Country Dropdown -------------------------------------- return [ // if (isDesktop) Spacer() else SizedBox( // height: 8, // ), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Country*", style: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w600, color: Color(0xFF575A74), ), ), SizedBox(height: 5), // CustomTextFieldForexWrapper( // isFocused: focusStates["_countries"] ?? false, // isDesktop: isDesktop, // child: SizedBox( // height: 40, // child: DropdownSearch( // selectedItem: countryMap[selectedCountry], // popupProps: PopupProps.menu( // showSearchBox: true, // Enables search functionality // searchFieldProps: TextFieldProps( // decoration: InputDecoration( // hintText: "Search ...", // 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 ", // 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; // _onCountryChanged(selectedCountry); // }); // }, // ), // ), // ), CustomTextFieldForexWrapper( isFocused: focusStates["_countriesFocused"] ?? false, isDesktop: isDesktop, child: SizedBox( height: 40, child: Focus( focusNode: focusNodes["_countriesFocusNode"], onFocusChange: (hasFocus) { setState(() { focusStates["_countriesFocused"] = hasFocus; }); }, child: GestureDetector( // onTap: () { // Request focus when user taps focusNodes["_countriesFocusNode"]?.requestFocus(); }, child: DropdownSearch( selectedItem: countryMap[selectedCountry], popupProps: PopupProps.menu( showSearchBox: true, fit: FlexFit.loose, constraints: BoxConstraints(maxHeight: 250), menuProps: const MenuProps( backgroundColor: Colors.white, ), itemBuilder: (context, item, isSelected) { print("contryItem - $item"); final match = RegExp( r'^(.*)\s\((.*)\)$', ).firstMatch(item); final countryName = match?.group(1) ?? ''; final countryCode = match?.group(2) ?? ''; return Container( color: Colors.white, padding: EdgeInsets.symmetric( horizontal: 10, vertical: 6, ), child: Padding( padding: const EdgeInsets.only(right: 2.0), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( countryName, style: GoogleFonts.poppins( fontSize: 11.5, ), ), Text( countryCode, style: GoogleFonts.poppins( fontSize: 11.5, color: Colors.grey, ), ), ], ), ), ); }, searchFieldProps: TextFieldProps( decoration: InputDecoration( hintText: "Search ...", 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( alignment: Alignment.centerLeft, child: Text( selectedItem ?? "Select ", style: GoogleFonts.poppins( fontSize: 12, color: Colors.black, ), ), ), onChanged: (String? newValue) { setState(() { // Find the country_code based on selected country_name selectedCountry = countryMap.entries .firstWhere( (entry) => entry.value == newValue, ) .key; _onCountryChanged(selectedCountry); }); }, ), ), ), ), ), if (errorMessages["country_code"] != null) ...[ SizedBox(height: 5), // Space before error message Text( errorMessages["country_code"]!, style: TextStyle(color: Colors.red, fontSize: 12), ), ], ], ), ], ), // if (isDesktop)SizedBox(width: 8,) else SizedBox( // height: 8, // ), if (isDesktop) Spacer() // SizedBox(width: MediaQuery.of(context).size.width * 0.048) else SizedBox(height: 8), Column( crossAxisAlignment: CrossAxisAlignment.center, children: [ Text( "Currency *", style: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w600, color: Color(0xFF575A74), ), ), SizedBox(height: 5), CustomTextFieldForexWrapper( isFocused: focusStates["_currencyFocused"] ?? false, isDesktop: isDesktop, color: Colors.transparent, // width: isDesktop // ? MediaQuery.of(context).size.width * 0.330 // : MediaQuery.of(context).size.width * 0.66, child: SizedBox( height: 40, child: Padding( padding: const EdgeInsets.all(8.0), child: Center( child: Text( // "cur", // "${selectedCurrency}", selectedCurrency ?? "Currency", // selectedCurrency?.isNotEmpty == true ? selectedCurrency! : "Currency", style: const TextStyle( fontSize: 12, fontWeight: FontWeight.w600, color: Color(0xFF575A74), ), ), ), ), ), ), if (errorMessages["currency"] != null) ...[ SizedBox(height: 5), // Space before error message Text( errorMessages["currency"]!, style: TextStyle(color: Colors.red, fontSize: 12), ), ], ], ), // if (isDesktop)SizedBox(width: 8,) else SizedBox(height: 8,), if (isDesktop) Spacer() else SizedBox(height: 8), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Perdiem Amount", style: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w600, color: Color(0xFF575A74), ), ), SizedBox(height: 5), CustomTextFieldForexWrapper( isFocused: focusStates["_perdiemAmount"] ?? false, isDesktop: isDesktop, color: Colors.transparent, child: SizedBox( height: 35, child: Padding( padding: const EdgeInsets.all(8.0), child: Text( // "amo", selectedPerdiemAmount ?? "Amount", // selectedPerdiemAmount?.isNotEmpty == true ? selectedPerdiemAmount! : "Amount", style: const TextStyle( fontSize: 12, fontWeight: FontWeight.w600, color: Color(0xFF575A74), ), // decoration: const InputDecoration( // labelText: "To", // labelStyle: TextStyle(fontSize: 12, color: Colors.grey), // floatingLabelBehavior: FloatingLabelBehavior.never, // border: InputBorder.none, // contentPadding: EdgeInsets.symmetric(vertical: 16), // ), ), ), ), ), ], ), // if (isDesktop) // Spacer() // else // SizedBox( // height: 8, // ), // if (isDesktop)Spacer() else SizedBox(height: 8,), ]; } List _buildSecondRow(bool isDesktop) { return [ if (isDesktop) Spacer(), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Transport", style: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w500, color: Color(0xFF575A74), ), ), SizedBox(height: 5), Container( padding: const EdgeInsets.only(left: 10), color: Colors.yellow.shade50, width: isDesktop ? MediaQuery.of(context).size.width * 0.07 : null, height: 30, child: TextField( focusNode: focusNodes["_transport"], controller: textControllers["_transport"], onChanged: (value) { setState(() { userEdited = true; }); _onFieldChangedForOthers(); }, keyboardType: TextInputType.numberWithOptions(decimal: true), inputFormatters: [ FilteringTextInputFormatter.allow( RegExp(r'^\d*\.?\d*$'), ), // Allow only positive numbers with optional decimal ], style: const TextStyle(fontSize: 12), decoration: const InputDecoration( labelText: "0", labelStyle: TextStyle(fontSize: 12, color: Colors.black), floatingLabelBehavior: FloatingLabelBehavior.never, border: InputBorder.none, contentPadding: EdgeInsets.symmetric(vertical: 16), ), ), ), // CustomTextFieldItnerarySubWrapper( // width: isDesktop ? MediaQuery.of(context).size.width * 0.07 : null, // isFocused: focusStates["_transport"] ?? false, // isDesktop: isDesktop, // child: SizedBox( // height: 40, // child: TextField( // focusNode: focusNodes["_transport"], // controller: textControllers["_transport"], // onChanged: (value) => _onFieldChangedForOthers(), // keyboardType: TextInputType.numberWithOptions(decimal: true), // inputFormatters: [ // FilteringTextInputFormatter.allow(RegExp( // r'^\d*\.?\d*$')), // Allow only positive numbers with optional decimal // ], // style: const TextStyle(fontSize: 12), // decoration: const InputDecoration( // labelText: "Transport", // labelStyle: TextStyle(fontSize: 12, color: Colors.grey), // floatingLabelBehavior: FloatingLabelBehavior.never, // border: InputBorder.none, // contentPadding: EdgeInsets.symmetric(vertical: 16), // ), // ), // ), // ), ], ), if (isDesktop) SizedBox(width: 10) else SizedBox(height: 8), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Accomodation", style: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w500, color: Color(0xFF575A74), ), ), SizedBox(height: 5), Container( padding: const EdgeInsets.only(left: 10), color: Colors.yellow.shade50, width: isDesktop ? MediaQuery.of(context).size.width * 0.07 : null, height: 30, child: TextField( focusNode: focusNodes["_accomodation"], controller: textControllers["_accomodation"], onChanged: (value) { setState(() { userEdited = true; }); _onFieldChangedForOthers(); }, keyboardType: TextInputType.numberWithOptions(decimal: true), inputFormatters: [ FilteringTextInputFormatter.allow( RegExp(r'^\d*\.?\d*$'), ), // Allow only positive numbers with optional decimal ], style: const TextStyle(fontSize: 12), decoration: const InputDecoration( labelText: "0", labelStyle: TextStyle(fontSize: 12, color: Colors.black), floatingLabelBehavior: FloatingLabelBehavior.never, border: InputBorder.none, contentPadding: EdgeInsets.symmetric(vertical: 16), ), ), ), // CustomTextFieldItnerarySubWrapper( // width: isDesktop ? MediaQuery.of(context).size.width * 0.07 : null, // isFocused: focusStates["_accomodation"] ?? false, // isDesktop: isDesktop, // child: SizedBox( // height: 40, // child: TextField( // focusNode: focusNodes["_accomodation"], // controller: textControllers["_accomodation"], // onChanged: (value) => _onFieldChangedForOthers(), // keyboardType: TextInputType.numberWithOptions(decimal: true), // inputFormatters: [ // FilteringTextInputFormatter.allow(RegExp( // r'^\d*\.?\d*$')), // Allow only positive numbers with optional decimal // ], // style: const TextStyle(fontSize: 12), // decoration: const InputDecoration( // labelText: "Accomodation", // labelStyle: TextStyle(fontSize: 12, color: Colors.grey), // floatingLabelBehavior: FloatingLabelBehavior.never, // border: InputBorder.none, // contentPadding: EdgeInsets.symmetric(vertical: 16), // ), // ), // ), // ), ], ), if (isDesktop) SizedBox(width: 10) else SizedBox(height: 8), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Telephone", style: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w500, color: Color(0xFF575A74), ), ), SizedBox(height: 5), Container( color: Colors.yellow.shade50, padding: const EdgeInsets.only(left: 10), width: isDesktop ? MediaQuery.of(context).size.width * 0.06 : null, height: 30, child: TextField( focusNode: focusNodes["_telephone"], controller: textControllers["_telephone"], onChanged: (value) { setState(() { userEdited = true; }); _onFieldChangedForOthers(); }, keyboardType: TextInputType.numberWithOptions(decimal: true), inputFormatters: [ FilteringTextInputFormatter.allow( RegExp(r'^\d*\.?\d*$'), ), // Allow only positive numbers with optional decimal ], style: const TextStyle(fontSize: 12), decoration: const InputDecoration( labelText: "0", labelStyle: TextStyle(fontSize: 12, color: Colors.black), floatingLabelBehavior: FloatingLabelBehavior.never, border: InputBorder.none, contentPadding: EdgeInsets.symmetric(vertical: 16), ), ), ), // CustomTextFieldItnerarySubWrapper( // width: isDesktop ? MediaQuery.of(context).size.width * 0.07 : null, // isFocused: focusStates["_telephone"] ?? false, // isDesktop: isDesktop, // child: SizedBox( // height: 40, // child: TextField( // focusNode: focusNodes["_telephone"], // controller: textControllers["_telephone"], // onChanged: (value) => _onFieldChangedForOthers(), // keyboardType: TextInputType.numberWithOptions(decimal: true), // inputFormatters: [ // FilteringTextInputFormatter.allow(RegExp( // r'^\d*\.?\d*$')), // Allow only positive numbers with optional decimal // ], // style: const TextStyle(fontSize: 12), // decoration: const InputDecoration( // labelText: "Telephone", // labelStyle: TextStyle(fontSize: 12, color: Colors.grey), // floatingLabelBehavior: FloatingLabelBehavior.never, // border: InputBorder.none, // contentPadding: EdgeInsets.symmetric(vertical: 16), // ), // ), // ), // ), ], ), if (isDesktop) SizedBox(width: 40) else SizedBox(height: 8), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Other Expenses", style: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w600, color: Color(0xFF575A74), ), ), SizedBox(height: 5), CustomTextFieldForexWrapper( isFocused: false, isDesktop: isDesktop, color: Colors.transparent, child: SizedBox( height: 35, child: Padding( padding: const EdgeInsets.all(8.0), child: Text( CalculatedOtherExpenses ?? "0", // focusNode: _toFocusNode, // controller: _toController, style: const TextStyle( fontSize: 12, fontWeight: FontWeight.w600, color: Color(0xFF575A74), ), // decoration: const InputDecoration( // labelText: "To", // labelStyle: TextStyle(fontSize: 12, color: Colors.grey), // floatingLabelBehavior: FloatingLabelBehavior.never, // border: InputBorder.none, // contentPadding: EdgeInsets.symmetric(vertical: 16), // ), ), ), ), ), ], ), ]; } List _buildCardDetailsRow(bool isDesktop) { List purposeList = widget.apiData?['flight_class'] ?? []; List> dropdownItems = purposeList .map( (item) => DropdownMenuItem( value: item['dropdown_value'], 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 String? selectedPurpose = dropdownItems.isNotEmpty ? dropdownItems.first.value : null; return [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Cash*", style: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w600, color: Color(0xFF575A74), ), ), SizedBox(height: 5), CustomTextFieldForexWrapper( isFocused: focusStates["_cash"] ?? false, isDesktop: isDesktop, child: SizedBox( height: 35, child: TextField( focusNode: focusNodes["_cash"], controller: textControllers["_cash"], keyboardType: TextInputType.number, inputFormatters: [ FilteringTextInputFormatter.digitsOnly, // ✅ Only allow digits ], style: const TextStyle(fontSize: 12), // keyboardType: TextInputType.number, onChanged: (value) { // errorMessages["deposit_on_cash"] = ""; errorMessages.remove("deposit_on_cash"); errorMessages.remove("deposit_on_card"); // errorMessages["deposit_on_card"] = ""; setState(() { userEdited = true; }); _validateCashAmount( value, ); // Call validation when text changes }, decoration: const InputDecoration( labelText: "Cash", labelStyle: TextStyle(fontSize: 12, color: Colors.grey), floatingLabelBehavior: FloatingLabelBehavior.never, border: InputBorder.none, contentPadding: EdgeInsets.symmetric(vertical: 16), ), ), ), ), if (errorMessages["deposit_on_cash"] != null) ...[ SizedBox(height: 5), // Space before error message Text( // "Required", errorMessages["deposit_on_cash"]!, style: TextStyle(color: Colors.red, fontSize: 12), ), ], ], ), if (isDesktop) Spacer() else SizedBox(height: 8), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Card*", style: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w600, color: Color(0xFF575A74), ), ), SizedBox(height: 5), CustomTextFieldForexWrapper( isFocused: focusStates["_card"] ?? false, isDesktop: isDesktop, child: SizedBox( height: 35, child: TextField( focusNode: focusNodes["_card"], controller: textControllers["_card"], style: const TextStyle(fontSize: 12), keyboardType: TextInputType.number, inputFormatters: [ FilteringTextInputFormatter.digitsOnly, // ✅ Only allow digits ], onChanged: (value) { errorMessages.remove("deposit_on_cash"); errorMessages.remove("deposit_on_card"); userEdited = true; _validateCardAmount( value, ); // Call validation when text changes }, decoration: const InputDecoration( labelText: "Card", labelStyle: TextStyle(fontSize: 12, color: Colors.grey), floatingLabelBehavior: FloatingLabelBehavior.never, border: InputBorder.none, contentPadding: EdgeInsets.symmetric(vertical: 16), ), ), ), ), if (errorMessages["deposit_on_card"] != null) ...[ SizedBox(height: 5), // Space before error message Text( errorMessages["deposit_on_card"]!, style: TextStyle(color: Colors.red, fontSize: 12), ), ], ], ), if (isDesktop) Spacer() else SizedBox(height: 8), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Total Amount", style: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w600, color: Color(0xFF575A74), ), ), SizedBox(height: 5), CustomTextFieldForexWrapper( isFocused: focusStates["_perdiemAmount"] ?? false, isDesktop: isDesktop, color: Colors.transparent, child: SizedBox( height: 35, child: Padding( padding: const EdgeInsets.all(8.0), child: Text( // "amo", selectedQuotedAmount ?? "0", // selectedPerdiemAmount?.isNotEmpty == true ? selectedPerdiemAmount! : "Amount", style: const TextStyle( fontSize: 13, fontWeight: FontWeight.w600, color: Color(0xFF575A74), ), ), ), ), ), // Container( // width: isDesktop ? MediaQuery.of(context).size.width * 0.15 : null, // // width: MediaQuery.of(context).size.width * 0.035, // height: // isDesktop ? MediaQuery.of(context).size.height * 0.09 : null, // child: Row( // crossAxisAlignment: CrossAxisAlignment.end, // mainAxisAlignment: MainAxisAlignment.end, // children: [ // Text( // "Total Amount", // style: TextStyle( // fontSize: 13, // fontWeight: FontWeight.w600, // color: Color(0xFF575A74)), // ), // ], // ), // ), ], ), // if (isDesktop) // SizedBox( // width: 35, // ) // else // SizedBox( // height: 8, // ), // // Column( // // crossAxisAlignment: CrossAxisAlignment.start, // children: [ // // SizedBox(height: 5), // Container( // // width: MediaQuery.of(context).size.width * 0.15, // height: // isDesktop ? MediaQuery.of(context).size.height * 0.09 : null, // padding: isDesktop ? const EdgeInsets.only(left: 12.0) : null, // child: Row( // crossAxisAlignment: CrossAxisAlignment.end, // mainAxisAlignment: MainAxisAlignment.start, // children: [ // Text( // // "amo", // selectedQuotedAmount ?? "0", // // selectedPerdiemAmount?.isNotEmpty == true ? selectedPerdiemAmount! : "Amount", // style: const TextStyle( // fontSize: 13, // fontWeight: FontWeight.w600, // color: Color(0xFF575A74)), // ), // ], // ), // ), // // CustomTextFieldItnerarySubWrapper( // // isFocused: focusStates["_perdiemAmount"] ?? false, // // isDesktop: isDesktop, // // color: Colors.transparent, // // child: SizedBox( // // height: 40, // // child: Padding( // // padding: const EdgeInsets.all(8.0), // // child: // // ), // // ), // // ), // ], // ), // Column( // crossAxisAlignment: CrossAxisAlignment.start, // children: [ // Text( // "Currency*", // style: TextStyle( // fontSize: 12, // fontWeight: FontWeight.w600, // color: Color(0xFF575A74)), // ), // SizedBox(height: 5), // CustomTextFieldItnerarySubWrapper( // isFocused: focusStates["_currency"] ?? false, // isDesktop: isDesktop, // child: SizedBox( // height: 40, // // child: DropdownButtonFormField( // focusNode: focusNodes["_currencyFocusNode"], // // controller: _hotelNameController, // value: selectedPurpose, // style: TextStyle(fontSize: 12), // decoration: InputDecoration( // border: InputBorder.none, // contentPadding: EdgeInsets.symmetric( // horizontal: 10), // Proper padding // ), // onChanged: purposeList.isNotEmpty // ? (newValue) { // setState(() { // selectedPurpose = newValue; // }); // } // : null, // items: dropdownItems, // ), // ), // ), // ], // ), ]; } List _buildCommetsRow(bool isDesktop) { return [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Comments", style: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w600, color: Color(0xFF575A74), ), ), SizedBox(height: 5), CustomTextFieldForexWrapper( isFocused: focusStates["_comments"] ?? false, // Dropdown doesn't use focus isDesktop: isDesktop, width: isDesktop ? MediaQuery.of(context).size.width * 0.45 : null, child: SizedBox( height: 35, child: TextField( focusNode: focusNodes["_comments"], controller: textControllers["_comments"], style: TextStyle(fontSize: 12), decoration: InputDecoration( labelText: "Comments", labelStyle: TextStyle(fontSize: 12, color: Colors.grey), floatingLabelBehavior: FloatingLabelBehavior.never, border: InputBorder.none, contentPadding: EdgeInsets.symmetric(vertical: 16), ), ), ), ), ], ), if (isDesktop) Spacer(), SizedBox(height: 5), // Actions row remains a Row Column( children: [ Row( mainAxisAlignment: MainAxisAlignment.end, children: _handleAction(isDesktop), ), ], ), ]; } List _buildForexCard(bool isDesktop) { return [ !isChecked ? Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Card Number*", style: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w600, color: Color(0xFF575A74), ), ), SizedBox(height: 5), CustomTextFieldForexWrapper( isFocused: focusStates["_cardNumber"] ?? false, isDesktop: isDesktop, child: SizedBox( height: 35, child: TextField( enabled: !isChecked, focusNode: focusNodes["_cardNumber"], controller: textControllers["_cardNumber"], style: const TextStyle(fontSize: 12), inputFormatters: [ FilteringTextInputFormatter.allow(RegExp(r'[0-9\s]')), LengthLimitingTextInputFormatter(16), ], onChanged: (value) { setState(() { errorMessages.remove("card_number"); }); }, decoration: const InputDecoration( labelText: "Card Number", labelStyle: TextStyle(fontSize: 12, color: Colors.grey), floatingLabelBehavior: FloatingLabelBehavior.never, border: InputBorder.none, contentPadding: EdgeInsets.symmetric(vertical: 16), ), ), ), ), if (errorMessages["card_number"] != null) ...[ SizedBox(height: 5), // Space before error message Text( errorMessages["card_number"]!, style: TextStyle(color: Colors.red, fontSize: 12), ), ], ], ) : Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Delivery Location *", style: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w600, color: Color(0xFF575A74), ), ), SizedBox(height: 5), CustomTextFieldForexWrapper( isFocused: focusStates["_deliveryLocation"] ?? false, // Dropdown doesn't use focus isDesktop: isDesktop, width: isDesktop ? MediaQuery.of(context).size.width * 0.45 : null, child: SizedBox( height: 35, child: TextField( focusNode: focusNodes["_deliveryLocation"], controller: textControllers["_deliveryLocation"], onChanged: (value) { errorMessages.remove("delivery_location"); }, style: TextStyle(fontSize: 12), decoration: InputDecoration( labelText: "Location", labelStyle: TextStyle(fontSize: 12, color: Colors.grey), floatingLabelBehavior: FloatingLabelBehavior.never, border: InputBorder.none, contentPadding: EdgeInsets.symmetric(vertical: 16), ), ), ), ), if (errorMessages["delivery_location"] != null) ...[ SizedBox(height: 5), // Space before error message Text( "Required", style: TextStyle(color: Colors.red, fontSize: 12), ), ], ], ), if (isDesktop) SizedBox(width: MediaQuery.of(context).size.width * 0.04) else SizedBox(height: 8), Column( children: [ Row( crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: isDesktop ? MainAxisAlignment.center : MainAxisAlignment.start, children: [ Container( alignment: Alignment.bottomLeft, child: Transform.scale( scale: 0.8, child: Checkbox( value: isChecked, activeColor: Color(0xFF114D8B), // checkColor: Color(0xFF114D8B), side: BorderSide( color: Colors.grey, // Change border color width: 1, // Adjust thickness ), onChanged: (bool? value) { setState(() { errorMessages.remove("card_number"); isChecked = value!; // if (isChecked) { // textControllers["_cardNumber"] // ?.clear(); // Clear the value when isChecked is true // } }); }, ), ), ), Text( "Check If You Don't Have a Forex Account", style: GoogleFonts.poppins( fontSize: 10, fontWeight: FontWeight.w600, color: Color(0xFF575A74), ), ), ], ), ], ), ]; } List _handleAction(bool isDesktop) { return [ // Close Button ElevatedButton( onPressed: () { widget.onClose("Forex", false); // Close the dialog or screen }, style: ElevatedButton.styleFrom( backgroundColor: Colors.grey[400], // Light grey color shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12), ), child: Text( "Close", style: GoogleFonts.poppins(color: Colors.white, fontSize: 12), ), ), SizedBox(width: 10), // Space between buttons // Save Changes Button ElevatedButton( onPressed: () { handleSave(); }, style: ElevatedButton.styleFrom( backgroundColor: Color(0xFF114D8B), // Primary color for save shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12), ), child: Text( "Save Changes", style: GoogleFonts.poppins(color: Colors.white, fontSize: 12), ), ), ]; } }