diff --git a/public/document-fields.js b/public/document-fields.js new file mode 100644 index 0000000..ffd8652 --- /dev/null +++ b/public/document-fields.js @@ -0,0 +1,605 @@ +// document-fields.js - Скрипт для управления полями документа в задачах + +(function() { + 'use strict'; + + // Конфигурация + const CONFIG = { + modalId: 'documentFieldsModal', + modalStyles: ` + + `, + buttonStyles: ` + + ` + }; + + // Текущий пользователь + let currentUser = null; + + // Получение текущего пользователя + async function getCurrentUser() { + try { + const response = await fetch('/api/user'); + const data = await response.json(); + if (data.user) { + currentUser = data.user; + console.log('✅ DocumentFields: текущий пользователь', currentUser); + } + return currentUser; + } catch (error) { + console.error('❌ DocumentFields: ошибка получения пользователя', error); + return null; + } + } + + // Создание модального окна + 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 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'); + + // Скрываем предыдущие сообщения + errorDiv.style.display = 'none'; + messageDiv.style.display = 'none'; + + // Показываем загрузку + form.style.display = 'none'; + loading.style.display = 'block'; + + // Активируем модальное окно + modalElement.classList.add('active'); + + // Устанавливаем ID задачи + document.getElementById('documentTaskId').textContent = taskId; + + // Устанавливаем автора (логин текущего пользователя) + const authorInput = document.getElementById('documentAuthor'); + if (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) { + document.getElementById('documentNumber').value = result.data.document_n || ''; + document.getElementById('documentDate').value = result.data.document_d || ''; + // Автор из БД может отличаться от текущего пользователя + if (result.data.document_a && !authorInput.value) { + authorInput.value = result.data.document_a; + } + } + } catch (error) { + console.error('❌ Ошибка загрузки полей документа:', error); + showError('Ошибка загрузки данных: ' + error.message); + } finally { + // Скрываем загрузку, показываем форму + loading.style.display = 'none'; + form.style.display = 'block'; + } + } + + // Закрытие модального окна + function closeModal() { + const modal = document.getElementById(CONFIG.modalId); + if (modal) { + modal.classList.remove('active'); + } + } + + // Показать ошибку + function showError(message) { + const errorDiv = document.getElementById('documentFieldsError'); + errorDiv.textContent = message; + errorDiv.style.display = 'block'; + + setTimeout(() => { + errorDiv.style.display = 'none'; + }, 5000); + } + + // Показать успех + function showSuccess(message) { + const messageDiv = document.getElementById('documentFieldsMessage'); + 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; + 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'); + 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); + } + } + + // Формируем HTML для отображения + 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; + }); + } + + // Добавление кнопки в карточки задач + function addDocumentFieldsButtons() { + if (!currentUser) return; + + document.querySelectorAll('[data-task-id]').forEach(card => { + // Проверяем, есть ли уже кнопка + if (card.querySelector('.document-fields-btn-icon')) return; + + const taskId = card.dataset.taskId; + + // Ищем контейнер action-buttons + const actionsContainer = card.querySelector('.action-buttons, .task-actions'); + + if (actionsContainer) { + 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); + } + }); + } + + // Наблюдатель за изменениями DOM + function observeDOM() { + const observer = new MutationObserver((mutations) => { + mutations.forEach((mutation) => { + if (mutation.addedNodes.length) { + window.documentFields.addButtons(); + } + }); + }); + + observer.observe(document.body, { + childList: true, + subtree: true + }); + } + + // Инициализация + async function init() { + console.log('🔄 DocumentFields module initializing...'); + + // Получаем текущего пользователя + await getCurrentUser(); + + // Создаем модальное окно + createModal(); + + // Добавляем кнопки на существующие карточки + setTimeout(() => { + window.documentFields.addButtons(); + }, 1000); + + // Запускаем наблюдение за DOM + observeDOM(); + + console.log('✅ DocumentFields module loaded'); + } + + // Экспортируем функции в глобальную область + window.documentFields = { + openModal, + closeModal, + saveFields, + addButtons: addDocumentFieldsButtons, + init + }; + + // Запускаем инициализацию после загрузки DOM + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', init); + } else { + init(); + } + +})(); \ No newline at end of file diff --git a/public/index.html b/public/index.html index 09b93a9..456af83 100644 --- a/public/index.html +++ b/public/index.html @@ -450,6 +450,7 @@ + \ No newline at end of file diff --git a/task-endpoints.js b/task-endpoints.js index 559af81..83cb9fc 100644 --- a/task-endpoints.js +++ b/task-endpoints.js @@ -1815,6 +1815,125 @@ app.post('/api/tasks/:taskId/files', requireAuth, upload.array('files', 15), (re }); }); }); + // API для обновления полей документа в задаче +app.put('/api/tasks/:taskId/document-fields', requireAuth, (req, res) => { + const { taskId } = req.params; + const { document_n, document_d, document_a } = req.body; + const userId = req.session.user.id; + const userLogin = req.session.user.login; + + console.log('📄 Обновление полей документа:'); + console.log('taskId:', taskId); + console.log('document_n:', document_n); + console.log('document_d:', document_d); + console.log('document_a:', document_a || userLogin); + console.log('userId:', userId); + + // Проверяем доступ к задаче + checkTaskAccess(userId, taskId, (err, hasAccess) => { + if (err || !hasAccess) { + return res.status(404).json({ error: 'Задача не найдена или у вас нет прав доступа' }); + } + + // Проверяем существование задачи + db.get("SELECT id FROM tasks WHERE id = ?", [taskId], (err, task) => { + if (err || !task) { + return res.status(404).json({ error: 'Задача не найдена' }); + } + + // Формируем запрос на обновление + let query = "UPDATE tasks SET "; + const updates = []; + const params = []; + + if (document_n !== undefined) { + updates.push("document_n = ?"); + params.push(document_n || null); + } + + if (document_d !== undefined) { + updates.push("document_d = ?"); + params.push(document_d || null); + } + + // document_a по умолчанию берем из сессии, если не передан + const authorValue = document_a !== undefined ? document_a : userLogin; + updates.push("document_a = ?"); + params.push(authorValue || null); + + if (updates.length === 0) { + return res.status(400).json({ error: 'Нет данных для обновления' }); + } + + updates.push("updated_at = CURRENT_TIMESTAMP"); + query += updates.join(", ") + " WHERE id = ?"; + params.push(taskId); + + db.run(query, params, function(err) { + if (err) { + console.error('❌ Ошибка обновления полей документа:', err); + return res.status(500).json({ error: err.message }); + } + + if (this.changes === 0) { + return res.status(404).json({ error: 'Задача не обновлена' }); + } + + // Логируем действие + logActivity(taskId, userId, 'DOCUMENT_FIELDS_UPDATED', + `Обновлены поля документа: №${document_n || ''}, дата: ${document_d || ''}`); + + console.log('✅ Поля документа успешно обновлены'); + + res.json({ + success: true, + message: 'Поля документа обновлены', + data: { + document_n: document_n || null, + document_d: document_d || null, + document_a: authorValue + } + }); + }); + }); + }); +}); + +// API для получения полей документа задачи +app.get('/api/tasks/:taskId/document-fields', requireAuth, (req, res) => { + const { taskId } = req.params; + const userId = req.session.user.id; + + checkTaskAccess(userId, taskId, (err, hasAccess) => { + if (err || !hasAccess) { + return res.status(404).json({ error: 'Задача не найдена или у вас нет прав доступа' }); + } + + db.get( + "SELECT document_n, document_d, document_a FROM tasks WHERE id = ?", + [taskId], + (err, row) => { + if (err) { + console.error('❌ Ошибка получения полей документа:', err); + return res.status(500).json({ error: err.message }); + } + + if (!row) { + return res.status(404).json({ error: 'Задача не найдена' }); + } + + res.json({ + success: true, + data: { + document_n: row.document_n || '', + document_d: row.document_d || '', + document_a: row.document_a || '' + } + }); + } + ); + }); +}); } module.exports = { setupTaskEndpoints,getApproverUsers }; \ No newline at end of file