/**
* 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.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 `
${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;
}