From 5c628282c63dc9f10632e1a950b5858fc1ff0bb3 Mon Sep 17 00:00:00 2001 From: venbaittech Date: Wed, 30 Apr 2025 13:29:03 +0530 Subject: [PATCH] itinerary changes --- lib/Screens/itnerary/accomodations.dart | 282 ++++- lib/Screens/itnerary/bus.dart | 88 +- lib/Screens/itnerary/flights.dart | 478 +++++--- lib/Screens/itnerary/forex.dart | 273 +++-- lib/Screens/itnerary/insurance.dart | 308 ++++- lib/Screens/itnerary/miscellaneous.dart | 68 +- lib/Screens/itnerary/taxi.dart | 66 +- lib/Screens/itnerary/train.dart | 252 ++-- lib/Screens/itnerary/visa.dart | 323 +++-- .../itnerary_list/accomodation_list.dart | 112 +- lib/Screens/itnerary_list/bus_list.dart | 70 +- lib/Screens/itnerary_list/flight_list.dart | 43 +- lib/Screens/itnerary_list/forex_list.dart | 92 +- lib/Screens/itnerary_list/insurance_list.dart | 70 +- .../itnerary_list/miscellaneous_list.dart | 68 +- lib/Screens/itnerary_list/taxi_list.dart | 71 +- lib/Screens/itnerary_list/train_list.dart | 112 +- lib/Screens/itnerary_list/visa_list.dart | 55 +- lib/Screens/plans/create_plans.dart | 1062 ++++++++--------- .../plans/dynamic_itinerary_stepper.dart | 282 +++-- lib/Screens/plans/list_plans.dart | 102 +- .../create_user/create_user.dart | 6 +- lib/app.dart | 33 +- lib/main.dart | 1 + lib/routes/custom_appBar.dart | 381 +++--- lib/routes/custom_drawer.dart | 4 +- lib/services/apiService.dart | 37 + 27 files changed, 2941 insertions(+), 1798 deletions(-) diff --git a/lib/Screens/itnerary/accomodations.dart b/lib/Screens/itnerary/accomodations.dart index 1449f3c..87e0b3e 100644 --- a/lib/Screens/itnerary/accomodations.dart +++ b/lib/Screens/itnerary/accomodations.dart @@ -6,6 +6,7 @@ import '../../widgets/custom_text_field.dart'; import '../../widgets/custom_text_itnerary_sub.dart'; class AccomodationScreen extends StatefulWidget { + final List> flightData; final Function(bool) onClose; // Callback function final Function(Map) onSaveAccomadation; final Map? 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 { + late ValueNotifier flightFirstTripDateNotifier; + late ValueNotifier flightLastTripDateNotifier; + final GlobalKey _formKey = GlobalKey(); final FocusNode _destinationFocusNode = FocusNode(); @@ -116,6 +121,30 @@ class _AccomodationScreenState extends State { _checkInTimeController.addListener(() => _clearError("checkin_time")); _checkOutController.addListener(() => _clearError("checkout_date")); _checkOutTimeController.addListener(() => _clearError("checkout_time")); + + flightFirstTripDateNotifier = ValueNotifier(null); + flightLastTripDateNotifier = ValueNotifier(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 { } } + Map getFlightTripDateRange( + List> flightData) { + final allTrips = flightData + .expand((flight) => flight['trips'] ?? []) + .whereType>() + .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 data) { errorMessages.clear(); // Reset errors @@ -159,6 +217,55 @@ class _AccomodationScreenState extends State { } } + // 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 { 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 { : Column( children: _buildThirdRow(isDesktop), ), - - SizedBox(height: 10), - Row( - mainAxisAlignment: MainAxisAlignment.end, - children: _handleAction(isDesktop), - ), ]; } List _buildFirstRow(isDesktop) { return [ + // Column( + // children: [ + // ValueListenableBuilder( + // valueListenable: flightFirstTripDateNotifier, + // builder: (context, value, child) => + // Text("First Trip Date: ${value ?? 'Not available'}"), + // ), + // ValueListenableBuilder( + // 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 { 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 { DateTime? _selectedCheckOutDate; TimeOfDay? _selectedCheckOutTime; + // Future _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 _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 { 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), + ), + ], + ), ]; } diff --git a/lib/Screens/itnerary/bus.dart b/lib/Screens/itnerary/bus.dart index 8149036..5c62a39 100644 --- a/lib/Screens/itnerary/bus.dart +++ b/lib/Screens/itnerary/bus.dart @@ -199,34 +199,34 @@ class _BusScreenState extends State { 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 { ...buildResponsiveRow(_buildThirdRow(isDesktop)), // Actions row remains a Row - Row( - mainAxisAlignment: MainAxisAlignment.end, - children: _handleAction(isDesktop), - ), ]; } @@ -607,26 +603,34 @@ class _BusScreenState extends State { 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), + ), + ], + ), ]; } diff --git a/lib/Screens/itnerary/flights.dart b/lib/Screens/itnerary/flights.dart index 13301ff..4d5913a 100644 --- a/lib/Screens/itnerary/flights.dart +++ b/lib/Screens/itnerary/flights.dart @@ -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> flightData; final Map? 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 { + ApiService apiService = ApiService(); final GlobalKey _formKey = GlobalKey(); + late ValueNotifier flightFirstTripDateNotifier; + late ValueNotifier flightLastTripDateNotifier; + + String? selectedCountry; + List> countryList = []; + Map selectedValues = {}; String? selectedTripType; @@ -60,6 +70,7 @@ class _FlightScreenState extends State { @override void initState() { super.initState(); + // _initializeRows(); // List purposeList = widget.apiData?['flight_trip_type'] ?? []; // selectedTripType ??= purposeList.isNotEmpty ? purposeList.first['dropdown_value'] : null; @@ -109,6 +120,56 @@ class _FlightScreenState extends State { textControllers["_time${i}Controller"] ?.addListener(() => _clearError("time_$i")); } + // loadCountryList(); + + flightFirstTripDateNotifier = ValueNotifier(null); + flightLastTripDateNotifier = ValueNotifier(null); + + WidgetsBinding.instance.addPostFrameCallback((_) { + final result = getFlightTripDateRange(widget.flightData); + flightFirstTripDateNotifier.value = result['firstTripDate']; + flightLastTripDateNotifier.value = result['lastTripDate']; + }); + } + + Map getFlightTripDateRange( + List> flightData) { + final allTrips = flightData + .expand((flight) => flight['trips'] ?? []) + .whereType>() + .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 loadCountryList() async { + final result = await apiService.fetchFlightsCountryList(); + + if (result is List) { + countryList = + result.map((item) => Map.from(item)).toList(); + } else { + countryList = []; // fallback or throw error + } } int getRowCount() { @@ -444,34 +505,15 @@ class _FlightScreenState extends State { 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 { }); } - // void _addNewRow() { - // setState(() { - // rowBuilders.add(_builClassType(false)); - // controllers.add(TextEditingController()); - // }); - // } - List _buildAccomadtionForm(bool isDesktop) { List buildResponsiveRow(List children) { return [ @@ -502,14 +537,14 @@ class _FlightScreenState extends State { } List> rowBuilders = [ - _builClassType(isDesktop, 1), + // _builClassType(isDesktop, 1), _buildSecondRow(isDesktop, 1) ]; List> 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 { if (selectedTripType == "Multitrip") ...List.generate(multiTripRowCount, (index) { - List firstRow = _builClassType(isDesktop, index + 1); + // List firstRow = _builClassType(isDesktop, index + 1); List 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 { 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 { }, 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 { // 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 { ]; } - List _builClassType(bool isDesktop, int index) { + List _buildSecondRow(bool isDesktop, int index) { List purposeList = widget.apiData?['flight_class'] ?? []; List> dropdownItems = purposeList @@ -910,96 +938,54 @@ class _FlightScreenState extends State { // 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( - 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 _buildSecondRow(bool isDesktop, int index) { DateTime? _selectedCheckOutDate; TimeOfDay? _selectedCheckOutTime; Future _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 { } } + late Map countryMap; // Mapping country_code -> country_name + late List 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( + 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 { isFocused: focusStates["_from${index}Focused"] ?? false, // isFocused: focusStates["_from${fieldIndex}Focused"] ?? false, isDesktop: isDesktop, + // child: SizedBox( + // height: 40, + // child: DropdownSearch( + // 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 { 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 { 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 { 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 { ], ], ), + 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 _buildThirdRow(bool isDesktop) { + List visa_available = + widget.apiData?['flight_visa_available'] ?? []; + // Default selected value + + List> dropdownItems = visa_available + .map((item) => DropdownMenuItem( + value: item['dropdown_key'], + child: Text(item['dropdown_value']), + )) + .toList(); + + selectedvisa_available ??= + dropdownItems.isNotEmpty ? dropdownItems.first.value : null; + + if (dropdownItems.isEmpty) { + dropdownItems.add( + DropdownMenuItem( + 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( + // 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 { 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), + ), + ], + ), ]; } diff --git a/lib/Screens/itnerary/forex.dart b/lib/Screens/itnerary/forex.dart index ca8b920..f7bd9d2 100644 --- a/lib/Screens/itnerary/forex.dart +++ b/lib/Screens/itnerary/forex.dart @@ -14,6 +14,7 @@ import '../../widgets/custom_text_itnerary_sub.dart'; import 'package:http/http.dart' as http; class ForexScreen extends StatefulWidget { + final List> flightData; final Map? apiData; final Function(bool) onClose; final Map? 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 { final GlobalKey _formKey = GlobalKey(); + late ValueNotifier flightFirstTripDateNotifier; + late ValueNotifier flightLastTripDateNotifier; + Map selectedValues = {}; bool isChecked = false; // State variable for checkbox @@ -106,6 +111,7 @@ class _ForexScreenState extends State { "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 { return errorMessages.isEmpty; // Valid if there are no errors } + Map getFlightTripDateRange( + List> flightData) { + final allTrips = flightData + .expand((flight) => flight['trips'] ?? []) + .whereType>() + .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 { textControllers["_forexEndDate"]?.addListener(_onFieldChanged); handleUpdatedField(); + + flightFirstTripDateNotifier = ValueNotifier(null); + flightLastTripDateNotifier = ValueNotifier(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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { // 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 { ? 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 { 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 { 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 { 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 { // 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 { 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 { 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), + ), + ], + ), ]; } diff --git a/lib/Screens/itnerary/insurance.dart b/lib/Screens/itnerary/insurance.dart index 5119c16..43b0d03 100644 --- a/lib/Screens/itnerary/insurance.dart +++ b/lib/Screens/itnerary/insurance.dart @@ -6,6 +6,7 @@ import '../../widgets/custom_text_field.dart'; import '../../widgets/custom_text_itnerary_sub.dart'; class InsuranceScreen extends StatefulWidget { + final List> flightData; final Map? apiData; final Function(bool) onClose; final Function(Map) 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 { Map selectedValues = {}; + late ValueNotifier flightFirstTripDateNotifier; + late ValueNotifier flightLastTripDateNotifier; + final FocusNode _tripTypeFocusNode = FocusNode(); final FocusNode _hotelNameFocusNode = FocusNode(); final FocusNode _fromFocusNode = FocusNode(); @@ -105,6 +110,58 @@ class _InsuranceScreenState extends State { selectedInsuranceType = widget.selectedItem!["type_of_insurance"].toString(); } + + flightFirstTripDateNotifier = ValueNotifier(null); + flightLastTripDateNotifier = ValueNotifier(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 getFlightTripDateRange( + List> flightData) { + final allTrips = flightData + .expand((flight) => flight['trips'] ?? []) + .whereType>() + .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 { } } + // 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 { 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 { ]; 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 { ...buildResponsiveRow(_buildThirdRow(isDesktop)), // Actions row remains a Row - Row( - mainAxisAlignment: MainAxisAlignment.end, - children: _handleAction(isDesktop), - ), ]; } @@ -338,6 +389,33 @@ class _InsuranceScreenState extends State { } List _buildSecondRow(bool isDesktop) { + List purposeList = + widget.apiData?['insurance_type_of_insurance'] ?? []; + // selectedInsuranceType = purposeList.isNotEmpty ? purposeList.first['dropdown_value'] : null; + + List> dropdownItems = purposeList + .map((item) => DropdownMenuItem( + value: item['dropdown_key'], + child: Text(item['dropdown_value']), + )) + .toList(); + + if (dropdownItems.isEmpty) { + dropdownItems.add( + DropdownMenuItem( + value: null, + child: Text("No options available", + style: TextStyle(color: Colors.grey)), + ), + ); + } + + // Default selected value + selectedInsuranceType ??= + dropdownItems.isNotEmpty ? dropdownItems.first.value : null; + + // ---------------------------------------------------------------- + DateTime? _selectedCheckOutDate; TimeOfDay? _selectedCheckOutTime; @@ -345,16 +423,41 @@ class _InsuranceScreenState extends State { 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 { 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 { } 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( + 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 { 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 { 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 { 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), + ), + ], + ), ]; } diff --git a/lib/Screens/itnerary/miscellaneous.dart b/lib/Screens/itnerary/miscellaneous.dart index 999f6da..f7bbca8 100644 --- a/lib/Screens/itnerary/miscellaneous.dart +++ b/lib/Screens/itnerary/miscellaneous.dart @@ -147,35 +147,12 @@ class _MiscellaneousScreenState extends State { 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 { ...buildResponsiveRow(_buildThirdRow(isDesktop)), // Actions row remains a Row - Row( - mainAxisAlignment: MainAxisAlignment.end, - children: _handleAction(isDesktop), - ), ]; } @@ -229,12 +202,6 @@ class _MiscellaneousScreenState extends State { : Column(children: _buildTripType(isDesktop)) ], ), - if (isDesktop) - Spacer() - else - SizedBox( - height: 8, - ), ]; } @@ -324,18 +291,19 @@ class _MiscellaneousScreenState extends State { 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 { ), ], ], - ) + ), + if (isDesktop) Spacer(), + SizedBox( + height: 5, + ), + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: _handleAction(isDesktop), + ), ]; } diff --git a/lib/Screens/itnerary/taxi.dart b/lib/Screens/itnerary/taxi.dart index e7c5141..70ab797 100644 --- a/lib/Screens/itnerary/taxi.dart +++ b/lib/Screens/itnerary/taxi.dart @@ -203,34 +203,13 @@ class _TaxiScreenState extends State { 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 { ...buildResponsiveRow(_buildThirdRow(isDesktop)), // Actions row remains a Row - Row( - mainAxisAlignment: MainAxisAlignment.end, - children: _handleAction(isDesktop), - ), ]; } @@ -738,23 +713,36 @@ class _TaxiScreenState extends State { ? 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), + ), + ], + ), ]; } diff --git a/lib/Screens/itnerary/train.dart b/lib/Screens/itnerary/train.dart index d867922..bee4728 100644 --- a/lib/Screens/itnerary/train.dart +++ b/lib/Screens/itnerary/train.dart @@ -212,34 +212,34 @@ class _TrainScreenState extends State { 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 { ...buildResponsiveRow(_buildThirdRow(isDesktop)), // Actions row remains a Row - Row( - mainAxisAlignment: MainAxisAlignment.end, - children: _handleAction(isDesktop), - ), ]; } @@ -345,9 +341,7 @@ class _TrainScreenState extends State { 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 { } List _builClassType(bool isDesktop) { - List purposeList = widget.apiData?['train_class'] ?? []; - - List> dropdownItems = purposeList - .map((item) => DropdownMenuItem( - value: item['dropdown_key'], - child: Text(item['dropdown_value']), - )) - .toList(); - - if (dropdownItems.isEmpty) { - dropdownItems.add( - DropdownMenuItem( - value: null, - child: Text("No options available", - style: TextStyle(color: Colors.grey)), - ), - ); - } - - // Default selected value - 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( - 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 _buildSecondRow(bool isDesktop) { @@ -490,7 +411,87 @@ class _TrainScreenState extends State { } } + //---------------------------------------------- + + List purposeList = widget.apiData?['train_class'] ?? []; + + List> dropdownItems = purposeList + .map((item) => DropdownMenuItem( + value: item['dropdown_key'], + child: Text(item['dropdown_value']), + )) + .toList(); + + if (dropdownItems.isEmpty) { + dropdownItems.add( + DropdownMenuItem( + value: null, + child: Text("No options available", + style: TextStyle(color: Colors.grey)), + ), + ); + } + + // Default selected value + 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( + 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 { 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 { 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 { 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), + ), + ], ) ]; } diff --git a/lib/Screens/itnerary/visa.dart b/lib/Screens/itnerary/visa.dart index 29c9d5d..a045342 100644 --- a/lib/Screens/itnerary/visa.dart +++ b/lib/Screens/itnerary/visa.dart @@ -7,6 +7,7 @@ import '../../widgets/custom_text_field.dart'; import '../../widgets/custom_text_itnerary_sub.dart'; class VisaScreen extends StatefulWidget { + final List> flightData; final Map? apiData; final List? 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 { final GlobalKey _formKey = GlobalKey(); + late ValueNotifier flightFirstTripDateNotifier; + late ValueNotifier flightLastTripDateNotifier; + Map selectedValues = {}; List countryList = []; @@ -105,6 +110,22 @@ class _VisaScreenState extends State { // selectedPurpose = widget.selectedItem!["selectedCountry"].toString(); selectedCountry = widget.selectedItem!["country_code"] as String?; } + + flightFirstTripDateNotifier = ValueNotifier(null); + flightLastTripDateNotifier = ValueNotifier(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 { }); } + Map getFlightTripDateRange( + List> flightData) { + final allTrips = flightData + .expand((flight) => flight['trips'] ?? []) + .whereType>() + .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 { 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 { ...buildResponsiveRow(_buildThirdRow(isDesktop)), // Actions row remains a Row - Row( - mainAxisAlignment: MainAxisAlignment.end, - children: _handleAction(isDesktop), - ), ]; } List _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 { } List _buildTripType(bool isDesktop) { - List purposeList = widget.apiData?['visa_type_of_visa'] ?? []; - - List> dropdownItems = purposeList - .map((item) => DropdownMenuItem( - value: item['dropdown_key'], - child: Text(item['dropdown_value']), - )) - .toList(); - - if (dropdownItems.isEmpty) { - dropdownItems.add( - DropdownMenuItem( - value: null, - child: Text("No options available", - style: TextStyle(color: Colors.grey)), - ), - ); - } - - // Default selected value - 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( - 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 _buildSecondRow(bool isDesktop) { @@ -380,16 +326,41 @@ class _VisaScreenState extends State { 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 { } } + // --------------------- + + List purposeList = widget.apiData?['visa_type_of_visa'] ?? []; + + List> dropdownItems = purposeList + .map((item) => DropdownMenuItem( + value: item['dropdown_key'], + child: Text(item['dropdown_value']), + )) + .toList(); + + if (dropdownItems.isEmpty) { + dropdownItems.add( + DropdownMenuItem( + value: null, + child: Text("No options available", + style: TextStyle(color: Colors.grey)), + ), + ); + } + + // Default selected value + 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( + 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 { 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( @@ -490,9 +540,7 @@ class _VisaScreenState extends State { 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 { 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), + ), + ], + ), ]; } diff --git a/lib/Screens/itnerary_list/accomodation_list.dart b/lib/Screens/itnerary_list/accomodation_list.dart index 3c856f3..dc3030f 100644 --- a/lib/Screens/itnerary_list/accomodation_list.dart +++ b/lib/Screens/itnerary_list/accomodation_list.dart @@ -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), diff --git a/lib/Screens/itnerary_list/bus_list.dart b/lib/Screens/itnerary_list/bus_list.dart index ac2063d..3603aca 100644 --- a/lib/Screens/itnerary_list/bus_list.dart +++ b/lib/Screens/itnerary_list/bus_list.dart @@ -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), + // ) + // ], + // ), + // ), ), ], ), diff --git a/lib/Screens/itnerary_list/flight_list.dart b/lib/Screens/itnerary_list/flight_list.dart index 39638a6..04235ce 100644 --- a/lib/Screens/itnerary_list/flight_list.dart +++ b/lib/Screens/itnerary_list/flight_list.dart @@ -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), + ) ], ), ), diff --git a/lib/Screens/itnerary_list/forex_list.dart b/lib/Screens/itnerary_list/forex_list.dart index 3a3b8a5..aa8b1a3 100644 --- a/lib/Screens/itnerary_list/forex_list.dart +++ b/lib/Screens/itnerary_list/forex_list.dart @@ -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), diff --git a/lib/Screens/itnerary_list/insurance_list.dart b/lib/Screens/itnerary_list/insurance_list.dart index 5cafbdc..e33118d 100644 --- a/lib/Screens/itnerary_list/insurance_list.dart +++ b/lib/Screens/itnerary_list/insurance_list.dart @@ -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), + // ) + // ], + // ), + // ), ), ], ), diff --git a/lib/Screens/itnerary_list/miscellaneous_list.dart b/lib/Screens/itnerary_list/miscellaneous_list.dart index 8382d39..17f3a07 100644 --- a/lib/Screens/itnerary_list/miscellaneous_list.dart +++ b/lib/Screens/itnerary_list/miscellaneous_list.dart @@ -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), + // ) + // ], + // ), + // ), ), ], ), diff --git a/lib/Screens/itnerary_list/taxi_list.dart b/lib/Screens/itnerary_list/taxi_list.dart index 77cb482..9d8d88a 100644 --- a/lib/Screens/itnerary_list/taxi_list.dart +++ b/lib/Screens/itnerary_list/taxi_list.dart @@ -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), + // ) + // ], + // ), + // ), ), ], ), diff --git a/lib/Screens/itnerary_list/train_list.dart b/lib/Screens/itnerary_list/train_list.dart index e78e8c6..9f65490 100644 --- a/lib/Screens/itnerary_list/train_list.dart +++ b/lib/Screens/itnerary_list/train_list.dart @@ -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), + // ) + // ], + // ), + // ), ), ], ), diff --git a/lib/Screens/itnerary_list/visa_list.dart b/lib/Screens/itnerary_list/visa_list.dart index a5087ac..c8d666d 100644 --- a/lib/Screens/itnerary_list/visa_list.dart +++ b/lib/Screens/itnerary_list/visa_list.dart @@ -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), + // ) + // ], + // ), ), ], ), diff --git a/lib/Screens/plans/create_plans.dart b/lib/Screens/plans/create_plans.dart index 05868c3..2ac5e9f 100644 --- a/lib/Screens/plans/create_plans.dart +++ b/lib/Screens/plans/create_plans.dart @@ -427,10 +427,6 @@ class CreateNewPlansState extends State { fetchCostCenter(); fetchCountryList(); - // _tripTitleController.addListener(() { - // print("Current Value: ${_tripTitleController.text}"); - // }); - _tripTitleFocusNode.addListener(() { setState(() { _isTripTitleFocused = _tripTitleFocusNode.hasFocus; @@ -1141,94 +1137,32 @@ class CreateNewPlansState extends State { SizedBox(height: 10), - Row( - children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "Trip Title", // Your label - style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.w600, - color: Color(0xFF575A74)), - ), - SizedBox(height: 5), - CustomTextFieldWrapper( - isFocused: _isTripTitleFocused, - isDesktop: widget.isDesktop, - child: SizedBox( - height: 40, - child: TextField( - focusNode: _tripTitleFocusNode, - controller: _tripTitleController, - style: TextStyle(fontSize: 12), - enabled: !widget.isViewMode, - decoration: InputDecoration( - labelText: "Trip Title", - labelStyle: - TextStyle(fontSize: 12, color: Colors.grey), - floatingLabelBehavior: FloatingLabelBehavior.never, - border: InputBorder.none, - contentPadding: EdgeInsets.symmetric(vertical: 16), - ), - ), - ), - ), - ], - ), - ], - ), - SizedBox( - height: 10, - ), - Row( - children: [ - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "Trip Type *", // Your label - style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.w600, - color: Color(0xFF575A74)), - ), - SizedBox(height: 5), - isMobile - ? SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: Row( - children: _buildTripType(isMobile), - ), - ) - : Row( - children: _buildTripType(isMobile), - ), - if (validationErrors["trip_type"] != null) - Padding( - padding: EdgeInsets.only(top: 4), - child: Text( - validationErrors["trip_type"]!, - style: TextStyle(color: Colors.red, fontSize: 10), - ), - ), - ], - ), - ), - ], - ), - SizedBox( - height: 15, - ), + // Row( + // children: [ + // Expanded( + // child: + // ), + // ], + // ), + isDesktop ? Row( crossAxisAlignment: CrossAxisAlignment.start, - children: _buildCostIsBillable()) + children: _buildTripRow(isMobile)) : Column( crossAxisAlignment: CrossAxisAlignment.start, - children: _buildCostIsBillable()), + children: _buildTripRow(isMobile)), + SizedBox( + height: 15, + ), + + isDesktop + ? Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: _buildCostCenter(isDesktop)) + : Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: _buildCostCenter(isDesktop)), SizedBox( height: 8, ), @@ -1249,12 +1183,16 @@ class CreateNewPlansState extends State { _buildDescriptionColumn(isDesktop), ], ), - Padding( - padding: const EdgeInsets.all(8.0), - child: Divider( - color: Color(0xFFE6E7F5), // Change color - thickness: 0.5, - ), + // Padding( + // padding: const EdgeInsets.all(8.0), + // child: Divider( + // color: Color(0xFFE6E7F5), // Change color + // thickness: 0.5, + // ), + // ), + + SizedBox( + height: 20, ), if (validationErrors["services"] != null) @@ -1313,123 +1251,51 @@ class CreateNewPlansState extends State { } /// Extracted helper function - List _buildCostIsBillable() { - List purposeList = apiData?['plan_is_billable'] ?? []; + /// + List _buildTripRow(bool isMobile) { + List purposeList = apiData?['plan_is_billable'] ?? []; return [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - "Cost Center *", // Your label + "Trip Type *", // Your label style: TextStyle( fontSize: 12, fontWeight: FontWeight.w600, color: Color(0xFF575A74)), ), SizedBox(height: 5), - CustomTextFieldWrapper( - isFocused: false, // Dropdown doesn't use focus - isDesktop: widget.isDesktop, - child: SizedBox( - height: 40, - width: double.infinity, - child: apiCostData == null - ? Center( - child: Transform.scale( - scale: 0.5, - child: CircularProgressIndicator(), - ), - ) - : DropdownSearch( - selectedItem: costCenterMap[selectedCostCenterId], - enabled: !widget.isViewMode, - popupProps: PopupProps.menu( - showSearchBox: true, - fit: FlexFit.loose, - constraints: BoxConstraints(maxHeight: 250), - searchFieldProps: TextFieldProps( - decoration: InputDecoration( - hintText: "Search Cost Center...", - hintStyle: - TextStyle(fontSize: 13, color: Colors.grey), - contentPadding: - EdgeInsets.symmetric(horizontal: 10), - ), - style: TextStyle(fontSize: 13)), - menuProps: MenuProps( - backgroundColor: Colors.white, - ), - itemBuilder: (context, item, isSelected) => Padding( - padding: const EdgeInsets.symmetric( - horizontal: 8.0, vertical: 6.0), - child: Text( - item, - style: TextStyle( - fontSize: - 13), // 👈 Set your desired text size here - ), - ), - ), - - items: costCenterMap.values.toList(), // just names - dropdownDecoratorProps: DropDownDecoratorProps( - dropdownSearchDecoration: InputDecoration( - border: InputBorder.none, - contentPadding: EdgeInsets.symmetric(horizontal: 1), - ), - ), - dropdownBuilder: (context, selectedItem) => Align( - alignment: Alignment.centerLeft, - child: Text( - selectedItem ?? "Select", - style: TextStyle(fontSize: 12), - ), - ), - onChanged: (String? newValue) { - setState(() { - selectedCostCenterId = costCenterMap.entries - .firstWhere((entry) => entry.value == newValue) - .key; - }); - }, - ), + isMobile + ? SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Row( + children: _buildTripType(isMobile), + ), + ) + : Row( + children: _buildTripType(isMobile), + ), + if (validationErrors["trip_type"] != null) + Padding( + padding: EdgeInsets.only(top: 4), + child: Text( + validationErrors["trip_type"]!, + style: TextStyle(color: Colors.red, fontSize: 10), + ), ), - - // child: SizedBox( - // height: 45, // Set appropriate height - // child: DropdownButtonFormField( - // value: selectedCostCenterId, - // style: TextStyle(fontSize: 12), - // decoration: InputDecoration( - // border: InputBorder.none, - // contentPadding: - // EdgeInsets.symmetric(horizontal: 10), // Proper padding - // ), - // onChanged: widget.isViewMode - // ? null - // : (newValue) { - // setState(() { - // selectedCostCenterId = newValue; - // }); - // }, - // items: apiCostData?.map>((item) { - // return DropdownMenuItem( - // value: item['department_id'], // ID as value - // child: Text(item['name'] ?? "Unknown"), - // ); - // }).toList(), - // ), - // ), - ), ], ), - SizedBox( - width: 25, + width: 150, height: 5, ), + // SizedBox( + // width: 25, + // height: 5, + // ), Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Is Billable ", // Your label @@ -1530,54 +1396,407 @@ class CreateNewPlansState extends State { // // ), ]) - // Column( - // crossAxisAlignment: CrossAxisAlignment.start, - // children: [ - // Text( - // "Is Billable ", // Your label - // style: TextStyle( - // fontSize: 12, - // fontWeight: FontWeight.w600, - // color: Color(0xFF575A74)), - // ), - // SizedBox(height: 5), - // Row( - // children: [ - // Row( - // children: [ - // Radio( - // value: "Billable", - // groupValue: _selectedIsBillable, - // activeColor: Colors.blueAccent, - // onChanged: (value) { - // setState(() { - // _selectedIsBillable = value; - // }); - // }, - // ), - // Text("Billable"), - // ], - // ), - // SizedBox(width: 20), // Spacing - // Row( - // children: [ - // Radio( - // value: "Non Billable", - // groupValue: _selectedIsBillable, - // activeColor: Colors.blueAccent, - // onChanged: (value) { - // setState(() { - // _selectedIsBillable = value; - // }); - // }, - // ), - // Text("Non Billable"), - // ], - // ), - // ], - // ), - // ], - // ) + ]; + } + + List _buildCostCenter(bool isDesktop) { + // if (apiData == null) { + // return Center(child: CircularProgressIndicator()); // Show loading indicator + // } + + // 'plan_purpose_of_travel' Starts ------------------------------------------------------ + + // List purposeList = apiData?['plan_purpose_of_travel'] ?? []; + List> purposeList = List>.from( + apiData?['plan_purpose_of_travel'] ?? []); + + List> dropdownItems = purposeList + .map((item) => DropdownMenuItem( + // value: item['dropdown_key'], + value: item['dropdown_key']?.toString(), + // value: item['dropdown_key'].toString(), + child: Text(item['dropdown_value']), + )) + .toList(); + + if (dropdownItems.isEmpty) { + dropdownItems.add( + DropdownMenuItem( + // value: null, + value: "1", + child: Text("No options available", + style: TextStyle(color: Colors.grey)), + ), + ); + } + + selectedPurpose ??= dropdownItems.isNotEmpty + ? dropdownItems.first.value.toString() + : "No options"; + + print( + "Dropdown Purpose List: ${dropdownItems.map((e) => e.value).toList()}"); + print("Selected Purpose: $selectedPurpose"); + + // 'plan_functional_department' Starts --------------------------------------------- + + // List funcDeptList = apiData?['plan_functional_department'] ?? []; + List> funcDeptList = List>.from( + apiData?['plan_functional_department'] ?? []); + + List> dropdownFuncDeptItems = funcDeptList + .map((item) => DropdownMenuItem( + // value: item['dropdown_key'], + value: item['dropdown_key']?.toString(), + child: Text(item['dropdown_value']), + )) + .toList(); + + if (dropdownFuncDeptItems.isEmpty) { + dropdownFuncDeptItems.add( + DropdownMenuItem( + // value: null, + value: "1", + child: Text("No options available", + style: TextStyle(color: Colors.grey)), + ), + ); + } + + // selectedFuncDept ??= dropdownFuncDeptItems.isNotEmpty ? dropdownFuncDeptItems.first.value.toString() : null; + selectedFuncDept ??= dropdownFuncDeptItems.isNotEmpty + ? dropdownFuncDeptItems.first.value.toString() + : "No options"; + + print( + "Dropdown Functional Department List: ${dropdownFuncDeptItems.map((e) => e.value).toList()}"); + print("Selected Functional Department: $selectedFuncDept"); + + return [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Cost Center *", // Your label + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74)), + ), + SizedBox(height: 5), + CustomTextFieldWrapper( + isFocused: false, // Dropdown doesn't use focus + isDesktop: widget.isDesktop, + child: SizedBox( + height: 35, + width: double.infinity, + child: apiCostData == null + ? Center( + child: Transform.scale( + scale: 0.5, + child: CircularProgressIndicator(), + ), + ) + : DropdownSearch( + selectedItem: costCenterMap[selectedCostCenterId], + enabled: !widget.isViewMode, + popupProps: PopupProps.menu( + showSearchBox: true, + fit: FlexFit.loose, + constraints: BoxConstraints(maxHeight: 250), + searchFieldProps: TextFieldProps( + decoration: InputDecoration( + hintText: "Search Cost Center...", + hintStyle: + TextStyle(fontSize: 13, color: Colors.grey), + contentPadding: + EdgeInsets.symmetric(horizontal: 10), + ), + style: TextStyle(fontSize: 13)), + menuProps: MenuProps( + backgroundColor: Colors.white, + ), + itemBuilder: (context, item, isSelected) => Padding( + padding: const EdgeInsets.symmetric( + horizontal: 8.0, vertical: 6.0), + child: Text( + item, + style: TextStyle( + fontSize: + 13), // 👈 Set your desired text size here + ), + ), + ), + + items: costCenterMap.values.toList(), // just names + dropdownDecoratorProps: DropDownDecoratorProps( + dropdownSearchDecoration: InputDecoration( + border: InputBorder.none, + contentPadding: EdgeInsets.symmetric(horizontal: 1), + ), + ), + dropdownBuilder: (context, selectedItem) => Align( + alignment: Alignment.centerLeft, + child: Text( + selectedItem ?? "Select", + style: TextStyle(fontSize: 12), + ), + ), + onChanged: (String? newValue) { + setState(() { + selectedCostCenterId = costCenterMap.entries + .firstWhere((entry) => entry.value == newValue) + .key; + }); + }, + ), + ), + + // child: SizedBox( + // height: 45, // Set appropriate height + // child: DropdownButtonFormField( + // value: selectedCostCenterId, + // style: TextStyle(fontSize: 12), + // decoration: InputDecoration( + // border: InputBorder.none, + // contentPadding: + // EdgeInsets.symmetric(horizontal: 10), // Proper padding + // ), + // onChanged: widget.isViewMode + // ? null + // : (newValue) { + // setState(() { + // selectedCostCenterId = newValue; + // }); + // }, + // items: apiCostData?.map>((item) { + // return DropdownMenuItem( + // value: item['department_id'], // ID as value + // child: Text(item['name'] ?? "Unknown"), + // ); + // }).toList(), + // ), + // ), + ), + ], + ), + SizedBox( + width: 25, + height: 5, + ), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Purpose of Travel *", // Your label + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74)), + ), + SizedBox(height: 5), + CustomTextFieldWrapper( + width: isDesktop + ? MediaQuery.of(context).size.width * 0.2 + : MediaQuery.of(context).size.width * 0.85, + isFocused: false, // Dropdown doesn't use focus + isDesktop: widget.isDesktop, + child: SizedBox( + height: 35, // Set appropriate height + + child: apiData == null + ? Center( + child: + CircularProgressIndicator()) // Show loading inside dropdown + : DropdownSearch>( + selectedItem: purposeList.firstWhere( + (item) => + item['dropdown_key'].toString() == selectedPurpose, + orElse: () => {}, // ✅ Safe fallback + ), + items: purposeList, + itemAsString: (Map item) => + item['dropdown_value'], + popupProps: PopupProps.menu( + showSearchBox: true, + fit: FlexFit.loose, + constraints: BoxConstraints(maxHeight: 250), + searchFieldProps: TextFieldProps( + decoration: InputDecoration( + hintText: "Search Purpose...", + hintStyle: + TextStyle(fontSize: 13, color: Colors.grey), + contentPadding: + EdgeInsets.symmetric(horizontal: 10), + ), + style: TextStyle(fontSize: 13)), + menuProps: MenuProps( + backgroundColor: Colors.white, + ), + itemBuilder: (context, item, isSelected) => Padding( + padding: const EdgeInsets.symmetric( + horizontal: 8.0, vertical: 6.0), + child: Text( + item['dropdown_value'], + style: TextStyle( + fontSize: + 13), // 👈 Set your desired text size here + ), + ), + ), + dropdownDecoratorProps: DropDownDecoratorProps( + dropdownSearchDecoration: InputDecoration( + contentPadding: EdgeInsets.symmetric( + horizontal: 1, + ), + border: InputBorder.none, + ), + ), + onChanged: widget.isViewMode + ? null + : (Map? newValue) { + setState(() { + selectedPurpose = + newValue?['dropdown_key'].toString(); + }); + print("selectedPurpose - $selectedPurpose"); + }, + dropdownBuilder: (context, selectedItem) => Align( + alignment: Alignment.centerLeft, + child: Text( + selectedItem?['dropdown_value'] ?? '', + style: TextStyle(fontSize: 12), + ), + ), + ), + ), + ), + + // CustomTextFieldWrapper( + // isFocused: false, // Dropdown doesn't use focus + // isDesktop: widget.isDesktop, + // child: SizedBox( + // height: 45, // Set appropriate height + // child: apiData == null + // ? Center( + // child: + // CircularProgressIndicator()) // Show loading inside dropdown + // : DropdownButtonFormField( + // value: selectedPurpose, + // style: TextStyle(fontSize: 12), + // decoration: InputDecoration( + // border: InputBorder.none, + // contentPadding: EdgeInsets.symmetric( + // horizontal: 10), // Proper padding + // ), + // onChanged: widget.isViewMode + // ? null + // : purposeList.isNotEmpty + // ? (newValue) { + // setState(() { + // selectedPurpose = newValue; + // }); + // print( + // "selectedPurpose - $selectedPurpose"); + // } + // : null, + // items: dropdownItems, + // ), + // ), + // ), + ], + ), + SizedBox( + width: 25, + height: 5, + ), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Functional Department", // Your label + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w600, + color: Color(0xFF575A74)), + ), + SizedBox(height: 5), + CustomTextFieldWrapper( + isFocused: false, // Dropdown doesn't use focus + isDesktop: widget.isDesktop, + width: isDesktop + ? MediaQuery.of(context).size.width * 0.2 + : MediaQuery.of(context).size.width * 0.85, + // width: MediaQuery.of(context).size.width * 0.2, + child: SizedBox( + height: 35, // Set appropriate height + child: apiData == null + ? Center( + child: + CircularProgressIndicator()) // Show loading inside dropdown + : DropdownSearch>( + selectedItem: funcDeptList.firstWhere( + (item) => + item['dropdown_key'].toString() == selectedFuncDept, + orElse: () => {}, // ✅ Safe fallback + ), + items: funcDeptList, + itemAsString: (Map item) => + item['dropdown_value'], + popupProps: PopupProps.menu( + showSearchBox: true, + fit: FlexFit.loose, + constraints: BoxConstraints(maxHeight: 250), + searchFieldProps: TextFieldProps( + decoration: InputDecoration( + hintText: "Search department...", + hintStyle: + TextStyle(fontSize: 13, color: Colors.grey), + contentPadding: + EdgeInsets.symmetric(horizontal: 10), + ), + style: TextStyle(fontSize: 13)), + menuProps: MenuProps( + backgroundColor: Colors.white, + ), + itemBuilder: (context, item, isSelected) => Padding( + padding: const EdgeInsets.symmetric( + horizontal: 8.0, vertical: 6.0), + child: Text( + item['dropdown_value'], + style: TextStyle( + fontSize: + 13), // 👈 Set your desired text size here + ), + ), + ), + dropdownDecoratorProps: DropDownDecoratorProps( + dropdownSearchDecoration: InputDecoration( + contentPadding: EdgeInsets.symmetric(horizontal: 1), + border: InputBorder.none, + ), + ), + onChanged: widget.isViewMode + ? null + : (Map? newValue) { + setState(() { + selectedFuncDept = + newValue?['dropdown_key'].toString(); + }); + print("selectedFuncDept - $selectedFuncDept"); + }, + dropdownBuilder: (context, selectedItem) => Align( + alignment: Alignment.centerLeft, + child: Text( + selectedItem?['dropdown_value'] ?? '', + style: TextStyle(fontSize: 12), + ), + ), + ), + ), + ), + ], + ), ]; } @@ -1794,78 +2013,6 @@ class CreateNewPlansState extends State { } Widget _buildNonDescriptionColumn() { - // if (apiData == null) { - // return Center(child: CircularProgressIndicator()); // Show loading indicator - // } - - // 'plan_purpose_of_travel' Starts ------------------------------------------------------ - - // List purposeList = apiData?['plan_purpose_of_travel'] ?? []; - List> purposeList = List>.from( - apiData?['plan_purpose_of_travel'] ?? []); - - List> dropdownItems = purposeList - .map((item) => DropdownMenuItem( - // value: item['dropdown_key'], - value: item['dropdown_key']?.toString(), - // value: item['dropdown_key'].toString(), - child: Text(item['dropdown_value']), - )) - .toList(); - - if (dropdownItems.isEmpty) { - dropdownItems.add( - DropdownMenuItem( - // value: null, - value: "1", - child: Text("No options available", - style: TextStyle(color: Colors.grey)), - ), - ); - } - - selectedPurpose ??= dropdownItems.isNotEmpty - ? dropdownItems.first.value.toString() - : "No options"; - - print( - "Dropdown Purpose List: ${dropdownItems.map((e) => e.value).toList()}"); - print("Selected Purpose: $selectedPurpose"); - - // 'plan_functional_department' Starts --------------------------------------------- - - // List funcDeptList = apiData?['plan_functional_department'] ?? []; - List> funcDeptList = List>.from( - apiData?['plan_functional_department'] ?? []); - - List> dropdownFuncDeptItems = funcDeptList - .map((item) => DropdownMenuItem( - // value: item['dropdown_key'], - value: item['dropdown_key']?.toString(), - child: Text(item['dropdown_value']), - )) - .toList(); - - if (dropdownFuncDeptItems.isEmpty) { - dropdownFuncDeptItems.add( - DropdownMenuItem( - // value: null, - value: "1", - child: Text("No options available", - style: TextStyle(color: Colors.grey)), - ), - ); - } - - // selectedFuncDept ??= dropdownFuncDeptItems.isNotEmpty ? dropdownFuncDeptItems.first.value.toString() : null; - selectedFuncDept ??= dropdownFuncDeptItems.isNotEmpty - ? dropdownFuncDeptItems.first.value.toString() - : "No options"; - - print( - "Dropdown Functional Department List: ${dropdownFuncDeptItems.map((e) => e.value).toList()}"); - print("Selected Functional Department: $selectedFuncDept"); - return Column(children: [ Row( children: [ @@ -1873,7 +2020,7 @@ class CreateNewPlansState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - "Purpose of Travel *", // Your label + "Trip Name", // Your label style: TextStyle( fontSize: 12, fontWeight: FontWeight.w600, @@ -1881,112 +2028,25 @@ class CreateNewPlansState extends State { ), SizedBox(height: 5), CustomTextFieldWrapper( - isFocused: false, // Dropdown doesn't use focus + isFocused: _isTripTitleFocused, isDesktop: widget.isDesktop, child: SizedBox( - height: 45, // Set appropriate height - width: double.infinity, - child: apiData == null - ? Center( - child: - CircularProgressIndicator()) // Show loading inside dropdown - : DropdownSearch>( - selectedItem: purposeList.firstWhere( - (item) => - item['dropdown_key'].toString() == - selectedPurpose, - orElse: () => - {}, // ✅ Safe fallback - ), - items: purposeList, - itemAsString: (Map item) => - item['dropdown_value'], - popupProps: PopupProps.menu( - showSearchBox: true, - fit: FlexFit.loose, - constraints: BoxConstraints(maxHeight: 200), - searchFieldProps: TextFieldProps( - decoration: InputDecoration( - hintText: "Search Purpose...", - hintStyle: TextStyle( - fontSize: 13, color: Colors.grey), - contentPadding: - EdgeInsets.symmetric(horizontal: 10), - ), - style: TextStyle(fontSize: 13)), - menuProps: MenuProps( - backgroundColor: Colors.white, - ), - itemBuilder: (context, item, isSelected) => Padding( - padding: const EdgeInsets.symmetric( - horizontal: 8.0, vertical: 6.0), - child: Text( - item['dropdown_value'], - style: TextStyle( - fontSize: - 13), // 👈 Set your desired text size here - ), - ), - ), - dropdownDecoratorProps: DropDownDecoratorProps( - dropdownSearchDecoration: InputDecoration( - contentPadding: - EdgeInsets.symmetric(horizontal: 1), - border: InputBorder.none, - ), - ), - onChanged: widget.isViewMode - ? null - : (Map? newValue) { - setState(() { - selectedPurpose = - newValue?['dropdown_key'].toString(); - }); - print("selectedPurpose - $selectedPurpose"); - }, - dropdownBuilder: (context, selectedItem) => Align( - alignment: Alignment.centerLeft, - child: Text( - selectedItem?['dropdown_value'] ?? '', - style: TextStyle(fontSize: 12), - ), - ), - ), + height: 35, + child: TextField( + focusNode: _tripTitleFocusNode, + controller: _tripTitleController, + style: TextStyle(fontSize: 12), + enabled: !widget.isViewMode, + decoration: InputDecoration( + labelText: "Trip Name", + labelStyle: TextStyle(fontSize: 12, color: Colors.grey), + floatingLabelBehavior: FloatingLabelBehavior.never, + border: InputBorder.none, + contentPadding: EdgeInsets.symmetric(vertical: 16), + ), + ), ), ), - - // CustomTextFieldWrapper( - // isFocused: false, // Dropdown doesn't use focus - // isDesktop: widget.isDesktop, - // child: SizedBox( - // height: 45, // Set appropriate height - // child: apiData == null - // ? Center( - // child: - // CircularProgressIndicator()) // Show loading inside dropdown - // : DropdownButtonFormField( - // value: selectedPurpose, - // style: TextStyle(fontSize: 12), - // decoration: InputDecoration( - // border: InputBorder.none, - // contentPadding: EdgeInsets.symmetric( - // horizontal: 10), // Proper padding - // ), - // onChanged: widget.isViewMode - // ? null - // : purposeList.isNotEmpty - // ? (newValue) { - // setState(() { - // selectedPurpose = newValue; - // }); - // print( - // "selectedPurpose - $selectedPurpose"); - // } - // : null, - // items: dropdownItems, - // ), - // ), - // ), ], ), ], @@ -1994,120 +2054,6 @@ class CreateNewPlansState extends State { SizedBox( height: 8, ), - Row( - children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "Functional Department", // Your label - style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.w600, - color: Color(0xFF575A74)), - ), - SizedBox(height: 5), - CustomTextFieldWrapper( - isFocused: false, // Dropdown doesn't use focus - isDesktop: widget.isDesktop, - child: SizedBox( - height: 45, // Set appropriate height - child: apiData == null - ? Center( - child: - CircularProgressIndicator()) // Show loading inside dropdown - : DropdownSearch>( - selectedItem: funcDeptList.firstWhere( - (item) => - item['dropdown_key'].toString() == - selectedFuncDept, - orElse: () => - {}, // ✅ Safe fallback - ), - items: funcDeptList, - itemAsString: (Map item) => - item['dropdown_value'], - popupProps: PopupProps.menu( - showSearchBox: true, - fit: FlexFit.loose, - constraints: BoxConstraints(maxHeight: 180), - searchFieldProps: TextFieldProps( - decoration: InputDecoration( - hintText: "Search department...", - hintStyle: TextStyle( - fontSize: 13, color: Colors.grey), - contentPadding: - EdgeInsets.symmetric(horizontal: 10), - ), - style: TextStyle(fontSize: 13)), - menuProps: MenuProps( - backgroundColor: Colors.white, - ), - itemBuilder: (context, item, isSelected) => Padding( - padding: const EdgeInsets.symmetric( - horizontal: 8.0, vertical: 6.0), - child: Text( - item['dropdown_value'], - style: TextStyle( - fontSize: - 13), // 👈 Set your desired text size here - ), - ), - ), - dropdownDecoratorProps: DropDownDecoratorProps( - dropdownSearchDecoration: InputDecoration( - contentPadding: - EdgeInsets.symmetric(horizontal: 1), - border: InputBorder.none, - ), - ), - onChanged: widget.isViewMode - ? null - : (Map? newValue) { - setState(() { - selectedFuncDept = - newValue?['dropdown_key'].toString(); - }); - print("selectedFuncDept - $selectedFuncDept"); - }, - dropdownBuilder: (context, selectedItem) => Align( - alignment: Alignment.centerLeft, - child: Text( - selectedItem?['dropdown_value'] ?? '', - style: TextStyle(fontSize: 12), - ), - ), - ), - - // DropdownButtonFormField( - // value: selectedFuncDept, - // style: TextStyle(fontSize: 12), - // decoration: InputDecoration( - // border: InputBorder.none, - // contentPadding: EdgeInsets.symmetric( - // horizontal: 10), // Proper padding - // ), - // onChanged: widget.isViewMode - // ? null - // : funcDeptList.isNotEmpty - // ? (newValue) { - // setState(() { - // selectedFuncDept = newValue; - // }); - // } - // : null, - // items: dropdownFuncDeptItems, - // ), - // - ), - ), - ], - ), - ], - ), - SizedBox( - height: 15, - ), ]); } @@ -2127,28 +2073,56 @@ class CreateNewPlansState extends State { color: Color(0xFF575A74)), ), SizedBox(height: 5), + CustomTextFieldWrapper( isFocused: _isdescriptionFocused, - width: isDesktop - ? MediaQuery.of(context).size.width * 0.4 - : MediaQuery.of(context).size.width * 0.85, isDesktop: widget.isDesktop, - child: TextField( - focusNode: _descriptionFocusNode, - controller: _descriptionController, - maxLines: 6, - keyboardType: TextInputType.multiline, - style: TextStyle(fontSize: 12), - enabled: !widget.isViewMode, - decoration: InputDecoration( - labelText: "Description", - 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.42 + : MediaQuery.of(context).size.width * 0.85, + child: SizedBox( + height: 35, + child: TextField( + focusNode: _descriptionFocusNode, + controller: _descriptionController, + style: TextStyle(fontSize: 12), + enabled: !widget.isViewMode, + decoration: InputDecoration( + labelText: "Description", + labelStyle: TextStyle(fontSize: 12, color: Colors.grey), + floatingLabelBehavior: FloatingLabelBehavior.never, + border: InputBorder.none, + contentPadding: EdgeInsets.symmetric(vertical: 16), + ), ), ), ), + // CustomTextFieldWrapper( + // isFocused: _isdescriptionFocused, + // width: isDesktop + // ? MediaQuery.of(context).size.width * 0.4 + // : MediaQuery.of(context).size.width * 0.85, + // isDesktop: widget.isDesktop, + // child: SizedBox( + // height: 35, + // child: + // TextField( + // focusNode: _descriptionFocusNode, + // controller: _descriptionController, + // // maxLines: 1, + // // keyboardType: TextInputType.multiline, + // style: TextStyle(fontSize: 12), + // enabled: !widget.isViewMode, + // decoration: InputDecoration( + // labelText: "Description", + // labelStyle: TextStyle(fontSize: 12, color: Colors.grey), + // floatingLabelBehavior: FloatingLabelBehavior.never, + // border: InputBorder.none, + // contentPadding: EdgeInsets.symmetric(vertical: 1), + // ), + // ), + // ), + // ), ], ), ], diff --git a/lib/Screens/plans/dynamic_itinerary_stepper.dart b/lib/Screens/plans/dynamic_itinerary_stepper.dart index f34b2b6..a1eacb0 100644 --- a/lib/Screens/plans/dynamic_itinerary_stepper.dart +++ b/lib/Screens/plans/dynamic_itinerary_stepper.dart @@ -75,19 +75,6 @@ class _DynamicItineraryState extends State { "Forex": [], }; - // Store form values for each tab - final Map> 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 { 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 { 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 { .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 { 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 newData) { setState(() { if (!itineraryData.containsKey(type)) { @@ -488,12 +469,13 @@ class _DynamicItineraryState extends State { 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 { 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 { loginUser: widget.loginUser, onSaveVisa: (data) => handleItineraryUpdate("Visa", data), selectedItem: selectedItem, + flightData: itineraryData["Flight"]!, ); break; case "Miscellaneous": @@ -580,66 +565,169 @@ class _DynamicItineraryState extends State { 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 { ), ), 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 { 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 { ) : 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), ], ), ), diff --git a/lib/Screens/plans/list_plans.dart b/lib/Screens/plans/list_plans.dart index 189f551..e7ef81f 100644 --- a/lib/Screens/plans/list_plans.dart +++ b/lib/Screens/plans/list_plans.dart @@ -24,6 +24,9 @@ class ListPlans extends StatefulWidget { class _ListPlansState extends State { final ApiService apiService = ApiService(); + int currentPage = 0; + int itemsPerPage = 8; + late Future> futurePlans; String? userId; String? orgId; @@ -285,7 +288,7 @@ class _ListPlansState extends State { 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 { 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 { ), ), // SizedBox(width: 16), + Spacer(), // ElevatedButton( // style: ElevatedButton.styleFrom( @@ -486,6 +490,11 @@ class _ListPlansState extends State { plans.sort((a, b) => int.parse(b.planId).compareTo(int.parse(a.planId))); + List 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 { 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 { 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 { ), 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 { ); 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, + ), + ], + ), + ], + ), ); }, ) diff --git a/lib/Screens/userManagement/create_user/create_user.dart b/lib/Screens/userManagement/create_user/create_user.dart index 60ee24b..dfa7ab2 100644 --- a/lib/Screens/userManagement/create_user/create_user.dart +++ b/lib/Screens/userManagement/create_user/create_user.dart @@ -42,7 +42,7 @@ class _CreateUserFormState extends State { // Map to store controllers dynamically final Map controllers = {}; bool isViewMode = false; - bool isEditProfile = true; + bool isEditProfile = false; // late final List? apiCountryData ; late List? apiCountryData; @@ -2932,11 +2932,11 @@ class _CreateUserFormState extends State { 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 diff --git a/lib/app.dart b/lib/app.dart index 2e1bf1b..3dddd1b 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -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 createState() => _MyAppState(); +} + +class _MyAppState extends State { + @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, ); } -} \ No newline at end of file +} diff --git a/lib/main.dart b/lib/main.dart index 056f69c..61e84a1 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; import 'app.dart'; void main() { diff --git a/lib/routes/custom_appBar.dart b/lib/routes/custom_appBar.dart index 7347402..1f608a5 100644 --- a/lib/routes/custom_appBar.dart +++ b/lib/routes/custom_appBar.dart @@ -138,6 +138,7 @@ class _CustomAppBarState extends State { return AppBar( backgroundColor: Colors.white, surfaceTintColor: Colors.white, + // elevation: 3, automaticallyImplyLeading: false, // leading: showBackButton @@ -147,176 +148,209 @@ class _CustomAppBarState extends State { // ) // : 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( - 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( + 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> 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> menuItems = [ PopupMenuItem buildMenuItem(Map item) { return PopupMenuItem( + 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, + ), + ), + ], ), ); } diff --git a/lib/routes/custom_drawer.dart b/lib/routes/custom_drawer.dart index 31188f3..3fb2ebf 100644 --- a/lib/routes/custom_drawer.dart +++ b/lib/routes/custom_drawer.dart @@ -124,8 +124,8 @@ class _CustomDrawerState extends State { @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( diff --git a/lib/services/apiService.dart b/lib/services/apiService.dart index e3df654..cba8872 100644 --- a/lib/services/apiService.dart +++ b/lib/services/apiService.dart @@ -447,4 +447,41 @@ class ApiService { throw Exception('Failed to load organizations'); } } + + // Flight From - To + + Future> 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'); + } + } }