// Скрипт для отображения задач, где пользователь является автором // Глобальные переменные let myAuthorTasks = []; let myAuthorTasksFiltered = []; let expandedMyTasks = new Set(); // Для отслеживания развернутых задач let updateInterval = null; // Интервал обновления let isUpdating = false; // Флаг для предотвращения множественных обновлений // Загрузка задач при открытии секции function showMyTasksSection() { showSection('mytasks'); loadMyAuthorTasks(); startAutoUpdate(); // Запускаем автообновление при открытии секции } // Остановка автообновления при уходе с секции function hideMyTasksSection() { stopAutoUpdate(); } // Запуск автоматического обновления function startAutoUpdate() { // Останавливаем предыдущий интервал, если был stopAutoUpdate(); // Запускаем новый интервал (каждые 15 секунд) updateInterval = setInterval(() => { autoUpdateTasks(); }, 15000); // 15000 мс = 15 секунд console.log('🔄 Автообновление задач запущено (каждые 15 сек)'); } // Остановка автоматического обновления function stopAutoUpdate() { if (updateInterval) { clearInterval(updateInterval); updateInterval = null; console.log('⏹️ Автообновление задач остановлено'); } } // Функция автоматического обновления async function autoUpdateTasks() { // Предотвращаем множественные обновления if (isUpdating) { console.log('⏳ Обновление уже выполняется, пропускаем...'); return; } // Проверяем, активна ли секция const mytasksSection = document.getElementById('mytasks-section'); if (!mytasksSection || !mytasksSection.classList.contains('active')) { console.log('⏸️ Секция неактивна, автообновление приостановлено'); stopAutoUpdate(); return; } isUpdating = true; try { console.log('🔄 Автообновление задач...', new Date().toLocaleTimeString()); const response = await fetch('/api/kanban-tasks?days=62&filter=created'); if (!response.ok) { throw new Error(`Ошибка сервера: ${response.status}`); } const data = await response.json(); const newTasks = data.tasks || []; // Проверяем, изменились ли данные if (hasTasksChanged(myAuthorTasks, newTasks)) { console.log('📊 Данные изменились, обновляем отображение'); myAuthorTasks = newTasks; filterMyTasks(); // Показываем уведомление об обновлении showUpdateNotification(); } else { console.log('📊 Данные не изменились'); } } catch (error) { console.error('❌ Ошибка автообновления:', error); } finally { isUpdating = false; } } // Показ уведомления об обновлении function showUpdateNotification() { const notification = document.createElement('div'); notification.className = 'update-notification'; notification.innerHTML = ` 🔄 Данные обновлены ${new Date().toLocaleTimeString()} `; document.body.appendChild(notification); // Анимация появления и исчезновения setTimeout(() => { notification.classList.add('show'); }, 10); setTimeout(() => { notification.classList.remove('show'); setTimeout(() => { notification.remove(); }, 300); }, 2000); } // Проверка, изменились ли данные function hasTasksChanged(oldTasks, newTasks) { if (oldTasks.length !== newTasks.length) return true; // Создаем Map для быстрого сравнения const oldMap = new Map(oldTasks.map(t => [t.id, t])); for (const newTask of newTasks) { const oldTask = oldMap.get(newTask.id); if (!oldTask) return true; // Проверяем важные поля if (oldTask.kanbanStatus !== newTask.kanbanStatus) return true; if (oldTask.title !== newTask.title) return true; if (oldTask.description !== newTask.description) return true; if (oldTask.due_date !== newTask.due_date) return true; // Проверяем изменения в назначениях if (!areAssignmentsEqual(oldTask.assignments, newTask.assignments)) return true; // Проверяем изменения в файлах if (!areFilesEqual(oldTask.files, newTask.files)) return true; } return false; } // Проверка равенства назначений function areAssignmentsEqual(oldAssignments, newAssignments) { if (!oldAssignments && !newAssignments) return true; if (!oldAssignments || !newAssignments) return false; if (oldAssignments.length !== newAssignments.length) return false; const oldMap = new Map(oldAssignments.map(a => [a.user_id, a])); for (const newAss of newAssignments) { const oldAss = oldMap.get(newAss.user_id); if (!oldAss) return false; if (oldAss.status !== newAss.status) return false; } return true; } // Проверка равенства файлов function areFilesEqual(oldFiles, newFiles) { if (!oldFiles && !newFiles) return true; if (!oldFiles || !newFiles) return false; if (oldFiles.length !== newFiles.length) return false; const oldIds = new Set(oldFiles.map(f => f.id)); const newIds = new Set(newFiles.map(f => f.id)); return oldIds.size === newIds.size && [...oldIds].every(id => newIds.has(id)); } // Основная функция загрузки задач async function loadMyAuthorTasks() { const container = document.getElementById('mytasks-list'); try { container.innerHTML = `

