Files
minicrm/public/ui.js
2026-02-03 12:04:48 +05:00

801 lines
34 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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();
}
// Загрузка профиля при переходе в личный кабинет
if (sectionName === 'profile') {
loadUserProfile();
loadNotificationSettings();
}
}
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 = '<div class="loading">Задачи не найдены</div>';
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);
return `
<div class="task-card ${isDeleted ? 'deleted' : ''} ${isClosed ? 'closed' : ''}" data-task-id="${task.id}">
<div class="task-header">
<div class="task-title" onclick="toggleTask(${task.id})" style="cursor: pointer; display: flex; justify-content: space-between; align-items: center;">
<div style="flex: 1;">
<!--
${task.task_type ? `<span class="task-type-badge ${task.task_type}">${getTaskTypeDisplayName(task.task_type)}</span>` : ''}
-->
<span class="task-number">Задача №${task.id}</span>
<strong>${task.title}</strong>
${isDeleted ? '<span class="deleted-badge">Удалена</span>' : ''}
${isClosed ? '<span class="closed-badge">Закрыта</span>' : ''}
${isCopy ? '<span class="copy-badge">Копия</span>' : ''}
${timeLeftInfo ? `<span class="deadline-badge ${timeLeftInfo.class}">${timeLeftInfo.text}</span>` : ''}
<span class="role-badge ${getRoleBadgeClass(userRole)}">${userRole}</span>
${task.assignments && task.assignments.length > 0 ? `<span class="task-number">${task.assignments.map(a => a.user_login || a.user_name).join(', ')}</span>` : ''}
</div>
<!--
<div class="task-status ${statusClass}">
${getStatusText(overallStatus)}
</div>
-->
<span class="task-status ${statusClass}">
Выполнить до: ${formatDateTime(task.due_date || task.created_at)}
</span>
<div class="expand-icon" style="margin-left: 10px; transition: transform 0.3s; transform: rotate(${isExpanded ? '180deg' : '0deg'});">
</div>
</div>
</div>
<div class="task-content ${isExpanded ? 'expanded' : ''}">
<div class="task-actions">
${!isDeleted && !isClosed ? `
${canEdit ? `<button class="add-file-btn" onclick="openAddFileModal(${task.id})" title="Добавить файл">📎</button>` : ''}
${canEdit ? `<button class="edit-btn" onclick="openEditModal(${task.id})" title="Редактировать">✏️</button>` : ''}
<button class="copy-btn" onclick="openCopyModal(${task.id})" title="Создать копию">📋</button>
${canEdit ? `<button class="rework-btn" onclick="openReworkModal(${task.id})" title="Вернуть на доработку">🔄</button>` : ''}
${canEdit ? `<button class="close-btn" onclick="closeTask(${task.id})" title="Закрыть задачу">🔒</button>` : ''}
${canEdit ? `<button class="delete-btn" onclick="deleteTask(${task.id})" title="Удалить">🗑️</button>` : ''}
` : ''}
${isClosed && canEdit ? `
<button class="reopen-btn" onclick="reopenTask(${task.id})" title="Открыть задачу">🔓</button>
` : ''}
${isDeleted && currentUser.role === 'admin' ? `
<button class="restore-btn" onclick="restoreTask(${task.id})" title="Восстановить">↶</button>
` : ''}
</div>
${isCopy && task.original_task_title ? `
<div class="task-original">
<small>Оригинал: "${task.original_task_title}" (создал: ${task.original_creator_name})</small>
</div>
` : ''}
<div class="task-description">${task.description || 'Нет описания'}</div>
${task.rework_comment ? `
<div class="rework-comment">
<strong>Комментарий к доработке:</strong> ${task.rework_comment}
</div>
` : ''}
<div class="file-list" id="files-${task.id}">
<strong>Файлы:</strong>
${task.files && task.files.length > 0 ?
`<div class="file-icons-container">${task.files.map(file => renderFileIcon(file)).join('')}</div>` :
'<span class="no-files">нет файлов</span>'
}
</div>
<div class="task-assignments">
<strong>Исполнители:</strong>
${task.assignments && task.assignments.length > 0 ?
renderAssignmentList(task.assignments, task.id, canEdit) :
'<div>Не назначены</div>'
}
</div>
</div>
<div class="task-meta">
<small>Создана: ${formatDateTime(task.start_date || task.created_at)} | Выполнить до: ${formatDateTime(task.due_date || task.created_at)} | Автор: ${task.creator_name} </small>
${task.deleted_at ? `<br><small>Удалена: ${formatDateTime(task.deleted_at)}</small>` : ''}
${task.closed_at ? `<br><small>Закрыта: ${formatDateTime(task.closed_at)}</small>` : ''}
</div>
</div>
`;
}).join('');
}
// Улучшенная функция рендеринга списка исполнителей с фильтрацией
function renderAssignmentList(assignments, taskId, canEdit) {
if (!assignments || assignments.length === 0) {
return '<div>Не назначены</div>';
}
// Создаем контейнер с возможностью фильтрации
return `
<div class="assignments-container">
<div class="assignments-filter">
<input type="text"
class="assignment-filter-input"
placeholder="Поиск исполнителя..."
data-task-id="${taskId}"
oninput="filterAssignments(${taskId})">
<span class="filter-count" id="filter-count-${taskId}">${assignments.length} исполнителей</span>
</div>
<div class="assignments-scroll-container" id="assignments-${taskId}">
${assignments.map(assignment => renderAssignment(assignment, taskId, canEdit)).join('')}
</div>
</div>
`;
}
// Функция для фильтрации исполнителей в конкретной задаче
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);
}
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);
return `
<div class="assignment ${isOverdue ? 'overdue' : ''} ${isRework ? 'rework' : ''}">
<span class="assignment-status ${statusClass}"></span>
<div style="flex: 1;">
<strong>${assignment.user_name}</strong>
${isCurrentUser ? '<small>(Вы)</small>' : ''}
${timeLeftInfo ? `<span class="deadline-indicator ${timeLeftInfo.class}">${timeLeftInfo.text}</span>` : ''}
${assignment.start_date || assignment.due_date ? `
<div class="assignment-dates">
${assignment.start_date ? `<small>Начало: ${formatDateTime(assignment.start_date)}</small>` : ''}
${assignment.due_date ? `<small>Выполнить до: ${formatDateTime(assignment.due_date)}</small>` : ''}
</div>
` : ''}
${assignment.rework_comment ? `
<div class="assignment-rework-comment">
<small><strong>Комментарий:</strong> ${assignment.rework_comment}</small>
</div>
` : ''}
</div>
<div class="action-buttons">
${isCurrentUser && assignment.status === 'assigned' ?
`<button onclick="updateStatus(${taskId}, ${assignment.user_id}, 'in_progress')">Приступить</button>` : ''}
${isCurrentUser && (assignment.status === 'in_progress' || assignment.status === 'overdue' || assignment.status === 'rework') ?
`<button onclick="updateStatus(${taskId}, ${assignment.user_id}, 'completed')">Выполнено</button>` : ''}
${canEdit ?
`<button class="edit-date-btn" onclick="openEditAssignmentModal(${taskId}, ${assignment.user_id})" title="Редактировать сроки">📅</button>` : ''}
</div>
</div>
`;
}
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 = '<div class="loading">Логи не найдены</div>';
return;
}
container.innerHTML = logs.map(log => `
<div class="log-entry">
<div class="log-time">${formatDateTime(log.created_at)}</div>
<div><strong>${log.user_name}</strong> - ${getActionText(log.action)}</div>
<div>Задача: "${log.task_title}"</div>
${log.details ? `<div>Детали: ${log.details}</div>` : ''}
</div>
`).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 = `
<div class="modal" id="add-file-modal">
<div class="modal-content">
<div class="modal-header">
<h3>Добавить файл к задаче: "${task.title}"</h3>
<span class="close" onclick="closeAddFileModal()">&times;</span>
</div>
<div class="modal-body">
<form id="add-file-form">
<input type="hidden" name="task_id" value="${taskId}">
<div class="form-group">
<label for="file-input">Выберите файл:</label>
<input type="file" id="file-input" name="files" multiple>
<small>Максимальный размер: 10MB</small>
</div>
<div class="form-group">
<label for="file-description">Описание файла (необязательно):</label>
<textarea id="file-description" name="description" rows="3"
placeholder="Описание файла..."></textarea>
</div>
<div class="modal-footer">
<button type="button" class="btn-cancel" onclick="closeAddFileModal()">Отмена</button>
<button type="submit" class="btn-primary">Добавить файл</button>
</div>
</form>
</div>
</div>
</div>
`;
// Добавляем модальное окно в DOM
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();
// Пробуем сначала 'files', затем 'file'
formData.append('files', file); // Или 'file' в зависимости от сервера
formData.append('task_id', taskId);
if (description) {
formData.append('description', description);
}
try {
// Попробуем с полем 'files' (скорее всего это правильное имя)
let response = await fetch(`/api/tasks/${taskId}/files`, {
method: 'POST',
body: formData
});
if (!response.ok) {
// Попробуем с полем 'file'
console.log('Попытка с именем поля "file"...');
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';
// Удаляем модальное окно из DOM через некоторое время
setTimeout(() => {
modal.parentElement.remove();
}, 300);
}
}
// Функция для закрытия модального окна добавления файла
function closeAddFileModal() {
const modal = document.getElementById('add-file-modal');
if (modal) {
modal.style.display = 'none';
// Удаляем модальное окно из DOM через некоторое время
setTimeout(() => {
modal.parentElement.remove();
}, 300);
}
}
// Функция для загрузки файлов задачи (если её еще нет)
async function loadTaskFiles(taskId) {
try {
const response = await fetch(`/api/tasks/${taskId}/files`);
const files = await response.json();
// Обновляем файлы в задаче
const taskIndex = tasks.findIndex(t => t.id === taskId);
if (taskIndex !== -1) {
tasks[taskIndex].files = files;
}
// Обновляем отображение файлов
const fileContainer = document.getElementById(`files-${taskId}`);
if (fileContainer) {
const fileList = fileContainer.querySelector('.file-icons-container');
if (fileList) {
fileList.innerHTML = files.map(file => renderFileIcon(file)).join('');
} else {
fileContainer.innerHTML = `
<strong>Файлы:</strong>
${files.length > 0 ?
`<div class="file-icons-container">${files.map(file => renderFileIcon(file)).join('')}</div>` :
'<span class="no-files">нет файлов</span>'
}
`;
}
}
} catch (error) {
console.error('Ошибка загрузки файлов:', error);
}
}
// Функция для выбора типа задачи
function selectTaskType(type) {
// Убираем активный класс со всех кнопок
document.querySelectorAll('.task-type-btn').forEach(btn => {
btn.classList.remove('active');
});
// Добавляем активный класс выбранной кнопке
document.querySelector(`.task-type-btn[data-type="${type}"]`).classList.add('active');
// Устанавливаем значение в скрытое поле
document.getElementById('task-type').value = type;
// Можем добавить дополнительную логику в зависимости от типа
updateTaskFormBasedOnType(type);
// Предлагаем заголовок по умолчанию
suggestDefaultTitle(type);
}
// Функция для предложения заголовка по умолчанию
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');
// Устанавливаем заголовки placeholder для поиска исполнителей
const defaultPlaceholders = {
'regular': 'Поиск исполнителей...',
'document': 'Поиск секретарей/администрации...',
'it': 'Поиск ИТ специалистов...',
'ahch': 'Поиск АХЧ сотрудников...',
'psychologist': 'Поиск психологов...',
'speech_therapist': 'Поиск логопедов...',
'hr': 'Поиск сотрудников кадровой службы...',
'certificate': 'Поиск секретаря/завуча...',
'e_journal': 'Поиск администратора электронного журнала...'
};
// Обновляем placeholder поля поиска исполнителей
if (userSearchField) {
userSearchField.placeholder = defaultPlaceholders[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';
}