import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; import 'package:responsive_builder/responsive_builder.dart'; import '../../widgets/custom_text_field.dart'; import '../../widgets/custom_text_itnerary_sub.dart'; class AccomodationScreen extends StatefulWidget { final Function(bool) onClose; // Callback function final Function(Map) onSaveAccomadation; final Map? selectedItem; final String? loginUser; AccomodationScreen({ required this.onClose, required this.onSaveAccomadation, required this.selectedItem, required this.loginUser}); @override _AccomodationScreenState createState() => _AccomodationScreenState(); } class _AccomodationScreenState extends State { final GlobalKey _formKey = GlobalKey(); final FocusNode _destinationFocusNode = FocusNode(); final FocusNode _hotelNameFocusNode = FocusNode(); final FocusNode _checkInFocusNode = FocusNode(); final FocusNode _checkInTimeFocusNode = FocusNode(); final FocusNode _checkOutFocusNode = FocusNode(); final FocusNode _checkOutTimeFocusNode = FocusNode(); final FocusNode _commentsFocusNode = FocusNode(); late TextEditingController _destinationController = TextEditingController(); late TextEditingController _hotelNameController = TextEditingController(); late TextEditingController _checkInController = TextEditingController(); late TextEditingController _checkInTimeController = TextEditingController(); late TextEditingController _checkOutController = TextEditingController(); late TextEditingController _checkOutTimeController = TextEditingController(); late TextEditingController _commentsController = TextEditingController(); bool _destinationFocused = false; bool _isHotelNameFocused = false; bool _checkInFocus = false; bool _checkInTimeFocus = false; bool _checkOutFocus = false; bool _checkOutTimeFocus = false; bool _commentsFocus = false; void _addFocusListener(FocusNode node, Function(bool) onFocusChange) { node.addListener(() { setState(() { onFocusChange(node.hasFocus); }); }); } Map errorMessages = {}; Map get accomadationData { Map data ={ "destination_city": _destinationController.text, "hotel_name": _hotelNameController.text, "checkin_date": _checkInController.text , "checkin_time": _checkInTimeController.text, "checkout_date": _checkOutController.text, "checkout_time": _checkOutTimeController.text, "comments": _commentsController.text, "created_by": widget.loginUser, "updated_by": widget.loginUser, }; if (widget.selectedItem != null) { if (widget.selectedItem?["indx"] != null && widget.selectedItem?["indx"] != 0) { data["indx"] = widget.selectedItem!["indx"]; } else if (widget.selectedItem?["accomodation_id"] != null && widget.selectedItem?["accomodation_id"] != 0) { data["accomodation_id"] = widget.selectedItem!["accomodation_id"]; } } return data; } TextEditingController initController(String key) { return TextEditingController(text: widget.selectedItem?[key] ?? ""); } @override void initState() { super.initState(); _addFocusListener(_destinationFocusNode, (focus) => _destinationFocused = focus); _addFocusListener(_hotelNameFocusNode, (focus) => _isHotelNameFocused = focus); _addFocusListener(_checkInFocusNode, (focus) => _checkInFocus = focus); _addFocusListener(_checkInTimeFocusNode, (focus) => _checkInTimeFocus = focus); _addFocusListener(_checkOutFocusNode, (focus) => _checkOutFocus = focus); _addFocusListener(_checkOutTimeFocusNode, (focus) => _checkOutTimeFocus = focus); _addFocusListener(_commentsFocusNode, (focus) => _commentsFocus = focus); _destinationController = initController("destination_city"); _hotelNameController = initController("hotel_name"); _checkInController = initController("checkin_date"); _checkInTimeController = initController("checkin_time"); _checkOutController = initController("checkout_date"); _checkOutTimeController = initController("checkout_time"); _commentsController = initController("comments"); _destinationController.addListener(() => _clearError("destination_city")); _hotelNameController.addListener(() => _clearError("hotel_name")); _checkInController.addListener(() => _clearError("checkin_date")); _checkInTimeController.addListener(() => _clearError("checkin_time")); _checkOutController.addListener(() => _clearError("checkout_date")); _checkOutTimeController.addListener(() => _clearError("checkout_time")); } @override void dispose() { _destinationFocusNode.dispose(); _destinationController.dispose(); _hotelNameController.dispose(); _checkInController.dispose(); _checkInTimeController.dispose(); _checkOutController.dispose(); _checkOutTimeController.dispose(); _commentsController.dispose(); super.dispose(); } void _clearError(String field) { if (mounted && errorMessages.containsKey(field)) { setState(() { errorMessages.remove(field); }); } } bool isValidData(Map data) { errorMessages.clear(); // Reset errors // Required fields that must not be empty List requiredFields = ["destination_city", "hotel_name","checkin_date","checkin_time","checkout_date", "checkout_time"]; // Check validation for each field for (String field in requiredFields) { if (data[field] == null || data[field].toString().trim().isEmpty) { errorMessages[field] = "Required"; } } return errorMessages.isEmpty; // Valid if there are no errors } void handleSave(){ print( "Handle Save accomadationData $accomadationData"); Map data = accomadationData; if (!isValidData(data)) { print("Validation Failed: Required fields are missing."); setState(() {}); return; // Stop execution if validation fails }else { widget.onSaveAccomadation(accomadationData); } widget.onClose(false);// Close screen after saving } @override Widget build(BuildContext context) { return ResponsiveBuilder(builder: (context, sizingInfo) { bool isMobile = sizingInfo.isMobile; 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: () { 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( child: Column(children: _buildAccomadtionForm(isDesktop)), ), ) ], ), ), ), ); }); } List _buildAccomadtionForm(isDesktop) { return [ isDesktop ? Row( children: _buildFirstRow(isDesktop), ) : Column( children: _buildFirstRow(isDesktop), ), SizedBox(height: 10), // Spacing between first and second row isDesktop ? Row( children: _buildSecondRow(isDesktop), ) : Column( children: _buildSecondRow(isDesktop), ), SizedBox(height: 10), isDesktop ? Row( children: _buildThirdRow(isDesktop), ) : Column( children: _buildThirdRow(isDesktop), ), SizedBox(height: 10), Row( mainAxisAlignment: MainAxisAlignment.end, children: _handleAction(isDesktop), ), ]; } List _buildFirstRow(isDesktop) { return [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Destination", style: TextStyle( fontSize: 12, fontWeight: FontWeight.w600, color: Color(0xFF575A74)), ), SizedBox(height: 5), CustomTextFieldWrapper( isFocused: _destinationFocused, isDesktop: isDesktop, child: SizedBox( height: 40, child: TextField( focusNode: _destinationFocusNode, controller: _destinationController, style: TextStyle(fontSize: 12), decoration: InputDecoration( labelText: "Destination", labelStyle: TextStyle(fontSize: 12, color: Colors.grey), floatingLabelBehavior: FloatingLabelBehavior.never, border: InputBorder.none, contentPadding: EdgeInsets.symmetric(vertical: 16), ), ), ), ), if (errorMessages["destination_city"] != null) ...[ SizedBox(height: 5), // Space before error message Text( errorMessages["destination_city"]!, style: TextStyle(color: Colors.red, fontSize: 12), ), ], ], ), if (isDesktop) Spacer() else SizedBox( height: 8, ), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Hotel Name", style: TextStyle( fontSize: 12, fontWeight: FontWeight.w600, color: Color(0xFF575A74)), ), SizedBox(height: 5), CustomTextFieldWrapper( isFocused: _isHotelNameFocused, isDesktop: isDesktop, child: SizedBox( height: 40, child: TextField( focusNode: _hotelNameFocusNode, // Assign the correct focus node controller: _hotelNameController, style: TextStyle(fontSize: 12), decoration: InputDecoration( labelText: "Hotel Name", labelStyle: TextStyle(fontSize: 12, color: Colors.grey), floatingLabelBehavior: FloatingLabelBehavior.never, border: InputBorder.none, contentPadding: EdgeInsets.symmetric(vertical: 16), ), ), ), ), if (errorMessages["hotel_name"] != null) ...[ SizedBox(height: 5), // Space before error message Text( errorMessages["hotel_name"]!, style: TextStyle(color: Colors.red, fontSize: 12), ), ], ], ), ]; } List _buildSecondRow(bool isDesktop) { DateTime? _selectedCheckInDate; TimeOfDay? _selectedCheckInTime; Future _selectCheckInDate(BuildContext context) async { DateTime now = DateTime.now(); DateTime today = DateTime(now.year, now.month, now.day); 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; _checkInController.text = DateFormat('yyyy-MM-dd').format(pickedDate); }); } } Future _selectCheckInTime(BuildContext context) async { TimeOfDay? pickedTime = await showTimePicker( context: context, initialTime: _selectedCheckInTime ?? TimeOfDay.now(), ); if (pickedTime != null && pickedTime != _selectedCheckInTime) { setState(() { _selectedCheckInTime = pickedTime; // Formatting time to HH:mm (24-hour format) final now = DateTime.now(); final formattedTime = DateFormat('HH:mm').format( DateTime(now.year, now.month, now.day, pickedTime.hour, pickedTime.minute), ); _checkInTimeController.text = formattedTime; }); } } //-------------------------------Check-In End 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 _selectCheckOutTime(BuildContext context) async { TimeOfDay? pickedTime = await showTimePicker( context: context, initialTime: _selectedCheckOutTime ?? TimeOfDay.now(), ); if (pickedTime != null && pickedTime != _selectedCheckOutTime) { setState(() { _selectedCheckOutTime = pickedTime; // Formatting time to HH:mm (24-hour format) final now = DateTime.now(); final formattedTime = DateFormat('HH:mm').format( DateTime(now.year, now.month, now.day, pickedTime.hour, pickedTime.minute), ); _checkOutTimeController.text = formattedTime; }); } } return [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Check-in*", style: TextStyle( fontSize: 12, fontWeight: FontWeight.w600, color: Color(0xFF575A74)), ), SizedBox(height: 5), CustomTextFieldItnerarySubWrapper( isFocused: _checkInFocus, isDesktop: isDesktop, child: SizedBox( height: 40, child: GestureDetector( onTap: () => _selectCheckInDate(context), child: AbsorbPointer( child: TextField( focusNode: _checkInFocusNode, controller: _checkInController, style: const TextStyle(fontSize: 12), decoration: const InputDecoration( labelText: "Select Date", labelStyle: TextStyle(fontSize: 12, color: Colors.grey), floatingLabelBehavior: FloatingLabelBehavior.never, border: InputBorder.none, contentPadding: EdgeInsets.symmetric(vertical: 16), suffixIcon: Icon(Icons.calendar_today, size: 16, color: Colors.grey), ), ), ), ), ), ), if (errorMessages["checkin_date"] != null) ...[ SizedBox(height: 5), // Space before error message Text( errorMessages["checkin_date"]!, style: TextStyle(color: Colors.red, fontSize: 12), ), ], ], ), if (isDesktop) Spacer() else SizedBox( height: 8, ), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Time (Check-in)", style: TextStyle( fontSize: 12, fontWeight: FontWeight.w600, color: Color(0xFF575A74)), ), SizedBox(height: 5), CustomTextFieldItnerarySubWrapper( isFocused: _checkInTimeFocus, isDesktop: isDesktop, child: SizedBox( height: 40, child: GestureDetector( onTap: () => _selectCheckInTime(context), child: AbsorbPointer( child: TextField( focusNode: _checkInTimeFocusNode, controller: _checkInTimeController, style: const TextStyle(fontSize: 12), decoration: const InputDecoration( labelText: "Select Time", labelStyle: TextStyle(fontSize: 12, color: Colors.grey), floatingLabelBehavior: FloatingLabelBehavior.never, border: InputBorder.none, contentPadding: EdgeInsets.symmetric(vertical: 16), suffixIcon: Icon(Icons.access_time, size: 16, color: Colors.grey), ), ), ), ), ), ), if (errorMessages["checkin_time"] != null) ...[ SizedBox(height: 5), // Space before error message Text( errorMessages["checkin_time"]!, style: TextStyle(color: Colors.red, fontSize: 12), ), ], ], ), if (isDesktop) Spacer() else SizedBox( height: 8, ), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Check-out*", style: TextStyle( fontSize: 12, fontWeight: FontWeight.w600, color: Color(0xFF575A74)), ), SizedBox(height: 5), CustomTextFieldItnerarySubWrapper( isFocused: _checkOutFocus, isDesktop: isDesktop, child: SizedBox( height: 40, child: GestureDetector( onTap: () => _selectCheckOutDate(context), child: AbsorbPointer( child: TextField( focusNode: _checkOutFocusNode, controller: _checkOutController, style: const TextStyle(fontSize: 12), decoration: const InputDecoration( labelText: "Select Date", labelStyle: TextStyle(fontSize: 12, color: Colors.grey), floatingLabelBehavior: FloatingLabelBehavior.never, border: InputBorder.none, contentPadding: EdgeInsets.symmetric(vertical: 16), suffixIcon: Icon(Icons.calendar_today, size: 16, color: Colors.grey), ), ), ), ), ), ), if (errorMessages["checkout_date"] != null) ...[ SizedBox(height: 5), // Space before error message Text( errorMessages["checkout_date"]!, style: TextStyle(color: Colors.red, fontSize: 12), ), ], ], ), if (isDesktop) Spacer() else SizedBox( height: 8, ), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Time (Check-out)", style: TextStyle( fontSize: 12, fontWeight: FontWeight.w600, color: Color(0xFF575A74)), ), SizedBox(height: 5), CustomTextFieldItnerarySubWrapper( isFocused: _checkOutTimeFocus, isDesktop: isDesktop, child: SizedBox( height: 40, child: GestureDetector( onTap: () => _selectCheckOutTime(context), child: AbsorbPointer( child: TextField( focusNode: _checkOutTimeFocusNode, controller: _checkOutTimeController, style: const TextStyle(fontSize: 12), decoration: const InputDecoration( labelText: "Select Time", labelStyle: TextStyle(fontSize: 12, color: Colors.grey), floatingLabelBehavior: FloatingLabelBehavior.never, border: InputBorder.none, contentPadding: EdgeInsets.symmetric(vertical: 16), suffixIcon: Icon(Icons.access_time, size: 16, color: Colors.grey), ), ), ), ), ), ), if (errorMessages["checkout_time"] != null) ...[ SizedBox(height: 5), // Space before error message Text( errorMessages["checkout_time"]!, style: TextStyle(color: Colors.red, fontSize: 12), ), ], ], ), ]; } List _buildThirdRow(bool isDesktop) { return [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Comments", style: TextStyle( fontSize: 12, fontWeight: FontWeight.w600, color: Color(0xFF575A74)), ), SizedBox(height: 5), CustomTextFieldWrapper( isFocused: _commentsFocus, // Dropdown doesn't use focus isDesktop: isDesktop, width: isDesktop ? MediaQuery.of(context).size.width * 0.4 : 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), ), ), ), ], ) ]; } List _handleAction(bool isDesktop) { return [ // Close Button ElevatedButton( onPressed: () { widget.onClose(false); // Close the dialog or screen }, style: ElevatedButton.styleFrom( backgroundColor: Colors.grey[400], // Light grey color shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12), ), child: Text( "Close", style: TextStyle(color: Colors.white, fontSize: 14), ), ), SizedBox(width: 10), // Space between buttons // Save Changes Button ElevatedButton( onPressed: () { handleSave(); }, style: ElevatedButton.styleFrom( backgroundColor: Colors.blue, // Primary color for save shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), padding: EdgeInsets.symmetric(horizontal: 20, vertical: 12), ), child: Text( "Save Changes", style: TextStyle(color: Colors.white, fontSize: 14), ), ), ]; } }