Create Plan Validations

This commit is contained in:
Venba Team 2025-03-28 17:43:35 +05:30
parent a82f4a271c
commit 4a1624acde
25 changed files with 2029 additions and 658 deletions

View File

@ -75,7 +75,10 @@ class _LoginWidgetState extends State<LoginWidget> {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text("Login Successful")),
SnackBar(
content: Text("Login Successful"),
backgroundColor: Colors.green, // Set background to green
),
);
context.go('/home'); // Navigate to home
} else {

View File

@ -56,7 +56,10 @@ class _LoginWidgetState extends State<LoginWidget>{
if (response.statusCode == 200) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text("Login Successful")),
SnackBar(
content: Text("Login Successful"),
backgroundColor: Colors.green, // Set background to green
),
);
context.go('/home'); // Login Success
}

View File

@ -10,9 +10,11 @@ class AccomodationScreen extends StatefulWidget {
final Function(bool) onClose; // Callback function
final Function(Map<String,dynamic>) onSaveAccomadation;
final Map<String, dynamic>? selectedItem;
final String? loginUser;
AccomodationScreen({
required this.onClose, required this.onSaveAccomadation, required this.selectedItem});
required this.onClose, required this.onSaveAccomadation, required this.selectedItem, required this.loginUser});
@override
_AccomodationScreenState createState() => _AccomodationScreenState();
@ -54,6 +56,7 @@ class _AccomodationScreenState extends State<AccomodationScreen> {
});
}
Map<String, String> errorMessages = {};
Map<String, dynamic> get accomadationData {
@ -65,6 +68,8 @@ class _AccomodationScreenState extends State<AccomodationScreen> {
"checkout_date": _checkOutController.text,
"checkout_time": _checkOutTimeController.text,
"comments": _commentsController.text,
"created_by": widget.loginUser,
"updated_by": widget.loginUser,
};
if (widget.selectedItem != null) {
@ -74,8 +79,6 @@ class _AccomodationScreenState extends State<AccomodationScreen> {
data["accomodation_id"] = widget.selectedItem!["accomodation_id"];
}
}
return data;
}
@ -95,8 +98,6 @@ class _AccomodationScreenState extends State<AccomodationScreen> {
_addFocusListener(_checkOutTimeFocusNode, (focus) => _checkOutTimeFocus = focus);
_addFocusListener(_commentsFocusNode, (focus) => _commentsFocus = focus);
_destinationController = initController("destination_city");
_hotelNameController = initController("hotel_name");
_checkInController = initController("checkin_date");
@ -105,8 +106,17 @@ class _AccomodationScreenState extends State<AccomodationScreen> {
_checkOutTimeController = initController("checkout_time");
_commentsController = initController("comments");
_destinationController.addListener(() => _clearError("destination_city"));
_hotelNameController.addListener(() => _clearError("hotel_name"));
_checkInController.addListener(() => _clearError("checkin_date"));
_checkInTimeController.addListener(() => _clearError("checkin_time"));
_checkOutController.addListener(() => _clearError("checkout_date"));
_checkOutTimeController.addListener(() => _clearError("checkout_time"));
}
@override
void dispose() {
_destinationFocusNode.dispose();
@ -120,16 +130,46 @@ class _AccomodationScreenState extends State<AccomodationScreen> {
super.dispose();
}
void _clearError(String field) {
if (mounted && errorMessages.containsKey(field)) {
setState(() {
errorMessages.remove(field);
});
}
}
bool isValidData(Map<String, dynamic> data) {
errorMessages.clear(); // Reset errors
// Required fields that must not be empty
List<String> requiredFields = ["destination_city", "hotel_name","checkin_date","checkin_time","checkout_date",
"checkout_time"];
// Check validation for each field
for (String field in requiredFields) {
if (data[field] == null || data[field].toString().trim().isEmpty) {
errorMessages[field] = "Required";
}
}
return errorMessages.isEmpty; // Valid if there are no errors
}
void handleSave(){
print( "Handle Save accomadtion $accomadationData");
widget.onSaveAccomadation(accomadationData); // Send object to parent
print( "Handle Save accomadationData $accomadationData");
Map<String,dynamic> data = accomadationData;
if (!isValidData(data)) {
print("Validation Failed: Required fields are missing.");
setState(() {});
return; // Stop execution if validation fails
}else {
widget.onSaveAccomadation(accomadationData);
}
widget.onClose(false);// Close screen after saving
// Clear only if this is a new entry
// if (widget.selectedItem == null) {
// _commentsController.clear();
// }
}
@ -251,7 +291,14 @@ class _AccomodationScreenState extends State<AccomodationScreen> {
),
),
),
],
if (errorMessages["destination_city"] != null) ...[
SizedBox(height: 5), // Space before error message
Text(
errorMessages["destination_city"]!,
style: TextStyle(color: Colors.red, fontSize: 12),
),
],
],
),
if (isDesktop)
Spacer()
@ -289,7 +336,15 @@ class _AccomodationScreenState extends State<AccomodationScreen> {
),
),
),
],
if (errorMessages["hotel_name"] != null) ...[
SizedBox(height: 5), // Space before error message
Text(
errorMessages["hotel_name"]!,
style: TextStyle(color: Colors.red, fontSize: 12),
),
],
],
),
];
}
@ -428,7 +483,14 @@ class _AccomodationScreenState extends State<AccomodationScreen> {
),
),
),
],
if (errorMessages["checkin_date"] != null) ...[
SizedBox(height: 5), // Space before error message
Text(
errorMessages["checkin_date"]!,
style: TextStyle(color: Colors.red, fontSize: 12),
),
],
],
),
if (isDesktop)
Spacer()
@ -473,7 +535,14 @@ class _AccomodationScreenState extends State<AccomodationScreen> {
),
),
),
],
if (errorMessages["checkin_time"] != null) ...[
SizedBox(height: 5), // Space before error message
Text(
errorMessages["checkin_time"]!,
style: TextStyle(color: Colors.red, fontSize: 12),
),
],
],
),
if (isDesktop)
Spacer()
@ -520,7 +589,14 @@ class _AccomodationScreenState extends State<AccomodationScreen> {
),
),
],
if (errorMessages["checkout_date"] != null) ...[
SizedBox(height: 5), // Space before error message
Text(
errorMessages["checkout_date"]!,
style: TextStyle(color: Colors.red, fontSize: 12),
),
],
],
),
if (isDesktop)
Spacer()
@ -566,6 +642,13 @@ class _AccomodationScreenState extends State<AccomodationScreen> {
),
),
if (errorMessages["checkout_time"] != null) ...[
SizedBox(height: 5), // Space before error message
Text(
errorMessages["checkout_time"]!,
style: TextStyle(color: Colors.red, fontSize: 12),
),
],
],
),
];

View File

@ -11,9 +11,10 @@ class BusScreen extends StatefulWidget {
final Function(bool) onClose;
final Function(Map<String, dynamic>)onSaveBus;
final Map<String, dynamic>? selectedItem;
final String? loginUser;
BusScreen({
required this.onClose, this.apiData, required this.onSaveBus, required this.selectedItem});
required this.onClose, this.apiData, required this.onSaveBus, required this.selectedItem, required this.loginUser});
@override
_BusScreenState createState() => _BusScreenState();
@ -33,6 +34,7 @@ class _BusScreenState extends State<BusScreen> {
final FocusNode _commentsFocusNode = FocusNode();
late Map<String, TextEditingController> _controllers;
Map<String, String> errorMessages = {};
late TextEditingController _tripTypeController = TextEditingController();
late TextEditingController _hotelNameController = TextEditingController();
@ -58,6 +60,8 @@ class _BusScreenState extends State<BusScreen> {
"date": _dateController.text,
"time": _timeController.text,
"comments": _buscommentsController.text,
"created_by": widget.loginUser,
"updated_by": widget.loginUser,
};
@ -129,6 +133,11 @@ class _BusScreenState extends State<BusScreen> {
_dateController = initController("date");
_timeController = initController("time");
_fromController.addListener(() => _clearError("from"));
_toController.addListener(() => _clearError("to"));
_dateController.addListener(() => _clearError("date"));
_timeController.addListener(() => _clearError("time"));
}
@override
@ -145,18 +154,49 @@ class _BusScreenState extends State<BusScreen> {
}
void _clearError(String field) {
if (mounted && errorMessages.containsKey(field)) {
setState(() {
errorMessages.remove(field);
});
}
}
bool isValidData(Map<String, dynamic> data) {
errorMessages.clear(); // Reset errors
// Required fields that must not be empty
List<String> requiredFields = ["from", "to","date","time"];
// Check validation for each field
for (String field in requiredFields) {
if (data[field] == null || data[field].toString().trim().isEmpty) {
errorMessages[field] = "Required";
}
}
return errorMessages.isEmpty; // Valid if there are no errors
}
void handleSave(){
print( "Handle Save miscellaneousData $busData");
widget.onSaveBus(busData); // Send object to parent
print( "Handle Save accomadationData $busData");
Map<String,dynamic> data = busData;
if (!isValidData(data)) {
print("Validation Failed: Required fields are missing.");
setState(() {});
return; // Stop execution if validation fails
}else {
widget.onSaveBus(busData);
}
widget.onClose(false);// Close screen after saving
// Clear only if this is a new entry
// if (widget.selectedItem == null) {
// _commentsController.clear();
// }
}
@override
Widget build(BuildContext context) {
return ResponsiveBuilder(builder: (context, sizingInfo) {
@ -317,10 +357,7 @@ class _BusScreenState extends State<BusScreen> {
selectedPurpose = newValue;
});
print("Updating form data: Flight -> trip_type -> ${newValue ?? ""}");
}
: null,
} : null,
items: dropdownItems,
),
@ -332,86 +369,6 @@ class _BusScreenState extends State<BusScreen> {
}
List<Widget> _builClassType(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(
"Class *",
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74)),
),
SizedBox(height: 5),
CustomTextFieldWrapper(
isFocused: _isHotelNameFocused,
isDesktop: isDesktop,
child: SizedBox(
height: 40,
child: DropdownButtonFormField<String>(
focusNode: _hotelNameFocusNode, // Assign the correct focus node
// 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,
),
// child: TextField(
// focusNode: _hotelNameFocusNode, // Assign the correct focus node
// controller: _hotelNameController,
// style: TextStyle(fontSize: 12),
// decoration: InputDecoration(
// labelText: "Select Class",
// labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
// floatingLabelBehavior: FloatingLabelBehavior.never,
// border: InputBorder.none,
// contentPadding: EdgeInsets.symmetric(vertical: 16),
// ),
// ),
),
),
],
),
];
}
List<Widget> _buildSecondRow(bool isDesktop) {
@ -494,6 +451,13 @@ class _BusScreenState extends State<BusScreen> {
),
),
),
if (errorMessages["from"] != null) ...[
SizedBox(height: 5), // Space before error message
Text(
"Required",
style: TextStyle(color: Colors.red, fontSize: 12),
),
],
],
),
if (isDesktop)
@ -533,6 +497,13 @@ class _BusScreenState extends State<BusScreen> {
),
),
),
if (errorMessages["to"] != null) ...[
SizedBox(height: 5), // Space before error message
Text(
"Required",
style: TextStyle(color: Colors.red, fontSize: 12),
),
],
],
),
if (isDesktop)
@ -580,6 +551,13 @@ class _BusScreenState extends State<BusScreen> {
),
),
if (errorMessages["date"] != null) ...[
SizedBox(height: 5), // Space before error message
Text(
"Required",
style: TextStyle(color: Colors.red, fontSize: 12),
),
],
],
),
if (isDesktop)
@ -626,6 +604,13 @@ class _BusScreenState extends State<BusScreen> {
),
),
if (errorMessages["time"] != null) ...[
SizedBox(height: 5), // Space before error message
Text(
"Required",
style: TextStyle(color: Colors.red, fontSize: 12),
),
],
],
),

View File

