// tasks-type.js - Управление отображением задач по типам // Не конфликтует с ui.js, использует собственные функции и пространство имен const TasksType = (function() { // Приватные переменные let currentTasks = []; let expandedTasks = new Set(); let currentType = 'document'; // Тип по умолчанию // Конфигурация типов задач const taskTypeConfig = { 'document': { endpoint: '/api/tasks_by_type?task_type=document', title: 'Документы', icon: '📄', badgeClass: 'document', emptyMessage: 'Нет задач по документам' }, 'it': { endpoint: '/api/tasks_by_type?task_type=it', title: 'ИТ задачи', icon: '💻', badgeClass: 'it', emptyMessage: 'Нет ИТ задач' }, 'ahch': { endpoint: '/api/tasks_by_type?task_type=ahch', title: 'АХЧ задачи', icon: '🔧', badgeClass: 'ahch', emptyMessage: 'Нет задач АХЧ' }, 'psychologist': { endpoint: '/api/tasks_by_type?task_type=psychologist', title: 'Психолог', icon: '🧠', badgeClass: 'psychologist', emptyMessage: 'Нет задач для психолога' }, 'speech_therapist': { endpoint: '/api/tasks_by_type?task_type=speech_therapist', title: 'Логопед', icon: '🗣️', badgeClass: 'speech_therapist', emptyMessage: 'Нет задач для логопеда' }, 'hr': { endpoint: '/api/tasks_by_type?task_type=hr', title: 'Кадры', icon: '👥', badgeClass: 'hr', emptyMessage: 'Нет кадровых задач' }, 'certificate': { endpoint: '/api/tasks_by_type?task_type=certificate', title: 'Справки', icon: '📜', badgeClass: 'certificate', emptyMessage: 'Нет задач по справкам' }, 'e_journal': { endpoint: '/api/tasks_by_type?task_type=e_journal', title: 'Электронный журнал', icon: '📊', badgeClass: 'e_journal', emptyMessage: 'Нет задач по ЭЖ' } }; // Инициализация function init() { createTaskTypeSection(); setupEventListeners(); } // Создание секции для задач по типам function createTaskTypeSection() { // Проверяем, существует ли уже секция if (document.getElementById('tasks-type-section')) { return; } const section = document.createElement('div'); section.id = 'tasks-type-section'; section.className = 'section'; section.innerHTML = `

Документы

