diff --git a/public/auth.js b/public/auth.js index db4018a..c0e3d52 100644 --- a/public/auth.js +++ b/public/auth.js @@ -91,9 +91,11 @@ function showMainInterface() { function reloadAllScripts() { //console.log('🔄 Перезагрузка всех скриптов после авторизации...'); - // Список скриптов для перезагрузки (в правильном порядке) + // Список скриптов для перезагрузки (в правильном порядке) + // 'loading-start.js', + // 'document-fields.js', const scriptsToReload = [ - 'loading-start.js', + 'users.js', 'ui.js', 'signature.js', @@ -106,7 +108,6 @@ function reloadAllScripts() { 'tasks_files.js', 'navbar.js', 'chat-ui.js', - 'document-fields.js', 'main.js' ]; diff --git a/public/style.css b/public/style.css index 27533c5..7bd7f8d 100644 --- a/public/style.css +++ b/public/style.css @@ -4420,4 +4420,238 @@ button.btn-primary { 0% { opacity: 0.6; } 50% { opacity: 1; } 100% { opacity: 0.6; } -} \ No newline at end of file +} + + .document-fields-container { + margin: 12px 0; + padding: 12px 15px; + background: linear-gradient(135deg, #f8f9ff 0%, #f0f2fa 100%); + border-left: 4px solid #4a90e2; + border-radius: 0 8px 8px 0; + box-shadow: 0 2px 6px rgba(0,0,0,0.05); + font-size: 0.95em; + width: 100%; + box-sizing: border-box; + } + + .document-fields-container.empty { + background: linear-gradient(135deg, #f9f9f9 0%, #f0f0f0 100%); + border-left-color: #9e9e9e; + opacity: 0.8; + } + + .document-fields-header { + display: flex; + align-items: center; + margin-bottom: 10px; + padding-bottom: 6px; + border-bottom: 1px dashed rgba(74, 144, 226, 0.3); + width: 100%; + } + + .document-fields-container.empty .document-fields-header { + border-bottom-color: rgba(158, 158, 158, 0.3); + } + + .document-icon { + font-size: 1.2em; + margin-right: 8px; + filter: drop-shadow(0 2px 2px rgba(0,0,0,0.1)); + flex-shrink: 0; + } + + .document-title { + font-weight: 600; + color: #2c3e50; + text-transform: uppercase; + font-size: 0.85em; + letter-spacing: 0.5px; + white-space: nowrap; + } + + .document-fields-container.empty .document-title { + color: #7f8c8d; + } + + /* Горизонтальное расположение полей */ + .document-fields-content { + display: flex; + flex-wrap: wrap; + gap: 12px 20px; + align-items: center; + width: 100%; + } + + /* Стили для каждого поля - текст по центру вертикально */ + .document-field { + display: inline-flex; + align-items: center; /* Выравнивание по центру вертикально */ + background: white; + padding: 4px 14px; /* Уменьшил верхний/нижний padding для лучшего выравнивания */ + border-radius: 30px; + box-shadow: 0 2px 4px rgba(0,0,0,0.05); + border: 1px solid rgba(0,0,0,0.03); + transition: all 0.2s ease; + max-width: 100%; + flex: 0 1 auto; + line-height: 1.4; /* Добавил для единообразия высоты строки */ + } + + .document-field:hover { + box-shadow: 0 4px 8px rgba(0,0,0,0.08); + transform: translateY(-1px); + } + + .document-field.long-value { + max-width: 300px; + } + + .document-fields-container.empty .document-field { + background: rgba(255,255,255,0.7); + } + + .field-label { + font-weight: 500; + color: #7f8c8d; + margin-right: 6px; + font-size: 0.9em; + white-space: nowrap; + flex-shrink: 0; + line-height: 1.4; /* Единообразие высоты строки */ + } + + .field-value { + font-weight: 600; + color: #2c3e50; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + max-width: 200px; + line-height: 1.4; /* Единообразие высоты строки */ + padding: 2px 0; /* Небольшой отступ для лучшего выравнивания */ + } + + .document-field.long-value .field-value { + max-width: 250px; + } + + /* Специфичные стили для разных типов полей */ + .document-number { + color: #e67e22; + background: rgba(230, 126, 34, 0.1); + padding: 2px 10px; + border-radius: 20px; + display: inline-block; + } + + .document-date { + color: #27ae60; + background: rgba(39, 174, 96, 0.1); + padding: 2px 10px; + border-radius: 20px; + display: inline-block; + } + + .document-author { + color: #3498db; + background: rgba(52, 152, 219, 0.1); + padding: 2px 10px; + border-radius: 20px; + display: inline-block; + } + + /* Стили для пустого состояния */ + .document-fields-empty { + display: flex; + align-items: center; + justify-content: center; + padding: 8px 0; + width: 100%; + } + + .empty-message { + color: #95a5a6; + font-style: italic; + font-size: 0.9em; + display: flex; + align-items: center; + gap: 5px; + } + + .empty-message::before { + content: "⏳"; + font-size: 1.1em; + opacity: 0.7; + } + + /* Анимация появления */ + .document-fields-container { + animation: slideIn 0.3s ease-out; + } + + @keyframes slideIn { + from { + opacity: 0; + transform: translateY(-5px); + } + to { + opacity: 1; + transform: translateY(0); + } + } + + /* Адаптивность для мобильных и узких экранов */ + @media (max-width: 768px) { + .document-fields-content { + flex-direction: column; + align-items: stretch; + gap: 8px; + } + + .document-field { + width: 100%; + max-width: 100%; + box-sizing: border-box; + justify-content: flex-start; /* Выравнивание по левому краю на мобильных */ + } + + .document-field.long-value { + max-width: 100%; + } + + .field-value { + max-width: calc(100% - 60px); + } + + .document-field.long-value .field-value { + max-width: calc(100% - 60px); + } + } + + /* Для очень узких экранов */ + @media (max-width: 480px) { + .document-fields-header { + flex-wrap: wrap; + } + + .document-title { + white-space: normal; + word-break: break-word; + } + + .document-field { + flex-wrap: wrap; + padding: 8px 12px; + justify-content: flex-start; + } + + .field-label { + margin-bottom: 4px; + } + + .field-value { + white-space: normal; + word-break: break-word; + max-width: 100%; + } + } \ No newline at end of file diff --git a/public/tasks.js b/public/tasks.js index e8cbe4a..14fc283 100644 --- a/public/tasks.js +++ b/public/tasks.js @@ -22,11 +22,11 @@ async function loadTasks() { } if (creatorFilter) { - params.push(`creator=${encodeURIComponent(creatorFilter)}`); // было creator_id, теперь creator + params.push(`creator=${encodeURIComponent(creatorFilter)}`); } if (assigneeFilter) { - params.push(`assignee=${encodeURIComponent(assigneeFilter)}`); // было assignee_id, теперь assignee + params.push(`assignee=${encodeURIComponent(assigneeFilter)}`); } if (deadlineFilter) { @@ -38,7 +38,7 @@ async function loadTasks() { } if (typeFilter) { - params.push(`task_type=${encodeURIComponent(typeFilter)}`); // было type, теперь task_type + params.push(`task_type=${encodeURIComponent(typeFilter)}`); } url += params.join('&'); @@ -53,6 +53,12 @@ async function loadTasks() { tasks = await response.json(); console.log(`Загружено ${tasks.length} задач`); + // Загружаем поля документа для задач типа "document" + await loadDocumentFieldsForTasks(); + + // Загружаем файлы для развернутых задач + await loadFilesForExpandedTasks(); + // Обновляем отображение const activeSection = document.querySelector('.section.active'); if (activeSection) { @@ -77,12 +83,64 @@ async function loadTasks() { } } +// Новая функция для загрузки полей документов +async function loadDocumentFieldsForTasks() { + const documentTasks = tasks.filter(task => task.task_type === 'document'); + + if (documentTasks.length === 0) { + return; + } + + console.log(`Загрузка полей документов для ${documentTasks.length} задач`); + + // Загружаем поля для каждой задачи + for (const task of documentTasks) { + try { + const docResponse = await fetch(`/api/tasks/${task.id}/document-fields`); + if (docResponse.ok) { + const docData = await docResponse.json(); + task.document_fields = docData.data || {}; + console.log(`✅ Загружены поля для задачи ${task.id}:`, task.document_fields); + } else { + task.document_fields = {}; + } + } catch (error) { + console.error(`Ошибка загрузки полей документа для задачи ${task.id}:`, error); + task.document_fields = {}; + } + } +} + +// Новая функция для загрузки файлов развернутых задач +async function loadFilesForExpandedTasks() { + const expandedTasksArray = tasks.filter(task => expandedTasks.has(task.id)); + + if (expandedTasksArray.length === 0) { + return; + } + + await Promise.all(expandedTasksArray.map(async (task) => { + try { + const filesResponse = await fetch(`/api/tasks/${task.id}/files`); + if (filesResponse.ok) { + task.files = await filesResponse.json(); + } else { + task.files = []; + } + } catch (error) { + console.error(`Ошибка загрузки файлов для задачи ${task.id}:`, error); + task.files = []; + } + })); +} + function showTasksWithoutDate() { showingTasksWithoutDate = true; const btn = document.getElementById('tasks-no-date-btn'); if (btn) btn.classList.add('active'); loadTasksWithoutDate(); } + function resetAllFilters() { document.getElementById('status-filter').value = 'active,in_progress,assigned,overdue,rework'; document.getElementById('creator-filter').value = ''; @@ -92,6 +150,7 @@ function resetAllFilters() { document.getElementById('search-tasks').value = ''; loadTasks(); } + async function loadTasksWithoutDate() { try { const response = await fetch('/api/tasks'); @@ -120,14 +179,15 @@ async function loadTasksWithoutDate() { } })); + // Загружаем поля документов + await loadDocumentFieldsForTasks(); + renderTasks(); } catch (error) { console.error('Ошибка загрузки задач без срока:', error); } } -// удален async function createTask(event) - async function openEditModal(taskId) { try { const response = await fetch(`/api/tasks/${taskId}`); @@ -188,7 +248,6 @@ async function updateTask(event) { return; } - // Используем editSelectedUsers const assignedUserIds = editSelectedUsers; const formData = new FormData(); @@ -226,12 +285,10 @@ async function updateTask(event) { function openCopyModal(taskId) { document.getElementById('copy-task-id').value = taskId; - // Устанавливаем дату по умолчанию (через 7 дней) const defaultDate = new Date(); defaultDate.setDate(defaultDate.getDate() + 7); document.getElementById('copy-due-date').value = defaultDate.toISOString().substring(0, 16); - // Сбрасываем выбранных пользователей copySelectedUsers = []; renderCopyUsersChecklist(users); @@ -256,7 +313,6 @@ async function copyTask(event) { return; } - // Используем copySelectedUsers const assignedUserIds = copySelectedUsers; if (assignedUserIds.length === 0) { @@ -501,53 +557,41 @@ async function updateStatus(taskId, userId, status) { function canUserEditTask(task) { if (!currentUser) return false; - // Администратор может всё if (currentUser.role === 'admin') return true; - // Пользователи с ролью 'tasks' могут редактировать любые задачи if (currentUser.role === 'tasks') return true; - // Создатель может редактировать свою задачу if (parseInt(task.created_by) === currentUser.id) { - // Но если задача уже назначена другим пользователям, - // создатель может только просматривать if (task.assignments && task.assignments.length > 0) { - // Проверяем, назначена ли задача другим пользователям (не только себе) const assignedToOthers = task.assignments.some(assignment => parseInt(assignment.user_id) !== currentUser.id ); if (assignedToOthers) { - // Создатель может только просматривать и закрывать задачу return true; } } return true; } - // Исполнитель может менять только свой статус if (task.assignments) { const isExecutor = task.assignments.some(assignment => parseInt(assignment.user_id) === currentUser.id ); if (isExecutor) { - // Исполнитель может менять только статус return false; } } return false; } -// функция для проверки прав на добавление файлов + function canUserAddFilesToTask(task) { if (!currentUser) return false; - // Администратор может всё if (currentUser.role === 'admin') return true; - // Создатель задачи может добавлять файлы if (parseInt(task.created_by) === currentUser.id) return true; - // Исполнитель задачи может добавлять файлы if (task.assignments) { const isExecutor = task.assignments.some(assignment => parseInt(assignment.user_id) === currentUser.id @@ -556,4 +600,22 @@ function canUserAddFilesToTask(task) { } return false; -} \ No newline at end of file +} + +// Добавляем отладочную функцию +function debugDocumentFields() { + console.log('=== ОТЛАДКА ПОЛЕЙ ДОКУМЕНТОВ ==='); + const documentTasks = tasks.filter(task => task.task_type === 'document'); + console.log(`Найдено ${documentTasks.length} задач типа "document"`); + + documentTasks.forEach(task => { + console.log(`Задача ${task.id}:`, { + title: task.title, + document_fields: task.document_fields || 'НЕТ ПОЛЕЙ' + }); + }); + console.log('================================'); +} + +// Вызываем отладку после загрузки (можно вызвать вручную из консоли) +window.debugDocumentFields = debugDocumentFields; \ No newline at end of file diff --git a/public/ui.js b/public/ui.js index e66f493..328ea97 100644 --- a/public/ui.js +++ b/public/ui.js @@ -27,6 +27,124 @@ function showSection(sectionName) { loadNotificationSettings(); } } +// Вызываем добавление стилей при загрузке страницы +document.addEventListener('DOMContentLoaded', function() { + addDocumentFieldsStyles(); +}); + +// Функция для красивого отображения реквизитов документа +function renderDocumentFields(taskId, fields) { + const hasData = fields.document_n || fields.document_d || fields.document_a; + + if (!hasData) { + return ` +
+
+ 📄 + Реквизиты документа +
+
+ Реквизиты документа отсутствуют +
+
+ `; + } + + // Формируем поля с проверкой на длинные значения + const fields_html = []; + + if (fields.document_n && fields.document_d) { + const numberValue = escapeHtml(fields.document_n); + const dateValue = escapeHtml(fields.document_d); + const isLongNumber = numberValue.length > 20; + fields_html.push(` +
+ Номер: + ${numberValue} +
+ `); + } + if (fields.document_n && fields.document_d) { + const numberValue = escapeHtml(fields.document_n); + const dateValue = escapeHtml(fields.document_d); + const isLongNumber = numberValue.length > 20; + fields_html.push(` +
+ Дата: + ${dateValue} +
+ `); + } + if (fields.document_a) { + const authorValue = escapeHtml(fields.document_a); + const isLongAuthor = authorValue.length > 15; + fields_html.push(` +
+ Автор: + ${authorValue} +
+ `); + } + + return ` +
+
+ 📄 + Реквизиты документа +
+
+ ${fields_html.join('')} +
+
+ `; +} + +// Обновленная функция loadTasks для загрузки полей документа +async function loadTasks() { + try { + const response = await fetch('/api/tasks'); + tasks = await response.json(); + + // Загружаем поля документа для каждой задачи типа "document" + for (const task of tasks) { + if (task.task_type === 'document') { + try { + const docResponse = await fetch(`/api/tasks/${task.id}/document-fields`); + if (docResponse.ok) { + const docData = await docResponse.json(); + task.document_fields = docData.data || {}; + } else { + task.document_fields = {}; + } + } catch (error) { + console.error(`Ошибка загрузки полей документа для задачи ${task.id}:`, error); + task.document_fields = {}; + } + } + } + + // Определяем активную секцию и рендерим в соответствующий контейнер + const activeSection = document.querySelector('.section.active'); + if (activeSection) { + const sectionId = activeSection.id; + if (sectionId === 'mytasks-section') { + renderTasksInContainer('mytasks-list'); + } else if (sectionId === 'runtasks-section') { + renderTasksInContainer('runtasks-list'); + } else { + renderTasks(); + } + } else { + renderTasks(); + } + } catch (error) { + console.error('Ошибка загрузки задач:', error); + const container = document.getElementById('tasks-list'); + if (container) { + container.innerHTML = '
Ошибка загрузки задач
'; + } + } +} function renderTasks() { const container = document.getElementById('tasks-list'); @@ -54,14 +172,14 @@ function renderTasks() { const timeLeftInfo = getTimeLeftInfo(task); + // Получаем реквизиты документа (если есть) + const documentFields = task.document_fields || {}; + return `
- Задача №${task.id} ${task.title} ${isDeleted ? 'Удалена' : ''} @@ -71,11 +189,6 @@ function renderTasks() { ${userRole} ${task.assignments && task.assignments.length > 0 ? `${task.assignments.map(a => a.user_login || a.user_name).join(', ')}` : ''}
- Выполнить до: ${formatDateTime(task.due_date || task.created_at)} @@ -123,13 +236,14 @@ ${currentUser && currentUser.role === 'tasks' && canEdit || currentUser.role ===
` : ''} + + ${task.task_type === 'document' ? renderDocumentFields(task.id, documentFields) : ''}
Файлы: ${task.files && task.files.length > 0 ? renderGroupedFilesWithDelete(task) : 'нет файлов'}
-
Исполнители: ${task.assignments && task.assignments.length > 0 ? @@ -153,13 +267,13 @@ ${task.assignments && task.assignments.length > 0 ? `; }).join(''); } + // Функция для рендеринга списка исполнителей с фильтрацией function renderAssignmentList(assignments, taskId, canEdit) { if (!assignments || assignments.length === 0) { return '
Не назначены
'; } - // Создаем контейнер с возможностью фильтрации return `
@@ -185,19 +299,16 @@ function openReworkAssignmentModal(taskId, userId, userName) { return; } - // Проверяем права (только автор задачи или администратор) if (parseInt(task.created_by) !== currentUser.id && currentUser.role !== 'admin') { alert('Только автор задачи может отправлять на доработку'); return; } - // Удаляем предыдущее модальное окно, если оно есть const existingModal = document.getElementById('rework-assignment-modal'); if (existingModal) { existingModal.remove(); } - // Создаем модальное окно const modal = document.createElement('div'); modal.className = 'modal'; modal.id = 'rework-assignment-modal'; @@ -229,12 +340,12 @@ function openReworkAssignmentModal(taskId, userId, userName) { document.body.appendChild(modal); - // Фокусируемся на textarea const textarea = document.getElementById(`rework-comment-${taskId}-${userId}`); if (textarea) { setTimeout(() => textarea.focus(), 100); } } + // Функция для принудительного завершения задачи исполнителя async function forceCompleteAssignment(taskId, userId, userName) { if (!confirm(`Вы уверены, что хотите отметить задачу как выполненную для сотрудника ${userName}?\nЭто действие нельзя отменить.`)) { @@ -251,7 +362,7 @@ async function forceCompleteAssignment(taskId, userId, userName) { if (response.ok) { alert(`✅ Задача отмечена как выполненная для сотрудника ${userName}`); - loadTasks(); // Перезагружаем задачи + loadTasks(); } else { const error = await response.json(); alert(`❌ Ошибка: ${error.error || 'Неизвестная ошибка'}`); @@ -261,7 +372,7 @@ async function forceCompleteAssignment(taskId, userId, userName) { alert('Сетевая ошибка при выполнении операции'); } } -// Вспомогательная функция для экранирования HTML + function escapeHtml(text) { if (!text) return ''; return String(text) @@ -272,9 +383,7 @@ function escapeHtml(text) { .replace(/'/g, '''); } -// Функция для отправки на доработку конкретного исполнителя async function submitReworkAssignment(taskId, userId) { - // Получаем textarea по уникальному ID const textarea = document.getElementById(`rework-comment-${taskId}-${userId}`); if (!textarea) { @@ -284,14 +393,6 @@ async function submitReworkAssignment(taskId, userId) { const comment = textarea.value.trim(); - // Детальная отладка - console.log('=== ОТПРАВКА НА ДОРАБОТКУ ==='); - console.log('Task ID:', taskId); - console.log('User ID:', userId); - console.log('Comment length:', comment.length); - console.log('Comment text:', comment); - console.log('============================'); - if (!comment) { alert('Пожалуйста, укажите комментарий к доработке'); textarea.style.border = '2px solid red'; @@ -299,7 +400,6 @@ async function submitReworkAssignment(taskId, userId) { return; } - // Блокируем кнопку отправки const submitBtn = document.querySelector(`#rework-assignment-modal .btn-warning`); if (submitBtn) { submitBtn.disabled = true; @@ -312,9 +412,7 @@ async function submitReworkAssignment(taskId, userId) { headers: { 'Content-Type': 'application/json', }, - body: JSON.stringify({ - comment: comment // Отправляем явно - }) + body: JSON.stringify({ comment: comment }) }); const data = await response.json(); @@ -322,7 +420,6 @@ async function submitReworkAssignment(taskId, userId) { if (response.ok) { alert('✅ Исполнитель отправлен на доработку'); closeReworkAssignmentModal(); - // Перезагружаем задачи if (typeof loadTasks === 'function') { loadTasks(); } else { @@ -335,14 +432,13 @@ async function submitReworkAssignment(taskId, userId) { console.error('❌ Ошибка:', error); alert('Сетевая ошибка при отправке на доработку: ' + error.message); } finally { - // Разблокируем кнопку if (submitBtn) { submitBtn.disabled = false; submitBtn.innerHTML = '🔄 Отправить на доработку'; } } } -// Функция для закрытия модального окна доработки + function closeReworkAssignmentModal() { const modal = document.getElementById('rework-assignment-modal'); if (modal) { @@ -353,7 +449,6 @@ function closeReworkAssignmentModal() { } } -// Функция для фильтрации исполнителей в конкретной задаче function filterAssignments(taskId) { const filterInput = document.querySelector(`.assignment-filter-input[data-task-id="${taskId}"]`); const scrollContainer = document.getElementById(`assignments-${taskId}`); @@ -385,7 +480,7 @@ function filterAssignments(taskId) { filterCount.textContent = `${visibleCount} из ${assignments.length} исполнителей`; } } -// поддержка новых контейнеров + function toggleTask(taskId) { if (expandedTasks.has(taskId)) { expandedTasks.delete(taskId); @@ -394,7 +489,6 @@ function toggleTask(taskId) { loadTaskFiles(taskId); } - // Определяем, какой контейнер сейчас активен const activeSection = document.querySelector('.section.active'); if (activeSection) { const sectionId = activeSection.id; @@ -435,7 +529,7 @@ function getTimeLeftInfo(task) { return null; } -// Функция для рендеринга одного исполнителя +// Обновленная функция рендеринга исполнителя с проверкой типа задачи function renderAssignment(assignment, taskId, canEdit) { const statusClass = getStatusClass(assignment.status); const isCurrentUser = assignment.user_id === currentUser.id; @@ -444,10 +538,11 @@ function renderAssignment(assignment, taskId, canEdit) { const timeLeftInfo = getAssignmentTimeLeftInfo(assignment); - // Проверяем, является ли текущий пользователь автором задачи const task = tasks.find(t => t.id === taskId); const isTaskCreator = task && parseInt(task.created_by) === currentUser.id; + const isDocumentTask = task && task.task_type === 'document'; + return `
@@ -471,7 +566,7 @@ function renderAssignment(assignment, taskId, canEdit) { ${isCurrentUser && assignment.status === 'assigned' ? `` : ''} ${isCurrentUser && (assignment.status === 'in_progress' || assignment.status === 'overdue' || assignment.status === 'rework') ? -`` : ''} +`` : ''} ${isTaskCreator && assignment.status !== 'assigned' ? `` : ''} ${isTaskCreator && assignment.status !== 'completed' ? @@ -483,6 +578,132 @@ ${canEdit ? `; } +// Функция для открытия модального окна завершения документа +function openDocumentCompleteModal(taskId, userId) { + const task = tasks.find(t => t.id === taskId); + if (!task) { + alert('Задача не найдена'); + return; + } + + const modalHtml = ` + + `; + + const modalContainer = document.createElement('div'); + modalContainer.innerHTML = modalHtml; + document.body.appendChild(modalContainer); + + setTimeout(() => { + document.getElementById('document-complete-modal').style.display = 'block'; + document.getElementById('doc-number').focus(); + }, 10); +} + +function closeDocumentCompleteModal() { + const modal = document.getElementById('document-complete-modal'); + if (modal) { + modal.style.display = 'none'; + setTimeout(() => { + modal.parentElement.remove(); + }, 300); + } +} + +async function submitDocumentComplete(taskId, userId) { + const docNumber = document.getElementById('doc-number').value.trim(); + const docDate = document.getElementById('doc-date').value; + + if (!docNumber) { + alert('Пожалуйста, укажите номер документа'); + document.getElementById('doc-number').focus(); + return; + } + + if (!docDate) { + alert('Пожалуйста, укажите дату документа'); + return; + } + + const submitBtn = document.querySelector('#document-complete-modal .btn-success'); + if (submitBtn) { + submitBtn.disabled = true; + submitBtn.innerHTML = '⏳ Сохранение...'; + } + + try { + const docResponse = await fetch(`/api/tasks/${taskId}/document-fields`, { + method: 'PUT', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + document_n: docNumber, + document_d: formatDateForDocument(docDate), + document_a: currentUser.login + }) + }); + + if (!docResponse.ok) { + const error = await docResponse.json(); + throw new Error(error.error || 'Ошибка сохранения реквизитов документа'); + } + + await updateStatus(taskId, userId, 'completed'); + closeDocumentCompleteModal(); + + } catch (error) { + console.error('❌ Ошибка:', error); + alert(`❌ Ошибка: ${error.message}`); + + if (submitBtn) { + submitBtn.disabled = false; + submitBtn.innerHTML = '✅ Завершить'; + } + } +} + +function formatDateForDocument(dateString) { + if (!dateString) return ''; + const date = new Date(dateString); + 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 getAssignmentTimeLeftInfo(assignment) { if (!assignment.due_date || assignment.status === 'completed') return null; @@ -579,7 +800,6 @@ function getUserRoleInTask(task) { if (currentUser.role === 'admin') return 'Администратор'; - // Создатель задачи всегда должен иметь право редактировать свою задачу if (parseInt(task.created_by) === currentUser.id) { return 'Создатель'; } @@ -615,7 +835,6 @@ function formatDateTimeForInput(dateTimeString) { return date.toISOString().slice(0, 16); } -// Логи активности async function loadActivityLogs() { try { const response = await fetch('/api/activity-logs'); @@ -665,36 +884,28 @@ function getActionText(action) { return actions[action] || action; } -// Функция для просмотра деталей задачи async function viewTaskDetails(taskId) { try { const response = await fetch(`/api/tasks/${taskId}`); const task = await response.json(); - - // Можно открыть модальное окно с подробной информацией - // Или показать в отдельной секции alert(`Задача: ${task.title}\n\nОписание: ${task.description || 'Нет описания'}\n\nСоздатель: ${task.creator_name}\nСрок: ${task.due_date ? new Date(task.due_date).toLocaleString('ru-RU') : 'Не установлен'}`); } catch (error) { console.error('Ошибка загрузки деталей задачи:', error); } } -// Функция для открытия модального окна добавления файла function openAddFileModal(taskId) { - // Находим задачу const task = tasks.find(t => t.id === taskId); if (!task) { alert('Задача не найдена'); return; } - // Проверяем права if (!canUserAddFilesToTask(task)) { alert('У вас нет прав для добавления файлов к этой задаче'); return; } - // Создаем модальное окно const modalHtml = `