itinerary changes

This commit is contained in:
venbaittech 2025-04-30 13:29:03 +05:30
parent 15992c4008
commit 5c628282c6
27 changed files with 2941 additions and 1798 deletions

View File

@ -6,6 +6,7 @@ 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 Function(Map<String, dynamic>) onSaveAccomadation;
final Map<String, dynamic>? selectedItem;
@ -15,13 +16,17 @@ class AccomodationScreen extends StatefulWidget {
{required this.onClose,
required this.onSaveAccomadation,
required this.selectedItem,
required this.loginUser});
required this.loginUser,
required this.flightData});
@override
_AccomodationScreenState createState() => _AccomodationScreenState();
}
class _AccomodationScreenState extends State<AccomodationScreen> {
late ValueNotifier<String?> flightFirstTripDateNotifier;
late ValueNotifier<String?> flightLastTripDateNotifier;
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
final FocusNode _destinationFocusNode = FocusNode();
@ -116,6 +121,30 @@ class _AccomodationScreenState extends State<AccomodationScreen> {
_checkInTimeController.addListener(() => _clearError("checkin_time"));
_checkOutController.addListener(() => _clearError("checkout_date"));
_checkOutTimeController.addListener(() => _clearError("checkout_time"));
flightFirstTripDateNotifier = ValueNotifier<String?>(null);
flightLastTripDateNotifier = ValueNotifier<String?>(null);
WidgetsBinding.instance.addPostFrameCallback((_) {
final result = getFlightTripDateRange(widget.flightData);
flightFirstTripDateNotifier.value = result['firstTripDate'];
flightLastTripDateNotifier.value = result['lastTripDate'];
// Only set controller after value is updated
final parsedDate =
DateTime.tryParse(flightFirstTripDateNotifier.value ?? '');
if (parsedDate != null) {
_checkInController.text = DateFormat('yyyy-MM-dd').format(parsedDate);
}
// Check and set default times if empty
if (_checkInTimeController.text.isEmpty) {
_checkInTimeController.text = '14:00'; // 2 PM
}
if (_checkOutTimeController.text.isEmpty) {
_checkOutTimeController.text = '12:00'; // 12 PM
}
});
}
@override
@ -139,6 +168,35 @@ class _AccomodationScreenState extends State<AccomodationScreen> {
}
}
Map<String, String?> getFlightTripDateRange(
List<Map<String, dynamic>> flightData) {
final allTrips = flightData
.expand((flight) => flight['trips'] ?? [])
.whereType<Map<String, dynamic>>()
.toList();
if (allTrips.isEmpty) {
return {
'firstTripDate': null,
'lastTripDate': null,
};
}
allTrips.sort((a, b) {
final aDate = DateTime.tryParse(a['date'] ?? '') ?? DateTime(1900);
final bDate = DateTime.tryParse(b['date'] ?? '') ?? DateTime(1900);
return aDate.compareTo(bDate);
});
final firstTrip = allTrips.first;
final lastTrip = allTrips.last;
return {
'firstTripDate': firstTrip['date'],
'lastTripDate': lastTrip['date'],
};
}
bool isValidData(Map<String, dynamic> data) {
errorMessages.clear(); // Reset errors
@ -159,6 +217,55 @@ class _AccomodationScreenState extends State<AccomodationScreen> {
}
}
// Additional validation: checkout_date >= checkin_date
final checkIn = data["checkin_date"];
final checkInTime = data["checkin_time"];
final checkOut = data["checkout_date"];
final checkOutTime = data["checkout_time"];
if (checkIn != null &&
checkOut != null &&
checkIn.toString().isNotEmpty &&
checkOut.toString().isNotEmpty) {
try {
final checkInDate = DateTime.parse(checkIn);
final checkOutDate = DateTime.parse(checkOut);
if (checkOutDate.isBefore(checkInDate)) {
errorMessages["checkout_date"] =
"Check-out date cannot be before check-in date";
} else if (checkOutDate.isAtSameMomentAs(checkInDate)) {
// If dates are same, check the times
if (checkInTime != null &&
checkOutTime != null &&
checkInTime.toString().isNotEmpty &&
checkOutTime.toString().isNotEmpty) {
try {
final checkInDateTime =
DateTime.parse("${checkIn}T${checkInTime}");
final checkOutDateTime =
DateTime.parse("${checkOut}T${checkOutTime}");
if (!checkOutDateTime.isAfter(checkInDateTime)) {
errorMessages["checkout_time"] =
"Check-out must be after check-in time";
} else {
final difference = checkOutDateTime.difference(checkInDateTime);
if (difference.inMinutes < 30) {
errorMessages["checkout_time"] =
"Check-out must be at least 30 minutes after check-in";
}
}
} catch (e) {
errorMessages["checkout_time"] = "Invalid time format";
}
}
}
} catch (e) {
errorMessages["checkout_date"] = "Invalid date format";
}
}
return errorMessages.isEmpty; // Valid if there are no errors
}
@ -185,35 +292,13 @@ class _AccomodationScreenState extends State<AccomodationScreen> {
bool isDesktop = sizingInfo.deviceScreenType == DeviceScreenType.desktop;
return Container(
color: Color(0xFFF4F4FB),
// color: Color(0xFFF4F4FB),
child: Form(
key: _formKey,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
Align(
alignment: Alignment.centerRight,
child: InkWell(
onTap: () {
print("Close icon clicked");
widget.onClose(false);
},
child: Icon(
Icons.close,
size: 18,
color: Color(0xFF575A74),
),
),
),
Text("Accomodation Booking",
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Color(0xFF575A74))),
SizedBox(
height: 6,
),
Padding(
padding: const EdgeInsets.all(28.0),
child: Center(
@ -256,17 +341,26 @@ class _AccomodationScreenState extends State<AccomodationScreen> {
: Column(
children: _buildThirdRow(isDesktop),
),
SizedBox(height: 10),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: _handleAction(isDesktop),
),
];
}
List<Widget> _buildFirstRow(isDesktop) {
return [
// Column(
// children: [
// ValueListenableBuilder<String?>(
// valueListenable: flightFirstTripDateNotifier,
// builder: (context, value, child) =>
// Text("First Trip Date: ${value ?? 'Not available'}"),
// ),
// ValueListenableBuilder<String?>(
// valueListenable: flightLastTripDateNotifier,
// builder: (context, value, child) =>
// Text("Last Trip Date: ${value ?? 'Not available'}"),
// ),
// ],
// ),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
@ -368,16 +462,41 @@ class _AccomodationScreenState extends State<AccomodationScreen> {
DateTime now = DateTime.now();
DateTime today = DateTime(now.year, now.month, now.day);
DateTime? pickedDate = await showDatePicker(
// Parse date from notifier if available, else use today
DateTime initialDate;
if (flightFirstTripDateNotifier.value != null) {
try {
initialDate = DateTime.parse(flightFirstTripDateNotifier.value!);
} catch (e) {
initialDate = today;
}
} else {
initialDate = today;
}
// Use previously selected date if valid
if (_selectedCheckInDate != null &&
_selectedCheckInDate!.isAfter(today)) {
initialDate = _selectedCheckInDate!;
}
final pickedDate = await showDatePicker(
context: context,
initialDate:
_selectedCheckInDate != null && _selectedCheckInDate!.isAfter(today)
? _selectedCheckInDate!
: today,
firstDate: today,
initialDate: initialDate,
firstDate: initialDate,
lastDate: DateTime(2100),
);
// DateTime? pickedDate = await showDatePicker(
// context: context,
// initialDate:
// _selectedCheckInDate != null && _selectedCheckInDate!.isAfter(today)
// ? _selectedCheckInDate!
// : today,
// firstDate: today,
// lastDate: DateTime(2100),
// );
if (pickedDate != null && pickedDate != _selectedCheckInDate) {
setState(() {
_selectedCheckInDate = pickedDate;
@ -410,17 +529,57 @@ class _AccomodationScreenState extends State<AccomodationScreen> {
DateTime? _selectedCheckOutDate;
TimeOfDay? _selectedCheckOutTime;
// Future<void> _selectCheckOutDate(BuildContext context) async {
// DateTime now = DateTime.now();
// DateTime today = DateTime(now.year, now.month, now.day);
//
// DateTime? pickedDate = await showDatePicker(
// context: context,
// initialDate: _selectedCheckOutDate != null &&
// _selectedCheckOutDate!.isAfter(today)
// ? _selectedCheckOutDate!
// : today,
// firstDate: today,
// lastDate: DateTime(2100),
// );
//
// if (pickedDate != null && pickedDate != _selectedCheckOutDate) {
// setState(() {
// _selectedCheckOutDate = pickedDate;
// _checkOutController.text =
// DateFormat('yyyy-MM-dd').format(pickedDate);
// });
// }
// }
Future<void> _selectCheckOutDate(BuildContext context) async {
DateTime now = DateTime.now();
DateTime today = DateTime(now.year, now.month, now.day);
DateTime? pickedDate = await showDatePicker(
DateTime? checkInDate;
try {
checkInDate = DateTime.parse(_checkInController.text);
} catch (e) {
checkInDate = today;
}
// // Ensure at least today is used
// DateTime firstDate = checkInDate.isAfter(today) ? checkInDate : today;
// DateTime initialDate = _selectedCheckOutDate != null &&
// _selectedCheckOutDate!.isAfter(firstDate)
// ? _selectedCheckOutDate!
// : firstDate;
DateTime firstDate = checkInDate;
DateTime initialDate = _selectedCheckOutDate != null &&
_selectedCheckOutDate!.isAfter(firstDate)
? _selectedCheckOutDate!
: firstDate;
final pickedDate = await showDatePicker(
context: context,
initialDate: _selectedCheckOutDate != null &&
_selectedCheckOutDate!.isAfter(today)
? _selectedCheckOutDate!
: today,
firstDate: today,
initialDate: initialDate,
firstDate: firstDate,
lastDate: DateTime(2100),
);
@ -678,23 +837,36 @@ class _AccomodationScreenState extends State<AccomodationScreen> {
width: isDesktop
? MediaQuery.of(context).size.width * 0.34
: MediaQuery.of(context).size.width * 0.66,
child: TextField(
focusNode: _commentsFocusNode,
controller: _commentsController,
maxLines: 6,
keyboardType: TextInputType.multiline,
style: TextStyle(fontSize: 12),
decoration: InputDecoration(
labelText: "Description",
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 4),
child: SizedBox(
height: 40,
child: TextField(
focusNode: _commentsFocusNode,
controller: _commentsController,
style: TextStyle(fontSize: 12),
decoration: InputDecoration(
labelText: "Comments",
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16),
),
),
),
),
],
)
),
if (isDesktop) Spacer(),
SizedBox(
height: 5,
),
Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: _handleAction(isDesktop),
),
],
),
];
}

View File

@ -199,34 +199,34 @@ class _BusScreenState extends State<BusScreen> {
bool isDesktop = sizingInfo.deviceScreenType == DeviceScreenType.desktop;
return Container(
color: Color(0xFFF4F4FB),
// color: Colors.white,
child: Form(
key: _formKey,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
Align(
alignment: Alignment.centerRight,
child: InkWell(
onTap: () {
widget.onClose(false);
},
child: Icon(
Icons.close,
size: 18,
color: Color(0xFF575A74),
),
),
),
Text("Bus Booking List",
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Color(0xFF575A74))),
SizedBox(
height: 6,
),
// Align(
// alignment: Alignment.centerRight,
// child: InkWell(
// onTap: () {
// widget.onClose(false);
// },
// child: Icon(
// Icons.close,
// size: 18,
// color: Color(0xFF575A74),
// ),
// ),
// ),
// Text("Bus Booking List",
// style: TextStyle(
// fontSize: 18,
// fontWeight: FontWeight.bold,
// color: Color(0xFF575A74))),
// SizedBox(
// height: 6,
// ),
Padding(
padding: const EdgeInsets.all(28.0),
child: Center(
@ -263,10 +263,6 @@ class _BusScreenState extends State<BusScreen> {
...buildResponsiveRow(_buildThirdRow(isDesktop)),
// Actions row remains a Row
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: _handleAction(isDesktop),
),
];
}
@ -607,26 +603,34 @@ class _BusScreenState extends State<BusScreen> {
CustomTextFieldWrapper(
isFocused: _commentsFocus, // Dropdown doesn't use focus
isDesktop: isDesktop,
width: isDesktop
? MediaQuery.of(context).size.width * 0.34
: MediaQuery.of(context).size.width * 0.66,
child: TextField(
focusNode: _commentsFocusNode,
controller: _buscommentsController,
maxLines: 6,
keyboardType: TextInputType.multiline,
style: TextStyle(fontSize: 12),
decoration: InputDecoration(
labelText: "Comments",
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 4),
width: isDesktop ? MediaQuery.of(context).size.width * 0.34 : null,
child: SizedBox(
height: 40,
child: TextField(
focusNode: _commentsFocusNode,
controller: _buscommentsController,
style: TextStyle(fontSize: 12),
decoration: InputDecoration(
labelText: "Comments",
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16),
),
),
),
),
],
)
),
if (isDesktop) Spacer(),
Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: _handleAction(isDesktop),
),
],
),
];
}

View File

