// tasks-type.js - Управление отображением задач по типам // Не конфликтует с ui.js, использует собственные функции и пространство имен const TasksType = (function() { // Приватные переменные let currentTasks = []; let expandedTasks = new Set(); let currentType = 'document'; // Тип по умолчанию let currentUser = null; // Конфигурация типов задач 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: 'Нет задач по ЭЖ' } }; // Функция для получения подписантов из idgroups async function getSigners() { try { // Сначала пробуем получить через новый API const response = await fetch('/api2/idusers'); if (!response.ok) { console.warn('API idusers не доступен, пробуем альтернативный метод'); return await getSignersAlternative(); } const data = await response.json(); // Фильтруем только активных пользователей // Проверяем наличие группы "Подписант" в group_name или в metadata const signers = data.filter(user => { if (!user.is_active) return false; // Проверяем group_name (если есть) if (user.group_name && user.group_name.toLowerCase().includes('подписант')) { return true; } // Проверяем metadata (если есть) if (user.metadata && typeof user.metadata === 'object') { const groups = user.metadata.groups || []; if (groups.some(g => g.toLowerCase().includes('подписант'))) { return true; } } // Проверяем ldap_group (если есть) if (user.ldap_group && user.ldap_group.toLowerCase().includes('подписант')) { return true; } return false; }); console.log('Найдены подписанты через API:', signers); // Преобразуем в нужный формат return signers.map(s => ({ id: s.user_id, name: s.user_name || 'Неизвестно', login: s.user_login || s.login || '', external_id: s.external_id || '' })); } catch (error) { console.error('Ошибка получения подписантов:', error); return await getSignersAlternative(); } } // Альтернативный метод получения подписантов async function getSignersAlternative() { try { // Пробуем получить через обычный API пользователей const response = await fetch('/api/users'); if (!response.ok) return []; const users = await response.json(); // Фильтруем по роли или другим признакам const signers = users.filter(user => { // Например, пользователи с ролью 'admin' или специальным полем return user.role === 'admin' || user.role === 'signer'; }); return signers.map(s => ({ id: s.id, name: s.name || s.login, login: s.login, external_id: s.external_id || '' })); } catch (error) { console.error('Ошибка альтернативного получения подписантов:', error); return []; } } // Функция для замены исполнителя на подписанта async function replaceWithSigner(taskId, currentUserId) { try { const signers = await getSigners(); if (signers.length === 0) { alert('❌ Подписанты не найдены в системе.\n\n' + 'Проверьте:\n' + '1. В таблице idgroups создана группа "Подписант"\n' + '2. В таблице idusers есть пользователи в этой группе\n' + '3. Пользователи активны (is_active = true)'); return false; } // Показываем диалог подтверждения с информацией о подписантах const signerNames = signers.map(s => `• ${s.name} (${s.login})`).join('\n'); const message = signers.length === 1 ? `✍️ Назначить подписанта:\n\n${signerNames}\n\nТекущий исполнитель будет заменен.` : `✍️ Назначить подписантов:\n\n${signerNames}\n\nБудут назначены ВСЕ найденные подписанты.`; if (!confirm(message)) { return false; } let response; // Если несколько подписантов, назначаем всех if (signers.length > 1) { const signerIds = signers.map(s => s.id); response = await fetch(`/api/tasks/${taskId}/replace-all-assignees`, { method: 'PUT', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ newAssigneeIds: signerIds }) }); if (response.ok) { const signerNames = signers.map(s => s.name).join(', '); alert(`✅ Задача назначена подписантам:\n${signerNames}`); } } else { // Если один подписант, заменяем текущего const signer = signers[0]; response = await fetch(`/api/tasks/${taskId}/replace-assignee`, { method: 'PUT', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ oldAssigneeId: currentUserId, newAssigneeId: signer.id }) }); if (response.ok) { alert(`✅ Задача переназначена подписанту:\n${signer.name} (${signer.login})`); } } if (response && response.ok) { // Перезагружаем задачи await loadTasks(); return true; } else if (response) { const error = await response.json(); alert('❌ Ошибка: ' + (error.error || 'Неизвестная ошибка')); } return false; } catch (error) { console.error('Ошибка при переназначении:', error); alert('❌ Сетевая ошибка при переназначении: ' + error.message); return false; } } // Инициализация function init() { // Получаем текущего пользователя fetch('/api/user') .then(response => response.json()) .then(data => { if (data.user) { currentUser = data.user; console.log('Текущий пользователь:', currentUser); } }) .catch(error => console.error('Ошибка получения пользователя:', error)); createTaskTypeSection(); setupEventListeners(); loadTasks(); } // Создание секции для задач по типам 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; flex-wrap: wrap; gap: 15px; } .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; flex-wrap: wrap; gap: 10px; } .tasks-type-title-info { flex: 1; min-width: 250px; } .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; } .task-number { color: #666; font-size: 12px; margin-right: 8px; } .tasks-type-status { padding: 4px 10px; border-radius: 4px; font-size: 12px; font-weight: 500; margin: 0 10px; white-space: nowrap; } .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; align-items: center; } .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-btn.sign-btn { background-color: #4a90e2; color: white; } .tasks-type-btn.sign-btn:hover { background-color: #357abd; } .tasks-type-btn.complete-btn { background-color: #27ae60; color: white; } .tasks-type-btn.complete-btn:hover { background-color: #219a52; } .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: 10px; border-bottom: 1px solid #f0f0f0; gap: 10px; } .tasks-type-assignment:last-child { border-bottom: none; } .tasks-type-assignment-status { width: 10px; height: 10px; border-radius: 50%; flex-shrink: 0; } .tasks-type-assignment-status.status-red { background-color: #c62828; } .tasks-type-assignment-status.status-orange { background-color: #ef6c00; } .tasks-type-assignment-status.status-green { background-color: #2e7d32; } .tasks-type-assignment-status.status-yellow { background-color: #fbc02d; } .tasks-type-assignment-status.status-darkred { background-color: #b71c1c; } .tasks-type-assignment-status.status-purple { background-color: #7b1fa2; } .tasks-type-assignment-status.status-gray { background-color: #616161; } .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; } .deleted-badge, .closed-badge { font-size: 11px; padding: 2px 6px; border-radius: 3px; margin-left: 5px; } .deleted-badge { background-color: #ffebee; color: #c62828; } .closed-badge { background-color: #eeeeee; color: #616161; } `; 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,assigned,in_progress,overdue,rework'); } 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) { // Проверяем, является ли текущий пользователь исполнителем задачи const isCurrentUserAssignee = task.assignments && task.assignments.some( assignment => assignment.user_id === currentUser?.id ); return `
${currentUser && (currentUser.role === 'admin' || currentUser.role === 'tasks') ? ` ` : ''} ${isCurrentUserAssignee ? ` ` : ''}
Описание:
${escapeHtml(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, 20)}${file.original_name.length > 20 ? '...' : ''} `).join('')}
`).join(''); } // Рендеринг исполнителей (теперь только информация, без кнопок) function renderAssignments(assignments, taskId) { return assignments.map(assignment => { return `
${escapeHtml(assignment.user_name)} ${assignment.user_id === currentUser?.id ? '(Вы)' : ''} ${assignment.due_date ? ` Срок: ${formatDateTime(assignment.due_date)} ` : ''}
`; }).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 => { if (s.id !== 'tasks-type-section') { s.style.display = 'none'; } }); section.style.display = 'block'; } }, 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('❌ Сетевая ошибка'); } }, replaceWithSigner: replaceWithSigner, openTaskChat: function(taskId) { window.open(`/chat?task_id=${taskId}`, '_blank'); }, openAddFileModal: function(taskId) { if (typeof window.openAddFileModal === 'function') { window.openAddFileModal(taskId); } else { alert('Функция добавления файлов недоступна'); } }, openEditModal: function(taskId) { if (typeof window.openEditModal === 'function') { window.openEditModal(taskId); } else { alert('Функция редактирования недоступна'); } }, openCopyModal: function(taskId) { if (typeof window.openCopyModal === 'function') { window.openCopyModal(taskId); } else { alert('Функция копирования недоступна'); } }, // Метод для проверки наличия подписантов checkSigners: async function() { const signers = await getSigners(); console.log('Найдено подписантов:', signers.length, signers); return signers; } }; })(); // Вспомогательные функции 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', { day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit' }); } 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 из основного скрипта const checkUser = setInterval(function() { if (typeof window.currentUser !== 'undefined') { clearInterval(checkUser); TasksType.init(); } }, 100); // Таймаут на случай, если currentUser так и не появится setTimeout(function() { clearInterval(checkUser); TasksType.init(); }, 5000); }); // Экспортируем в глобальную область window.TasksType = TasksType;