// ui.js - UI функции и рендеринг function showSection(sectionName) { document.querySelectorAll('.section').forEach(section => { section.classList.remove('active'); }); document.getElementById(sectionName + '-section').classList.add('active'); if (sectionName === 'tasks') { loadTasks(); } else if (sectionName === 'logs') { loadActivityLogs(); } else if (sectionName === 'kanban') { loadKanbanTasks(); } // Загрузка профиля при переходе в личный кабинет if (sectionName === 'profile') { loadUserProfile(); loadNotificationSettings(); } } function renderTasks() { const container = document.getElementById('tasks-list'); const showDeleted = document.getElementById('show-deleted')?.checked || false; let filteredTasks = tasks; if (!showDeleted) { filteredTasks = tasks.filter(task => task.status === 'active'); } if (filteredTasks.length === 0) { container.innerHTML = '
Задачи не найдены
'; return; } container.innerHTML = filteredTasks.map(task => { const isExpanded = expandedTasks.has(task.id); const overallStatus = getTaskOverallStatus(task); const statusClass = getStatusClass(overallStatus); const isDeleted = task.status === 'deleted'; const isClosed = task.closed_at !== null; const userRole = getUserRoleInTask(task); const canEdit = canUserEditTask(task); const isCopy = task.original_task_id !== null; const timeLeftInfo = getTimeLeftInfo(task); return `
Задача №${task.id} ${task.title} ${isDeleted ? 'Удалена' : ''} ${isClosed ? 'Закрыта' : ''} ${isCopy ? 'Копия' : ''} ${timeLeftInfo ? `${timeLeftInfo.text}` : ''} ${userRole} ${task.assignments && task.assignments.length > 0 ? `${task.assignments.map(a => a.user_login || a.user_name).join(', ')}` : ''}
${getStatusText(overallStatus)}
${!isDeleted && !isClosed ? ` ${canEdit ? `` : ''} ${canEdit ? `` : ''} ${canEdit ? `` : ''} ${canEdit ? `` : ''} ` : ''} ${isClosed && canEdit ? ` ` : ''} ${isDeleted && currentUser.role === 'admin' ? ` ` : ''}
${isCopy && task.original_task_title ? `
Оригинал: "${task.original_task_title}" (создал: ${task.original_creator_name})
` : ''}
${task.description || 'Нет описания'}
${task.rework_comment ? `
Комментарий к доработке: ${task.rework_comment}
` : ''}
Создана: ${formatDateTime(task.start_date || task.created_at)} ${task.due_date ? ` | Выполнить до: ${formatDateTime(task.due_date)}` : ''} ${showingTasksWithoutDate ? 'Без срока' : ''}
Файлы: ${task.files && task.files.length > 0 ? `
${task.files.map(file => renderFileIcon(file)).join('')}
` : 'нет файлов' }
Исполнители: ${task.assignments && task.assignments.length > 0 ? renderAssignmentList(task.assignments, task.id, canEdit) : '
Не назначены
' }
Создана: ${formatDateTime(task.created_at)} | Автор: ${task.creator_name} ${task.deleted_at ? `
Удалена: ${formatDateTime(task.deleted_at)}` : ''} ${task.closed_at ? `
Закрыта: ${formatDateTime(task.closed_at)}` : ''}
`; }).join(''); } // Улучшенная функция рендеринга списка исполнителей с фильтрацией function renderAssignmentList(assignments, taskId, canEdit) { if (!assignments || assignments.length === 0) { return '
Не назначены
'; } // Создаем контейнер с возможностью фильтрации return `
${assignments.length} исполнителей
${assignments.map(assignment => renderAssignment(assignment, taskId, canEdit)).join('')}
`; } // Функция для фильтрации исполнителей в конкретной задаче function filterAssignments(taskId) { const filterInput = document.querySelector(`.assignment-filter-input[data-task-id="${taskId}"]`); const scrollContainer = document.getElementById(`assignments-${taskId}`); const filterCount = document.getElementById(`filter-count-${taskId}`); if (!filterInput || !scrollContainer) return; const searchTerm = filterInput.value.toLowerCase(); const assignments = scrollContainer.querySelectorAll('.assignment'); let visibleCount = 0; assignments.forEach(assignment => { const userName = assignment.querySelector('strong')?.textContent?.toLowerCase() || ''; const userLogin = assignment.querySelector('small')?.textContent?.toLowerCase() || ''; const isVisible = userName.includes(searchTerm) || userLogin.includes(searchTerm) || searchTerm === ''; assignment.style.display = isVisible ? '' : 'none'; if (isVisible) { visibleCount++; } }); if (filterCount) { filterCount.textContent = `${visibleCount} из ${assignments.length} исполнителей`; } } function toggleTask(taskId) { if (expandedTasks.has(taskId)) { expandedTasks.delete(taskId); } else { expandedTasks.add(taskId); loadTaskFiles(taskId); } renderTasks(); } function getTimeLeftInfo(task) { if (!task.due_date || task.closed_at) return null; const dueDate = new Date(task.due_date); const now = new Date(); const timeLeft = dueDate.getTime() - now.getTime(); const hoursLeft = Math.floor(timeLeft / (60 * 60 * 1000)); if (hoursLeft <= 0) return null; if (hoursLeft <= 24) { return { text: `Менее 24ч`, class: 'deadline-24h' }; } else if (hoursLeft <= 48) { return { text: `Менее 48ч`, class: 'deadline-48h' }; } return null; } function renderAssignment(assignment, taskId, canEdit) { const statusClass = getStatusClass(assignment.status); const isCurrentUser = assignment.user_id === currentUser.id; const isOverdue = assignment.status === 'overdue'; const isRework = assignment.status === 'rework'; const timeLeftInfo = getAssignmentTimeLeftInfo(assignment); return `
${assignment.user_name} ${isCurrentUser ? '(Вы)' : ''} ${timeLeftInfo ? `${timeLeftInfo.text}` : ''} ${assignment.start_date || assignment.due_date ? `
${assignment.start_date ? `Начало: ${formatDateTime(assignment.start_date)}` : ''} ${assignment.due_date ? `Выполнить до: ${formatDateTime(assignment.due_date)}` : ''}
` : ''} ${assignment.rework_comment ? `
Комментарий: ${assignment.rework_comment}
` : ''}
${isCurrentUser && assignment.status === 'assigned' ? `` : ''} ${isCurrentUser && (assignment.status === 'in_progress' || assignment.status === 'overdue' || assignment.status === 'rework') ? `` : ''} ${canEdit ? `` : ''}
`; } function getAssignmentTimeLeftInfo(assignment) { if (!assignment.due_date || assignment.status === 'completed') return null; const dueDate = new Date(assignment.due_date); const now = new Date(); const timeLeft = dueDate.getTime() - now.getTime(); const hoursLeft = Math.floor(timeLeft / (60 * 60 * 1000)); if (hoursLeft <= 0) return null; if (hoursLeft <= 24) { return { text: `Осталось ${hoursLeft}ч`, class: 'deadline-24h' }; } else if (hoursLeft <= 48) { return { text: `Осталось ${hoursLeft}ч`, class: 'deadline-48h' }; } return null; } function getTaskOverallStatus(task) { if (task.status === 'deleted') return 'deleted'; if (task.closed_at) return 'closed'; if (!task.assignments || task.assignments.length === 0) return 'unassigned'; const assignments = task.assignments; let hasAssigned = false; let hasInProgress = false; let hasOverdue = false; let hasRework = false; let allCompleted = true; for (let assignment of assignments) { if (assignment.status === 'assigned') { hasAssigned = true; allCompleted = false; } else if (assignment.status === 'in_progress') { hasInProgress = true; allCompleted = false; } else if (assignment.status === 'overdue') { hasOverdue = true; allCompleted = false; } else if (assignment.status === 'rework') { hasRework = true; allCompleted = false; } else if (assignment.status !== 'completed') { allCompleted = false; } } if (allCompleted) return 'completed'; if (hasRework) return 'rework'; if (hasOverdue) return 'overdue'; if (hasInProgress) return 'in_progress'; if (hasAssigned) return 'assigned'; return 'unassigned'; } function getStatusClass(status) { switch (status) { case 'deleted': return 'status-gray'; case 'closed': return 'status-gray'; case 'unassigned': return 'status-purple'; case 'assigned': return 'status-red'; case 'in_progress': return 'status-orange'; case 'rework': return 'status-yellow'; case 'overdue': return 'status-darkred'; case 'completed': return 'status-green'; default: return 'status-purple'; } } function getStatusText(status) { switch (status) { case 'deleted': return 'Удалена'; case 'closed': return 'Закрыта'; case 'unassigned': return 'Не назначена'; case 'assigned': return 'Назначена'; case 'in_progress': return 'В работе'; case 'rework': return 'На доработке'; case 'overdue': return 'Просрочена'; case 'completed': return 'Выполнена'; default: return 'Неизвестно'; } } function getUserRoleInTask(task) { if (!currentUser) return 'Нет доступа'; if (currentUser.role === 'admin') return 'Администратор'; 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 'Создатель (только просмотр)'; } } return 'Создатель'; } if (task.assignments) { const isExecutor = task.assignments.some(assignment => parseInt(assignment.user_id) === currentUser.id ); if (isExecutor) return 'Исполнитель'; } return 'Наблюдатель'; } function getRoleBadgeClass(role) { switch (role) { case 'Администратор': return 'role-admin'; case 'Заказчик': return 'role-creator'; case 'Исполнитель': return 'role-executor'; default: return ''; } } function formatDateTime(dateTimeString) { if (!dateTimeString) return ''; const date = new Date(dateTimeString); return date.toLocaleString('ru-RU'); } function formatDateTimeForInput(dateTimeString) { if (!dateTimeString) return ''; const date = new Date(dateTimeString); return date.toISOString().slice(0, 16); } // Логи активности async function loadActivityLogs() { try { const response = await fetch('/api/activity-logs'); const logs = await response.json(); renderLogs(logs); } catch (error) { console.error('Ошибка загрузки логов:', error); } } function renderLogs(logs) { const container = document.getElementById('logs-list'); if (logs.length === 0) { container.innerHTML = '
Логи не найдены
'; return; } container.innerHTML = logs.map(log => `
${formatDateTime(log.created_at)}
${log.user_name} - ${getActionText(log.action)}
Задача: "${log.task_title}"
${log.details ? `
Детали: ${log.details}
` : ''}
`).join(''); } function getActionText(action) { const actions = { 'TASK_CREATED': 'создал задачу', 'TASK_COPIED': 'создал копию задачи', 'TASK_UPDATED': 'обновил задачу', 'TASK_DELETED': 'удалил задачу', 'TASK_RESTORED': 'восстановил задачу', 'TASK_ASSIGNED': 'назначил задачу', 'TASK_ASSIGNMENTS_UPDATED': 'обновил назначения', 'ASSIGNMENT_UPDATED': 'обновил сроки исполнителя', 'STATUS_CHANGED': 'изменил статус задачи', 'FILE_UPLOADED': 'загрузил файл', 'FILE_COPIED': 'скопировал файл', 'TASK_SENT_FOR_REWORK': 'вернул задачу на доработку', 'TASK_CLOSED': 'закрыл задачу', 'TASK_REOPENED': 'открыл задачу' }; 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); } }