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 ` +