/** * Korea Tourism Agency - Main JavaScript * Основные интерактивные функции для сайта */ document.addEventListener('DOMContentLoaded', function() { // ========================================== // Инициализация AOS (Animate On Scroll) // ========================================== if (typeof AOS !== 'undefined') { AOS.init({ duration: 800, easing: 'ease-in-out', once: true, offset: 100 }); } // ========================================== // Навигация и мобильное меню // ========================================== const navbar = document.querySelector('.navbar'); // Добавление класса при скролле window.addEventListener('scroll', function() { if (window.scrollY > 100) { if (navbar) navbar.classList.add('scrolled'); } else { if (navbar) navbar.classList.remove('scrolled'); } }); // ========================================== // Инициализация компонентов бронирования // ========================================== // Компонент для проверки доступности на главной странице const availabilityContainer = document.getElementById('availability-checker-container'); const guideSelectorContainer = document.getElementById('guide-selector-container'); if (availabilityContainer) { const availabilityChecker = new AvailabilityChecker({ container: availabilityContainer, mode: 'detailed', showSuggestions: true, onAvailabilityCheck: function(result) { if (result.availableGuides && result.availableGuides.length > 0) { // Показать селектор гидов если есть доступные if (guideSelectorContainer) { guideSelectorContainer.style.display = 'block'; const guideSelector = new GuideSelector({ container: guideSelectorContainer, mode: 'booking', showAvailability: true, selectedDate: result.date, onGuideSelect: function(guide) { // Перейти к бронированию с выбранным гидом window.location.href = `/routes?guide=${guide.id}&date=${result.date}`; } }); } } else { if (guideSelectorContainer) { guideSelectorContainer.style.display = 'none'; } } } }); } // Календарь гидов на странице гидов const guidesCalendarContainer = document.getElementById('guides-calendar-container'); if (guidesCalendarContainer) { const guidesCalendar = new GuideCalendarWidget({ container: guidesCalendarContainer, mode: 'readonly', showControls: false, showGuideInfo: true }); } // Компоненты бронирования на странице маршрута const bookingAvailabilityContainer = document.getElementById('booking-availability-checker'); const bookingGuideSelectorContainer = document.getElementById('booking-guide-selector'); if (bookingAvailabilityContainer) { const bookingAvailabilityChecker = new AvailabilityChecker({ container: bookingAvailabilityContainer, mode: 'inline', showSuggestions: false, onAvailabilityCheck: function(result) { if (result.availableGuides && result.availableGuides.length > 0) { if (bookingGuideSelectorContainer) { bookingGuideSelectorContainer.style.display = 'block'; const bookingGuideSelector = new GuideSelector({ container: bookingGuideSelectorContainer, mode: 'booking', showAvailability: false, availableGuides: result.availableGuides, onGuideSelect: function(guide) { // Заполнить скрытое поле с ID гида const selectedGuideIdInput = document.getElementById('selectedGuideId'); const preferredDateInput = document.getElementById('preferred_date'); const submitBtn = document.getElementById('submitBookingBtn'); if (selectedGuideIdInput) { selectedGuideIdInput.value = guide.id; } if (preferredDateInput) { preferredDateInput.value = result.date; } if (submitBtn) { submitBtn.disabled = false; } } }); } } } }); } // ========================================== // Поиск по сайту (обновленная версия) // ========================================== const searchInput = document.getElementById('search-input'); const searchResults = document.getElementById('search-results'); if (searchInput && searchResults) { let searchTimeout; searchInput.addEventListener('input', function() { const query = this.value.trim(); clearTimeout(searchTimeout); if (query.length < 2) { searchResults.style.display = 'none'; return; } searchTimeout = setTimeout(() => { performSearch(query); }, 300); }); // Скрытие результатов при клике вне поиска document.addEventListener('click', function(e) { if (!searchInput.contains(e.target) && !searchResults.contains(e.target)) { searchResults.style.display = 'none'; } }); } async function performSearch(query) { try { const response = await fetch('/api/search?q=' + encodeURIComponent(query)); const data = await response.json(); displaySearchResults(data); } catch (error) { console.error('Search error:', error); } } function displaySearchResults(results) { if (!searchResults) return; let html = ''; if (results.routes && results.routes.length > 0) { html += '
투어
'; results.routes.forEach(route => { html += '
'; html += ''; html += '' + (route.name_ko || route.name_en) + ''; html += '' + route.location + ''; html += '
'; }); html += '
'; } if (results.guides && results.guides.length > 0) { html += '
가이드
'; results.guides.forEach(guide => { html += '
'; html += ''; html += '' + guide.name + ''; html += '' + guide.specialization + ''; html += '
'; }); html += '
'; } if (results.articles && results.articles.length > 0) { html += '
기사
'; results.articles.forEach(article => { html += '
'; html += ''; html += '' + (article.title_ko || article.title_en) + ''; html += '
'; }); html += '
'; } if (!html) { html = '
검색 결과가 없습니다
'; } searchResults.innerHTML = html; searchResults.style.display = 'block'; } // ========================================== // Фильтрация туров // ========================================== const routeFilters = document.querySelectorAll('.route-filter'); const routeCards = document.querySelectorAll('.route-card'); routeFilters.forEach(filter => { filter.addEventListener('click', function() { const category = this.dataset.category; // Обновление активного фильтра routeFilters.forEach(f => f.classList.remove('active')); this.classList.add('active'); // Фильтрация карточек routeCards.forEach(card => { if (category === 'all' || card.dataset.category === category) { card.style.display = 'block'; card.classList.add('fade-in'); } else { card.style.display = 'none'; card.classList.remove('fade-in'); } }); }); }); // ========================================== // Форма бронирования // ========================================== const bookingForm = document.getElementById('booking-form'); if (bookingForm) { bookingForm.addEventListener('submit', async function(e) { e.preventDefault(); const submitBtn = this.querySelector('button[type="submit"]'); const originalText = submitBtn.textContent; submitBtn.disabled = true; submitBtn.innerHTML = ' 전송 중...'; try { const formData = new FormData(this); const response = await fetch('/api/booking', { method: 'POST', body: formData }); const result = await response.json(); if (response.ok) { showAlert('success', '예약 요청이 성공적으로 전송되었습니다!'); this.reset(); } else { showAlert('danger', result.error || '오류가 발생했습니다.'); } } catch (error) { console.error('Booking error:', error); showAlert('danger', '네트워크 오류가 발생했습니다.'); } finally { submitBtn.disabled = false; submitBtn.textContent = originalText; } }); } // ========================================== // Форма контактов // ========================================== const contactForm = document.getElementById('contact-form'); if (contactForm) { contactForm.addEventListener('submit', async function(e) { e.preventDefault(); const submitBtn = this.querySelector('button[type="submit"]'); const originalText = submitBtn.textContent; submitBtn.disabled = true; submitBtn.innerHTML = ' 전송 중...'; try { const formData = new FormData(this); const response = await fetch('/api/contact', { method: 'POST', body: formData }); const result = await response.json(); if (response.ok) { showAlert('success', '메시지가 성공적으로 전송되었습니다!'); this.reset(); } else { showAlert('danger', result.error || '오류가 발생했습니다.'); } } catch (error) { console.error('Contact error:', error); showAlert('danger', '네트워크 오류가 발생했습니다.'); } finally { submitBtn.disabled = false; submitBtn.textContent = originalText; } }); } // ========================================== // Галерея изображений // ========================================== const galleryItems = document.querySelectorAll('.gallery-item'); galleryItems.forEach(item => { item.addEventListener('click', function() { const img = this.querySelector('img'); if (img) { showImageModal(img.src, img.alt || 'Gallery Image'); } }); }); function showImageModal(src, alt) { const modal = document.createElement('div'); modal.className = 'image-modal'; modal.innerHTML = '
' + '
' + '' + '' + alt + '' + '
' + '
'; document.body.appendChild(modal); setTimeout(() => modal.classList.add('show'), 10); // Закрытие модального окна const closeModal = () => { modal.classList.remove('show'); setTimeout(() => { if (modal.parentNode) { document.body.removeChild(modal); } }, 300); }; modal.querySelector('.image-modal-close').addEventListener('click', closeModal); modal.querySelector('.image-modal-backdrop').addEventListener('click', function(e) { if (e.target === this) closeModal(); }); document.addEventListener('keydown', function(e) { if (e.key === 'Escape') closeModal(); }); } // ========================================== // Плавная прокрутка к секциям // ========================================== const scrollLinks = document.querySelectorAll('a[href^="#"]'); scrollLinks.forEach(link => { link.addEventListener('click', function(e) { e.preventDefault(); const targetId = this.getAttribute('href').substring(1); const targetElement = document.getElementById(targetId); if (targetElement) { targetElement.scrollIntoView({ behavior: 'smooth', block: 'start' }); } }); }); // ========================================== // Валидация форм // ========================================== const forms = document.querySelectorAll('.needs-validation'); forms.forEach(form => { form.addEventListener('submit', function(e) { if (!form.checkValidity()) { e.preventDefault(); e.stopPropagation(); } form.classList.add('was-validated'); }); }); // ========================================== // Tooltips и Popovers // ========================================== if (typeof bootstrap !== 'undefined') { const tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]')); tooltipTriggerList.map(function (tooltipTriggerEl) { return new bootstrap.Tooltip(tooltipTriggerEl); }); const popoverTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="popover"]')); popoverTriggerList.map(function (popoverTriggerEl) { return new bootstrap.Popover(popoverTriggerEl); }); } // ========================================== // Lazy loading изображений // ========================================== const lazyImages = document.querySelectorAll('img[data-src]'); if ('IntersectionObserver' in window) { const imageObserver = new IntersectionObserver((entries, observer) => { entries.forEach(entry => { if (entry.isIntersecting) { const img = entry.target; img.src = img.dataset.src; img.classList.remove('lazy'); imageObserver.unobserve(img); } }); }); lazyImages.forEach(img => imageObserver.observe(img)); } else { // Fallback для старых браузеров lazyImages.forEach(img => { img.src = img.dataset.src; img.classList.remove('lazy'); }); } // ========================================== // Утилитарные функции // ========================================== function showAlert(type, message) { const alertContainer = document.getElementById('alert-container') || createAlertContainer(); const alert = document.createElement('div'); alert.className = 'alert alert-' + type + ' alert-dismissible fade show'; alert.innerHTML = message + ''; alertContainer.appendChild(alert); // Автоскрытие через 5 секунд setTimeout(() => { if (alert.parentNode) { alert.remove(); } }, 5000); } // ========================================== // Вспомогательные функции для компонентов // ========================================== // Очистка результатов поиска function clearSearchResults() { const resultsContainer = document.getElementById('searchResults'); if (resultsContainer) { resultsContainer.style.display = 'none'; } const guideSelectorContainer = document.getElementById('guide-selector-container'); if (guideSelectorContainer) { guideSelectorContainer.style.display = 'none'; } } // Функция для быстрого бронирования (вызывается из компонентов) function quickBookTour(routeId, guideId, date, peopleCount = 1) { // Создаем модальное окно для быстрого бронирования const modal = document.createElement('div'); modal.className = 'modal fade'; modal.innerHTML = ` `; document.body.appendChild(modal); const bootstrapModal = new bootstrap.Modal(modal); bootstrapModal.show(); // Удаление модального окна после закрытия modal.addEventListener('hidden.bs.modal', function() { document.body.removeChild(modal); }); } // Делаем функции доступными глобально для использования в компонентах window.clearSearchResults = clearSearchResults; window.quickBookTour = quickBookTour; // ========================================== // Утилитарные функции (продолжение) // ========================================== // ========================================== // Финальные утилитарные функции // ========================================== function createAlertContainer() { const container = document.createElement('div'); container.id = 'alert-container'; container.className = 'position-fixed top-0 end-0 p-3'; container.style.zIndex = '9999'; document.body.appendChild(container); return container; } // Функция для форматирования чисел (валюта) function formatNumber(num) { return new Intl.NumberFormat('ru-RU').format(num); } // Делаем утилитарные функции доступными глобально window.formatNumber = formatNumber; console.log('Korea Tourism Agency - JavaScript with components loaded successfully! 🇰🇷'); });