Exceptional Classes

This commit is contained in:
venbaittech 2025-06-21 19:17:07 +05:30
parent e33af3cd8a
commit 53fd28d65d
18 changed files with 819 additions and 311 deletions

View File

@ -817,7 +817,7 @@ class _TravelAgentListPlansState extends State<TravelAgentListPlans> {
Navigator.pop(
context,
); // Close popup manually
ApiService.viewPlan(
ApiService.viewPlanTravelAgent(
context,
plan.planId,
isViewMode:

View File

@ -1,16 +1,20 @@
import 'package:dropdown_search/dropdown_search.dart';
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:intl/intl.dart';
import 'package:responsive_builder/responsive_builder.dart';
import '../../services/apiService.dart';
import '../../widgets/custom_confirmation_dialog.dart';
import '../../widgets/custom_text_field.dart';
import '../../widgets/custom_text_itnerary_sub.dart';
class AccomodationScreen extends StatefulWidget {
final List<Map<String, dynamic>> flightData;
final Function(bool) onClose; // Callback function
final Map<String, dynamic>? apiDataForClass;
final Function(String, bool) onClose; // Callback function
final Function(Map<String, dynamic>) onSaveAccomadation;
final void Function(bool)? onExceptionalClass;
final Map<String, dynamic>? selectedItem;
final String? loginUser;
final String? tripType;
@ -22,6 +26,8 @@ class AccomodationScreen extends StatefulWidget {
required this.loginUser,
required this.flightData,
this.tripType,
this.apiDataForClass,
this.onExceptionalClass,
});
@override
@ -34,6 +40,10 @@ class _AccomodationScreenState extends State<AccomodationScreen> {
// late Map<String, String> countryMap;
Map<String, String> countryMap = {};
String? selectedClass;
String? exceptionalClass = "0";
bool hasExceptionalClass = false;
late ValueNotifier<String?> flightFirstTripDateNotifier;
late ValueNotifier<String?> flightLastTripDateNotifier;
late ValueNotifier<String?> flightFirstToDestinationNotifier;
@ -42,6 +52,7 @@ class _AccomodationScreenState extends State<AccomodationScreen> {
final FocusNode _destinationFocusNode = FocusNode();
final FocusNode _hotelNameFocusNode = FocusNode();
final FocusNode _CategoryFocusNode = FocusNode();
final FocusNode _checkInFocusNode = FocusNode();
final FocusNode _checkInTimeFocusNode = FocusNode();
final FocusNode _checkOutFocusNode = FocusNode();
@ -58,6 +69,7 @@ class _AccomodationScreenState extends State<AccomodationScreen> {
bool _destinationFocused = false;
bool _isHotelNameFocused = false;
bool _CategoryFocused = false;
bool _checkInFocus = false;
bool _checkInTimeFocus = false;
bool _checkOutFocus = false;
@ -78,11 +90,13 @@ class _AccomodationScreenState extends State<AccomodationScreen> {
Map<String, dynamic> data = {
"destination_city": _destinationController.text,
"hotel_name": _hotelNameController.text,
"class": selectedClass,
"checkin_date": _checkInController.text,
"checkin_time": _checkInTimeController.text,
"checkout_date": _checkOutController.text,
"checkout_time": _checkOutTimeController.text,
"comments": _commentsController.text,
"is_this_exceptional": exceptionalClass,
"created_by": widget.loginUser,
"updated_by": widget.loginUser,
};
@ -191,6 +205,16 @@ class _AccomodationScreenState extends State<AccomodationScreen> {
_checkOutTimeController.text = '12:00'; // 12 PM
}
});
if (widget.selectedItem != null &&
widget.selectedItem!["is_this_exceptional"] != null) {
exceptionalClass = widget.selectedItem!["is_this_exceptional"].toString();
}
// Set the selected value if available
if (widget.selectedItem != null && widget.selectedItem!["class"] != null) {
selectedClass = widget.selectedItem!["class"].toString();
}
}
@override
@ -365,9 +389,13 @@ class _AccomodationScreenState extends State<AccomodationScreen> {
return; // Stop execution if validation fails
} else {
widget.onSaveAccomadation(accomadationData);
// if (hasExceptionalClass) {
// widget.onExceptionalClass?.call(true); // or false
// }
}
widget.onClose(false); // Close screen after saving
widget.onClose("Accomodation", false); // Close screen after saving
}
@override
@ -421,6 +449,34 @@ class _AccomodationScreenState extends State<AccomodationScreen> {
}
List<Widget> _buildFirstRow(isDesktop) {
List<dynamic> purposeList = widget.apiDataForClass?['hotel_class'] ?? [];
List<DropdownMenuItem<String>> dropdownItems =
purposeList
.map(
(item) => DropdownMenuItem<String>(
value: item['dropdown_key'],
child: Text(item['dropdown_value']),
),
)
.toList();
if (dropdownItems.isEmpty) {
dropdownItems.add(
DropdownMenuItem<String>(
value: null,
child: Text(
"No options available",
style: TextStyle(color: Colors.grey),
),
),
);
}
// Default selected value
// selectedClass ??=
// dropdownItems.isNotEmpty ? dropdownItems.first.value : null;
return [
// Column(
// children: [
@ -493,13 +549,13 @@ class _AccomodationScreenState extends State<AccomodationScreen> {
),
),
SizedBox(height: 5),
CustomTextFieldWrapper(
CustomTextFieldItnerarySubWrapper(
isFocused: _isHotelNameFocused,
isDesktop: isDesktop,
width:
isDesktop
? MediaQuery.of(context).size.width * 0.34
: MediaQuery.of(context).size.width * 0.66,
// width:
// isDesktop
// ? MediaQuery.of(context).size.width * 0.34
// : MediaQuery.of(context).size.width * 0.66,
child: SizedBox(
height: 40,
child: TextField(
@ -525,6 +581,147 @@ class _AccomodationScreenState extends State<AccomodationScreen> {
],
],
),
if (isDesktop) Spacer() else SizedBox(height: 8),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Category *",
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74),
),
),
SizedBox(height: 5),
CustomTextFieldItnerarySubWrapper(
// isFocused: focusStates["_class${index}Focused"] ?? false,
isFocused: false,
padding: const EdgeInsets.symmetric(horizontal: 0),
isDesktop: isDesktop,
child: SizedBox(
height: 40,
width: double.infinity,
child: DropdownSearch<Map<String, dynamic>>(
key:
selectedClass == null
? UniqueKey()
: ValueKey(selectedClass),
items: purposeList.cast<Map<String, dynamic>>(),
// selectedItem: purposeList.firstWhere(
// (item) => item['dropdown_key'] == selectedClass,
// orElse: () => {},
// ),
selectedItem:
selectedClass != null
? purposeList.firstWhere(
(item) => item['dropdown_key'] == selectedClass,
orElse: () => {},
)
: null,
itemAsString: (item) => item['dropdown_value'] ?? '',
popupProps: PopupProps.menu(
showSearchBox: false,
fit: FlexFit.loose,
menuProps: const MenuProps(backgroundColor: Colors.white),
itemBuilder: (context, item, isSelected) {
print("categotey - $item ");
final bool isNotAllowed = item['is_allowed'] == 'No';
return Padding(
padding: const EdgeInsets.symmetric(
horizontal: 10,
vertical: 5,
),
child: Text(
item['dropdown_value'] ?? '',
style: GoogleFonts.poppins(
fontSize: 12,
color: isNotAllowed ? Colors.redAccent : Colors.black,
),
),
);
},
),
dropdownDecoratorProps: const DropDownDecoratorProps(
dropdownSearchDecoration: InputDecoration(
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(
horizontal: 10,
vertical: 10,
),
),
),
dropdownBuilder: (context, selectedItem) {
if (selectedItem == null || selectedItem.isEmpty) {
return Text(
"Select ",
style: GoogleFonts.poppins(
color: Colors.grey,
fontSize: 13,
),
);
}
return Text(
selectedItem['dropdown_value'] ?? '',
style: GoogleFonts.poppins(
fontSize: 12,
// color: Colors.red,
color: Colors.black,
),
);
},
onChanged:
purposeList.isNotEmpty
? (Map<String, dynamic>? newValue) async {
if (newValue != null &&
newValue['is_allowed'] == 'No') {
final confirm = await showNotAllowedDialog(
context,
message:
"You are not entitled for this service. If you wish to continue with this service, you may require another approval.",
);
if (confirm) {
setState(() {
exceptionalClass = "1";
selectedClass = newValue['dropdown_key'];
});
print("Selected Purpose: ${selectedClass}");
} else {
setState(() {
exceptionalClass = "0";
selectedClass =
null; // Clear selection if user cancels
});
print(
"selectedItem resolved to: ${selectedClass}",
);
}
return;
} else if (newValue != null &&
newValue['is_allowed'] == 'yes') {
setState(() {
exceptionalClass = "0";
selectedClass = newValue?['dropdown_key'];
print("Selected Purpose: ${selectedClass}");
});
}
}
: null,
),
),
),
if (errorMessages["class"] != null) ...[
SizedBox(height: 5), // Space before error message
Text("Required", style: TextStyle(color: Colors.red, fontSize: 12)),
],
],
),
// if (isDesktop) Spacer() else SizedBox(height: 8),
];
}
@ -1025,7 +1222,8 @@ class _AccomodationScreenState extends State<AccomodationScreen> {
// Close Button
ElevatedButton(
onPressed: () {
widget.onClose(false); // Close the dialog or screen
widget.onClose("Accomodation", false);
// widget.onClose(false); // Close the dialog or screen
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.grey[400], // Light grey color

View File

@ -8,7 +8,7 @@ import '../../widgets/custom_text_itnerary_sub.dart';
class BusScreen extends StatefulWidget {
final Map<String, dynamic>? apiData;
final Function(bool) onClose;
final Function(String, bool) onClose;
final Function(Map<String, dynamic>) onSaveBus;
final Map<String, dynamic>? selectedItem;
final String? loginUser;
@ -190,8 +190,8 @@ class _BusScreenState extends State<BusScreen> {
} else {
widget.onSaveBus(busData);
}
widget.onClose(false); // Close screen after saving
widget.onClose("Bus", false);
// widget.onClose(false); // Close screen after saving
}
@override
@ -678,7 +678,8 @@ class _BusScreenState extends State<BusScreen> {
// Close Button
ElevatedButton(
onPressed: () {
widget.onClose(false); // Close the dialog or screen
widget.onClose("Bus", false);
// widget.onClose(false); // Close the dialog or screen
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.grey[400], // Light grey color

View File

@ -6,6 +6,7 @@ import 'package:intl/intl.dart';
import 'package:responsive_builder/responsive_builder.dart';
import '../../utils/auth_utils.dart';
import '../../widgets/custom_confirmation_dialog.dart';
import '../../widgets/custom_radio_button.dart';
import '../../widgets/custom_text_field.dart';
import '../../widgets/custom_text_itnerary_sub.dart';
@ -17,7 +18,7 @@ class FlightScreen extends StatefulWidget {
final Map<String, dynamic>? apiData;
final Map<String, dynamic>? apiDataForClass;
final String? loginUser;
final Function(bool) onClose;
final Function(String, bool) onClose;
final Function(Map<String, dynamic>) onSaveFlight;
final Map<String, dynamic>? selectedItem;
final ValueNotifier<String?> tripTypeNotifier;
@ -60,6 +61,8 @@ class FlightScreenState extends State<FlightScreen> {
String? selectedTripType;
Map<int, String?> selectedClasses = {}; // Store class selection for each trip
Map<int, String?> exceptionalClass =
{}; // Store class selection for each trip
Map<int, String?> selectedFrom = {}; // Store class selection for each trip
Map<int, String?> selectedTo = {}; // Store class selection for each trip
String? selectedvisa_available;
@ -409,6 +412,7 @@ class FlightScreenState extends State<FlightScreen> {
for (int i = 1; i <= rowCount; i++) {
final trip = {
"class": selectedClasses[i],
"is_this_exceptional": exceptionalClass[i],
// "from_place": countryMap[selectedFrom[i]],
"from_place": selectedFrom[i],
"to_place": selectedTo[i],
@ -505,6 +509,7 @@ class FlightScreenState extends State<FlightScreen> {
int index = i + 1; // Use 1-based indexing to match the form
selectedClasses[index] = trip["class"].toString();
exceptionalClass[index] = trip["is_this_exceptional"].toString();
selectedFrom[index] = trip["from_place"].toString();
selectedTo[index] = trip["to_place"].toString();
// textControllers["_from${index}Controller"] =
@ -545,7 +550,8 @@ class FlightScreenState extends State<FlightScreen> {
}
widget.onSaveFlight(flightsData); // Send object to parent
widget.onClose(false);
// widget.onClose(false);
widget.onClose("Flight", false);
print("FlightData - $flightsData");
}
@ -789,6 +795,16 @@ class FlightScreenState extends State<FlightScreen> {
selectedClasses = updatedClasses; // Update the map
Map<int, String?> updatedExptionalClasses = {};
newIndex = 1;
for (int i = 1; i <= exceptionalClass.length; i++) {
if (i == index) continue; // Skip the one being deleted
updatedExptionalClasses[newIndex] = exceptionalClass[i];
newIndex++;
}
exceptionalClass = updatedExptionalClasses;
// Shift the selectedFrom map BEFORE removing the index
Map<int, String?> updatedFrom = {};
newIndex = 1;
@ -1320,8 +1336,8 @@ class FlightScreenState extends State<FlightScreen> {
}
// Default selected value
selectedClasses[index] ??=
dropdownItems.isNotEmpty ? dropdownItems.first.value : null;
// selectedClasses[index] ??=
// dropdownItems.isNotEmpty ? dropdownItems.first.value : null;
// -------------------------------------------------
DateTime? _selectedCheckOutDate;
@ -2046,16 +2062,30 @@ class FlightScreenState extends State<FlightScreen> {
},
child: DropdownSearch<Map<String, dynamic>>(
items: purposeList.cast<Map<String, dynamic>>(),
selectedItem: purposeList.firstWhere(
(item) => item['dropdown_key'] == selectedClasses[index],
orElse: () => {},
),
key:
selectedClasses[index] == null
? UniqueKey()
: ValueKey(selectedClasses[index]),
// selectedItem: purposeList.firstWhere(
// (item) => item['dropdown_key'] == selectedClasses[index],
// orElse: () => {},
// ),
selectedItem:
selectedClasses[index] != null
? purposeList.firstWhere(
(item) =>
item['dropdown_key'] ==
selectedClasses[index],
orElse: () => {},
)
: null,
itemAsString: (item) => item['dropdown_value'] ?? '',
popupProps: PopupProps.menu(
showSearchBox: false,
fit: FlexFit.loose,
menuProps: const MenuProps(backgroundColor: Colors.white),
itemBuilder: (context, item, isSelected) {
final bool isNotAllowed = item['is_allowed'] == 'No';
return Padding(
padding: const EdgeInsets.symmetric(
horizontal: 10,
@ -2065,7 +2095,10 @@ class FlightScreenState extends State<FlightScreen> {
item['dropdown_value'] ?? '',
style: GoogleFonts.poppins(
fontSize: 12,
color: Colors.black,
color:
isNotAllowed
? Colors.redAccent
: Colors.black,
),
),
);
@ -2124,14 +2157,67 @@ class FlightScreenState extends State<FlightScreen> {
},
onChanged:
purposeList.isNotEmpty
? (Map<String, dynamic>? newValue) {
setState(() {
selectedClasses[index] =
newValue?['dropdown_key'];
print(
"Selected Class: ${selectedClasses[index]}",
? (Map<String, dynamic>? newValue) async {
// if (newValue != null &&
// newValue['is_allowed'] == 'No') {
// print('not allowed');
//
// exceptionalClass[index] = "1";
// } else if (newValue != null &&
// newValue['is_allowed'] == 'yes') {
// print('not allowed');
//
// exceptionalClass[index] = "0";
// }
//
// setState(() {
// selectedClasses[index] =
// newValue?['dropdown_key'];
// print(
// "Selected Class: ${selectedClasses[index]}",
// );
// });
if (newValue != null &&
newValue['is_allowed'] == 'No') {
final confirm = await showNotAllowedDialog(
context,
message:
"You are not entitled for this service. If you wish to continue with this service, you may require another approval.",
);
});
if (confirm) {
setState(() {
exceptionalClass[index] = "1";
selectedClasses[index] =
newValue['dropdown_key'];
});
print(
"Selected Purpose: ${selectedClasses[index]}",
);
} else {
setState(() {
exceptionalClass[index] = "0";
selectedClasses[index] =
null; // Clear selection if user cancels
});
print(
"selectedItem resolved to: ${selectedClasses[index]}",
);
}
return;
} else if (newValue != null &&
newValue['is_allowed'] == 'yes') {
setState(() {
exceptionalClass[index] = "0";
selectedClasses[index] =
newValue?['dropdown_key'];
print(
"Selected Purpose: ${selectedClasses[index]}",
);
});
}
}
: null,
),
@ -2690,7 +2776,8 @@ class FlightScreenState extends State<FlightScreen> {
// Close Button
ElevatedButton(
onPressed: () {
widget.onClose(false); // Close the dialog or screen
widget.onClose("Flight", false);
// widget.onClose(false); // Close the dialog or screen
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.grey[400], // Light grey color

View File

@ -18,7 +18,7 @@ import 'package:http/http.dart' as http;
class ForexScreen extends StatefulWidget {
final List<Map<String, dynamic>> flightData;
final Map<String, dynamic>? apiData;
final Function(bool) onClose;
final Function(String, bool) onClose;
final Map<String, dynamic>? selectedItem;
final List<dynamic>? apiCountryData;
final Function(Map<String, dynamic>) onSaveForex;
@ -366,8 +366,8 @@ class _ForexScreenState extends State<ForexScreen> {
} else {
widget.onSaveForex(forexData); // Send object to parent
}
widget.onClose(false); // Close screen after saving
widget.onClose("Forex", false);
// widget.onClose(false); // Close screen after saving
}
DateTime? _parseDate(String date) {
@ -2294,7 +2294,7 @@ class _ForexScreenState extends State<ForexScreen> {
// Close Button
ElevatedButton(
onPressed: () {
widget.onClose(false); // Close the dialog or screen
widget.onClose("Forex", false); // Close the dialog or screen
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.grey[400], // Light grey color

View File

@ -11,7 +11,7 @@ import '../../widgets/custom_text_itnerary_sub.dart';
class InsuranceScreen extends StatefulWidget {
final List<Map<String, dynamic>> flightData;
final Map<String, dynamic>? apiData;
final Function(bool) onClose;
final Function(String, bool) onClose;
final Function(Map<String, dynamic>) onSaveInsurance;
final Map<String, dynamic>? selectedItem;
final String? loginUser;
@ -290,8 +290,8 @@ class _InsuranceScreenState extends State<InsuranceScreen> {
} else {
widget.onSaveInsurance(InsuranceData);
}
widget.onClose(false); // Close screen after saving
widget.onClose("Insurance", false);
// widget.onClose(false); // Close screen after saving
}
DateTime? _parseDate(String date) {
@ -1371,7 +1371,7 @@ class _InsuranceScreenState extends State<InsuranceScreen> {
// Close Button
ElevatedButton(
onPressed: () {
widget.onClose(false); // Close the dialog or screen
widget.onClose("Insurance", false); // Close the dialog or screen
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.grey[400], // Light grey color

View File

@ -9,7 +9,8 @@ import '../../widgets/custom_text_itnerary_sub.dart';
class MiscellaneousScreen extends StatefulWidget {
final Map<String, dynamic>? apiData;
final Function(bool) onClose;
final Function(String, bool) onClose;
final Function(Map<String, dynamic>) onSaveMiscellaneous;
final Map<String, dynamic>? selectedItem;
final int? selectedIndex;
@ -137,7 +138,8 @@ class _MiscellaneousScreenState extends State<MiscellaneousScreen> {
widget.onSaveMiscellaneous(miscellaneousData); // Send object to parent
}
widget.onClose(false); // Close screen after saving
widget.onClose("Miscellaneous", false); // Close screen after saving
// widget.onClose(false); // Close screen after saving
// Clear only if this is a new entry
// if (widget.selectedItem == null) {
// _commentsController.clear();
@ -281,9 +283,10 @@ class _MiscellaneousScreenState extends State<MiscellaneousScreen> {
CustomTextFieldItnerarySubWrapper(
isFocused: _isHotelNameFocused,
isDesktop: isDesktop,
width: isDesktop
? MediaQuery.of(context).size.width * 0.34
: MediaQuery.of(context).size.width * 0.66,
width:
isDesktop
? MediaQuery.of(context).size.width * 0.34
: MediaQuery.of(context).size.width * 0.66,
child: SizedBox(
height: 40,
width: double.infinity,
@ -291,7 +294,7 @@ class _MiscellaneousScreenState extends State<MiscellaneousScreen> {
// focusNode: _taxiReqFocusNode,
items: purposeList.cast<Map<String, dynamic>>(),
selectedItem: purposeList.firstWhere(
(item) => item['dropdown_key'] == selectedSpecialType,
(item) => item['dropdown_key'] == selectedSpecialType,
orElse: () => {},
),
itemAsString: (item) => item['dropdown_value'] ?? '',
@ -328,31 +331,23 @@ class _MiscellaneousScreenState extends State<MiscellaneousScreen> {
if (selectedItem == null || selectedItem.isEmpty) {
return Text(
"Select ",
style: GoogleFonts.poppins(
color: Colors.grey,
fontSize: 13,
),
style: GoogleFonts.poppins(color: Colors.grey, fontSize: 13),
);
}
return Text(
selectedItem['dropdown_value'] ?? '',
style: GoogleFonts.poppins(
fontSize: 12,
color: Colors.black,
),
style: GoogleFonts.poppins(fontSize: 12, color: Colors.black),
);
},
onChanged:
purposeList.isNotEmpty
? (newValue) {
setState(() {
selectedSpecialType = newValue as String?;
});
print(
"Updating form data: -> ${newValue ?? ""}",
);
}
: null,
purposeList.isNotEmpty
? (newValue) {
setState(() {
selectedSpecialType = newValue as String?;
});
print("Updating form data: -> ${newValue ?? ""}");
}
: null,
),
),
),
@ -421,7 +416,8 @@ class _MiscellaneousScreenState extends State<MiscellaneousScreen> {
ElevatedButton(
onPressed: () {
_commentsController.clear();
widget.onClose(false); // Close the dialog or screen
widget.onClose("Miscellaneous", false);
// widget.onClose(false); // Close the dialog or screen
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.grey[400], // Light grey color

View File

@ -10,7 +10,7 @@ import '../../widgets/custom_text_itnerary_sub.dart';
class TaxiScreen extends StatefulWidget {
final Map<String, dynamic>? apiData;
final Function(bool) onClose;
final Function(String, bool) onClose;
final Function(Map<String, dynamic>) onSavetaxi;
final Map<String, dynamic>? selectedItem;
final String? loginUser;
@ -199,8 +199,8 @@ class _TaxiScreenState extends State<TaxiScreen> {
} else {
widget.onSavetaxi(taxiData);
}
widget.onClose(false); // Close screen after saving
widget.onClose("Taxi", false);
// widget.onClose(false); // Close screen after saving
}
@override
@ -404,7 +404,7 @@ class _TaxiScreenState extends State<TaxiScreen> {
// focusNode: _taxiReqFocusNode,
items: purposeList.cast<Map<String, dynamic>>(),
selectedItem: purposeList.firstWhere(
(item) => item['dropdown_key'] == selectedCarType,
(item) => item['dropdown_key'] == selectedCarType,
orElse: () => {},
),
itemAsString: (item) => item['dropdown_value'] ?? '',
@ -456,16 +456,14 @@ class _TaxiScreenState extends State<TaxiScreen> {
);
},
onChanged:
purposeList.isNotEmpty
? (newValue) {
setState(() {
selectedCarType = newValue as String?;
});
print(
"Updating form data: -> ${newValue ?? ""}",
);
}
: null,
purposeList.isNotEmpty
? (newValue) {
setState(() {
selectedCarType = newValue as String?;
});
print("Updating form data: -> ${newValue ?? ""}");
}
: null,
),
),
),
@ -544,9 +542,9 @@ class _TaxiScreenState extends State<TaxiScreen> {
isFocused: _taxiReqFocused,
isDesktop: isDesktop,
width:
isDesktop
? MediaQuery.of(context).size.width * 0.34
: MediaQuery.of(context).size.width * 0.66,
isDesktop
? MediaQuery.of(context).size.width * 0.34
: MediaQuery.of(context).size.width * 0.66,
child: SizedBox(
height: 40,
width: double.infinity,
@ -554,7 +552,7 @@ class _TaxiScreenState extends State<TaxiScreen> {
// focusNode: _taxiReqFocusNode,
items: purposeList.cast<Map<String, dynamic>>(),
selectedItem: purposeList.firstWhere(
(item) => item['dropdown_key'] == selectedReqTaxi,
(item) => item['dropdown_key'] == selectedReqTaxi,
orElse: () => {},
),
itemAsString: (item) => item['dropdown_value'] ?? '',
@ -591,31 +589,23 @@ class _TaxiScreenState extends State<TaxiScreen> {
if (selectedItem == null || selectedItem.isEmpty) {
return Text(
"Select ",
style: GoogleFonts.poppins(
color: Colors.grey,
fontSize: 13,
),
style: GoogleFonts.poppins(color: Colors.grey, fontSize: 13),
);
}
return Text(
selectedItem['dropdown_value'] ?? '',
style: GoogleFonts.poppins(
fontSize: 12,
color: Colors.black,
),
style: GoogleFonts.poppins(fontSize: 12, color: Colors.black),
);
},
onChanged:
purposeList.isNotEmpty
? (newValue) {
setState(() {
selectedReqTaxi = newValue as String?;
});
print(
"Updating form data: -> ${newValue ?? ""}",
);
}
: null,
purposeList.isNotEmpty
? (newValue) {
setState(() {
selectedReqTaxi = newValue as String?;
});
print("Updating form data: -> ${newValue ?? ""}");
}
: null,
),
),
),
@ -974,7 +964,7 @@ class _TaxiScreenState extends State<TaxiScreen> {
// Close Button
ElevatedButton(
onPressed: () {
widget.onClose(false); // Close the dialog or screen
widget.onClose("Taxi", false); // Close the dialog or screen
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.grey[400], // Light grey color

View File

@ -5,6 +5,7 @@ import 'package:intl/intl.dart';
import 'package:responsive_builder/responsive_builder.dart';
import '../../services/apiService.dart';
import '../../widgets/custom_confirmation_dialog.dart';
import '../../widgets/custom_text_field.dart';
import '../../widgets/custom_text_itnerary_sub.dart';
@ -12,7 +13,7 @@ class TrainScreen extends StatefulWidget {
final Map<String, dynamic>? apiData;
final Map<String, dynamic>? apiDataForClass;
final Function(Map<String, dynamic>) onSavetrain;
final Function(bool) onClose;
final Function(String, bool) onClose;
final Map<String, dynamic>? selectedItem;
final String? loginUser;
final String? tripType;
@ -67,6 +68,7 @@ class _TrainScreenState extends State<TrainScreen> {
bool _commentsFocus = false;
String? selectedClass;
String? exceptionalClass = "0";
String? selectedFrom;
String? selectedTo;
@ -80,6 +82,7 @@ class _TrainScreenState extends State<TrainScreen> {
// "from_station": _fromController.text,
"to_station": selectedTo,
// "to_station": _toController.text,
"is_this_exceptional": exceptionalClass,
"date": _dateController.text,
"time": _timeController.text,
"comments": _trainCommentsController.text,
@ -151,6 +154,11 @@ class _TrainScreenState extends State<TrainScreen> {
_dateController = initController("date");
_timeController = initController("time");
if (widget.selectedItem != null &&
widget.selectedItem!["is_this_exceptional"] != null) {
exceptionalClass = widget.selectedItem!["is_this_exceptional"].toString();
}
// Set the selected value if available
if (widget.selectedItem != null && widget.selectedItem!["class"] != null) {
selectedClass = widget.selectedItem!["class"].toString();
@ -288,8 +296,8 @@ class _TrainScreenState extends State<TrainScreen> {
} else {
widget.onSavetrain(trainData);
}
widget.onClose(false); // Close screen after saving
widget.onClose("Train", false);
// widget.onClose(false); // Close screen after saving
}
@override
@ -576,8 +584,8 @@ class _TrainScreenState extends State<TrainScreen> {
}
// Default selected value
selectedClass ??=
dropdownItems.isNotEmpty ? dropdownItems.first.value : null;
// selectedClass ??=
// dropdownItems.isNotEmpty ? dropdownItems.first.value : null;
return [
Column(
@ -631,17 +639,30 @@ class _TrainScreenState extends State<TrainScreen> {
height: 40,
width: double.infinity,
child: DropdownSearch<Map<String, dynamic>>(
key:
selectedClass == null
? UniqueKey()
: ValueKey(selectedClass),
items: purposeList.cast<Map<String, dynamic>>(),
selectedItem: purposeList.firstWhere(
(item) => item['dropdown_key'] == selectedClass,
orElse: () => {},
),
// selectedItem: purposeList.firstWhere(
// (item) => item['dropdown_key'] == selectedClass,
// orElse: () => {},
// ),
selectedItem:
selectedClass != null
? purposeList.firstWhere(
(item) => item['dropdown_key'] == selectedClass,
orElse: () => {},
)
: null,
itemAsString: (item) => item['dropdown_value'] ?? '',
popupProps: PopupProps.menu(
showSearchBox: false,
fit: FlexFit.loose,
menuProps: const MenuProps(backgroundColor: Colors.white),
itemBuilder: (context, item, isSelected) {
final bool isNotAllowed = item['is_allowed'] == 'No';
return Padding(
padding: const EdgeInsets.symmetric(
horizontal: 10,
@ -651,7 +672,7 @@ class _TrainScreenState extends State<TrainScreen> {
item['dropdown_value'] ?? '',
style: GoogleFonts.poppins(
fontSize: 12,
color: Colors.black,
color: isNotAllowed ? Colors.redAccent : Colors.black,
),
),
);
@ -660,7 +681,10 @@ class _TrainScreenState extends State<TrainScreen> {
dropdownDecoratorProps: const DropDownDecoratorProps(
dropdownSearchDecoration: InputDecoration(
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(horizontal: 10,vertical: 10,),
contentPadding: EdgeInsets.symmetric(
horizontal: 10,
vertical: 10,
),
),
),
dropdownBuilder: (context, selectedItem) {
@ -682,14 +706,59 @@ class _TrainScreenState extends State<TrainScreen> {
);
},
onChanged:
purposeList.isNotEmpty
? (Map<String, dynamic>? newValue) {
setState(() {
selectedClass= newValue?['dropdown_key'];
print("Selected Purpose: ${selectedClass}");
});
}
: null,
purposeList.isNotEmpty
? (Map<String, dynamic>? newValue) async {
if (newValue != null &&
newValue['is_allowed'] == 'No') {
final confirm = await showNotAllowedDialog(
context,
message:
"You are not entitled for this service. If you wish to continue with this service, you may require another approval.",
);
if (confirm) {
setState(() {
exceptionalClass = "1";
selectedClass = newValue['dropdown_key'];
});
print("Selected Purpose: ${selectedClass}");
} else {
setState(() {
exceptionalClass = "0";
selectedClass =
null; // Clear selection if user cancels
});
print(
"selectedItem resolved to: ${selectedClass}",
);
}
return;
} else if (newValue != null &&
newValue['is_allowed'] == 'yes') {
setState(() {
exceptionalClass = "0";
selectedClass = newValue?['dropdown_key'];
print("Selected Purpose: ${selectedClass}");
});
}
// if (newValue != null &&
// newValue['is_allowed'] == 'No') {
// print('not allowed');
//
// setState(() {
// exceptionalClass = "1";
// // hasExceptionalClass = true;
// });
// }
setState(() {
selectedClass = newValue?['dropdown_key'];
print("Selected Purpose: ${selectedClass}");
});
}
: null,
),
),
),
@ -817,16 +886,16 @@ class _TrainScreenState extends State<TrainScreen> {
menuProps: const MenuProps(backgroundColor: Colors.white),
itemBuilder:
(context, item, isSelected) => Container(
color: Colors.white,
padding: EdgeInsets.symmetric(
horizontal: 10,
vertical: 6,
),
child: Text(
item,
style: GoogleFonts.poppins(fontSize: 11.5),
),
),
color: Colors.white,
padding: EdgeInsets.symmetric(
horizontal: 10,
vertical: 6,
),
child: Text(
item,
style: GoogleFonts.poppins(fontSize: 11.5),
),
),
searchFieldProps: TextFieldProps(
decoration: InputDecoration(
hintText: "Search ...",
@ -843,22 +912,20 @@ class _TrainScreenState extends State<TrainScreen> {
),
dropdownBuilder:
(context, selectedItem) => Align(
alignment: Alignment.centerLeft,
child: Text(
selectedItem ?? "Select ",
style: GoogleFonts.poppins(
fontSize: 12,
color: Colors.black,
alignment: Alignment.centerLeft,
child: Text(
selectedItem ?? "Select ",
style: GoogleFonts.poppins(
fontSize: 12,
color: Colors.black,
),
),
),
),
),
onChanged: (String? newValue) {
setState(() {
selectedFrom =
countryMap.entries
.firstWhere(
(entry) => entry.value == newValue,
)
.firstWhere((entry) => entry.value == newValue)
.key;
});
},
@ -952,16 +1019,16 @@ class _TrainScreenState extends State<TrainScreen> {
menuProps: const MenuProps(backgroundColor: Colors.white),
itemBuilder:
(context, item, isSelected) => Container(
color: Colors.white,
padding: EdgeInsets.symmetric(
horizontal: 10,
vertical: 6,
),
child: Text(
item,
style: GoogleFonts.poppins(fontSize: 11.5),
),
),
color: Colors.white,
padding: EdgeInsets.symmetric(
horizontal: 10,
vertical: 6,
),
child: Text(
item,
style: GoogleFonts.poppins(fontSize: 11.5),
),
),
searchFieldProps: TextFieldProps(
decoration: InputDecoration(
hintText: "Search ...",
@ -978,22 +1045,20 @@ class _TrainScreenState extends State<TrainScreen> {
),
dropdownBuilder:
(context, selectedItem) => Align(
alignment: Alignment.centerLeft,
child: Text(
selectedItem ?? "Select ",
style: GoogleFonts.poppins(
fontSize: 12,
color: Colors.black,
alignment: Alignment.centerLeft,
child: Text(
selectedItem ?? "Select ",
style: GoogleFonts.poppins(
fontSize: 12,
color: Colors.black,
),
),
),
),
),
onChanged: (String? newValue) {
setState(() {
selectedTo =
countryMap.entries
.firstWhere(
(entry) => entry.value == newValue,
)
.firstWhere((entry) => entry.value == newValue)
.key;
});
},
@ -1174,7 +1239,8 @@ class _TrainScreenState extends State<TrainScreen> {
// Close Button
ElevatedButton(
onPressed: () {
widget.onClose(false);
// widget.onClose(false);
widget.onClose("Train", false);
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.grey[400], // Light grey color

View File

@ -13,7 +13,7 @@ class VisaScreen extends StatefulWidget {
final Map<String, dynamic>? apiData;
final List<dynamic>? apiCountryData;
final Function(bool) onClose;
final Function(String, bool) onClose;
final Function(Map<String, dynamic>) onSaveVisa;
final Map<String, dynamic>? selectedItem;
final String? loginUser;
@ -259,8 +259,8 @@ class _VisaScreenState extends State<VisaScreen> {
} else {
widget.onSaveVisa(visaData);
}
widget.onClose(false); // Close screen after saving
widget.onClose("Visa", false);
// widget.onClose(false); // Close screen after saving
}
@override
@ -513,9 +513,10 @@ class _VisaScreenState extends State<VisaScreen> {
CustomTextFieldItnerarySubWrapper(
isFocused: _isHotelNameFocused,
isDesktop: isDesktop,
width: isDesktop
? MediaQuery.of(context).size.width * 0.34
: MediaQuery.of(context).size.width * 0.66,
width:
isDesktop
? MediaQuery.of(context).size.width * 0.34
: MediaQuery.of(context).size.width * 0.66,
child: SizedBox(
height: 40,
child: DropdownSearch<String>(
@ -527,16 +528,16 @@ class _VisaScreenState extends State<VisaScreen> {
menuProps: const MenuProps(backgroundColor: Colors.white),
itemBuilder:
(context, item, isSelected) => Container(
color: Colors.white,
padding: EdgeInsets.symmetric(
horizontal: 10,
vertical: 6,
),
child: Text(
item,
style: GoogleFonts.poppins(fontSize: 11.5),
),
),
color: Colors.white,
padding: EdgeInsets.symmetric(
horizontal: 10,
vertical: 6,
),
child: Text(
item,
style: GoogleFonts.poppins(fontSize: 11.5),
),
),
searchFieldProps: TextFieldProps(
decoration: InputDecoration(
hintText: "Search ...",
@ -553,18 +554,21 @@ class _VisaScreenState extends State<VisaScreen> {
),
dropdownBuilder:
(context, selectedItem) => Align(
alignment: Alignment.centerLeft,
child: Text(
selectedItem ?? "Select ",
style: GoogleFonts.poppins(fontSize: 12,color: Colors.black,),
),
),
alignment: Alignment.centerLeft,
child: Text(
selectedItem ?? "Select ",
style: GoogleFonts.poppins(
fontSize: 12,
color: Colors.black,
),
),
),
onChanged: (String? newValue) {
setState(() {
selectedCountry =
countryMap.entries.firstWhere(
(entry) => entry.value == newValue,
).key;
countryMap.entries
.firstWhere((entry) => entry.value == newValue)
.key;
});
},
),
@ -635,7 +639,7 @@ class _VisaScreenState extends State<VisaScreen> {
child: DropdownSearch<Map<String, dynamic>>(
items: purposeList.cast<Map<String, dynamic>>(),
selectedItem: purposeList.firstWhere(
(item) => item['dropdown_key'] == selectedPurpose,
(item) => item['dropdown_key'] == selectedPurpose,
orElse: () => {},
),
itemAsString: (item) => item['dropdown_value'] ?? '',
@ -662,7 +666,10 @@ class _VisaScreenState extends State<VisaScreen> {
dropdownDecoratorProps: const DropDownDecoratorProps(
dropdownSearchDecoration: InputDecoration(
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(horizontal: 10,vertical: 10,),
contentPadding: EdgeInsets.symmetric(
horizontal: 10,
vertical: 10,
),
),
),
dropdownBuilder: (context, selectedItem) {
@ -692,14 +699,14 @@ class _VisaScreenState extends State<VisaScreen> {
// EdgeInsets.symmetric(horizontal: 10), // Proper padding
// ),
onChanged:
purposeList.isNotEmpty
? (Map<String, dynamic>? newValue) {
setState(() {
selectedPurpose= newValue?['dropdown_key'];
print("Selected Purpose: ${selectedPurpose}");
});
}
: null,
purposeList.isNotEmpty
? (Map<String, dynamic>? newValue) {
setState(() {
selectedPurpose = newValue?['dropdown_key'];
print("Selected Purpose: ${selectedPurpose}");
});
}
: null,
),
),
),
@ -826,7 +833,8 @@ class _VisaScreenState extends State<VisaScreen> {
// Close Button
ElevatedButton(
onPressed: () {
widget.onClose(false); // Close the dialog or screen
widget.onClose("Visa", false);
// widget.onClose(false); // Close the dialog or screen
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.grey[400], // Light grey color

View File

@ -133,7 +133,8 @@ class _FlightListWidgetState extends State<FlightListWidget> {
widget.isViewMode
? null
: () {
checkClass();
widget.onAddNew("Flight", true);
// checkClass();
print("New data");
},
child: Row(

View File

@ -298,6 +298,8 @@ class _CreatePlansState extends State<CreatePlan> {
context.go('/listPlan');
} else if (currentUri == "/approver/plans") {
context.go('/approvallist');
} else if (currentUri == "/travelagent/trips") {
context.go('/listTravelAgentPlan');
} else {
isApprover ? context.go('/approvallist') : context.go('/listPlan');
}
@ -381,6 +383,8 @@ class CreateNewPlansState extends State<CreateNewPlan> {
final TextEditingController _tripTitleController = TextEditingController();
final TextEditingController _descriptionController = TextEditingController();
final TextEditingController _excepntldescriptionController =
TextEditingController();
final TextEditingController _remarksController = TextEditingController();
// final FocusNode _tripTitleFocusNode = FocusNode();
@ -416,6 +420,7 @@ class CreateNewPlansState extends State<CreateNewPlan> {
bool showDomestic = false;
bool showInternational = false;
bool hasAction = true;
bool hasExceptionalClass = false;
late Map<String, String> costCenterMap;
List<String> costCenterIds = [];
@ -445,10 +450,10 @@ class CreateNewPlansState extends State<CreateNewPlan> {
List<Map<String, dynamic>> insuranceList = [];
List<Map<String, dynamic>> accommodationList = [];
List<Map<String, dynamic>> trainList = [];
List<Map<String, dynamic>> flightList = [];
List<Map<String, dynamic>> busList = [];
List<Map<String, dynamic>> taxiList = [];
List<Map<String, dynamic>> forexList = [];
List<Map<String, dynamic>> flightList = [];
List<Map<String, dynamic>> planStatusList = [];
@ -471,6 +476,7 @@ class CreateNewPlansState extends State<CreateNewPlan> {
"is_billable": _selectedIsBillable,
"purpose_of_travel": selectedPurpose,
"description": _descriptionController.text,
"exceptional_plan_reason": _excepntldescriptionController.text,
"functional_department": selectedFuncDept,
"so_number": "12345",
"created_by": selfId,
@ -495,6 +501,7 @@ class CreateNewPlansState extends State<CreateNewPlan> {
"is_billable",
"purpose_of_travel",
"description",
"excepntldescription",
"functional_department",
];
@ -535,6 +542,13 @@ class CreateNewPlansState extends State<CreateNewPlan> {
print("Updated $type List: $newList");
}
void handleExcentionalClass(bool val) {
print("handleExcentionalClass - $val");
setState(() {
hasExceptionalClass = val;
});
}
@override
void initState() {
super.initState();
@ -652,6 +666,13 @@ class CreateNewPlansState extends State<CreateNewPlan> {
_descriptionController.text =
widget.selectedPlanData['description'] ?? '';
_excepntldescriptionController.text =
widget.selectedPlanData['exceptional_plan_reason'] ?? '';
if (_excepntldescriptionController.text != "") {
hasExceptionalClass = true;
}
_selectedTripType = widget.selectedPlanData['trip_type'];
flightTripTypeNotifier.value = widget.selectedPlanData['trip_type'];
_selectedIsBillable =
@ -1136,6 +1157,8 @@ class CreateNewPlansState extends State<CreateNewPlan> {
"cost_center_id": selectedCostCenterId,
"functional_department": selectedFuncDept,
"purpose_of_travel": selectedPurpose,
if (hasExceptionalClass)
"exceptional_plan_reason": _excepntldescriptionController.text,
};
print("validateForm.....3");
for (var entry in requiredFields.entries) {
@ -1268,8 +1291,55 @@ class CreateNewPlansState extends State<CreateNewPlan> {
}
}
Future<void> checkExceptionalClass() async {
// pretend we fetch something
await Future.delayed(Duration(seconds: 1)); // Example async operation
int exceptionalCount = 0;
bool hasExceptional(List<Map<String, dynamic>> list) {
return list.any(
(item) =>
item['is_this_exceptional']?.toString() == '1' &&
item['is_active']?.toString() == '1',
);
}
bool hasExceptionalInFlights(List<Map<String, dynamic>> flights) {
for (var flight in flights) {
if (flight['is_active']?.toString() == '1') {
final trips = flight['trips'];
if (trips is List) {
final exceptionalTrip = trips.any(
(trip) => trip['is_this_exceptional']?.toString() == '1',
);
if (exceptionalTrip) return true;
}
}
}
return false;
}
if (hasExceptionalInFlights(flightList)) exceptionalCount++;
if (hasExceptional(trainList)) exceptionalCount++;
if (hasExceptional(accommodationList)) exceptionalCount++;
setState(() {
hasExceptionalClass = exceptionalCount >= 1;
if (!hasExceptionalClass) {
_excepntldescriptionController.text = '';
}
});
print("hasExceptionalClass: $hasExceptionalClass");
}
void handleSubmit() {
setState(() async {
await checkExceptionalClass();
print("Handle Submit....after check");
print("Hansle Submit....11");
if (validateForm() && temporaryMessage == null) {
print("Hansle Submit....11222");
@ -1690,17 +1760,18 @@ class CreateNewPlansState extends State<CreateNewPlan> {
? Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// _buildNonDescriptionColumn(),
// SizedBox(width: 25),
//
_buildDescriptionColumn(isDesktop),
SizedBox(width: 25),
if (hasExceptionalClass) _buildNonDescriptionColumn(),
],
)
: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// _buildNonDescriptionColumn(),
// SizedBox(height: 15),
_buildDescriptionColumn(isDesktop),
SizedBox(height: 15),
_buildNonDescriptionColumn(),
],
),
@ -1743,6 +1814,7 @@ class CreateNewPlansState extends State<CreateNewPlan> {
apiDataForClass: apiDataForClass,
apiCountryData: apiCountryData,
onItineraryUpdate: handleItineraryUpdate,
onExeptionalClass: handleExcentionalClass,
loginUser: selfId,
selectedPlanData: planData,
isViewMode: widget.isViewMode,
@ -3248,7 +3320,7 @@ class CreateNewPlansState extends State<CreateNewPlan> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Trip Name", // Your label
"Reason For Exceptional Class *", // Your label
style: GoogleFonts.poppins(
fontSize: 12,
fontWeight: FontWeight.w600,
@ -3256,32 +3328,54 @@ class CreateNewPlansState extends State<CreateNewPlan> {
),
),
SizedBox(height: 5),
CustomTextFieldWrapper(
isFocused: false,
// isFocused: _isTripTitleFocused,
isFocused: focusStates["excepntldescriptionFocused"] ?? false,
// isFocused: _isdescriptionFocused,
width:
widget.isDesktop
? MediaQuery.of(context).size.width * 0.42
: null,
isDesktop: widget.isDesktop,
child: SizedBox(
height: 35,
height: 55,
child: TextField(
// focusNode: _tripTitleFocusNode,
controller: _tripTitleController,
focusNode: focusNodes["excepntldescriptionFocusNode"],
controller: _excepntldescriptionController,
maxLines: 2,
keyboardType: TextInputType.multiline,
style: TextStyle(fontSize: 12),
enabled: !widget.isViewMode,
decoration: InputDecoration(
labelText: "Trip Name",
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
labelText: "Exceptional Description",
labelStyle: GoogleFonts.poppins(
fontSize: 12,
color: Colors.grey,
),
// labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16),
contentPadding: EdgeInsets.symmetric(vertical: 1),
),
),
),
),
if (validationErrors["exceptional_plan_reason"] != null)
Padding(
padding: EdgeInsets.only(top: 4),
child: Text(
validationErrors["exceptional_plan_reason"]!,
style: GoogleFonts.poppins(
fontSize: 12,
color: Colors.red,
),
),
),
],
),
],
),
SizedBox(height: 8),
],
);
}

View File

@ -34,6 +34,8 @@ class DynamicItinerary extends StatefulWidget {
final String? loginUser;
final Function(String, List<Map<String, dynamic>>)
onItineraryUpdate; // Updated Signature
final void Function(bool)? onExeptionalClass;
final Map<String, dynamic> selectedPlanData;
final bool isViewMode;
final GlobalKey<FlightScreenState> flightScreenKey;
@ -48,6 +50,7 @@ class DynamicItinerary extends StatefulWidget {
required this.selectedPlanData,
required this.isViewMode,
required this.hasAction,
this.onExeptionalClass,
this.tripType,
this.apiDataForClass,
required this.tripTypeNotifier,
@ -64,6 +67,7 @@ class DynamicItineraryState extends State<DynamicItinerary> {
String? _tripType;
String selectedOption = "";
String selectedListOption = "";
String selectedActiveTab = "";
bool isSelected = false;
Map<String, dynamic>? selectedItem;
@ -400,7 +404,8 @@ class DynamicItineraryState extends State<DynamicItinerary> {
return false; // No itinerary data available
}
void handleClose(bool value) {
void handleMClose(bool value) {
print("handleClos- $isSelected");
setState(() {
selectedOption = "";
isSelected = value;
@ -409,6 +414,17 @@ class DynamicItineraryState extends State<DynamicItinerary> {
});
}
void handleClose(name, bool value) {
print("handleMClos- $name, $isSelected");
setState(() {
selectedListOption = name;
selectedOption = "";
isSelected = value;
selectedIndex = null;
selectedItem = null;
});
}
void handleEdit(bool value, selectedItem, title) {
setState(() {
selectedOption = title;
@ -420,6 +436,7 @@ class DynamicItineraryState extends State<DynamicItinerary> {
}
void handlecreateNewPlan(name, bool value) {
print("handlecreateNewPlan- $name , $value");
setState(() {
selectedOption = name;
isSelected = value;
@ -710,6 +727,7 @@ class DynamicItineraryState extends State<DynamicItinerary> {
break;
case "Miscellaneous":
selectedWidget = MiscellaneousScreen(
// onClose: handleClose,
onClose: handleClose,
apiData: widget.apiData,
loginUser: widget.loginUser,
@ -723,10 +741,12 @@ class DynamicItineraryState extends State<DynamicItinerary> {
selectedWidget = AccomodationScreen(
onClose: handleClose,
loginUser: widget.loginUser,
apiDataForClass: widget.apiDataForClass,
onSaveAccomadation:
(data) => handleItineraryUpdate("Accomodation", data),
selectedItem: selectedItem,
flightData: itineraryData["Flight"]!,
onExceptionalClass: widget.onExeptionalClass,
);
break;
case "Forex":
@ -747,6 +767,7 @@ class DynamicItineraryState extends State<DynamicItinerary> {
tripTypeNotifier: widget.tripTypeNotifier,
hasAction: widget.hasAction,
tripType: widget.tripType,
// onClose: handleMClose,
onClose: handleClose,
loginUser: widget.loginUser,
onSaveFlight: (data) => handleItineraryUpdate("Flight", data),
@ -758,71 +779,6 @@ class DynamicItineraryState extends State<DynamicItinerary> {
break;
}
// return ResponsiveBuilder(builder: (context, sizingInfo) {
// bool isMobile = sizingInfo.isMobile;
// bool isDesktop = sizingInfo.deviceScreenType == DeviceScreenType.desktop;
//
// return Column(
// // mainAxisSize: MainAxisSize.min,
// children: [
// Transform.translate(
// offset: Offset(0, 20),
// child: Container(
// decoration: BoxDecoration(
// border: Border(
// bottom: BorderSide(color: Color(0xFFF4F4FB), width: 2)),
// borderRadius: BorderRadius.circular(1),
// color: Color(0xFFF4F4FB),
// ),
// margin: EdgeInsets.symmetric(
// horizontal: MediaQuery.of(context).size.width *
// 0.05, // 30% of screen width as horizontal padding
// vertical: MediaQuery.of(context).size.height *
// 0, // 5% of screen height as vertical padding
// ),
// child: isMobile
// ? Expanded(
// child: SingleChildScrollView(
// scrollDirection: Axis.horizontal,
// child: Row(
// children: _buildOptions(),
// ),
// ),
// )
// : Row(
// mainAxisAlignment: MainAxisAlignment.spaceEvenly,
// // mainAxisSize: MainAxisSize.min,
// children: _buildOptions(),
// ),
// ),
// ),
//
//
// Container(
// decoration: BoxDecoration(
// // border:
// // Border(bottom: BorderSide(color: Colors.black, width: 2)),
//
// border: Border.all(color: Colors.green, width: 1.5),
// borderRadius: BorderRadius.circular(1),
// color: Colors.yellowAccent.shade100,
// ),
// margin: EdgeInsets.only(top: 0),
// child: Column(
// // mainAxisSize: MainAxisSize.min,
// crossAxisAlignment: CrossAxisAlignment.stretch,
// children: [
// SizedBox(height: 2),
// isSelected ? selectedWidget : selectedListWidget,
// // TrainScreen()
// ],
// ),
// ),
//
// ],
// );
// });
return ResponsiveBuilder(
builder: (context, sizingInfo) {
bool isMobile = sizingInfo.isMobile;
@ -964,19 +920,69 @@ class DynamicItineraryState extends State<DynamicItinerary> {
}).toList();
}
bool checkClass() {
if (widget.hasAction) {
if (widget.tripType?.isNotEmpty == true) {
print("Teppp - ${widget.tripType}");
return true;
} else {
showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: const Text('Select Trip Type'),
content: const Text(
'Please select a trip type before selecting services.',
),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: const Text('OK'),
),
],
);
},
);
return false;
}
} else {
print("Teppp No - ${widget.tripType}");
return true; // still allow it when `hasAction` is false
}
}
Widget _buildOption(Map<String, dynamic> service, bool hasData) {
String name = service['name'];
String iconUrl = service['icon']; // Can be empty string
IconData fallbackIcon = _getLocalIconForService(name);
bool isOptionSelected = selectedListOption == name;
// bool isOptionSelected = selectedListOption == name;
bool isOptionSelected = selectedActiveTab == name;
// bool isOptionSelected =
// hasData ? selectedListOption == name : selectedOption == name;
return GestureDetector(
onTap: () {
setState(() {
selectedListOption = name;
isSelected = false;
});
if (checkClass()) {
setState(() {
if (hasData) {
selectedListOption = name;
selectedActiveTab = name;
isSelected = false;
} else {
selectedOption = name;
selectedActiveTab = name;
isSelected = true;
selectedItem = null;
}
});
}
// setState(() {
// selectedListOption = name;
// isSelected = false;
// // selectedOption = name;
// // isSelected = true;
// });
},
child: Container(
decoration: BoxDecoration(

View File

@ -1,3 +1,3 @@
//api url
// const String apiUrl = 'http://apitest.tripapprovaltool.com/tstat_be';
const String apiUrl = 'https://uat.tripapprovaltool.com/tstat_be';
const String apiUrl = 'http://apitest.tripapprovaltool.com/tstat_be';
// const String apiUrl = 'https://uat.tripapprovaltool.com/tstat_be';

View File

@ -680,11 +680,7 @@ final List<Map<String, dynamic>> menuItems = [
// 'label': 'Template List'
// },
// {'value': '/template', 'icon': Icons.ac_unit_sharp, 'label': 'Template'},
{
'value': '/report',
'icon': Icons.auto_graph,
'label': 'Reports',
},
{'value': '/report', 'icon': Icons.auto_graph, 'label': 'Reports'},
{
'value': '/CreateUserDetails',
'icon': Icons.account_circle,

View File

@ -81,6 +81,10 @@ final GoRouter router = GoRouter(
path: '/approver/plans',
builder: (context, state) => CreatePlan(),
),
GoRoute(
path: '/travelagent/trips',
builder: (context, state) => CreatePlan(),
),
GoRoute(
path: '/listUser',
builder: (context, state) => UserListScreen(),

View File

@ -643,6 +643,25 @@ class ApiService {
}
}
static Future<void> viewPlanTravelAgent(
BuildContext context,
String planId, {
bool isViewMode = false,
bool isMyTrips = false,
}) async {
try {
Map<String, dynamic> planData = await getViewPlanEdit(planId);
print("ViewAAA - $planData");
context.go(
'/travelagent/trips',
extra: {'planData': planData, 'isViewMode': isViewMode},
);
} catch (e) {
print("Error fetching plan: $e");
}
}
Future<Map<String, dynamic>> fetchUserApprovalList() async {
String? orgId = await getOrgId();
String? userId = await getUserId();
@ -899,8 +918,8 @@ class ApiService {
// download the Template for bulk upload the user
Future<void> getDownloadUserTemplateForUpload() async {
final String apiUrldata = '$apiUrl/api/user/userUploadTemplate'; // download the Template for bulk upload user => api
final String apiUrldata =
'$apiUrl/api/user/userUploadTemplate'; // download the Template for bulk upload user => api
// final String apiUrldata = '$apiUrl/auth/googlelogin';
@ -930,9 +949,9 @@ class ApiService {
// Create a link element to trigger the download
final anchor =
html.AnchorElement(href: url)
..setAttribute('download', 'user_file.xlsx')
..click();
html.AnchorElement(href: url)
..setAttribute('download', 'user_file.xlsx')
..click();
// Revoke the download URL to free up resources
html.Url.revokeObjectUrl(url);
@ -962,7 +981,6 @@ class ApiService {
}
}
//---------------------------------------User Management-----------------------------------
Future<Map<String, dynamic>> getSingleUser(int userId) async {
@ -1511,7 +1529,7 @@ class ApiService {
// ----------------------- User Management - CheckDuplicate end here ----------------------------------
// ----------------------- Report -------------------------
Future<Map<String, dynamic>> CallReports(String apiRoute,String body) async {
Future<Map<String, dynamic>> CallReports(String apiRoute, String body) async {
String apiUrldata = '$apiUrl/api/$apiRoute';
final token = await getToken();
@ -1521,8 +1539,11 @@ class ApiService {
final response = await http.post(
Uri.parse(apiUrldata),
headers: {'Authorization': 'Bearer $token','Content-Type': 'application/json'},
body: body
headers: {
'Authorization': 'Bearer $token',
'Content-Type': 'application/json',
},
body: body,
);
if (response.statusCode == 200) {
@ -1539,14 +1560,20 @@ class ApiService {
}
}
Future<void> reportExcelDownload(String apiRoute,String body,String name) async {
Future<void> reportExcelDownload(
String apiRoute,
String body,
String name,
) async {
final String apiUrldata = '$apiUrl/api/$apiRoute';
// Sanitize name for filename (optional)
final safeName = name.replaceAll(RegExp(r'[^\w\s-]'), ''); // remove any special chars if needed
final safeName = name.replaceAll(
RegExp(r'[^\w\s-]'),
'',
); // remove any special chars if needed
final excelName = '$safeName.xlsx';
final token = await getToken();
if (token == null) {
@ -1554,9 +1581,12 @@ class ApiService {
}
final response = await http.post(
Uri.parse(apiUrldata),
headers: {'Authorization': 'Bearer $token','Content-Type': 'application/json'},
body: body
Uri.parse(apiUrldata),
headers: {
'Authorization': 'Bearer $token',
'Content-Type': 'application/json',
},
body: body,
);
if (response.statusCode == 200) {
@ -1571,9 +1601,9 @@ class ApiService {
// Create a link element to trigger the download
final anchor =
html.AnchorElement(href: url)
..setAttribute('download', excelName)
..click();
html.AnchorElement(href: url)
..setAttribute('download', excelName)
..click();
// Revoke the download URL to free up resources
html.Url.revokeObjectUrl(url);
@ -1587,5 +1617,6 @@ class ApiService {
throw Exception('Failed to Download');
}
}
// ----------------------- Report -------------------------
// ----------------------- Report -------------------------
}

View File

@ -0,0 +1,30 @@
import 'package:flutter/material.dart';
Future<bool> showNotAllowedDialog(
BuildContext context, {
String? message,
}) async {
return await showDialog<bool>(
context: context,
barrierDismissible: false, // Force user to choose
builder: (context) {
return AlertDialog(
title: const Text('Notice'),
content: Text(
message ?? 'This class is not allowed. Do you want to continue?',
),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(false), // Cancel
child: const Text('Cancel'),
),
ElevatedButton(
onPressed: () => Navigator.of(context).pop(true), // OK
child: const Text('OK'),
),
],
);
},
) ??
false; // Default to false if dialog is dismissed
}