/** * GuideSelector - Компонент для выбора гида * Используется в формах бронирования и админке */ class GuideSelector { constructor(options = {}) { this.container = options.container || document.body; this.mode = options.mode || 'booking'; // 'booking', 'admin', 'simple' this.selectedDate = options.selectedDate || null; this.selectedGuideId = options.selectedGuideId || null; this.onGuideSelect = options.onGuideSelect || null; this.onDateChange = options.onDateChange || null; this.showAvailabilityOnly = options.showAvailabilityOnly !== false; this.multiple = options.multiple || false; this.placeholder = options.placeholder || 'Выберите гида'; this.guides = []; this.schedules = []; this.holidays = []; this.bookings = []; this.filteredGuides = []; this.init(); } async init() { this.render(); await this.loadData(); this.updateGuidesList(); this.bindEvents(); } render() { const modeClass = `guide-selector-${this.mode}`; const multipleClass = this.multiple ? 'guide-selector-multiple' : ''; this.container.innerHTML = `
${this.mode === 'booking' ? `

Выбор гида

Выберите подходящего гида для вашего тура

` : ''}
${this.showAvailabilityOnly ? `
` : ''}
Загрузка гидов...
${this.multiple ? ` ` : ''}
`; this.injectStyles(); } getId() { if (!this._id) { this._id = 'guide-selector-' + Math.random().toString(36).substr(2, 9); } return this._id; } injectStyles() { if (document.getElementById('guide-selector-styles')) return; const styles = ` `; document.head.insertAdjacentHTML('beforeend', styles); } bindEvents() { const dateInput = this.container.querySelector(`#dateInput-${this.getId()}`); const availabilityFilter = this.container.querySelector(`#availabilityFilter-${this.getId()}`); if (dateInput) { dateInput.addEventListener('change', (e) => { this.selectedDate = e.target.value; this.updateGuidesList(); if (this.onDateChange) { this.onDateChange(this.selectedDate); } }); } if (availabilityFilter) { availabilityFilter.addEventListener('change', (e) => { this.showAvailabilityOnly = e.target.checked; this.updateGuidesList(); }); } this.container.addEventListener('click', (e) => { const guideCard = e.target.closest('.guide-card'); if (guideCard && !guideCard.classList.contains('unavailable')) { const guideId = parseInt(guideCard.dataset.guideId); this.selectGuide(guideId); } const removeBtn = e.target.closest('.remove-guide'); if (removeBtn) { const guideId = parseInt(removeBtn.dataset.guideId); this.deselectGuide(guideId); } }); } async loadData() { try { const [guidesRes, schedulesRes, holidaysRes, bookingsRes] = await Promise.all([ fetch('/api/guides'), fetch('/api/guide-schedules'), fetch('/api/holidays'), fetch('/api/bookings') ]); const guidesData = await guidesRes.json(); const schedulesData = await schedulesRes.json(); const holidaysData = await holidaysRes.json(); const bookingsData = await bookingsRes.json(); this.guides = Array.isArray(guidesData) ? guidesData : (guidesData.data || []); this.schedules = Array.isArray(schedulesData) ? schedulesData : (schedulesData.data || []); this.holidays = Array.isArray(holidaysData) ? holidaysData : (holidaysData.data || []); this.bookings = Array.isArray(bookingsData) ? bookingsData : (bookingsData.data || []); } catch (error) { console.error('Ошибка загрузки данных:', error); this.showError('Ошибка загрузки данных'); } } updateGuidesList() { const listContainer = this.container.querySelector(`#guidesList-${this.getId()}`); if (!listContainer) return; if (!this.guides || this.guides.length === 0) { listContainer.innerHTML = '
Нет доступных гидов
'; return; } this.filteredGuides = this.guides.filter(guide => { if (!this.showAvailabilityOnly) return true; if (!this.selectedDate) return true; const status = this.getGuideStatus(guide.id, this.selectedDate); return status === 'working'; }); if (this.filteredGuides.length === 0) { listContainer.innerHTML = `
${this.selectedDate ? 'Нет доступных гидов на выбранную дату. Попробуйте другую дату.' : 'Нет доступных гидов' }
`; return; } listContainer.innerHTML = this.filteredGuides.map(guide => this.renderGuideCard(guide)).join(''); if (this.multiple) { this.updateSelectedGuidesList(); } } renderGuideCard(guide) { const status = this.selectedDate ? this.getGuideStatus(guide.id, this.selectedDate) : 'working'; const isSelected = this.multiple ? this.selectedGuideIds.includes(guide.id) : this.selectedGuideId === guide.id; const statusClass = status === 'working' ? 'available' : 'unavailable'; const cardClass = status === 'working' ? '' : 'unavailable'; const selectedClass = isSelected ? 'selected' : ''; const specializations = { 'city': 'Городские туры', 'mountain': 'Горные походы', 'fishing': 'Рыбалка', 'general': 'Универсальный' }; return `

${guide.name}

${specializations[guide.specialization] || guide.specialization}

${status === 'working' ? 'Доступен' : status === 'busy' ? 'Занят' : 'Выходной'}
${this.mode !== 'simple' ? `
Опыт: ${guide.experience || 'Не указан'} лет
Языки: ${guide.languages || 'Не указаны'}
Email: ${guide.email || 'Не указан'}
${guide.hourly_rate ? `${guide.hourly_rate}₩/час` : 'Цена договорная'}
` : ''}
`; } getGuideStatus(guideId, dateStr) { if (!dateStr) return 'working'; // Проверяем выходные дни const holiday = this.holidays.find(h => h.guide_id === guideId && h.holiday_date === dateStr ); if (holiday) return 'holiday'; // Проверяем бронирования const booking = this.bookings.find(b => b.guide_id === guideId && new Date(b.preferred_date).toISOString().split('T')[0] === dateStr ); if (booking) return 'busy'; return 'working'; } selectGuide(guideId) { if (this.multiple) { if (!this.selectedGuideIds) { this.selectedGuideIds = []; } if (!this.selectedGuideIds.includes(guideId)) { this.selectedGuideIds.push(guideId); this.updateGuidesList(); } } else { this.selectedGuideId = guideId; this.updateGuidesList(); } if (this.onGuideSelect) { const selectedGuides = this.multiple ? this.guides.filter(g => this.selectedGuideIds.includes(g.id)) : this.guides.find(g => g.id === guideId); this.onGuideSelect(selectedGuides); } } deselectGuide(guideId) { if (this.multiple && this.selectedGuideIds) { this.selectedGuideIds = this.selectedGuideIds.filter(id => id !== guideId); this.updateGuidesList(); if (this.onGuideSelect) { const selectedGuides = this.guides.filter(g => this.selectedGuideIds.includes(g.id)); this.onGuideSelect(selectedGuides); } } } updateSelectedGuidesList() { if (!this.multiple) return; const selectedContainer = this.container.querySelector(`#selectedGuides-${this.getId()}`); if (!selectedContainer) return; if (!this.selectedGuideIds || this.selectedGuideIds.length === 0) { selectedContainer.style.display = 'none'; return; } selectedContainer.style.display = 'block'; const listEl = selectedContainer.querySelector('.selected-guides-list'); listEl.innerHTML = this.selectedGuideIds.map(guideId => { const guide = this.guides.find(g => g.id === guideId); return ` ${guide.name} × `; }).join(''); } showError(message) { const listContainer = this.container.querySelector(`#guidesList-${this.getId()}`); if (listContainer) { listContainer.innerHTML = `
${message}
`; } } // Публичные методы setDate(dateStr) { this.selectedDate = dateStr; const dateInput = this.container.querySelector(`#dateInput-${this.getId()}`); if (dateInput) { dateInput.value = dateStr; } this.updateGuidesList(); } getSelectedGuides() { if (this.multiple) { return this.guides.filter(g => this.selectedGuideIds && this.selectedGuideIds.includes(g.id)); } else { return this.guides.find(g => g.id === this.selectedGuideId) || null; } } getAvailableGuides(dateStr = null) { const date = dateStr || this.selectedDate; if (!date) return this.guides; return this.guides.filter(guide => this.getGuideStatus(guide.id, date) === 'working' ); } refresh() { this.loadData().then(() => { this.updateGuidesList(); }); } } // Экспорт if (typeof module !== 'undefined' && module.exports) { module.exports = GuideSelector; } if (typeof window !== 'undefined') { window.GuideSelector = GuideSelector; }