import 'dart:convert'; import 'package:dropdown_search/dropdown_search.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:http/http.dart' as http; import '../../config/apiUrl.dart'; import '../../services/apiService.dart'; import '../../utils/auth_utils.dart'; import '../../widgets/custom_text_forex.dart'; import 'hotels_list.dart'; class HotelsData extends StatefulWidget { final Future> Function() fetchGetHotels; final bool isDesktop; final Color? layoutColor; final int? hotelsId; // <-- Add this final Map? hotelsData; const HotelsData({ super.key, required this.isDesktop, this.layoutColor, required this.fetchGetHotels, this.hotelsId, this.hotelsData, }); @override HotelsDataState createState() => HotelsDataState(); } class HotelsDataState extends State { final ApiService apiService = ApiService(); Map countryMap = {}; late List? apiCountryData; late List? apiAirlineCountryData; Map? apiData; final Map controllers = {}; Map errorMessages = {}; Map focusNodes = {}; Map focusStates = {}; List countryList = []; String? selectedCountry; String? selectedCountryName; String? selectedCity; String? selectedDuration; String? selectedPerdiemAmount; String? userId; int? hotelsDataId; late String isActive = "1"; bool isDisable = false; List dataHeader = [ "hotel_name", "hotel_chain", "category", "country_code", "city", ]; Map hotels_Details() { final data = { "hotel_name": controllers["hotel_name"]?.text, "hotel_chain": controllers["hotel_chain"]?.text, "category": controllers["category"]?.text, "country_code": selectedCountry, "country_name": selectedCountryName, "city": controllers["city"]?.text, "created_by": userId, "is_active": isActive, }; return data; } @override void initState() { super.initState(); apiCountryData = null; apiData = null; for (var field in dataHeader) { controllers[field] = TextEditingController(); focusNodes["${field}FocusNode"] = FocusNode(); focusStates["${field}Focused"] = false; } for (var key in focusNodes.keys) { _addFocusListener(focusNodes[key]!, (focus) { setState(() { focusStates[key.replaceFirst("FocusNode", "Focused")] = focus; }); }); } fetchCountries(); if (widget.hotelsId != null) { print('Editing Hotles ID: ${widget.hotelsId}'); updateHotelsDetails(); } _clearError(); } void _clearError() { setState(() { errorMessages.clear(); }); } @override void dispose() { for (var controller in controllers.values) { controller.dispose(); } for (var node in focusNodes.values) { node.dispose(); } super.dispose(); } void _addFocusListener(FocusNode node, Function(bool) updateState) { node.addListener(() { setState(() { updateState(node.hasFocus); }); }); } void updateHotelsDetails() { print("Update - ${widget.hotelsData}"); final data = widget.hotelsData; if (data == null) return; setState(() { selectedCountry = data['country_code']; // For dropdown selectedCountryName = data['country_name']; // For dropdown label or display controllers['city']?.text = data['city'] ?? ''; controllers['hotel_chain']?.text = data['hotel_chain'] ?? ''; controllers['category']?.text = data['category'] ?? ''; controllers['hotel_name']?.text = data['hotel_name'] ?? ''; isActive = data["is_active"]; final hotelsId = int.tryParse(data['hotel_id'].toString()); hotelsDataId = hotelsId; }); } Future fetchCountries() async { try { List countries = await apiService.fetchCountryList(); setState(() { apiCountryData = countries; }); } catch (e) { print('Error fetching country list: $e'); } } void toggleStatus() { setState(() { isActive = isActive == "1" ? "0" : "1"; }); } bool validateData() { errorMessages.clear(); final data = { "hotel_name": controllers["hotel_name"]?.text, "hotel_chain": controllers["hotel_chain"]?.text, "category": controllers["category"]?.text, "country_code": selectedCountry, "country": selectedCountryName, "city": controllers["city"]?.text, }; final requiredFields = [ "hotel_name", "hotel_chain", "country_code", "city", ]; // 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; } Future handleSubmit() async { userId = await getUserId(); setState(() { isDisable = true; // This triggers UI rebuild with error messages if (validateData()) { postHotelsData(); }else{ isDisable = false; } }); final hotelsData1 = hotels_Details(); print("submit data - $hotelsData1"); } Future postHotelsData({int isActive = 1}) async { final hotelsData = hotels_Details(); final String apiUrldata; if (hotelsDataId != null) { print("for edit hotel id - $hotelsDataId"); apiUrldata = '$apiUrl/api/updateHotels/$hotelsDataId'; hotelsData["hotel_id"] = hotelsDataId.toString(); hotelsData["updated_by"] = userId; (hotelsData.containsKey("created_by")) ? hotelsData.remove("created_by") : ''; (hotelsData.containsKey("country_name")) ? hotelsData.remove("country_name") : ''; } else { print("for add Hotel id - null"); apiUrldata = '$apiUrl/api/createHotels'; print("called apiUrl - $apiUrldata"); hotelsData["created_by"] = userId; (hotelsData.containsKey("country_name")) ? hotelsData.remove("country_name") : ''; } final token = await getToken(); // Fetch token if (token == null) { throw Exception('Token not found. Please log in.'); } try { final uri = Uri.parse(apiUrldata); final headers = { 'Authorization': 'Bearer $token', 'Content-Type': 'application/json', }; final body = jsonEncode(hotelsData); final response = hotelsDataId != null ? await http.put(uri, headers: headers, body: body) : await http.post(uri, headers: headers, body: body); if (response.statusCode == 200 || response.statusCode == 201) { print("Hotels Details Created successfully!"); print("Response: ${response.body}"); _clearError(); await widget.fetchGetHotels(); if (context.mounted) { Navigator.of(context).pop(); // Close modal only if mounted } setState(() { isDisable = false; }); // Do NOT re-enable here if success } else if (response.statusCode == 404) { if (context.mounted) { Navigator.of(context).pop(); } final message = jsonDecode(response.body)['message'] ?? 'Unknown error'; ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(message), backgroundColor: Colors.redAccent, behavior: SnackBarBehavior.floating, ), ); setState(() { isDisable = false; }); } else { print("Failed to submit plan. Status: ${response.statusCode}"); print("Error: ${response.body}"); setState(() { isDisable = false; }); } } catch (e) { print("Error submitting plan: $e"); setState(() { isDisable = false; }); } } @override Widget build(BuildContext context) { late Map countryMap; // Mapping country_code -> country_name late List countryCodes; // List of country codes // countryList = []; countryList = apiCountryData ?? []; // Map country codes to country names // countryMap = { // for (var item in countryList) // item['country_code'] as String: item['country_name'] as String, // }; countryMap = { for (var country in countryList) (country['country_code'] ?? ''): '${country['country_name'] ?? ''} (${country['country_code'] ?? ''})', }; // Extract only country codes for processing countryCodes = countryMap.keys.toList(); selectedCountry ??= null; return AlertDialog( backgroundColor: Colors.white, contentPadding: const EdgeInsets.fromLTRB(34, 30, 34, 30), // contentPadding: const EdgeInsets.fromLTRB(24, 20, 24, 10), shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), content: Column( mainAxisSize: MainAxisSize.min, children: [ // Row 1: Title + Edit + Delete buttons Row( children: [ Text( (hotelsDataId != null) ? 'Edit Hotels' : 'Create Hotels', style: GoogleFonts.poppins(fontSize: 15, color: Colors.black), ), const Spacer(), ], ), const SizedBox(height: 2), Divider(thickness: 0.2, color: Colors.blueGrey.shade100), const SizedBox(height: 10), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Hotel Name *", style: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w600, color: Color(0xFF575A74), ), ), SizedBox(height: 5), CustomTextFieldForexWrapper( // isFocused: false, isFocused: focusStates["hotel_nameFocused"] ?? false, isDesktop: widget.isDesktop, color: Colors.transparent, child: SizedBox( height: 40, child: TextField( focusNode: focusNodes["hotel_nameFocusNode"], controller: controllers["hotel_name"], style: const TextStyle(fontSize: 12), decoration: const InputDecoration( labelText: "Hotel Name", labelStyle: TextStyle(fontSize: 11, 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), ), ], ], ), const SizedBox(height: 5), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Hotel Chain *", style: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w600, color: Color(0xFF575A74), ), ), SizedBox(height: 5), CustomTextFieldForexWrapper( isFocused: focusStates["hotel_chainFocused"] ?? false, isDesktop: widget.isDesktop, color: Colors.transparent, child: SizedBox( height: 40, child: TextField( focusNode: focusNodes["hotel_chainFocusNode"], controller: controllers["hotel_chain"], style: const TextStyle(fontSize: 12), decoration: const InputDecoration( labelText: "Hotel Chain", labelStyle: TextStyle(fontSize: 11, color: Colors.grey), floatingLabelBehavior: FloatingLabelBehavior.never, border: InputBorder.none, contentPadding: EdgeInsets.symmetric(vertical: 16), ), ), ), ), if (errorMessages["hotel_chain"] != null) ...[ SizedBox(height: 5), // Space before error message Text( errorMessages["hotel_chain"]!, style: TextStyle(color: Colors.red, fontSize: 12), ), ], ], ), const SizedBox(height: 5), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Category", style: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w600, color: Color(0xFF575A74), ), ), SizedBox(height: 5), CustomTextFieldForexWrapper( // isFocused: false, isFocused: focusStates["categoryFocused"] ?? false, isDesktop: widget.isDesktop, color: Colors.transparent, child: SizedBox( height: 40, child: TextField( focusNode: focusNodes["categoryFocusNode"], controller: controllers["category"], inputFormatters: [ FilteringTextInputFormatter.allow(RegExp(r'[a-zA-Z0-9 ]')), ], style: const TextStyle(fontSize: 12), decoration: const InputDecoration( labelText: "Category", labelStyle: TextStyle(fontSize: 11, color: Colors.grey), floatingLabelBehavior: FloatingLabelBehavior.never, border: InputBorder.none, contentPadding: EdgeInsets.symmetric(vertical: 16), ), ), ), ), ], ), const SizedBox(height: 10), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "Country *", style: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w600, color: Color(0xFF575A74), ), ), SizedBox(height: 5), CustomTextFieldForexWrapper( isFocused: false, padding: const EdgeInsets.symmetric(horizontal: 0), isDesktop: widget.isDesktop, child: SizedBox( height: 40, width: double.infinity, child: Focus( focusNode: focusNodes["country_codeFocusNode"], onFocusChange: (hasFocus) { setState(() { focusStates["country_codeFocused"] = hasFocus; }); }, child: GestureDetector( onTap: () { // Request focus when user taps focusNodes["country_codeFocusNode"]?.requestFocus(); }, child: DropdownSearch( selectedItem: countryMap[selectedCountry], popupProps: PopupProps.menu( showSearchBox: true, // Enables search functionality menuProps: const MenuProps(backgroundColor: Colors.white), constraints: BoxConstraints(maxHeight: 200), itemBuilder: (context, item, isSelected) { print("contryItem - $item"); final match = RegExp(r'^(.*)\s\((.*)\)$',).firstMatch(item); final countryName = match?.group(1) ?? ''; final countryCode = match?.group(2) ?? ''; return Padding( padding: const EdgeInsets.symmetric( horizontal: 8.0, vertical: 0.02, ), child: Padding( padding: const EdgeInsets.all(8.0), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( countryName, style: GoogleFonts.poppins(fontSize: 11.5), ), Text( countryCode, style: GoogleFonts.poppins(fontSize: 11.5,color:Colors.grey), ), ], ), ), );}, searchFieldProps: TextFieldProps( decoration: InputDecoration( hintText: "Search ...", hintStyle: GoogleFonts.poppins(fontSize: 11), contentPadding: EdgeInsets.symmetric(horizontal: 1,vertical: 1), ), ), ), items: countryMap.values.toList(), dropdownDecoratorProps: DropDownDecoratorProps( dropdownSearchDecoration: InputDecoration( // border: InputBorder.none, border: OutlineInputBorder( borderRadius: BorderRadius.circular(8), borderSide: BorderSide( color: (focusStates["country_codeFocused"] ?? false) ? widget.layoutColor! : Colors.white, // width: 0.5, ), ), enabledBorder: OutlineInputBorder( borderSide: BorderSide( color: (focusStates["country_codeFocused"] ?? false) ? widget.layoutColor! : Colors.white, // : const Color(0xFFD6D5E6), // width: 0.5, // const Color(0xFFD6D5E6), ), ), focusedBorder: OutlineInputBorder( borderSide: BorderSide(color: widget.layoutColor!, width: 1), ), contentPadding: EdgeInsets.symmetric(horizontal: 10.0, vertical: 8.0,), ), ), dropdownBuilder: (context, selectedItem) => Align( // Center-align selected item alignment: Alignment.centerLeft, child: Text( selectedItem ?? "Select ", style: GoogleFonts.poppins(fontSize: 11), ), ), onChanged: (String? newValue) { setState(() { // Find the country_code based on selected country_name selectedCountry = countryMap.entries .firstWhere((entry) => entry.value == newValue) .key; final match = RegExp(r'^(.*)\s\((.*)\)$').firstMatch(newValue!); final countryName = match?.group(1) ?? newValue; // --> "Ascension Islands" // final countryCode = match?.group(2) ?? ""; selectedCountryName = countryName; }); }, ), ) ) ), ), if (errorMessages["country_code"] != null) ...[ SizedBox(height: 5), // Space before error message Text( errorMessages["country_code"]!, style: TextStyle(color: Colors.red, fontSize: 12), ), ], ], ), const SizedBox(height: 10), Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( "City *", style: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w600, color: Color(0xFF575A74), ), ), SizedBox(height: 5), CustomTextFieldForexWrapper( isFocused: focusStates["cityFocused"] ?? false, isDesktop: widget.isDesktop, color: Colors.transparent, child: SizedBox( height: 40, child: TextField( focusNode: focusNodes["cityFocusNode"], controller: controllers["city"], style: const TextStyle(fontSize: 12), decoration: const InputDecoration( labelText: "City", labelStyle: TextStyle(fontSize: 11, color: Colors.grey), floatingLabelBehavior: FloatingLabelBehavior.never, border: InputBorder.none, contentPadding: EdgeInsets.symmetric(vertical: 16), ), ), ), ), if (errorMessages["city"] != null) ...[ SizedBox(height: 5), // Space before error message Text( errorMessages["city"]!, style: TextStyle(color: Colors.red, fontSize: 12), ), ], ], ), SizedBox(height: 10), // if (isDesktop)SizedBox(width: 8,) else SizedBox(height: 8,), if (hotelsDataId != null) Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( "Change Status ", style: GoogleFonts.poppins( fontSize: 12, fontWeight: FontWeight.w600, color: Color(0xFF575A74), ), ), Tooltip( message: isActive == "1" ? "Tap to deactivate" : "Tap to activate", child: GestureDetector( onTap: toggleStatus, child: Text( isActive == "1" ? "Active" : "Inactive", style: TextStyle( fontSize: 13, fontFamily: "Inter", color: isActive == "1" ? Colors.green : Colors.red, ), ), ), ), ], ), if (hotelsDataId != null) SizedBox(height: 15), Row( mainAxisAlignment: MainAxisAlignment.end, children: [ // SizedBox( // child: ElevatedButton( // onPressed: () { // // You can get text from commentController.text // Navigator.of(context).pop(); // Close the modal // }, // style: ElevatedButton.styleFrom( // backgroundColor: widget.layoutColor, // shape: RoundedRectangleBorder( // borderRadius: BorderRadius.circular(8), // ), // ), // child: Text('Cancel', // style: GoogleFonts.poppins( // fontSize: 13, color: Colors.white)), // ), // ), // SizedBox( // width: 10, // ), SizedBox( child: ElevatedButton( onPressed: isDisable ? null : () async { setState(() { isDisable = true; }); await handleSubmit(); // You can get text from commentController.text // Navigator.of(context).pop(); // Close the modal // Optional: re-enable only on error // setState(() { // isDisable = false; // }); }, style: ElevatedButton.styleFrom( backgroundColor: widget.layoutColor, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8), ), ), child: Text( 'Save', style: GoogleFonts.poppins( fontSize: 11, color: Colors.white, ), ), ), ), ], ), // : SizedBox.shrink(), ], ), ); } }