// Ждём полной загрузки DOM перед выполнением кода
document.addEventListener("DOMContentLoaded", function () {
// Находим элемент контейнера для календаря по ID
var calendarContainer = document.getElementById("kt_calendar_app");
// Проверяем, существует ли контейнер для календаря
if (!calendarContainer) {
// Если контейнер не найден, выводим ошибку в консоль и останавливаем выполнение
console.error(
"Ошибка: контейнер для календаря с ID 'kt_calendar_app' не найден."
);
return;
}
// Создаём новый экземпляр календаря FullCalendar
var calendar = new FullCalendar.Calendar(calendarContainer, {
initialView: "dayGridMonth", // Устанавливаем начальный вид как месячный календарь
locale: "ru", // Устанавливаем русский язык для интерфейса
timeZone: "UTC", // Устанавливаем часовой пояс UTC
headerToolbar: {
// Настраиваем панель инструментов календаря
left: "prev,next today", // Кнопки "предыдущий", "следующий" и "сегодня" слева
center: "title", // Заголовок (название месяца/недели) в центре
right: "dayGridMonth,timeGridWeek,timeGridDay" // Переключатели видов справа
},
editable: true, // Разрешаем редактировать события (перетаскивание)
eventDurationEditable: true, // Разрешаем изменять длительность событий
eventResizableFromStart: true, // Разрешаем изменять размер события с начала
events: "get_events.php", // URL для получения событий через AJAX
eventClick: function (info) {
// Обработчик клика по событию
openEventModal(info); // Открываем модальное окно с деталями события
},
eventDrop: function (info) {
// Обработчик перемещения события
console.log("Перемещение:", info.event.start, info.event.end); // Логируем новые даты
updateEvent(info); // Обновляем событие на сервере
},
eventResize: function (info) {
// Обработчик изменения размера события
console.log("Растягивание:", info.event.start, info.event.end); // Логируем новые даты
updateEvent(info); // Обновляем событие на сервере
},
eventConstraint: {
// Ограничения времени для событий
start: "00:00", // Начало дня
end: "24:00" // Конец дня
},
displayEventTime: false, // Скрываем отображение времени в событиях
// Преобразуем данные событий перед отображением
eventDataTransform: function (eventData) {
eventData.allDay = true; // Все события в dayGridMonth становятся цельнодневными
eventData.editable = true; // Разрешаем редактирование
eventData.durationEditable = true; // Разрешаем изменение длительности
eventData.startEditable = true; // Разрешаем изменение начала
return eventData; // Возвращаем изменённые данные события
}
});
// Рендерим календарь на странице
calendar.render();
// Инициализируем Flatpickr для выбора начальной даты
var startDatePicker = flatpickr("#kt_calendar_datepicker_start_date", {
enableTime: false, // Отключаем выбор времени
dateFormat: "Y-m-d", // Формат даты: ГГГГ-ММ-ДД
defaultDate: "2025-03-04" // Устанавливаем дату по умолчанию
});
// Инициализируем Flatpickr для выбора конечной даты
var endDatePicker = flatpickr("#kt_calendar_datepicker_end_date", {
enableTime: false, // Отключаем выбор времени
dateFormat: "Y-m-d", // Формат даты: ГГГГ-ММ-ДД
defaultDate: "2025-03-08" // Устанавливаем дату по умолчанию
});
// Инициализируем Flatpickr для выбора начального времени
var startTimePicker = flatpickr("#kt_calendar_datepicker_start_time", {
enableTime: true, // Включаем выбор времени
noCalendar: true, // Отключаем календарь
dateFormat: "H:i", // Формат времени: ЧЧ:ММ
time_24hr: true, // Используем 24-часовой формат
defaultHour: 15, // Устанавливаем часы по умолчанию
defaultMinute: 30 // Устанавливаем минуты по умолчанию
});
// Инициализируем Flatpickr для выбора конечного времени
var endTimePicker = flatpickr("#kt_calendar_datepicker_end_time", {
enableTime: true, // Включаем выбор времени
noCalendar: true, // Отключаем календарь
dateFormat: "H:i", // Формат времени: ЧЧ:ММ
time_24hr: true, // Используем 24-часовой формат
defaultHour: 17, // Устанавливаем часы по умолчанию
defaultMinute: 20 // Устанавливаем минуты по умолчанию
});
// Находим элементы управления для переключения "весь день"
var allDayCheckbox = document.getElementById("kt_calendar_datepicker_allday");
var startTimeInput = document.getElementById(
"kt_calendar_datepicker_start_time"
);
var endTimeInput = document.getElementById("kt_calendar_datepicker_end_time");
// Добавляем обработчик изменения состояния чекбокса "весь день"
allDayCheckbox.addEventListener("change", function () {
if (this.checked) {
// Если выбрано "весь день"
startTimeInput.disabled = true; // Отключаем поле начального времени
endTimeInput.disabled = true; // Отключаем поле конечного времени
startTimePicker.setDate(null); // Очищаем начальное время
endTimePicker.setDate(null); // Очищаем конечное время
} else {
// Если "весь день" отключён
startTimeInput.disabled = false; // Включаем поле начального времени
endTimeInput.disabled = false; // Включаем поле конечного времени
startTimePicker.setDate("15:30"); // Устанавливаем начальное время по умолчанию
endTimePicker.setDate("17:20"); // Устанавливаем конечное время по умолчанию
}
});
// Добавляем обработчик нажатия на кнопку отправки формы добавления события
document
.getElementById("kt_modal_add_event_submit")
.addEventListener("click", function (event) {
event.preventDefault(); // Предотвращаем стандартную отправку формы
// Собираем данные формы
var form = document.getElementById("kt_modal_add_event_form");
var formData = new FormData(form);
// Функция форматирования даты и времени для отправки на сервер
function formatDateTime(dateStr, timeStr, isAllDay) {
if (!dateStr) return null; // Если дата не указана, возвращаем null
// Преобразуем дату из формата ДД.ММ.ГГГГ в ГГГГ-ММ-ДД, если нужно
const date = dateStr.includes(".")
? dateStr.split(".").reverse().join("-")
: dateStr;
if (isAllDay || !timeStr) return date; // Для событий "весь день" возвращаем только дату
return `${date} ${timeStr}:00`; // Добавляем время в формате ЧЧ:ММ:СС
}
// Получаем значения из полей формы
const startDate = document.getElementById(
"kt_calendar_datepicker_start_date"
).value;
const startTime = document.getElementById(
"kt_calendar_datepicker_start_time"
).value;
const endDate = document.getElementById("kt_calendar_datepicker_end_date")
.value;
const endTime = document.getElementById("kt_calendar_datepicker_end_time")
.value;
const isAllDay = allDayCheckbox.checked;
// Форматируем даты и добавляем их в formData
formData.set(
"cnc_event_start_date",
formatDateTime(startDate, startTime, isAllDay)
);
formData.set(
"cnc_event_end_date",
formatDateTime(endDate, endTime, isAllDay)
);
// Отправляем данные на сервер через AJAX
fetch("save_events.php", {
method: "POST", // Используем метод POST
body: formData // Передаём данные формы
})
.then((response) => response.json()) // Парсим ответ как JSON
.then((result) => {
// Обрабатываем результат
if (result.success) {
// Если событие успешно добавлено
alert("Событие успешно добавлено!"); // Показываем уведомление
form.reset(); // Сбрасываем форму
// Закрываем модальное окно
var modalElement = document.getElementById("kt_modal_add_event");
var modalInstance = bootstrap.Modal.getInstance(modalElement);
if (modalInstance) {
modalInstance.hide();
}
// Создаём объект нового события для добавления в календарь
var newEvent = {
id: result.event_id, // ID события с сервера
title: formData.get("cnc_event_title"), // Заголовок события
start: formData.get("cnc_event_start_date").replace(" ", "T"), // Начало события
end: formData.get("cnc_event_end_date").replace(" ", "T"), // Конец события
description: formData.get("cnc_event_description"), // Описание
location: formData.get("cnc_event_location"), // Место
color: formData.get("cnc_event_color"), // Цвет события
allDay: true, // Устанавливаем как цельнодневное
editable: true, // Разрешаем редактирование
durationEditable: true, // Разрешаем изменение длительности
startEditable: true // Разрешаем изменение начала
};
// Добавляем событие в календарь
calendar.addEvent(newEvent);
// Показываем уведомление об успехе
showAlert("Подію збережено", "Подію збережено. Гарного дня!");
} else {
// Если произошла ошибка
alert("Ошибка: " + result.error); // Показываем сообщение об ошибке
}
})
.catch((error) => {
// Обрабатываем ошибки AJAX-запроса
console.error("Ошибка при добавлении события:", error);
});
});
});
// Функция открытия модального окна с деталями события
function openEventModal(info) {
// Заполняем данные события в модальном окне
document.querySelector('[data-kt-calendar="cnc_event_name"]').textContent =
info.event.title || "Без названия";
document.querySelector(
'[data-kt-calendar="cnc_event_description"]'
).textContent = info.event.extendedProps.description || "Нет описания";
document.querySelector(
'[data-kt-calendar="cnc_event_location"]'
).textContent = info.event.extendedProps.location || "Не указано";
// Форматируем даты и время для отображения
const startDate = info.event.start.toLocaleDateString("ru-RU");
const endDate = info.event.end
? info.event.end.toLocaleDateString("ru-RU")
: "Не указано";
const startTime = info.event.allDay
? ""
: info.event.start.toLocaleTimeString("ru-RU", {
hour: "2-digit",
minute: "2-digit"
});
const endTime =
info.event.allDay || !info.event.end
? ""
: info.event.end.toLocaleTimeString("ru-RU", {
hour: "2-digit",
minute: "2-digit"
});
// Отображаем даты и время в модальном окне
document.querySelector(
'[data-kt-calendar="cnc_event_start_date"]'
).textContent = startDate + (startTime ? ` ${startTime}` : "");
document.querySelector(
'[data-kt-calendar="cnc_event_end_date"]'
).textContent = endDate + (endTime ? ` ${endTime}` : "");
// Открываем модальное окно с помощью Bootstrap
var eventModal = new bootstrap.Modal(
document.getElementById("kt_modal_view_event")
);
eventModal.show();
}
// Функция обновления события после перемещения или изменения размера
function updateEvent(info) {
// Создаём объект FormData для отправки данных на сервер
var formData = new FormData();
formData.append("event_id", info.event.id); // ID события для идентификации в базе
// Функция форматирования даты и времени для отправки на сервер
const formatDateTime = (date) => {
if (!date) return null; // Если дата отсутствует, возвращаем null
if (info.event.allDay) {
// Если событие цельнодневное
return date.toISOString().split("T")[0]; // Формат: ГГГГ-ММ-ДД
}
// Формат с временем: ГГГГ-ММ-ДД ЧЧ:ММ:СС
return date.toISOString().slice(0, 19).replace("T", " ");
};
// Получаем новые даты начала и окончания события
const newStart = formatDateTime(info.event.start);
let newEnd = formatDateTime(info.event.end);
// Если событие не имеет конца (однодневное), используем дату начала как конец
if (!newEnd && info.event.allDay) {
newEnd = newStart;
} else if (!newEnd && !info.event.allDay) {
// Если событие с временем, но конец не указан, можно установить значение по умолчанию
const originalEvent = calendar.getEventById(info.event.id);
const originalEndTime = originalEvent.end
? originalEvent.end.toISOString().slice(11, 16)
: "17:20"; // Например, "17:20"
newEnd = `${newStart.split(" ")[0]} ${originalEndTime}:00`;
}
// Добавляем новые даты в FormData
formData.append("new_start_date", newStart);
formData.append("new_end_date", newEnd);
// Отправляем запрос на сервер для обновления события
fetch("update_event.php", {
method: "POST", // Метод POST
body: formData // Данные формы
})
.then((response) => response.json()) // Парсим ответ как JSON
.then((result) => {
if (result.success) {
// Если сервер подтвердил успешное обновление
// Показываем уведомление об успешном обновлении
showAlert(
"Событие обновлено",
"Событие успешно обновлено в базе данных!"
);
} else {
// Если произошла ошибка на сервере
console.error("Ошибка при обновлении:", result.error); // Логируем ошибку
info.revert(); // Откатываем изменения в календаре
showAlert("Ошибка", "Не удалось обновить событие: " + result.error); // Уведомление об ошибке
}
})
.catch((error) => {
// Обрабатываем ошибки сетевого запроса
console.error("Ошибка AJAX:", error);
info.revert(); // Откатываем изменения
showAlert("Ошибка", "Произошла ошибка при обновлении события."); // Уведомление об ошибке
});
}
// Убедимся, что обработчики вызывают updateEvent корректно
var calendar = new FullCalendar.Calendar(calendarContainer, {
// ... (остальные настройки остаются без изменений)
eventDrop: function (info) {
// Обработчик перетаскивания события
console.log("Перемещение:", info.event.start, info.event.end); // Логируем для отладки
updateEvent(info); // Вызываем функцию обновления
},
eventResize: function (info) {
// Обработчик изменения размера события
console.log("Растягивание:", info.event.start, info.event.end); // Логируем для отладки
updateEvent(info); // Вызываем функцию обновления
}
// ... (остальные настройки)
});
// Функция отображения уведомления на странице
function showAlert(title, message) {
// Создаём HTML-код для уведомления
var alertHtml = `
<div class="alert alert-dismissible bg-light-primary border border-primary d-flex flex-column flex-sm-row p-5 mb-10 position-fixed top-0 end-0 m-3 shadow" style="z-index: 1050;">
<i class="ki-duotone ki-search-list fs-2hx text-success me-4 mb-5 mb-sm-0"></i>
<div class="d-flex flex-column pe-0 pe-sm-10">
<h5 class="mb-1">${title}</h5>
<span>${message}</span>
</div>
<button type="button" class="btn btn-icon ms-sm-auto" data-bs-dismiss="alert">
<i class="ki-duotone ki-cross fs-1 text-success"></i>
</button>
</div>
`;
// Создаём элемент и добавляем его в DOM
var alertContainer = document.createElement("div");
alertContainer.innerHTML = alertHtml;
document.body.appendChild(alertContainer);
// Удаляем уведомление через 5 секунд
setTimeout(() => {
alertContainer.remove();
}, 5000);
}
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.