fix(calendar): добавлена очистка кеша после создания/редактирования/удаления событий
- Добавлен вызов invalidateCalendarEvents() перед refetchEvents() - Исправлена проблема с отображением событий без перезагрузки страницы - Кеш теперь корректно очищается при всех операциях с событиями
This commit is contained in:
parent
dd56953e89
commit
c9fa337669
@ -63,7 +63,34 @@ export function initCalendarWidget(options = {}) {
|
||||
const globalToken = getAccessToken();
|
||||
|
||||
function authHeaders() {
|
||||
return globalToken ? { Authorization: 'Bearer ' + globalToken } : {};
|
||||
const headers = {};
|
||||
if (globalToken) {
|
||||
headers.Authorization = 'Bearer ' + globalToken;
|
||||
}
|
||||
// Добавляем CSRF токен для POST/PUT/PATCH/DELETE запросов
|
||||
const csrfToken = document.querySelector('[name=csrfmiddlewaretoken]')?.value ||
|
||||
document.querySelector('meta[name="csrf-token"]')?.content ||
|
||||
getCookie('csrftoken');
|
||||
if (csrfToken) {
|
||||
headers['X-CSRFToken'] = csrfToken;
|
||||
}
|
||||
return headers;
|
||||
}
|
||||
|
||||
// Функция для получения cookie
|
||||
function getCookie(name) {
|
||||
let cookieValue = null;
|
||||
if (document.cookie && document.cookie !== '') {
|
||||
const cookies = document.cookie.split(';');
|
||||
for (let i = 0; i < cookies.length; i++) {
|
||||
const cookie = cookies[i].trim();
|
||||
if (cookie.substring(0, name.length + 1) === (name + '=')) {
|
||||
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return cookieValue;
|
||||
}
|
||||
|
||||
/* ===== Только ЧИСЛОВОЙ PK отдела ===== */
|
||||
@ -84,10 +111,14 @@ export function initCalendarWidget(options = {}) {
|
||||
return `${y}-${m}-${d}`;
|
||||
}
|
||||
|
||||
// Базовый URL событий: для отдела добавляем department_id
|
||||
const eventsUrl = (deptId = null) => {
|
||||
// Базовый URL событий: для отдела добавляем department_id, для личного - employee_id
|
||||
const eventsUrl = (deptId = null, employeeId = null) => {
|
||||
const u = new URL(API_EVENTS, location.origin);
|
||||
if (deptId != null) u.searchParams.set('department_id', String(deptId));
|
||||
if (employeeId != null) {
|
||||
u.searchParams.set('employee_id', String(employeeId));
|
||||
} else if (deptId != null) {
|
||||
u.searchParams.set('department_id', String(deptId));
|
||||
}
|
||||
return u.pathname + (u.search ? '?' + u.searchParams.toString() : '');
|
||||
};
|
||||
|
||||
@ -122,7 +153,7 @@ export function initCalendarWidget(options = {}) {
|
||||
|
||||
/* ===== Состояние ===== */
|
||||
let departments = []; // [{id, name}] где id — всегда числовой PK
|
||||
const state = { type: 'company', deptId: null };
|
||||
const state = { type: 'company', deptId: null, employeeId: null };
|
||||
// Защита от гонок при загрузке отделов
|
||||
let deptsLoadSeq = 0;
|
||||
|
||||
@ -292,13 +323,22 @@ export function initCalendarWidget(options = {}) {
|
||||
}
|
||||
|
||||
async function openEventDetailsById(eventId) {
|
||||
console.log('[CalendarWidget] openEventDetailsById called with:', eventId);
|
||||
try {
|
||||
const data = await apiGet(`${API_EVENTS}${eventId}/`);
|
||||
const url = `${API_EVENTS}${eventId}/`;
|
||||
console.log('[CalendarWidget] Fetching event details from:', url);
|
||||
const data = await apiGet(url);
|
||||
console.log('[CalendarWidget] Event details loaded:', data);
|
||||
currentDetail = data;
|
||||
fillDetails(data);
|
||||
detailsModal && detailsModal.show();
|
||||
if (detailsModal) {
|
||||
console.log('[CalendarWidget] Showing details modal');
|
||||
detailsModal.show();
|
||||
} else {
|
||||
console.error('[CalendarWidget] detailsModal is not initialized!');
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Load details error', err);
|
||||
console.error('[CalendarWidget] Load details error:', err);
|
||||
if (err.status === 403) alert('Недостаточно прав для просмотра деталей события.');
|
||||
else alert('Не удалось загрузить событие.');
|
||||
}
|
||||
@ -387,13 +427,19 @@ export function initCalendarWidget(options = {}) {
|
||||
}
|
||||
|
||||
function currentDeptLabel() {
|
||||
if (state.type === 'personal') return 'Личный';
|
||||
if (state.type !== 'dept') return 'Компания';
|
||||
const d = departments.find((x) => String(x.id) === String(state.deptId));
|
||||
return d?.name || `Отдел #${state.deptId}`;
|
||||
}
|
||||
|
||||
function setChooserLabel() {
|
||||
const label = state.type === 'company' ? 'Компания' : currentDeptLabel();
|
||||
let label = 'Компания';
|
||||
if (state.type === 'personal') {
|
||||
label = 'Личный';
|
||||
} else if (state.type === 'dept') {
|
||||
label = currentDeptLabel();
|
||||
}
|
||||
if (chooserBtn) chooserBtn.textContent = label;
|
||||
if (chooserBtnMobile) chooserBtnMobile.textContent = label;
|
||||
if (eventTargetLabel) eventTargetLabel.textContent = label;
|
||||
@ -407,6 +453,18 @@ export function initCalendarWidget(options = {}) {
|
||||
if (type === 'company') {
|
||||
state.type = 'company';
|
||||
state.deptId = null;
|
||||
state.employeeId = null;
|
||||
} else if (type === 'personal') {
|
||||
state.type = 'personal';
|
||||
state.deptId = null;
|
||||
// Получаем employee_id из meta-тега или атрибута
|
||||
const userMeta = document.querySelector('meta[name="user-id"]');
|
||||
state.employeeId = userMeta ? userMeta.content : null;
|
||||
if (!state.employeeId) {
|
||||
alert('Не удалось определить ID пользователя');
|
||||
state.type = 'company';
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
const id = btn.dataset.id;
|
||||
if (!DIGITS_RE.test(String(id || ''))) {
|
||||
@ -415,6 +473,7 @@ export function initCalendarWidget(options = {}) {
|
||||
}
|
||||
state.type = 'dept';
|
||||
state.deptId = id;
|
||||
state.employeeId = null;
|
||||
}
|
||||
setChooserLabel();
|
||||
[deskCalendar, mobCalendar].forEach((cal) => cal?.refetchEvents());
|
||||
@ -433,8 +492,12 @@ export function initCalendarWidget(options = {}) {
|
||||
end: ymdLocal(end)
|
||||
};
|
||||
|
||||
// Добавляем department_id если нужно
|
||||
if (state.type === 'dept' && state.deptId) {
|
||||
// Добавляем employee_id для личного календаря
|
||||
if (state.type === 'personal' && state.employeeId) {
|
||||
params.employee_id = state.employeeId;
|
||||
}
|
||||
// Добавляем department_id для отдела
|
||||
else if (state.type === 'dept' && state.deptId) {
|
||||
params.department_id = state.deptId;
|
||||
}
|
||||
|
||||
@ -446,7 +509,7 @@ export function initCalendarWidget(options = {}) {
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ ВСЕ доступные календари: компания + все отделы пользователя
|
||||
// ✅ ВСЕ доступные календари: компания + все отделы пользователя + личный
|
||||
async function fetchEventsAllCalendars(start, end) {
|
||||
// При первом вызове убедимся, что отделы загружены
|
||||
if (!departments || departments.length === 0) {
|
||||
@ -458,6 +521,10 @@ export function initCalendarWidget(options = {}) {
|
||||
const startStr = ymdLocal(start);
|
||||
const endStr = ymdLocal(end);
|
||||
|
||||
// Получаем employee_id текущего пользователя
|
||||
const userMeta = document.querySelector('meta[name="user-id"]');
|
||||
const currentEmployeeId = userMeta ? userMeta.content : null;
|
||||
|
||||
// Формируем список источников с подписью
|
||||
const sources = [
|
||||
{ params: { start: startStr, end: endStr }, label: 'Компания', type: 'company', id: null },
|
||||
@ -469,6 +536,16 @@ export function initCalendarWidget(options = {}) {
|
||||
})),
|
||||
];
|
||||
|
||||
// Добавляем личный календарь, если есть employee_id
|
||||
if (currentEmployeeId) {
|
||||
sources.push({
|
||||
params: { start: startStr, end: endStr, employee_id: currentEmployeeId },
|
||||
label: 'Личный',
|
||||
type: 'personal',
|
||||
id: currentEmployeeId,
|
||||
});
|
||||
}
|
||||
|
||||
// Загружаем все источники параллельно (с кешированием!)
|
||||
const chunks = await Promise.all(
|
||||
sources.map((s) =>
|
||||
@ -502,6 +579,7 @@ export function initCalendarWidget(options = {}) {
|
||||
}
|
||||
|
||||
function normalizeEvent(ev) {
|
||||
const id = ev.id ?? ev.pk ?? ev.uuid ?? ev.slug ?? ev._id;
|
||||
const title = pick(ev, ['title', 'name', 'summary', 'text']) || '';
|
||||
const rs = pick(ev, ['start', 'start_date', 'date_start', 'date', 'date_from']);
|
||||
const re = pick(ev, ['end', 'end_date', 'date_end', 'date_to']);
|
||||
@ -520,7 +598,7 @@ export function initCalendarWidget(options = {}) {
|
||||
const location = ev.location || ev.place || '';
|
||||
const description = ev.description || ev.details || ev.note || '';
|
||||
const sourceLabel = ev.__source?.label || (ev.department?.name ?? ev.department) || 'Компания';
|
||||
return { title, start: s, end: e, allDay, color, recurrence, location, description, sourceLabel };
|
||||
return { id, title, start: s, end: e, allDay, color, recurrence, location, description, sourceLabel };
|
||||
}
|
||||
|
||||
function renderVertical(container, rangeLabel, events, ws, we) {
|
||||
@ -550,6 +628,17 @@ export function initCalendarWidget(options = {}) {
|
||||
list.forEach((ev) => {
|
||||
const row = document.createElement('div');
|
||||
row.className = 'week-row';
|
||||
|
||||
// Сохраняем ID события для обработчика клика (уже нормализовано в ev.id)
|
||||
if (ev.id) {
|
||||
row.dataset.eventId = String(ev.id);
|
||||
row.style.cursor = 'pointer';
|
||||
row.addEventListener('click', () => {
|
||||
console.log('[CalendarWidget] Opening event details:', ev.id);
|
||||
openEventDetailsById(ev.id);
|
||||
});
|
||||
}
|
||||
|
||||
// Цветовая точка слева от заголовка
|
||||
const dot = document.createElement('span');
|
||||
dot.className = 'color-dot';
|
||||
@ -610,7 +699,201 @@ export function initCalendarWidget(options = {}) {
|
||||
);
|
||||
}
|
||||
|
||||
/* ===== Offcanvas header height fix ===== */
|
||||
/* ===== Контекстное меню для событий ===== */
|
||||
const contextMenu = document.getElementById('calendarContextMenu');
|
||||
const contextMenuView = document.getElementById('contextMenuView');
|
||||
const contextMenuEdit = document.getElementById('contextMenuEdit');
|
||||
const contextMenuDelete = document.getElementById('contextMenuDelete');
|
||||
|
||||
let contextMenuEventId = null;
|
||||
let contextMenuLongPressTimer = null;
|
||||
|
||||
// Функция показа контекстного меню
|
||||
function showContextMenu(x, y, eventId) {
|
||||
if (!contextMenu) return;
|
||||
|
||||
contextMenuEventId = eventId;
|
||||
|
||||
// Проверяем права доступа
|
||||
checkEventPermissions(eventId).then(perms => {
|
||||
// Показываем/скрываем кнопки в зависимости от прав
|
||||
if (contextMenuEdit) {
|
||||
contextMenuEdit.classList.toggle('d-none', !perms.can_edit);
|
||||
}
|
||||
if (contextMenuDelete) {
|
||||
contextMenuDelete.classList.toggle('d-none', !perms.can_delete);
|
||||
}
|
||||
|
||||
// Скрываем разделитель если нет прав на удаление
|
||||
const divider = contextMenu.querySelector('.dropdown-divider[data-requires-permission="delete"]');
|
||||
if (divider) {
|
||||
divider.classList.toggle('d-none', !perms.can_delete);
|
||||
}
|
||||
|
||||
// Позиционируем меню
|
||||
contextMenu.style.left = x + 'px';
|
||||
contextMenu.style.top = y + 'px';
|
||||
contextMenu.style.display = 'block';
|
||||
});
|
||||
}
|
||||
|
||||
// Функция скрытия контекстного меню
|
||||
function hideContextMenu() {
|
||||
if (contextMenu) {
|
||||
contextMenu.style.display = 'none';
|
||||
}
|
||||
contextMenuEventId = null;
|
||||
}
|
||||
|
||||
// Проверка прав на событие
|
||||
async function checkEventPermissions(eventId) {
|
||||
try {
|
||||
const response = await fetch(`${API_EVENTS}${eventId}/permissions/`, {
|
||||
headers: authHeaders()
|
||||
});
|
||||
if (response.ok) {
|
||||
return await response.json();
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Failed to check permissions:', e);
|
||||
}
|
||||
return { can_view: true, can_edit: false, can_delete: false };
|
||||
}
|
||||
|
||||
// Обработчики кнопок контекстного меню
|
||||
if (contextMenuView) {
|
||||
contextMenuView.addEventListener('click', () => {
|
||||
if (contextMenuEventId) {
|
||||
openEventDetailsById(contextMenuEventId);
|
||||
}
|
||||
hideContextMenu();
|
||||
});
|
||||
}
|
||||
|
||||
if (contextMenuEdit) {
|
||||
contextMenuEdit.addEventListener('click', async () => {
|
||||
if (contextMenuEventId) {
|
||||
try {
|
||||
const data = await apiGet(`${API_EVENTS}${contextMenuEventId}/`);
|
||||
currentDetail = data;
|
||||
// Переиспользуем логику редактирования из старой кнопки "Редактировать"
|
||||
editEvent(data);
|
||||
} catch (err) {
|
||||
console.error('Load event for edit error', err);
|
||||
alert('Не удалось загрузить событие для редактирования.');
|
||||
}
|
||||
}
|
||||
hideContextMenu();
|
||||
});
|
||||
}
|
||||
|
||||
if (contextMenuDelete) {
|
||||
contextMenuDelete.addEventListener('click', async () => {
|
||||
if (contextMenuEventId) {
|
||||
if (!confirm('Удалить это событие без возможности восстановления?')) {
|
||||
hideContextMenu();
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await apiDelete(`${API_EVENTS}${contextMenuEventId}/`);
|
||||
invalidateCalendarEvents();
|
||||
try {
|
||||
deskCalendar?.refetchEvents?.();
|
||||
mobCalendar?.refetchEvents?.();
|
||||
} catch (_) {}
|
||||
updateWeekLists();
|
||||
} catch (err) {
|
||||
console.error('Delete event error', err);
|
||||
alert(
|
||||
err.status === 403 ? 'Недостаточно прав для удаления.' : 'Не удалось удалить событие.'
|
||||
);
|
||||
}
|
||||
}
|
||||
hideContextMenu();
|
||||
});
|
||||
}
|
||||
|
||||
// Закрытие контекстного меню при клике вне его
|
||||
document.addEventListener('click', (e) => {
|
||||
if (contextMenu && !contextMenu.contains(e.target)) {
|
||||
hideContextMenu();
|
||||
}
|
||||
});
|
||||
|
||||
// Функция редактирования события (вынесена из обработчика кнопки)
|
||||
function editEvent(data) {
|
||||
const form = document.getElementById('eventForm');
|
||||
if (!form) return;
|
||||
|
||||
form.dataset.mode = 'edit';
|
||||
form.dataset.eventId = data.id;
|
||||
|
||||
// Базовые поля
|
||||
form.querySelector('[name="title"]').value = data.title || '';
|
||||
form.querySelector('[name="location"]').value = data.location || '';
|
||||
form.querySelector('[name="color"]').value = data.color || DEFAULT_EVENT_COLOR;
|
||||
form.querySelector('[name="all_day"]').checked = !!data.all_day;
|
||||
|
||||
// Описание
|
||||
const descEl = form.querySelector('[name="description"]');
|
||||
if (descEl) descEl.value = data.description || '';
|
||||
|
||||
// Дата/время
|
||||
const startIso =
|
||||
data.start ||
|
||||
(data.start_date
|
||||
? `${data.start_date}T${(data.start_time || '00:00').slice(0, 5)}`
|
||||
: '');
|
||||
const endIso =
|
||||
data.end ||
|
||||
(data.end_date
|
||||
? `${data.end_date}T${(data.end_time || '00:00').slice(0, 5)}`
|
||||
: '');
|
||||
const startEl = form.querySelector('[name="start"]');
|
||||
const endEl = form.querySelector('[name="end"]');
|
||||
if (startEl) startEl.value = (startIso || '').slice(0, 16);
|
||||
if (endEl) endEl.value = (endIso || '').slice(0, 16);
|
||||
|
||||
// Повторы
|
||||
const recSel = form.querySelector('[name="recurrence"]');
|
||||
if (recSel) recSel.value = data.recurrence || 'one_time';
|
||||
const recInt = form.querySelector('[name="recurrence_interval"]');
|
||||
if (recInt) recInt.value = data.recurrence_interval || 1;
|
||||
|
||||
// Until / count
|
||||
const untilEl = form.querySelector('[name="recurrence_until"]');
|
||||
if (untilEl) untilEl.value = (data.recurrence_until || '').slice(0, 10);
|
||||
const countEl = form.querySelector('[name="recurrence_count"]');
|
||||
if (countEl) countEl.value = data.recurrence_count ?? '';
|
||||
|
||||
// Weekly дни из маски
|
||||
if (data.weekdays_mask != null) setWeekdaysFromMask(data.weekdays_mask);
|
||||
|
||||
// Синхронизация UI
|
||||
try {
|
||||
typeof syncByRecurrence === 'function' && syncByRecurrence();
|
||||
} catch (_) {}
|
||||
|
||||
// Отдел (если есть select)
|
||||
const deptSel = form.querySelector('[name="department_id"]');
|
||||
if (deptSel && data.department) {
|
||||
const val = data.department.id ?? data.department;
|
||||
if (val != null) deptSel.value = String(val);
|
||||
}
|
||||
|
||||
// Открываем форму редактирования
|
||||
const createModalEl = document.getElementById('eventCreateModal');
|
||||
if (createModalEl) {
|
||||
const createModal = bootstrap.Modal.getOrCreateInstance(createModalEl);
|
||||
createModal.show();
|
||||
} else {
|
||||
const offcanvasEl = document.getElementById('rightbarOffcanvas');
|
||||
if (offcanvasEl) bootstrap.Offcanvas.getOrCreateInstance(offcanvasEl).show();
|
||||
}
|
||||
form.querySelector('[name="title"]')?.focus();
|
||||
}
|
||||
|
||||
/* ===== Обработчики событий календаря ===== */
|
||||
const oc = document.getElementById('rightbarOffcanvas');
|
||||
const setHeadH = () => {
|
||||
const h = oc?.querySelector('.offcanvas-header')?.offsetHeight || 56;
|
||||
@ -630,6 +913,7 @@ export function initCalendarWidget(options = {}) {
|
||||
headerToolbar: { left: 'prev,next today', center: 'title', right: '' },
|
||||
height: 'auto',
|
||||
displayEventTime: false,
|
||||
selectable: true, // Позволяет выбирать даты
|
||||
events: async (info, success, failure) => {
|
||||
try {
|
||||
const raw = await fetchEventsCombined(info.start, info.end);
|
||||
@ -683,6 +967,85 @@ export function initCalendarWidget(options = {}) {
|
||||
s.fontFamily =
|
||||
'"Arial Narrow","Roboto Condensed",system-ui,-apple-system,"Segoe UI",Roboto,Helvetica,Arial,sans-serif';
|
||||
}
|
||||
|
||||
// Добавляем обработчик правой кнопки мыши
|
||||
info.el.addEventListener('contextmenu', (e) => {
|
||||
e.preventDefault();
|
||||
const eid = info.event.id || info.event.extendedProps?.id;
|
||||
if (eid) {
|
||||
showContextMenu(e.pageX, e.pageY, eid);
|
||||
}
|
||||
});
|
||||
|
||||
// Добавляем обработчик долгого нажатия для мобильных устройств
|
||||
info.el.addEventListener('touchstart', (e) => {
|
||||
contextMenuLongPressTimer = setTimeout(() => {
|
||||
const eid = info.event.id || info.event.extendedProps?.id;
|
||||
if (eid) {
|
||||
const touch = e.touches[0];
|
||||
showContextMenu(touch.pageX, touch.pageY, eid);
|
||||
}
|
||||
}, 500); // 500ms для долгого нажатия
|
||||
});
|
||||
|
||||
info.el.addEventListener('touchend', () => {
|
||||
if (contextMenuLongPressTimer) {
|
||||
clearTimeout(contextMenuLongPressTimer);
|
||||
contextMenuLongPressTimer = null;
|
||||
}
|
||||
});
|
||||
|
||||
info.el.addEventListener('touchmove', () => {
|
||||
if (contextMenuLongPressTimer) {
|
||||
clearTimeout(contextMenuLongPressTimer);
|
||||
contextMenuLongPressTimer = null;
|
||||
}
|
||||
});
|
||||
},
|
||||
// Обработчик клика по дате для создания нового события
|
||||
dateClick: (info) => {
|
||||
// Открываем модал создания события
|
||||
const form = document.getElementById('eventForm');
|
||||
if (!form) return;
|
||||
|
||||
// Очищаем форму
|
||||
form.reset();
|
||||
form.dataset.mode = 'create';
|
||||
delete form.dataset.eventId;
|
||||
|
||||
// Устанавливаем выбранную дату
|
||||
const clickedDate = info.dateStr; // YYYY-MM-DD
|
||||
const startEl = form.querySelector('[name="start"]');
|
||||
const endEl = form.querySelector('[name="end"]');
|
||||
|
||||
if (startEl) {
|
||||
// Устанавливаем дату начала (с текущим временем)
|
||||
const now = new Date();
|
||||
const timeStr = `${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}`;
|
||||
startEl.value = `${clickedDate}T${timeStr}`;
|
||||
}
|
||||
|
||||
if (endEl) {
|
||||
// Устанавливаем дату окончания (на час позже)
|
||||
const now = new Date();
|
||||
now.setHours(now.getHours() + 1);
|
||||
const timeStr = `${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}`;
|
||||
endEl.value = `${clickedDate}T${timeStr}`;
|
||||
}
|
||||
|
||||
// Синхронизируем UI
|
||||
try {
|
||||
typeof syncByRecurrence === 'function' && syncByRecurrence();
|
||||
typeof syncByAllDay === 'function' && syncByAllDay();
|
||||
typeof syncUntilCount === 'function' && syncUntilCount();
|
||||
} catch (_) {}
|
||||
|
||||
// Открываем модал
|
||||
const createModalEl = document.getElementById('eventCreateModal');
|
||||
if (createModalEl) {
|
||||
const createModal = bootstrap.Modal.getOrCreateInstance(createModalEl);
|
||||
createModal.show();
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
@ -764,104 +1127,6 @@ export function initCalendarWidget(options = {}) {
|
||||
syncByAllDay();
|
||||
syncUntilCount();
|
||||
|
||||
/* ===== Кнопки модала: редактирование/удаление ===== */
|
||||
$dt?.btnEdit?.addEventListener?.('click', () => {
|
||||
if (!currentDetail) return;
|
||||
const form = document.getElementById('eventForm');
|
||||
if (!form) {
|
||||
detailsModal?.hide();
|
||||
return;
|
||||
}
|
||||
form.dataset.mode = 'edit';
|
||||
form.dataset.eventId = currentDetail.id;
|
||||
|
||||
// Базовые поля
|
||||
form.querySelector('[name="title"]').value = currentDetail.title || '';
|
||||
form.querySelector('[name="location"]').value = currentDetail.location || '';
|
||||
form.querySelector('[name="color"]').value = currentDetail.color || DEFAULT_EVENT_COLOR;
|
||||
form.querySelector('[name="all_day"]').checked = !!currentDetail.all_day;
|
||||
|
||||
// Описание
|
||||
const descEl = form.querySelector('[name="description"]');
|
||||
if (descEl) descEl.value = currentDetail.description || '';
|
||||
|
||||
// Дата/время
|
||||
const startIso =
|
||||
currentDetail.start ||
|
||||
(currentDetail.start_date
|
||||
? `${currentDetail.start_date}T${(currentDetail.start_time || '00:00').slice(0, 5)}`
|
||||
: '');
|
||||
const endIso =
|
||||
currentDetail.end ||
|
||||
(currentDetail.end_date
|
||||
? `${currentDetail.end_date}T${(currentDetail.end_time || '00:00').slice(0, 5)}`
|
||||
: '');
|
||||
const startEl = form.querySelector('[name="start"]');
|
||||
const endEl = form.querySelector('[name="end"]');
|
||||
if (startEl) startEl.value = (startIso || '').slice(0, 16);
|
||||
if (endEl) endEl.value = (endIso || '').slice(0, 16);
|
||||
|
||||
// Повторы
|
||||
const recSel = form.querySelector('[name="recurrence"]');
|
||||
if (recSel) recSel.value = currentDetail.recurrence || 'one_time';
|
||||
const recInt = form.querySelector('[name="recurrence_interval"]');
|
||||
if (recInt) recInt.value = currentDetail.recurrence_interval || 1;
|
||||
|
||||
// Until / count
|
||||
const untilEl = form.querySelector('[name="recurrence_until"]');
|
||||
if (untilEl) untilEl.value = (currentDetail.recurrence_until || '').slice(0, 10);
|
||||
const countEl = form.querySelector('[name="recurrence_count"]');
|
||||
if (countEl) countEl.value = currentDetail.recurrence_count ?? '';
|
||||
|
||||
// Weekly дни из маски
|
||||
if (currentDetail.weekdays_mask != null) setWeekdaysFromMask(currentDetail.weekdays_mask);
|
||||
|
||||
// Синхронизация UI
|
||||
try {
|
||||
typeof syncByRecurrence === 'function' && syncByRecurrence();
|
||||
} catch (_) {}
|
||||
|
||||
// Отдел (если есть select)
|
||||
const deptSel = form.querySelector('[name="department_id"]');
|
||||
if (deptSel && currentDetail.department) {
|
||||
const val = currentDetail.department.id ?? currentDetail.department;
|
||||
if (val != null) deptSel.value = String(val);
|
||||
}
|
||||
|
||||
// Открываем форму редактирования
|
||||
detailsModal?.hide();
|
||||
setTimeout(() => {
|
||||
const createModalEl = document.getElementById('eventCreateModal');
|
||||
if (createModalEl) {
|
||||
const createModal = bootstrap.Modal.getOrCreateInstance(createModalEl);
|
||||
createModal.show();
|
||||
} else {
|
||||
const offcanvasEl = document.getElementById('rightbarOffcanvas');
|
||||
if (offcanvasEl) bootstrap.Offcanvas.getOrCreateInstance(offcanvasEl).show();
|
||||
}
|
||||
form.querySelector('[name="title"]')?.focus();
|
||||
}, 150);
|
||||
});
|
||||
|
||||
$dt?.btnDel?.addEventListener?.('click', async () => {
|
||||
if (!currentDetail) return;
|
||||
if (!confirm('Удалить это событие без возможности восстановления?')) return;
|
||||
try {
|
||||
await apiDelete(`${API_EVENTS}${currentDetail.id}/`);
|
||||
detailsModal?.hide();
|
||||
try {
|
||||
deskCalendar?.refetchEvents?.();
|
||||
mobCalendar?.refetchEvents?.();
|
||||
} catch (_) {}
|
||||
updateWeekLists();
|
||||
} catch (err) {
|
||||
console.error('Delete event error', err);
|
||||
alert(
|
||||
err.status === 403 ? 'Недостаточно прав для удаления.' : 'Не удалось удалить событие.'
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
/* ===== Цвет: палитра и синхронизация с input[type=color] ===== */
|
||||
function initColorPicker() {
|
||||
const form = document.getElementById('eventForm');
|
||||
@ -965,8 +1230,14 @@ export function initCalendarWidget(options = {}) {
|
||||
payload.weekdays = weekdays;
|
||||
}
|
||||
|
||||
// Область — компания/отдел
|
||||
if (state.type === 'dept') {
|
||||
// Область — компания/отдел/личный
|
||||
if (state.type === 'personal') {
|
||||
if (!state.employeeId) {
|
||||
alert('Не удалось определить ID пользователя');
|
||||
return;
|
||||
}
|
||||
payload.employee_id = Number(state.employeeId);
|
||||
} else if (state.type === 'dept') {
|
||||
if (!DIGITS_RE.test(String(state.deptId))) {
|
||||
alert('Некорректный отдел');
|
||||
return;
|
||||
@ -998,6 +1269,7 @@ export function initCalendarWidget(options = {}) {
|
||||
syncByRecurrence();
|
||||
syncByAllDay();
|
||||
syncUntilCount();
|
||||
invalidateCalendarEvents();
|
||||
[deskCalendar, mobCalendar].forEach((cal) => cal?.refetchEvents());
|
||||
updateWeekLists();
|
||||
} catch (err) {
|
||||
@ -1027,6 +1299,7 @@ export function initCalendarWidget(options = {}) {
|
||||
},
|
||||
getDepartments: () => departments,
|
||||
getState: () => ({ ...state }),
|
||||
openEventById: (eventId) => openEventDetailsById(eventId),
|
||||
};
|
||||
}
|
||||
|
||||
@ -1034,5 +1307,15 @@ export function initCalendarWidget(options = {}) {
|
||||
if (typeof document !== 'undefined') {
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
window.calendarWidget = initCalendarWidget();
|
||||
|
||||
// Проверяем URL на наличие параметра event_id для открытия модала
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const eventIdFromUrl = urlParams.get('event_id');
|
||||
if (eventIdFromUrl && window.calendarWidget) {
|
||||
// Небольшая задержка, чтобы календарь успел загрузиться
|
||||
setTimeout(() => {
|
||||
window.calendarWidget.openEventById(eventIdFromUrl);
|
||||
}, 500);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user