@ -7,17 +7,20 @@ import '../../widgets/custom_text_field.dart';
import '../../widgets/custom_text_itnerary_sub.dart';
class FlightScreen extends StatefulWidget {
final Map<String, String> formData;
final Map<String, dynamic>? apiData;
final Function(String tab, String key, String value) updateFormData;
final String? loginUser;
final Function(bool) onClose;
final Function(Map<String,dynamic>) onSaveFlight;
final Map<String,dynamic>? selectedItem;
FlightScreen({required this.formData, required this.updateFormData, required this.apiData,
required this.onClose});
FlightScreen({ required this.apiData,required this.loginUser,
required this.onClose, required this.onSaveFlight,required this.selectedItem});
@override
_FlightScreenState createState() => _FlightScreenState();
}
@ -27,6 +30,7 @@ class _FlightScreenState extends State<FlightScreen> {
Map<String, String?> selectedValues = {};
String? selectedTripType;
Map<int, String?> selectedClasses = {}; // Store class selection for each trip
String? selectedvisa_available;
int multiTripRowCount = 1;
@ -37,33 +41,33 @@ class _FlightScreenState extends State<FlightScreen> {
Map<String, bool> focusStates = {};
Map<String, TextEditingController> textControllers = {};
Map<String, String> fieldErrors = {}; // Holds error messages
List<List<Widget>> rowBuilders = []; // Holds row widgets
List<TextEditingController> controllers = []; // Dynamic controllers
late Map<String, TextEditingController> _controllers;
final FocusNode _tripTypeFocusNode = FocusNode();
final FocusNode _hotelNameFocusNode = FocusNode();
late TextEditingController _tripTypeController = TextEditingController();
late TextEditingController _hotelNameController = TextEditingController();
late TextEditingController _fromController = TextEditingController();
late TextEditingController _toController = TextEditingController();
late TextEditingController _dateController = TextEditingController();
late TextEditingController _timeController = TextEditingController();
late TextEditingController _commentsController = TextEditingController();
bool _tripTypeFocused = false;
Map<String, String> errorMessages = {};
@override
void initState() {
super.initState();
// _initializeRows();
// List<dynamic> purposeList = widget.apiData?['flight_trip_type'] ?? [];
// selectedTripType ??= purposeList.isNotEmpty ? purposeList.first['dropdown_value'] : null;
// Get trip type from widget.selectedItem
selectedTripType = widget.selectedItem?["trip_type"] as String?;
// If null, set it to the first available value from purposeList
List<dynamic> purposeList = widget.apiData?['flight_trip_type'] ?? [];
selectedTripType = purposeList.isNotEmpty ? purposeList.first['dropdown_value'] : null;
if (selectedTripType == null && purposeList.isNotEmpty) {
selectedTripType = purposeList.first['dropdown_value'] as String?;
}
_initializeFields();
getRowCount();
@ -73,28 +77,6 @@ class _FlightScreenState extends State<FlightScreen> {
print("Text Controllers Keys: ${textControllers.keys.toList()}");
// Initialize focus nodes and states dynamically
// for (var field in dataHeader) {
// textControllers["${field}1Controller"] =
// TextEditingController(text: widget.formData[field] ?? "");
// focusNodes["${field}1FocusNode"] = FocusNode();
// focusStates["${field}1Focused"] = false;
//
// }
// for (var key in dataHeader) {
// String controllerKey = "${key}1Controller";
// textControllers[controllerKey] = TextEditingController(text: widget.formData[key] ?? "");
//
// textControllers[controllerKey]?.addListener(() {
// print("$controllerKey updated: ${textControllers[controllerKey]?.text}");
// });
// }
// Add focus listeners
for (var key in focusNodes.keys) {
_addFocusListener(focusNodes[key]!, (focus) {
setState(() {
@ -103,37 +85,23 @@ class _FlightScreenState extends State<FlightScreen> {
});
}
handleUpdateField();
// _addFocusListener(_tripTypeFocusNode, (focus) => _tripTypeFocused = focus);
// _addFocusListener(_hotelNameFocusNode, (focus) => _isHotelNameFocused = focus);
// _addFocusListener(_fromFocusNode, (focus) => _fromFocus = focus);
// _addFocusListener(_toFocusNode, (focus) => _toFocus = focus);
// _addFocusListener(_dateFocusNode, (focus) => _dateFocus = focus);
// _addFocusListener(_timeFocusNode, (focus) => _timeFocus = focus);
// _addFocusListener(_commentsFocusNode, (focus) => _commentsFocus = focus);
// _commentsFocusNode.addListener(() {
// setState(() {
// _commentsFocus = _commentsFocusNode.hasFocus;
// });
// });
List<TextEditingController> controllers = [
_tripTypeController, _hotelNameController, _fromController, _toController, _dateController,
_timeController, _commentsController
];
List<String> keys = [
"_tripType", "_hotelName", "_from", "_Check_In_Time", "_Check_Out",
"_Check_Out_Time", "_comments"
];
for (int i = 0; i < controllers.length; i++) {
controllers[i].addListener(() {
widget.updateFormData("Flight", keys[i], controllers[i].text);
});
int rowCount = 1; // Default row count for One-way
if (selectedTripType == "Roundtrip") {
rowCount = 2; // Fixed for Roundtrip
} else if (selectedTripType == "Multitrip") {
rowCount = multiTripRowCount; // Dynamic row count for Multitrip
}
// Loop through each row and add listeners to clear errors
for (int i = 1; i <= rowCount; i++) {
textControllers["_from${i}Controller"]?.addListener(() => _clearError("from_place_$i"));
textControllers["_to${i}Controller"]?.addListener(() => _clearError("to_place_$i"));
textControllers["_date${i}Controller"]?.addListener(() => _clearError("date_$i"));
textControllers["_time${i}Controller"]?.addListener(() => _clearError("time_$i"));
}
}
@ -180,7 +148,7 @@ class _FlightScreenState extends State<FlightScreen> {
for (var field in dataHeader) {
for (int i = 1; i <= rowCount; i++) {
textControllers["${field}${i}Controller"] =
TextEditingController(text: widget.formData[field]?.toString() ?? "");
TextEditingController();
focusNodes["${field}${i}FocusNode"] = FocusNode();
focusStates["${field}${i}Focused"] = false;
@ -201,32 +169,6 @@ class _FlightScreenState extends State<FlightScreen> {
}
// void _initializeFields() {
// textControllers.clear();
// focusNodes.clear();
// focusStates.clear();
//
// int rowCount = 1; // Default for Oneway
// if (selectedTripType == "RoundTrip") {
// rowCount = 2;
// } else if (selectedTripType == "Multitrip") {
// rowCount = multiTripRowCount;
// }
//
// for (var field in dataHeader) {
// for (int i = 1; i <= rowCount; i++) {
// textControllers["${field}${i}Controller"] = TextEditingController(text: widget.formData[field] ?? "");
// focusNodes["${field}${i}FocusNode"] = FocusNode();
// focusStates["${field}${i}Focused"] = false;
// }
// }
//
// setState(() {}); // Ensure UI updates
// }
// Call this function when adding more rows for Multitrip
void addMultiTripRow() {
setState(() {
multiTripRowCount++; // Increment row count
@ -242,6 +184,17 @@ class _FlightScreenState extends State<FlightScreen> {
});
}
void _clearError(String field) {
if (mounted && errorMessages.containsKey(field)) {
setState(() {
errorMessages.remove(field);
});
}
}
@override
void dispose() {
// _tripTypeFocusNode.dispose();
@ -261,6 +214,195 @@ class _FlightScreenState extends State<FlightScreen> {
Map<String, dynamic> get flightsData{
List<Map<String, dynamic>> trips = [];
int rowCount = 1; // Default for One-way
if (selectedTripType == "Roundtrip") {
rowCount = 2; // Fixed count for Roundtrip
} else if (selectedTripType == "Multitrip") {
rowCount = multiTripRowCount; // Use dynamic count for Multitrip
}
for (int i = 1; i <= rowCount; i++) {
trips.add({
"class": selectedClasses[i] ,
"from_place": textControllers["_from${i}Controller"]?.text ?? "",
"to_place": textControllers["_to${i}Controller"]?.text ?? "",
"date": textControllers["_date${i}Controller"]?.text ?? "",
"time": textControllers["_time${i}Controller"]?.text ?? "",
"created_by": widget.loginUser,
"updated_by": widget.loginUser,
});
}
Map<String,dynamic> data ={
"trip_type": selectedTripType,
"comments": textControllers["_comments1Controller"]?.text ?? "",
"visa_available": selectedvisa_available,
"created_by": widget.loginUser,
"updated_by": widget.loginUser,
"trips": trips,
};
if (widget.selectedItem != null) {
if (widget.selectedItem?["indx"] != null && widget.selectedItem?["indx"] != 0) {
data["indx"] = widget.selectedItem!["indx"];
} else if (widget.selectedItem?["flight_id"] != null && widget.selectedItem?["flight_id"] != 0) {
data["flight_id"] = widget.selectedItem!["flight_id"];
}
}
return data;
}
TextEditingController initController(String key) {
return TextEditingController(text: widget.selectedItem?[key] ?? "");
}
void handleUpdateField() {
if (widget.selectedItem != null) {
textControllers["_comments1Controller"] = initController("comments");
// selectedTripType = widget.selectedItem!["trip_type"] as String?;
// selectedvisa_available = widget.selectedItem!["visa_available"].toString();
if ( widget.selectedItem!["trip_type"] != null) {
selectedTripType = widget.selectedItem!["trip_type"].toString();
}
if ( widget.selectedItem!["visa_available"] != null) {
selectedvisa_available = widget.selectedItem!["visa_available"].toString();
}
// Extract trips from selectedItem
List<dynamic> selectedTrips = widget.selectedItem!["trips"] ?? [];
// Ensure the selectedClasses map and textControllers are cleared before populating
// selectedClasses.clear();
// textControllers.clear();
// Set row count dynamically for Multitrip
if (selectedTripType == "Multitrip") {
multiTripRowCount = selectedTrips.length;
}
// Loop through selected trips and populate text controllers
for (int i = 0; i < selectedTrips.length; i++) {
var trip = selectedTrips[i] as Map<String, dynamic>;
int index = i + 1; // Use 1-based indexing to match the form
selectedClasses[index] = trip["class"].toString();
textControllers["_from${index}Controller"] = TextEditingController(text: trip["from_place"]);
textControllers["_to${index}Controller"] = TextEditingController(text: trip["to_place"]);
textControllers["_date${index}Controller"] = TextEditingController(text: trip["date"]);
textControllers["_time${index}Controller"] = TextEditingController(text: trip["time"]);
}
print("Selected ITEM - ${widget.selectedItem}");
print("Total Trips Loaded: ${selectedTrips.length}");
print("Controllers Set: ${textControllers.keys}");
}
}
void handleSave() {
print("Controllers Key-Value Pairs:");
if (!validateFields()) {
print("Validation failed. Please fill all required fields.");
return;
}
widget.onSaveFlight(flightsData); // Send object to parent
widget.onClose(false);
print("FlightData - $flightsData");
}
bool validateFields() {
errorMessages.clear(); // Reset errors
int rowCount = 1; // Default row count for One-way
if (selectedTripType == "Roundtrip") {
rowCount = 2; // Fixed for Roundtrip
} else if (selectedTripType == "Multitrip") {
rowCount = multiTripRowCount; // Dynamic row count for Multitrip
}
// Loop through each trip row and validate required fields
for (int i = 1; i <= rowCount; i++) {
if (textControllers["_from${i}Controller"]?.text.trim().isEmpty ?? true) {
errorMessages["from_place_$i"] = "Required";
}
if (textControllers["_to${i}Controller"]?.text.trim().isEmpty ?? true) {
errorMessages["to_place_$i"] = "Required";
}
if (textControllers["_date${i}Controller"]?.text.trim().isEmpty ?? true) {
errorMessages["date_$i"] = "Required";
}
if (textControllers["_time${i}Controller"]?.text.trim().isEmpty ?? true) {
errorMessages["time_$i"] = "Required";
}
}
setState(() {}); // Update UI to show error messages
return errorMessages.isEmpty; // Returns true if all required fields are filled
}
void removeTrip(int index) {
if (selectedTripType == "Multitrip" && multiTripRowCount > 1) {
print("Delete Index - $index");
setState(() {
multiTripRowCount--; // Reduce trip count
// Remove corresponding text controllers
textControllers.remove("_from${index}Controller");
textControllers.remove("_to${index}Controller");
textControllers.remove("_date${index}Controller");
textControllers.remove("_time${index}Controller");
// Step 2: Shift remaining textControllers keys
Map<String, TextEditingController> updatedTextControllers = {};
int newIndex = 1;
for (int i = 1; i <= multiTripRowCount + 1; i++) {
if (i == index) continue; // Skip the deleted one
updatedTextControllers["_from${newIndex}Controller"] = textControllers["_from${i}Controller"]!;
updatedTextControllers["_to${newIndex}Controller"] = textControllers["_to${i}Controller"]!;
updatedTextControllers["_date${newIndex}Controller"] = textControllers["_date${i}Controller"]!;
updatedTextControllers["_time${newIndex}Controller"] = textControllers["_time${i}Controller"]!;
newIndex++;
}
textControllers = updatedTextControllers;
// Shift the selectedClasses map BEFORE removing the index
Map<int, String?> updatedClasses = {};
newIndex = 1;
for (int i = 1; i <= selectedClasses.length; i++) {
if (i == index) continue; // Skip the one being deleted
updatedClasses[newIndex] = selectedClasses[i];
newIndex++;
}
selectedClasses = updatedClasses; // Update the map
print("FlightData - $flightsData");
print("Updated Trips: $multiTripRowCount");
print("Updated Classes: $selectedClasses");
});
}
}
@override
Widget build(BuildContext context) {
return ResponsiveBuilder(builder: (context, sizingInfo) {
@ -345,7 +487,7 @@ class _FlightScreenState extends State<FlightScreen> {
// Iterate over rowBuilders based on selectedTripType
if (selectedTripType == "Oneway")
if (selectedTripType == "Oneway")
...rowBuilders.expand((row) => buildResponsiveRow(row)),
@ -400,7 +542,7 @@ class _FlightScreenState extends State<FlightScreen> {
// Create TextEditingController if it doesn't exist
if (!textControllers.containsKey(keyController)) {
textControllers[keyController] = TextEditingController(
text: widget.formData[field]?.toString() ?? "",
);
}
@ -523,11 +665,12 @@ class _FlightScreenState extends State<FlightScreen> {
if (selectedTripType != "Multitrip") {
multiTripRowCount = 1;
}
errorMessages.clear();
});
print("Updating form data: Flight -> trip_type -> $selectedTripType");
_initializeFields();
widget.updateFormData("Flight", "trip_type", newValue ?? "");
// _initializeRows();
}
: null,
@ -556,7 +699,17 @@ class _FlightScreenState extends State<FlightScreen> {
),
),
),
IconButton(onPressed: (){},
SizedBox(
width: isDesktop? 1000 : 80, // Ensure full width
child: Divider(
thickness: 1, // Make it more visible
),
),
IconButton(onPressed: (){
removeTrip(index);
},
icon: Icon(Icons.delete),color: Colors.red,iconSize: 20,)
@ -569,7 +722,7 @@ class _FlightScreenState extends State<FlightScreen> {
List<DropdownMenuItem<String>> dropdownItems = purposeList
.map((item)=>DropdownMenuItem<String>(
value: item['dropdown_value'],
value: item['dropdown_key'],
child: Text(item['dropdown_value']),
)).toList();
@ -583,7 +736,7 @@ class _FlightScreenState extends State<FlightScreen> {
}
// Default selected value
String? selectedPurpose = dropdownItems.isNotEmpty ? dropdownItems.first.value : null;
selectedClasses[index] ??= dropdownItems.isNotEmpty ? dropdownItems.first.value : null;
return [
@ -593,12 +746,15 @@ class _FlightScreenState extends State<FlightScreen> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (selectedTripType == "Multitrip" && index >= 2)
if (selectedTripType == "Multitrip" )
isDesktop?
Row(mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [..._buildDelete(isDesktop, index)])
: Row(mainAxisAlignment: MainAxisAlignment.center,
children: [..._buildDelete(isDesktop, index)]),
SizedBox(
width: MediaQuery.of(context).size.width * 0.89,
child: Row(mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [..._buildDelete(isDesktop, index)]),
)
: Row(mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [..._buildDelete(isDesktop, index)]),
SizedBox(height: 20),
@ -622,7 +778,7 @@ class _FlightScreenState extends State<FlightScreen> {
focusNode: focusNodes["_class${index}FocusNode"],
// focusNode: _tripTypeFocusNode, // Assign the correct focus node
// controller: _hotelNameController,
value: selectedPurpose,
value: selectedClasses[index] ,
style: TextStyle(fontSize: 12),
decoration: InputDecoration(
border: InputBorder.none,
@ -632,10 +788,10 @@ class _FlightScreenState extends State<FlightScreen> {
onChanged: purposeList.isNotEmpty
? (newValue) {
setState(() {
selectedPurpose = newValue;
selectedClasses[index] = newValue;
});
print(selectedPurpose);
print( selectedClasses[index] );
}
: null,
@ -739,6 +895,18 @@ class _FlightScreenState extends State<FlightScreen> {
),
),
),
if (errorMessages["from_place_$index"] != null) ...[
SizedBox(height: 5), // Space before error message
Text(
"Required",
style: TextStyle(color: Colors.red, fontSize: 12),
),
],
],
),
if (isDesktop)
@ -778,6 +946,13 @@ class _FlightScreenState extends State<FlightScreen> {
),
),
),
if (errorMessages["to_place_$index"] != null) ...[
SizedBox(height: 5), // Space before error message
Text(
"Required",
style: TextStyle(color: Colors.red, fontSize: 12),
),
],
],
),
if (isDesktop)
@ -826,6 +1001,18 @@ class _FlightScreenState extends State<FlightScreen> {
),
),
if (errorMessages["date_$index"] != null) ...[
SizedBox(height: 5), // Space before error message
Text(
"Required",
style: TextStyle(color: Colors.red, fontSize: 12),
),
],
],
),
if (isDesktop)
@ -873,6 +1060,14 @@ class _FlightScreenState extends State<FlightScreen> {
),
),
if (errorMessages["time_$index"] != null) ...[
SizedBox(height: 5), // Space before error message
Text(
"Required",
style: TextStyle(color: Colors.red, fontSize: 12),
),
],
],
),
@ -930,11 +1125,11 @@ class _FlightScreenState extends State<FlightScreen> {
List<DropdownMenuItem<String>> dropdownItems = visa_available
.map((item)=>DropdownMenuItem<String>(
value: item['dropdown_value'],
value: item['dropdown_key'],
child: Text(item['dropdown_value']),
)).toList();
selectedvisa_available = dropdownItems.isNotEmpty ? dropdownItems.first.value : null;
selectedvisa_available ??= dropdownItems.isNotEmpty ? dropdownItems.first.value : null;
if (dropdownItems.isEmpty) {
dropdownItems.add(
@ -985,7 +1180,7 @@ class _FlightScreenState extends State<FlightScreen> {
});
print("Updating form data: Flight -> trip_type -> $selectedvisa_available");
widget.updateFormData("Flight", "visa_available", newValue ?? "");
// _initializeRows();
}
: null,
@ -1008,7 +1203,7 @@ class _FlightScreenState extends State<FlightScreen> {
// Close Button
ElevatedButton(
onPressed: () {
Navigator.of(context).pop(); // Close the dialog or screen
widget.onClose(false);// Close the dialog or screen
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.grey[400], // Light grey color
@ -1027,7 +1222,7 @@ class _FlightScreenState extends State<FlightScreen> {
// Save Changes Button
ElevatedButton(
onPressed: () {
// TODO: Implement save logic
handleSave();
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.blue, // Primary color for save

View File

@ -2,6 +2,7 @@ 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';
@ -19,11 +20,13 @@ class ForexScreen extends StatefulWidget {
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.onSaveForex, required this.loginUser });
@override
_ForexScreenState createState() => _ForexScreenState();
@ -35,6 +38,8 @@ class _ForexScreenState extends State<ForexScreen> {
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 = {};
@ -56,18 +61,20 @@ class _ForexScreenState extends State<ForexScreen> {
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": _formatDate(textControllers["_forexStartDate"]?.text),
"end_date": _formatDate(textControllers["_forexEndDate"]?.text),
"start_date": textControllers["_forexStartDate"]?.text,
"end_date": textControllers["_forexEndDate"]?.text,
"country_code": selectedCountry,
"duration": selectedDuration,
"currency": selectedCurrency,
@ -81,8 +88,18 @@ class _ForexScreenState extends State<ForexScreen> {
"deposit_on_cash": textControllers["_cash"]?.text,
"delivery_location": textControllers["_deliveryLocation"]?.text,
"comments": textControllers["_comments"]?.text,
"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;
}
@ -138,7 +155,10 @@ class _ForexScreenState extends State<ForexScreen> {
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.");
}
@ -152,15 +172,63 @@ class _ForexScreenState extends State<ForexScreen> {
}
}
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");
}
// 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
}
void handleSave(){
print( "Handle Save forexData $forexData");
widget.onSaveForex(forexData); // Send object to parent
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() {
@ -195,6 +263,40 @@ class _ForexScreenState extends State<ForexScreen> {
textControllers["_forexStartDate"]?.addListener(_onFieldChanged);
textControllers["_forexEndDate"]?.addListener(_onFieldChanged);
handleUpdatedField();
}
void handleUpdatedField(){
// Set the selected value if available
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
_onFieldChangedForOthers();
setState(() {}); // Update the UI
// // Calculate other expenses (if applicable)
// CalculatedOtherExpenses = calculateOtherExpenses();
}
}
void _addFocusListener(FocusNode node, Function(bool) updateState) {
@ -239,9 +341,77 @@ class _ForexScreenState extends State<ForexScreen> {
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 = (transport + accommodation + telephone).toStringAsFixed(2);
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(() {});
}
@ -259,6 +429,24 @@ class _ForexScreenState extends State<ForexScreen> {
}
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) {
@ -465,19 +653,40 @@ class _ForexScreenState extends State<ForexScreen> {
height: 40,
child: GestureDetector(
onTap: () => _selectCheckOutDate(context),
// 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: const InputDecoration(
decoration: InputDecoration(
labelText: "Select Date",
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
labelStyle: const TextStyle(fontSize: 12, color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16),
suffixIcon: Icon(Icons.calendar_today,
contentPadding: const EdgeInsets.symmetric(vertical: 16),
suffixIcon: const Icon(Icons.calendar_today,
size: 16, color: Colors.grey),
),
),
@ -486,6 +695,13 @@ class _ForexScreenState extends State<ForexScreen> {
),
),
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)
@ -514,7 +730,26 @@ class _ForexScreenState extends State<ForexScreen> {
height: 40,
child: GestureDetector(
onTap: () => _selectForexEndDate(context),
// 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"],
@ -535,6 +770,14 @@ class _ForexScreenState extends State<ForexScreen> {
),
),
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),
),
],
],
),
@ -596,7 +839,13 @@ class _ForexScreenState extends State<ForexScreen> {
),
),
),
if (errorMessages["country_code"] != null) ...[
SizedBox(height: 5), // Space before error message
Text(
"Select Country",
style: TextStyle(color: Colors.red, fontSize: 12),
),
],
],
),
@ -612,9 +861,9 @@ class _ForexScreenState extends State<ForexScreen> {
return [
if (isDesktop) Spacer() else SizedBox(
height: 8,
),
// if (isDesktop) Spacer() else SizedBox(
// height: 8,
// ),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
@ -653,9 +902,10 @@ class _ForexScreenState extends State<ForexScreen> {
),
],
),
if (isDesktop)SizedBox(width: 8,) else SizedBox(
height: 8,
),
// if (isDesktop)SizedBox(width: 8,) else SizedBox(
// height: 8,
// ),
if (isDesktop)Spacer() else SizedBox(height: 8,),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
@ -689,7 +939,8 @@ class _ForexScreenState extends State<ForexScreen> {
],
),
if (isDesktop)SizedBox(width: 8,) else SizedBox(height: 8,),
// if (isDesktop)SizedBox(width: 8,) else SizedBox(height: 8,),
if (isDesktop)Spacer() else SizedBox(height: 8,),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
@ -730,6 +981,39 @@ class _ForexScreenState extends State<ForexScreen> {
),
if (isDesktop)Spacer() else SizedBox(height: 8,),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Quoted 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: 18, fontWeight: FontWeight.w600,color: Color(0xFF575A74)),
),
),
),
),
],
),
// if (isDesktop)Spacer() else SizedBox(height: 8,),
];
}
@ -759,6 +1043,10 @@ class _ForexScreenState extends State<ForexScreen> {
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",
@ -799,6 +1087,10 @@ class _ForexScreenState extends State<ForexScreen> {
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",
@ -838,6 +1130,10 @@ class _ForexScreenState extends State<ForexScreen> {
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",
@ -944,7 +1240,12 @@ class _ForexScreenState extends State<ForexScreen> {
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),
@ -956,6 +1257,14 @@ class _ForexScreenState extends State<ForexScreen> {
),
),
),
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),
),
],
],
),
@ -988,6 +1297,10 @@ class _ForexScreenState extends State<ForexScreen> {
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),
@ -998,6 +1311,16 @@ class _ForexScreenState extends State<ForexScreen> {
),
),
),
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),
),
],
],
),
@ -1082,6 +1405,13 @@ class _ForexScreenState extends State<ForexScreen> {
),
),
),
if (errorMessages["card_number"] != null) ...[
SizedBox(height: 5), // Space before error message
Text(
"Required",
style: TextStyle(color: Colors.red, fontSize: 12),
),
],
],
),
@ -1123,6 +1453,13 @@ class _ForexScreenState extends State<ForexScreen> {
),
),
),
if (errorMessages["delivery_location"] != null) ...[
SizedBox(height: 5), // Space before error message
Text(
"Required",
style: TextStyle(color: Colors.red, fontSize: 12),
),
],
],
)
:SizedBox.shrink()
@ -1201,7 +1538,7 @@ class _ForexScreenState extends State<ForexScreen> {
// Close Button
ElevatedButton(
onPressed: () {
Navigator.of(context).pop(); // Close the dialog or screen
widget.onClose(false); // Close the dialog or screen
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.grey[400], // Light grey color

View File

@ -11,10 +11,12 @@ class InsuranceScreen extends StatefulWidget {
final Function(bool) onClose;
final Function(Map<String, dynamic>) onSaveInsurance;
final Map<String,dynamic>? selectedItem;
final String? loginUser;
InsuranceScreen({
required this.onClose, required this.apiData, required this.onSaveInsurance, required this.selectedItem});
required this.onClose, required this.apiData, required this.onSaveInsurance,
required this.selectedItem,required this.loginUser});
@override
_InsuranceScreenState createState() => _InsuranceScreenState();
@ -45,6 +47,8 @@ class _InsuranceScreenState extends State<InsuranceScreen> {
String? selectedTripType;
String? selectedInsuranceType;
Map<String, String> errorMessages = {};
Map<String, dynamic> get InsuranceData{
Map<String, dynamic> data = {
@ -52,6 +56,8 @@ class _InsuranceScreenState extends State<InsuranceScreen> {
"start_date": _startdateController.text,
"end_date": _endDateController.text,
"comments": _insuranceCommentsController.text,
"created_by": widget.loginUser,
"updated_by": widget.loginUser,
};
if (widget.selectedItem != null) {
@ -103,17 +109,53 @@ class _InsuranceScreenState extends State<InsuranceScreen> {
bool isValidData(Map<String, dynamic> data) {
errorMessages.clear(); // Reset errors
// Required fields that must not be empty
List<String> requiredFields = ["type_of_insurance", "start_date","end_date"];
// Check validation for each field
for (String field in requiredFields) {
if (data[field] == null || data[field].toString().trim().isEmpty) {
errorMessages[field] = "Required";
}
}
return errorMessages.isEmpty; // Valid if there are no errors
}
void handleSave(){
print( "Handle Save InsuranceData $InsuranceData");
widget.onSaveInsurance(InsuranceData); // Send object to parent
Map<String,dynamic> data = InsuranceData;
if (!isValidData(data)) {
print("Validation Failed: Required fields are missing.");
setState(() {});
return; // Stop execution if validation fails
}else {
widget.onSaveInsurance(InsuranceData);
}
widget.onClose(false);// Close screen after saving
// // Clear only if this is a new entry
// if (widget.selectedItem == null) {
// _commentsController.clear();
// }
}
DateTime? _parseDate(String date) {
try {
return DateFormat("yyyy-MM-dd").parse(date); // Change format if needed
} catch (e) {
return null;
}
}
@override
void dispose() {
_tripTypeFocusNode.dispose();
@ -284,6 +326,10 @@ class _InsuranceScreenState extends State<InsuranceScreen> {
? (newValue) {
setState(() {
selectedInsuranceType = newValue;
if (selectedInsuranceType!.isNotEmpty) {
errorMessages.remove("type_of_insurance");
}
});
print(selectedInsuranceType);
@ -375,7 +421,14 @@ class _InsuranceScreenState extends State<InsuranceScreen> {
height: 40,
child: GestureDetector(
onTap: () => _selectCheckOutDate(context),
onTap: () async{
await _selectCheckOutDate(context);
if(_startdateController.text.isNotEmpty){
setState(() {
errorMessages.remove("start_date"); // Removes the key completely
});
}
},
child: AbsorbPointer(
child: TextField(
focusNode: _dateFocusNode,
@ -396,7 +449,16 @@ class _InsuranceScreenState extends State<InsuranceScreen> {
),
),
],
if (errorMessages["start_date"] != null) ...[
SizedBox(height: 5), // Space before error message
Text(
"Required",
style: TextStyle(color: Colors.red, fontSize: 12),
),
],
],
),
if (isDesktop)
Spacer()
@ -423,7 +485,25 @@ class _InsuranceScreenState extends State<InsuranceScreen> {
height: 40,
child: GestureDetector(
onTap: () => _selectEndCheckOutDate(context),
onTap: () async {
await _selectEndCheckOutDate(context);
if (_endDateController.text.isNotEmpty) {
DateTime? startDate = _parseDate(_startdateController.text);
DateTime? endDate = _parseDate(_endDateController.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: _dateFocusNode,
@ -444,6 +524,15 @@ class _InsuranceScreenState extends State<InsuranceScreen> {
),
),
if (errorMessages["end_date"] != null) ...[
SizedBox(height: 5), // Space before error message
Text(
// "Required",
errorMessages["end_date"]!,
style: TextStyle(color: Colors.red, fontSize: 12),
),
],
],
),
if (isDesktop)

View File

@ -12,10 +12,11 @@ class MiscellaneousScreen extends StatefulWidget {
final Function(Map<String, dynamic>) onSaveMiscellaneous;
final Map<String, dynamic>? selectedItem;
final int? selectedIndex;
final String? loginUser;
MiscellaneousScreen({
required this.onClose, required this.apiData, required this.onSaveMiscellaneous,
this.selectedItem, this.selectedIndex,});
this.selectedItem, this.selectedIndex,required this.loginUser});
@override
_MiscellaneousScreenState createState() => _MiscellaneousScreenState();
@ -38,19 +39,23 @@ class _MiscellaneousScreenState extends State<MiscellaneousScreen> {
bool _isHotelNameFocused = false;
bool _commentsFocus = false;
String? selectedSpecialType;
Map<String, String> errorMessages = {};
Map<String, dynamic> get miscellaneousData {
Map<String, dynamic> data = {
"special_request": selectedSpecialType,
"comments": _commentsController.text,
"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?["miscellaneous_id"] != null && widget.selectedItem?["miscellaneous_id"] != 0) {
data["miscellaneous_id"] = widget.selectedItem!["miscellaneous_id"];
}
@ -81,8 +86,6 @@ class _MiscellaneousScreenState extends State<MiscellaneousScreen> {
});
});
_commentsController =
TextEditingController(text: widget.selectedItem?["comments"] ?? "");
@ -103,15 +106,46 @@ class _MiscellaneousScreenState extends State<MiscellaneousScreen> {
}
bool isValidData(Map<String, dynamic> data) {
errorMessages.clear(); // Reset errors
// Required fields that must not be empty
List<String> requiredFields = ["special_request", "comments"];
// 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
}
void handleSave(){
print( "Handle Save miscellaneousData $miscellaneousData");
widget.onSaveMiscellaneous(miscellaneousData); // Send object to parent
Map<String,dynamic> data = miscellaneousData;
if (!isValidData(data)) {
print("Validation Failed: Required fields are missing.");
setState(() {});
return; // Stop execution if validation fails
}else {
widget.onSaveMiscellaneous(miscellaneousData); // Send object to parent
}
widget.onClose(false);// Close screen after saving
// Clear only if this is a new entry
if (widget.selectedItem == null) {
_commentsController.clear();
}
// if (widget.selectedItem == null) {
// _commentsController.clear();
// }
}
@ -280,6 +314,13 @@ class _MiscellaneousScreenState extends State<MiscellaneousScreen> {
),
),
if (errorMessages["special_request"] != null) ...[
SizedBox(height: 5), // Space before error message
Text(
"Select Type",
style: TextStyle(color: Colors.red, fontSize: 12),
),
],
];
}
@ -319,6 +360,13 @@ class _MiscellaneousScreenState extends State<MiscellaneousScreen> {
),
),
),
if (errorMessages["comments"] != null) ...[
SizedBox(height: 5), // Space before error message
Text(
"Required",
style: TextStyle(color: Colors.red, fontSize: 12),
),
],
],
)
];

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:intl/intl.dart';
import 'package:responsive_builder/responsive_builder.dart';
@ -10,9 +11,11 @@ class TaxiScreen extends StatefulWidget {
final Function(bool) onClose;
final Function(Map<String,dynamic>) onSavetaxi;
final Map<String,dynamic>? selectedItem;
final String? loginUser;
TaxiScreen({
required this.onClose, this.apiData, required this.onSavetaxi, required this.selectedItem});
required this.onClose, this.apiData, required this.onSavetaxi,
required this.selectedItem,required this.loginUser});
@override
_TaxiScreenState createState() => _TaxiScreenState();
@ -22,6 +25,7 @@ class _TaxiScreenState extends State<TaxiScreen> {
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
Map<String, String?> selectedValues = {};
Map<String, String> errorMessages = {};
final FocusNode _destinationFocusNode = FocusNode();
final FocusNode _locationFocusNode = FocusNode();
@ -64,6 +68,8 @@ class _TaxiScreenState extends State<TaxiScreen> {
"no_of_passengers": _numPassengerController.text,
"car_type": selectedCarType,
"comments": _taxiCommentsController.text,
"created_by": widget.loginUser,
"updated_by": widget.loginUser,
// "updated_on": ,
// "updated_by": ,
@ -117,6 +123,12 @@ class _TaxiScreenState extends State<TaxiScreen> {
selectedCarType = widget.selectedItem!["car_type"].toString();
}
_destinationController.addListener(() => _clearError("destination_city"));
_locationController.addListener(() => _clearError("location_of_pickup"));
_dateController.addListener(() => _clearError("date"));
_timeController.addListener(() => _clearError("time"));
_numPassengerController.addListener(() => _clearError("no_of_passengers"));
}
@ -143,18 +155,58 @@ class _TaxiScreenState extends State<TaxiScreen> {
void _clearError(String field) {
if (mounted && errorMessages.containsKey(field)) {
setState(() {
errorMessages.remove(field);
});
}
}
bool isValidData(Map<String, dynamic> data) {
errorMessages.clear(); // Reset errors
// Required fields that must not be empty
List<String> requiredFields = ["destination_city", "location_of_pickup","no_of_passengers","date","time"];
// Check validation for each field
for (String field in requiredFields) {
if (data[field] == null || data[field].toString().trim().isEmpty) {
errorMessages[field] = "Required";
}
}
return errorMessages.isEmpty; // Valid if there are no errors
}
void handleSave(){
print( "Handle Save taxiData $taxiData");
widget.onSavetaxi(taxiData); // Send object to parent
Map<String,dynamic> data = taxiData;
if (!isValidData(data)) {
print("Validation Failed: Required fields are missing.");
setState(() {});
return; // Stop execution if validation fails
}else {
widget.onSavetaxi(taxiData);
}
widget.onClose(false);// Close screen after saving
// Clear only if this is a new entry
// if (widget.selectedItem == null) {
// _commentsController.clear();
// }
}
@override
Widget build(BuildContext context) {
return ResponsiveBuilder(builder: (context, sizingInfo) {
@ -306,6 +358,10 @@ class _TaxiScreenState extends State<TaxiScreen> {
focusNode: _numPassengerFocusNode,
controller: _numPassengerController,
style: const TextStyle(fontSize: 12),
keyboardType: TextInputType.numberWithOptions(decimal: true),
inputFormatters: [
FilteringTextInputFormatter.allow(RegExp(r'^\d*\.?\d*$')), // Allow only positive numbers with optional decimal
],
decoration: const InputDecoration(
labelText: "Number of Passenger",
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
@ -317,6 +373,14 @@ class _TaxiScreenState extends State<TaxiScreen> {
),
),
),
if (errorMessages["no_of_passengers"] != null) ...[
SizedBox(height: 5), // Space before error message
Text(
"Required",
style: TextStyle(color: Colors.red, fontSize: 12),
),
],
],
),
if (isDesktop)
@ -519,7 +583,14 @@ class _TaxiScreenState extends State<TaxiScreen> {
),
),
),
],
if (errorMessages["destination_city"] != null) ...[
SizedBox(height: 5), // Space before error message
Text(
"Required",
style: TextStyle(color: Colors.red, fontSize: 12),
),
],
],
),
if (isDesktop)
Spacer()
@ -558,6 +629,13 @@ class _TaxiScreenState extends State<TaxiScreen> {
),
),
),
if (errorMessages["location_of_pickup"] != null) ...[
SizedBox(height: 5), // Space before error message
Text(
"Required",
style: TextStyle(color: Colors.red, fontSize: 12),
),
],
],
),
if (isDesktop)
@ -605,7 +683,14 @@ class _TaxiScreenState extends State<TaxiScreen> {
),
),
],
if (errorMessages["date"] != null) ...[
SizedBox(height: 5), // Space before error message
Text(
"Required",
style: TextStyle(color: Colors.red, fontSize: 12),
),
],
],
),
if (isDesktop)
Spacer()
@ -651,7 +736,14 @@ class _TaxiScreenState extends State<TaxiScreen> {
),
),
],
if (errorMessages["time"] != null) ...[
SizedBox(height: 5), // Space before error message
Text(
"Required",
style: TextStyle(color: Colors.red, fontSize: 12),
),
],
],
),
];
}

