// document-fields.js - Скрипт для управления полями документа в задачах
// Показывает кнопку "Реквизиты" только для задач с типом "document",
// только когда задача раскрыта и только для пользователей из групп "Подписант" или "Секретарь"
(function() {
'use strict';
// Конфигурация
const CONFIG = {
modalId: 'documentFieldsModal',
signerGroup: 'Подписант',
secretaryGroup: 'Секретарь',
apiEndpoint: '/api2/idusers',
usersEndpoint: '/api/users',
modalStyles: `
`,
buttonStyles: `
`
};
// Текущий пользователь
let currentUser = null;
// Группы пользователя (все группы из разных источников)
let userGroups = [];
// Кэш для типов задач
const taskTypeCache = new Map();
// Множество задач, для которых уже выполняется проверка
const pendingChecks = new Set();
// Селекторы для поиска раскрытых задач
const EXPANDED_TASK_SELECTORS = [
'.task-card.expanded',
'.task-item.expanded',
'[data-expanded="true"]',
'.task-details',
'.task-content.expanded'
];
// Состояние календаря
let calendarState = {
currentDate: new Date(),
selectedDate: null,
isOpen: false,
inputElement: null
};
// Получение текущего пользователя
async function getCurrentUser() {
try {
const response = await fetch('/api/user');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
if (data.user) {
currentUser = data.user;
console.log('✅ DocumentFields: текущий пользователь', currentUser.login);
// Получаем все группы пользователя
await getAllUserGroups(currentUser.login || currentUser.id);
}
return currentUser;
} catch (error) {
console.error('❌ DocumentFields: ошибка получения пользователя', error);
return null;
}
}
// Получение ВСЕХ групп пользователя из разных источников
async function getAllUserGroups(userLogin) {
const allGroups = new Set();
try {
// 1. Пробуем получить через API /api2/idusers
await fetchGroupsFromApi2(userLogin, allGroups);
// 2. Пробуем получить через API /api/users
await fetchGroupsFromUsersApi(userLogin, allGroups);
// 3. Проверяем, есть ли группы в currentUser
if (currentUser) {
addGroupsFromCurrentUser(allGroups);
}
// Преобразуем Set в массив и сохраняем
userGroups = Array.from(allGroups);
console.log('✅ DocumentFields: ВСЕ группы пользователя:', userGroups);
// Детальный вывод для отладки
console.log('🔍 Проверка доступа:');
console.log(' - Группы:', userGroups);
console.log(' - Ищем "Подписант":', userGroups.some(g =>
g.toLowerCase().includes(CONFIG.signerGroup.toLowerCase())));
console.log(' - Ищем "Секретарь":', userGroups.some(g =>
g.toLowerCase().includes(CONFIG.secretaryGroup.toLowerCase())));
return userGroups;
} catch (error) {
console.error('❌ DocumentFields: ошибка получения групп', error);
return [];
}
}
// Получение групп из /api2/idusers
async function fetchGroupsFromApi2(userLogin, groupsSet) {
try {
const response = await fetch(`${CONFIG.apiEndpoint}?login=${userLogin}`);
if (!response.ok) {
console.warn('⚠️ DocumentFields: API /api2/idusers вернул ошибку', response.status);
return;
}
const data = await response.json();
console.log('📦 Данные из /api2/idusers:', data);
// Обрабатываем массив пользователей
if (Array.isArray(data)) {
const userData = data.find(u =>
u.login === userLogin ||
u.user_login === userLogin ||
u.name === userLogin ||
u.user_name === userLogin
);
if (userData) {
extractGroupsFromUserData(userData, groupsSet);
}
}
// Обрабатываем объект пользователя
else if (data && typeof data === 'object') {
extractGroupsFromUserData(data, groupsSet);
}
} catch (error) {
console.error('❌ DocumentFields: ошибка fetchGroupsFromApi2', error);
}
}
// Получение групп из /api/users
async function fetchGroupsFromUsersApi(userLogin, groupsSet) {
try {
const response = await fetch(`${CONFIG.usersEndpoint}?login=${userLogin}`);
if (!response.ok) {
console.warn('⚠️ DocumentFields: API /api/users вернул ошибку', response.status);
return;
}
const data = await response.json();
console.log('📦 Данные из /api/users:', data);
// Обрабатываем данные в зависимости от формата
if (Array.isArray(data)) {
const userData = data.find(u =>
u.login === userLogin ||
u.username === userLogin ||
u.email === userLogin
);
if (userData) {
extractGroupsFromUserData(userData, groupsSet);
}
} else if (data && typeof data === 'object') {
extractGroupsFromUserData(data, groupsSet);
}
} catch (error) {
console.error('❌ DocumentFields: ошибка fetchGroupsFromUsersApi', error);
}
}
// Извлечение групп из данных пользователя
function extractGroupsFromUserData(userData, groupsSet) {
if (!userData) return;
console.log('🔍 Извлекаем группы из:', userData);
// 1. Проверяем group_name (может быть строкой или массивом)
if (userData.group_name) {
if (Array.isArray(userData.group_name)) {
userData.group_name.forEach(g => {
if (g) groupsSet.add(String(g).trim());
});
} else if (typeof userData.group_name === 'string') {
// Может быть строкой с разделителями
const groups = userData.group_name.split(/[,;|]/).map(g => g.trim());
groups.forEach(g => {
if (g) groupsSet.add(g);
});
}
}
// 2. Проверяем ldap_group
if (userData.ldap_group) {
if (Array.isArray(userData.ldap_group)) {
userData.ldap_group.forEach(g => {
if (g) groupsSet.add(String(g).trim());
});
} else if (typeof userData.ldap_group === 'string') {
groupsSet.add(userData.ldap_group.trim());
}
}
// 3. Проверяем metadata.groups
if (userData.metadata?.groups) {
if (Array.isArray(userData.metadata.groups)) {
userData.metadata.groups.forEach(g => {
if (g) groupsSet.add(String(g).trim());
});
}
}
// 4. Проверяем просто groups (если есть)
if (userData.groups) {
if (Array.isArray(userData.groups)) {
userData.groups.forEach(g => {
if (g) groupsSet.add(String(g).trim());
});
} else if (typeof userData.groups === 'string') {
const groups = userData.groups.split(/[,;|]/).map(g => g.trim());
groups.forEach(g => {
if (g) groupsSet.add(g);
});
}
}
// 5. Проверяем roles (если есть)
if (userData.roles) {
if (Array.isArray(userData.roles)) {
userData.roles.forEach(r => {
if (r) groupsSet.add(String(r).trim());
});
}
}
// 6. Проверяем department (может содержать группу)
if (userData.department) {
groupsSet.add(String(userData.department).trim());
}
// 7. Проверяем position (может содержать группу)
if (userData.position) {
groupsSet.add(String(userData.position).trim());
}
}
// Добавление групп из currentUser
function addGroupsFromCurrentUser(groupsSet) {
if (!currentUser) return;
// Проверяем различные поля в currentUser
const fieldsToCheck = [
'group',
'groups',
'role',
'roles',
'department',
'position',
'user_group',
'user_groups'
];
fieldsToCheck.forEach(field => {
if (currentUser[field]) {
if (Array.isArray(currentUser[field])) {
currentUser[field].forEach(g => {
if (g) groupsSet.add(String(g).trim());
});
} else if (typeof currentUser[field] === 'string') {
groupsSet.add(currentUser[field].trim());
}
}
});
}
// Проверка, имеет ли пользователь доступ (Подписант или Секретарь)
function hasUserAccess() {
if (!userGroups || userGroups.length === 0) {
console.log('❌ DocumentFields: у пользователя нет групп');
return false;
}
// Приводим искомые группы к нижнему регистру
const signerGroupLower = CONFIG.signerGroup.toLowerCase();
const secretaryGroupLower = CONFIG.secretaryGroup.toLowerCase();
// Проверяем КАЖДУЮ группу пользователя
for (const group of userGroups) {
if (!group) continue;
const groupLower = String(group).toLowerCase().trim();
// Точное совпадение
if (groupLower === signerGroupLower || groupLower === secretaryGroupLower) {
console.log(`✅ Найдено точное совпадение: "${group}"`);
return true;
}
// Частичное совпадение (содержит подстроку)
if (groupLower.includes(signerGroupLower) || groupLower.includes(secretaryGroupLower)) {
console.log(`✅ Найдено частичное совпадение: "${group}" содержит искомую группу`);
return true;
}
// Совпадение по словам (если группа состоит из нескольких слов)
const groupWords = groupLower.split(/[\s\-_]/);
const signerWords = signerGroupLower.split(/[\s\-_]/);
const secretaryWords = secretaryGroupLower.split(/[\s\-_]/);
// Проверяем, содержит ли группа все слова из искомой группы
const matchesSigner = signerWords.every(word =>
groupLower.includes(word) || groupWords.some(gw => gw.includes(word))
);
const matchesSecretary = secretaryWords.every(word =>
groupLower.includes(word) || groupWords.some(gw => gw.includes(word))
);
if (matchesSigner || matchesSecretary) {
console.log(`✅ Найдено совпадение по словам: "${group}"`);
return true;
}
}
console.log('❌ DocumentFields: пользователь НЕ имеет доступа');
console.log(' Группы пользователя:', userGroups);
console.log(' Нужные группы:', [CONFIG.signerGroup, CONFIG.secretaryGroup]);
return false;
}
// Функция для отладки - показывает все группы пользователя
function debugUserGroups() {
console.log('=== ОТЛАДКА ГРУПП ПОЛЬЗОВАТЕЛЯ ===');
console.log('Текущий пользователь:', currentUser);
console.log('Все группы:', userGroups);
console.log('Ищем "Подписант":', userGroups.filter(g =>
g.toLowerCase().includes(CONFIG.signerGroup.toLowerCase())
));
console.log('Ищем "Секретарь":', userGroups.filter(g =>
g.toLowerCase().includes(CONFIG.secretaryGroup.toLowerCase())
));
console.log('Доступ:', hasUserAccess());
console.log('================================');
return {
user: currentUser,
groups: userGroups,
hasAccess: hasUserAccess()
};
}
// Получение типа задачи по ID
async function getTaskType(taskId) {
// Проверяем кэш
if (taskTypeCache.has(taskId)) {
return taskTypeCache.get(taskId);
}
// Проверяем, не выполняется ли уже проверка для этой задачи
if (pendingChecks.has(taskId)) {
// Ждем завершения проверки
return new Promise((resolve) => {
const checkInterval = setInterval(() => {
if (taskTypeCache.has(taskId)) {
clearInterval(checkInterval);
resolve(taskTypeCache.get(taskId));
}
}, 100);
// Таймаут на случай ошибки
setTimeout(() => {
clearInterval(checkInterval);
resolve('regular');
}, 5000);
});
}
pendingChecks.add(taskId);
try {
const response = await fetch(`/api/tasks/${taskId}/type`);
if (!response.ok) {
if (response.status === 404) {
taskTypeCache.set(taskId, 'regular');
return 'regular';
}
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
const taskType = data.task_type || 'regular';
// Сохраняем в кэш
taskTypeCache.set(taskId, taskType);
return taskType;
} catch (error) {
console.error(`❌ Ошибка получения типа задачи ${taskId}:`, error);
taskTypeCache.set(taskId, 'regular');
return 'regular';
} finally {
pendingChecks.delete(taskId);
}
}
// Проверка, раскрыта ли задача
function isTaskExpanded(taskCard) {
// Проверяем по различным селекторам
for (const selector of EXPANDED_TASK_SELECTORS) {
if (taskCard.matches(selector) || taskCard.querySelector(selector)) {
return true;
}
}
// Проверяем наличие классов раскрытия
if (taskCard.classList.contains('expanded') ||
taskCard.classList.contains('open') ||
taskCard.classList.contains('active')) {
return true;
}
// Проверяем атрибуты
if (taskCard.getAttribute('data-expanded') === 'true' ||
taskCard.getAttribute('aria-expanded') === 'true') {
return true;
}
return false;
}
// Функции для работы с календарем
function formatDate(date) {
if (!date) return '';
const day = String(date.getDate()).padStart(2, '0');
const month = String(date.getMonth() + 1).padStart(2, '0');
const year = date.getFullYear();
return `${day}.${month}.${year}`;
}
function parseDate(dateStr) {
if (!dateStr) return null;
const parts = dateStr.split('.');
if (parts.length === 3) {
const day = parseInt(parts[0], 10);
const month = parseInt(parts[1], 10) - 1;
const year = parseInt(parts[2], 10);
return new Date(year, month, day);
}
return null;
}
function getMonthName(month, year) {
const date = new Date(year, month, 1);
return date.toLocaleString('ru-RU', { month: 'long' });
}
function generateCalendar(year, month, selectedDate) {
const firstDay = new Date(year, month, 1).getDay();
const daysInMonth = new Date(year, month + 1, 0).getDate();
// Корректировка первого дня (0 - воскресенье, делаем понедельник первым)
let startOffset = firstDay === 0 ? 6 : firstDay - 1;
const today = new Date();
const todayStr = formatDate(today);
let html = '';
let dayCount = 1;
for (let i = 0; i < 6; i++) {
for (let j = 0; j < 7; j++) {
if (i === 0 && j < startOffset) {
html += '
';
} else if (dayCount <= daysInMonth) {
const currentDate = new Date(year, month, dayCount);
const dateStr = formatDate(currentDate);
const isSelected = selectedDate && dateStr === formatDate(selectedDate);
const isToday = dateStr === todayStr;
html += `${dayCount}
`;
dayCount++;
} else {
html += '';
}
}
}
return html;
}
function renderCalendar() {
const calendar = document.getElementById('inlineCalendar');
if (!calendar) return;
const year = calendarState.currentDate.getFullYear();
const month = calendarState.currentDate.getMonth();
const monthName = getMonthName(month, year);
let html = `
${generateCalendar(year, month, calendarState.selectedDate)}
`;
calendar.innerHTML = html;
}
function toggleCalendar() {
if (calendarState.isOpen) {
closeCalendar();
} else {
openCalendar();
}
}
function openCalendar() {
const calendar = document.getElementById('inlineCalendar');
if (!calendar) return;
const dateInput = document.getElementById('documentDate');
if (dateInput && dateInput.value) {
const parsedDate = parseDate(dateInput.value);
if (parsedDate && !isNaN(parsedDate.getTime())) {
calendarState.selectedDate = parsedDate;
calendarState.currentDate = new Date(parsedDate);
}
}
calendarState.isOpen = true;
calendar.style.display = 'block';
renderCalendar();
}
function closeCalendar() {
const calendar = document.getElementById('inlineCalendar');
if (calendar) {
calendar.style.display = 'none';
}
calendarState.isOpen = false;
}
function selectDate(dateStr) {
const dateInput = document.getElementById('documentDate');
if (dateInput) {
dateInput.value = dateStr;
calendarState.selectedDate = parseDate(dateStr);
}
closeCalendar();
}
function prevMonth() {
calendarState.currentDate.setMonth(calendarState.currentDate.getMonth() - 1);
renderCalendar();
}
function nextMonth() {
calendarState.currentDate.setMonth(calendarState.currentDate.getMonth() + 1);
renderCalendar();
}
// Создание модального окна
function createModal() {
if (!document.getElementById('document-fields-styles')) {
const styleElement = document.createElement('div');
styleElement.id = 'document-fields-styles';
styleElement.innerHTML = CONFIG.modalStyles + CONFIG.buttonStyles;
document.head.appendChild(styleElement);
}
if (document.getElementById(CONFIG.modalId)) {
return;
}
const modalHTML = `
`;
document.body.insertAdjacentHTML('beforeend', modalHTML);
// Добавляем обработчик клика вне календаря для его закрытия
document.addEventListener('click', function(event) {
const calendar = document.getElementById('inlineCalendar');
const calendarIcon = document.querySelector('.calendar-icon');
if (calendar && calendarState.isOpen) {
if (!calendar.contains(event.target) && !calendarIcon.contains(event.target)) {
closeCalendar();
}
}
});
}
// Открытие модального окна
async function openModal(taskId) {
// Проверяем тип задачи
const taskType = await getTaskType(taskId);
if (taskType !== 'document') {
alert('Эта функция доступна только для задач типа "Документ"');
return;
}
const modal = document.getElementById(CONFIG.modalId);
if (!modal) {
createModal();
}
const modalElement = document.getElementById(CONFIG.modalId);
const form = document.getElementById('documentFieldsForm');
const loading = document.getElementById('documentFieldsLoading');
const errorDiv = document.getElementById('documentFieldsError');
const messageDiv = document.getElementById('documentFieldsMessage');
if (errorDiv) errorDiv.style.display = 'none';
if (messageDiv) messageDiv.style.display = 'none';
if (form) form.style.display = 'none';
if (loading) loading.style.display = 'block';
modalElement.classList.add('active');
const taskIdSpan = document.getElementById('documentTaskId');
if (taskIdSpan) taskIdSpan.textContent = taskId;
const authorInput = document.getElementById('documentAuthor');
if (authorInput && currentUser) {
authorInput.value = currentUser.login || '';
}
try {
const response = await fetch(`/api/tasks/${taskId}/document-fields`);
if (!response.ok) {
throw new Error('Ошибка загрузки данных');
}
const result = await response.json();
if (result.success) {
const numberInput = document.getElementById('documentNumber');
const dateInput = document.getElementById('documentDate');
if (numberInput) numberInput.value = result.data.document_n || '';
if (dateInput) {
dateInput.value = result.data.document_d || '';
// Устанавливаем выбранную дату в календарь
if (result.data.document_d) {
const parsedDate = parseDate(result.data.document_d);
if (parsedDate && !isNaN(parsedDate.getTime())) {
calendarState.selectedDate = parsedDate;
calendarState.currentDate = new Date(parsedDate);
}
} else {
calendarState.selectedDate = null;
calendarState.currentDate = new Date();
}
}
if (result.data.document_a && authorInput && !authorInput.value) {
authorInput.value = result.data.document_a;
}
}
} catch (error) {
console.error('❌ Ошибка загрузки полей документа:', error);
showError('Ошибка загрузки данных: ' + error.message);
} finally {
if (loading) loading.style.display = 'none';
if (form) form.style.display = 'block';
}
}
// Закрытие модального окна
function closeModal() {
const modal = document.getElementById(CONFIG.modalId);
if (modal) {
modal.classList.remove('active');
const numberInput = document.getElementById('documentNumber');
const dateInput = document.getElementById('documentDate');
const authorInput = document.getElementById('documentAuthor');
if (numberInput) numberInput.value = '';
if (dateInput) dateInput.value = '';
if (authorInput) authorInput.value = '';
// Закрываем календарь
closeCalendar();
// Сбрасываем состояние календаря
calendarState = {
currentDate: new Date(),
selectedDate: null,
isOpen: false,
inputElement: null
};
}
}
// Показать ошибку
function showError(message) {
const errorDiv = document.getElementById('documentFieldsError');
if (!errorDiv) return;
errorDiv.textContent = message;
errorDiv.style.display = 'block';
setTimeout(() => {
errorDiv.style.display = 'none';
}, 5000);
}
// Показать успех
function showSuccess(message) {
const messageDiv = document.getElementById('documentFieldsMessage');
if (!messageDiv) return;
messageDiv.textContent = message;
messageDiv.style.display = 'block';
setTimeout(() => {
messageDiv.style.display = 'none';
closeModal();
}, 2000);
}
// Валидация даты
function validateDate(dateStr) {
if (!dateStr) return true;
const datePattern = /^(\d{2})\.(\d{2})\.(\d{4})$/;
if (!datePattern.test(dateStr)) {
return 'Неверный формат даты. Используйте ДД.ММ.ГГГГ';
}
const [, day, month, year] = dateStr.match(datePattern);
const date = new Date(year, month - 1, day);
if (date.getDate() != day || date.getMonth() + 1 != month || date.getFullYear() != year) {
return 'Некорректная дата';
}
return true;
}
// Сохранение полей
async function saveFields() {
const taskId = document.getElementById('documentTaskId')?.textContent;
if (!taskId) {
showError('ID задачи не найден');
return;
}
const document_n = document.getElementById('documentNumber')?.value.trim() || '';
let document_d = document.getElementById('documentDate')?.value.trim() || '';
const document_a = document.getElementById('documentAuthor')?.value.trim() || currentUser?.login;
if (document_d) {
const dateValidation = validateDate(document_d);
if (dateValidation !== true) {
showError(dateValidation);
return;
}
}
const saveBtn = document.querySelector('.document-fields-btn.save');
if (!saveBtn) return;
const originalText = saveBtn.textContent;
saveBtn.textContent = 'Сохранение...';
saveBtn.disabled = true;
try {
const response = await fetch(`/api/tasks/${taskId}/document-fields`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
document_n: document_n || null,
document_d: document_d || null,
document_a: document_a || null
})
});
const result = await response.json();
if (response.ok && result.success) {
showSuccess('✅ Поля документа сохранены');
updateTaskCardDisplay(taskId, result.data);
} else {
showError(result.error || 'Ошибка сохранения');
}
} catch (error) {
console.error('❌ Ошибка сохранения:', error);
showError('Ошибка сети: ' + error.message);
} finally {
saveBtn.textContent = originalText;
saveBtn.disabled = false;
}
}
// Обновление отображения в карточке задачи
function updateTaskCardDisplay(taskId, data) {
const taskCards = document.querySelectorAll(`[data-task-id="${taskId}"]`);
taskCards.forEach(card => {
let docContainer = card.querySelector('.document-fields-display');
if (!docContainer) {
docContainer = document.createElement('div');
docContainer.className = 'document-fields-display';
const header = card.querySelector('.task-header');
if (header) {
header.after(docContainer);
} else {
card.prepend(docContainer);
}
}
let displayHTML = '';
if (data.document_n) {
displayHTML += `№: ${data.document_n}`;
}
if (data.document_d) {
displayHTML += `Дата: ${data.document_d}`;
}
if (data.document_a) {
displayHTML += `Автор: ${data.document_a}`;
}
if (!data.document_n && !data.document_d) {
displayHTML += 'Нет данных документа';
}
displayHTML += '
';
docContainer.innerHTML = displayHTML;
});
}
// Добавление кнопки в раскрытую задачу
async function addButtonToExpandedTask(taskCard) {
if (!currentUser) return;
// Проверяем, имеет ли пользователь доступ (Подписант или Секретарь)
if (!hasUserAccess()) {
return;
}
const taskId = taskCard.dataset.taskId;
if (!taskId) return;
// Проверяем, есть ли уже кнопка
if (taskCard.querySelector('.document-fields-btn-icon')) return;
// Проверяем, раскрыта ли задача
if (!isTaskExpanded(taskCard)) return;
// Показываем индикатор загрузки
const actionsContainer = taskCard.querySelector('.action-buttons, .task-actions');
if (!actionsContainer) return;
const loadingIndicator = document.createElement('span');
loadingIndicator.className = 'document-fields-placeholder';
loadingIndicator.title = 'Проверка типа задачи...';
actionsContainer.appendChild(loadingIndicator);
try {
// Получаем тип задачи
const taskType = await getTaskType(taskId);
// Удаляем индикатор загрузки
loadingIndicator.remove();
// Добавляем кнопку только для задач с типом "document"
if (taskType === 'document') {
const btn = document.createElement('button');
btn.className = 'document-fields-btn-icon document';
btn.innerHTML = '📄 Реквизиты';
btn.onclick = (e) => {
e.stopPropagation();
window.documentFields.openModal(taskId);
};
btn.title = 'Редактировать реквизиты документа';
actionsContainer.appendChild(btn);
console.log(`✅ Добавлена кнопка для раскрытой задачи ${taskId} (тип: document)`);
}
} catch (error) {
console.error(`❌ Ошибка проверки задачи ${taskId}:`, error);
loadingIndicator.remove();
}
}
// Обработчик раскрытия задачи
function handleTaskExpand(mutations) {
for (const mutation of mutations) {
if (mutation.type === 'attributes' &&
(mutation.attributeName === 'class' || mutation.attributeName === 'data-expanded')) {
const target = mutation.target;
if (target.dataset?.taskId && isTaskExpanded(target)) {
addButtonToExpandedTask(target);
}
}
if (mutation.type === 'childList' && mutation.addedNodes.length) {
for (const node of mutation.addedNodes) {
if (node.nodeType === 1) { // Element node
// Проверяем сам элемент
if (node.dataset?.taskId && isTaskExpanded(node)) {
addButtonToExpandedTask(node);
}
// Проверяем дочерние элементы
const expandedTasks = node.querySelectorAll('[data-task-id]');
expandedTasks.forEach(task => {
if (isTaskExpanded(task)) {
addButtonToExpandedTask(task);
}
});
}
}
}
}
}
// Наблюдатель за изменениями DOM
function observeDOM() {
// Наблюдатель за атрибутами и появлением новых элементов
const observer = new MutationObserver(handleTaskExpand);
observer.observe(document.body, {
attributes: true,
attributeFilter: ['class', 'data-expanded', 'aria-expanded'],
childList: true,
subtree: true
});
console.log('👀 Наблюдатель за раскрытием задач запущен');
// Также проверяем уже раскрытые задачи при загрузке
setTimeout(() => {
document.querySelectorAll('[data-task-id]').forEach(task => {
if (isTaskExpanded(task)) {
addButtonToExpandedTask(task);
}
});
}, 2000);
}
// Очистка кэша
function clearCache() {
taskTypeCache.clear();
pendingChecks.clear();
console.log('🗑️ Кэш типов задач очищен');
}
// Функция для ручного обновления групп пользователя
async function refreshUserGroups() {
if (currentUser) {
await getAllUserGroups(currentUser.login || currentUser.id);
return hasUserAccess();
}
return false;
}
// Инициализация
async function init() {
console.log('🔄 DocumentFields module initializing...');
try {
await getCurrentUser();
// Проверяем доступ пользователя
if (hasUserAccess()) {
createModal();
observeDOM();
console.log('✅ DocumentFields module loaded (with access)');
// Для отладки показываем группы
setTimeout(debugUserGroups, 1000);
} else {
console.log('ℹ️ DocumentFields module loaded (no access - user is not Signer or Secretary)');
debugUserGroups(); // Показываем отладку даже при отсутствии доступа
}
} catch (error) {
console.error('❌ Ошибка инициализации:', error);
}
}
// Экспортируем функции
window.documentFields = {
openModal,
closeModal,
saveFields,
getTaskType,
clearCache,
init,
addButtonToExpandedTask,
isTaskExpanded,
hasUserAccess,
getAllUserGroups,
refreshUserGroups,
debugUserGroups,
// Функции календаря
toggleCalendar,
selectDate,
prevMonth,
nextMonth
};
// Запускаем инициализацию
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})();