Forex List

This commit is contained in:
venbaittech 2025-05-20 15:04:49 +05:30
parent cafa8cbc95
commit d85209139f
20 changed files with 1746 additions and 124 deletions

View File

@ -567,8 +567,12 @@ class _ListAllPlansState extends State<ListAllPlans> {
// List<Plan> plans = snapshot.data!; // List<Plan> plans = snapshot.data!;
// List<Plan> plans =
// filteredPlans.isNotEmpty ? filteredPlans : allPlans;
List<Plan> plans = List<Plan> plans =
filteredPlans.isNotEmpty ? filteredPlans : allPlans; searchController.text.isEmpty ? allPlans : filteredPlans;
plans.sort((a, b) => plans.sort((a, b) =>
int.parse(b.planId).compareTo(int.parse(a.planId))); int.parse(b.planId).compareTo(int.parse(a.planId)));
@ -654,7 +658,7 @@ class _ListAllPlansState extends State<ListAllPlans> {
), ),
softWrap: true, softWrap: true,
overflow: TextOverflow.ellipsis)), overflow: TextOverflow.ellipsis)),
DataCell(Text(plan.employeeCode ?? "no data", DataCell(Text(plan.employeeCode ?? " - ",
style: TextStyle( style: TextStyle(
fontSize: 13, fontSize: 13,
fontFamily: "Inter", fontFamily: "Inter",
@ -968,11 +972,36 @@ class _ListAllPlansState extends State<ListAllPlans> {
children: [ children: [
Expanded( Expanded(
child: isDesktop child: isDesktop
? SingleChildScrollView( ? (searchController.text.isNotEmpty &&
scrollDirection: Axis.vertical, filteredPlans.isEmpty
child: table, // <-- your existing table ? Center(
) child: Text(
: buildMobileCardView(paginatedPlans), "No matches found",
style: GoogleFonts.poppins(
fontSize: 14, color: Colors.grey),
),
)
: SingleChildScrollView(
scrollDirection: Axis.vertical,
child: table,
))
: (searchController.text.isNotEmpty &&
filteredPlans.isEmpty
? Center(
child: Text(
"No matches found",
style: GoogleFonts.poppins(
fontSize: 14, color: Colors.grey),
),
)
: buildMobileCardView(paginatedPlans)),
// child: isDesktop
// ? SingleChildScrollView(
// scrollDirection: Axis.vertical,
// child: table, // <-- your existing table
// )
// : buildMobileCardView(paginatedPlans),
), ),
PaginationControls( PaginationControls(
currentPage: currentPage, currentPage: currentPage,

View File

@ -498,8 +498,12 @@ class _TravelAgentListPlansState extends State<TravelAgentListPlans> {
// List<Plan> plans = snapshot.data!; // List<Plan> plans = snapshot.data!;
// List<Plan> plans =
// filteredPlans.isNotEmpty ? filteredPlans : allPlans;
List<Plan> plans = List<Plan> plans =
filteredPlans.isNotEmpty ? filteredPlans : allPlans; searchController.text.isEmpty ? allPlans : filteredPlans;
plans.sort((a, b) => plans.sort((a, b) =>
int.parse(b.planId).compareTo(int.parse(a.planId))); int.parse(b.planId).compareTo(int.parse(a.planId)));
@ -587,7 +591,7 @@ class _TravelAgentListPlansState extends State<TravelAgentListPlans> {
softWrap: true, softWrap: true,
overflow: TextOverflow.ellipsis)), overflow: TextOverflow.ellipsis)),
DataCell(Text(plan.employeeCode ?? "no data", DataCell(Text(plan.employeeCode ?? " - ",
style: TextStyle( style: TextStyle(
fontSize: 13, fontSize: 13,
fontFamily: "Inter", fontFamily: "Inter",
@ -702,29 +706,33 @@ class _TravelAgentListPlansState extends State<TravelAgentListPlans> {
plan.planId); plan.planId);
}, },
), ),
IconButton(
icon: const Icon(
Icons.comment,
color: Color(0xFF475569),
size: 11,
),
onPressed: () {
showDialog(
context: context,
builder: (context) =>
CommentModal(
// planId: plan.planId,
planId: plan
.planId
.toString(),
layoutColorForUser:
layoutColor!,
role:
"Travel Agent"),
);
}),
], ],
), ),
), ),
), ),
], ],
), ),
IconButton(
icon: const Icon(
Icons.comment,
color: Color(0xFF475569),
size: 11,
),
onPressed: () {
showDialog(
context: context,
builder: (context) => CommentModal(
// planId: plan.planId,
planId: plan.planId.toString(),
layoutColorForUser:
layoutColor!,
role: "Travel Agent"),
);
}),
], ],
), ),
), ),
@ -821,23 +829,6 @@ class _TravelAgentListPlansState extends State<TravelAgentListPlans> {
apiService: apiService, apiService: apiService,
layoutColor: layoutColor, layoutColor: layoutColor,
), ),
IconButton(
icon: const Icon(
Icons.comment,
color: Color(0xFF475569),
size: 11,
),
onPressed: () {
showDialog(
context: context,
builder: (context) => CommentModal(
// planId: plan.planId,
planId: plan.planId.toString(),
layoutColorForUser:
layoutColor!,
role: "Travel Agent"),
);
}),
], ],
), ),
@ -942,11 +933,36 @@ class _TravelAgentListPlansState extends State<TravelAgentListPlans> {
children: [ children: [
Expanded( Expanded(
child: isDesktop child: isDesktop
? SingleChildScrollView( ? (searchController.text.isNotEmpty &&
scrollDirection: Axis.vertical, filteredPlans.isEmpty
child: table, // <-- your existing table ? Center(
) child: Text(
: buildMobileCardView(paginatedPlans), "No matches found",
style: GoogleFonts.poppins(
fontSize: 14, color: Colors.grey),
),
)
: SingleChildScrollView(
scrollDirection: Axis.vertical,
child: table,
))
: (searchController.text.isNotEmpty &&
filteredPlans.isEmpty
? Center(
child: Text(
"No matches found",
style: GoogleFonts.poppins(
fontSize: 14, color: Colors.grey),
),
)
: buildMobileCardView(paginatedPlans)),
// child: isDesktop
// ? SingleChildScrollView(
// scrollDirection: Axis.vertical,
// child: table, // <-- your existing table
// )
// : buildMobileCardView(paginatedPlans),
), ),
PaginationControls( PaginationControls(
currentPage: currentPage, currentPage: currentPage,

View File

@ -529,8 +529,15 @@ class _ApprovalListState extends State<ApprovalList> {
); );
} }
// List<Plan> plans =
// filteredPlans.isNotEmpty ? filteredPlans : allPlans;
// List<Plan> plans =
// searchController.text.isEmpty ? allPlans : filteredPlans;
List<Plan> plans = List<Plan> plans =
filteredPlans.isNotEmpty ? filteredPlans : allPlans; searchController.text.isEmpty ? allPlans : filteredPlans;
plans.sort((a, b) => plans.sort((a, b) =>
int.parse(b.planId).compareTo(int.parse(a.planId))); int.parse(b.planId).compareTo(int.parse(a.planId)));
@ -619,7 +626,7 @@ class _ApprovalListState extends State<ApprovalList> {
), ),
softWrap: true, softWrap: true,
overflow: TextOverflow.ellipsis)), overflow: TextOverflow.ellipsis)),
DataCell(Text(plan.employeeCode ?? "no data", DataCell(Text(plan.employeeCode ?? " - ",
style: TextStyle( style: TextStyle(
fontSize: 13, fontSize: 13,
fontFamily: "Inter", fontFamily: "Inter",
@ -1066,11 +1073,36 @@ class _ApprovalListState extends State<ApprovalList> {
children: [ children: [
Expanded( Expanded(
child: isDesktop child: isDesktop
? SingleChildScrollView( ? (searchController.text.isNotEmpty &&
scrollDirection: Axis.vertical, filteredPlans.isEmpty
child: table, // <-- your existing table ? Center(
) child: Text(
: buildMobileCardView(paginatedPlans), "No matches found",
style: GoogleFonts.poppins(
fontSize: 14, color: Colors.grey),
),
)
: SingleChildScrollView(
scrollDirection: Axis.vertical,
child: table,
))
: (searchController.text.isNotEmpty &&
filteredPlans.isEmpty
? Center(
child: Text(
"No matches found",
style: GoogleFonts.poppins(
fontSize: 14, color: Colors.grey),
),
)
: buildMobileCardView(paginatedPlans)),
// child: isDesktop
// ? SingleChildScrollView(
// scrollDirection: Axis.vertical,
// child: table, // <-- your existing table
// )
// : buildMobileCardView(paginatedPlans),
), ),
PaginationControls( PaginationControls(
currentPage: currentPage, currentPage: currentPage,

View File

@ -365,7 +365,7 @@ class _LoginWidgetState extends State<LoginWidget> {
), ),
const SizedBox(height: 2), const SizedBox(height: 2),
Text( Text(
"Welcome To TravelSpends", "Welcome To Trip Approval Tools",
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
fontSize: 11, fontSize: 11,
fontWeight: FontWeight.w400, fontWeight: FontWeight.w400,

View File

@ -96,7 +96,7 @@ class _UserSelectionDialogState extends State<UserSelectionDialog> {
print("Users fetched: ${_users.length}"); print("Users fetched: ${_users.length}");
for (var user in _users) { for (var user in _users) {
print("${user.firstName} ${user.lastName}"); print("${user.firstName} ${user.lastName} ${user.empCode}");
} }
} else { } else {
throw Exception( throw Exception(
@ -146,7 +146,8 @@ class _UserSelectionDialogState extends State<UserSelectionDialog> {
print("Users fetched: ${_users.length}"); print("Users fetched: ${_users.length}");
for (var travvelr in _traveller) { for (var travvelr in _traveller) {
print("${travvelr.firstName} ${travvelr.lastName}"); print(
"${travvelr.firstName} ${travvelr.lastName} ${travvelr.mobileNo}");
} }
} else { } else {
throw Exception( throw Exception(
@ -200,6 +201,8 @@ class _UserSelectionDialogState extends State<UserSelectionDialog> {
} else { } else {
_filteredList = [ _filteredList = [
..._users.where((user) { ..._users.where((user) {
print("usersLLL : ${user}");
List<String> searchFields = [ List<String> searchFields = [
"${user.firstName} ${user.lastName}".toLowerCase(), "${user.firstName} ${user.lastName}".toLowerCase(),
user.email.toLowerCase() ?? "", user.email.toLowerCase() ?? "",
@ -222,6 +225,39 @@ class _UserSelectionDialogState extends State<UserSelectionDialog> {
} }
} }
void _filterTravellers(String query) {
print("Filtering _filterUsersTravellers...");
setState(() {
_filteredList.clear(); // Reset the list before filtering
if (query.isEmpty) {
_filteredList = [
..._users.map((user) => {"type": "user", "data": user}),
];
} else {
_filteredList = [
..._traveller.where((traveller) {
List<String> searchFields = [
"${traveller.firstName} ${traveller.lastName}".toLowerCase(),
traveller.email.toLowerCase() ?? "",
traveller.travellerId.toLowerCase() ?? "",
traveller.mobileNo ?? "",
];
return searchFields
.any((field) => field.contains(query.toLowerCase()));
}).map((traveller) => {"type": "traveller", "data": traveller}),
];
}
});
print("Filtered List:");
for (var item in _filteredList) {
var user = item["data"];
print(
"${item["type"].toUpperCase()} - ${user.firstName} ${user.lastName}");
}
}
void _filterUsersTravellers(String query) { void _filterUsersTravellers(String query) {
print("Filtering _filterUsersTravellers..."); print("Filtering _filterUsersTravellers...");
setState(() { setState(() {
@ -287,8 +323,11 @@ class _UserSelectionDialogState extends State<UserSelectionDialog> {
mainAxisSize: mainAxisSize:
MainAxisSize.min, // Ensures content doesn't expand unnecessarily MainAxisSize.min, // Ensures content doesn't expand unnecessarily
children: [ children: [
Text("Please Select User", widget.title == "Others"
style: GoogleFonts.poppins(fontSize: 14)), ? Text("Please Select Other User",
style: GoogleFonts.poppins(fontSize: 14))
: Text("Please Select Other Employee",
style: GoogleFonts.poppins(fontSize: 14)),
SizedBox(height: 10), SizedBox(height: 10),
// Search Field // Search Field
@ -299,7 +338,7 @@ class _UserSelectionDialogState extends State<UserSelectionDialog> {
_showTravellerForm = false; _showTravellerForm = false;
}); });
widget.title == "Others" widget.title == "Others"
? _filterUsersTravellers(query) ? _filterTravellers(query)
: _filterUsers(query); : _filterUsers(query);
}, },
style: GoogleFonts.poppins(fontSize: 12), style: GoogleFonts.poppins(fontSize: 12),
@ -370,14 +409,30 @@ class _UserSelectionDialogState extends State<UserSelectionDialog> {
final user = item["data"]; // Extract user object final user = item["data"]; // Extract user object
final userType = final userType =
item["type"]; // "user" or "traveller" item["type"]; // "user" or "traveller"
if (user is Map<String, dynamic>) {
print(
"userLsirer - ${jsonEncode(user)}"); // pretty JSON-like string
} else {
print("userLsirer - $user"); // fallback
}
return ListTile( return ListTile(
title: Text( title: Text(
"${user.firstName ?? "Unknown"} ${user.lastName ?? ""}", "${user.firstName ?? "Unknown"} ${user.lastName ?? ""}",
style: GoogleFonts.poppins(fontSize: 11), style: GoogleFonts.poppins(fontSize: 11),
), ),
// subtitle: Text( subtitle: userType == "user"
// "ID: ${userType == "user" ? user.userId : user.travellerId}"), ? Text(
"Employee ID: ${user.empCode ?? ""} ",
// "Employee ID: ${userType == "user" ? user.userId : user.travellerId}",
style:
GoogleFonts.poppins(fontSize: 10),
)
: Text(
"Mobile : ${user.mobileNo ?? ""} ",
// "Employee ID: ${userType == "user" ? user.userId : user.travellerId}",
style:
GoogleFonts.poppins(fontSize: 10),
),
onTap: () { onTap: () {
String selectedUser = String selectedUser =
"${user.firstName ?? "Unknown"} ${user.lastName ?? ""}"; "${user.firstName ?? "Unknown"} ${user.lastName ?? ""}";
@ -642,7 +697,8 @@ class _TravelerFormState extends State<TravelerForm> {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
Text("Create Traveler", style: TextStyle(color: Colors.black54)), Text("Create Traveler",
style: GoogleFonts.poppins(color: Colors.black54)),
SizedBox(height: 7), SizedBox(height: 7),
Expanded( Expanded(
child: SingleChildScrollView( child: SingleChildScrollView(
@ -664,7 +720,7 @@ class _TravelerFormState extends State<TravelerForm> {
TextButton( TextButton(
onPressed: () => _onSubmit(context), onPressed: () => _onSubmit(context),
child: Text("Add", child: Text("Add",
style: GoogleFonts.poppins(color: Colors.blueAccent)), style: GoogleFonts.poppins(color: Color(0xFF114D8B))),
), ),
], ],
), ),

View File

@ -0,0 +1,262 @@
import 'package:dropdown_search/dropdown_search.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import '../../widgets/custom_text_forex.dart';
class ForexData extends StatefulWidget {
final bool isDesktop;
final Color? layoutColor;
const ForexData({super.key, required this.isDesktop, this.layoutColor});
@override
ForexDataState createState() => ForexDataState();
}
class ForexDataState extends State<ForexData> {
List<dynamic> countryList = [];
String? selectedCountry;
String? selectedCurrency;
String? selectedDuration;
String? selectedPerdiemAmount;
@override
Widget build(BuildContext context) {
late Map<String, String> countryMap; // Mapping country_code -> country_name
late List<String> countryCodes; // List of country codes
countryList = [];
// 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;
return AlertDialog(
backgroundColor: Colors.white,
contentPadding: const EdgeInsets.fromLTRB(34, 30, 34, 30),
// contentPadding: const EdgeInsets.fromLTRB(24, 20, 24, 10),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
// Row 1: Title + Edit + Delete buttons
Row(
children: [
Text(
'Create Forex Details',
style: GoogleFonts.poppins(fontSize: 18, color: Colors.black),
),
const Spacer(),
],
),
const SizedBox(height: 10),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Country",
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74)),
),
SizedBox(height: 5),
CustomTextFieldForexWrapper(
isFocused: false,
isDesktop: widget.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;
});
},
),
),
),
// if (errorMessages["country_code"] != null) ...[
// SizedBox(height: 5), // Space before error message
// Text(
// "Select Country",
// style: TextStyle(color: Colors.red, fontSize: 12),
// ),
// ],
],
),
const SizedBox(height: 10),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Currency *",
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74)),
),
SizedBox(height: 5),
CustomTextFieldForexWrapper(
isFocused: false,
isDesktop: widget.isDesktop,
color: Colors.transparent,
// width: isDesktop
// ? MediaQuery.of(context).size.width * 0.330
// : MediaQuery.of(context).size.width * 0.66,
child: SizedBox(
height: 40,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Center(
child: Text(
// "cur",
// "${selectedCurrency}",
selectedCurrency ?? "Currency",
// selectedCurrency?.isNotEmpty == true ? selectedCurrency! : "Currency",
style: const TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74)),
),
),
),
),
),
],
),
// if (isDesktop)SizedBox(width: 8,) else SizedBox(height: 8,),
SizedBox(
height: 10,
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Perdiem Amount",
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74)),
),
SizedBox(height: 5),
CustomTextFieldForexWrapper(
isFocused: false,
isDesktop: widget.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),
// ),
),
),
),
),
],
),
SizedBox(
height: 15,
),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
// SizedBox(
// child: ElevatedButton(
// onPressed: () {
// // You can get text from commentController.text
// Navigator.of(context).pop(); // Close the modal
// },
// style: ElevatedButton.styleFrom(
// backgroundColor: widget.layoutColor,
// shape: RoundedRectangleBorder(
// borderRadius: BorderRadius.circular(8),
// ),
// ),
// child: Text('Cancel',
// style: GoogleFonts.poppins(
// fontSize: 13, color: Colors.white)),
// ),
// ),
// SizedBox(
// width: 10,
// ),
SizedBox(
child: ElevatedButton(
onPressed: () {
// You can get text from commentController.text
Navigator.of(context).pop(); // Close the modal
},
style: ElevatedButton.styleFrom(
backgroundColor: widget.layoutColor,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
child: Text('Submit',
style: GoogleFonts.poppins(
fontSize: 11, color: Colors.white)),
),
),
],
)
// : SizedBox.shrink(),
],
),
);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -513,6 +513,40 @@ class FlightScreenState extends State<FlightScreen> {
print("FlightData - $flightsData"); print("FlightData - $flightsData");
} }
void validateTimeDifference(int index) {
if (index <= 1) return; // Skip validation for the first row
String? prevDateStr = textControllers["_date${index - 1}Controller"]?.text;
String? prevTimeStr = textControllers["_time${index - 1}Controller"]?.text;
String? currDateStr = textControllers["_date${index}Controller"]?.text;
String? currTimeStr = textControllers["_time${index}Controller"]?.text;
if (prevDateStr != null &&
prevDateStr.isNotEmpty &&
prevTimeStr != null &&
prevTimeStr.isNotEmpty &&
currDateStr != null &&
currDateStr.isNotEmpty &&
currTimeStr != null &&
currTimeStr.isNotEmpty) {
try {
final prevDateTime = DateTime.parse("$prevDateStr $prevTimeStr");
final currDateTime = DateTime.parse("$currDateStr $currTimeStr");
if (!currDateTime.isAfter(prevDateTime)) {
errorMessages["time_$index"] = "Must be after previous time";
} else if (currDateTime.difference(prevDateTime).inMinutes < 30) {
errorMessages["time_$index"] =
"Must be least 30 mins after previous time";
} else {
errorMessages.remove("time_$index");
}
} catch (e) {
errorMessages["time_$index"] = "Invalid time format";
}
}
}
bool validateFields() { bool validateFields() {
errorMessages.clear(); // Reset errors errorMessages.clear(); // Reset errors
@ -1124,7 +1158,8 @@ class FlightScreenState extends State<FlightScreen> {
} }
} }
Future<void> _selectCheckOutTime(BuildContext context) async { Future<void> _selectCheckOutTime(
BuildContext context, int index, VoidCallback onPicked) async {
TimeOfDay? pickedTime = await showTimePicker( TimeOfDay? pickedTime = await showTimePicker(
context: context, context: context,
initialTime: _selectedCheckOutTime ?? TimeOfDay.now(), initialTime: _selectedCheckOutTime ?? TimeOfDay.now(),
@ -1141,6 +1176,8 @@ class FlightScreenState extends State<FlightScreen> {
); );
// _timeController.text = formattedTime; // _timeController.text = formattedTime;
textControllers["_time${index}Controller"]?.text = formattedTime; textControllers["_time${index}Controller"]?.text = formattedTime;
onPicked();
}); });
} }
} }
@ -1485,12 +1522,23 @@ class FlightScreenState extends State<FlightScreen> {
child: SizedBox( child: SizedBox(
height: 40, height: 40,
child: GestureDetector( child: GestureDetector(
onTap: () => _selectCheckOutTime(context), onTap: () {
_clearError("time_$index");
// validateTimeDifference(index);
// _selectCheckOutTime(context);
_selectCheckOutTime(context, index, () {
validateTimeDifference(index);
setState(
() {}); // Force rebuild to show the error immediately
});
},
child: AbsorbPointer( child: AbsorbPointer(
child: TextField( child: TextField(
focusNode: focusNodes["_time${index}FocusNode"], focusNode: focusNodes["_time${index}FocusNode"],
// controller: _timeController, // controller: _timeController,
controller: textControllers["_time${index}Controller"], controller: textControllers["_time${index}Controller"],
style: const TextStyle(fontSize: 12), style: const TextStyle(fontSize: 12),
decoration: const InputDecoration( decoration: const InputDecoration(
labelText: "Time", labelText: "Time",
@ -1509,7 +1557,7 @@ class FlightScreenState extends State<FlightScreen> {
if (errorMessages["time_$index"] != null) ...[ if (errorMessages["time_$index"] != null) ...[
SizedBox(height: 5), // Space before error message SizedBox(height: 5), // Space before error message
Text( Text(
"Required", errorMessages["time_$index"]!,
style: TextStyle(color: Colors.red, fontSize: 12), style: TextStyle(color: Colors.red, fontSize: 12),
), ),
], ],

View File

@ -375,6 +375,8 @@ class CreateNewPlansState extends State<CreateNewPlan> {
late bool isApproverApproved = false; late bool isApproverApproved = false;
late bool isApproverRejected = false; late bool isApproverRejected = false;
String? temporaryMessage;
//Getter Method //Getter Method
Map<String, dynamic> get planData => { Map<String, dynamic> get planData => {
"org_id": orgId, "org_id": orgId,
@ -990,7 +992,20 @@ class CreateNewPlansState extends State<CreateNewPlan> {
bool anyServiceSelected = bool anyServiceSelected =
serviceLists.any((list) => list != null && list.isNotEmpty); serviceLists.any((list) => list != null && list.isNotEmpty);
if (!anyServiceSelected) { if (!anyServiceSelected) {
validationErrors["services"] = "Please select at least one service"; // validationErrors["services"] = "Please select at least one service";
setState(() {
temporaryMessage = "Please select at least one service";
});
// Clear message after 3 seconds
Future.delayed(Duration(seconds: 3), () {
if (mounted) {
setState(() {
temporaryMessage = null;
});
}
});
} }
return validationErrors.isEmpty; // Returns true if no errors return validationErrors.isEmpty; // Returns true if no errors
@ -1431,12 +1446,13 @@ class CreateNewPlansState extends State<CreateNewPlan> {
height: 20, height: 20,
), ),
if (validationErrors["services"] != null) if (temporaryMessage != null)
Row( Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
Text( Text(
validationErrors["services"]!, // validationErrors["services"]!,
temporaryMessage!,
style: GoogleFonts.poppins( style: GoogleFonts.poppins(
color: Colors.red, color: Colors.red,
fontSize: 10, fontSize: 10,
@ -1445,6 +1461,10 @@ class CreateNewPlansState extends State<CreateNewPlan> {
], ],
), ),
if (temporaryMessage != null)
SizedBox(
height: 10,
),
Row( Row(
children: [ children: [
Expanded( Expanded(
@ -2293,6 +2313,8 @@ class CreateNewPlansState extends State<CreateNewPlan> {
if (_selectedOption == "Option 2" || if (_selectedOption == "Option 2" ||
_selectedOption == "Option 3") { _selectedOption == "Option 3") {
_showInputDialog(selected["title"]!); _showInputDialog(selected["title"]!);
} else if (_selectedOption == "Option 1") {
otherUserName = userName;
} }
}); });
}, },

View File

@ -606,8 +606,11 @@ class _ListPlansState extends State<ListPlans> {
// List<Plan> plans = snapshot.data!; // List<Plan> plans = snapshot.data!;
// List<Plan> plans =
// filteredPlans.isNotEmpty ? filteredPlans : allPlans;
List<Plan> plans = List<Plan> plans =
filteredPlans.isNotEmpty ? filteredPlans : allPlans; searchController.text.isEmpty ? allPlans : filteredPlans;
plans.sort((a, b) => plans.sort((a, b) =>
int.parse(b.planId).compareTo(int.parse(a.planId))); int.parse(b.planId).compareTo(int.parse(a.planId)));
@ -695,7 +698,7 @@ class _ListPlansState extends State<ListPlans> {
softWrap: true, softWrap: true,
overflow: TextOverflow.ellipsis)), overflow: TextOverflow.ellipsis)),
DataCell(Text(plan.employeeCode ?? "no data", DataCell(Text(plan.employeeCode ?? " - ",
style: TextStyle( style: TextStyle(
fontSize: 13, fontSize: 13,
fontFamily: "Inter", fontFamily: "Inter",
@ -1059,11 +1062,35 @@ class _ListPlansState extends State<ListPlans> {
children: [ children: [
Expanded( Expanded(
child: isDesktop child: isDesktop
? SingleChildScrollView( ? (searchController.text.isNotEmpty &&
scrollDirection: Axis.vertical, filteredPlans.isEmpty
child: table, // <-- your existing table ? Center(
) child: Text(
: buildMobileCardView(paginatedPlans), "No matches found",
style: GoogleFonts.poppins(
fontSize: 14, color: Colors.grey),
),
)
: SingleChildScrollView(
scrollDirection: Axis.vertical,
child: table,
))
: (searchController.text.isNotEmpty &&
filteredPlans.isEmpty
? Center(
child: Text(
"No matches found",
style: GoogleFonts.poppins(
fontSize: 14, color: Colors.grey),
),
)
: buildMobileCardView(paginatedPlans)),
// child: isDesktop
// ? SingleChildScrollView(
// scrollDirection: Axis.vertical,
// child: table, // <-- your existing table
// )
// : buildMobileCardView(paginatedPlans),
), ),
PaginationControls( PaginationControls(
currentPage: currentPage, currentPage: currentPage,

View File

@ -594,7 +594,8 @@ class _CreateUserFormDetialsState extends State<CreateUserFormDetials> {
"first_name", "first_name",
"last_name", "last_name",
"email", "email",
"mobile_no" "mobile_no",
"employeeCode"
]; ];
if (apiselectedUser == null) { if (apiselectedUser == null) {

View File

@ -312,6 +312,9 @@ class _OfficeDetailsState extends State<OfficeDetails> {
style: GoogleFonts.poppins(fontSize: 12, color: Colors.black), style: GoogleFonts.poppins(fontSize: 12, color: Colors.black),
controller: widget.controllers["employeeCode"], controller: widget.controllers["employeeCode"],
enabled: !widget.isViewMode, enabled: !widget.isViewMode,
onChanged: (value) {
_clearError("employeeCode");
},
decoration: InputDecoration( decoration: InputDecoration(
labelText: "Employee Code", labelText: "Employee Code",
labelStyle: labelStyle:
@ -323,6 +326,13 @@ class _OfficeDetailsState extends State<OfficeDetails> {
), ),
), ),
), ),
if (widget.errorMessages["employeeCode"] != null) ...[
SizedBox(height: 5), // Space before error message
Text(
widget.errorMessages["employeeCode"]!,
style: TextStyle(color: Colors.red, fontSize: 12),
),
],
], ],
); );
} }

View File

@ -265,7 +265,7 @@ class TravellerDetailsState extends State<TravellerDetails> {
} }
List<Map<String, dynamic>> get hotelMembershipList { List<Map<String, dynamic>> get hotelMembershipList {
return hotelLoyaltyEntries.map((entry) { final mappedList = hotelLoyaltyEntries.map((entry) {
return { return {
"id": entry["id"], "id": entry["id"],
"hotel_name": entry["controller_hotel"].text ?? '', "hotel_name": entry["controller_hotel"].text ?? '',
@ -275,6 +275,12 @@ class TravellerDetailsState extends State<TravellerDetails> {
"is_active": entry["is_active"] ?? "1", "is_active": entry["is_active"] ?? "1",
}; };
}).toList(); }).toList();
final allEntriesEmpty = mappedList.every((entry) =>
(entry["hotel_name"] as String).trim().isEmpty &&
(entry["membership_number"] as String).trim().isEmpty);
return allEntriesEmpty ? [] : mappedList;
} }
// List<Map<String, dynamic>> get frequentFlierList { // List<Map<String, dynamic>> get frequentFlierList {
@ -293,14 +299,14 @@ class TravellerDetailsState extends State<TravellerDetails> {
// } // }
List<Map<String, dynamic>> get frequentFlierList { List<Map<String, dynamic>> get frequentFlierList {
return frequentFlierEntries.map((entry) { final mappedList = frequentFlierEntries.map((entry) {
final frequent_flier_number = entry["controller_flier_number"]; final frequent_flier_number = entry["controller_flier_number"];
print("frequent_flier_number- $frequent_flier_number"); print("frequent_flier_number- $frequent_flier_number");
return { return {
"id": entry["id"], "id": entry["id"],
"airline": entry["airline"], // string value "airline": entry["airline"] ?? "",
"frequent_flier_number": frequent_flier_number is TextEditingController "frequent_flier_number": frequent_flier_number is TextEditingController
? frequent_flier_number.text ? frequent_flier_number.text
: "", : "",
@ -309,6 +315,12 @@ class TravellerDetailsState extends State<TravellerDetails> {
"is_active": entry["is_active"] ?? "1", "is_active": entry["is_active"] ?? "1",
}; };
}).toList(); }).toList();
final allEntriesEmpty = mappedList.every((entry) =>
(entry["airline"] as String).trim().isEmpty &&
(entry["frequent_flier_number"] as String).trim().isEmpty);
return allEntriesEmpty ? [] : mappedList;
} }
// List<Map<String, dynamic>> get visaDetailsList { // List<Map<String, dynamic>> get visaDetailsList {
@ -335,7 +347,7 @@ class TravellerDetailsState extends State<TravellerDetails> {
// } // }
List<Map<String, dynamic>> get visaDetailsList { List<Map<String, dynamic>> get visaDetailsList {
return visaEntries.map((entry) { final mappedList = visaEntries.map((entry) {
final fromController = entry["controller_valid_from"]; final fromController = entry["controller_valid_from"];
final uptoController = entry["controller_valid_upto"]; final uptoController = entry["controller_valid_upto"];
@ -352,6 +364,18 @@ class TravellerDetailsState extends State<TravellerDetails> {
"is_active": entry["is_active"] ?? "1", "is_active": entry["is_active"] ?? "1",
}; };
}).toList(); }).toList();
final allEntriesEmpty = mappedList.every((entry) =>
(entry["country_code"] == null ||
entry["country_code"].toString().trim().isEmpty) &&
(entry["visa_type_id"] == null ||
entry["visa_type_id"].toString().trim().isEmpty) &&
(entry["valid_from"] == null ||
entry["valid_from"].toString().trim().isEmpty) &&
(entry["valid_upto"] == null ||
entry["valid_upto"].toString().trim().isEmpty));
return allEntriesEmpty ? [] : mappedList;
} }
void updateTravel() { void updateTravel() {
@ -1983,11 +2007,16 @@ class TravellerDetailsState extends State<TravellerDetails> {
child: TextField( child: TextField(
style: GoogleFonts.poppins(fontSize: 12, color: Colors.black), style: GoogleFonts.poppins(fontSize: 12, color: Colors.black),
controller: controllers["emergency_contact"], controller: controllers["emergency_contact"],
inputFormatters: [
FilteringTextInputFormatter.allow(RegExp(r'[0-9\s]')),
],
// maxLength: 10,
enabled: !widget.isViewMode, enabled: !widget.isViewMode,
onChanged: (value) { onChanged: (value) {
_clearError("emergency_contact"); _clearError("emergency_contact");
}, },
decoration: InputDecoration( decoration: InputDecoration(
// counterText: '',
labelText: "Contact Number", labelText: "Contact Number",
labelStyle: labelStyle:
GoogleFonts.poppins(fontSize: 12, color: Colors.grey), GoogleFonts.poppins(fontSize: 12, color: Colors.grey),

View File

@ -4,7 +4,7 @@ class SearchTraveler {
final String lastName; final String lastName;
final String email; final String email;
final String mobileNo; final String mobileNo;
String? empCode;
SearchTraveler({ SearchTraveler({
required this.travellerId, required this.travellerId,
@ -12,20 +12,17 @@ class SearchTraveler {
required this.lastName, required this.lastName,
required this.email, required this.email,
required this.mobileNo, required this.mobileNo,
this.empCode,
}); });
factory SearchTraveler.fromJson(Map<String, dynamic> json) {
factory SearchTraveler.fromJson(Map<String, dynamic> json){
return SearchTraveler( return SearchTraveler(
travellerId: json['traveller_id'].toString(), travellerId: json['traveller_id'].toString(),
firstName: json['first_name'].toString()?? '', firstName: json['first_name'].toString() ?? '',
lastName: json['last_name'].toString()?? '', lastName: json['last_name'].toString() ?? '',
email: json['email'].toString()?? '', email: json['email'].toString() ?? '',
mobileNo: json['mobile_no'].toString()?? '', mobileNo: json['mobile'].toString() ?? '',
empCode: json['employee_code'].toString() ?? '',
); );
} }
}
}

View File

@ -1,10 +1,11 @@
class SearchUser{ class SearchUser {
final String userId; final String userId;
final String firstName; final String firstName;
final String lastName; final String lastName;
final String email; final String email;
final String mobileNo; final String mobileNo;
final String alternateMobileNo; final String alternateMobileNo;
String? empCode;
SearchUser({ SearchUser({
required this.userId, required this.userId,
@ -13,18 +14,18 @@ class SearchUser{
required this.email, required this.email,
required this.mobileNo, required this.mobileNo,
required this.alternateMobileNo, required this.alternateMobileNo,
}); this.empCode,
});
factory SearchUser.fromJson(Map<String, dynamic> json) {
factory SearchUser.fromJson(Map<String, dynamic> json){
return SearchUser( return SearchUser(
userId: json['user_id'].toString(), userId: json['user_id'].toString(),
firstName: json['first_name'].toString()?? '', firstName: json['first_name'].toString() ?? '',
lastName: json['last_name'].toString()?? '', lastName: json['last_name'].toString() ?? '',
email: json['email'].toString()?? '', email: json['email'].toString() ?? '',
mobileNo: json['mobile_no'].toString()?? '', mobileNo: json['mobile_no'].toString() ?? '',
alternateMobileNo: json['alternate_mobile_no'].toString()?? '', alternateMobileNo: json['alternate_mobile_no'].toString() ?? '',
empCode: json['employee_code'].toString() ?? '',
); );
} }
}
}

View File

@ -268,7 +268,8 @@ class _CustomAppBarState extends State<CustomAppBar> {
TabSelection.allTrips, '/listAllPlan'), TabSelection.allTrips, '/listAllPlan'),
layoutColor!, layoutColor!,
isSelected: selectedTab == TabSelection.allTrips, isSelected: selectedTab == TabSelection.allTrips,
icon: Icons.insights_outlined, icon: Icons.format_list_bulleted_rounded,
// icon: Icons.insights_outlined,
), ),
if (userDetails["role"] != "Travel Agent") if (userDetails["role"] != "Travel Agent")
@ -282,7 +283,8 @@ class _CustomAppBarState extends State<CustomAppBar> {
layoutColor!, layoutColor!,
// () => context.go('/listTravelAgentPlan'), // () => context.go('/listTravelAgentPlan'),
isSelected: selectedTab == TabSelection.myTrips, isSelected: selectedTab == TabSelection.myTrips,
icon: Icons.request_page_outlined, icon: Icons.shopping_bag_outlined,
// icon: Icons.request_page_outlined,
), ),
if (userData?["role"] != "Travel Agent") // for others if (userData?["role"] != "Travel Agent") // for others
@ -293,7 +295,7 @@ class _CustomAppBarState extends State<CustomAppBar> {
layoutColor!, layoutColor!,
// () => context.go('/listPlan'), // () => context.go('/listPlan'),
isSelected: selectedTab == TabSelection.myTrips, isSelected: selectedTab == TabSelection.myTrips,
icon: Icons.request_page_outlined, icon: Icons.shopping_bag_outlined,
), ),
// if (userDetails["role"] == "Travel Agent") // if (userDetails["role"] == "Travel Agent")
// buildNavItem("My Trips", _myTravelRequestColor, // buildNavItem("My Trips", _myTravelRequestColor,
@ -317,7 +319,8 @@ class _CustomAppBarState extends State<CustomAppBar> {
layoutColor!, layoutColor!,
// () => context.go('/ApprovalList'), // () => context.go('/ApprovalList'),
isSelected: selectedTab == TabSelection.myApprovals, isSelected: selectedTab == TabSelection.myApprovals,
icon: Icons.assessment_outlined, icon: Icons.verified_outlined,
// icon: Icons.assessment_outlined,
), ),
// buildNavItem("My Approvals", _myApprovalsColor, () { // buildNavItem("My Approvals", _myApprovalsColor, () {
@ -368,6 +371,8 @@ class _CustomAppBarState extends State<CustomAppBar> {
break; break;
case '/PolicyList': case '/PolicyList':
context.go('/PolicyList'); context.go('/PolicyList');
case '/getForexDetails':
context.go('/getForexDetails');
case '/CreateUserDetails': case '/CreateUserDetails':
context.go( context.go(
"/CreateUserDetails", "/CreateUserDetails",
@ -504,6 +509,7 @@ final List<Map<String, dynamic>> menuItems = [
}, },
{'value': '/group', 'icon': Icons.group, 'label': 'Group'}, {'value': '/group', 'icon': Icons.group, 'label': 'Group'},
{'value': '/PolicyList', 'icon': Icons.policy, 'label': 'Policy'}, {'value': '/PolicyList', 'icon': Icons.policy, 'label': 'Policy'},
{'value': '/getForexDetails', 'icon': Icons.policy, 'label': 'Forex'},
{ {
'value': '/CreateUserDetails', 'value': '/CreateUserDetails',
'icon': Icons.account_circle, 'icon': Icons.account_circle,

View File

@ -4,6 +4,7 @@ import 'package:flutter/material.dart';
import 'package:frontend/Screens/authentication/login/login_page.dart'; import 'package:frontend/Screens/authentication/login/login_page.dart';
import 'package:frontend/Screens/authentication/loginPage1.dart'; import 'package:frontend/Screens/authentication/loginPage1.dart';
import 'package:frontend/Screens/dashboard/home_page.dart'; import 'package:frontend/Screens/dashboard/home_page.dart';
import 'package:frontend/Screens/forex/forex_list.dart';
import 'package:frontend/Screens/organization/orgSetup.dart'; import 'package:frontend/Screens/organization/orgSetup.dart';
import 'package:frontend/Screens/organization/org_List.dart'; import 'package:frontend/Screens/organization/org_List.dart';
import 'package:frontend/Screens/plans/create_plans.dart'; import 'package:frontend/Screens/plans/create_plans.dart';
@ -95,6 +96,10 @@ final GoRouter router = GoRouter(
path: '/group', path: '/group',
builder: (context, state) => GroupList(), builder: (context, state) => GroupList(),
), ),
GoRoute(
path: '/getForexDetails',
builder: (context, state) => ForexDataList(),
),
GoRoute( GoRoute(
path: '/approvallist', path: '/approvallist',
builder: (context, state) => ApprovalList(), builder: (context, state) => ApprovalList(),

View File

@ -227,6 +227,7 @@ class CommentModalState extends State<CommentModal> {
// You can also make a TextEditingController if you want to collect input // You can also make a TextEditingController if you want to collect input
return AlertDialog( return AlertDialog(
backgroundColor: Colors.white,
contentPadding: const EdgeInsets.fromLTRB(24, 20, 24, 10), contentPadding: const EdgeInsets.fromLTRB(24, 20, 24, 10),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
content: Column( content: Column(

View File

@ -23,7 +23,7 @@ class CustomTextField extends StatelessWidget {
double widthFactor; double widthFactor;
if (sizingInfo.deviceScreenType == DeviceScreenType.desktop) { if (sizingInfo.deviceScreenType == DeviceScreenType.desktop) {
widthFactor = 0.15; // Reduce width for desktop widthFactor = 0.9; // Reduce width for desktop
} else if (sizingInfo.deviceScreenType == DeviceScreenType.tablet) { } else if (sizingInfo.deviceScreenType == DeviceScreenType.tablet) {
widthFactor = 0.8; // Slightly reduced width for tablets widthFactor = 0.8; // Slightly reduced width for tablets
} else { } else {
@ -32,17 +32,18 @@ class CustomTextField extends StatelessWidget {
return Center( return Center(
child: Container( child: Container(
height: 35, height: 40,
width: MediaQuery.of(context).size.width * widthFactor, width: MediaQuery.of(context).size.width * widthFactor,
child: TextFormField( child: TextFormField(
controller: controller, controller: controller,
keyboardType: keyboardType, keyboardType: keyboardType,
cursorColor: Colors.blueAccent, cursorColor: Colors.blueAccent,
style: GoogleFonts.poppins(fontSize: 12.0), style: GoogleFonts.poppins(fontSize: 11.5),
decoration: InputDecoration( decoration: InputDecoration(
hintStyle: GoogleFonts.poppins(color: Colors.grey), hintStyle: GoogleFonts.poppins(color: Colors.grey),
floatingLabelStyle: GoogleFonts.poppins( floatingLabelStyle: GoogleFonts.poppins(
color: Colors.black, // color: Colors.black,
color: Color(0xFF575A74),
), ),
floatingLabelBehavior: FloatingLabelBehavior.always, floatingLabelBehavior: FloatingLabelBehavior.always,
labelText: labelText, labelText: labelText,
@ -52,30 +53,30 @@ class CustomTextField extends StatelessWidget {
fontSize: 10, height: 0.8, color: Colors.red), fontSize: 10, height: 0.8, color: Colors.red),
border: OutlineInputBorder( border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
borderSide: borderSide: BorderSide(
BorderSide(color: Colors.grey, width: 0.5), // Grey border color: Colors.grey.shade200, width: 1), // Grey border
), ),
enabledBorder: OutlineInputBorder( enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
borderSide: BorderSide( borderSide: BorderSide(
color: Colors.black54, color: Colors.grey.shade200,
width: 0.2), // Grey border when not focused width: 1), // Grey border when not focused
), ),
focusedBorder: OutlineInputBorder( focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
borderSide: BorderSide( borderSide: BorderSide(
color: Colors.grey, color: Colors.grey.shade200,
width: 0.5), // Blue border when focused width: 1), // Blue border when focused
), ),
errorBorder: OutlineInputBorder( errorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
borderSide: BorderSide( borderSide: BorderSide(
color: Colors.grey, width: 0.5), // Same as normal color: Colors.grey.shade200, width: 1), // Same as normal
), ),
focusedErrorBorder: OutlineInputBorder( focusedErrorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
borderSide: BorderSide( borderSide: BorderSide(
color: Colors.grey, width: 0.5), // Same as focused color: Colors.grey.shade200, width: 1), // Same as focused
), ),
), ),
validator: validator, validator: validator,

View File

@ -4,6 +4,7 @@ import 'package:flutter/material.dart';
import '../Screens/allTrips/remarks_list.dart'; import '../Screens/allTrips/remarks_list.dart';
import '../data/models/plan.dart'; import '../data/models/plan.dart';
import '../services/apiService.dart'; import '../services/apiService.dart';
import '../utils/travelAgent_remarks.dart';
import 'custom_popup.dart'; import 'custom_popup.dart';
class PlanPopupMenu extends StatelessWidget { class PlanPopupMenu extends StatelessWidget {
@ -91,6 +92,22 @@ class PlanPopupMenu extends StatelessWidget {
role: "User"), role: "User"),
); );
}), }),
IconButton(
icon: const Icon(
Icons.comment,
color: Color(0xFF475569),
size: 11,
),
onPressed: () {
showDialog(
context: context,
builder: (context) => CommentModal(
// planId: plan.planId,
planId: plan.planId.toString(),
layoutColorForUser: layoutColor!,
role: "Travel Agent"),
);
}),
], ],
), ),
), ),