diff --git a/public/document-fields.js b/public/document-fields.js index ffd8652..c04866a 100644 --- a/public/document-fields.js +++ b/public/document-fields.js @@ -1,4 +1,5 @@ // document-fields.js - Скрипт для управления полями документа в задачах +// Показывает кнопку "Реквизиты" только для задач с типом "document" и только когда задача раскрыта (function() { 'use strict'; @@ -202,6 +203,9 @@ cursor: pointer; font-size: 0.9em; transition: background-color 0.2s; + display: inline-flex; + align-items: center; + gap: 4px; } .document-fields-btn-icon:hover { @@ -216,6 +220,11 @@ background-color: #7B1FA2; } + .document-fields-btn-icon.document.loading { + opacity: 0.7; + cursor: wait; + } + .document-fields-badge { display: inline-block; padding: 2px 8px; @@ -225,21 +234,55 @@ color: #1976d2; margin-left: 8px; } + + .document-fields-placeholder { + display: inline-block; + width: 16px; + height: 16px; + border: 2px solid #f3f3f3; + border-top: 2px solid #9C27B0; + border-radius: 50%; + animation: spin 1s linear infinite; + margin-right: 4px; + } + + @keyframes spin { + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } + } ` }; // Текущий пользователь 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); + console.log('✅ DocumentFields: текущий пользователь', currentUser.login); } return currentUser; } catch (error) { @@ -248,9 +291,88 @@ } } + // Получение типа задачи по 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'; @@ -258,7 +380,6 @@ document.head.appendChild(styleElement); } - // Проверяем, существует ли уже модальное окно if (document.getElementById(CONFIG.modalId)) { return; } @@ -313,6 +434,14 @@ // Открытие модального окна async function openModal(taskId) { + // Проверяем тип задачи + const taskType = await getTaskType(taskId); + + if (taskType !== 'document') { + alert('Эта функция доступна только для задач типа "Документ"'); + return; + } + const modal = document.getElementById(CONFIG.modalId); if (!modal) { createModal(); @@ -324,28 +453,23 @@ const errorDiv = document.getElementById('documentFieldsError'); const messageDiv = document.getElementById('documentFieldsMessage'); - // Скрываем предыдущие сообщения - errorDiv.style.display = 'none'; - messageDiv.style.display = 'none'; + if (errorDiv) errorDiv.style.display = 'none'; + if (messageDiv) messageDiv.style.display = 'none'; - // Показываем загрузку - form.style.display = 'none'; - loading.style.display = 'block'; + if (form) form.style.display = 'none'; + if (loading) loading.style.display = 'block'; - // Активируем модальное окно modalElement.classList.add('active'); - // Устанавливаем ID задачи - document.getElementById('documentTaskId').textContent = taskId; + const taskIdSpan = document.getElementById('documentTaskId'); + if (taskIdSpan) taskIdSpan.textContent = taskId; - // Устанавливаем автора (логин текущего пользователя) const authorInput = document.getElementById('documentAuthor'); - if (currentUser) { + if (authorInput && currentUser) { authorInput.value = currentUser.login || ''; } try { - // Загружаем текущие значения const response = await fetch(`/api/tasks/${taskId}/document-fields`); if (!response.ok) { throw new Error('Ошибка загрузки данных'); @@ -354,10 +478,13 @@ const result = await response.json(); if (result.success) { - document.getElementById('documentNumber').value = result.data.document_n || ''; - document.getElementById('documentDate').value = result.data.document_d || ''; - // Автор из БД может отличаться от текущего пользователя - if (result.data.document_a && !authorInput.value) { + 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; } } @@ -365,9 +492,8 @@ console.error('❌ Ошибка загрузки полей документа:', error); showError('Ошибка загрузки данных: ' + error.message); } finally { - // Скрываем загрузку, показываем форму - loading.style.display = 'none'; - form.style.display = 'block'; + if (loading) loading.style.display = 'none'; + if (form) form.style.display = 'block'; } } @@ -376,12 +502,22 @@ 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'; @@ -393,6 +529,8 @@ // Показать успех function showSuccess(message) { const messageDiv = document.getElementById('documentFieldsMessage'); + if (!messageDiv) return; + messageDiv.textContent = message; messageDiv.style.display = 'block'; @@ -404,9 +542,8 @@ // Валидация даты function validateDate(dateStr) { - if (!dateStr) return true; // Пустая дата допустима + if (!dateStr) return true; - // Проверка формата ДД.ММ.ГГГГ const datePattern = /^(\d{2})\.(\d{2})\.(\d{4})$/; if (!datePattern.test(dateStr)) { return 'Неверный формат даты. Используйте ДД.ММ.ГГГГ'; @@ -424,12 +561,16 @@ // Сохранение полей async function saveFields() { - const taskId = document.getElementById('documentTaskId').textContent; - 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; + 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) { @@ -438,8 +579,9 @@ } } - // Показываем загрузку const saveBtn = document.querySelector('.document-fields-btn.save'); + if (!saveBtn) return; + const originalText = saveBtn.textContent; saveBtn.textContent = 'Сохранение...'; saveBtn.disabled = true; @@ -461,8 +603,6 @@ if (response.ok && result.success) { showSuccess('✅ Поля документа сохранены'); - - // Обновляем отображение в карточке задачи, если есть updateTaskCardDisplay(taskId, result.data); } else { showError(result.error || 'Ошибка сохранения'); @@ -481,14 +621,12 @@ 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); @@ -497,7 +635,6 @@ } } - // Формируем HTML для отображения let displayHTML = '