diff --git a/lib/Screens/authentication/login/login_widget.dart b/lib/Screens/authentication/login/login_widget.dart index 836bd7b..d273b85 100644 --- a/lib/Screens/authentication/login/login_widget.dart +++ b/lib/Screens/authentication/login/login_widget.dart @@ -75,7 +75,10 @@ class _LoginWidgetState extends State { 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 { diff --git a/lib/Screens/authentication/loginPage1.dart b/lib/Screens/authentication/loginPage1.dart index 093960a..f7d4ce6 100644 --- a/lib/Screens/authentication/loginPage1.dart +++ b/lib/Screens/authentication/loginPage1.dart @@ -56,7 +56,10 @@ class _LoginWidgetState extends State{ 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 } diff --git a/lib/Screens/itnerary/accomodations.dart b/lib/Screens/itnerary/accomodations.dart index c5453b9..9d24738 100644 --- a/lib/Screens/itnerary/accomodations.dart +++ b/lib/Screens/itnerary/accomodations.dart @@ -10,9 +10,11 @@ class AccomodationScreen extends StatefulWidget { final Function(bool) onClose; // Callback function final Function(Map) onSaveAccomadation; final Map? 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 { }); } + Map errorMessages = {}; Map get accomadationData { @@ -65,6 +68,8 @@ class _AccomodationScreenState extends State { "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 { data["accomodation_id"] = widget.selectedItem!["accomodation_id"]; } } - - return data; } @@ -95,8 +98,6 @@ class _AccomodationScreenState extends State { _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 { _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 { super.dispose(); } + void _clearError(String field) { + if (mounted && errorMessages.containsKey(field)) { + setState(() { + errorMessages.remove(field); + }); + } + } + bool isValidData(Map data) { + errorMessages.clear(); // Reset errors + + // Required fields that must not be empty + List 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 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 { ), ), ), - ], + 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 { ), ), ), - ], + + 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 { ), ), ), - ], + 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 { ), ), ), - ], + 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 { ), ), - ], + 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 { ), ), + if (errorMessages["checkout_time"] != null) ...[ + SizedBox(height: 5), // Space before error message + Text( + errorMessages["checkout_time"]!, + style: TextStyle(color: Colors.red, fontSize: 12), + ), + ], ], ), ]; diff --git a/lib/Screens/itnerary/bus.dart b/lib/Screens/itnerary/bus.dart index 8c12584..bd5e2b7 100644 --- a/lib/Screens/itnerary/bus.dart +++ b/lib/Screens/itnerary/bus.dart @@ -11,9 +11,10 @@ class BusScreen extends StatefulWidget { final Function(bool) onClose; final Function(Map)onSaveBus; final Map? 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 { final FocusNode _commentsFocusNode = FocusNode(); late Map _controllers; + Map errorMessages = {}; late TextEditingController _tripTypeController = TextEditingController(); late TextEditingController _hotelNameController = TextEditingController(); @@ -58,6 +60,8 @@ class _BusScreenState extends State { "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 { _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 { } + void _clearError(String field) { + if (mounted && errorMessages.containsKey(field)) { + setState(() { + errorMessages.remove(field); + }); + } + } + bool isValidData(Map data) { + errorMessages.clear(); // Reset errors + + // Required fields that must not be empty + List 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 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 { selectedPurpose = newValue; }); print("Updating form data: Flight -> trip_type -> ${newValue ?? ""}"); - - - } - : null, + } : null, items: dropdownItems, ), @@ -332,86 +369,6 @@ class _BusScreenState extends State { } - List _builClassType(bool isDesktop){ - - List purposeList = widget.apiData?['flight_class'] ?? []; - - List> dropdownItems = purposeList - .map((item)=>DropdownMenuItem( - value: item['dropdown_value'], - child: Text(item['dropdown_value']), - )).toList(); - - if (dropdownItems.isEmpty) { - dropdownItems.add( - DropdownMenuItem( - 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( - 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 _buildSecondRow(bool isDesktop) { @@ -494,6 +451,13 @@ class _BusScreenState extends State { ), ), ), + 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 { ), ), ), + 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 { ), ), + 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 { ), ), + if (errorMessages["time"] != null) ...[ + SizedBox(height: 5), // Space before error message + Text( + "Required", + style: TextStyle(color: Colors.red, fontSize: 12), + ), + ], ], ), diff --git a/lib/Screens/itnerary/flights.dart b/lib/Screens/itnerary/flights.dart index c269163..587716e 100644 --- a/lib/Screens/itnerary/flights.dart +++ b/lib/Screens/itnerary/flights.dart @@ -7,17 +7,20 @@ import '../../widgets/custom_text_field.dart'; import '../../widgets/custom_text_itnerary_sub.dart'; class FlightScreen extends StatefulWidget { - final Map formData; + final Map? apiData; - final Function(String tab, String key, String value) updateFormData; + final String? loginUser; final Function(bool) onClose; + final Function(Map) onSaveFlight; + final Map? 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 { Map selectedValues = {}; String? selectedTripType; + Map selectedClasses = {}; // Store class selection for each trip String? selectedvisa_available; int multiTripRowCount = 1; @@ -37,33 +41,33 @@ class _FlightScreenState extends State { Map focusStates = {}; Map textControllers = {}; + Map fieldErrors = {}; // Holds error messages List> rowBuilders = []; // Holds row widgets List controllers = []; // Dynamic controllers - late Map _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 errorMessages = {}; @override void initState() { super.initState(); // _initializeRows(); + // List 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 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 { 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 { }); } + 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 controllers = [ - _tripTypeController, _hotelNameController, _fromController, _toController, _dateController, - _timeController, _commentsController - ]; - - List 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 { 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 { } - - - // 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 { }); } + 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 { + Map get flightsData{ + + List> 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 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 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; + 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 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 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 { // 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 { // 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 { 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 { ), ), ), - 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 { List> dropdownItems = purposeList .map((item)=>DropdownMenuItem( - value: item['dropdown_value'], + value: item['dropdown_key'], child: Text(item['dropdown_value']), )).toList(); @@ -583,7 +736,7 @@ class _FlightScreenState extends State { } // 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 { 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 { 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 { onChanged: purposeList.isNotEmpty ? (newValue) { setState(() { - selectedPurpose = newValue; + selectedClasses[index] = newValue; }); - print(selectedPurpose); + print( selectedClasses[index] ); } : null, @@ -739,6 +895,18 @@ class _FlightScreenState extends State { ), ), ), + + + + 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 { ), ), ), + 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 { ), ), + + 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 { ), ), + 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 { List> dropdownItems = visa_available .map((item)=>DropdownMenuItem( - 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 { }); 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 { // 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 { // Save Changes Button ElevatedButton( onPressed: () { - // TODO: Implement save logic + handleSave(); }, style: ElevatedButton.styleFrom( backgroundColor: Colors.blue, // Primary color for save diff --git a/lib/Screens/itnerary/forex.dart b/lib/Screens/itnerary/forex.dart index 6fec1e6..78b5632 100644 --- a/lib/Screens/itnerary/forex.dart +++ b/lib/Screens/itnerary/forex.dart @@ -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? selectedItem; final List? apiCountryData; final Function(Map)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 { Map selectedValues = {}; bool isChecked = false; // State variable for checkbox + int fifteenPercent = 0; + int remainingAmount = 0; Map focusNodes = {}; Map focusStates = {}; @@ -56,18 +61,20 @@ class _ForexScreenState extends State { return date; // Return as is if parsing fails } } + Map errorMessages = {}; String? selectedCountry; String? selectedCurrency; String? selectedDuration; String? selectedPerdiemAmount; String? CalculatedOtherExpenses; + String? selectedQuotedAmount; Map get forexData { Map 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 { "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 { 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 { } } + bool isValidForexData(Map data) { + errorMessages.clear(); // Reset errors + + // Required fields that must not be empty + List 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 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 { 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 { 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 { } + 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 { 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 { ), ), + 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 { 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 { ), ), + 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 { ), ), ), - + 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 { 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 { ), ], ), - 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 { ], ), - 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 { ), 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 { 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 { 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 { 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 { 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 { ), ), ), + 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 { 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 { ), ), ), + 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 { ), ), ), + 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 { ), ), ), + 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 { // 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 diff --git a/lib/Screens/itnerary/insurance.dart b/lib/Screens/itnerary/insurance.dart index 8f663b0..20aa8dc 100644 --- a/lib/Screens/itnerary/insurance.dart +++ b/lib/Screens/itnerary/insurance.dart @@ -11,10 +11,12 @@ class InsuranceScreen extends StatefulWidget { final Function(bool) onClose; final Function(Map) onSaveInsurance; final Map? 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 { String? selectedTripType; String? selectedInsuranceType; + Map errorMessages = {}; + Map get InsuranceData{ Map data = { @@ -52,6 +56,8 @@ class _InsuranceScreenState extends State { "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 { + + + bool isValidData(Map data) { + errorMessages.clear(); // Reset errors + + // Required fields that must not be empty + List 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 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 { ? (newValue) { setState(() { selectedInsuranceType = newValue; + if (selectedInsuranceType!.isNotEmpty) { + errorMessages.remove("type_of_insurance"); + } + }); print(selectedInsuranceType); @@ -375,7 +421,14 @@ class _InsuranceScreenState extends State { 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 { ), ), - ], + + + 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 { 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 { ), ), + 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) diff --git a/lib/Screens/itnerary/miscellaneous.dart b/lib/Screens/itnerary/miscellaneous.dart index dd38027..0d13e8d 100644 --- a/lib/Screens/itnerary/miscellaneous.dart +++ b/lib/Screens/itnerary/miscellaneous.dart @@ -12,10 +12,11 @@ class MiscellaneousScreen extends StatefulWidget { final Function(Map) onSaveMiscellaneous; final Map? 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 { bool _isHotelNameFocused = false; bool _commentsFocus = false; - - String? selectedSpecialType; + Map errorMessages = {}; + Map get miscellaneousData { Map 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 { }); }); - - _commentsController = TextEditingController(text: widget.selectedItem?["comments"] ?? ""); @@ -103,15 +106,46 @@ class _MiscellaneousScreenState extends State { } + + + bool isValidData(Map data) { + errorMessages.clear(); // Reset errors + + // Required fields that must not be empty + List 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 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 { ), ), + 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 { ), ), ), + if (errorMessages["comments"] != null) ...[ + SizedBox(height: 5), // Space before error message + Text( + "Required", + style: TextStyle(color: Colors.red, fontSize: 12), + ), + ], ], ) ]; diff --git a/lib/Screens/itnerary/taxi.dart b/lib/Screens/itnerary/taxi.dart index 854b4f3..326826c 100644 --- a/lib/Screens/itnerary/taxi.dart +++ b/lib/Screens/itnerary/taxi.dart @@ -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) onSavetaxi; final Map? 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 { final GlobalKey _formKey = GlobalKey(); Map selectedValues = {}; + Map errorMessages = {}; final FocusNode _destinationFocusNode = FocusNode(); final FocusNode _locationFocusNode = FocusNode(); @@ -64,6 +68,8 @@ class _TaxiScreenState extends State { "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 { 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 { + + void _clearError(String field) { + if (mounted && errorMessages.containsKey(field)) { + setState(() { + errorMessages.remove(field); + }); + } + } + + + + + + + + bool isValidData(Map data) { + errorMessages.clear(); // Reset errors + + // Required fields that must not be empty + List 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 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 { 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 { ), ), ), + 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 { ), ), ), - ], + 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 { ), ), ), + 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 { ), ), - ], + 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 { ), ), - ], + if (errorMessages["time"] != null) ...[ + SizedBox(height: 5), // Space before error message + Text( + "Required", + style: TextStyle(color: Colors.red, fontSize: 12), + ), + ], + ], ), ]; } diff --git a/lib/Screens/itnerary/train.dart b/lib/Screens/itnerary/train.dart index 614aaf1..a9331d5 100644 --- a/lib/Screens/itnerary/train.dart +++ b/lib/Screens/itnerary/train.dart @@ -11,9 +11,10 @@ class TrainScreen extends StatefulWidget { final Function(Map)onSavetrain; final Function(bool) onClose; final Map? 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 { String? selectedClass; - + Map errorMessages = {}; Map get trainData { Map 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 { _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 { 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 { } + void _clearError(String field) { + if (mounted && errorMessages.containsKey(field)) { + setState(() { + errorMessages.remove(field); + }); + } + } + + + bool isValidData(Map data) { + errorMessages.clear(); // Reset errors + + // Required fields that must not be empty + List 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 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 { ) : 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 { ), ), - ], + 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 { ), ), ), - ], + 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 { ), ), ), - ], + 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 { ), ), - ], + 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 { ), ), + if (errorMessages["time"] != null) ...[ + SizedBox(height: 5), // Space before error message + Text( + "Required", + style: TextStyle(color: Colors.red, fontSize: 12), + ), + ], ], ), diff --git a/lib/Screens/itnerary/visa.dart b/lib/Screens/itnerary/visa.dart index d6a3676..a7fe3d2 100644 --- a/lib/Screens/itnerary/visa.dart +++ b/lib/Screens/itnerary/visa.dart @@ -15,9 +15,12 @@ class VisaScreen extends StatefulWidget { final Function(bool) onClose; final Function(Map) onSaveVisa; final Map? 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 { String? selectedPurpose; String? selectedCountry; + Map errorMessages = {}; + + Map get visaData{ Map 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 { 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 { } + bool isValidData(Map data) { + errorMessages.clear(); // Reset errors + + // Required fields that must not be empty + List 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 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 { ) : 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 { // List countryNames = countryList.map((item) => item['country_name'] as String).toList(); // + + late Map countryMap; // Mapping country_code -> country_name late List countryCodes; // List of country codes @@ -350,10 +393,7 @@ class _VisaScreenState extends State { // 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 { 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( - // 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 { 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 { ), ), + if (errorMessages["start_date"] != null) ...[ + SizedBox(height: 5), // Space before error message + Text( + "Required", + style: TextStyle(color: Colors.red, fontSize: 12), + ), + ], ], ), diff --git a/lib/Screens/itnerary_list/accomodation_list.dart b/lib/Screens/itnerary_list/accomodation_list.dart index 4673152..4bcb8b0 100644 --- a/lib/Screens/itnerary_list/accomodation_list.dart +++ b/lib/Screens/itnerary_list/accomodation_list.dart @@ -49,8 +49,12 @@ class AccomodationListWidget extends StatelessWidget { List _buildDataRows() { + List> 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 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( diff --git a/lib/Screens/itnerary_list/bus_list.dart b/lib/Screens/itnerary_list/bus_list.dart index f7c80ac..58f16f6 100644 --- a/lib/Screens/itnerary_list/bus_list.dart +++ b/lib/Screens/itnerary_list/bus_list.dart @@ -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 _buildDataRows() { + List> 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 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"]!)), diff --git a/lib/Screens/itnerary_list/flight_list.dart b/lib/Screens/itnerary_list/flight_list.dart index d775ea1..0797a16 100644 --- a/lib/Screens/itnerary_list/flight_list.dart +++ b/lib/Screens/itnerary_list/flight_list.dart @@ -1,7 +1,11 @@ import 'package:flutter/material.dart'; class FlightListWidget extends StatelessWidget { - const FlightListWidget({super.key}); + final List> flightList; + final Function( bool,Map, String) onOpen; + final Function(Map) 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 _buildDataRows() { - List> data = [ - {"tripType": "One-Way", "class": "Economy", "from": "NYC", "to": "LA"}, - {"tripType": "Round-Trip", "class": "Business", "from": "SF", "to": "Seattle"}, - ]; - return data.map((bus) { + List> filteredList = flightList + .where((item) => item["is_active"] == "1") + .toList(); + print("filteredList- $filteredList"); + + + return filteredList.asMap().entries.map((entry) { + Map 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); }, ), ], diff --git a/lib/Screens/itnerary_list/forex_list.dart b/lib/Screens/itnerary_list/forex_list.dart index 3860d7b..cd7d0d4 100644 --- a/lib/Screens/itnerary_list/forex_list.dart +++ b/lib/Screens/itnerary_list/forex_list.dart @@ -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 _buildDataRows() { + List> 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 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")), diff --git a/lib/Screens/itnerary_list/insurance_list.dart b/lib/Screens/itnerary_list/insurance_list.dart index 497d0b0..5e01369 100644 --- a/lib/Screens/itnerary_list/insurance_list.dart +++ b/lib/Screens/itnerary_list/insurance_list.dart @@ -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 _buildDataRows() { + List> 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 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"]!)), diff --git a/lib/Screens/itnerary_list/miscellaneous_list.dart b/lib/Screens/itnerary_list/miscellaneous_list.dart index 1411ce7..5f64ac4 100644 --- a/lib/Screens/itnerary_list/miscellaneous_list.dart +++ b/lib/Screens/itnerary_list/miscellaneous_list.dart @@ -5,9 +5,10 @@ class MiscellaneousListWidget extends StatelessWidget { final List> miscellaneousList; final Function(bool, Map, String) onOpen; final Function(Map) onDeleteMiscellaneous; + final Map? 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 _buildDataRows() { print("miscellaneousList - $miscellaneousList"); + List 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> 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 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( diff --git a/lib/Screens/itnerary_list/taxi_list.dart b/lib/Screens/itnerary_list/taxi_list.dart index fe51c09..d9e9bd0 100644 --- a/lib/Screens/itnerary_list/taxi_list.dart +++ b/lib/Screens/itnerary_list/taxi_list.dart @@ -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 _buildDataRows() { + List> 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 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"]!)), diff --git a/lib/Screens/itnerary_list/train_list.dart b/lib/Screens/itnerary_list/train_list.dart index b9f7d57..3c71d00 100644 --- a/lib/Screens/itnerary_list/train_list.dart +++ b/lib/Screens/itnerary_list/train_list.dart @@ -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 _buildDataRows() { - return trainList.asMap().entries.map((entry) { + List> filteredList = trainList + .where((item) => item["is_active"] == "1") + .toList(); + print("filteredList- $filteredList"); + + + return filteredList.asMap().entries.map((entry) { final Map 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( diff --git a/lib/Screens/itnerary_list/visa_list.dart b/lib/Screens/itnerary_list/visa_list.dart index 213608b..4d3cd31 100644 --- a/lib/Screens/itnerary_list/visa_list.dart +++ b/lib/Screens/itnerary_list/visa_list.dart @@ -4,8 +4,11 @@ class VisaListWidget extends StatelessWidget { final List> visaList; final Function(bool, Map, String) onOpen; final Function(Map) onDeleteMiscellaneous; + final Map? apiData; + final List? 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 _buildDataRows() { + List> filteredList = visaList + .where((item) => item["is_active"] == "1") + .toList(); + print("filteredList- $filteredList"); - return visaList.asMap().entries.map((entry){ + List 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 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: [ diff --git a/lib/Screens/plans/create_plans.dart b/lib/Screens/plans/create_plans.dart index 4985b35..2625057 100644 --- a/lib/Screens/plans/create_plans.dart +++ b/lib/Screens/plans/create_plans.dart @@ -28,6 +28,23 @@ class _CreatePlansState extends State { return ResponsiveBuilder(builder: (context, sizingInfo) { bool isDesktop = sizingInfo.deviceScreenType == DeviceScreenType.desktop; + + final args = GoRouterState.of(context).extra as Map? ?? {}; + // final planData = args?['planData']; + final bool isViewMode = args?['isViewMode'] ?? false; + + final Map planData = args['planData'] as Map? ?? {}; + + + // print("isViewMode: $isViewMode"); + + // final bool isViewMode = true; + // final planData = GoRouterState.of(context).extra as Map? ?? {}; + + + print("RECived palndata"); + // print("RECived palndata - ${planData}"); + return Scaffold( backgroundColor: Colors.white, body: Column( @@ -47,7 +64,10 @@ class _CreatePlansState extends State { ), ), Text( - "New Plan", + isViewMode + ? "View Plan" + : (planData.isNotEmpty ? "Update Plan" : "New Plan"), + style: TextStyle(fontSize: 18), ), ], @@ -70,7 +90,7 @@ class _CreatePlansState extends State { 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 { class CreateNewPlan extends StatefulWidget { final bool isDesktop; - const CreateNewPlan({super.key, required this.isDesktop}); + final bool isViewMode; + final Map 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 { late String _selectedOption = "Option 1"; // late String? _selectedIsBillable = "Billable"; + String? selectedPlanId; String? userDetails; String? userName; @@ -111,6 +134,8 @@ class _CreateNewPlansState extends State { String? selectedplanUserId; bool? selectedIstravelUser; + + Map? apiData; // Store API response here List? apiCountryData; List? apiCostData; // Store API response here @@ -127,9 +152,14 @@ class _CreateNewPlansState extends State { Map validationErrors = {}; List> miscellaneousList = []; // List> miscellaneousList = [{"special_request": 1, "comments": "posta", "indx": 1}]; + List> visaList = []; + List> insuranceList = []; + List> accommodationList = []; List> trainList = []; List> busList = []; List> taxiList = []; + List> forexList = []; + List> flightList = []; //Getter Method Map get planData => { @@ -144,30 +174,29 @@ class _CreateNewPlansState extends State { "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> newMiscellaneousList) { - setState(() { - miscellaneousList = newMiscellaneousList; // Update miscellaneous data - }); - print("Updated Miscellaneous Data in CreateNewPlan: $miscellaneousList"); - } + // // Function to update miscellaneous list + // void updateMiscellaneousData(List> newMiscellaneousList) { + // setState(() { + // miscellaneousList = newMiscellaneousList; // Update miscellaneous data + // }); + // print("Updated Miscellaneous Data in CreateNewPlan: $miscellaneousList"); + // } void handleItineraryUpdate(String type, List> newList) { @@ -176,7 +205,13 @@ class _CreateNewPlansState extends State { 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 { 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 { super.initState(); fetchUserDetails(); + fetchPlans(); fetchCostCenter(); fetchCountryList(); @@ -223,6 +262,8 @@ class _CreateNewPlansState extends State { }); }); + handleUpdateData(); + } @override @@ -233,6 +274,68 @@ class _CreateNewPlansState extends State { } + + 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>.from(widget.selectedPlanData['flight'] ?? []); + accommodationList = List>.from(widget.selectedPlanData['accomodation'] ?? []); + busList = List>.from(widget.selectedPlanData['bus'] ?? []); + taxiList = List>.from(widget.selectedPlanData['taxi'] ?? []); + trainList = List>.from(widget.selectedPlanData['train'] ?? []); + visaList = List>.from(widget.selectedPlanData['visa'] ?? []); + forexList = List>.from(widget.selectedPlanData['forex'] ?? []); + insuranceList = List>.from(widget.selectedPlanData['insurance'] ?? []); + miscellaneousList = List>.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 { List 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 { if(validateForm()){ print("Form submitted successfully: $planData"); postPlanData(planData); + context.go('/listPlan'); } }); } - Future postPlanData(Map planData) async { - final String apiUrldata = '$apiUrl/api/plans/createOrEditPlan'; - final token = await getToken(); // Fetch token + Future postPlanData(Map 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 { 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 { 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 { 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 { return Row( children: [ Radio( - 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 { 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 { 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 { 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 { 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 { List> dropdownItems = purposeList .map((item)=>DropdownMenuItem( - 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( - 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 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 funcDeptList = apiData?['plan_functional_department'] ?? []; List> dropdownFuncDeptItems = funcDeptList .map((item)=>DropdownMenuItem( - 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( - 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 { isDesktop: widget.isDesktop, child: SizedBox( height: 45, // Set appropriate height - child: DropdownButtonFormField( + child: apiData == null + ? Center(child: CircularProgressIndicator()) // Show loading inside dropdown + : + DropdownButtonFormField( value: selectedPurpose, style: TextStyle(fontSize: 12), decoration: InputDecoration( @@ -1029,11 +1171,12 @@ class _CreateNewPlansState extends State { 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 { isDesktop: widget.isDesktop, child: SizedBox( height: 45, // Set appropriate height - child: DropdownButtonFormField( + child: + apiData == null + ? Center(child: CircularProgressIndicator()) // Show loading inside dropdown + :DropdownButtonFormField( value: selectedFuncDept, style: TextStyle(fontSize: 12), decoration: InputDecoration( @@ -1073,7 +1219,7 @@ class _CreateNewPlansState extends State { 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 { 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 { List _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"), + ), + ) + + ]; } diff --git a/lib/Screens/plans/dynamic_itinerary_stepper.dart b/lib/Screens/plans/dynamic_itinerary_stepper.dart index 65b556f..a594878 100644 --- a/lib/Screens/plans/dynamic_itinerary_stepper.dart +++ b/lib/Screens/plans/dynamic_itinerary_stepper.dart @@ -24,8 +24,13 @@ import '../itnerary_list/visa_list.dart'; class DynamicItinerary extends StatefulWidget { final Map? apiData; final List? apiCountryData; + final String? loginUser; final Function(String, List>) onItineraryUpdate; // Updated Signature - const DynamicItinerary({super.key, required this.apiData, required this.onItineraryUpdate, required this.apiCountryData}); + final Map 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 { // List> miscellaneousList = []; Map>> 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 { "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>.from(widget.selectedPlanData['train'] ?? []), + "Bus": List>.from(widget.selectedPlanData['bus'] ?? []), + "Taxi": List>.from(widget.selectedPlanData['taxi'] ?? []), + "Miscellaneous": List>.from(widget.selectedPlanData['miscellaneous'] ?? []), + "Flight": List>.from(widget.selectedPlanData['flight'] ?? []), + "Accomodation": List>.from(widget.selectedPlanData['accomodation'] ?? []), + "Insurance": List>.from(widget.selectedPlanData['insurance'] ?? []), + "Visa": List>.from(widget.selectedPlanData['visa'] ?? []), + "Forex": List>.from(widget.selectedPlanData['forex'] ?? []), + }; + }); + } + else { + print("No itinerary data available"); + } + } + + bool hasAnyItineraryData() { + List 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 { 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 { 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 data){ setState(() { - if(!itineraryData.containsKey(type)){ - return; + // if(!itineraryData.containsKey(type)){ + // return; + // } + + + if (!itineraryData.containsKey(type)) { + itineraryData[type] = []; // Initialize if null } List> 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 { 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 { 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 { 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 { 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 { SizedBox(width: 5), SizedBox(width: 5), - if (selectedListOption == title) + if (selectedListOption == title && widget.isViewMode == false) GestureDetector( onTap: () { setState(() { diff --git a/lib/Screens/plans/list_plans.dart b/lib/Screens/plans/list_plans.dart index a623ac4..078ede5 100644 --- a/lib/Screens/plans/list_plans.dart +++ b/lib/Screens/plans/list_plans.dart @@ -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{ } + Future > 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? 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 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{ List 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(), ), ), - - ), - ); - - }, + ), + ), + ), + ), ); + + + + }, ), diff --git a/lib/routes/custom_drawer.dart b/lib/routes/custom_drawer.dart index 22e8a23..98fb8d4 100644 --- a/lib/routes/custom_drawer.dart +++ b/lib/routes/custom_drawer.dart @@ -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); + } + } ); } diff --git a/lib/routes/custom_router.dart b/lib/routes/custom_router.dart index 9c21741..0500a69 100644 --- a/lib/routes/custom_router.dart +++ b/lib/routes/custom_router.dart @@ -24,5 +24,7 @@ final GoRouter router = GoRouter( path: '/createPlan', builder: (context, state) => CreatePlan(), ), + + ], ); \ No newline at end of file