@ -1,5 +1,6 @@
import 'package:dropdown_search/dropdown_search.dart';
import 'package:flutter/material.dart';
import 'package:frontend/services/apiService.dart';
import 'package:intl/intl.dart';
import 'package:responsive_builder/responsive_builder.dart';
@ -8,6 +9,7 @@ import '../../widgets/custom_text_field.dart';
import '../../widgets/custom_text_itnerary_sub.dart';
class FlightScreen extends StatefulWidget {
final List<Map<String, dynamic>> flightData;
final Map<String, dynamic>? apiData;
final String? loginUser;
final Function(bool) onClose;
@ -19,15 +21,23 @@ class FlightScreen extends StatefulWidget {
required this.loginUser,
required this.onClose,
required this.onSaveFlight,
required this.selectedItem});
required this.selectedItem,
required this.flightData});
@override
_FlightScreenState createState() => _FlightScreenState();
}
class _FlightScreenState extends State<FlightScreen> {
ApiService apiService = ApiService();
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
late ValueNotifier<String?> flightFirstTripDateNotifier;
late ValueNotifier<String?> flightLastTripDateNotifier;
String? selectedCountry;
List<Map<String, dynamic>> countryList = [];
Map<String, String?> selectedValues = {};
String? selectedTripType;
@ -60,6 +70,7 @@ class _FlightScreenState extends State<FlightScreen> {
@override
void initState() {
super.initState();
// _initializeRows();
// List<dynamic> purposeList = widget.apiData?['flight_trip_type'] ?? [];
// selectedTripType ??= purposeList.isNotEmpty ? purposeList.first['dropdown_value'] : null;
@ -109,6 +120,56 @@ class _FlightScreenState extends State<FlightScreen> {
textControllers["_time${i}Controller"]
?.addListener(() => _clearError("time_$i"));
}
// loadCountryList();
flightFirstTripDateNotifier = ValueNotifier<String?>(null);
flightLastTripDateNotifier = ValueNotifier<String?>(null);
WidgetsBinding.instance.addPostFrameCallback((_) {
final result = getFlightTripDateRange(widget.flightData);
flightFirstTripDateNotifier.value = result['firstTripDate'];
flightLastTripDateNotifier.value = result['lastTripDate'];
});
}
Map<String, String?> getFlightTripDateRange(
List<Map<String, dynamic>> flightData) {
final allTrips = flightData
.expand((flight) => flight['trips'] ?? [])
.whereType<Map<String, dynamic>>()
.toList();
if (allTrips.isEmpty) {
return {
'firstTripDate': null,
'lastTripDate': null,
};
}
allTrips.sort((a, b) {
final aDate = DateTime.tryParse(a['date'] ?? '') ?? DateTime(1900);
final bDate = DateTime.tryParse(b['date'] ?? '') ?? DateTime(1900);
return aDate.compareTo(bDate);
});
final firstTrip = allTrips.first;
final lastTrip = allTrips.last;
return {
'firstTripDate': firstTrip['date'],
'lastTripDate': lastTrip['date'],
};
}
Future<void> loadCountryList() async {
final result = await apiService.fetchFlightsCountryList();
if (result is List) {
countryList =
result.map((item) => Map<String, dynamic>.from(item)).toList();
} else {
countryList = []; // fallback or throw error
}
}
int getRowCount() {
@ -444,34 +505,15 @@ class _FlightScreenState extends State<FlightScreen> {
bool isDesktop = sizingInfo.deviceScreenType == DeviceScreenType.desktop;
return Container(
color: Color(0xFFF4F4FB),
// color: Color(0xFFF4F4FB),
// color: Color(0xFFF9F9F9), // Slightly lighter than white
child: Form(
key: _formKey,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
Align(
alignment: Alignment.centerRight,
child: InkWell(
onTap: () {
widget.onClose(false);
},
child: Icon(
Icons.close,
size: 18,
color: Color(0xFF575A74),
),
),
),
Text("Flight Booking",
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Color(0xFF575A74))),
SizedBox(
height: 6,
),
Padding(
padding: const EdgeInsets.all(28.0),
child: Center(
@ -486,13 +528,6 @@ class _FlightScreenState extends State<FlightScreen> {
});
}
// void _addNewRow() {
// setState(() {
// rowBuilders.add(_builClassType(false));
// controllers.add(TextEditingController());
// });
// }
List<Widget> _buildAccomadtionForm(bool isDesktop) {
List<Widget> buildResponsiveRow(List<Widget> children) {
return [
@ -502,14 +537,14 @@ class _FlightScreenState extends State<FlightScreen> {
}
List<List<Widget>> rowBuilders = [
_builClassType(isDesktop, 1),
// _builClassType(isDesktop, 1),
_buildSecondRow(isDesktop, 1)
];
List<List<Widget>> rowRoundBuilders = [
_builClassType(isDesktop, 1),
// _builClassType(isDesktop, 1),
_buildSecondRow(isDesktop, 1),
_builClassType(isDesktop, 2),
// _builClassType(isDesktop, 2),
_buildSecondRow(isDesktop, 2)
];
@ -527,11 +562,11 @@ class _FlightScreenState extends State<FlightScreen> {
if (selectedTripType == "Multitrip")
...List.generate(multiTripRowCount, (index) {
List<Widget> firstRow = _builClassType(isDesktop, index + 1);
// List<Widget> firstRow = _builClassType(isDesktop, index + 1);
List<Widget> secondRow = _buildSecondRow(isDesktop, index + 1);
return [
...buildResponsiveRow(firstRow), // Row 1
// ...buildResponsiveRow(firstRow), // Row 1
...buildResponsiveRow(secondRow), // Row 2
];
}).expand((row) => row),
@ -546,13 +581,12 @@ class _FlightScreenState extends State<FlightScreen> {
alignment: Alignment.centerRight,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.blueAccent,
backgroundColor: Colors.green,
foregroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
side: BorderSide(color: Colors.blueAccent, width: 2),
),
padding: EdgeInsets.symmetric(horizontal: 8, vertical: 2),
padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12),
),
onPressed: () {
setState(() {
@ -591,18 +625,14 @@ class _FlightScreenState extends State<FlightScreen> {
},
child: Text(
"Add Trip",
style: TextStyle(fontSize: 10, fontWeight: FontWeight.bold),
style: TextStyle(color: Colors.white, fontSize: 12),
),
),
),
...buildResponsiveRow(_buildvisa(isDesktop)),
// ...buildResponsiveRow(_buildvisa(isDesktop)),
...buildResponsiveRow(_buildThirdRow(isDesktop)),
// Actions row remains a Row
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: _handleAction(isDesktop),
),
];
}
@ -658,9 +688,7 @@ class _FlightScreenState extends State<FlightScreen> {
// isFocused: _tripTypeFocused,
isFocused: focusStates["_tripType1Focused"] ?? false,
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.32 : null,
child: SizedBox(
height: 40,
width: double.infinity,
@ -887,7 +915,7 @@ class _FlightScreenState extends State<FlightScreen> {
];
}
List<Widget> _builClassType(bool isDesktop, int index) {
List<Widget> _buildSecondRow(bool isDesktop, int index) {
List<dynamic> purposeList = widget.apiData?['flight_class'] ?? [];
List<DropdownMenuItem<String>> dropdownItems = purposeList
@ -910,96 +938,54 @@ class _FlightScreenState extends State<FlightScreen> {
// Default selected value
selectedClasses[index] ??=
dropdownItems.isNotEmpty ? dropdownItems.first.value : null;
// -------------------------------------------------
return [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (selectedTripType == "Multitrip")
isDesktop
?
// SizedBox(
// width: MediaQuery.of(context).size.width * 0.89,
// child:
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
// crossAxisAlignment: CrossAxisAlignment.center,
children: [
// Spacer(flex: 2),
// _buildDelete(isDesktop, index)
// Spacer(flex: 2),
..._buildDelete(isDesktop, index)
])
// )
: Row(mainAxisAlignment: MainAxisAlignment.center, children: [
// _buildDelete(isDesktop, index)
..._buildDelete(isDesktop, index)
]),
SizedBox(height: 20),
Text(
"Class $index *",
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74)),
),
SizedBox(height: 5),
CustomTextFieldWrapper(
isFocused: focusStates["_class${index}Focused"] ?? false,
isDesktop: isDesktop,
width: isDesktop
? MediaQuery.of(context).size.width * 0.34
: MediaQuery.of(context).size.width * 0.66,
child: SizedBox(
height: 40,
child: DropdownButtonFormField<String>(
focusNode: focusNodes["_class${index}FocusNode"],
// focusNode: _tripTypeFocusNode, // Assign the correct focus node
// controller: _hotelNameController,
value: selectedClasses[index],
style: TextStyle(fontSize: 12),
decoration: InputDecoration(
border: InputBorder.none,
contentPadding:
EdgeInsets.symmetric(horizontal: 10), // Proper padding
),
onChanged: purposeList.isNotEmpty
? (newValue) {
setState(() {
selectedClasses[index] = newValue;
});
print(selectedClasses[index]);
}
: null,
items: dropdownItems,
),
),
),
],
),
];
}
List<Widget> _buildSecondRow(bool isDesktop, int index) {
DateTime? _selectedCheckOutDate;
TimeOfDay? _selectedCheckOutTime;
Future<void> _selectCheckOutDate(BuildContext context) async {
DateTime now = DateTime.now();
DateTime today = DateTime(now.year, now.month, now.day);
// Determine the minimum date (firstDate) based on previous index if available
DateTime firstDate = today;
if (index == 1 &&
flightLastTripDateNotifier.value != null &&
flightLastTripDateNotifier.value!.isNotEmpty) {
try {
final tripDate = DateFormat('yyyy-MM-dd')
.parseStrict(flightLastTripDateNotifier.value!);
if (tripDate.isAfter(today)) {
firstDate = tripDate;
}
} catch (_) {
// handle parse error if needed
}
} else if (index > 1) {
final previousDateString =
textControllers["_date${index - 1}Controller"]?.text;
if (previousDateString != null && previousDateString.isNotEmpty) {
try {
final previousDate =
DateFormat('yyyy-MM-dd').parseStrict(previousDateString);
if (previousDate.isAfter(today)) {
firstDate = previousDate;
}
} catch (_) {
// handle parse error if necessary
}
}
}
DateTime initialDate = _selectedCheckOutDate != null &&
_selectedCheckOutDate!.isAfter(firstDate)
? _selectedCheckOutDate!
: firstDate;
DateTime? pickedDate = await showDatePicker(
context: context,
initialDate: _selectedCheckOutDate != null &&
_selectedCheckOutDate!.isAfter(today)
? _selectedCheckOutDate!
: today,
firstDate: today,
initialDate: initialDate,
firstDate: firstDate,
lastDate: DateTime(2100),
);
@ -1034,7 +1020,73 @@ class _FlightScreenState extends State<FlightScreen> {
}
}
late Map<String, String> countryMap; // Mapping country_code -> country_name
late List<String> countryCodes; // List of country codes
countryMap = {
for (var item in countryList)
item['country_code'] as String: item['country_name'] as String
};
// Extract only country codes for processing
countryCodes = countryMap.keys.toList();
selectedCountry ??= null;
return [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Class $index *",
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74)),
),
SizedBox(height: 5),
CustomTextFieldItnerarySubWrapper(
isFocused: focusStates["_class${index}Focused"] ?? false,
isDesktop: isDesktop,
// width: isDesktop
// ? MediaQuery.of(context).size.width * 0.34
// : MediaQuery.of(context).size.width * 0.66,
child: SizedBox(
height: 40,
child: DropdownButtonFormField<String>(
focusNode: focusNodes["_class${index}FocusNode"],
// focusNode: _tripTypeFocusNode, // Assign the correct focus node
// controller: _hotelNameController,
value: selectedClasses[index],
style: TextStyle(fontSize: 12),
decoration: InputDecoration(
border: InputBorder.none,
contentPadding:
EdgeInsets.symmetric(horizontal: 10), // Proper padding
),
onChanged: purposeList.isNotEmpty
? (newValue) {
setState(() {
selectedClasses[index] = newValue;
});
print(selectedClasses[index]);
}
: null,
items: dropdownItems,
),
),
),
],
),
if (isDesktop)
SizedBox(
width: 20,
)
else
SizedBox(
height: 8,
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
@ -1051,6 +1103,51 @@ class _FlightScreenState extends State<FlightScreen> {
isFocused: focusStates["_from${index}Focused"] ?? false,
// isFocused: focusStates["_from${fieldIndex}Focused"] ?? false,
isDesktop: isDesktop,
// child: SizedBox(
// height: 40,
// child: DropdownSearch<String>(
// selectedItem: countryMap[selectedCountry],
// popupProps: PopupProps.menu(
// showSearchBox: true, // Enables search functionality
// searchFieldProps: TextFieldProps(
// decoration: InputDecoration(
// hintText: "Search Country...",
// contentPadding: EdgeInsets.symmetric(horizontal: 10),
// ),
// ),
// ),
// items: countryMap.values.toList(),
// dropdownDecoratorProps: DropDownDecoratorProps(
// dropdownSearchDecoration: InputDecoration(
// border: InputBorder.none,
// contentPadding: EdgeInsets.symmetric(
// horizontal: 1,
// ),
// ),
// ),
// dropdownBuilder: (context, selectedItem) => Align(
// // Center-align selected item
// alignment: Alignment.centerLeft,
// child: Text(
// selectedItem ?? "Select Country",
// style: TextStyle(fontSize: 12),
// ),
// ),
// onChanged: (String? newValue) {
// setState(() {
// // Find the country_code based on selected country_name
// selectedCountry = countryMap.entries
// .firstWhere((entry) => entry.value == newValue)
// .key;
//
// if (selectedCountry!.isNotEmpty) {
// errorMessages.remove("country_code");
// }
// });
// },
// ),
// )
child: SizedBox(
height: 40,
child: TextField(
@ -1142,6 +1239,7 @@ class _FlightScreenState extends State<FlightScreen> {
CustomTextFieldItnerarySubWrapper(
isFocused: focusStates["_date${index}Focused"] ?? false,
isDesktop: isDesktop,
width: isDesktop ? MediaQuery.of(context).size.width * 0.11 : null,
child: SizedBox(
height: 40,
child: GestureDetector(
@ -1195,6 +1293,7 @@ class _FlightScreenState extends State<FlightScreen> {
CustomTextFieldItnerarySubWrapper(
isFocused: focusStates["_timeFocused"] ?? false,
isDesktop: isDesktop,
width: isDesktop ? MediaQuery.of(context).size.width * 0.08 : null,
child: SizedBox(
height: 40,
child: GestureDetector(
@ -1206,7 +1305,7 @@ class _FlightScreenState extends State<FlightScreen> {
controller: textControllers["_time${index}Controller"],
style: const TextStyle(fontSize: 12),
decoration: const InputDecoration(
labelText: "Select Time",
labelText: "Time",
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
@ -1228,11 +1327,106 @@ class _FlightScreenState extends State<FlightScreen> {
],
],
),
if (selectedTripType == "Multitrip")
Container(
// color: Colors.blueGrey,
// padding: const EdgeInsets.only(top: 50, bottom: 50),
child: IconButton(
onPressed: () {
removeTrip(index);
},
icon: Icon(
Icons.close,
color: Colors.redAccent,
size: 20,
),
),
)
];
}
List<Widget> _buildThirdRow(bool isDesktop) {
List<dynamic> visa_available =
widget.apiData?['flight_visa_available'] ?? [];
// Default selected value
List<DropdownMenuItem<String>> dropdownItems = visa_available
.map((item) => DropdownMenuItem<String>(
value: item['dropdown_key'],
child: Text(item['dropdown_value']),
))
.toList();
selectedvisa_available ??=
dropdownItems.isNotEmpty ? dropdownItems.first.value : null;
if (dropdownItems.isEmpty) {
dropdownItems.add(
DropdownMenuItem<String>(
value: null,
child: Text("No options available",
style: TextStyle(color: Colors.grey)),
),
);
}
return [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Visa Required",
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74)),
),
SizedBox(height: 5),
CustomTextFieldItnerarySubWrapper(
// isFocused: _tripTypeFocused,
isFocused: focusStates["_visa1Focused"] ?? false,
isDesktop: isDesktop,
// width: isDesktop
// ? MediaQuery.of(context).size.width * 0.34
// : MediaQuery.of(context).size.width * 0.66,
child: SizedBox(
height: 40,
child: DropdownButtonFormField<String>(
// focusNode: _tripTypeFocusNode, // Assign the correct focus node
focusNode: focusNodes["_visa1FocusNode"],
value: selectedvisa_available,
style: TextStyle(fontSize: 12),
decoration: InputDecoration(
border: InputBorder.none,
contentPadding:
EdgeInsets.symmetric(horizontal: 10), // Proper padding
),
onChanged: visa_available.isNotEmpty
? (newValue) {
setState(() {
selectedvisa_available = newValue;
// selectedTripType = "Oneway";
// Reset `multiTripRowCount` when switching away from Multitrip
});
print(
"Updating form data: Flight -> trip_type -> $selectedvisa_available");
// _initializeRows();
}
: null,
items: dropdownItems,
),
),
),
],
),
if (isDesktop)
SizedBox(
width: 20,
),
SizedBox(
height: 5,
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
@ -1247,27 +1441,37 @@ class _FlightScreenState extends State<FlightScreen> {
CustomTextFieldWrapper(
isFocused: focusStates["_comments1Focused"] ?? false,
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.32 : null,
child: TextField(
focusNode: focusNodes["_comments1FocusNode"],
// controller: _commentsController,
controller: textControllers["_comments1Controller"],
maxLines: 6,
keyboardType: TextInputType.multiline,
// maxLines: 6,
// keyboardType: TextInputType.multiline,
style: TextStyle(fontSize: 12),
decoration: InputDecoration(
labelText: "Description",
labelText: "Comments",
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 4),
// contentPadding: EdgeInsets.symmetric(vertical: 1),
),
),
),
],
)
),
if (isDesktop) Spacer(),
SizedBox(
height: 5,
),
Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: _handleAction(isDesktop),
),
],
),
];
}

