// document-fields.js - Скрипт для управления полями документа в задачах // Показывает кнопку "Реквизиты" только для задач с типом "document" и только когда задача раскрыта (function() { 'use strict'; // Конфигурация const CONFIG = { modalId: 'documentFieldsModal', modalStyles: ` `, buttonStyles: ` ` }; // Текущий пользователь let currentUser = null; // Кэш для типов задач 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' ]; // Получение текущего пользователя 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); } return currentUser; } catch (error) { console.error('❌ DocumentFields: ошибка получения пользователя', error); return null; } } // Получение типа задачи по 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 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); } // Открытие модального окна 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_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 = ''; } } // Показать ошибку 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; 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 init() { console.log('🔄 DocumentFields module initializing...'); try { await getCurrentUser(); createModal(); observeDOM(); console.log('✅ DocumentFields module loaded'); } catch (error) { console.error('❌ Ошибка инициализации:', error); } } // Экспортируем функции window.documentFields = { openModal, closeModal, saveFields, getTaskType, clearCache, init, addButtonToExpandedTask, isTaskExpanded }; // Запускаем инициализацию if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } })();