// 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(); } else if (sectionName === 'mytasks') { console.log('загружаю loadMyTasks'); loadTasks(); } else if (sectionName === 'runtasks') { console.log('загружаю loadRunTasks'); loadTasks(); } // Загрузка профиля при переходе в личный кабинет if (sectionName === 'profile') { loadUserProfile(); loadNotificationSettings(); } } // Вызываем добавление стилей при загрузке страницы document.addEventListener('DOMContentLoaded', function() { addDocumentFieldsStyles(); }); // Функция для красивого отображения реквизитов документа function renderDocumentFields(taskId, fields) { const hasData = fields.document_n || fields.document_d || fields.document_a; if (!hasData) { return `
📄 Реквизиты документа
Реквизиты документа отсутствуют
`; } // Формируем поля с проверкой на длинные значения const fields_html = []; if (fields.document_n && fields.document_d) { const numberValue = escapeHtml(fields.document_n); const dateValue = escapeHtml(fields.document_d); const isLongNumber = numberValue.length > 20; fields_html.push(`
Номер: ${numberValue}
`); } if (fields.document_n && fields.document_d) { const numberValue = escapeHtml(fields.document_n); const dateValue = escapeHtml(fields.document_d); const isLongNumber = numberValue.length > 20; fields_html.push(`
Дата: ${dateValue}
`); } if (fields.document_a) { const authorValue = escapeHtml(fields.document_a); const isLongAuthor = authorValue.length > 15; fields_html.push(`
Автор: ${authorValue}
`); } return `
📄 Реквизиты документа
${fields_html.join('')}
`; } // Обновленная функция loadTasks для загрузки полей документа async function loadTasks() { try { const response = await fetch('/api/tasks'); tasks = await response.json(); // Загружаем поля документа для каждой задачи типа "document" for (const task of tasks) { if (task.task_type === 'document') { try { const docResponse = await fetch(`/api/tasks/${task.id}/document-fields`); if (docResponse.ok) { const docData = await docResponse.json(); task.document_fields = docData.data || {}; } else { task.document_fields = {}; } } catch (error) { console.error(`Ошибка загрузки полей документа для задачи ${task.id}:`, error); task.document_fields = {}; } } } // Определяем активную секцию и рендерим в соответствующий контейнер const activeSection = document.querySelector('.section.active'); if (activeSection) { const sectionId = activeSection.id; if (sectionId === 'mytasks-section') { renderTasksInContainer('mytasks-list'); } else if (sectionId === 'runtasks-section') { renderTasksInContainer('runtasks-list'); } else { renderTasks(); } } else { renderTasks(); } } catch (error) { console.error('Ошибка загрузки задач:', error); const container = document.getElementById('tasks-list'); if (container) { container.innerHTML = '
Ошибка загрузки задач
'; } } } 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); // Получаем реквизиты документа (если есть) const documentFields = task.document_fields || {}; 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(', ')}` : ''}
Выполнить до: ${formatDateTime(task.due_date || task.created_at)}
${isExpanded ? `
${!isDeleted && !isClosed ? ` ${currentUser && currentUser.login === 'minicrm' ? `` : ''} ${currentUser && currentUser.login === 'kalugin.o' ? `` : ''} ${currentUser && currentUser.role === 'tasks' && canEdit || currentUser.role === 'admin' ? `` : ''} ${currentUser && currentUser.role === 'tasks' && canEdit || currentUser.role === 'admin' ? `` : ''} ${currentUser && currentUser.login === 'minicrm' ? `` : ''} ${currentUser && currentUser.login === 'minicrm' ? `` : ''} ${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}
` : ''} ${task.task_type === 'document' ? renderDocumentFields(task.id, documentFields) : ''}
Файлы: ${task.files && task.files.length > 0 ? renderGroupedFilesWithDelete(task) : 'нет файлов'}
Исполнители: ${task.assignments && task.assignments.length > 0 ? renderAssignmentList(task.assignments, task.id, canEdit) : '
Не назначены
' }
Создана: ${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.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 openReworkAssignmentModal(taskId, userId, userName) { const task = tasks.find(t => t.id === taskId); if (!task) { alert('Задача не найдена'); return; } if (parseInt(task.created_by) !== currentUser.id && currentUser.role !== 'admin') { alert('Только автор задачи может отправлять на доработку'); return; } const existingModal = document.getElementById('rework-assignment-modal'); if (existingModal) { existingModal.remove(); } const modal = document.createElement('div'); modal.className = 'modal'; modal.id = 'rework-assignment-modal'; modal.style.display = 'block'; modal.innerHTML = ` `; document.body.appendChild(modal); const textarea = document.getElementById(`rework-comment-${taskId}-${userId}`); if (textarea) { setTimeout(() => textarea.focus(), 100); } } // Функция для принудительного завершения задачи исполнителя async function forceCompleteAssignment(taskId, userId, userName) { if (!confirm(`Вы уверены, что хотите отметить задачу как выполненную для сотрудника ${userName}?\nЭто действие нельзя отменить.`)) { return; } try { const response = await fetch(`/api/tasks/${taskId}/force-complete/${userId}`, { method: 'PUT', headers: { 'Content-Type': 'application/json', } }); if (response.ok) { alert(`✅ Задача отмечена как выполненная для сотрудника ${userName}`); loadTasks(); } else { const error = await response.json(); alert(`❌ Ошибка: ${error.error || 'Неизвестная ошибка'}`); } } catch (error) { console.error('❌ Ошибка:', error); alert('Сетевая ошибка при выполнении операции'); } } function escapeHtml(text) { if (!text) return ''; return String(text) .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, '''); } async function submitReworkAssignment(taskId, userId) { const textarea = document.getElementById(`rework-comment-${taskId}-${userId}`); if (!textarea) { alert('Ошибка: поле комментария не найдено'); return; } const comment = textarea.value.trim(); if (!comment) { alert('Пожалуйста, укажите комментарий к доработке'); textarea.style.border = '2px solid red'; textarea.focus(); return; } const submitBtn = document.querySelector(`#rework-assignment-modal .btn-warning`); if (submitBtn) { submitBtn.disabled = true; submitBtn.innerHTML = '⏳ Отправка...'; } try { const response = await fetch(`/api/tasks/${taskId}/rework-assignment/${userId}`, { method: 'PUT', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ comment: comment }) }); const data = await response.json(); if (response.ok) { alert('✅ Исполнитель отправлен на доработку'); closeReworkAssignmentModal(); if (typeof loadTasks === 'function') { loadTasks(); } else { location.reload(); } } else { alert(`❌ Ошибка: ${data.error || 'Неизвестная ошибка'}`); } } catch (error) { console.error('❌ Ошибка:', error); alert('Сетевая ошибка при отправке на доработку: ' + error.message); } finally { if (submitBtn) { submitBtn.disabled = false; submitBtn.innerHTML = '🔄 Отправить на доработку'; } } } function closeReworkAssignmentModal() { const modal = document.getElementById('rework-assignment-modal'); if (modal) { modal.style.display = 'none'; setTimeout(() => { modal.remove(); }, 300); } } 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); } const activeSection = document.querySelector('.section.active'); if (activeSection) { const sectionId = activeSection.id; if (sectionId === 'mytasks-section') { renderTasksInContainer('mytasks-list'); } else if (sectionId === 'runtasks-section') { renderTasksInContainer('runtasks-list'); } else if (sectionId === 'tasks-section') { renderTasks(); } } else { 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); const task = tasks.find(t => t.id === taskId); const isTaskCreator = task && parseInt(task.created_by) === currentUser.id; const isDocumentTask = task && task.task_type === 'document'; 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 openDocumentCompleteModal(taskId, userId) { const task = tasks.find(t => t.id === taskId); if (!task) { alert('Задача не найдена'); return; } const modalHtml = ` `; const modalContainer = document.createElement('div'); modalContainer.innerHTML = modalHtml; document.body.appendChild(modalContainer); setTimeout(() => { document.getElementById('document-complete-modal').style.display = 'block'; document.getElementById('doc-number').focus(); }, 10); } function closeDocumentCompleteModal() { const modal = document.getElementById('document-complete-modal'); if (modal) { modal.style.display = 'none'; setTimeout(() => { modal.parentElement.remove(); }, 300); } } async function submitDocumentComplete(taskId, userId) { const docNumber = document.getElementById('doc-number').value.trim(); const docDate = document.getElementById('doc-date').value; if (!docNumber) { alert('Пожалуйста, укажите номер документа'); document.getElementById('doc-number').focus(); return; } if (!docDate) { alert('Пожалуйста, укажите дату документа'); return; } const submitBtn = document.querySelector('#document-complete-modal .btn-success'); if (submitBtn) { submitBtn.disabled = true; submitBtn.innerHTML = '⏳ Сохранение...'; } try { const docResponse = await fetch(`/api/tasks/${taskId}/document-fields`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ document_n: docNumber, document_d: formatDateForDocument(docDate), document_a: currentUser.login }) }); if (!docResponse.ok) { const error = await docResponse.json(); throw new Error(error.error || 'Ошибка сохранения реквизитов документа'); } await updateStatus(taskId, userId, 'completed'); closeDocumentCompleteModal(); } catch (error) { console.error('❌ Ошибка:', error); alert(`❌ Ошибка: ${error.message}`); if (submitBtn) { submitBtn.disabled = false; submitBtn.innerHTML = '✅ Завершить'; } } } function formatDateForDocument(dateString) { if (!dateString) return ''; const date = new Date(dateString); const day = String(date.getDate()).padStart(2, '0'); const month = String(date.getMonth() + 1).padStart(2, '0'); const year = date.getFullYear(); return `${day}.${month}.${year}`; } 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) { 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); } } function openAddFileModal(taskId) { const task = tasks.find(t => t.id === taskId); if (!task) { alert('Задача не найдена'); return; } if (!canUserAddFilesToTask(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 (expandedTasks.has(taskId)) { renderTasks(); } } else { const errorText = await response.text(); console.error('Ошибка сервера:', errorText); alert(`Ошибка при добавлении файла: ${response.status} ${response.statusText}`); } } catch (error) { console.error('Ошибка:', error); alert('Сетевая ошибка при добавлении файла: ' + error.message); } }); 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); } } async function loadTaskFiles(taskId) { try { const response = await fetch(`/api/tasks/${taskId}/files`); const allFiles = await response.json(); const taskIndex = tasks.findIndex(t => t.id === taskId); if (taskIndex === -1) { console.error('Задача не найдена:', taskId); return; } tasks[taskIndex].files = allFiles; const fileContainer = document.getElementById(`files-${taskId}`); if (fileContainer) { fileContainer.innerHTML = ` Файлы: ${allFiles.length > 0 ? (typeof renderGroupedFilesWithDelete === 'function' ? renderGroupedFilesWithDelete(tasks[taskIndex]) : renderGroupedFiles(tasks[taskIndex])) : 'нет файлов'} `; } } catch (error) { console.error('Ошибка загрузки файлов:', error); } } async function selectTaskType(type) { document.querySelectorAll('.task-type-btn').forEach(btn => { btn.classList.remove('active'); }); const selectedBtn = document.querySelector(`.task-type-btn[data-type="${type}"]`); if (selectedBtn) { selectedBtn.classList.add('active'); } document.getElementById('task-type').value = type; updateTaskFormBasedOnType(type); suggestDefaultTitle(type); selectedUsers = []; const checklist = document.getElementById('users-checklist'); if (checklist) { checklist.innerHTML = '
⏳ Загрузка доступных пользователей...
'; } try { if (type === 'document') { await reloadUsersForDocumentType(); } else if (['it', 'ahch', 'psychologist', 'speech_therapist', 'hr', 'certificate', 'e_journal'].includes(type)) { const groupNames = { 'it': 'ИТ специалист', 'ahch': 'АХЧ', 'psychologist': 'психолог', 'speech_therapist': 'логопед', 'hr': 'Диспетчер', 'certificate': 'Администрация', 'e_journal': 'Админ ЭЖ' }; await reloadUsersForType(groupNames[type]); } else { await loadUsers(); } } catch (error) { console.error('Ошибка загрузки пользователей:', error); if (checklist) { checklist.innerHTML = '
Ошибка загрузки пользователей
'; } } } async function reloadUsersForDocumentType() { try { const response = await fetch('/api/users'); const allUsersData = await response.json(); const secretaries = []; for (const user of allUsersData) { if (user.id === currentUser.id) continue; const groups = await getUserGroups(user.id); const hasSecretaryGroup = groups.some(group => group.name === 'Секретарь' || (typeof group === 'string' && group.includes('Секретарь')) ); if (hasSecretaryGroup) { secretaries.push(user); } } users = secretaries; filteredUsers = [...users]; renderUsersChecklist(); } catch (error) { console.error('Ошибка загрузки секретарей:', error); } } async function reloadUsersForType(TypegroupName) { try { const response = await fetch('/api/users'); const allUsersData = await response.json(); const TypegroupUsers = []; for (const user of allUsersData) { if (user.id === currentUser.id) continue; const groups = await getUserGroups(user.id); const hasTargetGroup = groups.some(group => group.name === TypegroupName || (typeof group === 'string' && group.includes(TypegroupName)) ); if (hasTargetGroup) { TypegroupUsers.push(user); } } users = TypegroupUsers; filteredUsers = [...users]; renderUsersChecklist(); return filteredUsers; } catch (error) { console.error(`Ошибка загрузки пользователей группы "${TypegroupName}":`, error); throw error; } } async function getUserGroups(userId) { if (!window.userGroupsCache) window.userGroupsCache = {}; if (window.userGroupsCache[userId]) { return window.userGroupsCache[userId]; } try { const response = await fetch(`/api2/idusers/user/${userId}/groups`); if (!response.ok) return []; const groups = await response.json(); window.userGroupsCache[userId] = groups || []; return window.userGroupsCache[userId]; } catch (error) { console.error('Ошибка получения групп пользователя:', error); return []; } } function suggestDefaultTitle(type) { const titleField = document.getElementById('title'); if (!titleField) return; if (titleField.value.trim() !== '') return; const defaultTitles = { 'regular': '', 'document': 'Согласование документа: ', 'it': 'Заявка в ИТ отдел: ', 'ahch': 'Заявка в АХЧ: ', 'psychologist': 'Заявка к психологу: ', 'speech_therapist': 'Заявка к логопеду: ', 'hr': 'Заявка диспетчеру расписания: ', 'certificate': 'Заявка на получение справки: ', 'e_journal': 'Заявка на доступ в электронный журнал: ' }; const defaultTitle = defaultTitles[type] || ''; titleField.placeholder = `${defaultTitle}укажите детали...`; } function updateTaskFormBasedOnType(type) { const userSearchField = document.getElementById('user-search'); const taskTypeInfo = document.getElementById('task-type-info'); const defaultPlaceholders = { 'regular': 'Поиск исполнителей...', 'document': 'Поиск секретарей/администрации...', 'it': 'Поиск ИТ специалистов...', 'ahch': 'Поиск АХЧ сотрудников...', 'psychologist': 'Поиск психологов...', 'speech_therapist': 'Поиск логопедов...', 'hr': 'Поиск сотрудников кадровой службы...', 'certificate': 'Поиск секретаря/завуча...', 'e_journal': 'Поиск администратора электронного журнала...' }; if (userSearchField) { userSearchField.placeholder = defaultPlaceholders[type] || 'Поиск исполнителей...'; } if (taskTypeInfo) { const typeInfo = { 'regular': 'Доступны все пользователи в зависимости от ваших прав', 'document': 'Доступны только пользователи из группы "Секретарь"', 'it': 'Доступны ИТ специалисты', 'ahch': 'Доступны сотрудники АХЧ', 'psychologist': 'Доступны психологи', 'speech_therapist': 'Доступны логопеды', 'hr': 'Доступны сотрудники кадровой службы', 'certificate': 'Доступны секретари и завучи', 'e_journal': 'Доступны администраторы электронного журнала' }; taskTypeInfo.textContent = typeInfo[type] || ''; } showAdditionalFieldsForType(type); } function showAdditionalFieldsForType(type) { hideAllAdditionalFields(); switch(type) { case 'certificate': showCertificateFields(); break; case 'e_journal': showEJournalFields(); break; case 'psychologist': case 'speech_therapist': showSpecialistFields(type); break; } } function hideAllAdditionalFields() {} function showCertificateFields() {} function showEJournalFields() {} function showSpecialistFields(type) {} function getTaskTypeName(type) { const typeNames = { 'regular': 'задачу', 'document': 'название документа', 'it': 'описание проблемы', 'ahch': 'описание заявки', 'psychologist': 'повод для обращения к психологу', 'speech_therapist': 'повод для обращения к логопеду', 'hr': 'вопрос к кадровой службе', 'certificate': 'тип необходимой справки', 'e_journal': 'информацию для доступа к журналу' }; return typeNames[type] || 'задачу'; } function getTaskTypeDisplayName(type) { const typeNames = { 'regular': 'Задача', 'document': 'Документ', 'it': 'ИТ', 'ahch': 'АХЧ', 'psychologist': 'Психолог', 'speech_therapist': 'Логопед', 'hr': 'Кадры', 'certificate': 'Справка', 'e_journal': 'Эл. журнал' }; return typeNames[type] || type; } function getTaskTypeIcon(type) { const icons = { 'regular': 'fas fa-tasks', 'document': 'fas fa-file-signature', 'it': 'fas fa-desktop', 'ahch': 'fas fa-tools', 'psychologist': 'fas fa-brain', 'speech_therapist': 'fas fa-comment-medical', 'hr': 'fas fa-users', 'certificate': 'fas fa-file-certificate', 'e_journal': 'fas fa-book' }; return icons[type] || 'fas fa-tasks'; } async function openManageAssigneesModal(taskId) { const task = tasks.find(t => t.id === taskId); if (!task) { alert('Задача не найдена'); return; } if (!canUserEditTask(task)) { alert('У вас нет прав для управления исполнителями этой задачи'); return; } const currentAssignees = task.assignments || []; try { const response = await fetch(`/api/tasks/${taskId}/available-assignees`); const availableUsers = await response.json(); const allUsersResponse = await fetch('/api/users'); const allUsers = await allUsersResponse.json(); const modalHtml = ` `; const modalContainer = document.createElement('div'); modalContainer.innerHTML = modalHtml; document.body.appendChild(modalContainer); setTimeout(() => { document.getElementById('manage-assignees-modal').style.display = 'block'; }, 10); } catch (error) { console.error('Ошибка загрузки данных:', error); alert('Ошибка загрузки данных пользователей'); } } async function assignAdd_openModal(taskId) { const task = tasks.find(t => t.id === taskId); if (!task) { alert('Задача не найдена'); return; } if (!canUserEditTask(task)) { alert('У вас нет прав для управления исполнителями этой задачи'); return; } try { const response = await fetch(`/api/tasks/${taskId}/available-assignees`); let availableUsers = await response.json(); const taskCreatorId = task.created_by; availableUsers = availableUsers.filter(user => parseInt(user.id) !== parseInt(taskCreatorId) ); const modalHtml = ` `; const modalContainer = document.createElement('div'); modalContainer.innerHTML = modalHtml; document.body.appendChild(modalContainer); setTimeout(() => { document.getElementById('manage-assignees-modal').style.display = 'block'; }, 10); } catch (error) { console.error('Ошибка загрузки данных:', error); alert('Ошибка загрузки данных пользователей'); } } async function assignRemove_openModal(taskId) { const task = tasks.find(t => t.id === taskId); if (!task) { alert('Задача не найдена'); return; } if (!canUserEditTask(task)) { alert('У вас нет прав для управления исполнителями этой задачи'); return; } const currentAssignees = (task.assignments || []).filter(assignee => assignee.status !== 'deleted' && assignee.status !== 'удален' ); const modalHtml = ` `; const modalContainer = document.createElement('div'); modalContainer.innerHTML = modalHtml; document.body.appendChild(modalContainer); setTimeout(() => { document.getElementById('manage-assignees-modal').style.display = 'block'; }, 10); } function switchManageTab(tabName) { document.querySelectorAll('.tab-btn').forEach(btn => btn.classList.remove('active')); document.querySelectorAll('.tab-content').forEach(content => content.classList.remove('active')); const activeTabBtn = document.querySelector(`.tab-btn[onclick*="${tabName}"]`); if (activeTabBtn) activeTabBtn.classList.add('active'); const activeContent = document.getElementById(`${tabName}-tab`); if (activeContent) activeContent.classList.add('active'); } function filterAvailableUsers() { const search = document.getElementById('available-users-search')?.value.toLowerCase() || ''; const checkboxes = document.querySelectorAll('.available-user-checkbox'); checkboxes.forEach(checkbox => { const label = checkbox.parentElement.textContent.toLowerCase(); const isVisible = label.includes(search) || search === ''; checkbox.parentElement.parentElement.style.display = isVisible ? '' : 'none'; }); } function filterReplaceAllUsers() { const search = document.getElementById('replace-all-users-search')?.value.toLowerCase() || ''; const checkboxes = document.querySelectorAll('.replace-all-user-checkbox'); checkboxes.forEach(checkbox => { const label = checkbox.parentElement.textContent.toLowerCase(); const isVisible = label.includes(search) || search === ''; checkbox.parentElement.parentElement.style.display = isVisible ? '' : 'none'; }); } async function addAssignees(taskId) { const selectedCheckboxes = document.querySelectorAll('.available-user-checkbox:checked'); const assigneeIds = Array.from(selectedCheckboxes).map(cb => cb.value); if (assigneeIds.length === 0) { alert('Выберите хотя бы одного пользователя для добавления'); return; } try { const response = await fetch(`/api/tasks/${taskId}/assignees`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ assigneeIds }) }); if (response.ok) { const result = await response.json(); alert(result.message); closeManageAssigneesModal(); loadTasks(); } else { const error = await response.json(); alert(`Ошибка: ${error.error || 'Неизвестная ошибка'}`); } } catch (error) { console.error('Ошибка:', error); alert('Сетевая ошибка при добавлении исполнителей'); } } async function removeAssignees(taskId) { const selectedCheckboxes = document.querySelectorAll('.remove-user-checkbox:checked'); const assigneeIds = Array.from(selectedCheckboxes).map(cb => cb.value); if (assigneeIds.length === 0) { alert('Выберите хотя бы одного исполнителя для удаления'); return; } if (!confirm(`Вы уверены, что хотите удалить ${assigneeIds.length} исполнителей из задачи?`)) { return; } const results = []; for (const assigneeId of assigneeIds) { try { const response = await fetch(`/api/tasks/${taskId}/assignees/${assigneeId}`, { method: 'DELETE' }); if (response.ok) { results.push({ id: assigneeId, success: true }); } else { const error = await response.json(); results.push({ id: assigneeId, success: false, error: error.error }); } } catch (error) { results.push({ id: assigneeId, success: false, error: error.message }); } } const successful = results.filter(r => r.success).length; const failed = results.filter(r => !r.success); if (failed.length > 0) { alert(`Удалено ${successful} исполнителей. Ошибки: ${failed.map(f => `ID ${f.id}: ${f.error}`).join('; ')}`); } else { alert(`Успешно удалено ${successful} исполнителей`); } closeManageAssigneesModal(); loadTasks(); } async function replaceAssignee(taskId) { const oldAssigneeId = document.getElementById('old-assignee-select').value; const newAssigneeId = document.getElementById('new-assignee-select').value; if (!oldAssigneeId || !newAssigneeId) { alert('Выберите старого и нового исполнителя'); return; } if (!confirm('Вы уверены, что хотите заменить исполнителя?')) { return; } try { const response = await fetch(`/api/tasks/${taskId}/replace-assignee`, { method: 'PUT', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ oldAssigneeId, newAssigneeId }) }); if (response.ok) { const result = await response.json(); alert(result.message); closeManageAssigneesModal(); loadTasks(); } else { const error = await response.json(); alert(`Ошибка: ${error.error || 'Неизвестная ошибка'}`); } } catch (error) { console.error('Ошибка:', error); alert('Сетевая ошибка при замене исполнителя'); } } async function replaceAllAssignees(taskId) { const selectedCheckboxes = document.querySelectorAll('.replace-all-user-checkbox:checked'); const newAssigneeIds = Array.from(selectedCheckboxes).map(cb => cb.value); if (newAssigneeIds.length === 0) { alert('Выберите хотя бы одного нового исполнителя'); return; } if (!confirm('Вы уверены, что хотите заменить всех исполнителей? Текущие исполнители будут удалены.')) { return; } try { const response = await fetch(`/api/tasks/${taskId}/replace-all-assignees`, { method: 'PUT', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ newAssigneeIds }) }); if (response.ok) { const result = await response.json(); alert(result.message); closeManageAssigneesModal(); loadTasks(); } else { const error = await response.json(); alert(`Ошибка: ${error.error || 'Неизвестная ошибка'}`); } } catch (error) { console.error('Ошибка:', error); alert('Сетевая ошибка при замене исполнителей'); } } async function assignAllToUser(taskId) { const targetUserId = document.getElementById('target-user-select').value; if (!targetUserId) { alert('Выберите пользователя для назначения'); return; } if (!confirm('Вы уверены, что хотите назначить задачу только этому пользователю? Все текущие исполнители будут удалены.')) { return; } try { const response = await fetch(`/api/tasks/${taskId}/assign-all-to-user`, { method: 'PUT', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ targetUserId }) }); if (response.ok) { const result = await response.json(); alert(result.message); closeManageAssigneesModal(); loadTasks(); } else { const error = await response.json(); alert(`Ошибка: ${error.error || 'Неизвестная ошибка'}`); } } catch (error) { console.error('Ошибка:', error); alert('Сетевая ошибка при назначении задачи'); } } function closeManageAssigneesModal() { const modal = document.getElementById('manage-assignees-modal'); if (modal) { modal.style.display = 'none'; setTimeout(() => { modal.parentElement.remove(); }, 300); } } 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, id: uploaderId, files: [] }; } filesByUploader[uploaderId].files.push(file); }); const visibleGroups = []; for (const uploaderId in filesByUploader) { const uploaderGroup = filesByUploader[uploaderId]; const uploaderIdNum = parseInt(uploaderId); let canSeeThisUploader = false; if (currentUser.role === 'admin') { canSeeThisUploader = true; } else if (parseInt(task.created_by) === currentUser.id) { canSeeThisUploader = true; } else { const creatorId = parseInt(task.created_by); if (uploaderIdNum === creatorId) { canSeeThisUploader = true; } else if (uploaderIdNum === currentUser.id) { canSeeThisUploader = true; } else { canSeeThisUploader = false; } } if (canSeeThisUploader) { visibleGroups.push({ name: uploaderGroup.name, id: uploaderGroup.id, files: uploaderGroup.files }); } } if (visibleGroups.length === 0) { return 'нет файлов'; } if (visibleGroups.length === 1) { const uploader = visibleGroups[0]; return `
${uploader.name}:
${uploader.files.map(file => renderFileIcon(file)).join('')}
`; } return visibleGroups.map(uploader => `
${uploader.name}:
${uploader.files.map(file => renderFileIcon(file)).join('')}
`).join(''); }