⏳ Загрузка задач...
`; // Добавляем секцию после основного контента или в указанное место const container = document.querySelector('.container') || document.body; container.appendChild(section); // Добавляем стили addStyles(); } // Добавление стилей function addStyles() { if (document.getElementById('tasks-type-styles')) { return; } const style = document.createElement('style'); style.id = 'tasks-type-styles'; style.textContent = ` .tasks-type-container { padding: 20px; max-width: 95%; margin: 0 auto; } .tasks-type-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; } .tasks-type-header h2 { margin: 0; font-size: 24px; color: #333; } .tasks-type-controls { display: flex; gap: 10px; } .tasks-type-select { padding: 8px 12px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px; background-color: white; cursor: pointer; } .tasks-type-select:hover { border-color: #999; } .tasks-type-refresh-btn { padding: 8px 12px; background-color: #f0f0f0; border: 1px solid #ddd; border-radius: 4px; cursor: pointer; font-size: 16px; } .tasks-type-refresh-btn:hover { background-color: #e0e0e0; } .tasks-type-filters { display: flex; gap: 15px; margin-bottom: 20px; flex-wrap: wrap; } .filter-group { flex: 1; min-width: 200px; } .tasks-type-search-input, .tasks-type-filter-select { width: 100%; padding: 8px 12px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px; } .tasks-type-checkbox { display: flex; align-items: center; gap: 5px; cursor: pointer; } .tasks-type-list { display: flex; flex-direction: column; gap: 15px; } .tasks-type-card { background-color: white; border: 1px solid #e0e0e0; border-radius: 8px; overflow: hidden; transition: box-shadow 0.3s; } .tasks-type-card:hover { box-shadow: 0 4px 12px rgba(0,0,0,0.1); } .tasks-type-card.deleted { opacity: 0.7; background-color: #f9f9f9; } .tasks-type-card.closed { background-color: #f5f5f5; } .tasks-type-header-card { padding: 15px; cursor: pointer; display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid #e0e0e0; } .tasks-type-title-info { flex: 1; } .tasks-type-badge { display: inline-block; padding: 3px 8px; border-radius: 4px; font-size: 12px; font-weight: bold; margin-right: 10px; } .tasks-type-badge.document { background-color: #e3f2fd; color: #1976d2; } .tasks-type-badge.it { background-color: #f3e5f5; color: #7b1fa2; } .tasks-type-badge.ahch { background-color: #fff3e0; color: #f57c00; } .tasks-type-badge.psychologist { background-color: #e8f5e8; color: #388e3c; } .tasks-type-badge.speech_therapist { background-color: #ffebee; color: #d32f2f; } .tasks-type-badge.hr { background-color: #e1f5fe; color: #0288d1; } .tasks-type-badge.certificate { background-color: #fce4ec; color: #c2185b; } .tasks-type-badge.e_journal { background-color: #ede7f6; color: #512da8; } .tasks-type-status { padding: 4px 10px; border-radius: 4px; font-size: 12px; font-weight: 500; margin: 0 10px; } .tasks-type-status.status-red { background-color: #ffebee; color: #c62828; } .tasks-type-status.status-orange { background-color: #fff3e0; color: #ef6c00; } .tasks-type-status.status-green { background-color: #e8f5e8; color: #2e7d32; } .tasks-type-status.status-yellow { background-color: #fff9c4; color: #fbc02d; } .tasks-type-status.status-darkred { background-color: #ffcdd2; color: #b71c1c; } .tasks-type-status.status-purple { background-color: #f3e5f5; color: #7b1fa2; } .tasks-type-status.status-gray { background-color: #eeeeee; color: #616161; } .tasks-type-expand-icon { margin-left: 10px; transition: transform 0.3s; } .tasks-type-content { display: none; padding: 15px; } .tasks-type-content.expanded { display: block; } .tasks-type-actions { display: flex; gap: 8px; margin-bottom: 15px; flex-wrap: wrap; } .tasks-type-btn { padding: 6px 10px; border: none; border-radius: 4px; cursor: pointer; font-size: 13px; transition: background-color 0.2s; } .tasks-type-btn:hover { opacity: 0.9; } .tasks-type-description { background-color: #f9f9f9; padding: 10px; border-radius: 4px; margin: 10px 0; } .tasks-type-rework { background-color: #fff3e0; padding: 10px; border-radius: 4px; margin: 10px 0; border-left: 4px solid #ff9800; } .tasks-type-files { margin: 10px 0; } .tasks-type-assignments { margin: 10px 0; } .tasks-type-assignment { display: flex; align-items: center; padding: 8px; border-bottom: 1px solid #f0f0f0; } .tasks-type-assignment:last-child { border-bottom: none; } .tasks-type-assignment-status { width: 10px; height: 10px; border-radius: 50%; margin-right: 10px; } .tasks-type-meta { margin-top: 10px; padding-top: 10px; border-top: 1px solid #e0e0e0; color: #666; font-size: 12px; } .tasks-type-empty { text-align: center; padding: 40px; color: #999; font-size: 16px; background-color: #f9f9f9; border-radius: 8px; } .tasks-type-loading { text-align: center; padding: 40px; color: #666; } .tasks-type-error { text-align: center; padding: 40px; color: #d32f2f; background-color: #ffebee; border-radius: 8px; } .file-group { margin: 10px 0; } .file-icons-container { display: flex; gap: 10px; flex-wrap: wrap; margin-top: 5px; } .file-icon-link { text-decoration: none; color: #1976d2; padding: 4px 8px; background-color: #e3f2fd; border-radius: 4px; font-size: 13px; } .file-icon-link:hover { background-color: #bbdefb; } .deadline-indicator { display: inline-block; padding: 2px 6px; border-radius: 3px; font-size: 11px; margin-left: 5px; } .deadline-indicator.deadline-24h { background-color: #ffebee; color: #c62828; } .deadline-indicator.deadline-48h { background-color: #fff3e0; color: #ef6c00; } `; document.head.appendChild(style); } // Настройка обработчиков событий function setupEventListeners() { // Селектор типа задач const selector = document.getElementById('tasks-type-selector'); if (selector) { selector.addEventListener('change', function(e) { currentType = e.target.value; updateTitle(); loadTasks(); }); } // Кнопка обновления const refreshBtn = document.getElementById('tasks-type-refresh'); if (refreshBtn) { refreshBtn.addEventListener('click', function() { loadTasks(); }); } // Поиск const searchInput = document.getElementById('tasks-type-search'); if (searchInput) { searchInput.addEventListener('input', debounce(function() { loadTasks(); }, 300)); } // Фильтр статуса const statusFilter = document.getElementById('tasks-type-status-filter'); if (statusFilter) { statusFilter.addEventListener('change', function() { loadTasks(); }); } // Показ удаленных const showDeleted = document.getElementById('tasks-type-show-deleted'); if (showDeleted) { showDeleted.addEventListener('change', function() { loadTasks(); }); } } // Debounce функция для поиска function debounce(func, wait) { let timeout; return function executedFunction(...args) { const later = () => { clearTimeout(timeout); func(...args); }; clearTimeout(timeout); timeout = setTimeout(later, wait); }; } // Обновление заголовка function updateTitle() { const title = document.getElementById('tasks-type-title'); const config = taskTypeConfig[currentType] || taskTypeConfig.document; if (title) { title.textContent = `${config.icon} ${config.title}`; } } // Загрузка задач async function loadTasks() { const listContainer = document.getElementById('tasks-type-list'); if (!listContainer) return; listContainer.innerHTML = '
⏳ Загрузка задач...
'; try { const config = taskTypeConfig[currentType] || taskTypeConfig.document; let url = config.endpoint; // Добавляем параметры фильтрации const params = new URLSearchParams(); const search = document.getElementById('tasks-type-search')?.value; if (search) { params.append('search', search); } const status = document.getElementById('tasks-type-status-filter')?.value; if (status && status !== 'all') { if (status === 'active') { params.append('status', 'active'); } else { params.append('status', status); } } const showDeleted = document.getElementById('tasks-type-show-deleted')?.checked; if (showDeleted) { params.append('showDeleted', 'true'); } const queryString = params.toString(); if (queryString) { url += '&' + queryString; } const response = await fetch(url); const data = await response.json(); // Проверяем структуру ответа if (data.tasks) { currentTasks = data.tasks; } else if (Array.isArray(data)) { currentTasks = data; } else { currentTasks = []; } renderTasks(); } catch (error) { console.error('Ошибка загрузки задач:', error); listContainer.innerHTML = '
❌ Ошибка загрузки задач
'; } } // Рендеринг задач function renderTasks() { const container = document.getElementById('tasks-type-list'); if (!container) return; if (!currentTasks || currentTasks.length === 0) { const config = taskTypeConfig[currentType] || taskTypeConfig.document; container.innerHTML = `
${config.emptyMessage}
`; return; } container.innerHTML = currentTasks.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 timeLeftInfo = getTimeLeftInfo(task); const config = taskTypeConfig[task.task_type] || taskTypeConfig.document; return `
${config.icon} ${getTaskTypeDisplayName(task.task_type)} №${task.id} ${escapeHtml(task.title)} ${isDeleted ? 'Удалена' : ''} ${isClosed ? 'Закрыта' : ''} ${timeLeftInfo ? `${timeLeftInfo.text}` : ''}
Выполнить до: ${formatDateTime(task.due_date || task.created_at)}
${isExpanded ? renderExpandedContent(task) : ''}
`; }).join(''); } // Рендеринг развернутого содержимого function renderExpandedContent(task) { return `
${currentUser && currentUser.login === 'minicrm' ? ` ` : ''}
${task.description || 'Нет описания'}
${task.rework_comment ? `
Комментарий к доработке: ${escapeHtml(task.rework_comment)}
` : ''}
Файлы: ${task.files && task.files.length > 0 ? renderGroupedFiles(task) : 'нет файлов'}
Исполнители: ${task.assignments && task.assignments.length > 0 ? renderAssignments(task.assignments, task.id) : '
Не назначены
'}
Создана: ${formatDateTime(task.start_date || task.created_at)} | Автор: ${task.creator_name || 'Неизвестно'} ${task.due_date ? `| Срок: ${formatDateTime(task.due_date)}` : ''}
`; } // Рендеринг файлов function renderGroupedFiles(task) { if (!task.files || task.files.length === 0) { return 'нет файлов'; } const filesByUploader = {}; task.files.forEach(file => { const uploaderId = file.user_id; const uploaderName = file.user_name || 'Неизвестный пользователь'; if (!filesByUploader[uploaderId]) { filesByUploader[uploaderId] = { name: uploaderName, files: [] }; } filesByUploader[uploaderId].files.push(file); }); return Object.values(filesByUploader).map(uploader => `
${escapeHtml(uploader.name)}:
${uploader.files.map(file => ` 📎 ${escapeHtml(file.original_name).substring(0, 15)}${file.original_name.length > 15 ? '...' : ''} `).join('')}
`).join(''); } // Рендеринг исполнителей function renderAssignments(assignments, taskId) { return assignments.map(assignment => `
${escapeHtml(assignment.user_name)} ${assignment.user_id === currentUser?.id ? '(Вы)' : ''} ${assignment.due_date ? ` Срок: ${formatDateTime(assignment.due_date)} ` : ''}
${assignment.user_id === currentUser?.id ? ` ` : ''}
`).join(''); } // Публичные методы return { init: init, show: function(type = 'document') { currentType = type; const selector = document.getElementById('tasks-type-selector'); if (selector) { selector.value = type; } updateTitle(); loadTasks(); // Показываем секцию const section = document.getElementById('tasks-type-section'); if (section) { // Скрываем другие секции document.querySelectorAll('.section').forEach(s => { s.classList.remove('active'); }); section.classList.add('active'); } }, loadTasks: loadTasks, toggleTask: function(taskId) { if (expandedTasks.has(taskId)) { expandedTasks.delete(taskId); } else { expandedTasks.add(taskId); } renderTasks(); }, updateStatus: async function(taskId, userId, status) { try { const response = await fetch(`/api/tasks/${taskId}/status`, { method: 'PUT', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ userId, status }) }); if (response.ok) { await loadTasks(); } else { const error = await response.json(); alert('Ошибка: ' + (error.error || 'Неизвестная ошибка')); } } catch (error) { console.error('Ошибка:', error); alert('Сетевая ошибка'); } }, openTaskChat: function(taskId) { window.open(`/chat?task_id=${taskId}`, '_blank'); }, openAddFileModal: function(taskId) { if (typeof openAddFileModal === 'function') { openAddFileModal(taskId); } else { alert('Функция добавления файлов недоступна'); } }, openEditModal: function(taskId) { if (typeof openEditModal === 'function') { openEditModal(taskId); } else { alert('Функция редактирования недоступна'); } }, openCopyModal: function(taskId) { if (typeof openCopyModal === 'function') { openCopyModal(taskId); } else { alert('Функция копирования недоступна'); } } }; })(); // Вспомогательные функции (не конфликтуют с ui.js) 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 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: `Осталось ${hoursLeft}ч`, class: 'deadline-24h' }; } else if (hoursLeft <= 48) { return { text: `Осталось ${hoursLeft}ч`, class: 'deadline-48h' }; } return null; } function formatDateTime(dateTimeString) { if (!dateTimeString) return ''; const date = new Date(dateTimeString); return date.toLocaleString('ru-RU'); } function getTaskTypeDisplayName(type) { const typeNames = { 'regular': 'Задача', 'document': 'Документ', 'it': 'ИТ', 'ahch': 'АХЧ', 'psychologist': 'Психолог', 'speech_therapist': 'Логопед', 'hr': 'Кадры', 'certificate': 'Справка', 'e_journal': 'Эл. журнал' }; return typeNames[type] || type; } function escapeHtml(text) { if (!text) return ''; return String(text) .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, '''); } // Инициализация при загрузке страницы document.addEventListener('DOMContentLoaded', function() { // Проверяем, что currentUser определен (из основного скрипта) if (typeof currentUser !== 'undefined') { TasksType.init(); } else { // Ждем загрузки currentUser const checkUser = setInterval(function() { if (typeof currentUser !== 'undefined') { clearInterval(checkUser); TasksType.init(); } }, 100); } }); // Экспортируем в глобальную область window.TasksType = TasksType;