1755 lines
57 KiB
Dart
1755 lines
57 KiB
Dart
import 'dart:convert';
|
|
|
|
import 'package:dropdown_search/dropdown_search.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter/services.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(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 GlobalKey<FormState> _formKey = GlobalKey<FormState>();
|
|
|
|
late ValueNotifier<String?> flightFirstTripDateNotifier;
|
|
late ValueNotifier<String?> flightLastTripDateNotifier;
|
|
|
|
late String? userCardNumber;
|
|
|
|
Map<String, String?> selectedValues = {};
|
|
bool isChecked = false; // State variable for checkbox
|
|
|
|
int fifteenPercent = 0;
|
|
int remainingAmount = 0;
|
|
|
|
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 {
|
|
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;
|
|
|
|
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,
|
|
"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),
|
|
// "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("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<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() ?? "";
|
|
});
|
|
_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<String, dynamic> data) {
|
|
errorMessages.clear(); // Reset errors
|
|
|
|
// Required fields that must not be empty
|
|
List<String> requiredFields = [
|
|
"start_date",
|
|
"end_date",
|
|
"country_code",
|
|
"deposit_on_card",
|
|
"deposit_on_cash",
|
|
// "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 validation for each field
|
|
for (String field in requiredFields) {
|
|
if (data[field] == null || data[field].toString().trim().isEmpty) {
|
|
errorMessages[field] = "This field is required";
|
|
}
|
|
}
|
|
|
|
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();
|
|
|
|
if (allTrips.isEmpty) {
|
|
return {
|
|
'firstTripDate': null,
|
|
'lastTripDate': 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;
|
|
|
|
return {
|
|
'firstTripDate': firstTrip['date'],
|
|
'lastTripDate': lastTrip['date'],
|
|
};
|
|
}
|
|
|
|
void handleSave() {
|
|
print("Handle Save forexData $forexData");
|
|
|
|
Map<String, dynamic> data = forexData;
|
|
|
|
if (!isValidForexData(data)) {
|
|
print("Validation Failed: Required fields are missing.");
|
|
setState(() {});
|
|
return; // Stop execution if validation fails
|
|
} else {
|
|
widget.onSaveForex(forexData); // Send object to parent
|
|
}
|
|
|
|
widget.onClose(false); // Close screen after saving
|
|
}
|
|
|
|
DateTime? _parseDate(String date) {
|
|
try {
|
|
return DateFormat("yyyy-MM-dd").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("textControllers - $textControllers");
|
|
|
|
// 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);
|
|
|
|
handleUpdatedField();
|
|
|
|
flightFirstTripDateNotifier = ValueNotifier<String?>(null);
|
|
flightLastTripDateNotifier = ValueNotifier<String?>(null);
|
|
|
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
final result = getFlightTripDateRange(widget.flightData);
|
|
flightFirstTripDateNotifier.value = result['firstTripDate'];
|
|
flightLastTripDateNotifier.value = result['lastTripDate'];
|
|
|
|
// ✅ Only set controller after value is updated
|
|
final parsedDate =
|
|
DateTime.tryParse(flightFirstTripDateNotifier.value ?? '');
|
|
if (parsedDate != null) {
|
|
textControllers["_forexStartDate"]?.text =
|
|
DateFormat('yyyy-MM-dd').format(parsedDate);
|
|
}
|
|
|
|
final parsedEndDate =
|
|
DateTime.tryParse(flightLastTripDateNotifier.value ?? '');
|
|
if (parsedEndDate != null) {
|
|
textControllers["_forexEndDate"]?.text =
|
|
DateFormat('yyyy-MM-dd').format(parsedEndDate);
|
|
}
|
|
});
|
|
}
|
|
|
|
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["_forexEndDate"] = initController("end_date");
|
|
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?;
|
|
selectedPerdiemAmount = widget.selectedItem!["perdiem_amount"] as String?;
|
|
isChecked =
|
|
widget.selectedItem!["have_card"] == "1"; // Convert string to bool
|
|
|
|
// 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);
|
|
}
|
|
|
|
// Handle field changes
|
|
void _onFieldChanged() {
|
|
if (_isForexDataComplete()) {
|
|
postgetForexData(getForexData);
|
|
}
|
|
}
|
|
|
|
// 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(2);
|
|
|
|
// 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?;
|
|
});
|
|
_divideQuotedAmount();
|
|
errorMessages.clear();
|
|
}
|
|
|
|
void _divideQuotedAmount() {
|
|
int? quotedAmount = int.tryParse(selectedQuotedAmount!);
|
|
print("DIVIDREFD - $selectedQuotedAmount -$quotedAmount");
|
|
if (quotedAmount != null) {
|
|
fifteenPercent =
|
|
(quotedAmount * 15) ~/ 100; // Calculate 15% (integer division)
|
|
remainingAmount = quotedAmount - fifteenPercent; // Subtract from total
|
|
|
|
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!;
|
|
|
|
print(
|
|
"CAsh - $cashAmount- CaRd- $enteredAmount - $calculateAmnt - selectedQuotedAmount - $qouteAmount");
|
|
|
|
if (enteredAmount == null || calculateAmnt > qouteAmount!) {
|
|
errorMessages["deposit_on_card"] = "Amount cannot exceed $qouteAmount";
|
|
} else {
|
|
errorMessages["deposit_on_card"] = ""; // Clear error if valid
|
|
}
|
|
|
|
// Refresh UI if using StatefulWidget
|
|
setState(() {});
|
|
}
|
|
|
|
void _validateCashAmount(String value) {
|
|
print("_validateCashAmount - $value - $fifteenPercent");
|
|
|
|
int? enteredAmount = int.tryParse(value);
|
|
|
|
if (enteredAmount == null || enteredAmount > fifteenPercent) {
|
|
errorMessages["deposit_on_cash"] = "Amount cannot exceed $fifteenPercent";
|
|
} else {
|
|
errorMessages["deposit_on_cash"] = ""; // Clear error if valid
|
|
}
|
|
|
|
// 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)
|
|
// ];
|
|
|
|
List<Widget> rowBuilders = [
|
|
..._builClassType(isDesktop), // Spread the List<Widget>
|
|
Divider(),
|
|
..._buildSecondRow(isDesktop), // Spread the List<Widget>
|
|
];
|
|
|
|
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);
|
|
} 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),
|
|
);
|
|
|
|
// 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('yyyy-MM-dd').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),
|
|
);
|
|
|
|
// 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('yyyy-MM-dd').format(pickedDate);
|
|
});
|
|
}
|
|
}
|
|
|
|
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();
|
|
|
|
selectedCountry ??= null;
|
|
|
|
// // Set default selected value
|
|
// if (selectedCountry == null && countryCodes.isNotEmpty) {
|
|
// selectedCountry = countryCodes.first;
|
|
// }
|
|
|
|
// -------------------------- End Selected Country Dropdown --------------------------------------
|
|
|
|
return [
|
|
Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
"Start Date",
|
|
style: TextStyle(
|
|
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 {
|
|
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 &&
|
|
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"],
|
|
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(
|
|
"Select 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: TextStyle(
|
|
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 {
|
|
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 {
|
|
setState(() {
|
|
errorMessages.remove("end_date");
|
|
});
|
|
}
|
|
}
|
|
},
|
|
|
|
child: AbsorbPointer(
|
|
child: TextField(
|
|
focusNode: focusNodes["_forexEndDate"],
|
|
controller: textControllers["_forexEndDate"],
|
|
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"]!,
|
|
// "Select End Date",
|
|
style: TextStyle(color: Colors.red, fontSize: 12),
|
|
),
|
|
],
|
|
],
|
|
),
|
|
if (isDesktop)
|
|
Spacer()
|
|
else
|
|
SizedBox(
|
|
height: 8,
|
|
),
|
|
Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
"Countries",
|
|
style: TextStyle(
|
|
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 Country...",
|
|
contentPadding: EdgeInsets.symmetric(horizontal: 10),
|
|
),
|
|
),
|
|
),
|
|
items: countryMap.values.toList(),
|
|
dropdownDecoratorProps: DropDownDecoratorProps(
|
|
dropdownSearchDecoration: InputDecoration(
|
|
border: InputBorder.none,
|
|
contentPadding: EdgeInsets.symmetric(
|
|
horizontal: 1,
|
|
),
|
|
),
|
|
),
|
|
dropdownBuilder: (context, selectedItem) => Align(
|
|
// Center-align selected item
|
|
alignment: Alignment.centerLeft,
|
|
child: Text(
|
|
selectedItem ?? "Select Country",
|
|
style: TextStyle(fontSize: 12),
|
|
),
|
|
),
|
|
onChanged: (String? newValue) {
|
|
setState(() {
|
|
// Find the country_code based on selected country_name
|
|
selectedCountry = countryMap.entries
|
|
.firstWhere((entry) => entry.value == newValue)
|
|
.key;
|
|
_onCountryChanged(selectedCountry);
|
|
});
|
|
},
|
|
),
|
|
),
|
|
),
|
|
if (errorMessages["country_code"] != null) ...[
|
|
SizedBox(height: 5), // Space before error message
|
|
Text(
|
|
"Select Country",
|
|
style: TextStyle(color: Colors.red, fontSize: 12),
|
|
),
|
|
],
|
|
],
|
|
),
|
|
];
|
|
}
|
|
|
|
List<Widget> _builClassType(bool isDesktop) {
|
|
return [
|
|
// if (isDesktop) Spacer() else SizedBox(
|
|
// height: 8,
|
|
// ),
|
|
Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
"Durartion",
|
|
style: TextStyle(
|
|
fontSize: 12,
|
|
fontWeight: FontWeight.w600,
|
|
color: Color(0xFF575A74)),
|
|
),
|
|
SizedBox(height: 5),
|
|
CustomTextFieldItnerarySubWrapper(
|
|
isFocused: focusStates["_durationFocused"] ?? false,
|
|
isDesktop: isDesktop,
|
|
color: Colors.transparent,
|
|
child: SizedBox(
|
|
height: 30,
|
|
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),
|
|
// ),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
// if (isDesktop)SizedBox(width: 8,) else SizedBox(
|
|
// height: 8,
|
|
// ),
|
|
if (isDesktop)
|
|
Spacer()
|
|
else
|
|
SizedBox(
|
|
height: 8,
|
|
),
|
|
Column(
|
|
crossAxisAlignment: CrossAxisAlignment.center,
|
|
children: [
|
|
Text(
|
|
"Currency *",
|
|
style: TextStyle(
|
|
fontSize: 12,
|
|
fontWeight: FontWeight.w600,
|
|
color: Color(0xFF575A74)),
|
|
),
|
|
SizedBox(height: 5),
|
|
CustomTextFieldItnerarySubWrapper(
|
|
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: 30,
|
|
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 (isDesktop)SizedBox(width: 8,) else SizedBox(height: 8,),
|
|
if (isDesktop)
|
|
Spacer()
|
|
else
|
|
SizedBox(
|
|
height: 8,
|
|
),
|
|
Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
"Perdiem Amount",
|
|
style: TextStyle(
|
|
fontSize: 12,
|
|
fontWeight: FontWeight.w600,
|
|
color: Color(0xFF575A74)),
|
|
),
|
|
SizedBox(height: 5),
|
|
CustomTextFieldItnerarySubWrapper(
|
|
isFocused: focusStates["_perdiemAmount"] ?? false,
|
|
isDesktop: isDesktop,
|
|
color: Colors.transparent,
|
|
child: SizedBox(
|
|
height: 30,
|
|
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<Widget> _buildSecondRow(bool isDesktop) {
|
|
return [
|
|
Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
"Transport",
|
|
style: TextStyle(
|
|
fontSize: 12,
|
|
fontWeight: FontWeight.w600,
|
|
color: Color(0xFF575A74)),
|
|
),
|
|
SizedBox(height: 5),
|
|
CustomTextFieldItnerarySubWrapper(
|
|
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)
|
|
Spacer()
|
|
else
|
|
SizedBox(
|
|
height: 8,
|
|
),
|
|
Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
"Accomodation",
|
|
style: TextStyle(
|
|
fontSize: 12,
|
|
fontWeight: FontWeight.w600,
|
|
color: Color(0xFF575A74)),
|
|
),
|
|
SizedBox(height: 5),
|
|
CustomTextFieldItnerarySubWrapper(
|
|
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)
|
|
Spacer()
|
|
else
|
|
SizedBox(
|
|
height: 8,
|
|
),
|
|
Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
"Telephone",
|
|
style: TextStyle(
|
|
fontSize: 12,
|
|
fontWeight: FontWeight.w600,
|
|
color: Color(0xFF575A74)),
|
|
),
|
|
SizedBox(height: 5),
|
|
CustomTextFieldItnerarySubWrapper(
|
|
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)
|
|
Spacer()
|
|
else
|
|
SizedBox(
|
|
height: 8,
|
|
),
|
|
Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
"Other Expenses",
|
|
style: TextStyle(
|
|
fontSize: 12,
|
|
fontWeight: FontWeight.w600,
|
|
color: Color(0xFF575A74)),
|
|
),
|
|
SizedBox(height: 5),
|
|
CustomTextFieldItnerarySubWrapper(
|
|
isFocused: false,
|
|
isDesktop: isDesktop,
|
|
color: Colors.transparent,
|
|
child: SizedBox(
|
|
height: 40,
|
|
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: TextStyle(
|
|
fontSize: 12,
|
|
fontWeight: FontWeight.w600,
|
|
color: Color(0xFF575A74)),
|
|
),
|
|
SizedBox(height: 5),
|
|
CustomTextFieldItnerarySubWrapper(
|
|
isFocused: focusStates["_cash"] ?? false,
|
|
isDesktop: isDesktop,
|
|
child: SizedBox(
|
|
height: 40,
|
|
child: TextField(
|
|
focusNode: focusNodes["_cash"],
|
|
controller: textControllers["_cash"],
|
|
style: const TextStyle(fontSize: 12),
|
|
keyboardType: TextInputType.number,
|
|
onChanged: (value) {
|
|
_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)
|
|
SizedBox(
|
|
width: MediaQuery.of(context).size.width * 0.035,
|
|
)
|
|
else
|
|
SizedBox(
|
|
height: 8,
|
|
),
|
|
|
|
Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
"Card*",
|
|
style: TextStyle(
|
|
fontSize: 12,
|
|
fontWeight: FontWeight.w600,
|
|
color: Color(0xFF575A74)),
|
|
),
|
|
SizedBox(height: 5),
|
|
CustomTextFieldItnerarySubWrapper(
|
|
isFocused: focusStates["_card"] ?? false,
|
|
isDesktop: isDesktop,
|
|
child: SizedBox(
|
|
height: 40,
|
|
child: TextField(
|
|
focusNode: focusNodes["_card"],
|
|
controller: textControllers["_card"],
|
|
style: const TextStyle(fontSize: 12),
|
|
keyboardType: TextInputType.number,
|
|
onChanged: (value) {
|
|
_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)
|
|
SizedBox(
|
|
width: MediaQuery.of(context).size.width * 0.035,
|
|
)
|
|
else
|
|
SizedBox(
|
|
height: 8,
|
|
),
|
|
|
|
Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
"Total Amount",
|
|
style: TextStyle(
|
|
fontSize: 12,
|
|
fontWeight: FontWeight.w600,
|
|
color: Color(0xFF575A74)),
|
|
),
|
|
SizedBox(height: 5),
|
|
CustomTextFieldItnerarySubWrapper(
|
|
isFocused: focusStates["_perdiemAmount"] ?? false,
|
|
isDesktop: isDesktop,
|
|
color: Colors.transparent,
|
|
child: SizedBox(
|
|
height: 40,
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(8.0),
|
|
child: Text(
|
|
// "amo",
|
|
selectedQuotedAmount ?? "0",
|
|
// selectedPerdiemAmount?.isNotEmpty == true ? selectedPerdiemAmount! : "Amount",
|
|
style: const TextStyle(
|
|
fontSize: 12,
|
|
fontWeight: FontWeight.w600,
|
|
color: Color(0xFF575A74)),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
|
|
// 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: TextStyle(
|
|
fontSize: 12,
|
|
fontWeight: FontWeight.w600,
|
|
color: Color(0xFF575A74)),
|
|
),
|
|
SizedBox(height: 5),
|
|
CustomTextFieldWrapper(
|
|
isFocused:
|
|
focusStates["_comments"] ?? false, // Dropdown doesn't use focus
|
|
isDesktop: isDesktop,
|
|
width: isDesktop ? MediaQuery.of(context).size.width * 0.34 : 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<Widget> _buildForexCard(bool isDesktop) {
|
|
return [
|
|
!isChecked
|
|
? Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
"Card Number",
|
|
style: TextStyle(
|
|
fontSize: 12,
|
|
fontWeight: FontWeight.w600,
|
|
color: Color(0xFF575A74)),
|
|
),
|
|
SizedBox(height: 5),
|
|
CustomTextFieldItnerarySubWrapper(
|
|
isFocused: focusStates["_cardNumber"] ?? false,
|
|
isDesktop: isDesktop,
|
|
child: SizedBox(
|
|
height: 40,
|
|
child: TextField(
|
|
enabled: !isChecked,
|
|
focusNode: focusNodes["_cardNumber"],
|
|
controller: textControllers["_cardNumber"],
|
|
style: const TextStyle(fontSize: 12),
|
|
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(
|
|
"Required",
|
|
style: TextStyle(color: Colors.red, fontSize: 12),
|
|
),
|
|
],
|
|
],
|
|
)
|
|
: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
"Delivery Location",
|
|
style: TextStyle(
|
|
fontSize: 12,
|
|
fontWeight: FontWeight.w600,
|
|
color: Color(0xFF575A74)),
|
|
),
|
|
SizedBox(height: 5),
|
|
CustomTextFieldWrapper(
|
|
isFocused: focusStates["_deliveryLocation"] ??
|
|
false, // Dropdown doesn't use focus
|
|
isDesktop: isDesktop,
|
|
width: isDesktop
|
|
? MediaQuery.of(context).size.width * 0.34
|
|
: null,
|
|
child: SizedBox(
|
|
height: 35,
|
|
child: TextField(
|
|
focusNode: focusNodes["_deliveryLocation"],
|
|
controller: textControllers["_deliveryLocation"],
|
|
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.035,
|
|
)
|
|
else
|
|
SizedBox(
|
|
height: 8,
|
|
),
|
|
Column(
|
|
children: [
|
|
Row(
|
|
crossAxisAlignment: CrossAxisAlignment.center,
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
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(() {
|
|
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: TextStyle(
|
|
fontSize: 10,
|
|
fontWeight: FontWeight.w600,
|
|
color: Color(0xFF575A74)),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
];
|
|
}
|
|
|
|
List<Widget> _handleAction(bool isDesktop) {
|
|
return [
|
|
// Close Button
|
|
ElevatedButton(
|
|
onPressed: () {
|
|
widget.onClose(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: TextStyle(color: Colors.white, fontSize: 14),
|
|
),
|
|
),
|
|
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: TextStyle(color: Colors.white, fontSize: 14),
|
|
),
|
|
),
|
|
];
|
|
}
|
|
}
|