PageSourceSearch

https://victoriaspb.ru/js/calendar.js

js victoriaspb.ru collected 2026-10-06 13:17:36 UTC 11,228 bytes, 315 lines download raw bytes

1// Элементы DOM
2const openModalBtn = document.querySelectorAll('.openModal');
3const closeModalBtn = document.getElementById('closeModal');
4const modal = document.getElementById('dateModal');
5const showToursBtn = document.getElementById('showTours');
6const setDate = document.getElementById('setDate');
7const resetBtn = document.getElementById('resetBtn');
8const calendarsContainer = document.getElementById('calendarsContainer');
9let target = null;
10
11// Состояние выбранных дат
12let selectedDates = [];
13
14// Названия месяцев
15const monthNames = [
16    'Январь', 'Февраль', 'Март', 'Апрель', 'Май', 'Июнь',
17    'Июль', 'Август', 'Сентябрь', 'Октябрь', 'Ноябрь', 'Декабрь'
18];
19
20// Открытие модального окна
21openModalBtn.forEach(e => e.addEventListener('click', (event) => {
22    event.preventDefault();
23    modal.classList.add('active');
24    target = event.target.closest('.openModal')?.dataset?.field || null;
25    if (target) {
26        generateCalendars();
27        showToursBtn.style.display = "none";
28        setDate.style.display = "block";
29    } else {
30        generateCalendars();
31        showToursBtn.style.display = "block";
32        setDate.style.display = "none";
33    }
34    document.body.style.overflow = 'hidden';
35}));
36
37// Закрытие модального окна
38closeModalBtn.addEventListener('click', closeModal);
39showToursBtn.addEventListener('click', showTours);
40resetBtn.addEventListener('click', resetDates);
41setDate.addEventListener('click', insertDate);
42
43// Закрытие по клику на фон
44modal.addEventListener('click', (e) => {
45    if (e.target === modal) {
46        closeModal();
47    }
48});
49
50function closeModal() {
51    modal.classList.remove('active');
52    document.body.style.overflow = 'auto';
53}
54
55// Форматирование даты для URL (YYYY-MM-DD)
56function formatDateForUrl(date) {
57    const year = date.getFullYear();
58    const month = String(date.getMonth() + 1).padStart(2, '0');
59    const day = String(date.getDate()).padStart(2, '0');
60    return `${year}-${month}-${day}`;
61}
62
63// Переход на страницу с экскурсиями
64function insertDate() {
65    if (target) {
66        document.querySelector(`input[name=${target}`).value = formatDateForUrl(selectedDates[0]);
67        closeModal();
68    }
69}
70
71// Переход на страницу с экскурсиями
72function showTours() {
73    // Если даты не выбраны - открываем страницу без параметров
74    if (selectedDates.length === 0) {
75        window.location.href = '/tours/all';
76        return;
77    }
78
79    let url = '/tours/';
80    
81    if (selectedDates.length === 1) {
82        // Одна дата
83        url += `all?date=${formatDateForUrl(selectedDates[0])}`;
84    } else if (selectedDates.length === 2) {
85        // Диапазон дат
86        url += `interval?date-in=${formatDateForUrl(selectedDates[0])}&date-out=${formatDateForUrl(selectedDates[1])}`;
87    }
88
89    // Переход на страницу с экскурсиями
90    window.location.href = url;
91}
92
93// Сброс выбранных дат
94function resetDates() {
95    selectedDates = [];
96    clearAllSelections();
97    updateResetButton();
98}
99
100// Обновление состояния кнопки "Сбросить"
101function updateResetButton() {
102    if (selectedDates.length === 0) {
103        resetBtn.disabled = true;
104    } else {
105        resetBtn.disabled = false;
106    }
107}
108
109// Генерация календарей 
110function generateCalendars() {
111    const today = new Date();
112    calendarsContainer.innerHTML = '';
113
114    let monthes = 12;
115    if (target) monthes = 12;
116
117    for (let i = 0; i < monthes; i++) {
118        const currentMonth = new Date(today.getFullYear(), today.getMonth() + i, 1);
119        const monthDiv = createMonthCalendar(currentMonth);
120        calendarsContainer.appendChild(monthDiv);
121    }
122}
123
124// Создание календаря на один месяц
125function createMonthCalendar(date) {
126    const monthDiv = document.createElement('div');
127    monthDiv.className = 'month-calendar';
128
129    // Заголовок месяца
130    const monthTitle = document.createElement('div');
131    monthTitle.className = 'month-title';
132    monthTitle.textContent = `${monthNames[date.getMonth()]} ${date.getFullYear()}`;
133    monthDiv.appendChild(monthTitle);
134
135    // Сетка календаря
136    const calendarGrid = document.createElement('div');
137    calendarGrid.className = 'calendar-grid';
138
139    const year = date.getFullYear();
140    const month = date.getMonth();
141    
142    // Первый день месяца и количество дней
143    const firstDay = new Date(year, month, 1);
144    const lastDay = new Date(year, month + 1, 0);
145    const daysInMonth = lastDay.getDate();
146    
147    // Определяем день недели (понедельник = 0, воскресенье = 6)
148    let firstDayOfWeek = firstDay.getDay() - 1;
149    if (firstDayOfWeek === -1) firstDayOfWeek = 6;
150
151    // Пустые ячейки до начала месяца
152    for (let i = 0; i < firstDayOfWeek; i++) {
153        const emptyDay = document.createElement('div');
154        emptyDay.className = 'calendar-day empty';
155        calendarGrid.appendChild(emptyDay);
156    }
157
158    // Дни месяца
159    const today = new Date();
160    today.setHours(0, 0, 0, 0);
161
162    for (let day = 1; day <= daysInMonth; day++) {
163        const dayDiv = document.createElement('div');
164        dayDiv.className = 'calendar-day';
165        dayDiv.textContent = day;
166
167        const currentDate = new Date(year, month, day);
168        currentDate.setHours(0, 0, 0, 0);
169
170        // Проверка на выходные (суббота = 6, воскресенье = 0)
171        const dayOfWeek = currentDate.getDay();
172        if (dayOfWeek === 0 || dayOfWeek === 6) {
173            dayDiv.classList.add('weekend');
174        }
175
176        // Отключаем прошедшие даты
177        if (currentDate < today) {
178            dayDiv.classList.add('disabled');
179        } else {
180            dayDiv.addEventListener('click', () => selectDate(currentDate, dayDiv));
181            // Добавляем обработчик наведения для превью диапазона
182            dayDiv.addEventListener('mouseenter', () => previewRange(currentDate));
183        }
184
185        // Сохраняем дату в атрибуте
186        dayDiv.dataset.date = currentDate.toISOString();
187
188        calendarGrid.appendChild(dayDiv);
189    }
190
191    monthDiv.appendChild(calendarGrid);
192    return monthDiv;
193}
194
195// Выбор даты
196function selectDate(date, element) {
197    const dateStr = date.toISOString();
198
199    if (selectedDates.length === 0) {
200        // Первая дата
201        selectedDates.push(date);
202        element.classList.add('selected');
203    } else if (selectedDates.length === 1) {
204        // Вторая дата
205        const firstDate = selectedDates[0];
206        
207        if (date.getTime() === firstDate.getTime()) {
208            // Клик на ту же дату - сброс
209            selectedDates = [];
210            clearAllSelections();
211        } else {
212            // Добавляем вторую дату и сортируем
213            selectedDates.push(date);
214            selectedDates.sort((a, b) => a - b);
215            updateCalendarDisplay();
216        }
217    } else {
218        // Сброс и выбор новой даты
219        selectedDates = [date];
220        clearAllSelections();
221        element.classList.add('selected');
222    }
223    
224    // Обновляем состояние кнопки "Сбросить"
225    updateResetButton();
226}
227
228// Обновление отображения календаря
229function updateCalendarDisplay() {
230    clearAllSelections();
231
232    if (selectedDates.length === 0) return;
233
234    const allDays = document.querySelectorAll('.calendar-day:not(.empty):not(.disabled)');
235    
236    allDays.forEach(dayDiv => {
237        const dayDate = new Date(dayDiv.dataset.date);
238        dayDate.setHours(0, 0, 0, 0);
239
240        if (selectedDates.length === 1) {
241            // Только одна дата
242            if (dayDate.getTime() === selectedDates[0].getTime()) {
243                dayDiv.classList.add('selected');
244            }
245        } else if (selectedDates.length === 2) {
246            const startDate = selectedDates[0];
247            const endDate = selectedDates[1];
248
249            if (dayDate.getTime() === startDate.getTime() || dayDate.getTime() === endDate.getTime()) {
250                dayDiv.classList.add('selected');
251            } else if (dayDate > startDate && dayDate < endDate) {
252                dayDiv.classList.add('in-range');
253            }
254        }
255    });
256}
257
258// Очистка всех выделений
259function clearAllSelections() {
260    const allDays = document.querySelectorAll('.calendar-day');
261    allDays.forEach(day => {
262        day.classList.remove('selected', 'in-range', 'preview-range');
263    });
264}
265
266// Превью диапазона при наведении
267function previewRange(hoverDate) {
268    // Показываем превью только если выбрана одна дата
269    if (selectedDates.length !== 1) return;
270
271    const firstDate = selectedDates[0];
272    
273    // Очищаем только превью, сохраняя selected
274    const allDays = document.querySelectorAll('.calendar-day');
275    allDays.forEach(day => {
276        day.classList.remove('preview-range');
277    });
278
279    // Если наведение на ту же дату, что уже выбрана - не показываем превью
280    if (hoverDate.getTime() === firstDate.getTime()) return;
281
282    // Определяем начальную и конечную даты для превью
283    const startDate = hoverDate < firstDate ? hoverDate : firstDate;
284    const endDate = hoverDate > firstDate ? hoverDate : firstDate;
285
286    // Применяем превью к датам в диапазоне
287    allDays.forEach(dayDiv => {
288        if (dayDiv.classList.contains('empty') || dayDiv.classList.contains('disabled')) return;
289        
290        const dayDate = new Date(dayDiv.dataset.date);
291        dayDate.setHours(0, 0, 0, 0);
292
293        // Добавляем превью для дат между выбранной и текущей
294        if (dayDate > startDate && dayDate < endDate) {
295            dayDiv.classList.add('preview-range');
296        }
297    });
298}
299
300// Очистка превью при уходе курсора с календаря
301function clearPreview() {
302    // Очищаем превью только если выбрана одна дата
303    if (selectedDates.length === 1) {
304        const allDays = document.querySelectorAll('.calendar-day');
305        allDays.forEach(day => {
306            day.classList.remove('preview-range');
307        });
308    }
309}
310
311// Добавляем обработчик ухода курсора с контейнера календарей
312calendarsContainer.addEventListener('mouseleave', clearPreview);
313
314// Инициализация календарей при загрузке
315generateCalendars();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.