// kanban.js - Канбан-доска let kanbanTasks = []; let kanbanDays = 14; let currentDraggedTask = null; function showKanbanSection() { showSection('kanban'); loadKanbanTasks(); } async function loadKanbanTasks() { try { const daysSelect = document.getElementById('kanban-days'); const filterSelect = document.getElementById('kanban-filter'); // Если есть выбор в интерфейсе - используем его, иначе - значение по умолчанию if (daysSelect) { kanbanDays = parseInt(daysSelect.value) || 14; } else { kanbanDays = 14; } let filter = 'all'; if (filterSelect) { filter = filterSelect.value; } const response = await fetch(`/api/kanban-tasks?days=${kanbanDays}&filter=${filter}`); if (!response.ok) { throw new Error(`Ошибка сервера: ${response.status}`); } const data = await response.json(); kanbanTasks = data.tasks || []; renderKanban(data.filter); } catch (error) { console.error('Ошибка загрузки задач для Канбана:', error); document.getElementById('kanban-board').innerHTML = `
❌ Ошибка загрузки Канбана: ${error.message}
`; } } function renderKanban(filter = 'all') { const container = document.getElementById('kanban-board'); // Группируем задачи по статусам (убрали 'unassigned') const columns = { 'assigned': { title: 'Назначены', tasks: [], color: '#e74c3c' }, 'in_progress': { title: 'В работе', tasks: [], color: '#f39c12' }, 'rework': { title: 'На доработке', tasks: [], color: '#f1c40f' }, 'overdue': { title: 'Просрочены', tasks: [], color: '#c0392b' }, 'completed': { title: 'Выполнены', tasks: [], color: '#2ecc71' } }; // Распределяем задачи по колонкам kanbanTasks.forEach(task => { const status = task.kanbanStatus || 'assigned'; // Преобразуем 'unassigned' в 'assigned' const actualStatus = status === 'unassigned' ? 'assigned' : status; if (columns[actualStatus]) { columns[actualStatus].tasks.push(task); } else { // Если статус не найден, добавляем в 'assigned' columns['assigned'].tasks.push(task); } }); // Статистика по фильтру let filterTitle = 'Все задачи'; if (filter === 'created') filterTitle = 'Задачи, которые я поставил'; if (filter === 'assigned') filterTitle = 'Задачи, которые мне поставили'; container.innerHTML = `
${filterTitle} Всего задач: ${kanbanTasks.length}
${Object.entries(columns).map(([status, column]) => `

${column.title}

${column.tasks.length}
${renderKanbanCards(column.tasks, filter)}
`).join('')}
`; // Делаем колонки перетаскиваемыми (кроме 'overdue' и 'assigned') makeKanbanDraggable(); } function renderKanbanCards(tasks, filter) { if (tasks.length === 0) { return '
Нет задач
'; } return tasks.map(task => { // Определяем иконку роли let roleIcon = ''; let roleTitle = ''; if (task.userRole === 'creator') { roleIcon = '👤'; roleTitle = 'Вы поставили эту задачу'; } else if (task.userRole === 'assignee') { roleIcon = '🎯'; roleTitle = 'Вам поставили эту задачу'; } // Исправление: безопасное получение имени пользователя const userName = task.assignments && task.assignments.length > 0 && task.assignments[0]?.user_name ? task.assignments[0].user_name : 'Неизвестно'; // Исправление: безопасное получение первого символа имени const userInitial = userName && userName.length > 0 ? userName.charAt(0) : '?'; return `
#${task.id}
${roleIcon}
${task.title || 'Без названия'}
${task.due_date ? `📅 ${formatDate(task.due_date)}` : 'Без срока'}
${task.assignments && task.assignments.length > 0 ? task.assignments.slice(0, 3).map(a => { // Исправление: безопасное получение имени исполнителя const assigneeName = a.user_name || 'Неизвестно'; const assigneeInitial = assigneeName && assigneeName.length > 0 ? assigneeName.charAt(0) : '?'; return `${assigneeInitial}`; }).join('') : '👤' } ${task.assignments && task.assignments.length > 3 ? `+${task.assignments.length - 3}` : '' }
`; }).join(''); } function getDayWord(days) { if (days === 1) return 'день'; if (days >= 2 && days <= 4) return 'дня'; return 'дней'; } function makeKanbanDraggable() { const cards = document.querySelectorAll('.kanban-card'); const columns = document.querySelectorAll('.kanban-column-body:not([style*="opacity: 0.6"])'); cards.forEach(card => { card.addEventListener('dragstart', (e) => { e.dataTransfer.setData('text/plain', card.dataset.taskId); card.classList.add('dragging'); }); card.addEventListener('dragend', () => { card.classList.remove('dragging'); }); }); columns.forEach(column => { const status = column.parentElement.dataset.status; // Запрещаем перетаскивание в 'overdue' и 'assigned' if (status === 'overdue' || status === 'assigned') { return; } column.addEventListener('dragover', (e) => { e.preventDefault(); const draggingCard = document.querySelector('.dragging'); if (draggingCard) { column.appendChild(draggingCard); } }); column.addEventListener('drop', async (e) => { e.preventDefault(); const taskId = e.dataTransfer.getData('text/plain'); const newStatus = column.parentElement.dataset.status; if (taskId) { try { // Запрещаем установку статуса 'overdue' и 'assigned' if (newStatus === 'overdue' || newStatus === 'assigned') { alert('Невозможно изменить статус задачи на "Просрочены" или "Назначены" через Канбан'); // Возвращаем задачу в исходное положение loadKanbanTasks(); return; } // Обновляем статус на сервере const response = await fetch(`/api/kanban-tasks/${taskId}/status`, { method: 'PUT', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ status: newStatus }) }); if (response.ok) { // Перезагружаем Канбан loadKanbanTasks(); } else { const error = await response.json(); alert(`Ошибка обновления статуса: ${error.error || 'Неизвестная ошибка'}`); // Возвращаем задачу в исходное положение loadKanbanTasks(); } } catch (error) { console.error('Ошибка обновления статуса:', error); alert('Ошибка обновления статуса'); loadKanbanTasks(); } } }); }); } function openKanbanTask(taskId) { // Находим задачу и открываем её в основном интерфейсе const task = kanbanTasks.find(t => t.id == taskId); if (task) { showSection('tasks'); // Прокручиваем к задаче setTimeout(() => { const taskElement = document.querySelector(`.task-card[data-task-id="${taskId}"]`); if (taskElement) { taskElement.scrollIntoView({ behavior: 'smooth', block: 'center' }); // Раскрываем задачу если она свернута if (!expandedTasks.has(taskId)) { toggleTask(taskId); } } }, 100); } } function copyKanbanTask(taskId) { openCopyModal(taskId); } function formatDate(dateString) { if (!dateString) return ''; const date = new Date(dateString); return date.toLocaleDateString('ru-RU'); }