ts-tat/lib/Screens/itnerary/forex.dart
2025-10-27 17:35:57 +05:30

2470 lines
85 KiB
Dart

import 'dart:convert';
import 'package:dropdown_search/dropdown_search.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:frontend/services/apiService.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<Map<String, dynamic>> flightData;
final Map<String, dynamic>? apiData;
final Function(String, bool) onClose;
final Map<String, dynamic>? selectedItem;
final List<dynamic>? apiCountryData;
final Function(Map<String, dynamic>) 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<ForexScreen> {
final ApiService apiService = ApiService();
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
late ValueNotifier<String?> flightFirstTripDateNotifier;
late ValueNotifier<String?> flightLastTripDateNotifier;
late ValueNotifier<String?> flightFirstTripToPlaceNotifier;
// late final tripuserId;
String? tripuserId;
// late String? userCardNumber;
Map<String, String?> selectedValues = {};
bool isChecked = false; // State variable for checkbox
int fifteenPercent = 0;
int remainingAmount = 0;
bool isWidget = false;
Map<String, FocusNode> focusNodes = {};
Map<String, bool> focusStates = {};
Map<String, TextEditingController> textControllers = {};
List<dynamic> countryList = [];
List<String> 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<String, String> errorMessages = {};
String? selectedCountry;
String? selectedCurrency;
String? selectedDuration;
String? selectedPerdiemAmount;
String? CalculatedOtherExpenses;
String? selectedQuotedAmount;
int? selectedCashPercent;
int? selectedCardPercent;
bool userEdited = false;
Map<String, dynamic> get forexData {
Map<String, dynamic> 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<String, dynamic> get getForexData {
return {
"country_code": selectedCountry,
"start_date": _formatDate(textControllers["_forexStartDate"]?.text),
"end_date": _formatDate(textControllers["_forexEndDate"]?.text),
"user_id": tripuserId,
// "currency": selectedCurrency ?? "",
};
}
Future<String?> getToken() async {
final prefs = await SharedPreferences.getInstance();
return prefs.getString('auth_token');
}
Future<void> postgetForexData(Map<String, dynamic> 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',
'app-signature': 'ts-traveltool-2025-signature-123456',
},
body: jsonEncode(forexData), // Convert map to JSON
);
if (response.statusCode == 200) {
print("Plan submitted successfully!");
print("Response: ${response.body}");
final Map<String, dynamic> 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 if (response.statusCode == 403) {
print("403-FORB");
await apiService.logout(context);
return null;
// throw Exception('Failed to load users');
} else {
print("Failed to submit plan. Status: ${response.statusCode}");
print("Error: ${response.body}");
}
} catch (e) {
print(" Error submitting plan: $e");
}
}
bool isValidForexData(Map<String, dynamic> data) {
// Required fields that must not be empty
List<String> 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<String, String?> getFlightTripDateRange(
List<Map<String, dynamic>> flightData,
) {
final allTrips =
flightData
.expand((flight) => flight['trips'] ?? [])
.whereType<Map<String, dynamic>>()
.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<String, dynamic> 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<String?>(null);
flightLastTripDateNotifier = ValueNotifier<String?>(null);
flightFirstTripToPlaceNotifier = ValueNotifier<String?>(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) {
print('tripuserId - $tripuserId');
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()) {
print('_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<Widget> _buildAccomadtionForm(bool isDesktop) {
List<Widget> buildResponsiveRow(List<Widget> children) {
return [
isDesktop ? Row(children: children) : Column(children: children),
SizedBox(height: 10),
];
}
// List<List<Widget>> rowBuilders = [
//
// _builClassType(isDesktop),
// _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<Widget> _buildFirstRow(isDesktop) {
DateTime? _selectedCheckOutDate;
DateTime? _selectedEndDate;
Future<void> _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<void> _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"],
inputFormatters: [
FilteringTextInputFormatter.allow(
RegExp(r'[a-zA-Z0-9 _-]'),
),
],
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"],
inputFormatters: [
FilteringTextInputFormatter.allow(
RegExp(r'[a-zA-Z0-9 _-]'),
),
],
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<Widget> _builClassType(bool isDesktop) {
late Map<String, String> countryMap; // Mapping country_code -> country_name
late List<String> 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<String, String> 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<String>(
// 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<String>(
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:
isDesktop ? CrossAxisAlignment.center : CrossAxisAlignment.start,
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:
isDesktop
? Center(child: currencyText())
: Container(child: currencyText()),
// 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,),
];
}
Widget currencyText() => Text(
selectedCurrency ?? "Currency",
style: const TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74),
),
);
List<Widget> _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<Widget> _buildCardDetailsRow(bool isDesktop) {
List<dynamic> purposeList = widget.apiData?['flight_class'] ?? [];
List<DropdownMenuItem<String>> dropdownItems =
purposeList
.map(
(item) => DropdownMenuItem<String>(
value: item['dropdown_value'],
child: Text(item['dropdown_value']),
),
)
.toList();
if (dropdownItems.isEmpty) {
dropdownItems.add(
DropdownMenuItem<String>(
value: null,
child: Text(
"No options available",
style: TextStyle(color: Colors.grey),
),
),
);
}
// 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<String>(
// 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<Widget> _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"],
inputFormatters: [
FilteringTextInputFormatter.allow(RegExp(r'[a-zA-Z0-9 _-]')),
],
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<Widget> _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");
},
inputFormatters: [
FilteringTextInputFormatter.allow(
RegExp(r'[a-zA-Z0-9 _-]'),
),
],
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<Widget> _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),
),
),
];
}
}