View File

@ -14,6 +14,7 @@ import '../../widgets/custom_text_itnerary_sub.dart';
import 'package:http/http.dart' as http;
class ForexScreen extends StatefulWidget {
final List<Map<String, dynamic>> flightData;
final Map<String, dynamic>? apiData;
final Function(bool) onClose;
final Map<String, dynamic>? selectedItem;
@ -27,7 +28,8 @@ class ForexScreen extends StatefulWidget {
required this.selectedItem,
required this.apiCountryData,
required this.onSaveForex,
required this.loginUser});
required this.loginUser,
required this.flightData});
@override
_ForexScreenState createState() => _ForexScreenState();
@ -36,6 +38,9 @@ class ForexScreen extends StatefulWidget {
class _ForexScreenState extends State<ForexScreen> {
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
late ValueNotifier<String?> flightFirstTripDateNotifier;
late ValueNotifier<String?> flightLastTripDateNotifier;
Map<String, String?> selectedValues = {};
bool isChecked = false; // State variable for checkbox
@ -106,6 +111,7 @@ class _ForexScreenState extends State<ForexScreen> {
"deposit_on_cash": textControllers["_cash"]?.text,
"delivery_location": textControllers["_deliveryLocation"]?.text,
"comments": textControllers["_comments"]?.text,
"total": selectedQuotedAmount,
"created_by": widget.loginUser,
"updated_by": widget.loginUser,
};
@ -220,6 +226,35 @@ class _ForexScreenState extends State<ForexScreen> {
return errorMessages.isEmpty; // Valid if there are no errors
}
Map<String, String?> getFlightTripDateRange(
List<Map<String, dynamic>> flightData) {
final allTrips = flightData
.expand((flight) => flight['trips'] ?? [])
.whereType<Map<String, dynamic>>()
.toList();
if (allTrips.isEmpty) {
return {
'firstTripDate': null,
'lastTripDate': null,
};
}
allTrips.sort((a, b) {
final aDate = DateTime.tryParse(a['date'] ?? '') ?? DateTime(1900);
final bDate = DateTime.tryParse(b['date'] ?? '') ?? DateTime(1900);
return aDate.compareTo(bDate);
});
final firstTrip = allTrips.first;
final lastTrip = allTrips.last;
return {
'firstTripDate': firstTrip['date'],
'lastTripDate': lastTrip['date'],
};
}
void handleSave() {
print("Handle Save forexData $forexData");
@ -280,6 +315,30 @@ class _ForexScreenState extends State<ForexScreen> {
textControllers["_forexEndDate"]?.addListener(_onFieldChanged);
handleUpdatedField();
flightFirstTripDateNotifier = ValueNotifier<String?>(null);
flightLastTripDateNotifier = ValueNotifier<String?>(null);
WidgetsBinding.instance.addPostFrameCallback((_) {
final result = getFlightTripDateRange(widget.flightData);
flightFirstTripDateNotifier.value = result['firstTripDate'];
flightLastTripDateNotifier.value = result['lastTripDate'];
// Only set controller after value is updated
final parsedDate =
DateTime.tryParse(flightFirstTripDateNotifier.value ?? '');
if (parsedDate != null) {
textControllers["_forexStartDate"]?.text =
DateFormat('yyyy-MM-dd').format(parsedDate);
}
final parsedEndDate =
DateTime.tryParse(flightLastTripDateNotifier.value ?? '');
if (parsedEndDate != null) {
textControllers["_forexEndDate"]?.text =
DateFormat('yyyy-MM-dd').format(parsedEndDate);
}
});
}
void handleUpdatedField() {
@ -468,34 +527,13 @@ class _ForexScreenState extends State<ForexScreen> {
bool isDesktop = sizingInfo.deviceScreenType == DeviceScreenType.desktop;
return Container(
color: Color(0xFFF4F4FB),
// color: Color(0xFFF4F4FB),
child: Form(
key: _formKey,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
Align(
alignment: Alignment.centerRight,
child: InkWell(
onTap: () {
widget.onClose(false);
},
child: Icon(
Icons.close,
size: 18,
color: Color(0xFF575A74),
),
),
),
Text("Forex List",
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Color(0xFF575A74))),
SizedBox(
height: 6,
),
Padding(
padding: const EdgeInsets.all(28.0),
child: Center(
@ -532,48 +570,43 @@ class _ForexScreenState extends State<ForexScreen> {
return [
...buildResponsiveRow(_buildFirstRow(isDesktop)),
SizedBox(
height: 28,
height: 5,
),
Text(
"Forex Details",
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
color: Color(0xFF575A74)),
Align(
alignment: Alignment.centerLeft,
child: Text(
"Forex Details",
style: TextStyle(
fontSize: 13,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74)),
),
),
// SizedBox(
// height: 8,
// ),
Divider(
thickness: 0.3,
),
SizedBox(
height: 8,
height: 3,
),
Divider(),
SizedBox(
height: 8,
),
...buildResponsiveRow(_builClassType(isDesktop)),
SizedBox(
height: 8,
height: 3,
),
Divider(
thickness: 0.3,
),
Divider(),
SizedBox(
height: 28,
height: 10,
),
...buildResponsiveRow(_buildSecondRow(isDesktop)),
...buildResponsiveRow(_buildCardDetailsRow(isDesktop)),
...buildResponsiveRow(_buildFprexCard(isDesktop)),
...buildResponsiveRow(_buildThirdRow(isDesktop)),
...buildResponsiveRow(_buildCommetsRow(isDesktop)),
// Actions row remains a Row
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: _handleAction(isDesktop),
),
];
}
@ -585,16 +618,43 @@ class _ForexScreenState extends State<ForexScreen> {
DateTime now = DateTime.now();
DateTime today = DateTime(now.year, now.month, now.day);
DateTime? pickedDate = await showDatePicker(
// Parse date from notifier if available, else use today
DateTime initialDate;
if (flightFirstTripDateNotifier.value != null) {
try {
initialDate = DateTime.parse(flightFirstTripDateNotifier.value!);
textControllers["_forexStartDate"]?.text =
DateFormat('yyyy-MM-dd').format(initialDate);
} catch (e) {
initialDate = today;
}
} else {
initialDate = today;
}
// Use previously selected date if valid
if (_selectedCheckOutDate != null &&
_selectedCheckOutDate!.isAfter(today)) {
initialDate = _selectedCheckOutDate!;
}
final pickedDate = await showDatePicker(
context: context,
initialDate: _selectedCheckOutDate != null &&
_selectedCheckOutDate!.isAfter(today)
? _selectedCheckOutDate!
: today,
firstDate: today,
initialDate: initialDate,
firstDate: initialDate,
lastDate: DateTime(2100),
);
// DateTime? pickedDate = await showDatePicker(
// context: context,
// initialDate: _selectedCheckOutDate != null &&
// _selectedCheckOutDate!.isAfter(today)
// ? _selectedCheckOutDate!
// : today,
// firstDate: today,
// lastDate: DateTime(2100),
// );
if (pickedDate != null && pickedDate != _selectedCheckOutDate) {
setState(() {
_selectedCheckOutDate = pickedDate;
@ -610,16 +670,41 @@ class _ForexScreenState extends State<ForexScreen> {
DateTime now = DateTime.now();
DateTime today = DateTime(now.year, now.month, now.day);
DateTime? pickedDate = await showDatePicker(
// Parse date from notifier if available, else use today
DateTime initialDate;
if (flightLastTripDateNotifier.value != null) {
try {
initialDate = DateTime.parse(flightLastTripDateNotifier.value!);
} catch (e) {
initialDate = today;
}
} else {
initialDate = today;
}
// Use previously selected date if valid
if (_selectedCheckOutDate != null &&
_selectedCheckOutDate!.isAfter(today)) {
initialDate = _selectedCheckOutDate!;
}
final pickedDate = await showDatePicker(
context: context,
initialDate:
_selectedEndDate != null && _selectedEndDate!.isAfter(today)
? _selectedEndDate!
: today,
firstDate: today,
initialDate: initialDate,
firstDate: initialDate,
lastDate: DateTime(2100),
);
// DateTime? pickedDate = await showDatePicker(
// context: context,
// initialDate:
// _selectedEndDate != null && _selectedEndDate!.isAfter(today)
// ? _selectedEndDate!
// : today,
// firstDate: today,
// lastDate: DateTime(2100),
// );
if (pickedDate != null && pickedDate != _selectedEndDate) {
setState(() {
_selectedEndDate = pickedDate;
@ -657,7 +742,7 @@ class _ForexScreenState extends State<ForexScreen> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Forex Start Date",
"Start Date",
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
@ -736,7 +821,7 @@ class _ForexScreenState extends State<ForexScreen> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Forex End Date",
"End Date",
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
@ -898,7 +983,7 @@ class _ForexScreenState extends State<ForexScreen> {
isDesktop: isDesktop,
color: Colors.transparent,
child: SizedBox(
height: 40,
height: 30,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
@ -906,7 +991,7 @@ class _ForexScreenState extends State<ForexScreen> {
// selectedDuration?.isNotEmpty == true ? selectedDuration! : "Duration",
selectedDuration ?? "Duration",
style: const TextStyle(
fontSize: 18,
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74)),
// decoration: const InputDecoration(
@ -950,7 +1035,7 @@ class _ForexScreenState extends State<ForexScreen> {
? MediaQuery.of(context).size.width * 0.330
: MediaQuery.of(context).size.width * 0.66,
child: SizedBox(
height: 40,
height: 30,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Center(
@ -960,7 +1045,7 @@ class _ForexScreenState extends State<ForexScreen> {
selectedCurrency ?? "Currency",
// selectedCurrency?.isNotEmpty == true ? selectedCurrency! : "Currency",
style: const TextStyle(
fontSize: 18,
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74)),
),
@ -993,7 +1078,7 @@ class _ForexScreenState extends State<ForexScreen> {
isDesktop: isDesktop,
color: Colors.transparent,
child: SizedBox(
height: 40,
height: 30,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
@ -1001,7 +1086,7 @@ class _ForexScreenState extends State<ForexScreen> {
selectedPerdiemAmount ?? "Amount",
// selectedPerdiemAmount?.isNotEmpty == true ? selectedPerdiemAmount! : "Amount",
style: const TextStyle(
fontSize: 18,
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74)),
// decoration: const InputDecoration(
@ -1186,7 +1271,7 @@ class _ForexScreenState extends State<ForexScreen> {
// focusNode: _toFocusNode,
// controller: _toController,
style: const TextStyle(
fontSize: 18,
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74)),
// decoration: const InputDecoration(
@ -1358,7 +1443,7 @@ class _ForexScreenState extends State<ForexScreen> {
selectedQuotedAmount ?? "0",
// selectedPerdiemAmount?.isNotEmpty == true ? selectedPerdiemAmount! : "Amount",
style: const TextStyle(
fontSize: 18,
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74)),
),
@ -1524,26 +1609,38 @@ class _ForexScreenState extends State<ForexScreen> {
isFocused:
focusStates["_comments"] ?? false, // Dropdown doesn't use focus
isDesktop: isDesktop,
width: isDesktop
? MediaQuery.of(context).size.width * 0.330
: MediaQuery.of(context).size.width * 0.66,
child: TextField(
focusNode: focusNodes["_comments"],
controller: textControllers["_comments"],
maxLines: 3,
keyboardType: TextInputType.multiline,
style: TextStyle(fontSize: 12),
decoration: InputDecoration(
labelText: "Comments",
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 4),
width: isDesktop ? MediaQuery.of(context).size.width * 0.34 : null,
child: SizedBox(
height: 35,
child: TextField(
focusNode: focusNodes["_comments"],
controller: textControllers["_comments"],
style: TextStyle(fontSize: 12),
decoration: InputDecoration(
labelText: "Comments",
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16),
),
),
),
),
],
)
),
if (isDesktop) Spacer(),
SizedBox(
height: 5,
),
// Actions row remains a Row
Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: _handleAction(isDesktop),
),
],
),
];
}

View File

@ -6,6 +6,7 @@ import '../../widgets/custom_text_field.dart';
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(Map<String, dynamic>) onSaveInsurance;
@ -17,7 +18,8 @@ class InsuranceScreen extends StatefulWidget {
required this.apiData,
required this.onSaveInsurance,
required this.selectedItem,
required this.loginUser});
required this.loginUser,
required this.flightData});
@override
_InsuranceScreenState createState() => _InsuranceScreenState();
@ -28,6 +30,9 @@ class _InsuranceScreenState extends State<InsuranceScreen> {
Map<String, String?> selectedValues = {};
late ValueNotifier<String?> flightFirstTripDateNotifier;
late ValueNotifier<String?> flightLastTripDateNotifier;
final FocusNode _tripTypeFocusNode = FocusNode();
final FocusNode _hotelNameFocusNode = FocusNode();
final FocusNode _fromFocusNode = FocusNode();
@ -105,6 +110,58 @@ class _InsuranceScreenState extends State<InsuranceScreen> {
selectedInsuranceType =
widget.selectedItem!["type_of_insurance"].toString();
}
flightFirstTripDateNotifier = ValueNotifier<String?>(null);
flightLastTripDateNotifier = ValueNotifier<String?>(null);
WidgetsBinding.instance.addPostFrameCallback((_) {
final result = getFlightTripDateRange(widget.flightData);
flightFirstTripDateNotifier.value = result['firstTripDate'];
flightLastTripDateNotifier.value = result['lastTripDate'];
// Only set controller after value is updated
final parsedDate =
DateTime.tryParse(flightFirstTripDateNotifier.value ?? '');
if (parsedDate != null) {
_startdateController.text = DateFormat('yyyy-MM-dd').format(parsedDate);
}
final parsedEndDate =
DateTime.tryParse(flightLastTripDateNotifier.value ?? '');
if (parsedEndDate != null) {
_endDateController.text =
DateFormat('yyyy-MM-dd').format(parsedEndDate);
}
});
}
Map<String, String?> getFlightTripDateRange(
List<Map<String, dynamic>> flightData) {
final allTrips = flightData
.expand((flight) => flight['trips'] ?? [])
.whereType<Map<String, dynamic>>()
.toList();
if (allTrips.isEmpty) {
return {
'firstTripDate': null,
'lastTripDate': null,
};
}
allTrips.sort((a, b) {
final aDate = DateTime.tryParse(a['date'] ?? '') ?? DateTime(1900);
final bDate = DateTime.tryParse(b['date'] ?? '') ?? DateTime(1900);
return aDate.compareTo(bDate);
});
final firstTrip = allTrips.first;
final lastTrip = allTrips.last;
return {
'firstTripDate': firstTrip['date'],
'lastTripDate': lastTrip['date'],
};
}
void _addFocusListener(FocusNode node, Function(bool) updateState) {
@ -132,6 +189,25 @@ class _InsuranceScreenState extends State<InsuranceScreen> {
}
}
// Additional validation: checkout_date >= checkin_date
final checkIn = data["start_date"];
final checkOut = data["end_date"];
if (checkIn != null &&
checkOut != null &&
checkIn.toString().isNotEmpty &&
checkOut.toString().isNotEmpty) {
try {
final checkInDate = DateTime.parse(checkIn);
final checkOutDate = DateTime.parse(checkOut);
if (checkOutDate.isBefore(checkInDate)) {
errorMessages["end_date"] = "EndDate date cannot be before StartDate";
}
} catch (e) {
errorMessages["end_date"] = "Invalid date format";
}
}
return errorMessages.isEmpty; // Valid if there are no errors
}
@ -175,34 +251,13 @@ class _InsuranceScreenState extends State<InsuranceScreen> {
bool isDesktop = sizingInfo.deviceScreenType == DeviceScreenType.desktop;
return Container(
color: Color(0xFFF4F4FB),
// color: Color(0xFFF4F4FB),
child: Form(
key: _formKey,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
Align(
alignment: Alignment.centerRight,
child: InkWell(
onTap: () {
widget.onClose(false);
},
child: Icon(
Icons.close,
size: 18,
color: Color(0xFF575A74),
),
),
),
Text("Insurance Booking List",
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Color(0xFF575A74))),
SizedBox(
height: 6,
),
Padding(
padding: const EdgeInsets.all(28.0),
child: Center(
@ -231,7 +286,7 @@ class _InsuranceScreenState extends State<InsuranceScreen> {
];
return [
...buildResponsiveRow(_buildFirstRow(isDesktop)),
// ...buildResponsiveRow(_buildFirstRow(isDesktop)),
// Iterate over rowBuilders and wrap each in a responsive container
...rowBuilders.expand((row) => buildResponsiveRow(row)),
@ -239,10 +294,6 @@ class _InsuranceScreenState extends State<InsuranceScreen> {
...buildResponsiveRow(_buildThirdRow(isDesktop)),
// Actions row remains a Row
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: _handleAction(isDesktop),
),
];
}
@ -338,6 +389,33 @@ class _InsuranceScreenState extends State<InsuranceScreen> {
}
List<Widget> _buildSecondRow(bool isDesktop) {
List<dynamic> purposeList =
widget.apiData?['insurance_type_of_insurance'] ?? [];
// selectedInsuranceType = purposeList.isNotEmpty ? purposeList.first['dropdown_value'] : null;
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
selectedInsuranceType ??=
dropdownItems.isNotEmpty ? dropdownItems.first.value : null;
// ----------------------------------------------------------------
DateTime? _selectedCheckOutDate;
TimeOfDay? _selectedCheckOutTime;
@ -345,16 +423,41 @@ class _InsuranceScreenState extends State<InsuranceScreen> {
DateTime now = DateTime.now();
DateTime today = DateTime(now.year, now.month, now.day);
DateTime? pickedDate = await showDatePicker(
// Parse date from notifier if available, else use today
DateTime initialDate;
if (flightFirstTripDateNotifier.value != null) {
try {
initialDate = DateTime.parse(flightFirstTripDateNotifier.value!);
} catch (e) {
initialDate = today;
}
} else {
initialDate = today;
}
// Use previously selected date if valid
if (_selectedCheckOutDate != null &&
_selectedCheckOutDate!.isAfter(today)) {
initialDate = _selectedCheckOutDate!;
}
final pickedDate = await showDatePicker(
context: context,
initialDate: _selectedCheckOutDate != null &&
_selectedCheckOutDate!.isAfter(today)
? _selectedCheckOutDate!
: today,
firstDate: today,
initialDate: initialDate,
firstDate: initialDate,
lastDate: DateTime(2100),
);
// DateTime? pickedDate = await showDatePicker(
// context: context,
// initialDate: _selectedCheckOutDate != null &&
// _selectedCheckOutDate!.isAfter(today)
// ? _selectedCheckOutDate!
// : today,
// firstDate: today,
// lastDate: DateTime(2100),
// );
if (pickedDate != null && pickedDate != _selectedCheckOutDate) {
setState(() {
_selectedCheckOutDate = pickedDate;
@ -368,16 +471,43 @@ class _InsuranceScreenState extends State<InsuranceScreen> {
DateTime now = DateTime.now();
DateTime today = DateTime(now.year, now.month, now.day);
DateTime? pickedDate = await showDatePicker(
DateTime? checkInDate;
try {
checkInDate = DateTime.parse(_startdateController.text);
} catch (e) {
checkInDate = today;
}
// // Ensure at least today is used
// DateTime firstDate = checkInDate.isAfter(today) ? checkInDate : today;
// DateTime initialDate = _selectedCheckOutDate != null &&
// _selectedCheckOutDate!.isAfter(firstDate)
// ? _selectedCheckOutDate!
// : firstDate;
DateTime firstDate = checkInDate;
DateTime initialDate = _selectedCheckOutDate != null &&
_selectedCheckOutDate!.isAfter(firstDate)
? _selectedCheckOutDate!
: firstDate;
final pickedDate = await showDatePicker(
context: context,
initialDate: _selectedCheckOutDate != null &&
_selectedCheckOutDate!.isAfter(today)
? _selectedCheckOutDate!
: today,
firstDate: today,
initialDate: initialDate,
firstDate: firstDate,
lastDate: DateTime(2100),
);
// DateTime? pickedDate = await showDatePicker(
// context: context,
// initialDate: _selectedCheckOutDate != null &&
// _selectedCheckOutDate!.isAfter(today)
// ? _selectedCheckOutDate!
// : today,
// firstDate: today,
// lastDate: DateTime(2100),
// );
if (pickedDate != null && pickedDate != _selectedCheckOutDate) {
setState(() {
_selectedCheckOutDate = pickedDate;
@ -387,6 +517,57 @@ class _InsuranceScreenState extends State<InsuranceScreen> {
}
return [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Insurance Type",
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74)),
),
SizedBox(height: 5),
CustomTextFieldWrapper(
isFocused: _isHotelNameFocused,
isDesktop: isDesktop,
width: isDesktop ? MediaQuery.of(context).size.width * 0.34 : null,
child: SizedBox(
height: 40,
child: DropdownButtonFormField<String>(
focusNode: _tripTypeFocusNode, // Assign the correct focus node
value: selectedInsuranceType,
style: TextStyle(fontSize: 12),
decoration: InputDecoration(
border: InputBorder.none,
contentPadding:
EdgeInsets.symmetric(horizontal: 10), // Proper padding
),
onChanged: purposeList.isNotEmpty
? (newValue) {
setState(() {
selectedInsuranceType = newValue;
if (selectedInsuranceType!.isNotEmpty) {
errorMessages.remove("type_of_insurance");
}
});
print(selectedInsuranceType);
}
: null,
items: dropdownItems,
),
),
),
],
),
if (isDesktop)
Spacer()
else
SizedBox(
height: 8,
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
@ -401,9 +582,7 @@ class _InsuranceScreenState extends State<InsuranceScreen> {
CustomTextFieldWrapper(
isFocused: _dateFocus,
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.15 : null,
child: SizedBox(
height: 40,
child: GestureDetector(
@ -464,9 +643,7 @@ class _InsuranceScreenState extends State<InsuranceScreen> {
CustomTextFieldWrapper(
isFocused: _dateFocus,
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.15 : null,
child: SizedBox(
height: 40,
child: GestureDetector(
@ -548,23 +725,36 @@ class _InsuranceScreenState extends State<InsuranceScreen> {
width: isDesktop
? MediaQuery.of(context).size.width * 0.34
: MediaQuery.of(context).size.width * 0.66,
child: TextField(
focusNode: _commentsFocusNode,
controller: _insuranceCommentsController,
maxLines: 6,
keyboardType: TextInputType.multiline,
style: TextStyle(fontSize: 12),
decoration: InputDecoration(
labelText: "Description",
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 4),
child: SizedBox(
height: 40,
child: TextField(
focusNode: _commentsFocusNode,
controller: _insuranceCommentsController,
style: TextStyle(fontSize: 12),
decoration: InputDecoration(
labelText: "Comments",
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16),
),
),
),
),
],
)
),
if (isDesktop) Spacer(),
SizedBox(
height: 5,
),
Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: _handleAction(isDesktop),
),
],
),
];
}

View File

@ -147,35 +147,12 @@ class _MiscellaneousScreenState extends State<MiscellaneousScreen> {
bool isDesktop = sizingInfo.deviceScreenType == DeviceScreenType.desktop;
return Container(
color: Color(0xFFF4F4FB),
child: Form(
key: _formKey,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
Align(
alignment: Alignment.centerRight,
child: InkWell(
onTap: () {
_commentsController.clear();
widget.onClose(false);
},
child: Icon(
Icons.close,
size: 18,
color: Color(0xFF575A74),
),
),
),
Text("Miscellaneous Booking List",
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Color(0xFF575A74))),
SizedBox(
height: 6,
),
Padding(
padding: const EdgeInsets.all(28.0),
child: Center(
@ -204,10 +181,6 @@ class _MiscellaneousScreenState extends State<MiscellaneousScreen> {
...buildResponsiveRow(_buildThirdRow(isDesktop)),
// Actions row remains a Row
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: _handleAction(isDesktop),
),
];
}
@ -229,12 +202,6 @@ class _MiscellaneousScreenState extends State<MiscellaneousScreen> {
: Column(children: _buildTripType(isDesktop))
],
),
if (isDesktop)
Spacer()
else
SizedBox(
height: 8,
),
];
}
@ -324,18 +291,19 @@ class _MiscellaneousScreenState extends State<MiscellaneousScreen> {
width: isDesktop
? MediaQuery.of(context).size.width * 0.34
: MediaQuery.of(context).size.width * 0.66,
child: TextField(
focusNode: _commentsFocusNode,
controller: _commentsController,
maxLines: 6,
keyboardType: TextInputType.multiline,
style: TextStyle(fontSize: 12),
decoration: InputDecoration(
labelText: "Comments",
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 4),
child: SizedBox(
height: 40,
child: TextField(
focusNode: _commentsFocusNode,
controller: _commentsController,
style: TextStyle(fontSize: 12),
decoration: InputDecoration(
labelText: "Comments",
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16),
),
),
),
),
@ -347,7 +315,15 @@ class _MiscellaneousScreenState extends State<MiscellaneousScreen> {
),
],
],
)
),
if (isDesktop) Spacer(),
SizedBox(
height: 5,
),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: _handleAction(isDesktop),
),
];
}

View File

@ -203,34 +203,13 @@ class _TaxiScreenState extends State<TaxiScreen> {
bool isDesktop = sizingInfo.deviceScreenType == DeviceScreenType.desktop;
return Container(
color: Color(0xFFF4F4FB),
// color: Color(0xFFF4F4FB),
child: Form(
key: _formKey,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
Align(
alignment: Alignment.centerRight,
child: InkWell(
onTap: () {
widget.onClose(false);
},
child: Icon(
Icons.close,
size: 18,
color: Color(0xFF575A74),
),
),
),
Text("Taxi Booking List",
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Color(0xFF575A74))),
SizedBox(
height: 6,
),
Padding(
padding: const EdgeInsets.all(28.0),
child: Center(
@ -267,10 +246,6 @@ class _TaxiScreenState extends State<TaxiScreen> {
...buildResponsiveRow(_buildThirdRow(isDesktop)),
// Actions row remains a Row
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: _handleAction(isDesktop),
),
];
}
@ -738,23 +713,36 @@ class _TaxiScreenState extends State<TaxiScreen> {
? MediaQuery.of(context).size.width * 0.34
: MediaQuery.of(context).size.width * 0.66,
child: TextField(
focusNode: _commentsFocusNode,
controller: _taxiCommentsController,
maxLines: 6,
keyboardType: TextInputType.multiline,
style: TextStyle(fontSize: 12),
decoration: InputDecoration(
labelText: "Description",
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 4),
child: SizedBox(
height: 40,
child: TextField(
focusNode: _commentsFocusNode,
controller: _taxiCommentsController,
style: TextStyle(fontSize: 12),
decoration: InputDecoration(
labelText: "Comments",
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16),
),
),
),
),
],
)
),
if (isDesktop) Spacer(),
SizedBox(
height: 5,
),
Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: _handleAction(isDesktop),
),
],
),
];
}

View File

@ -212,34 +212,34 @@ class _TrainScreenState extends State<TrainScreen> {
bool isDesktop = sizingInfo.deviceScreenType == DeviceScreenType.desktop;
return Container(
color: Color(0xFFF4F4FB),
// color: Color(0xFFF4F4FB),
child: Form(
key: _formKey,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
Align(
alignment: Alignment.centerRight,
child: InkWell(
onTap: () {
widget.onClose(false);
},
child: Icon(
Icons.close,
size: 18,
color: Color(0xFF575A74),
),
),
),
Text("Train Booking List",
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Color(0xFF575A74))),
SizedBox(
height: 6,
),
// Align(
// alignment: Alignment.centerRight,
// child: InkWell(
// onTap: () {
// widget.onClose(false);
// },
// child: Icon(
// Icons.close,
// size: 18,
// color: Color(0xFF575A74),
// ),
// ),
// ),
// Text("Train Booking List",
// style: TextStyle(
// fontSize: 18,
// fontWeight: FontWeight.bold,
// color: Color(0xFF575A74))),
// SizedBox(
// height: 6,
// ),
Padding(
padding: const EdgeInsets.all(28.0),
child: Center(
@ -276,10 +276,6 @@ class _TrainScreenState extends State<TrainScreen> {
...buildResponsiveRow(_buildThirdRow(isDesktop)),
// Actions row remains a Row
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: _handleAction(isDesktop),
),
];
}
@ -345,9 +341,7 @@ class _TrainScreenState extends State<TrainScreen> {
CustomTextFieldWrapper(
isFocused: _trainNoFocused,
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.32 : null,
child: SizedBox(
height: 40,
child: TextField(
@ -368,80 +362,7 @@ class _TrainScreenState extends State<TrainScreen> {
}
List<Widget> _builClassType(bool isDesktop) {
List<dynamic> purposeList = widget.apiData?['train_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(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Class *",
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74)),
),
SizedBox(height: 5),
CustomTextFieldWrapper(
isFocused: _isHotelNameFocused,
isDesktop: isDesktop,
width: isDesktop
? MediaQuery.of(context).size.width * 0.34
: MediaQuery.of(context).size.width * 0.66,
child: SizedBox(
height: 40,
child: DropdownButtonFormField<String>(
focusNode: _hotelNameFocusNode, // Assign the correct focus node
// controller: _hotelNameController,
value: selectedClass,
style: TextStyle(fontSize: 12),
decoration: InputDecoration(
border: InputBorder.none,
contentPadding:
EdgeInsets.symmetric(horizontal: 10), // Proper padding
),
onChanged: purposeList.isNotEmpty
? (newValue) {
setState(() {
selectedClass = newValue;
});
}
: null,
items: dropdownItems,
),
),
),
if (errorMessages["class"] != null) ...[
SizedBox(height: 5), // Space before error message
Text(
"Required",
style: TextStyle(color: Colors.red, fontSize: 12),
),
],
],
),
];
return [];
}
List<Widget> _buildSecondRow(bool isDesktop) {
@ -490,7 +411,87 @@ class _TrainScreenState extends State<TrainScreen> {
}
}
//----------------------------------------------
List<dynamic> purposeList = widget.apiData?['train_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(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Class *",
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74)),
),
SizedBox(height: 5),
CustomTextFieldItnerarySubWrapper(
isFocused: _isHotelNameFocused,
isDesktop: isDesktop,
// width: isDesktop
// ? MediaQuery.of(context).size.width * 0.34
// : MediaQuery.of(context).size.width * 0.66,
child: SizedBox(
height: 40,
child: DropdownButtonFormField<String>(
focusNode: _hotelNameFocusNode, // Assign the correct focus node
// controller: _hotelNameController,
value: selectedClass,
style: TextStyle(fontSize: 12),
decoration: InputDecoration(
border: InputBorder.none,
contentPadding:
EdgeInsets.symmetric(horizontal: 10), // Proper padding
),
onChanged: purposeList.isNotEmpty
? (newValue) {
setState(() {
selectedClass = newValue;
});
}
: null,
items: dropdownItems,
),
),
),
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,
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
@ -595,6 +596,7 @@ class _TrainScreenState extends State<TrainScreen> {
CustomTextFieldItnerarySubWrapper(
isFocused: _dateFocus,
isDesktop: isDesktop,
width: isDesktop ? MediaQuery.of(context).size.width * 0.11 : null,
child: SizedBox(
height: 40,
child: GestureDetector(
@ -647,6 +649,7 @@ class _TrainScreenState extends State<TrainScreen> {
CustomTextFieldItnerarySubWrapper(
isFocused: _timeFocus,
isDesktop: isDesktop,
width: isDesktop ? MediaQuery.of(context).size.width * 0.08 : null,
child: SizedBox(
height: 40,
child: GestureDetector(
@ -698,25 +701,38 @@ class _TrainScreenState extends State<TrainScreen> {
CustomTextFieldWrapper(
isFocused: _commentsFocus, // Dropdown doesn't use focus
isDesktop: isDesktop,
width: isDesktop
? MediaQuery.of(context).size.width * 0.34
: MediaQuery.of(context).size.width * 0.66,
child: TextField(
focusNode: _commentsFocusNode,
controller: _trainCommentsController,
maxLines: 6,
keyboardType: TextInputType.multiline,
style: TextStyle(fontSize: 12),
decoration: InputDecoration(
labelText: "Comments",
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 4),
width: isDesktop ? MediaQuery.of(context).size.width * 0.32 : null,
child: SizedBox(
height: 40,
child: TextField(
focusNode: _commentsFocusNode,
controller: _trainCommentsController,
// maxLines: 6,
// keyboardType: TextInputType.multiline,
style: TextStyle(fontSize: 12),
decoration: InputDecoration(
labelText: "Comments",
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16),
),
),
),
),
],
),
if (isDesktop) Spacer(),
SizedBox(
height: 5,
),
Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: _handleAction(isDesktop),
),
],
)
];
}

View File

@ -7,6 +7,7 @@ import '../../widgets/custom_text_field.dart';
import '../../widgets/custom_text_itnerary_sub.dart';
class VisaScreen extends StatefulWidget {
final List<Map<String, dynamic>> flightData;
final Map<String, dynamic>? apiData;
final List<dynamic>? apiCountryData;
@ -21,7 +22,8 @@ class VisaScreen extends StatefulWidget {
this.apiData,
required this.selectedItem,
required this.apiCountryData,
required this.loginUser});
required this.loginUser,
required this.flightData});
@override
_VisaScreenState createState() => _VisaScreenState();
@ -30,6 +32,9 @@ class VisaScreen extends StatefulWidget {
class _VisaScreenState extends State<VisaScreen> {
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
late ValueNotifier<String?> flightFirstTripDateNotifier;
late ValueNotifier<String?> flightLastTripDateNotifier;
Map<String, String?> selectedValues = {};
List<dynamic> countryList = [];
@ -105,6 +110,22 @@ class _VisaScreenState extends State<VisaScreen> {
// selectedPurpose = widget.selectedItem!["selectedCountry"].toString();
selectedCountry = widget.selectedItem!["country_code"] as String?;
}
flightFirstTripDateNotifier = ValueNotifier<String?>(null);
flightLastTripDateNotifier = ValueNotifier<String?>(null);
WidgetsBinding.instance.addPostFrameCallback((_) {
final result = getFlightTripDateRange(widget.flightData);
flightFirstTripDateNotifier.value = result['firstTripDate'];
flightLastTripDateNotifier.value = result['lastTripDate'];
// Only set controller after value is updated
final parsedDate =
DateTime.tryParse(flightFirstTripDateNotifier.value ?? '');
if (parsedDate != null) {
_dateController.text = DateFormat('yyyy-MM-dd').format(parsedDate);
}
});
}
void _addFocusListener(FocusNode node, Function(bool) updateState) {
@ -115,6 +136,35 @@ class _VisaScreenState extends State<VisaScreen> {
});
}
Map<String, String?> getFlightTripDateRange(
List<Map<String, dynamic>> flightData) {
final allTrips = flightData
.expand((flight) => flight['trips'] ?? [])
.whereType<Map<String, dynamic>>()
.toList();
if (allTrips.isEmpty) {
return {
'firstTripDate': null,
'lastTripDate': null,
};
}
allTrips.sort((a, b) {
final aDate = DateTime.tryParse(a['date'] ?? '') ?? DateTime(1900);
final bDate = DateTime.tryParse(b['date'] ?? '') ?? DateTime(1900);
return aDate.compareTo(bDate);
});
final firstTrip = allTrips.first;
final lastTrip = allTrips.last;
return {
'firstTripDate': firstTrip['date'],
'lastTripDate': lastTrip['date'],
};
}
@override
void dispose() {
_tripTypeFocusNode.dispose();
@ -171,34 +221,12 @@ class _VisaScreenState extends State<VisaScreen> {
bool isDesktop = sizingInfo.deviceScreenType == DeviceScreenType.desktop;
return Container(
color: Color(0xFFF4F4FB),
child: Form(
key: _formKey,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
Align(
alignment: Alignment.centerRight,
child: InkWell(
onTap: () {
widget.onClose(false);
},
child: Icon(
Icons.close,
size: 18,
color: Color(0xFF575A74),
),
),
),
Text("Visa Registration",
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Color(0xFF575A74))),
SizedBox(
height: 6,
),
Padding(
padding: const EdgeInsets.all(28.0),
child: Center(
@ -232,38 +260,11 @@ class _VisaScreenState extends State<VisaScreen> {
...buildResponsiveRow(_buildThirdRow(isDesktop)),
// Actions row remains a Row
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: _handleAction(isDesktop),
),
];
}
List<Widget> _buildFirstRow(isDesktop) {
return [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Type of Visa",
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74)),
),
SizedBox(height: 5),
isDesktop
? Row(children: _buildTripType(isDesktop))
: 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),
),
],
],
),
if (isDesktop)
Spacer()
else
@ -274,62 +275,7 @@ class _VisaScreenState extends State<VisaScreen> {
}
List<Widget> _buildTripType(bool isDesktop) {
List<dynamic> purposeList = widget.apiData?['visa_type_of_visa'] ?? [];
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
selectedPurpose ??=
dropdownItems.isNotEmpty ? dropdownItems.first.value : null;
return [
CustomTextFieldWrapper(
isFocused: _tripTypeFocused,
isDesktop: isDesktop,
width: isDesktop
? MediaQuery.of(context).size.width * 0.34
: MediaQuery.of(context).size.width * 0.66,
child: SizedBox(
height: 40,
child: DropdownButtonFormField<String>(
focusNode: _tripTypeFocusNode, // Assign the correct focus node
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;
});
print(
"Updating form data: Flight -> trip_type -> ${newValue ?? ""}");
}
: null,
items: dropdownItems,
),
),
),
];
return [];
}
List<Widget> _buildSecondRow(bool isDesktop) {
@ -380,16 +326,41 @@ class _VisaScreenState extends State<VisaScreen> {
DateTime now = DateTime.now();
DateTime today = DateTime(now.year, now.month, now.day);
DateTime? pickedDate = await showDatePicker(
// Parse date from notifier if available, else use today
DateTime initialDate;
if (flightFirstTripDateNotifier.value != null) {
try {
initialDate = DateTime.parse(flightFirstTripDateNotifier.value!);
} catch (e) {
initialDate = today;
}
} else {
initialDate = today;
}
// Use previously selected date if valid
if (_selectedCheckOutDate != null &&
_selectedCheckOutDate!.isAfter(today)) {
initialDate = _selectedCheckOutDate!;
}
final pickedDate = await showDatePicker(
context: context,
initialDate: _selectedCheckOutDate != null &&
_selectedCheckOutDate!.isAfter(today)
? _selectedCheckOutDate!
: today,
firstDate: today,
initialDate: initialDate,
firstDate: initialDate,
lastDate: DateTime(2100),
);
// DateTime? pickedDate = await showDatePicker(
// context: context,
// initialDate: _selectedCheckOutDate != null &&
// _selectedCheckOutDate!.isAfter(today)
// ? _selectedCheckOutDate!
// : today,
// firstDate: today,
// lastDate: DateTime(2100),
// );
if (pickedDate != null && pickedDate != _selectedCheckOutDate) {
setState(() {
_selectedCheckOutDate = pickedDate;
@ -398,7 +369,88 @@ class _VisaScreenState extends State<VisaScreen> {
}
}
// ---------------------
List<dynamic> purposeList = widget.apiData?['visa_type_of_visa'] ?? [];
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
selectedPurpose ??=
dropdownItems.isNotEmpty ? dropdownItems.first.value : null;
return [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"Type of Visa",
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Color(0xFF575A74)),
),
SizedBox(height: 5),
CustomTextFieldWrapper(
isFocused: _tripTypeFocused,
isDesktop: isDesktop,
width: isDesktop
? MediaQuery.of(context).size.width * 0.34
: MediaQuery.of(context).size.width * 0.66,
child: SizedBox(
height: 40,
child: DropdownButtonFormField<String>(
focusNode: _tripTypeFocusNode, // Assign the correct focus node
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;
});
print(
"Updating form data: Flight -> trip_type -> ${newValue ?? ""}");
}
: null,
items: dropdownItems,
),
),
),
if (errorMessages["type_of_visa"] != null) ...[
SizedBox(height: 5), // Space before error message
Text(
"Required",
style: TextStyle(color: Colors.red, fontSize: 12),
),
],
],
),
if (isDesktop)
Spacer()
else
SizedBox(
height: 8,
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
@ -413,9 +465,7 @@ class _VisaScreenState extends State<VisaScreen> {
CustomTextFieldWrapper(
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.15 : null,
child: SizedBox(
height: 40,
child: DropdownSearch<String>(
@ -490,9 +540,7 @@ class _VisaScreenState extends State<VisaScreen> {
CustomTextFieldWrapper(
isFocused: _dateFocus,
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.15 : null,
child: SizedBox(
height: 40,
child: GestureDetector(
@ -554,23 +602,36 @@ class _VisaScreenState extends State<VisaScreen> {
width: isDesktop
? MediaQuery.of(context).size.width * 0.34
: MediaQuery.of(context).size.width * 0.66,
child: TextField(
focusNode: _commentsFocusNode,
controller: _visaCommentsController,
maxLines: 6,
keyboardType: TextInputType.multiline,
style: TextStyle(fontSize: 12),
decoration: InputDecoration(
labelText: "Comments",
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 4),
child: SizedBox(
height: 40,
child: TextField(
focusNode: _commentsFocusNode,
controller: _visaCommentsController,
style: TextStyle(fontSize: 12),
decoration: InputDecoration(
labelText: "Comments",
labelStyle: TextStyle(fontSize: 12, color: Colors.grey),
floatingLabelBehavior: FloatingLabelBehavior.never,
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(vertical: 16),
),
),
),
),
],
)
),
if (isDesktop) Spacer(),
SizedBox(
height: 5,
),
Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: _handleAction(isDesktop),
),
],
),
];
}

View File

@ -9,13 +9,14 @@ class AccomodationListWidget extends StatelessWidget {
final Function(String, bool) onAddNew;
final bool isViewMode;
const AccomodationListWidget(
{super.key,
required this.accommodationList,
required this.onOpen,
required this.onDeleteAccommodation,
required this.onAddNew,
required this.isViewMode});
const AccomodationListWidget({
super.key,
required this.accommodationList,
required this.onOpen,
required this.onDeleteAccommodation,
required this.onAddNew,
required this.isViewMode,
});
@override
Widget build(BuildContext context) {
@ -24,6 +25,7 @@ class AccomodationListWidget extends StatelessWidget {
return Padding(
padding: const EdgeInsets.all(16.0),
child: Container(
margin: const EdgeInsets.only(top: 16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
@ -31,28 +33,17 @@ class AccomodationListWidget extends StatelessWidget {
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"Accomodation Booking List",
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
// Text(
// "Accomodation Booking List",
// style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
// ),
MouseRegion(
cursor: isViewMode
? SystemMouseCursors.forbidden
: SystemMouseCursors.click,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Color(0xFF114D8B),
foregroundColor: Colors.white,
disabledBackgroundColor: Color(0xFF114D8B),
disabledForegroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
side: BorderSide(color: Color(0xFF114D8B), width: 2),
),
padding:
EdgeInsets.symmetric(horizontal: 20, vertical: 12),
),
onPressed: isViewMode
child: GestureDetector(
onTap: isViewMode
? null
: () {
print("New data");
@ -62,20 +53,73 @@ class AccomodationListWidget extends StatelessWidget {
mainAxisSize:
MainAxisSize.min, // Ensures content fits nicely
children: [
Text(
"Add New",
style: TextStyle(fontSize: 13),
),
SizedBox(width: 8), // spacing between icon and text
// Text(
// "Add New",
// style: TextStyle(fontSize: 13),
// ),
// SizedBox(width: 8), // spacing between icon and text
Icon(
Icons.add_circle_outline_rounded,
size: 15,
color: Colors.white,
),
Icons.add_circle_sharp,
size: 30,
color: Color(0xFF114D8B),
)
],
),
),
// onPressed: isViewMode
// ? null
// : () {
// print("New data");
//
// },
// child: Row(
// mainAxisSize: MainAxisSize.min,
// children: [
// Icon(
// Icons.add_circle_sharp,
// size: 30,
// color: Color(0xFF114D8B),
// )
// ],
// ),
),
// MouseRegion(
// cursor: isViewMode
// ? SystemMouseCursors.forbidden
// : SystemMouseCursors.click,
// child: ElevatedButton(
// style: ElevatedButton.styleFrom(
// backgroundColor: Color(0xFF114D8B),
// foregroundColor: Colors.white,
// disabledBackgroundColor: Color(0xFF114D8B),
// disabledForegroundColor: Colors.white,
// shape: RoundedRectangleBorder(
// borderRadius: BorderRadius.circular(8),
// side: BorderSide(color: Color(0xFF114D8B), width: 2),
// ),
// padding:
// EdgeInsets.symmetric(horizontal: 20, vertical: 12),
// ),
// onPressed: isViewMode
// ? null
// : () {
// print("New data");
// onAddNew("Accomodation", true);
// },
// child: Row(
// mainAxisSize:
// MainAxisSize.min, // Ensures content fits nicely
// children: [
// // spacing between icon and text
// Icon(
// Icons.add_circle_sharp,
// size: 30,
// color: Color(0xFF114D8B),
// )
// ],
// ),
// ),
// ),
],
),
const SizedBox(height: 16),

View File

@ -31,6 +31,7 @@ class BusListWidget extends StatelessWidget {
return Padding(
padding: const EdgeInsets.all(16.0),
child: Container(
margin: const EdgeInsets.only(top: 16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
@ -38,28 +39,12 @@ class BusListWidget extends StatelessWidget {
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"Bus Booking List",
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
MouseRegion(
cursor: isViewMode
? SystemMouseCursors.forbidden
: SystemMouseCursors.click,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Color(0xFF114D8B),
foregroundColor: Colors.white,
disabledBackgroundColor: Color(0xFF114D8B),
disabledForegroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
side: BorderSide(color: Color(0xFF114D8B), width: 2),
),
padding:
EdgeInsets.symmetric(horizontal: 20, vertical: 12),
),
onPressed: isViewMode
child: GestureDetector(
onTap: isViewMode
? null
: () {
print("New data");
@ -69,19 +54,50 @@ class BusListWidget extends StatelessWidget {
mainAxisSize:
MainAxisSize.min, // Ensures content fits nicely
children: [
Text(
"Add New",
style: TextStyle(fontSize: 13),
),
SizedBox(width: 8), // spacing between icon and text
// Text(
// "Add New",
// style: TextStyle(fontSize: 13),
// ),
// SizedBox(width: 8), // spacing between icon and text
Icon(
Icons.add_circle_outline_rounded,
size: 15,
color: Colors.white,
),
Icons.add_circle_sharp,
size: 30,
color: Color(0xFF114D8B),
)
],
),
),
// child: ElevatedButton(
// style: ElevatedButton.styleFrom(
// backgroundColor: Color(0xFF114D8B),
// foregroundColor: Colors.white,
// disabledBackgroundColor: Color(0xFF114D8B),
// disabledForegroundColor: Colors.white,
// shape: RoundedRectangleBorder(
// borderRadius: BorderRadius.circular(8),
// side: BorderSide(color: Color(0xFF114D8B), width: 2),
// ),
// padding:
// EdgeInsets.symmetric(horizontal: 20, vertical: 12),
// ),
// onPressed: isViewMode
// ? null
// : () {
// print("New data");
// onAddNew("Bus", true);
// },
// child: Row(
// mainAxisSize:
// MainAxisSize.min, // Ensures content fits nicely
// children: [
// Icon(
// Icons.add_circle_sharp,
// size: 30,
// color: Color(0xFF114D8B),
// )
// ],
// ),
// ),
),
],
),

View File

@ -26,6 +26,7 @@ class FlightListWidget extends StatelessWidget {
return Padding(
padding: const EdgeInsets.all(16.0),
child: Container(
margin: const EdgeInsets.only(top: 16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
@ -33,28 +34,16 @@ class FlightListWidget extends StatelessWidget {
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"Flight Booking List",
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
// Text(
// "Flight Booking List",
// style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
// ),
MouseRegion(
cursor: isViewMode
? SystemMouseCursors.forbidden
: SystemMouseCursors.click,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Color(0xFF114D8B),
foregroundColor: Colors.white,
disabledBackgroundColor: Color(0xFF114D8B),
disabledForegroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
side: BorderSide(color: Color(0xFF114D8B), width: 2),
),
padding:
EdgeInsets.symmetric(horizontal: 20, vertical: 12),
),
onPressed: isViewMode
child: GestureDetector(
onTap: isViewMode
? null
: () {
print("New data");
@ -64,16 +53,16 @@ class FlightListWidget extends StatelessWidget {
mainAxisSize:
MainAxisSize.min, // Ensures content fits nicely
children: [
Text(
"Add New",
style: TextStyle(fontSize: 13),
),
SizedBox(width: 8), // spacing between icon and text
// Text(
// "Add New",
// style: TextStyle(fontSize: 13),
// ),
// SizedBox(width: 8), // spacing between icon and text
Icon(
Icons.add_circle_outline_rounded,
size: 15,
color: Colors.white,
),
Icons.add_circle_sharp,
size: 30,
color: Color(0xFF114D8B),
)
],
),
),

View File

@ -30,6 +30,7 @@ class ForexListWidget extends StatelessWidget {
return Padding(
padding: const EdgeInsets.all(16.0),
child: Container(
margin: const EdgeInsets.only(top: 16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
@ -37,28 +38,13 @@ class ForexListWidget extends StatelessWidget {
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"Forex List",
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
MouseRegion(
cursor: isViewMode
? SystemMouseCursors.forbidden
: SystemMouseCursors.click,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Color(0xFF114D8B),
foregroundColor: Colors.white,
disabledBackgroundColor: Color(0xFF114D8B),
disabledForegroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
side: BorderSide(color: Color(0xFF114D8B), width: 2),
),
padding:
EdgeInsets.symmetric(horizontal: 20, vertical: 12),
),
onPressed: isViewMode
child: GestureDetector(
onTap: isViewMode
? null
: () {
print("New data");
@ -68,20 +54,72 @@ class ForexListWidget extends StatelessWidget {
mainAxisSize:
MainAxisSize.min, // Ensures content fits nicely
children: [
Text(
"Add New",
style: TextStyle(fontSize: 13),
),
SizedBox(width: 8), // spacing between icon and text
// Text(
// "Add New",
// style: TextStyle(fontSize: 13),
// ),
// SizedBox(width: 8), // spacing between icon and text
Icon(
Icons.add_circle_outline_rounded,
size: 15,
color: Colors.white,
),
Icons.add_circle_sharp,
size: 30,
color: Color(0xFF114D8B),
)
],
),
),
// onPressed: isViewMode
// ? null
// : () {
// print("New data");
//
// },
// child: Row(
// mainAxisSize: MainAxisSize.min,
// children: [
// Icon(
// Icons.add_circle_sharp,
// size: 30,
// color: Color(0xFF114D8B),
// )
// ],
// ),
),
// MouseRegion(
// cursor: isViewMode
// ? SystemMouseCursors.forbidden
// : SystemMouseCursors.click,
// child: ElevatedButton(
// style: ElevatedButton.styleFrom(
// backgroundColor: Color(0xFF114D8B),
// foregroundColor: Colors.white,
// disabledBackgroundColor: Color(0xFF114D8B),
// disabledForegroundColor: Colors.white,
// shape: RoundedRectangleBorder(
// borderRadius: BorderRadius.circular(8),
// side: BorderSide(color: Color(0xFF114D8B), width: 2),
// ),
// padding:
// EdgeInsets.symmetric(horizontal: 20, vertical: 12),
// ),
// onPressed: isViewMode
// ? null
// : () {
// print("New data");
// onAddNew("Forex", true);
// },
// child: Row(
// mainAxisSize:
// MainAxisSize.min, // Ensures content fits nicely
// children: [
// Icon(
// Icons.add_circle_sharp,
// size: 30,
// color: Color(0xFF114D8B),
// )
// ],
// ),
// ),
// ),
],
),
const SizedBox(height: 16),

View File

@ -28,6 +28,7 @@ class InsuranceListWidget extends StatelessWidget {
return Padding(
padding: const EdgeInsets.all(16.0),
child: Container(
margin: const EdgeInsets.only(top: 16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
@ -35,28 +36,12 @@ class InsuranceListWidget extends StatelessWidget {
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"Insurance Booking List",
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
MouseRegion(
cursor: isViewMode
? SystemMouseCursors.forbidden
: SystemMouseCursors.click,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Color(0xFF114D8B),
foregroundColor: Colors.white,
disabledBackgroundColor: Color(0xFF114D8B),
disabledForegroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
side: BorderSide(color: Color(0xFF114D8B), width: 2),
),
padding:
EdgeInsets.symmetric(horizontal: 20, vertical: 12),
),
onPressed: isViewMode
child: GestureDetector(
onTap: isViewMode
? null
: () {
print("New data");
@ -66,19 +51,50 @@ class InsuranceListWidget extends StatelessWidget {
mainAxisSize:
MainAxisSize.min, // Ensures content fits nicely
children: [
Text(
"Add New",
style: TextStyle(fontSize: 13),
),
SizedBox(width: 8), // spacing between icon and text
// Text(
// "Add New",
// style: TextStyle(fontSize: 13),
// ),
// SizedBox(width: 8), // spacing between icon and text
Icon(
Icons.add_circle_outline_rounded,
size: 15,
color: Colors.white,
),
Icons.add_circle_sharp,
size: 30,
color: Color(0xFF114D8B),
)
],
),
),
// child: ElevatedButton(
// style: ElevatedButton.styleFrom(
// backgroundColor: Color(0xFF114D8B),
// foregroundColor: Colors.white,
// disabledBackgroundColor: Color(0xFF114D8B),
// disabledForegroundColor: Colors.white,
// shape: RoundedRectangleBorder(
// borderRadius: BorderRadius.circular(8),
// side: BorderSide(color: Color(0xFF114D8B), width: 2),
// ),
// padding:
// EdgeInsets.symmetric(horizontal: 20, vertical: 12),
// ),
// onPressed: isViewMode
// ? null
// : () {
// print("New data");
// onAddNew("Insurance", true);
// },
// child: Row(
// mainAxisSize:
// MainAxisSize.min, // Ensures content fits nicely
// children: [
// Icon(
// Icons.add_circle_sharp,
// size: 30,
// color: Color(0xFF114D8B),
// )
// ],
// ),
// ),
),
],
),

View File

@ -24,6 +24,7 @@ class MiscellaneousListWidget extends StatelessWidget {
return Padding(
padding: const EdgeInsets.all(16.0),
child: Container(
margin: const EdgeInsets.only(top: 16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
@ -31,42 +32,63 @@ class MiscellaneousListWidget extends StatelessWidget {
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"Miscellaneous List",
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
MouseRegion(
cursor: isViewMode
? SystemMouseCursors.forbidden
: SystemMouseCursors.click,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Color(0xFF114D8B),
foregroundColor: Colors.white,
disabledBackgroundColor: Color(0xFF114D8B),
disabledForegroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
side: BorderSide(color: Color(0xFF114D8B), width: 2),
),
padding:
EdgeInsets.symmetric(horizontal: 20, vertical: 12),
),
onPressed: isViewMode
child: GestureDetector(
onTap: isViewMode
? null
: () {
print("New data");
onAddNew("Miscellaneous", true);
},
child: Row(
mainAxisSize: MainAxisSize.min,
mainAxisSize:
MainAxisSize.min, // Ensures content fits nicely
children: [
Text("Add New", style: TextStyle(fontSize: 13)),
SizedBox(width: 8),
Icon(Icons.add_circle_outline_rounded,
size: 15, color: Colors.white),
// Text(
// "Add New",
// style: TextStyle(fontSize: 13),
// ),
// SizedBox(width: 8), // spacing between icon and text
Icon(
Icons.add_circle_sharp,
size: 30,
color: Color(0xFF114D8B),
)
],
),
),
// child: ElevatedButton(
// style: ElevatedButton.styleFrom(
// backgroundColor: Color(0xFF114D8B),
// foregroundColor: Colors.white,
// disabledBackgroundColor: Color(0xFF114D8B),
// disabledForegroundColor: Colors.white,
// shape: RoundedRectangleBorder(
// borderRadius: BorderRadius.circular(8),
// side: BorderSide(color: Color(0xFF114D8B), width: 2),
// ),
// padding:
// EdgeInsets.symmetric(horizontal: 20, vertical: 12),
// ),
// onPressed: isViewMode
// ? null
// : () {
// onAddNew("Miscellaneous", true);
// },
// child: Row(
// mainAxisSize: MainAxisSize.min,
// children: [
// Icon(
// Icons.add_circle_sharp,
// size: 30,
// color: Color(0xFF114D8B),
// )
// ],
// ),
// ),
),
],
),

View File

@ -26,6 +26,7 @@ class TaxiListWidget extends StatelessWidget {
return Padding(
padding: const EdgeInsets.all(16.0),
child: Container(
margin: const EdgeInsets.only(top: 16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
@ -33,28 +34,13 @@ class TaxiListWidget extends StatelessWidget {
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"Taxi Booking List",
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
MouseRegion(
cursor: isViewMode
? SystemMouseCursors.forbidden
: SystemMouseCursors.click,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Color(0xFF114D8B),
foregroundColor: Colors.white,
disabledBackgroundColor: Color(0xFF114D8B),
disabledForegroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
side: BorderSide(color: Color(0xFF114D8B), width: 2),
),
padding:
EdgeInsets.symmetric(horizontal: 20, vertical: 12),
),
onPressed: isViewMode
child: GestureDetector(
onTap: isViewMode
? null
: () {
print("New data");
@ -64,19 +50,50 @@ class TaxiListWidget extends StatelessWidget {
mainAxisSize:
MainAxisSize.min, // Ensures content fits nicely
children: [
Text(
"Add New",
style: TextStyle(fontSize: 13),
),
SizedBox(width: 8), // spacing between icon and text
// Text(
// "Add New",
// style: TextStyle(fontSize: 13),
// ),
// SizedBox(width: 8), // spacing between icon and text
Icon(
Icons.add_circle_outline_rounded,
size: 15,
color: Colors.white,
),
Icons.add_circle_sharp,
size: 30,
color: Color(0xFF114D8B),
)
],
),
),
// child: ElevatedButton(
// style: ElevatedButton.styleFrom(
// backgroundColor: Color(0xFF114D8B),
// foregroundColor: Colors.white,
// disabledBackgroundColor: Color(0xFF114D8B),
// disabledForegroundColor: Colors.white,
// shape: RoundedRectangleBorder(
// borderRadius: BorderRadius.circular(8),
// side: BorderSide(color: Color(0xFF114D8B), width: 2),
// ),
// padding:
// EdgeInsets.symmetric(horizontal: 20, vertical: 12),
// ),
// onPressed: isViewMode
// ? null
// : () {
// print("New data");
// onAddNew("Taxi", true);
// },
// child: Row(
// mainAxisSize:
// MainAxisSize.min, // Ensures content fits nicely
// children: [
// Icon(
// Icons.add_circle_sharp,
// size: 30,
// color: Color(0xFF114D8B),
// )
// ],
// ),
// ),
),
],
),

View File

@ -26,6 +26,7 @@ class TrainListWidget extends StatelessWidget {
return Padding(
padding: const EdgeInsets.all(16.0),
child: Container(
margin: const EdgeInsets.only(top: 16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
@ -33,50 +34,87 @@ class TrainListWidget extends StatelessWidget {
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"Train Booking List",
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
MouseRegion(
cursor: isViewMode
? SystemMouseCursors.forbidden
: SystemMouseCursors.click,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Color(0xFF114D8B),
foregroundColor: Colors.white,
disabledBackgroundColor: Color(0xFF114D8B),
disabledForegroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
side: BorderSide(color: Color(0xFF114D8B), width: 2),
child: MouseRegion(
cursor: isViewMode
? SystemMouseCursors.forbidden
: SystemMouseCursors.click,
child: GestureDetector(
onTap: isViewMode
? null
: () {
print("New data");
onAddNew("Train", true);
},
child: Row(
mainAxisSize:
MainAxisSize.min, // Ensures content fits nicely
children: [
// Text(
// "Add New",
// style: TextStyle(fontSize: 13),
// ),
// SizedBox(width: 8), // spacing between icon and text
Icon(
Icons.add_circle_sharp,
size: 30,
color: Color(0xFF114D8B),
)
],
),
padding:
EdgeInsets.symmetric(horizontal: 20, vertical: 12),
),
onPressed: isViewMode
? null
: () {
print("New data");
onAddNew("Train", true);
},
child: Row(
mainAxisSize:
MainAxisSize.min, // Ensures content fits nicely
children: [
Text(
"Add New",
style: TextStyle(fontSize: 13),
),
SizedBox(width: 8), // spacing between icon and text
Icon(
Icons.add_circle_outline_rounded,
size: 15,
color: Colors.white,
),
],
),
// onPressed: isViewMode
// ? null
// : () {
// print("New data");
//
// },
// child: Row(
// mainAxisSize: MainAxisSize.min,
// children: [
// Icon(
// Icons.add_circle_sharp,
// size: 30,
// color: Color(0xFF114D8B),
// )
// ],
// ),
),
// child: ElevatedButton(
// style: ElevatedButton.styleFrom(
// backgroundColor: Color(0xFF114D8B),
// foregroundColor: Colors.white,
// disabledBackgroundColor: Color(0xFF114D8B),
// disabledForegroundColor: Colors.white,
// shape: RoundedRectangleBorder(
// borderRadius: BorderRadius.circular(8),
// side: BorderSide(color: Color(0xFF114D8B), width: 2),
// ),
// padding:
// EdgeInsets.symmetric(horizontal: 20, vertical: 12),
// ),
// onPressed: isViewMode
// ? null
// : () {
// print("New data");
// onAddNew("Train", true);
// },
// child: Row(
// mainAxisSize:
// MainAxisSize.min, // Ensures content fits nicely
// children: [
// Icon(
// Icons.add_circle_sharp,
// size: 30,
// color: Color(0xFF114D8B),
// )
// ],
// ),
// ),
),
],
),

View File

@ -28,6 +28,7 @@ class VisaListWidget extends StatelessWidget {
return Padding(
padding: const EdgeInsets.all(16.0),
child: Container(
margin: const EdgeInsets.only(top: 16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
@ -35,43 +36,51 @@ class VisaListWidget extends StatelessWidget {
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"Visa List",
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
MouseRegion(
cursor: isViewMode
? SystemMouseCursors.forbidden
: SystemMouseCursors.click,
child: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Color(0xFF114D8B),
foregroundColor: Colors.white,
disabledBackgroundColor: Color(0xFF114D8B),
disabledForegroundColor: Colors.white,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
side: BorderSide(color: Color(0xFF114D8B), width: 2),
),
padding:
EdgeInsets.symmetric(horizontal: 20, vertical: 12),
),
onPressed: isViewMode
child: GestureDetector(
onTap: isViewMode
? null
: () {
print("New data");
onAddNew("Visa", true);
},
child: Row(
mainAxisSize: MainAxisSize.min,
mainAxisSize:
MainAxisSize.min, // Ensures content fits nicely
children: [
Text("Add New", style: TextStyle(fontSize: 13)),
SizedBox(width: 8),
Icon(Icons.add_circle_outline_rounded,
size: 15, color: Colors.white),
// Text(
// "Add New",
// style: TextStyle(fontSize: 13),
// ),
// SizedBox(width: 8), // spacing between icon and text
Icon(
Icons.add_circle_sharp,
size: 30,
color: Color(0xFF114D8B),
)
],
),
),
// onPressed: isViewMode
// ? null
// : () {
// print("New data");
//
// },
// child: Row(
// mainAxisSize: MainAxisSize.min,
// children: [
// Icon(
// Icons.add_circle_sharp,
// size: 30,
// color: Color(0xFF114D8B),
// )
// ],
// ),
),
],
),

File diff suppressed because it is too large Load Diff

View File

@ -75,19 +75,6 @@ class _DynamicItineraryState extends State<DynamicItinerary> {
"Forex": [],
};
// Store form values for each tab
final Map<String, Map<String, String>> formData = {
"Flight": {}, // Stores data for the Flight tab
"Train": {}, // Stores data for the Train tab
"Taxi": {}, // Stores data for the Car tab
"Bus": {}, // Stores data for the Bus tab
"Insurance": {}, // Stores data for the Bus tab
"Accomodation": {}, // Stores data for the Accomodation tab
"Miscellaneous": {},
"Forex": {},
"Visa": {},
};
@override
void initState() {
super.initState();
@ -101,7 +88,7 @@ class _DynamicItineraryState extends State<DynamicItinerary> {
setState(() {
selectedAllServices = result;
});
print("Fetched services: $selectedAllServices");
print("Fetched services Order: $selectedAllServices");
} catch (e) {
print('Error fetching role list: $e');
}
@ -161,8 +148,12 @@ class _DynamicItineraryState extends State<DynamicItinerary> {
selectedIds.contains(service['service_id'].toString()))
.toList();
// setState(() {
// ServicesChoosed = [...originalFiltered, ...additionalServices];
// });
setState(() {
ServicesChoosed = [...originalFiltered, ...additionalServices];
ServicesChoosed = [...originalFiltered, ...additionalServices]
..sort((a, b) => (a['order'] ?? 0).compareTo(b['order'] ?? 0));
});
print(
@ -177,9 +168,14 @@ class _DynamicItineraryState extends State<DynamicItinerary> {
.toList();
setState(() {
ServicesChoosed = filtered;
ServicesChoosed = filtered
..sort((a, b) => (a['order'] ?? 0).compareTo(b['order'] ?? 0));
});
// setState(() {
// ServicesChoosed = filtered;
// });
print("Filtered Selected Services Chooesed: $ServicesChoosed");
}
}
@ -287,21 +283,6 @@ class _DynamicItineraryState extends State<DynamicItinerary> {
Widget selectedWidget;
Widget selectedListWidget;
// void updateFormData(String tab, String key, String value) {
// setState(() {
// formData[tab]![key] = value; // Update the stored data
// });
// }
void updateFormData(String tab, String key, String value) {
setState(() {
if (!formData.containsKey(tab)) {
formData[tab] = {}; // Initialize if null
}
formData[tab]![key] = value; // Update the stored data
});
}
void handleItineraryUpdate(String type, Map<String, dynamic> newData) {
setState(() {
if (!itineraryData.containsKey(type)) {
@ -488,12 +469,13 @@ class _DynamicItineraryState extends State<DynamicItinerary> {
break;
case "Accomodation":
selectedListWidget = AccomodationListWidget(
accommodationList: itineraryData["Accomodation"]!,
onOpen: handleEdit,
onAddNew: handlecreateNewPlan,
isViewMode: widget.isViewMode,
onDeleteAccommodation: (data) =>
handleItinerarydelete("Accomodation", data));
accommodationList: itineraryData["Accomodation"]!,
onOpen: handleEdit,
onAddNew: handlecreateNewPlan,
isViewMode: widget.isViewMode,
onDeleteAccommodation: (data) =>
handleItinerarydelete("Accomodation", data),
);
break;
case "Miscellaneous":
selectedListWidget = MiscellaneousListWidget(
@ -545,11 +527,13 @@ class _DynamicItineraryState extends State<DynamicItinerary> {
break;
case "Insurance":
selectedWidget = InsuranceScreen(
onClose: handleClose,
apiData: widget.apiData,
loginUser: widget.loginUser,
onSaveInsurance: (data) => handleItineraryUpdate("Insurance", data),
selectedItem: selectedItem);
onClose: handleClose,
apiData: widget.apiData,
loginUser: widget.loginUser,
onSaveInsurance: (data) => handleItineraryUpdate("Insurance", data),
selectedItem: selectedItem,
flightData: itineraryData["Flight"]!,
);
break;
case "Visa":
@ -560,6 +544,7 @@ class _DynamicItineraryState extends State<DynamicItinerary> {
loginUser: widget.loginUser,
onSaveVisa: (data) => handleItineraryUpdate("Visa", data),
selectedItem: selectedItem,
flightData: itineraryData["Flight"]!,
);
break;
case "Miscellaneous":
@ -580,66 +565,169 @@ class _DynamicItineraryState extends State<DynamicItinerary> {
onSaveAccomadation: (data) =>
handleItineraryUpdate("Accomodation", data),
selectedItem: selectedItem,
flightData: itineraryData["Flight"]!,
);
break;
case "Forex":
selectedWidget = ForexScreen(
onClose: handleClose,
apiData: widget.apiData,
loginUser: widget.loginUser,
apiCountryData: widget.apiCountryData,
onSaveForex: (data) => handleItineraryUpdate("Forex", data),
selectedItem: selectedItem);
onClose: handleClose,
apiData: widget.apiData,
loginUser: widget.loginUser,
apiCountryData: widget.apiCountryData,
onSaveForex: (data) => handleItineraryUpdate("Forex", data),
selectedItem: selectedItem,
flightData: itineraryData["Flight"]!,
);
break;
case "Flight":
default:
selectedWidget = FlightScreen(
onClose: handleClose,
loginUser: widget.loginUser,
onSaveFlight: (data) => handleItineraryUpdate("Flight", data),
apiData: widget.apiData,
selectedItem: selectedItem);
onClose: handleClose,
loginUser: widget.loginUser,
onSaveFlight: (data) => handleItineraryUpdate("Flight", data),
apiData: widget.apiData,
selectedItem: selectedItem,
flightData: itineraryData["Flight"]!,
);
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;
bool isDesktop = sizingInfo.deviceScreenType == DeviceScreenType.desktop;
return Column(
// mainAxisSize: MainAxisSize.min,
return Stack(
clipBehavior: Clip.none,
children: [
// Second container (yellow box)
Container(
margin: EdgeInsets.only(
top: 40), // Push it down to make room for the tab bar
padding: EdgeInsets.all(12),
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(color: Color(0xFFF4F4FB), width: 2)),
borderRadius: BorderRadius.circular(1),
// color: Color(0xFFF4F4FB),
color: Colors.white, // Card background
// color: Colors.yellow.shade50, // Card background
// color: Color(0xFFF9F9F9), // Slightly lighter than white
// border: Border.all(color: Color(0xFFE6E7F5), width: 1.3),
border: Border.all(color: Color(0xFFE6E7F5), width: 1.2),
borderRadius: BorderRadius.circular(8),
boxShadow: [
BoxShadow(
// color: Color(0x0D000000), // 5% opacity black
color: Colors.black12, // 5% opacity black
blurRadius: 5,
offset: Offset(0, 0.2),
),
],
),
// padding: EdgeInsets.all(10),
child: isMobile
? Expanded(
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
SizedBox(height: 2),
isSelected ? selectedWidget : selectedListWidget,
],
),
),
// First container (tab bar) positioned above
Positioned(
top: 0,
left: MediaQuery.of(context).size.width * 0.05,
right: MediaQuery.of(context).size.width * 0.05,
child: Container(
padding: EdgeInsets.symmetric(vertical: 10, horizontal: 12),
decoration: BoxDecoration(
color: Colors.white, // Card background
borderRadius: BorderRadius.circular(8),
// color: Color(0xFFE6E7F5)
// border: Border.all(color: Colors.black12, width: 1.3),
border: Border.all(color: Color(0xFFE6E7F5), width: 1.2),
boxShadow: [
BoxShadow(
color: Colors.black12,
// color: Color(0x0D000000), // 5% opacity black
blurRadius: 10,
offset: Offset(0, 0.2),
),
],
),
child: isMobile
? SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
children: _buildOptions(),
),
)
: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: _buildOptions(),
),
)
: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
// mainAxisSize: MainAxisSize.min,
children: _buildOptions(),
),
),
Column(
// mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
SizedBox(height: 2),
isSelected ? selectedWidget : selectedListWidget,
// TrainScreen()
],
),
),
],
);
@ -703,7 +791,7 @@ class _DynamicItineraryState extends State<DynamicItinerary> {
),
),
padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 10),
child: Row(
child: Column(
children: [
iconUrl.isNotEmpty
? Image.network(
@ -713,7 +801,7 @@ class _DynamicItineraryState extends State<DynamicItinerary> {
errorBuilder: (context, error, stackTrace) {
return Icon(
fallbackIcon,
size: 18,
size: 25,
color: isOptionSelected
? Color(0xFF114D8B)
: Color(0xFF475569),
@ -722,24 +810,30 @@ class _DynamicItineraryState extends State<DynamicItinerary> {
)
: Icon(
fallbackIcon,
size: 18,
size: 25,
color: isOptionSelected
? Color(0xFF114D8B)
: Color(0xFF475569),
),
SizedBox(width: 2),
Text(
name,
style: TextStyle(
fontSize: 14,
color: isOptionSelected ? Color(0xFF114D8B) : Color(0xFF475569),
fontFamily: "Archivo",
fontWeight:
isOptionSelected ? FontWeight.bold : FontWeight.w500,
),
SizedBox(height: 2),
Row(
children: [
Text(
name,
style: TextStyle(
fontSize: 14,
color: isOptionSelected
? Color(0xFF114D8B)
: Color(0xFF475569),
fontFamily: "Archivo",
fontWeight:
isOptionSelected ? FontWeight.bold : FontWeight.w500,
),
),
SizedBox(width: 4),
if (hasData) Icon(Icons.circle, size: 8, color: Colors.green),
],
),
SizedBox(width: 4),
if (hasData) Icon(Icons.circle, size: 8, color: Colors.green),
],
),
),

View File

@ -24,6 +24,9 @@ class ListPlans extends StatefulWidget {
class _ListPlansState extends State<ListPlans> {
final ApiService apiService = ApiService();
int currentPage = 0;
int itemsPerPage = 8;
late Future<List<Plan>> futurePlans;
String? userId;
String? orgId;
@ -285,7 +288,7 @@ class _ListPlansState extends State<ListPlans> {
margin: isDesktop
? EdgeInsets.all(10.0)
: EdgeInsets.only(left: 20.0, bottom: 20.0, top: 10.0, right: 20.0),
padding: const EdgeInsets.all(10),
// padding: const EdgeInsets.all(10),
height: isDesktop
? MediaQuery.of(context).size.height * 0.98
: MediaQuery.of(context).size.height,
@ -305,7 +308,7 @@ class _ListPlansState extends State<ListPlans> {
child: Padding(
padding: const EdgeInsets.all(1.0),
child: Container(
padding: const EdgeInsets.all(10.0),
// padding: const EdgeInsets.all(10.0),
color: Colors.white,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
@ -356,6 +359,7 @@ class _ListPlansState extends State<ListPlans> {
),
),
// SizedBox(width: 16),
Spacer(),
// ElevatedButton(
// style: ElevatedButton.styleFrom(
@ -486,6 +490,11 @@ class _ListPlansState extends State<ListPlans> {
plans.sort((a, b) =>
int.parse(b.planId).compareTo(int.parse(a.planId)));
List<Plan> paginatedPlans = plans
.skip(currentPage * itemsPerPage)
.take(itemsPerPage)
.toList();
Widget table = LayoutBuilder(
builder: (context, constraints) {
double minWidth = isDesktop ? constraints.maxWidth : 1300;
@ -508,13 +517,13 @@ class _ListPlansState extends State<ListPlans> {
fontFamily: "Archivo",
fontWeight: FontWeight.bold))),
DataColumn(
label: Text('UserName',
label: Text('Trip Name',
style: TextStyle(
color: Color(0xFF9E9DBD),
fontFamily: "Archivo",
fontWeight: FontWeight.bold))),
DataColumn(
label: Text('Trip Title',
label: Text('UserName',
style: TextStyle(
color: Color(0xFF9E9DBD),
fontFamily: "Archivo",
@ -544,21 +553,13 @@ class _ListPlansState extends State<ListPlans> {
fontFamily: "Archivo",
fontWeight: FontWeight.bold))),
],
rows: plans.map((plan) {
rows: paginatedPlans.map((plan) {
return DataRow(cells: [
DataCell(Text(plan.planId,
style: TextStyle(
fontSize: 13,
fontFamily: "Archivo",
))),
DataCell(Text(
plan.userName.isNotEmpty
? plan.userName
: plan.travellerName,
style: TextStyle(
fontSize: 13,
fontFamily: "Archivo",
))),
DataCell(Text(plan.tripTitle,
style: TextStyle(
fontSize: 13,
@ -566,6 +567,14 @@ class _ListPlansState extends State<ListPlans> {
),
softWrap: true,
overflow: TextOverflow.ellipsis)),
DataCell(Text(
plan.userName.isNotEmpty
? plan.userName
: plan.travellerName,
style: TextStyle(
fontSize: 13,
fontFamily: "Archivo",
))),
DataCell(Text(plan.tripType,
style: TextStyle(
fontSize: 13,
@ -663,15 +672,66 @@ class _ListPlansState extends State<ListPlans> {
);
return Expanded(
child: isDesktop
? table
: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: SingleChildScrollView(
scrollDirection: Axis.vertical,
child: table,
child: Column(
children: [
isDesktop
? table
: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: SingleChildScrollView(
scrollDirection: Axis.vertical,
child: table,
),
),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
// Previous button with arrow icon
IconButton(
icon: Icon(
Icons.arrow_back_ios_new,
size: 10,
),
onPressed: currentPage > 0
? () {
setState(() {
currentPage--;
});
}
: null,
),
),
SizedBox(width: 2),
// Page number text
Text(
'Page ${currentPage + 1}',
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w400,
color: Colors.black87,
),
),
SizedBox(width: 2),
// Next button with arrow icon
IconButton(
icon: Icon(
Icons.arrow_forward_ios,
size: 10,
),
onPressed: (currentPage + 1) * itemsPerPage <
plans.length
? () {
setState(() {
currentPage++;
});
}
: null,
),
],
),
],
),
);
},
)

View File

@ -42,7 +42,7 @@ class _CreateUserFormState extends State<CreateUserForm> {
// Map to store controllers dynamically
final Map<String, TextEditingController> controllers = {};
bool isViewMode = false;
bool isEditProfile = true;
bool isEditProfile = false;
// late final List<dynamic>? apiCountryData ;
late List<dynamic>? apiCountryData;
@ -2932,11 +2932,11 @@ class _CreateUserFormState extends State<CreateUserForm> {
onPressed: () {
isEditProfile ? context.go('/listPlan') : context.go('/listUser');
},
child: isEditProfile ? Text("Back") : Text("Cancel")),
child: isViewMode ? Text("Back") : Text("Cancel")),
SizedBox(
width: 20,
),
if (!isEditProfile)
if (!isViewMode)
MouseRegion(
cursor: isViewMode
? SystemMouseCursors.forbidden

View File

@ -1,10 +1,37 @@
// import 'package:flutter/material.dart';
// import 'package:frontend/routes/custom_router.dart';
//
// class MyApp extends StatelessWidget {
// const MyApp({super.key});
//
// @override
// Widget build(BuildContext context) {
// return MaterialApp.router(
// title: 'TRIP MANAGEMENT',
// routerConfig: router,
// debugShowCheckedModeBanner: false,
// );
// }
// }
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:frontend/routes/custom_router.dart';
class MyApp extends StatelessWidget {
class MyApp extends StatefulWidget {
const MyApp({super.key});
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
@override
void initState() {
super.initState();
SemanticsBinding.instance.ensureSemantics(); // Safe here
}
@override
Widget build(BuildContext context) {
return MaterialApp.router(
@ -13,4 +40,4 @@ class MyApp extends StatelessWidget {
debugShowCheckedModeBanner: false,
);
}
}
}

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'app.dart';
void main() {

View File

@ -138,6 +138,7 @@ class _CustomAppBarState extends State<CustomAppBar> {
return AppBar(
backgroundColor: Colors.white,
surfaceTintColor: Colors.white,
// elevation: 3,
automaticallyImplyLeading: false,
// leading: showBackButton
@ -147,176 +148,209 @@ class _CustomAppBarState extends State<CustomAppBar> {
// )
// : null,
titleSpacing: 0,
title: Row(
children: [
Padding(
padding: const EdgeInsets.all(10),
child: selectedOrg?['logo'] != null
? ClipRect(
child: Image.network(
selectedOrg!['logo'],
width: 100,
height: 50,
fit: BoxFit.contain,
errorBuilder: (context, error, stackTrace) {
return const CircleAvatar(
radius: 20,
backgroundColor: Colors.redAccent,
child: Icon(Icons.error, size: 10),
);
},
),
)
: const CircleAvatar(
radius: 20,
backgroundColor: Colors.white,
child: Icon(
Icons.add_a_photo,
size: 10,
color: Colors.grey,
),
),
),
const SizedBox(width: 8),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
MouseRegion(
cursor: SystemMouseCursors.click,
child: Row(
children: [
GestureDetector(
onTap: () {
print("ONTAP Custom");
print("ONTAP Custom- $userDetails ");
context.go(
"/CreateUserDetails",
extra: {
"selectedUser": userDetails,
"isEditProfile": true,
"isViewMode": true
},
title: Padding(
padding: EdgeInsets.symmetric(
horizontal: MediaQuery.of(context).size.width * 0.05),
child: Row(
children: [
Padding(
padding: const EdgeInsets.all(10),
// padding: const EdgeInsets.symmetric(horizontal: 40, vertical: 10),
child: selectedOrg?['logo'] != null
? ClipRect(
child: Image.network(
selectedOrg!['logo'],
width: 100,
height: 50,
fit: BoxFit.contain,
errorBuilder: (context, error, stackTrace) {
return const CircleAvatar(
radius: 20,
backgroundColor: Colors.redAccent,
child: Icon(Icons.error, size: 10),
);
},
child: Text(
userData?["name"] ?? "N/A",
style: TextStyle(
fontSize: 13,
fontWeight: FontWeight.w600,
fontFamily: "Archivo",
// color: Color(0xFF12B24B),
color: layoutColor,
),
),
),
],
),
)
: const CircleAvatar(
radius: 20,
backgroundColor: Colors.white,
child: Icon(
Icons.add_a_photo,
size: 10,
color: Colors.grey,
),
),
),
const SizedBox(width: 8),
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(width: 15),
MouseRegion(
cursor: SystemMouseCursors.click,
onEnter: (_) {
setState(() {
_myTravelRequestColor =
Colors.blue; // Change color on hover
});
},
onExit: (_) {
setState(() {
_myTravelRequestColor =
Color(0xFF475569); // Revert color when hover ends
});
},
child: GestureDetector(
onTap: () {
context.go('/listPlan');
},
child: Text(
"My Travel Request",
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
// color: Color(0xFF475569),
color: _myTravelRequestColor,
fontFamily: "Archivo"),
)),
),
const SizedBox(width: 25),
MouseRegion(
cursor: SystemMouseCursors.click,
onEnter: (_) {
setState(() {
_myApprovalsColor = Colors.blue; // Change color on hover
});
},
onExit: (_) {
setState(() {
_myApprovalsColor =
Color(0xFF475569); // Revert color when hover ends
});
},
child: GestureDetector(
onTap: () {
context.go('/ApprovalList');
},
child: Text(
"My Approvals",
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
color: _myApprovalsColor,
// color: Color(0xFF475569),
fontFamily: "Archivo"),
)),
),
],
),
),
],
],
),
),
actions: [
Row(
children: [
MouseRegion(
cursor: SystemMouseCursors.click,
onEnter: (_) {
setState(() {
_myTravelRequestColor = Colors.blue; // Change color on hover
});
},
onExit: (_) {
setState(() {
_myTravelRequestColor =
Color(0xFF475569); // Revert color when hover ends
});
},
child: GestureDetector(
onTap: () {
context.go('/listPlan');
},
child: Text(
"My Travel Request",
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
// color: Color(0xFF475569),
color: _myTravelRequestColor,
fontFamily: "Archivo"),
)),
),
const SizedBox(width: 25),
MouseRegion(
cursor: SystemMouseCursors.click,
onEnter: (_) {
setState(() {
_myApprovalsColor = Colors.blue; // Change color on hover
});
},
onExit: (_) {
setState(() {
_myApprovalsColor =
Color(0xFF475569); // Revert color when hover ends
});
},
child: GestureDetector(
onTap: () {
context.go('/ApprovalList');
},
child: Text(
"My Approvals",
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
color: _myApprovalsColor,
// color: Color(0xFF475569),
fontFamily: "Archivo"),
)),
),
const SizedBox(width: 15),
if (userData?["role"] != "User")
Builder(
builder: (context) => PopupMenuButton<String>(
color: Colors.white,
icon: const Icon(Icons.settings, color: Colors.black87),
offset: const Offset(0, 50), // 👈 shift it 50 pixels down
onSelected: (String value) {
switch (value) {
case '/OrganizationSetup':
context.go('/OrganizationSetup');
break;
case '/listUser':
context.go('/listUser');
break;
case '/group':
context.go('/group');
break;
case '/PolicyList':
context.go('/PolicyList');
case '/CreateUserDetails':
context.go(
"/CreateUserDetails",
extra: {
"selectedUser": userDetails,
"isEditProfile": true,
"isViewMode": true
},
);
case '/logout':
context.go('/');
break;
}
},
itemBuilder: (BuildContext context) =>
menuItems.map(buildMenuItem).toList(),
Padding(
padding: EdgeInsets.symmetric(
horizontal: MediaQuery.of(context).size.width * 0.05),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
userData?["name"] ?? "N/A",
style: TextStyle(
fontSize: 13,
fontWeight: FontWeight.w600,
fontFamily: "Archivo",
// color: Color(0xFF12B24B),
color: layoutColor,
),
),
const SizedBox(width: 8),
],
SizedBox(
height: 1,
),
Row(
children: [
// if (userData?["role"] != "User")
Builder(
builder: (context) => PopupMenuButton<String>(
color: Colors.white,
padding: EdgeInsets.zero,
// icon: const Icon(Icons.arrow_drop_down,
// size: 20, color: Colors.black87),
offset: const Offset(0, 50), // 👈 shift it 50 pixels down
onSelected: (String value) {
switch (value) {
case '/OrganizationSetup':
context.go('/OrganizationSetup');
break;
case '/listUser':
context.go('/listUser');
break;
case '/group':
context.go('/group');
break;
case '/PolicyList':
context.go('/PolicyList');
case '/CreateUserDetails':
context.go(
"/CreateUserDetails",
extra: {
"selectedUser": userDetails,
"isEditProfile": true,
"isViewMode": true
},
);
case '/logout':
context.go('/');
break;
}
},
// itemBuilder: (BuildContext context) =>
// menuItems.map(buildMenuItem).toList(),
itemBuilder: (BuildContext context) {
final isUser = userData?["role"] == "User";
final filteredItems = isUser
? menuItems
.where((item) =>
item['value'] == '/CreateUserDetails' ||
item['value'] == '/logout')
.toList()
: menuItems;
return filteredItems.map(buildMenuItem).toList();
},
child: MouseRegion(
cursor: SystemMouseCursors.click,
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(
userData?["role"] ?? "Role",
style: const TextStyle(
fontSize: 11,
fontWeight: FontWeight.w300,
fontFamily: "Archivo",
color: Colors.black,
),
),
const Icon(
Icons.arrow_drop_down,
size: 20,
color: Colors.black87,
),
],
),
),
),
),
const SizedBox(width: 8),
],
),
],
),
),
],
bottom: PreferredSize(
@ -339,7 +373,11 @@ final List<Map<String, dynamic>> menuItems = [
'icon': Icons.business,
'label': 'Organization'
},
{'value': '/listUser', 'icon': Icons.manage_accounts, 'label': 'Users'},
{
'value': '/listUser',
'icon': Icons.manage_accounts,
'label': 'User Management'
},
{'value': '/group', 'icon': Icons.group, 'label': 'Group'},
{'value': '/PolicyList', 'icon': Icons.policy, 'label': 'Policy'},
{
@ -352,10 +390,25 @@ final List<Map<String, dynamic>> menuItems = [
PopupMenuItem<String> buildMenuItem(Map<String, dynamic> item) {
return PopupMenuItem<String>(
height: 40, // 👈 reduce PopupMenuItem height
value: item['value'],
child: ListTile(
leading: Icon(item['icon'], size: 18),
title: Text(item['label'], style: const TextStyle(fontSize: 13)),
padding:
EdgeInsets.symmetric(horizontal: 12), // 👈 control left-right spacing
child: Row(
children: [
Icon(item['icon'],
size: 18, color: Colors.black87), // 👈 smaller, cleaner icon
SizedBox(width: 10), // 👈 small space between icon and text
Text(
item['label'],
style: TextStyle(
fontSize: 13,
fontFamily: "Roboto",
fontWeight: FontWeight.w400,
color: Colors.black87,
),
),
],
),
);
}

View File

@ -124,8 +124,8 @@ class _CustomDrawerState extends State<CustomDrawer> {
@override
Widget build(BuildContext context) {
Widget drawerContent = Container(
color: Colors.white,
// color: Color(0xFFF3F3FA),
// color: Colors.white,
child: Container(
margin: const EdgeInsets.all(18),
child: Column(

View File

@ -447,4 +447,41 @@ class ApiService {
throw Exception('Failed to load organizations');
}
}
// Flight From - To
Future<List<dynamic>> fetchFlightsCountryList() async {
final String apiUrldata = '$apiUrl/api/getAirportCodeMaster';
final token = await getToken();
if (token == null) {
throw Exception('Token not found. Please log in.');
}
final response = await http.get(
Uri.parse(apiUrldata),
headers: {
'Authorization': 'Bearer $token',
'Content-Type': 'application/json',
},
);
if (response.statusCode == 200) {
try {
final data = json.decode(response.body);
print("Country - $data");
if (!data.containsKey('data') || data['data'] is! List) {
throw Exception(
"Invalid response format: 'data' field is missing or not a List");
}
return data['data'];
} catch (e) {
throw Exception('Error parsing response: $e');
}
} else {
throw Exception('Failed to load country list');
}
}
}