Files
tourrism_site/public/admin-calendar-component.html
Andrey K. Choi 13c752b93a feat: Оптимизация навигации AdminJS в логические группы
- Объединены ресурсы в 5 логических групп: Контент сайта, Бронирования, Отзывы и рейтинги, Персонал и гиды, Администрирование
- Удалены дублирующие настройки navigation для чистой группировки
- Добавлены CSS стили для визуального отображения иерархии с отступами
- Добавлены эмодзи-иконки для каждого типа ресурсов через CSS
- Улучшена навигация с правильной вложенностью элементов
2025-11-30 21:57:58 +09:00

462 lines
16 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Календарь гидов</title>
<style>
.calendar-container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}
.calendar-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 30px;
padding: 20px;
background: #f8f9fa;
border-radius: 8px;
}
.calendar-navigation {
display: flex;
align-items: center;
gap: 20px;
}
.nav-button {
background: #007bff;
color: white;
border: none;
padding: 10px 15px;
border-radius: 5px;
cursor: pointer;
font-size: 18px;
transition: background-color 0.3s;
}
.nav-button:hover {
background: #0056b3;
}
.current-date {
font-size: 24px;
font-weight: 600;
color: #343a40;
min-width: 200px;
text-align: center;
}
.guides-filter {
display: flex;
flex-wrap: wrap;
gap: 10px;
align-items: center;
}
.filter-label {
font-weight: 600;
color: #495057;
margin-right: 15px;
}
.guide-checkbox {
display: flex;
align-items: center;
gap: 5px;
padding: 8px 12px;
border: 2px solid #dee2e6;
border-radius: 20px;
cursor: pointer;
transition: all 0.3s;
background: white;
font-size: 14px;
}
.guide-checkbox:hover {
border-color: #007bff;
background: #e3f2fd;
}
.guide-checkbox.checked {
background: #007bff;
color: white;
border-color: #007bff;
}
.guide-checkbox input[type="checkbox"] {
display: none;
}
.calendar-grid {
display: grid;
grid-template-columns: repeat(7, 1fr);
gap: 1px;
background: #dee2e6;
border-radius: 8px;
overflow: hidden;
margin-top: 20px;
}
.calendar-day {
background: white;
min-height: 120px;
padding: 8px;
position: relative;
border: 1px solid transparent;
}
.calendar-day.other-month {
background: #f8f9fa;
color: #6c757d;
}
.calendar-day.today {
background: #fff3cd;
border-color: #ffeaa7;
}
.day-number {
font-weight: 600;
font-size: 16px;
margin-bottom: 8px;
}
.day-header {
background: #343a40;
color: white;
padding: 15px 8px;
text-align: center;
font-weight: 600;
font-size: 14px;
}
.guide-status {
display: flex;
flex-wrap: wrap;
gap: 4px;
}
.guide-badge {
padding: 2px 6px;
border-radius: 12px;
font-size: 11px;
font-weight: 500;
white-space: nowrap;
}
.guide-badge.working {
background: #d4edda;
color: #155724;
}
.guide-badge.holiday {
background: #f8d7da;
color: #721c24;
}
.guide-badge.busy {
background: #fff3cd;
color: #856404;
}
.legend {
display: flex;
justify-content: center;
gap: 30px;
margin-top: 20px;
padding: 15px;
background: #f8f9fa;
border-radius: 8px;
font-size: 14px;
}
.legend-item {
display: flex;
align-items: center;
gap: 8px;
}
.legend-color {
width: 12px;
height: 12px;
border-radius: 3px;
}
.error {
background: #f8d7da;
color: #721c24;
padding: 15px;
border-radius: 5px;
text-align: center;
margin: 20px 0;
}
</style>
</head>
<body>
<div class="calendar-container">
<div class="calendar-header">
<div class="calendar-navigation">
<button class="nav-button" id="prevMonth"></button>
<span class="current-date" id="currentDate"></span>
<button class="nav-button" id="nextMonth"></button>
</div>
<div class="guides-filter">
<span class="filter-label">Фильтр гидов:</span>
<div id="guidesFilter"></div>
</div>
</div>
<div id="calendarGrid" class="calendar-grid"></div>
<div class="legend">
<div class="legend-item">
<div class="legend-color" style="background: #d4edda;"></div>
<span>Работает</span>
</div>
<div class="legend-item">
<div class="legend-color" style="background: #f8d7da;"></div>
<span>Выходной</span>
</div>
<div class="legend-item">
<div class="legend-color" style="background: #fff3cd;"></div>
<span>Забронирован</span>
</div>
</div>
</div>
<script>
class GuideCalendar {
constructor() {
this.currentDate = new Date();
this.guides = [];
this.schedules = [];
this.holidays = [];
this.bookings = [];
this.selectedGuides = new Set();
this.init();
}
async init() {
await this.loadData();
this.renderGuidesFilter();
this.renderCalendar();
this.updateMonthDisplay();
this.bindEvents();
}
bindEvents() {
const prevBtn = document.getElementById('prevMonth');
const nextBtn = document.getElementById('nextMonth');
if (prevBtn) {
prevBtn.addEventListener('click', () => this.changeMonth(-1));
}
if (nextBtn) {
nextBtn.addEventListener('click', () => this.changeMonth(1));
}
}
async loadData() {
try {
// Загружаем гидов
const guidesResponse = await fetch('/api/guides');
const guidesData = await guidesResponse.json();
this.guides = Array.isArray(guidesData) ? guidesData : (guidesData.data || guidesData.guides || []);
// Загружаем расписания
const schedulesResponse = await fetch('/api/guide-schedules');
const schedulesData = await schedulesResponse.json();
this.schedules = Array.isArray(schedulesData) ? schedulesData : (schedulesData.data || schedulesData.schedules || []);
// Загружаем выходные дни
const holidaysResponse = await fetch('/api/holidays');
const holidaysData = await holidaysResponse.json();
this.holidays = Array.isArray(holidaysData) ? holidaysData : (holidaysData.data || holidaysData.holidays || []);
// Загружаем существующие бронирования
const bookingsResponse = await fetch('/api/bookings');
const bookingsData = await bookingsResponse.json();
this.bookings = Array.isArray(bookingsData) ? bookingsData : (bookingsData.data || bookingsData.bookings || []);
// По умолчанию показываем всех гидов
if (this.guides && this.guides.length > 0) {
this.guides.forEach(guide => this.selectedGuides.add(guide.id));
}
} catch (error) {
console.error('Ошибка загрузки данных:', error);
document.getElementById('calendarGrid').innerHTML =
'<div class="error">Ошибка загрузки данных календаря</div>';
}
}
renderGuidesFilter() {
const filterContainer = document.getElementById('guidesFilter');
filterContainer.innerHTML = '';
if (!this.guides || !Array.isArray(this.guides)) {
filterContainer.innerHTML = '<div class="error">Нет доступных гидов</div>';
return;
}
this.guides.forEach(guide => {
const checkbox = document.createElement('label');
checkbox.className = 'guide-checkbox';
if (this.selectedGuides.has(guide.id)) {
checkbox.classList.add('checked');
}
checkbox.innerHTML = `
<input type="checkbox"
${this.selectedGuides.has(guide.id) ? 'checked' : ''}
data-guide-id="${guide.id}">
<span>${guide.name}</span>
`;
checkbox.addEventListener('click', (e) => {
e.preventDefault();
this.toggleGuide(guide.id);
});
filterContainer.appendChild(checkbox);
});
}
toggleGuide(guideId) {
if (this.selectedGuides.has(guideId)) {
this.selectedGuides.delete(guideId);
} else {
this.selectedGuides.add(guideId);
}
this.renderGuidesFilter();
this.renderCalendar();
}
renderCalendar() {
const grid = document.getElementById('calendarGrid');
grid.innerHTML = '';
// Заголовки дней недели
const dayHeaders = ['Пн', 'Вт', 'Ср', 'Чт', 'Пт', 'Сб', 'Вс'];
dayHeaders.forEach(day => {
const headerDiv = document.createElement('div');
headerDiv.className = 'day-header';
headerDiv.textContent = day;
grid.appendChild(headerDiv);
});
const year = this.currentDate.getFullYear();
const month = this.currentDate.getMonth();
// Первый день месяца
const firstDay = new Date(year, month, 1);
const lastDay = new Date(year, month + 1, 0);
// Первый понедельник на календаре
const startDate = new Date(firstDay);
const dayOfWeek = firstDay.getDay();
const mondayOffset = dayOfWeek === 0 ? -6 : -(dayOfWeek - 1);
startDate.setDate(firstDay.getDate() + mondayOffset);
// Генерируем 6 недель
for (let week = 0; week < 6; week++) {
for (let day = 0; day < 7; day++) {
const currentDay = new Date(startDate);
currentDay.setDate(startDate.getDate() + week * 7 + day);
const dayDiv = document.createElement('div');
dayDiv.className = 'calendar-day';
if (currentDay.getMonth() !== month) {
dayDiv.classList.add('other-month');
}
if (this.isToday(currentDay)) {
dayDiv.classList.add('today');
}
dayDiv.innerHTML = this.renderDay(currentDay);
grid.appendChild(dayDiv);
}
}
}
renderDay(date) {
const dayNumber = date.getDate();
const dateStr = this.formatDate(date);
let guideStatusHtml = '';
// Получаем статусы выбранных гидов для этого дня
this.guides.forEach(guide => {
if (!this.selectedGuides.has(guide.id)) return;
const status = this.getGuideStatus(guide.id, dateStr);
const statusClass = status === 'holiday' ? 'holiday' :
status === 'busy' ? 'busy' : 'working';
guideStatusHtml += `<div class="guide-badge ${statusClass}">${guide.name.split(' ')[0]}</div>`;
});
return `
<div class="day-number">${dayNumber}</div>
<div class="guide-status">${guideStatusHtml}</div>
`;
}
getGuideStatus(guideId, dateStr) {
// Проверяем выходные дни
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 &&
this.formatDate(new Date(b.preferred_date)) === dateStr
);
if (booking) return 'busy';
return 'working';
}
formatDate(date) {
return date.toISOString().split('T')[0];
}
isToday(date) {
const today = new Date();
return date.toDateString() === today.toDateString();
}
updateMonthDisplay() {
const monthDisplay = document.getElementById('currentDate');
const monthNames = [
'Январь', 'Февраль', 'Март', 'Апрель', 'Май', 'Июнь',
'Июль', 'Август', 'Сентябрь', 'Октябрь', 'Ноябрь', 'Декабрь'
];
monthDisplay.textContent = `${monthNames[this.currentDate.getMonth()]} ${this.currentDate.getFullYear()}`;
}
changeMonth(delta) {
this.currentDate.setMonth(this.currentDate.getMonth() + delta);
this.renderCalendar();
this.updateMonthDisplay();
}
}
// Инициализация календаря
document.addEventListener('DOMContentLoaded', () => {
new GuideCalendar();
});
</script>
</body>
</html>