Загрузка ваших задач...

`; const response = await fetch('/api/kanban-tasks?days=62&filter=created'); if (!response.ok) { throw new Error(`Ошибка сервера: ${response.status}`); } const data = await response.json(); myAuthorTasks = data.tasks || []; filterMyTasks(); } catch (error) { console.error('Ошибка загрузки задач:', error); container.innerHTML = `
Ошибка загрузки задач: ${error.message}
`; } } // Функция фильтрации задач function filterMyTasks() { const statusFilter = document.getElementById('mytasks-status-filter')?.value || 'all'; const searchText = document.getElementById('mytasks-search')?.value.toLowerCase() || ''; myAuthorTasksFiltered = myAuthorTasks.filter(task => { if (statusFilter !== 'all') { const taskStatus = task.kanbanStatus || 'assigned'; if (taskStatus !== statusFilter) return false; } if (searchText) { const title = task.title || ''; const description = task.description || ''; const searchable = `${title} ${description}`.toLowerCase(); if (!searchable.includes(searchText)) return false; } return true; }); renderMyAuthorTasks(); } // Функция отображения задач в стиле ui.js function renderMyAuthorTasks() { const container = document.getElementById('mytasks-list'); if (!container) return; if (myAuthorTasks.length === 0) { container.innerHTML = '
У вас пока нет созданных задач
'; return; } if (myAuthorTasksFiltered.length === 0) { container.innerHTML = '
Задачи не найдены
'; return; } // Сортируем задачи по дате создания (новые сверху) const sortedTasks = [...myAuthorTasksFiltered].sort((a, b) => new Date(b.created_at || 0) - new Date(a.created_at || 0) ); container.innerHTML = sortedTasks.map(task => { const isExpanded = expandedMyTasks.has(task.id); const overallStatus = task.kanbanStatus || 'assigned'; const statusClass = getStatusClass(overallStatus); const isClosed = task.closed_at !== null; const isCopy = task.original_task_id !== null; const timeLeftInfo = getTimeLeftInfo(task); return `
Задача №${task.id} ${task.title || 'Без названия'} ${task.task_type ? `${getTaskTypeDisplayName(task.task_type)}` : ''} ${isClosed ? 'Закрыта' : ''} ${isCopy ? 'Копия' : ''} ${timeLeftInfo ? `${timeLeftInfo.text}` : ''} ${task.assignments && task.assignments.length > 0 ? `${task.assignments.map(a => a.user_name).join(', ')}` : '' }
Выполнить до: ${formatDateTime(task.due_date || task.created_at)}
${isExpanded ? `
${currentUser && currentUser.login === 'minicrm' ? `` : '' } ${currentUser && currentUser.login === 'kalugin.o' ? `` : '' } ${currentUser && (currentUser.role === 'tasks' || currentUser.role === 'admin') ? `` : '' } ${currentUser && (currentUser.role === 'tasks' || currentUser.role === 'admin') ? `` : '' } ${currentUser && currentUser.login === 'minicrm' ? `` : '' } ${currentUser && currentUser.login === 'minicrm' ? `` : '' }
` : ''} ${isCopy && task.original_task_title ? `
Оригинал: "${task.original_task_title}" (создал: ${task.original_creator_name})
` : ''}
${task.description || 'Нет описания'}
${task.rework_comment ? `
Комментарий к доработке: ${task.rework_comment}
` : ''}
Файлы: ${task.files && task.files.length > 0 ? renderGroupedFilesWithDelete ? renderGroupedFilesWithDelete(task) : renderGroupedFiles(task) : 'нет файлов' }
Исполнители: ${task.assignments && task.assignments.length > 0 ? renderAssignmentList(task.assignments, task.id, true) : '
Не назначены
' }
Создана: ${formatDateTime(task.start_date || task.created_at)} | Выполнить до: ${formatDateTime(task.due_date || task.created_at)} | Автор: ${task.creator_name} | Тип: ${task.task_type ? `${getTaskTypeDisplayName(task.task_type)}` : ''} ${task.closed_at ? `
Закрыта: ${formatDateTime(task.closed_at)}` : ''}
`; }).join(''); // Загружаем файлы для развернутых задач expandedMyTasks.forEach(taskId => { if (myAuthorTasks.some(t => t.id == taskId)) { loadTaskFiles(taskId); } }); } // Функция для переключения развернутого состояния задачи function toggleMyTask(taskId) { if (expandedMyTasks.has(taskId)) { expandedMyTasks.delete(taskId); } else { expandedMyTasks.add(taskId); loadTaskFiles(taskId); } renderMyAuthorTasks(); } 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 getTaskTypeDisplayName(type) { const typeNames = { 'regular': 'Задача', 'document': 'Документ', 'it': 'ИТ', 'ahch': 'АХЧ', 'psychologist': 'Психолог', 'speech_therapist': 'Логопед', 'hr': 'Кадры', 'certificate': 'Справка', 'e_journal': 'Эл. журнал' }; return typeNames[type] || type; } function formatDateTime(dateTimeString) { if (!dateTimeString) return ''; const date = new Date(dateTimeString); return date.toLocaleString('ru-RU'); } // Функция для рендеринга одного исполнителя (копия из ui.js с небольшими адаптациями) 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); const task = myAuthorTasks.find(t => t.id === taskId); const isTaskCreator = task && parseInt(task.created_by) === currentUser.id; 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') ? `` : ''} ${isTaskCreator && assignment.status !== 'assigned' ? `` : ''} ${isTaskCreator && assignment.status !== 'completed' ? `` : ''} ${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 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 openAddFileModal(taskId) { if (typeof window.openAddFileModal === 'function') { return window.openAddFileModal(taskId); } const task = myAuthorTasks.find(t => t.id === taskId); if (!task) { alert('Задача не найдена'); return; } const modalHtml = ` `; const modalContainer = document.createElement('div'); modalContainer.innerHTML = modalHtml; document.body.appendChild(modalContainer); document.getElementById('add-file-form').addEventListener('submit', async function(e) { e.preventDefault(); const fileInput = document.getElementById('file-input'); const description = document.getElementById('file-description').value; if (fileInput.files.length === 0) { alert('Выберите файл для загрузки'); return; } const file = fileInput.files[0]; const formData = new FormData(); formData.append('files', file); formData.append('task_id', taskId); if (description) { formData.append('description', description); } try { let response = await fetch(`/api/tasks/${taskId}/files`, { method: 'POST', body: formData }); if (!response.ok) { formData.delete('files'); formData.append('file', file); response = await fetch(`/api/tasks/${taskId}/files`, { method: 'POST', body: formData }); } if (response.ok) { alert('Файл успешно добавлен'); await loadTaskFiles(taskId); closeAddFileModal(); if (expandedMyTasks.has(taskId)) { renderMyAuthorTasks(); } } else { alert(`Ошибка при добавлении файла: ${response.status}`); } } catch (error) { console.error('Ошибка:', error); alert('Сетевая ошибка при добавлении файла'); } }); setTimeout(() => { document.getElementById('add-file-modal').style.display = 'block'; }, 10); } function closeAddFileModal() { const modal = document.getElementById('add-file-modal'); if (modal) { modal.style.display = 'none'; setTimeout(() => { modal.parentElement.remove(); }, 300); } } // Функция для открытия чата задачи function openTaskChat(taskId) { if (typeof window.openTaskChat === 'function') { window.openTaskChat(taskId); } else { window.open(`/chat?task_id=${taskId}`, '_blank'); } } // Функция для открытия модального окна редактирования function openEditModal(taskId) { if (typeof window.openEditModal === 'function') { window.openEditModal(taskId); } else { console.log('Открытие редактирования задачи:', taskId); } } // Функция для открытия модального окна копирования function openCopyModal(taskId) { if (typeof window.openCopyModal === 'function') { window.openCopyModal(taskId); } else { console.log('Открытие копирования задачи:', taskId); } } // Функция для открытия модального окна доработки function openReworkModal(taskId) { if (typeof window.openReworkModal === 'function') { window.openReworkModal(taskId); } else { console.log('Открытие доработки задачи:', taskId); } } // Функция для закрытия задачи async function closeTask(taskId) { if (!confirm('Вы уверены, что хотите закрыть задачу?')) return; try { const response = await fetch(`/api/tasks/${taskId}/close`, { method: 'PUT' }); if (response.ok) { alert('Задача закрыта'); loadMyAuthorTasks(); } else { const error = await response.json(); alert(`Ошибка: ${error.error || 'Неизвестная ошибка'}`); } } catch (error) { console.error('Ошибка:', error); alert('Сетевая ошибка при закрытии задачи'); } } // Функция для удаления задачи async function deleteTask(taskId) { if (!confirm('Вы уверены, что хотите удалить задачу?')) return; try { const response = await fetch(`/api/tasks/${taskId}`, { method: 'DELETE' }); if (response.ok) { alert('Задача удалена'); loadMyAuthorTasks(); } else { const error = await response.json(); alert(`Ошибка: ${error.error || 'Неизвестная ошибка'}`); } } catch (error) { console.error('Ошибка:', error); alert('Сетевая ошибка при удалении задачи'); } } // Функция для обновления статуса исполнителя async function updateStatus(taskId, userId, newStatus) { try { const response = await fetch(`/api/tasks/${taskId}/assignments/${userId}`, { method: 'PUT', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ status: newStatus }) }); if (response.ok) { loadMyAuthorTasks(); } else { const error = await response.json(); alert(`Ошибка: ${error.error || 'Неизвестная ошибка'}`); } } catch (error) { console.error('Ошибка:', error); alert('Сетевая ошибка при обновлении статуса'); } } // Автоматическая загрузка при открытии секции document.addEventListener('DOMContentLoaded', () => { const mytasksSection = document.getElementById('mytasks-section'); if (mytasksSection && mytasksSection.style.display !== 'none') { loadMyAuthorTasks(); } }); // Экспортируем функции в глобальную область window.showMyTasksSection = showMyTasksSection; window.loadMyAuthorTasks = loadMyAuthorTasks; window.filterMyTasks = filterMyTasks; window.toggleMyTask = toggleMyTask; window.openAddFileModal = openAddFileModal; window.closeAddFileModal = closeAddFileModal; window.openTaskChat = openTaskChat; window.openEditModal = openEditModal; window.openCopyModal = openCopyModal; window.openReworkModal = openReworkModal; window.closeTask = closeTask; window.deleteTask = deleteTask; window.updateStatus = updateStatus; window.filterAssignments = filterAssignments; window.renderGroupedFiles = renderGroupedFiles;