View File

@ -11,9 +11,10 @@ class TrainScreen extends StatefulWidget {
final Function(Map<String, dynamic>)onSavetrain;
final Function(bool) onClose;
final Map<String, dynamic>? selectedItem;
final String? loginUser;
TrainScreen({
required this.onClose, this.apiData, required this.onSavetrain, required this.selectedItem});
required this.onClose, this.apiData, required this.onSavetrain, required this.selectedItem, required this.loginUser});
@override
_TrainScreenState createState() => _TrainScreenState();
@ -51,18 +52,20 @@ class _TrainScreenState extends State<TrainScreen> {
String? selectedClass;
Map<String, String> errorMessages = {};
Map<String , dynamic> get trainData {
Map<String, dynamic> data ={
"train_no": _trainNoController.text,
"class": selectedClass,
"from": _fromController.text,
"to": _toController.text,
"from_station": _fromController.text,
"to_station": _toController.text,
"date": _dateController.text,
"time": _timeController.text,
"comments": _trainCommentsController.text,
"created_by": widget.loginUser,
"updated_by": widget.loginUser,
};
if (widget.selectedItem != null) {
@ -124,8 +127,8 @@ class _TrainScreenState extends State<TrainScreen> {
_trainCommentsController = initController("comments");
_trainNoController = initController("train_no");
_fromController = initController("from");
_toController = initController("to");
_fromController = initController("from_station");
_toController = initController("to_station");
_dateController = initController("date");
_timeController = initController("time");
@ -133,6 +136,13 @@ class _TrainScreenState extends State<TrainScreen> {
if (widget.selectedItem != null && widget.selectedItem!["class"] != null) {
selectedClass = widget.selectedItem!["class"].toString();
}
_trainNoController.addListener(() => _clearError("train_no"));
_fromController.addListener(() => _clearError("from_station"));
_toController.addListener(() => _clearError("to_station"));
_dateController.addListener(() => _clearError("date"));
_timeController.addListener(() => _clearError("time"));
}
@ -150,18 +160,53 @@ class _TrainScreenState extends State<TrainScreen> {
}
void _clearError(String field) {
if (mounted && errorMessages.containsKey(field)) {
setState(() {
errorMessages.remove(field);
});
}
}
bool isValidData(Map<String, dynamic> data) {
errorMessages.clear(); // Reset errors
// Required fields that must not be empty
List<String> requiredFields = ["train_no", "class","from_station", "to_station","date","time"];
// Check validation for each field
for (String field in requiredFields) {
if (data[field] == null || data[field].toString().trim().isEmpty) {
errorMessages[field] = "Required";
}
}
return errorMessages.isEmpty; // Valid if there are no errors
}
void handleSave(){
print( "Handle Save trainData $trainData");
widget.onSavetrain(trainData); // Send object to parent
print( "Handle Save accomadationData $trainData");
Map<String,dynamic> data = trainData;
if (!isValidData(data)) {
print("Validation Failed: Required fields are missing.");
setState(() {});
return; // Stop execution if validation fails
}else {
widget.onSavetrain(trainData);
}
widget.onClose(false);// Close screen after saving
// Clear only if this is a new entry
// if (widget.selectedItem == null) {
// _commentsController.clear();
// }
}
@override
Widget build(BuildContext context) {
return ResponsiveBuilder(builder: (context, sizingInfo) {
@ -260,8 +305,14 @@ class _TrainScreenState extends State<TrainScreen> {
) :
Column(
children: _buildTripType(isDesktop)
)
),
if (errorMessages["train_no"] != null) ...[
SizedBox(height: 5), // Space before error message
Text(
"Required",
style: TextStyle(color: Colors.red, fontSize: 12),
),
],
],
),
@ -390,7 +441,14 @@ class _TrainScreenState extends State<TrainScreen> {
),
),
],
if (errorMessages["class"] != null) ...[
SizedBox(height: 5), // Space before error message
Text(
"Required",
style: TextStyle(color: Colors.red, fontSize: 12),
),
],
],
),
];
}
@ -478,7 +536,14 @@ class _TrainScreenState extends State<TrainScreen> {
),
),
),
],
if (errorMessages["from_station"] != null) ...[
SizedBox(height: 5), // Space before error message
Text(
"Required",
style: TextStyle(color: Colors.red, fontSize: 12),
),
],
],
),
if (isDesktop)
Spacer()
@ -517,7 +582,14 @@ class _TrainScreenState extends State<TrainScreen> {
),
),
),
],
if (errorMessages["to_station"] != null) ...[
SizedBox(height: 5), // Space before error message
Text(
"Required",
style: TextStyle(color: Colors.red, fontSize: 12),
),
],
],
),
if (isDesktop)
Spacer()
@ -564,7 +636,14 @@ class _TrainScreenState extends State<TrainScreen> {
),
),
],
if (errorMessages["date"] != null) ...[
SizedBox(height: 5), // Space before error message
Text(
"Required",
style: TextStyle(color: Colors.red, fontSize: 12),
),
],
],
),
if (isDesktop)
Spacer()
@ -610,6 +689,13 @@ class _TrainScreenState extends State<TrainScreen> {
),
),
if (errorMessages["time"] != null) ...[
SizedBox(height: 5), // Space before error message
Text(
"Required",
style: TextStyle(color: Colors.red, fontSize: 12),
),
],
],
),

