Added API Integration: - calendarsApi.js: Complete API wrapper for /api/v1/calendar/calendars/ * CRUD operations (create, read, update, delete) * Subscribe/unsubscribe endpoints * My subscriptions endpoint * Cache invalidation support Added UI Components: - calendarManager.js: Calendar list management component * Displays available calendars with color indicators * Toggle calendar visibility (checkboxes) * Subscribe/unsubscribe buttons * Edit calendar button (for owners) * Real-time calendar filtering - calendarManageModal.js: Calendar creation/editing modal * Create personal/public/department calendars * Edit calendar properties (title, description, color) * Delete calendar (owner only) * Department selection for department calendars * Auto-subscription settings Added Styles: - calendar-manager.scss: BEM-based styling * Calendar list with glassmorphism design * Color indicators and icons * Hover states and animations * Responsive design for mobile * Empty state styling Updated Templates: - calendar_desktop.html: Added calendar list UI * Replaced dropdown with collapsible calendar list * Added manage calendar button * Updated create event button icon - calendar_modal_manage.html: New modal template * Form fields for calendar properties * Radio buttons for calendar types * Department selector * Auto-subscription checkboxes * Delete button for owners - rightbar_calendar.html: Included new modal Updated Imports: - main.scss: Added calendar-manager component import Status: Phase 3 Part 1 complete (UI components ready) Next: Part 2 - Integration with calendarWidget.js for event filtering
273 lines
8.5 KiB
JavaScript
273 lines
8.5 KiB
JavaScript
/**
|
||
* @fileoverview Calendar Manage Modal - модальное окно создания/редактирования календаря
|
||
* @module components/calendarManageModal
|
||
*/
|
||
|
||
import { createCalendar, updateCalendar, deleteCalendar } from '../api/calendarsApi.js';
|
||
import { getMyDepartments } from '../api/departmentsApi.js';
|
||
|
||
/**
|
||
* Инициализация модального окна управления календарём
|
||
* @param {Object} options - Параметры
|
||
* @param {Function} options.onSuccess - Callback при успешном сохранении/удалении
|
||
* @returns {Object} API модального окна
|
||
*/
|
||
export function initCalendarManageModal(options = {}) {
|
||
const { onSuccess = () => {} } = options;
|
||
|
||
const modal = document.getElementById('calendarManageModal');
|
||
if (!modal) {
|
||
console.warn('[CalendarManageModal] Modal element not found');
|
||
return null;
|
||
}
|
||
|
||
const bsModal = new bootstrap.Modal(modal);
|
||
|
||
// Elements
|
||
const form = document.getElementById('calendarManageForm');
|
||
const titleEl = document.getElementById('calendarManageModalTitle');
|
||
const calendarIdInput = document.getElementById('calendarId');
|
||
const titleInput = document.getElementById('calendarTitle');
|
||
const descInput = document.getElementById('calendarDescription');
|
||
const colorInput = document.getElementById('calendarColor');
|
||
const colorTextInput = document.getElementById('calendarColorText');
|
||
const typeInputs = document.querySelectorAll('input[name="calendar_type"]');
|
||
const deptSelect = document.getElementById('calendarDepartment');
|
||
const deptSelectWrapper = document.getElementById('calendarDepartmentSelect');
|
||
const autoSubscribeDeptWrapper = document.getElementById('calendarAutoSubscribeDeptWrapper');
|
||
const saveBtn = document.getElementById('calendarSaveBtn');
|
||
const deleteBtn = document.getElementById('calendarDeleteBtn');
|
||
const errorEl = document.getElementById('calendarManageError');
|
||
|
||
let currentCalendar = null;
|
||
|
||
/**
|
||
* Открыть модальное окно для создания календаря
|
||
*/
|
||
function openForCreate() {
|
||
currentCalendar = null;
|
||
titleEl.textContent = 'Создать календарь';
|
||
deleteBtn.classList.add('d-none');
|
||
resetForm();
|
||
bsModal.show();
|
||
}
|
||
|
||
/**
|
||
* Открыть модальное окно для редактирования календаря
|
||
* @param {Object} calendar - Данные календаря
|
||
*/
|
||
function openForEdit(calendar) {
|
||
currentCalendar = calendar;
|
||
titleEl.textContent = 'Редактировать календарь';
|
||
deleteBtn.classList.remove('d-none');
|
||
|
||
// Заполнить форму
|
||
calendarIdInput.value = calendar.id;
|
||
titleInput.value = calendar.title;
|
||
descInput.value = calendar.description || '';
|
||
colorInput.value = calendar.color || '#0d6efd';
|
||
colorTextInput.value = calendar.color || '#0d6efd';
|
||
|
||
// Определить тип
|
||
let type = 'personal';
|
||
if (calendar.is_global) {
|
||
type = 'public';
|
||
} else if (calendar.is_department) {
|
||
type = 'department';
|
||
}
|
||
|
||
typeInputs.forEach(input => {
|
||
input.checked = input.value === type;
|
||
});
|
||
|
||
// Если отдел, выбрать его
|
||
if (calendar.owner_department) {
|
||
deptSelect.value = calendar.owner_department;
|
||
}
|
||
|
||
// Настройки
|
||
document.getElementById('calendarAutoSubscribeNewUsers').checked =
|
||
calendar.auto_subscribe_new_users || false;
|
||
document.getElementById('calendarAutoSubscribeDept').checked =
|
||
calendar.auto_subscribe_department_members || false;
|
||
|
||
updateTypeVisibility();
|
||
bsModal.show();
|
||
}
|
||
|
||
/**
|
||
* Сбросить форму
|
||
*/
|
||
function resetForm() {
|
||
form.reset();
|
||
calendarIdInput.value = '';
|
||
colorInput.value = '#0d6efd';
|
||
colorTextInput.value = '#0d6efd';
|
||
errorEl.classList.add('d-none');
|
||
updateTypeVisibility();
|
||
}
|
||
|
||
/**
|
||
* Обновить видимость полей в зависимости от типа
|
||
*/
|
||
function updateTypeVisibility() {
|
||
const selectedType = document.querySelector('input[name="calendar_type"]:checked')?.value;
|
||
|
||
if (selectedType === 'department') {
|
||
deptSelectWrapper.classList.remove('d-none');
|
||
autoSubscribeDeptWrapper.classList.remove('d-none');
|
||
} else {
|
||
deptSelectWrapper.classList.add('d-none');
|
||
autoSubscribeDeptWrapper.classList.add('d-none');
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Загрузить список отделов
|
||
*/
|
||
async function loadDepartments() {
|
||
try {
|
||
const departments = await getMyDepartments();
|
||
|
||
// Заполнить select
|
||
deptSelect.innerHTML = '<option value="">Выберите отдел...</option>' +
|
||
departments.map(dept =>
|
||
`<option value="${dept.id}">${dept.name}</option>`
|
||
).join('');
|
||
|
||
} catch (error) {
|
||
console.error('[CalendarManageModal] Failed to load departments:', error);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Собрать данные формы
|
||
* @returns {Object}
|
||
*/
|
||
function getFormData() {
|
||
const data = {
|
||
title: titleInput.value.trim(),
|
||
description: descInput.value.trim(),
|
||
color: colorInput.value,
|
||
auto_subscribe_new_users: document.getElementById('calendarAutoSubscribeNewUsers').checked,
|
||
auto_subscribe_department_members: document.getElementById('calendarAutoSubscribeDept').checked
|
||
};
|
||
|
||
const type = document.querySelector('input[name="calendar_type"]:checked')?.value;
|
||
|
||
if (type === 'personal') {
|
||
data.visibility = 'private';
|
||
// owner_user будет установлен автоматически на backend
|
||
} else if (type === 'public') {
|
||
data.visibility = 'public';
|
||
} else if (type === 'department') {
|
||
data.visibility = 'department';
|
||
data.owner_department = parseInt(deptSelect.value);
|
||
|
||
if (!data.owner_department) {
|
||
throw new Error('Выберите отдел');
|
||
}
|
||
}
|
||
|
||
return data;
|
||
}
|
||
|
||
/**
|
||
* Сохранить календарь
|
||
*/
|
||
async function save() {
|
||
try {
|
||
errorEl.classList.add('d-none');
|
||
saveBtn.disabled = true;
|
||
|
||
const data = getFormData();
|
||
|
||
if (currentCalendar) {
|
||
// Обновление
|
||
await updateCalendar(currentCalendar.id, data);
|
||
} else {
|
||
// Создание
|
||
await createCalendar(data);
|
||
}
|
||
|
||
bsModal.hide();
|
||
onSuccess();
|
||
|
||
} catch (error) {
|
||
console.error('[CalendarManageModal] Save failed:', error);
|
||
errorEl.textContent = error.message || 'Не удалось сохранить календарь';
|
||
errorEl.classList.remove('d-none');
|
||
} finally {
|
||
saveBtn.disabled = false;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Удалить календарь
|
||
*/
|
||
async function remove() {
|
||
if (!currentCalendar) return;
|
||
|
||
if (!confirm(`Удалить календарь "${currentCalendar.title}"?\n\nВсе события этого календаря будут удалены.`)) {
|
||
return;
|
||
}
|
||
|
||
try {
|
||
deleteBtn.disabled = true;
|
||
await deleteCalendar(currentCalendar.id);
|
||
bsModal.hide();
|
||
onSuccess();
|
||
} catch (error) {
|
||
console.error('[CalendarManageModal] Delete failed:', error);
|
||
errorEl.textContent = error.message || 'Не удалось удалить календарь';
|
||
errorEl.classList.remove('d-none');
|
||
} finally {
|
||
deleteBtn.disabled = false;
|
||
}
|
||
}
|
||
|
||
// Event listeners
|
||
|
||
// Синхронизация цвета
|
||
colorInput.addEventListener('input', (e) => {
|
||
colorTextInput.value = e.target.value;
|
||
});
|
||
|
||
colorTextInput.addEventListener('input', (e) => {
|
||
const value = e.target.value;
|
||
if (/^#[0-9A-Fa-f]{6}$/.test(value)) {
|
||
colorInput.value = value;
|
||
}
|
||
});
|
||
|
||
// Изменение типа календаря
|
||
typeInputs.forEach(input => {
|
||
input.addEventListener('change', updateTypeVisibility);
|
||
});
|
||
|
||
// Сохранить
|
||
saveBtn.addEventListener('click', save);
|
||
|
||
// Удалить
|
||
deleteBtn.addEventListener('click', remove);
|
||
|
||
// Сброс при закрытии
|
||
modal.addEventListener('hidden.bs.modal', resetForm);
|
||
|
||
// Слушать событие редактирования из других компонентов
|
||
document.addEventListener('calendar:edit', (e) => {
|
||
openForEdit(e.detail);
|
||
});
|
||
|
||
// Инициализация
|
||
loadDepartments();
|
||
|
||
// Public API
|
||
return {
|
||
openForCreate,
|
||
openForEdit,
|
||
show: () => bsModal.show(),
|
||
hide: () => bsModal.hide()
|
||
};
|
||
}
|