ts-tat/lib/Screens/itnerary/forex.dart
2025-05-03 17:59:17 +05:30

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),
),
),
];
}
}