View File

@ -15,9 +15,12 @@ class VisaScreen extends StatefulWidget {
final Function(bool) onClose;
final Function(Map<String,dynamic>) onSaveVisa;
final Map<String, dynamic>? selectedItem;
final String? loginUser;
VisaScreen({
required this.onClose,required this.onSaveVisa, this.apiData, required this.selectedItem, required this.apiCountryData});
required this.onClose,required this.onSaveVisa, this.apiData, required this.selectedItem,
required this.apiCountryData, required this.loginUser});
@override
_VisaScreenState createState() => _VisaScreenState();
@ -52,13 +55,18 @@ class _VisaScreenState extends State<VisaScreen> {
String? selectedPurpose;
String? selectedCountry;
Map<String, String> errorMessages = {};
Map<String, dynamic> get visaData{
Map<String, dynamic> data ={
"type_of_visa" :selectedPurpose,
"country": selectedCountry,
// "country_code": selectedCountry,
// "country": selectedCountry,
"country_code": selectedCountry,
"start_date": _dateController.text,
"comments":_visaCommentsController.text
"comments":_visaCommentsController.text,
"created_by": widget.loginUser,
"updated_by": widget.loginUser,
};
if (widget.selectedItem != null) {
@ -91,8 +99,9 @@ class _VisaScreenState extends State<VisaScreen> {
if (widget.selectedItem != null && widget.selectedItem!["type_of_visa"] != null) {
selectedPurpose = widget.selectedItem!["type_of_visa"].toString();
}
if (widget.selectedItem != null && widget.selectedItem!["selectedCountry"] != null) {
selectedPurpose = widget.selectedItem!["selectedCountry"].toString();
if (widget.selectedItem != null && widget.selectedItem!["country_code"] != null) {
// selectedPurpose = widget.selectedItem!["selectedCountry"].toString();
selectedCountry = widget.selectedItem!["country_code"] as String?;
}
@ -123,19 +132,45 @@ class _VisaScreenState extends State<VisaScreen> {
}
bool isValidData(Map<String, dynamic> data) {
errorMessages.clear(); // Reset errors
// Required fields that must not be empty
List<String> requiredFields = ["type_of_visa", "country_code","start_date"];
// 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
}
void handleSave(){
print( "Handle Save miscellaneousData $visaData");
widget.onSaveVisa(visaData); // Send object to parent
print( "Handle Save visaData $visaData");
Map<String,dynamic> data = visaData;
if (!isValidData(data)) {
print("Validation Failed: Required fields are missing.");
setState(() {});
return; // Stop execution if validation fails
}else {
widget.onSaveVisa(visaData);
}
widget.onClose(false);// Close screen after saving
// // Clear only if this is a new entry
// if (widget.selectedItem == null) {
// _visaCommentsController.clear();
// }
}
@override
Widget build(BuildContext context) {
return ResponsiveBuilder(builder: (context, sizingInfo) {
@ -233,8 +268,14 @@ class _VisaScreenState extends State<VisaScreen> {
) :
Column(
children: _buildTripType(isDesktop)
)
),
if (errorMessages["type_of_visa"] != null) ...[
SizedBox(height: 5), // Space before error message
Text(
"Required",
style: TextStyle(color: Colors.red, fontSize: 12),
),
],
],
),
@ -336,6 +377,8 @@ class _VisaScreenState extends State<VisaScreen> {
// List<String> countryNames = countryList.map((item) => item['country_name'] as String).toList();
//
late Map<String, String> countryMap; // Mapping country_code -> country_name
late List<String> countryCodes; // List of country codes
@ -350,10 +393,7 @@ class _VisaScreenState extends State<VisaScreen> {
// Extract only country codes for processing
countryCodes = countryMap.keys.toList();
// Set default selected value
if (selectedCountry == null && countryCodes.isNotEmpty) {
selectedCountry = countryCodes.first;
}
selectedCountry ??= null;
// ____________
DateTime? _selectedCheckOutDate;
@ -432,40 +472,23 @@ class _VisaScreenState extends State<VisaScreen> {
selectedCountry = countryMap.entries
.firstWhere((entry) => entry.value == newValue)
.key;
if (selectedCountry!.isNotEmpty) {
errorMessages.remove("country_code");
}
});
},
),
),
),
// CustomTextFieldWrapper(
// isFocused: _isHotelNameFocused,
// isDesktop: isDesktop,
// child: SizedBox(
// height: 40,
//
// child: DropdownButtonFormField<String>(
// focusNode: _hotelNameFocusNode, // Assign the correct focus node
// // controller: _hotelNameController,
// value: selectedCountry,
// style: TextStyle(fontSize: 12),
// decoration: InputDecoration(
// border: InputBorder.none,
// contentPadding: EdgeInsets.symmetric(
// horizontal: 10), // Proper padding
// ),
// onChanged: countryList.isNotEmpty
// ? (newValue) {
// setState(() {
// selectedCountry = newValue;
// });
// }
// : null,
// items: dropdownItems,
// ),
//
//
// ),
// ),
if (errorMessages["country_code"] != null) ...[
SizedBox(height: 5), // Space before error message
Text(
"Required",
style: TextStyle(color: Colors.red, fontSize: 12),
),
],
],
),
@ -493,7 +516,15 @@ class _VisaScreenState extends State<VisaScreen> {
height: 40,
child: GestureDetector(
onTap: () => _selectCheckOutDate(context),
onTap: ()async{
await _selectCheckOutDate(context);
if (_dateController.text.isNotEmpty) {
setState(() {
errorMessages.remove("start_date");
});
}
},
child: AbsorbPointer(
child: TextField(
focusNode: _dateFocusNode,
@ -514,6 +545,13 @@ class _VisaScreenState extends State<VisaScreen> {
),
),
if (errorMessages["start_date"] != null) ...[
SizedBox(height: 5), // Space before error message
Text(
"Required",
style: TextStyle(color: Colors.red, fontSize: 12),
),
],
],
),

View File

@ -49,8 +49,12 @@ class AccomodationListWidget extends StatelessWidget {
List<DataRow> _buildDataRows() {
List<Map<String, dynamic>> filteredList = accommodationList
.where((item) => item["is_active"] == "1")
.toList();
print("filteredList- $filteredList");
return accommodationList.asMap() .entries.map((entry) {
return filteredList.asMap() .entries.map((entry) {
final Map<String, dynamic> item = entry.value;
@ -72,7 +76,7 @@ class AccomodationListWidget extends StatelessWidget {
IconButton(
icon: Icon(Icons.edit, color: Colors.green),
onPressed: () {
onOpen(true, item, "Accommodation");
onOpen(true, item, "Accomodation");
},
),
IconButton(

View File

@ -43,7 +43,7 @@ class BusListWidget extends StatelessWidget{
),
columns: const [
DataColumn(label: Text('#')),
// DataColumn(label: Text('#')),
DataColumn(label: Text('From')),
DataColumn(label: Text('To')),
DataColumn(label: Text('Date')),
@ -67,13 +67,18 @@ class BusListWidget extends StatelessWidget{
List<DataRow> _buildDataRows() {
List<Map<String, dynamic>> filteredList = busList
.where((item) => item["is_active"] == "1")
.toList();
print("filteredList- $filteredList");
return busList.asMap().entries.map((entry) {
return filteredList.asMap().entries.map((entry) {
final Map<String, dynamic> item = entry.value;
return DataRow(cells: [
DataCell(Text(item["indx"]?.toString() ?? "N/A")), // Index column
// DataCell(Text(item["indx"]?.toString() ?? "N/A")), // Index column
DataCell(Text(item["from"]!)),
DataCell(Text(item["to"]!)),
DataCell(Text(item["date"]!)),

View File

@ -1,7 +1,11 @@
import 'package:flutter/material.dart';
class FlightListWidget extends StatelessWidget {
const FlightListWidget({super.key});
final List<Map<String,dynamic>> flightList;
final Function( bool,Map<String,dynamic>, String) onOpen;
final Function(Map<String,dynamic>) onDeleteFlight;
const FlightListWidget({super.key, required this.flightList, required this.onOpen, required this.onDeleteFlight});
@override
Widget build(BuildContext context) {
@ -27,8 +31,8 @@ class FlightListWidget extends StatelessWidget {
horizontalInside: BorderSide(color: Colors.black12), // Only horizontal lines
),
columns: const [
DataColumn(label: Text('#')),
DataColumn(label: Text('Trip Type')),
DataColumn(label: Text('Class')),
DataColumn(label: Text('From')),
DataColumn(label: Text('To')),
DataColumn(label: Text('Actions')),
@ -44,17 +48,24 @@ class FlightListWidget extends StatelessWidget {
}
List<DataRow> _buildDataRows() {
List<Map<String, String>> data = [
{"tripType": "One-Way", "class": "Economy", "from": "NYC", "to": "LA"},
{"tripType": "Round-Trip", "class": "Business", "from": "SF", "to": "Seattle"},
];
return data.map((bus) {
List<Map<String, dynamic>> filteredList = flightList
.where((item) => item["is_active"] == "1")
.toList();
print("filteredList- $filteredList");
return filteredList.asMap().entries.map((entry) {
Map<String,dynamic> item = entry.value;
print("Trip Type: ${item["trip_type"]}");
return DataRow(cells: [
DataCell(Text(bus["tripType"]!)),
DataCell(Text(bus["class"]!)),
DataCell(Text(bus["from"]!)),
DataCell(Text(bus["to"]!)),
DataCell(Text(item["indx"]?.toString() ?? "N/A")),
DataCell(Text(item["trip_type"]?.toString() ?? "N/A")),
DataCell(Text(item["trips"].isNotEmpty ? item["trips"][0]["from_place"]?.toString() ?? "N/A" : "N/A")),
DataCell(Text(item["trips"].isNotEmpty ? item["trips"][0]["to_place"]?.toString() ?? "N/A" : "N/A")),
DataCell(Row(
children: [
IconButton(
@ -66,13 +77,13 @@ class FlightListWidget extends StatelessWidget {
IconButton(
icon: Icon(Icons.edit, color: Colors.green),
onPressed: () {
// Edit action
onOpen(true, item, "Flight");
},
),
IconButton(
icon: Icon(Icons.delete, color: Colors.red),
onPressed: () {
// Delete action
onDeleteFlight(item);
},
),
],

View File

@ -37,7 +37,7 @@ class ForexListWidget extends StatelessWidget{
horizontalInside: BorderSide(color: Colors.black12), // Only horizontal lines
),
columns: const [
DataColumn(label: Text('#')),
// DataColumn(label: Text('#')),
DataColumn(label: Text('Forex Start Date')),
DataColumn(label: Text('Forex End Date')),
DataColumn(label: Text('Country')),
@ -60,13 +60,17 @@ class ForexListWidget extends StatelessWidget{
List<DataRow> _buildDataRows() {
List<Map<String, dynamic>> filteredList = forexList
.where((item) => item["is_active"] == "1")
.toList();
print("filteredList- $filteredList");
return forexList.asMap().entries.map((entry) {
return filteredList.asMap().entries.map((entry) {
Map<String,dynamic> item = entry.value;
return DataRow(cells: [
DataCell(Text(item["indx"]?.toString() ?? "N/A")), // Index column
// DataCell(Text(item["indx"]?.toString() ?? "N/A")), // Index column
DataCell(Text(item["start_date"] ?? "N/A")),
DataCell(Text(item["end_date"] ?? "N/A")),
DataCell(Text(item["country_code"] ?? "N/A")),

View File

@ -32,7 +32,7 @@ class InsuranceListWidget extends StatelessWidget {
horizontalInside: BorderSide(color: Colors.black12), // Only horizontal lines
),
columns: const [
DataColumn(label: Text('#')),
// DataColumn(label: Text('#')),
DataColumn(label: Text('Insurance Type')),
DataColumn(label: Text('Start Date')),
DataColumn(label: Text('End Date')),
@ -51,12 +51,16 @@ class InsuranceListWidget extends StatelessWidget {
List<DataRow> _buildDataRows() {
List<Map<String, dynamic>> filteredList = insuranceList
.where((item) => item["is_active"] == "1")
.toList();
print("filteredList- $filteredList");
return insuranceList.asMap().entries.map((entry) {
return filteredList.asMap().entries.map((entry) {
Map<String,dynamic> item = entry.value;
return DataRow(cells: [
DataCell(Text(item["indx"]?.toString() ?? "N/A")), // Index column
// DataCell(Text(item["indx"]?.toString() ?? "N/A")), // Index column
DataCell(Text(item["type_of_insurance"]!)),
DataCell(Text(item["start_date"]!)),
DataCell(Text(item["end_date"]!)),

View File

@ -5,9 +5,10 @@ class MiscellaneousListWidget extends StatelessWidget {
final List<Map<String,dynamic>> miscellaneousList;
final Function(bool, Map<String, dynamic>, String) onOpen;
final Function(Map<String, dynamic>) onDeleteMiscellaneous;
final Map<String, dynamic>? apiData;
const MiscellaneousListWidget({super.key, required this.miscellaneousList, required this.onOpen,
required this.onDeleteMiscellaneous});
required this.onDeleteMiscellaneous,required this.apiData});
@override
Widget build(BuildContext context) {
@ -33,10 +34,10 @@ class MiscellaneousListWidget extends StatelessWidget {
horizontalInside: BorderSide(color: Colors.black12), // Only horizontal lines
),
columns: const [
DataColumn(label: Text('#')),
// DataColumn(label: Text('#')),
DataColumn(label: Text('Special Request')),
DataColumn(label: Text('Comments')),
DataColumn(label: Text('Created On')),
// DataColumn(label: Text('Created On')),
DataColumn(label: Text('Actions')),
],
rows: _buildDataRows(),
@ -52,16 +53,38 @@ class MiscellaneousListWidget extends StatelessWidget {
List<DataRow> _buildDataRows() {
print("miscellaneousList - $miscellaneousList");
List<dynamic> purposeList = apiData?['miscellaneous_special_request'] ?? [];
return miscellaneousList.asMap().entries.map( (entry) {
print("purposeList - $purposeList");
String getRequestValue(String? specialRequestKey) {
if (specialRequestKey == null) return "N/A";
return purposeList
.firstWhere(
(element) => element["dropdown_key"].toString() == specialRequestKey,
orElse: () => {"dropdown_value": "N/A"},
)["dropdown_value"]
.toString();
}
List<Map<String, dynamic>> filteredList = miscellaneousList
.where((item) => item["is_active"] == "1")
.toList();
print("filteredList- $filteredList");
return filteredList.asMap().entries.map( (entry) {
int index = entry.key + 1; // To start index from 1
Map<String, dynamic> item = entry.value;
print(item);
return DataRow(cells: [
DataCell(Text(item["indx"]?.toString() ?? "N/A")), // Index column
DataCell(Text(item["special_request"] ?? "N/A")),
// DataCell(Text(item["indx"]?.toString() ?? "N/A")), // Index column
// DataCell(Text(specialRequestValue)),
DataCell(Text(getRequestValue(item["special_request"]?.toString()))),
DataCell(Text(item["comments"] ?? "N/A")),
DataCell(Text(item["created_on"] ?? "N/A")),
// DataCell(Text(item["created_on"] ?? "N/A")),
DataCell(Row(
children: [
IconButton(

View File

@ -30,7 +30,7 @@ class TaxiListWidget extends StatelessWidget {
horizontalInside: BorderSide(color: Colors.black12), // Only horizontal lines
),
columns: const [
DataColumn(label: Text('#')),
// DataColumn(label: Text('#')),
DataColumn(label: Text('Destination')),
DataColumn(label: Text('Location Of Pickup')),
DataColumn(label: Text('Date')),
@ -49,13 +49,17 @@ class TaxiListWidget extends StatelessWidget {
List<DataRow> _buildDataRows() {
List<Map<String, dynamic>> filteredList = taxiList
.where((item) => item["is_active"] == "1")
.toList();
print("filteredList- $filteredList");
return taxiList.asMap().entries.map((entry) {
return filteredList.asMap().entries.map((entry) {
final Map<String, dynamic> item = entry.value;
return DataRow(cells: [
DataCell(Text(item["indx"]?.toString() ?? "N/A")), // Index column
// DataCell(Text(item["indx"]?.toString() ?? "N/A")), // Index column
DataCell(Text(item["destination_city"]!)),
DataCell(Text(item["location_of_pickup"]!)),
DataCell(Text(item["date"]!)),

View File

@ -30,11 +30,11 @@ class TrainListWidget extends StatelessWidget {
horizontalInside: BorderSide(color: Colors.black12), // Only horizontal lines
),
columns: const [
DataColumn(label: Text('#')),
// DataColumn(label: Text('#')),
DataColumn(label: Text('Train Number')),
DataColumn(label: Text('Class')),
// DataColumn(label: Text('Class')),
DataColumn(label: Text('From')),
DataColumn(label: Text('To')),
// DataColumn(label: Text('To')),
DataColumn(label: Text('Actions')),
],
rows: _buildDataRows(),
@ -51,15 +51,22 @@ class TrainListWidget extends StatelessWidget {
List<DataRow> _buildDataRows() {
return trainList.asMap().entries.map((entry) {
List<Map<String, dynamic>> filteredList = trainList
.where((item) => item["is_active"] == "1")
.toList();
print("filteredList- $filteredList");
return filteredList.asMap().entries.map((entry) {
final Map<String,dynamic> item = entry.value;
return DataRow(cells: [
DataCell(Text(item["indx"]?.toString() ?? "N/A")),
// DataCell(Text(item["indx"]?.toString() ?? "N/A")),
DataCell(Text(item["train_no"]!)),
DataCell(Text(item["class"]!)),
DataCell(Text(item["from"]!)),
DataCell(Text(item["to"]!)),
// DataCell(Text(item["class"]!)),
DataCell(Text(item["from_station"]!)),
// DataCell(Text(item["to_station"]!)),
DataCell(Row(
children: [
IconButton(

View File

@ -4,8 +4,11 @@ class VisaListWidget extends StatelessWidget {
final List<Map<String, dynamic>> visaList;
final Function(bool, Map<String, dynamic>, String) onOpen;
final Function(Map<String, dynamic>) onDeleteMiscellaneous;
final Map<String, dynamic>? apiData;
final List<dynamic>? apiCountryData;
const VisaListWidget({super.key, required this.visaList, required this.onOpen,required this.onDeleteMiscellaneous});
const VisaListWidget({super.key, required this.visaList, required this.onOpen,
required this.onDeleteMiscellaneous, required this.apiData, required this.apiCountryData});
@override
Widget build(BuildContext context) {
@ -31,7 +34,7 @@ class VisaListWidget extends StatelessWidget {
horizontalInside: BorderSide(color: Colors.black12), // Only horizontal lines
),
columns: const [
DataColumn(label: Text('#')),
// DataColumn(label: Text('#')),
DataColumn(label: Text('Type of Visa')),
DataColumn(label: Text('Country')),
DataColumn(label: Text('Start Date')),
@ -49,16 +52,36 @@ class VisaListWidget extends StatelessWidget {
}
List<DataRow> _buildDataRows() {
List<Map<String, dynamic>> filteredList = visaList
.where((item) => item["is_active"] == "1")
.toList();
print("filteredList- $filteredList");
return visaList.asMap().entries.map((entry){
List<dynamic> visatypeList = apiData?['visa_type_of_visa'] ?? [];
String getRequestForVisa(String? specialRequestKey) {
if (specialRequestKey == null) return "N/A";
return visatypeList
.firstWhere(
(element) => element["dropdown_key"].toString() == specialRequestKey,
orElse: () => {"dropdown_value": "N/A"},
)["dropdown_value"]
.toString();
}
return filteredList.asMap().entries.map((entry){
int index = entry.key + 1; // To start index from 1
Map<String, dynamic> item = entry.value;
print(item);
return DataRow(cells: [
DataCell(Text(item["indx"]?.toString() ?? "N/A")),
DataCell(Text(item["type_of_visa"]!)),
DataCell(Text(item["country"]!)),
// DataCell(Text(item["indx"]?.toString() ?? "N/A")),
// DataCell(Text(item["type_of_visa"]!)),
DataCell( Text(getRequestForVisa( item["type_of_visa"]!.toString()))),
DataCell(Text(item["country_code"]!)),
DataCell(Text(item["start_date"]!)),
DataCell(Row(
children: [

View File

@ -28,6 +28,23 @@ class _CreatePlansState extends State<CreatePlan> {
return ResponsiveBuilder(builder: (context, sizingInfo) {
bool isDesktop = sizingInfo.deviceScreenType == DeviceScreenType.desktop;
final args = GoRouterState.of(context).extra as Map<String, dynamic>? ?? {};
// final planData = args?['planData'];
final bool isViewMode = args?['isViewMode'] ?? false;
final Map<String, dynamic> planData = args['planData'] as Map<String, dynamic>? ?? {};
// print("isViewMode: $isViewMode");
// final bool isViewMode = true;
// final planData = GoRouterState.of(context).extra as Map<String, dynamic>? ?? {};
print("RECived palndata");
// print("RECived palndata - ${planData}");
return Scaffold(
backgroundColor: Colors.white,
body: Column(
@ -47,7 +64,10 @@ class _CreatePlansState extends State<CreatePlan> {
),
),
Text(
"New Plan",
isViewMode
? "View Plan"
: (planData.isNotEmpty ? "Update Plan" : "New Plan"),
style: TextStyle(fontSize: 18),
),
],
@ -70,7 +90,7 @@ class _CreatePlansState extends State<CreatePlan> {
child: SingleChildScrollView(
child: Padding(
padding: EdgeInsets.all(26.0),
child: CreateNewPlan(isDesktop: isDesktop),
child: CreateNewPlan(isDesktop: isDesktop, selectedPlanData: planData, isViewMode : isViewMode),
),
),
),
@ -84,7 +104,9 @@ class _CreatePlansState extends State<CreatePlan> {
class CreateNewPlan extends StatefulWidget {
final bool isDesktop;
const CreateNewPlan({super.key, required this.isDesktop});
final bool isViewMode;
final Map<String, dynamic> selectedPlanData;
const CreateNewPlan({super.key, required this.isDesktop, required this.selectedPlanData, required this.isViewMode});
@override
_CreateNewPlansState createState() => _CreateNewPlansState();
@ -103,6 +125,7 @@ class _CreateNewPlansState extends State<CreateNewPlan> {
late String _selectedOption = "Option 1";
// late String? _selectedIsBillable = "Billable";
String? selectedPlanId;
String? userDetails;
String? userName;
@ -111,6 +134,8 @@ class _CreateNewPlansState extends State<CreateNewPlan> {
String? selectedplanUserId;
bool? selectedIstravelUser;
Map<String, dynamic>? apiData; // Store API response here
List<dynamic>? apiCountryData;
List<dynamic>? apiCostData; // Store API response here
@ -127,9 +152,14 @@ class _CreateNewPlansState extends State<CreateNewPlan> {
Map<String, String?> validationErrors = {};
List<Map<String, dynamic>> miscellaneousList = [];
// List<Map<String, dynamic>> miscellaneousList = [{"special_request": 1, "comments": "posta", "indx": 1}];
List<Map<String, dynamic>> visaList = [];
List<Map<String, dynamic>> insuranceList = [];
List<Map<String, dynamic>> accommodationList = [];
List<Map<String, dynamic>> trainList = [];
List<Map<String, dynamic>> busList = [];
List<Map<String, dynamic>> taxiList = [];
List<Map<String, dynamic>> forexList = [];
List<Map<String, dynamic>> flightList = [];
//Getter Method
Map<String, dynamic> get planData => {
@ -144,30 +174,29 @@ class _CreateNewPlansState extends State<CreateNewPlan> {
"functional_department": selectedFuncDept,
"so_number": "12345",
"status": "0",
// "created_on": "2025-02-10 14:38:21",
"created_by": selfId,
// "updated_on": null,
"updated_by": null,
"updated_by": selfId,
"is_active": "1",
"flight":[],
"accommodation": [],
"bus": [],
"insurance": [],
"flight":flightList,
"accomodation": accommodationList,
"bus": busList,
"taxi": taxiList,
"train": trainList,
"visa": visaList,
"forex": forexList,
"insurance": insuranceList,
"miscellaneous": miscellaneousList,
"taxi": [],
"train": [],
"visa": [],
};
// Function to update miscellaneous list
void updateMiscellaneousData(List<Map<String, dynamic>> newMiscellaneousList) {
setState(() {
miscellaneousList = newMiscellaneousList; // Update miscellaneous data
});
print("Updated Miscellaneous Data in CreateNewPlan: $miscellaneousList");
}
// // Function to update miscellaneous list
// void updateMiscellaneousData(List<Map<String, dynamic>> newMiscellaneousList) {
// setState(() {
// miscellaneousList = newMiscellaneousList; // Update miscellaneous data
// });
// print("Updated Miscellaneous Data in CreateNewPlan: $miscellaneousList");
// }
void handleItineraryUpdate(String type, List<Map<String, dynamic>> newList) {
@ -176,7 +205,13 @@ class _CreateNewPlansState extends State<CreateNewPlan> {
case "Miscellaneous":
miscellaneousList = newList;
break;
case "Train":
case "Visa":
visaList = newList;
break;
case "Insurance":
insuranceList = newList;
break;
case "Train":
trainList = newList;
break;
case "Bus":
@ -186,10 +221,13 @@ class _CreateNewPlansState extends State<CreateNewPlan> {
taxiList = newList;
break;
case "Forex":
taxiList = newList;
forexList = newList;
break;
case "Accommodation":
taxiList = newList;
case "Flight":
flightList = newList;
break;
case "Accomodation":
accommodationList = newList;
break;
default:
print("Unknown itinerary type: $type");
@ -203,6 +241,7 @@ class _CreateNewPlansState extends State<CreateNewPlan> {
super.initState();
fetchUserDetails();
fetchPlans();
fetchCostCenter();
fetchCountryList();
@ -223,6 +262,8 @@ class _CreateNewPlansState extends State<CreateNewPlan> {
});
});
handleUpdateData();
}
@override
@ -233,6 +274,68 @@ class _CreateNewPlansState extends State<CreateNewPlan> {
}
void handleUpdateData() {
if (widget.selectedPlanData != null) {
setState(() {
planUsrId = widget.selectedPlanData['user_id'] ?? '';
_tripTitleController.text = widget.selectedPlanData['trip_title'] ?? '';
_descriptionController.text = widget.selectedPlanData['description'] ?? '';
_selectedTripType = widget.selectedPlanData['trip_type'];
_selectedIsBillable = widget.selectedPlanData['is_billable'] == "1" ? "1" : "2";
// selectedCostCenterId = widget.selectedPlanData['cost_center_id']?.toString() ;
// selectedPurpose = widget.selectedPlanData['purpose_of_travel']?.toString();
// selectedFuncDept =widget.selectedPlanData['functional_department']?.toString();
if (widget.selectedPlanData!["cost_center_id"] != null) {
selectedCostCenterId = widget.selectedPlanData!["cost_center_id"].toString();
}
//
if (widget.selectedPlanData!["purpose_of_travel"] != null) {
selectedPurpose = widget.selectedPlanData!["purpose_of_travel"].toString();
}
if (widget.selectedPlanData!["functional_department"] != null) {
// selectedFuncDept = widget.selectedPlanData!["functional_department"].toString();
selectedFuncDept = widget.selectedPlanData!["functional_department"];
}
// Assign lists from selectedPlanData, ensuring they are properly formatted
flightList = List<Map<String, dynamic>>.from(widget.selectedPlanData['flight'] ?? []);
accommodationList = List<Map<String, dynamic>>.from(widget.selectedPlanData['accomodation'] ?? []);
busList = List<Map<String, dynamic>>.from(widget.selectedPlanData['bus'] ?? []);
taxiList = List<Map<String, dynamic>>.from(widget.selectedPlanData['taxi'] ?? []);
trainList = List<Map<String, dynamic>>.from(widget.selectedPlanData['train'] ?? []);
visaList = List<Map<String, dynamic>>.from(widget.selectedPlanData['visa'] ?? []);
forexList = List<Map<String, dynamic>>.from(widget.selectedPlanData['forex'] ?? []);
insuranceList = List<Map<String, dynamic>>.from(widget.selectedPlanData['insurance'] ?? []);
miscellaneousList = List<Map<String, dynamic>>.from(widget.selectedPlanData['miscellaneous'] ?? []);
});
if (widget.selectedPlanData.containsKey('plan_id') && widget.selectedPlanData['plan_id'] != null) {
print("Plan ID exists: ${widget.selectedPlanData['plan_id']}");
selectedPlanId = widget.selectedPlanData['plan_id']?.toString();
} else {
print("Plan ID is missing or null");
}
print("updatedPlanDAta - $planData");
}
}
void getSelectedPlanFor(){
if (!mounted) return;
@ -381,9 +484,13 @@ class _CreateNewPlansState extends State<CreateNewPlan> {
List <dynamic> plansJson = data['data']; // 'data' is a Map, not a List
setState(() {
apiCostData = plansJson; // Store API response in state
if(apiCostData!.isNotEmpty){
selectedCostCenterId =apiCostData?.first['department_id'];
}
// if(apiCostData!.isNotEmpty){
// selectedCostCenterId =apiCostData?.first['department_id'];
// }
if (apiCostData != null && apiCostData!.isNotEmpty) {
selectedCostCenterId ??= apiCostData!.first['department_id']?.toString();
}
});
print('plansJSON');
@ -482,39 +589,44 @@ class _CreateNewPlansState extends State<CreateNewPlan> {
if(validateForm()){
print("Form submitted successfully: $planData");
postPlanData(planData);
context.go('/listPlan');
}
});
}
Future<void> postPlanData(Map<String, dynamic> planData) async {
final String apiUrldata = '$apiUrl/api/plans/createOrEditPlan';
final token = await getToken(); // Fetch token
Future<void> postPlanData(Map<String, dynamic> planData) async {
final String apiUrldata = '$apiUrl/api/plans/createOrEditPlan';
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(planData), // Convert map to JSON
);
if (response.statusCode == 200) {
print("Plan submitted successfully!");
print("Response: ${response.body}");
} else {
print("Failed to submit plan. Status: ${response.statusCode}");
print("Error: ${response.body}");
if (token == null) {
throw Exception('Token not found. Please log in.');
}
if (selectedPlanId != null && selectedPlanId!.isNotEmpty) {
planData['plan_id'] = selectedPlanId; // Add plan_id for update
}
try {
final response = await http.post(
Uri.parse(apiUrldata),
headers: {
'Authorization': 'Bearer $token',
'Content-Type': 'application/json',
},
body: jsonEncode(planData), // Convert map to JSON
);
if (response.statusCode == 200) {
print("Plan submitted successfully!");
print("Response: ${response.body}");
} else {
print("Failed to submit plan. Status: ${response.statusCode}");
print("Error: ${response.body}");
}
} catch (e) {
print(" Error submitting plan: $e");
}
} catch (e) {
print(" Error submitting plan: $e");
}
}
@ -606,6 +718,7 @@ class _CreateNewPlansState extends State<CreateNewPlan> {
focusNode: _tripTitleFocusNode,
controller: _tripTitleController,
style: TextStyle(fontSize: 12),
enabled: !widget.isViewMode,
decoration: InputDecoration(
labelText: "Trip Title",
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
@ -705,7 +818,7 @@ class _CreateNewPlansState extends State<CreateNewPlan> {
Row(
children: [
Expanded(child: DynamicItinerary(apiData: apiData, apiCountryData: apiCountryData,
onItineraryUpdate: handleItineraryUpdate,
onItineraryUpdate: handleItineraryUpdate,loginUser: selfId,selectedPlanData: planData, isViewMode:widget.isViewMode ,
)), // Wrap with Expanded if needed
],
),
@ -754,7 +867,7 @@ class _CreateNewPlansState extends State<CreateNewPlan> {
contentPadding:
EdgeInsets.symmetric(horizontal: 10), // Proper padding
),
onChanged: (newValue) {
onChanged: widget.isViewMode ? null : (newValue) {
setState(() {
selectedCostCenterId = newValue;
});
@ -790,15 +903,19 @@ class _CreateNewPlansState extends State<CreateNewPlan> {
return Row(
children: [
Radio<String>(
value: item['dropdown_key'], // Use dropdown_value as value
// value: item['dropdown_key'], // Use dropdown_value as value
value: item['dropdown_key'].toString(),// Convert to String
groupValue: _selectedIsBillable,
activeColor: Colors.blueAccent,
onChanged: (value) {
onChanged: widget.isViewMode ? null :(value) {
setState(() {
_selectedIsBillable = value;
});
print("SELECBILL - $_selectedIsBillable");
},
),
Text(item['dropdown_value'] ?? ''), // Display dropdown_value
SizedBox(width: 20), // Spacing
],
@ -882,7 +999,7 @@ class _CreateNewPlansState extends State<CreateNewPlan> {
title: Text(option["title"]!),
value: option["value"]!,
groupValue: _selectedOption,
onChanged: (value) {
onChanged: widget.isViewMode ? null : (value) {
setState(() {
_selectedOption = value!;
if(value == 'Option 2' || value == 'Option 3' ){
@ -918,7 +1035,7 @@ class _CreateNewPlansState extends State<CreateNewPlan> {
title: Text("Domestic"),
value: "1",
groupValue: _selectedTripType,
onChanged: (value) {
onChanged: widget.isViewMode ? null : (value) {
setState(() {
_selectedTripType = value!;
});
@ -941,7 +1058,7 @@ class _CreateNewPlansState extends State<CreateNewPlan> {
title: Text("International"),
value: "2",
groupValue: _selectedTripType,
onChanged: (value) {
onChanged: widget.isViewMode ? null :(value) {
setState(() {
_selectedTripType = value!;
});
@ -955,6 +1072,9 @@ class _CreateNewPlansState extends State<CreateNewPlan> {
Widget _buildNonDescriptionColumn (){
// if (apiData == null) {
// return Center(child: CircularProgressIndicator()); // Show loading indicator
// }
// 'plan_purpose_of_travel' Starts ------------------------------------------------------
@ -962,46 +1082,65 @@ class _CreateNewPlansState extends State<CreateNewPlan> {
List<DropdownMenuItem<String>> dropdownItems = purposeList
.map((item)=>DropdownMenuItem<String>(
value: item['dropdown_key'],
child: Text(item['dropdown_value']),
// value: item['dropdown_key'],
value: item['dropdown_key']?.toString(),
// value: item['dropdown_key'].toString(),
child: Text(item['dropdown_value']),
)).toList();
if (dropdownItems.isEmpty) {
dropdownItems.add(
DropdownMenuItem<String>(
value: null,
// value: null,
value: "1",
child: Text("No options available", style: TextStyle(color: Colors.grey)),
),
);
}
// Default selected value
selectedPurpose ??= dropdownItems.isNotEmpty ? dropdownItems.first.value : null;
// 'plan_functional_department' Starts ---------------------------------------------
// Ensure Selected Value Exists in the Dropdown List
List<String> dropdownKeys = dropdownItems.map((e) => e.value ?? "").toList();
selectedPurpose ??= dropdownItems.isNotEmpty ? dropdownItems.first.value.toString() : "No options";
print("Dropdown Purpose List: ${dropdownItems.map((e) => e.value).toList()}");
print("Selected Purpose: $selectedPurpose");
// 'plan_functional_department' Starts ---------------------------------------------
List<dynamic> funcDeptList = apiData?['plan_functional_department'] ?? [];
List<DropdownMenuItem<String>> dropdownFuncDeptItems = funcDeptList
.map((item)=>DropdownMenuItem<String>(
value: item['dropdown_key'],
// value: item['dropdown_key'],
value: item['dropdown_key']?.toString(),
child: Text(item['dropdown_value']),
)).toList();
if (dropdownFuncDeptItems.isEmpty) {
dropdownFuncDeptItems.add(
DropdownMenuItem<String>(
value: null,
// value: null,
value: "1",
child: Text("No options available", style: TextStyle(color: Colors.grey)),
),
);
}
// Default selected value
selectedFuncDept ??= dropdownFuncDeptItems.isNotEmpty ? dropdownFuncDeptItems.first.value : null;
// 'plan_functional_department' End
// selectedFuncDept ??= dropdownFuncDeptItems.isNotEmpty ? dropdownFuncDeptItems.first.value.toString() : null;
selectedFuncDept ??= dropdownFuncDeptItems.isNotEmpty ? dropdownFuncDeptItems.first.value.toString() : "No options";
return Column(
print("Dropdown Functional Department List: ${dropdownFuncDeptItems.map((e) => e.value).toList()}");
print("Selected Functional Department: $selectedFuncDept");
return Column(
children: [
Row(
children: [
@ -1021,7 +1160,10 @@ class _CreateNewPlansState extends State<CreateNewPlan> {
isDesktop: widget.isDesktop,
child: SizedBox(
height: 45, // Set appropriate height
child: DropdownButtonFormField<String>(
child: apiData == null
? Center(child: CircularProgressIndicator()) // Show loading inside dropdown
:
DropdownButtonFormField<String>(
value: selectedPurpose,
style: TextStyle(fontSize: 12),
decoration: InputDecoration(
@ -1029,11 +1171,12 @@ class _CreateNewPlansState extends State<CreateNewPlan> {
contentPadding: EdgeInsets.symmetric(
horizontal: 10), // Proper padding
),
onChanged: purposeList.isNotEmpty
onChanged: widget.isViewMode ? null : purposeList.isNotEmpty
? (newValue) {
setState(() {
selectedPurpose = newValue;
});
print("selectedPurpose - $selectedPurpose");
}
: null,
items: dropdownItems,
@ -1065,7 +1208,10 @@ class _CreateNewPlansState extends State<CreateNewPlan> {
isDesktop: widget.isDesktop,
child: SizedBox(
height: 45, // Set appropriate height
child: DropdownButtonFormField<String>(
child:
apiData == null
? Center(child: CircularProgressIndicator()) // Show loading inside dropdown
:DropdownButtonFormField<String>(
value: selectedFuncDept,
style: TextStyle(fontSize: 12),
decoration: InputDecoration(
@ -1073,7 +1219,7 @@ class _CreateNewPlansState extends State<CreateNewPlan> {
contentPadding: EdgeInsets.symmetric(
horizontal: 10), // Proper padding
),
onChanged: funcDeptList.isNotEmpty
onChanged: widget.isViewMode ? null : funcDeptList.isNotEmpty
? (newValue) {
setState(() {
selectedFuncDept = newValue;
@ -1123,7 +1269,8 @@ class _CreateNewPlansState extends State<CreateNewPlan> {
maxLines: 6,
keyboardType: TextInputType.multiline,
style: TextStyle(fontSize: 12),
decoration: InputDecoration(
enabled: !widget.isViewMode,
decoration: InputDecoration(
labelText: "Description",
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never,
@ -1143,14 +1290,42 @@ class _CreateNewPlansState extends State<CreateNewPlan> {
List<Widget> _buildSubmit(isDesktop){
return[
ElevatedButton(
onPressed: (){},
child: Text("Cancel")),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.white,
foregroundColor: Colors.blueAccent,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
side: BorderSide(color: Colors.blueAccent, width: 2),
),
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
),
onPressed: (){
context.go('/listPlan');
},
child: Text("Cancel")
),
SizedBox(width: 20,),
ElevatedButton(
onPressed: (){
handleSubmit();
},
child: Text("Submit"))
MouseRegion(
cursor: widget.isViewMode ? SystemMouseCursors.forbidden : SystemMouseCursors.click,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: widget.isViewMode ? Colors.blueAccent : Colors.blueAccent, // Keep original color
foregroundColor: widget.isViewMode ? Colors.white : Colors.white, // Keep original color
disabledBackgroundColor: Colors.blueAccent, // Ensure color remains when disabled
disabledForegroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
side: BorderSide(color: Colors.blueAccent, width: 2),
),
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
),
onPressed: widget.isViewMode ? null : handleSubmit, // Disable when in view mode
child: Text("Submit"),
),
)
];
}

View File

@ -24,8 +24,13 @@ import '../itnerary_list/visa_list.dart';
class DynamicItinerary extends StatefulWidget {
final Map<String, dynamic>? apiData;
final List<dynamic>? apiCountryData;
final String? loginUser;
final Function(String, List<Map<String, dynamic>>) onItineraryUpdate; // Updated Signature
const DynamicItinerary({super.key, required this.apiData, required this.onItineraryUpdate, required this.apiCountryData});
final Map<String,dynamic> selectedPlanData;
final bool isViewMode ;
const DynamicItinerary({super.key, required this.apiData, required this.onItineraryUpdate,
required this.apiCountryData, required this.loginUser,required this.selectedPlanData, required this.isViewMode});
@override
@ -44,103 +49,14 @@ class _DynamicItineraryState extends State<DynamicItinerary> {
// List<Map<String, dynamic>> miscellaneousList = [];
Map<String, List<Map<String, dynamic>>> itineraryData = {
"Train": [{
"train_id": "2",
"plan_id": "2",
"class": "1",
"train_no": "OJH65JHB87",
"from": "Madurai",
"to": "Chennai",
"date": "2025-02-02",
"comments": "1 st class AC",
"created_on": "2025-02-10 14:38:21",
"created_by": null,
"updated_on": null,
"updated_by": null,
"is_active": "1"
}],
"Bus": [{
"bus_id": "2",
"plan_id": "2",
"from": "Chennai - OMR",
"to": "Chennai - ECR",
"date": "2025-02-15",
"time": "12:00:00",
"comments": "i need ac bus",
"created_on": "2025-02-10 14:38:21",
"created_by": null,
"updated_on": null,
"updated_by": null,
"is_active": "1"
}],
"Taxi": [{
"taxi_id": "2",
"plan_id": "2",
"destination_city": "Madurai",
"date": "2025-02-15",
"time": "12:00:00",
"location_of_pickup": "chennai - ECR",
"car_required_for": "1",
"no_of_passengers": "2",
"car_type": "1",
"comments": "Come Sharply",
"created_by": "f",
"updated_by": "g",
"is_active": "1"
}],
"Miscellaneous": [{
"miscellaneous_id": "2",
"plan_id": "2",
"special_request": "2",
"comments": "Please arrange one guide for me ",
"created_by": null,
"updated_by": null,
"is_active": "1"
}],
"Train": [],
"Bus": [],
"Taxi": [],
"Miscellaneous": [],
"Flight": [],
"Accommodation": [{
"accomodation_id": "2",
"plan_id": "2",
"destination_city": "chennai",
"hotel_name": "The park",
"checkin_date": "2025-02-14",
"checkin_time": "03:00:00",
"checkout_date": "2025-02-15",
"checkout_time": "03:00:00",
"comments": "A/c is must",
"created_on": "2025-02-10 14:38:21",
"created_by": null,
"updated_on": null,
"updated_by": null,
"is_active": "0"
}],
"Insurance": [{
"insurance_id": "2",
"plan_id": "2",
"start_date": "2025-02-01",
"end_date": "2025-02-28",
"type_of_insurance": "1",
"comments": "Temp insurance",
"created_on": "2025-02-10 14:38:21",
"created_by": null,
"updated_on": null,
"updated_by": null,
"is_active": "1"
}],
"Visa": [{
"visa_id": "2",
"plan_id": "2",
"country": "2",
// "country_code": "2",
"type_of_visa": "2",
"start_date": "2025-02-01",
"comments": "visa registered",
"created_on": "2025-02-10 14:38:21",
"created_by": null,
"updated_on": null,
"updated_by": null,
"is_active": "1"
}],
"Accomodation": [],
"Insurance": [],
"Visa": [],
"Forex": [],
};
@ -152,12 +68,62 @@ class _DynamicItineraryState extends State<DynamicItinerary> {
"Taxi": {}, // Stores data for the Car tab
"Bus": {}, // Stores data for the Bus tab
"Insurance": {}, // Stores data for the Bus tab
"Accommodation": {},// Stores data for the Accommodation tab
"Miscellaneous": {},// Stores data for the Accommodation tab
"Accomodation": {},// Stores data for the Accomodation tab
"Miscellaneous": {},
"Forex": {},
"Visa": {},// Stores data for the Accommodation tab
"Visa": {},
};
@override
void initState() {
super.initState();
handleSelectedPlan();
}
void handleSelectedPlan(){
// Check if selectedPlanData has itinerary data
if (hasAnyItineraryData()) {
print("selectedPlanData HAS DATA");
setState(() {
itineraryData = {
"Train": List<Map<String, dynamic>>.from(widget.selectedPlanData['train'] ?? []),
"Bus": List<Map<String, dynamic>>.from(widget.selectedPlanData['bus'] ?? []),
"Taxi": List<Map<String, dynamic>>.from(widget.selectedPlanData['taxi'] ?? []),
"Miscellaneous": List<Map<String, dynamic>>.from(widget.selectedPlanData['miscellaneous'] ?? []),
"Flight": List<Map<String, dynamic>>.from(widget.selectedPlanData['flight'] ?? []),
"Accomodation": List<Map<String, dynamic>>.from(widget.selectedPlanData['accomodation'] ?? []),
"Insurance": List<Map<String, dynamic>>.from(widget.selectedPlanData['insurance'] ?? []),
"Visa": List<Map<String, dynamic>>.from(widget.selectedPlanData['visa'] ?? []),
"Forex": List<Map<String, dynamic>>.from(widget.selectedPlanData['forex'] ?? []),
};
});
}
else {
print("No itinerary data available");
}
}
bool hasAnyItineraryData() {
List<String> keys = [
"train", "bus", "taxi", "miscellaneous", "flight",
"accomodation", "insurance", "visa", "forex"
];
for (String key in keys) {
if (widget.selectedPlanData.containsKey(key) &&
widget.selectedPlanData[key] is List &&
(widget.selectedPlanData[key] as List).isNotEmpty) {
return true; // At least one list has data
}
}
return false; // No itinerary data available
}
void handleClose(bool value) {
setState(() {
selectedOption = "";
@ -231,6 +197,7 @@ class _DynamicItineraryState extends State<DynamicItinerary> {
if (itemId != -1) {
print(" Updating existing item with id: $existingId");
newData["is_active"] = "1";
itemList[itemId] = newData;
return;
}
@ -241,39 +208,57 @@ class _DynamicItineraryState extends State<DynamicItinerary> {
int itemIndex = itemList.indexWhere((item) => item["indx"] == existingIndex);
if (itemIndex != -1) {
print("Updating existing item with indx: $existingIndex");
newData["is_active"] = "1";
itemList[itemIndex] = newData; // Update the item
return;
}
}
// CASE 3: New Entry (Assign new indx)
print(" Creating new entry");
newData["indx"] = itemList.length + 1; // Assign a new indx
newData["is_active"] = "1";
itemList.add(newData);
print(" Updated $type List: ${itineraryData[type]}");
});
print( " onItineraryUpdate - $type - ${itineraryData[type]!} ");
widget.onItineraryUpdate(type, itineraryData[type]!); // Notify parent
}
void handleItinerarydelete(String type, Map<String, dynamic> data){
setState(() {
if(!itineraryData.containsKey(type)){
return;
// if(!itineraryData.containsKey(type)){
// return;
// }
if (!itineraryData.containsKey(type)) {
itineraryData[type] = []; // Initialize if null
}
List<Map<String, dynamic>> itemList = itineraryData[type]!;
String? idKey = "${type.toLowerCase()}_id";
String? existingId = data[idKey];
// int? existingId = data["id"];
String? existingId = data["id"];
// String? existingId = data["id"];
int? existingIndex = data["indx"];
print("🗑️ Deleting item -> ID: $existingId, Index: $existingIndex");
// Delete by ID
if(existingId != null && existingId != 0){
itemList.removeWhere((item) => item["id"]?.toString() == existingId.toString());
// itemList.removeWhere((item) => item[idKey]?.toString() == existingId.toString());
for (var item in itemList) {
if (item[idKey]?.toString() == existingId.toString()) {
item["is_active"] = 0; // Soft delete
print("Updated is_active to 0 for ID: $existingId");
}
}
itineraryData[type] = List.from(itemList);
print("Deleted by ID: $existingId");
}
//Delete by Index
@ -287,6 +272,7 @@ class _DynamicItineraryState extends State<DynamicItinerary> {
itineraryData[type]= List.from(itemList);
});
print( " onItineraryUpdate - $type - ${itineraryData[type]!} ");
widget.onItineraryUpdate(type, itineraryData[type]!); // Notify parent
}
@ -334,6 +320,7 @@ class _DynamicItineraryState extends State<DynamicItinerary> {
break;
case "Visa":
selectedListWidget = VisaListWidget(visaList : itineraryData["Visa"]!,
apiData: widget.apiData, apiCountryData : widget.apiCountryData,
onOpen: handleEdit,
onDeleteMiscellaneous: (data) => handleItinerarydelete("Visa", data),);
break;
@ -342,69 +329,76 @@ class _DynamicItineraryState extends State<DynamicItinerary> {
onOpen: handleEdit,
onDeleteForex: (data) => handleItinerarydelete("Forex", data),);
break;
case "Accommodation":
selectedListWidget = AccomodationListWidget(accommodationList: itineraryData["Accommodation"]!,
case "Accomodation":
selectedListWidget = AccomodationListWidget(accommodationList: itineraryData["Accomodation"]!,
onOpen: handleEdit,
onDeleteAccommodation:(data) => handleItinerarydelete("Accommodation", data));
onDeleteAccommodation:(data) => handleItinerarydelete("Accomodation", data));
break;
case "Miscellaneous":
selectedListWidget = MiscellaneousListWidget(miscellaneousList: itineraryData["Miscellaneous"]!,
onOpen: handleEdit,
onOpen: handleEdit,apiData: widget.apiData,
onDeleteMiscellaneous: (data) => handleItinerarydelete("Miscellaneous", data),
);
break;
case "Flight":
default:
selectedListWidget = FlightListWidget();
selectedListWidget = FlightListWidget(flightList : itineraryData["Flight"]!,
onOpen: handleEdit,
onDeleteFlight: (data) => handleItinerarydelete("Flight", data)
);
break;
}
switch (selectedOption) {
case "Train":
selectedWidget = TrainScreen(onClose: handleClose, apiData: widget.apiData,
selectedWidget = TrainScreen(onClose: handleClose, apiData: widget.apiData, loginUser : widget.loginUser,
onSavetrain :(data) => handleItineraryUpdate("Train", data),
selectedItem: selectedItem);
break;
case "Taxi":
selectedWidget = TaxiScreen(onClose: handleClose,apiData: widget.apiData,
selectedWidget = TaxiScreen(onClose: handleClose,apiData: widget.apiData, loginUser : widget.loginUser,
onSavetaxi: (data)=> handleItineraryUpdate("Taxi", data),
selectedItem: selectedItem);
break;
case "Bus":
selectedWidget = BusScreen(onClose: handleClose, apiData: widget.apiData,
selectedWidget = BusScreen(onClose: handleClose, apiData: widget.apiData, loginUser : widget.loginUser,
onSaveBus: (data)=> handleItineraryUpdate("Bus", data),
selectedItem: selectedItem);
break;
case "Insurance":
selectedWidget = InsuranceScreen(onClose: handleClose, apiData: widget.apiData,
selectedWidget = InsuranceScreen(onClose: handleClose, apiData: widget.apiData, loginUser : widget.loginUser,
onSaveInsurance:(data) => handleItineraryUpdate("Insurance", data),
selectedItem: selectedItem);
break;
case "Visa":
selectedWidget = VisaScreen(onClose: handleClose,apiData: widget.apiData, apiCountryData : widget.apiCountryData,
selectedWidget = VisaScreen(onClose: handleClose,apiData: widget.apiData, apiCountryData : widget.apiCountryData, loginUser : widget.loginUser,
onSaveVisa: (data) => handleItineraryUpdate("Visa", data),
selectedItem: selectedItem,);
break;
case "Miscellaneous":
selectedWidget = MiscellaneousScreen(onClose: handleClose, apiData: widget.apiData,
selectedWidget = MiscellaneousScreen(onClose: handleClose, apiData: widget.apiData, loginUser : widget.loginUser,
onSaveMiscellaneous: (data) => handleItineraryUpdate("Miscellaneous", data),
selectedItem: selectedItem, selectedIndex: selectedIndex, );
break;
case "Accommodation":
selectedWidget = AccomodationScreen( onClose: handleClose,
onSaveAccomadation: (data)=>handleItineraryUpdate("Accommodation", data),
case "Accomodation":
selectedWidget = AccomodationScreen( onClose: handleClose, loginUser : widget.loginUser,
onSaveAccomadation: (data)=>handleItineraryUpdate("Accomodation", data),
selectedItem: selectedItem, );
break;
case "Forex":
selectedWidget = ForexScreen( onClose: handleClose,apiData: widget.apiData, apiCountryData : widget.apiCountryData,
selectedWidget = ForexScreen( onClose: handleClose,apiData: widget.apiData, loginUser : widget.loginUser,
apiCountryData : widget.apiCountryData,
onSaveForex: (data)=>handleItineraryUpdate("Forex", data),
selectedItem: selectedItem);
break;
case "Flight":
default:
selectedWidget = FlightScreen(onClose: handleClose,formData: formData["Flight"]!, updateFormData: updateFormData, apiData: widget.apiData,);
selectedWidget = FlightScreen(onClose: handleClose,loginUser : widget.loginUser,
onSaveFlight: (data)=>handleItineraryUpdate("Flight", data), apiData: widget.apiData,
selectedItem: selectedItem
);
break;
}
@ -478,7 +472,7 @@ class _DynamicItineraryState extends State<DynamicItinerary> {
SizedBox(width: 20),
_buildOption("Bus"),
SizedBox(width: 20),
_buildOption("Accommodation"),
_buildOption("Accomodation"),
SizedBox(width: 20),
_buildOption("Forex"),
SizedBox(width: 20),
@ -509,7 +503,7 @@ class _DynamicItineraryState extends State<DynamicItinerary> {
SizedBox(width: 5),
SizedBox(width: 5),
if (selectedListOption == title)
if (selectedListOption == title && widget.isViewMode == false)
GestureDetector(
onTap: () {
setState(() {

View File

@ -1,4 +1,5 @@
import 'dart:convert';
import 'dart:core';
import 'package:frontend/data/models/plan.dart';
import 'package:go_router/go_router.dart';
import 'package:http/http.dart' as http;
@ -64,6 +65,48 @@ class _ListPlansState extends State<ListPlans>{
}
Future <Map<String,dynamic>> getViewPlan(String planId) async{
final String apiUrldata = '$apiUrl/api/plans/find/$planId';
print("API URL: $apiUrldata");
final token = await getToken();
if (token == null) {
throw Exception('Token not found. Please log in.');
}
final response = await http.put(
Uri.parse(apiUrldata),
headers: {
'Authorization': 'Bearer $token', // Add token here
'Content-Type': 'application/json',
},
);
if (response.statusCode == 200) {
final Map<String,dynamic>? resData = json.decode(response.body);
return resData?["data"];
} else {
throw Exception('Failed to load plans');
}
}
void viewPlan(String planId, {bool isViewMode = false}) async{
try {
Map<String, dynamic> planData = await getViewPlan(planId);
print("ViewAAA - $planData");
context.go('/createPlan',extra: {'planData': planData, 'isViewMode': isViewMode} );
} catch (e) {
print("Error fetching plan: $e");
}
}
Widget build(BuildContext context) {
return ResponsiveBuilder(builder: (context, sizingInfo) {
bool isDesktop = sizingInfo.deviceScreenType == DeviceScreenType.desktop;
@ -134,83 +177,189 @@ class _ListPlansState extends State<ListPlans>{
List<Plan> plans = snapshot.data!; // Extract the list of plans
return ResponsiveBuilder(
builder: (context, sizingInfo) {
bool isTabletOrDesktop = sizingInfo.isTablet || sizingInfo.isDesktop;
return SingleChildScrollView(
scrollDirection: Axis.vertical,
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
// scrollDirection: isTabletOrDesktop ? Axis.vertical : Axis.horizontal,
child: SizedBox(
// constraints: isTabletOrDesktop
// ? const BoxConstraints(maxWidth: double.infinity)
// : BoxConstraints.tightFor(width: 600),
width: MediaQuery.of(context).size.width ,
// Ensure planId is sorted in descending order
plans.sort((a, b) => int.parse(b.planId.toString()).compareTo(int.parse(a.planId.toString())));
// return ResponsiveBuilder(
// builder: (context, sizingInfo) {
// bool isTabletOrDesktop = sizingInfo.isTablet || sizingInfo.isDesktop;
//
// return SingleChildScrollView(
// scrollDirection: Axis.horizontal,
// child: Container(
// color: Colors.grey,
// child: SizedBox(
// width: MediaQuery.of(context).size.width ,
// child: SingleChildScrollView(
// scrollDirection: Axis.vertical,
// // scrollDirection: isTabletOrDesktop ? Axis.vertical : Axis.horizontal,
//
// // constraints: isTabletOrDesktop
// // ? const BoxConstraints(maxWidth: double.infinity)
// // : BoxConstraints.tightFor(width: 600),
//
//
// child: DataTable(
// // columnSpacing: 50.0,
// dividerThickness: 0.5, // Reduce the thickness of row dividers
// border: TableBorder(
// horizontalInside: BorderSide(width: 0.5, color: Colors.grey.shade200), // Reduce horizontal line thickness
// ),
// columns: const [
// DataColumn(label: Text('Plan ID', style: TextStyle(color: Colors.grey, fontWeight: FontWeight.bold,))),
// DataColumn(label: Text('Trip Title', style: TextStyle(color: Colors.grey, fontWeight: FontWeight.bold,))),
// DataColumn(label: Text('Trip Type', style: TextStyle(color: Colors.grey, fontWeight: FontWeight.bold,))),
// DataColumn(label: Text('Cost Center', style: TextStyle(color: Colors.grey, fontWeight: FontWeight.bold,))),
// // DataColumn(label: Text('Functional Department', style: TextStyle(color: Colors.grey, fontWeight: FontWeight.bold,))),
// // DataColumn(label: Text('Purpose Of Travel', style: TextStyle(color: Colors.grey, fontWeight: FontWeight.bold,))),
// // DataColumn(label: Text('Description', style: TextStyle(color: Colors.grey, fontWeight: FontWeight.bold,))),
// //
// DataColumn(label: Text('Is Billable', style: TextStyle(color: Colors.grey, fontWeight: FontWeight.bold,))),
// DataColumn(label: Text('Status', style: TextStyle(color: Colors.grey, fontWeight: FontWeight.bold,))),
// DataColumn(label: Text('Actions', style: TextStyle(color: Colors.grey, fontWeight: FontWeight.bold,))),
// ],
// rows: plans.map((plan) {
// return DataRow(cells: [
// DataCell(Text(plan.planId)),
// // DataCell(Text(plan.tripTitle)),
// DataCell(Row(
// children: [
// Flexible(
// child: Text(
// plan.tripTitle,
// softWrap: true,
// overflow: TextOverflow.ellipsis, // Adds "..." if text is too long
// ),
// ),
// ],
// )),
//
//
// DataCell(Text(plan.tripType)),
// DataCell(Text(plan.costCenter)),
// // DataCell(Text(plan.functionalDepartment)),
// // DataCell(Text(plan.purposeOfTravel)),
// // DataCell(Text(plan.description)),
// //
// DataCell(Text(plan.isBillable)),
// DataCell(Text(plan.status)),
// DataCell(
// TextButton(
// onPressed: () {
// viewPlan(plan.planId);
// print("View button clicked for ${plan.planId}");
// },
// child: const Text('View',
// style: TextStyle(color: Colors.blueAccent)),
// ),
// ),
// ]);
// }).toList(),
// ),
//
//
// ),
// ),
// ),
// );
//
// },
// );
return Expanded(
child: SingleChildScrollView(
// scrollDirection: Axis.horizontal, // Outer wrapper for horizontal scrolling
scrollDirection: Axis.vertical,
child: SizedBox(
width: MediaQuery.of(context).size.width * 1.5,
// width: MediaQuery.of(context).size.width , // Ensure table is wider than screen
// width: double.infinity , // Ensure table is wider than screen
child: SingleChildScrollView(
// scrollDirection: Axis.vertical, // Inner wrapper for vertical scrolling
scrollDirection: Axis.horizontal, // Inner wrapper for vertical scrolling
child: ConstrainedBox(
constraints: BoxConstraints(minWidth: 1300),
// width: MediaQuery.of(context).size.width ,
child: Container(
// color: Colors.amber,
child: DataTable(
// columnSpacing: 50.0,
dividerThickness: 0.5, // Reduce the thickness of row dividers
columnSpacing: 50.0, // Adjust spacing between columns
dividerThickness: 0.5,
border: TableBorder(
horizontalInside: BorderSide(width: 0.5, color: Colors.grey.shade200), // Reduce horizontal line thickness
horizontalInside: BorderSide(width: 0.5, color: Colors.grey.shade200),
),
columns: const [
DataColumn(label: Text('Plan ID', style: TextStyle(color: Colors.grey, fontWeight: FontWeight.bold,))),
DataColumn(label: Text('Trip Title', style: TextStyle(color: Colors.grey, fontWeight: FontWeight.bold,))),
DataColumn(label: Text('Trip Type', style: TextStyle(color: Colors.grey, fontWeight: FontWeight.bold,))),
DataColumn(label: Text('Cost Center', style: TextStyle(color: Colors.grey, fontWeight: FontWeight.bold,))),
// DataColumn(label: Text('Functional Department', style: TextStyle(color: Colors.grey, fontWeight: FontWeight.bold,))),
// DataColumn(label: Text('Purpose Of Travel', style: TextStyle(color: Colors.grey, fontWeight: FontWeight.bold,))),
// DataColumn(label: Text('Description', style: TextStyle(color: Colors.grey, fontWeight: FontWeight.bold,))),
//
DataColumn(label: Text('Is Billable', style: TextStyle(color: Colors.grey, fontWeight: FontWeight.bold,))),
DataColumn(label: Text('Status', style: TextStyle(color: Colors.grey, fontWeight: FontWeight.bold,))),
DataColumn(label: Text('Actions', style: TextStyle(color: Colors.grey, fontWeight: FontWeight.bold,))),
DataColumn(label: Text('Plan ID', style: TextStyle(fontWeight: FontWeight.bold))),
DataColumn(label: Text('Trip Title', style: TextStyle(fontWeight: FontWeight.bold))),
DataColumn(label: Text('Trip Type', style: TextStyle(fontWeight: FontWeight.bold))),
DataColumn(label: Text('Cost Center', style: TextStyle(fontWeight: FontWeight.bold))),
DataColumn(label: Text('Is Billable', style: TextStyle(fontWeight: FontWeight.bold))),
DataColumn(label: Text('Status', style: TextStyle(fontWeight: FontWeight.bold))),
DataColumn(label: Text('Actions', style: TextStyle(fontWeight: FontWeight.bold))),
],
rows: plans.map((plan) {
rows: plans.map((plan) {
return DataRow(cells: [
DataCell(Text(plan.planId)),
// DataCell(Text(plan.tripTitle)),
DataCell(Row(
children: [
Flexible(
child: Text(
plan.tripTitle,
softWrap: true,
overflow: TextOverflow.ellipsis, // Adds "..." if text is too long
),
),
],
)),
DataCell(Text(plan.tripTitle, softWrap: true, overflow: TextOverflow.ellipsis)),
DataCell(Text(plan.tripType)),
DataCell(Text(plan.costCenter)),
// DataCell(Text(plan.functionalDepartment)),
// DataCell(Text(plan.purposeOfTravel)),
// DataCell(Text(plan.description)),
//
DataCell(Text(plan.isBillable)),
DataCell(Text(plan.status)),
DataCell(
TextButton(
Row(
children:[
IconButton(
icon: Icon(Icons.remove_red_eye, color: Colors.blue),
onPressed: () {
print("View button clicked for ${plan.tripTitle}");
viewPlan(plan.planId, isViewMode: true);
},
child: const Text('View',
style: TextStyle(color: Colors.blueAccent)),
),
IconButton(
icon: Icon(Icons.edit, color: Colors.green),
onPressed: () {
viewPlan(plan.planId, isViewMode: false);
},
),
// IconButton(
// icon: Icon(Icons.delete, color: Colors.red),
// onPressed: () {
// deletePlan(plan.planId);
// },
// ),
]
)
),
]);
}).toList(),
),
),
),
);
},
),
),
),
),
);
},
),

View File

@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:responsive_builder/responsive_builder.dart';
import 'package:shared_preferences/shared_preferences.dart';
class CustomDrawer extends StatelessWidget{
@ -24,9 +25,9 @@ class CustomDrawer extends StatelessWidget{
_buildDrawerItem(context, Icons.home,'Home', '/home'),
_buildExpandableItem(context,Icons.assessment,'Plans',[
_buildSubDrawerItem(context,'My Plans','/listPlan'),
_buildSubDrawerItem(context,'PlanB','/PlanB')
// _buildSubDrawerItem(context,'PlanB','/PlanB')
]),
_buildDrawerItem(context,Icons.login,'Login','/')
_buildDrawerItem(context,Icons.logout,'Logout','/')
],
);
@ -50,10 +51,16 @@ class CustomDrawer extends StatelessWidget{
return ListTile(
leading: Icon(icon),
title: Text(title),
onTap: (){
context.go(route);
if (!isDesktop) Navigator.pop(context);
},
onTap: () async {
if (route == '/') {
// Handle logout separately
final pref = await SharedPreferences.getInstance();
await pref.clear(); // Clear stored token or session data
context.go("/"); // Redirect to login instead of home
} else {
context.go(route);
}
}
);
}

View File

@ -24,5 +24,7 @@ final GoRouter router = GoRouter(
path: '/createPlan',
builder: (context, state) => CreatePlan(),
),
],
);