From 4c9298c5732318d5d7d2171ec66d30bb4f2b8b37 Mon Sep 17 00:00:00 2001 From: kalugin66 Date: Thu, 19 Feb 2026 16:07:07 +0500 Subject: [PATCH] =?UTF-8?q?=D0=BF=D0=BE=D0=B4=D0=BF=D0=B8=D1=81=D1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- database.js | 5 + public/index.html | 3 +- public/navbar.js | 2 +- public/signature.js | 315 ++++++++++++++++++++++++++++++++++ public/tasks-type.js | 397 +++++++++++++++++++++++++++++++++++-------- public/ui.js | 4 +- 6 files changed, 652 insertions(+), 74 deletions(-) create mode 100644 public/signature.js diff --git a/database.js b/database.js index 6a29792..c81042d 100644 --- a/database.js +++ b/database.js @@ -1648,10 +1648,15 @@ function checkTaskAccess(userId, taskId, callback) { return; } + // Администратор имеет полный доступ if (user && user.role === 'admin') { callback(null, true); return; } + if (user && user.role === 'tasks') { + callback(null, true); + return; + } db.get("SELECT status, created_by, closed_at FROM tasks WHERE id = ?", [taskId], (err, task) => { if (err || !task) { diff --git a/public/index.html b/public/index.html index cb88a74..c881f24 100644 --- a/public/index.html +++ b/public/index.html @@ -417,12 +417,13 @@ + - + diff --git a/public/navbar.js b/public/navbar.js index d950890..15acc37 100644 --- a/public/navbar.js +++ b/public/navbar.js @@ -82,7 +82,7 @@ navButtons.push( id: "create-task-btn" } ); - if (currentUser && navbar_checkUserGroup('Секретарь') || currentUser && currentUser.role === 'admin') { + if (currentUser && navbar_checkUserGroup('1Секретарь') || currentUser && currentUser.role === 'admin') { navButtons.push({ onclick: "TasksType.show('document')", className: "nav-btn tasks", diff --git a/public/signature.js b/public/signature.js new file mode 100644 index 0000000..a19bd0f --- /dev/null +++ b/public/signature.js @@ -0,0 +1,315 @@ +// signature.js - Универсальный скрипт для подписания задач +(function() { + 'use strict'; + + // Конфигурация + const CONFIG = { + signerGroup: 'Подписант', + apiEndpoint: '/api2/idusers', + usersEndpoint: '/api/users', + replaceEndpoint: '/api/tasks/${taskId}/replace-assignee', + replaceAllEndpoint: '/api/tasks/${taskId}/replace-all-assignees', + taskTypeDocument: 'document' // Тип задачи "Документ" + }; + + // Текущий пользователь + let currentUser = null; + + // Получение текущего пользователя + async function getCurrentUser() { + try { + const response = await fetch('/api/user'); + const data = await response.json(); + if (data.user) { + currentUser = data.user; + console.log('✅ Signature: текущий пользователь', currentUser); + } + return currentUser; + } catch (error) { + console.error('❌ Signature: ошибка получения пользователя', error); + return null; + } + } + + // Получение подписантов + async function getSigners() { + try { + const response = await fetch(CONFIG.apiEndpoint); + if (!response.ok) return await getSignersFallback(); + + const data = await response.json(); + const signers = data.filter(user => + user.is_active && ( + user.group_name?.toLowerCase().includes(CONFIG.signerGroup.toLowerCase()) || + user.metadata?.groups?.some(g => g.toLowerCase().includes(CONFIG.signerGroup.toLowerCase())) || + user.ldap_group?.toLowerCase().includes(CONFIG.signerGroup.toLowerCase()) + ) + ); + + return signers.map(s => ({ + id: s.user_id, + name: s.user_name || s.login || 'Неизвестно', + login: s.user_login || s.login || '' + })); + } catch { + return await getSignersFallback(); + } + } + + // Запасной метод получения подписантов + async function getSignersFallback() { + try { + const res = await fetch(CONFIG.usersEndpoint); + if (!res.ok) return []; + const users = await res.json(); + return users.filter(u => u.role === 'admin' || u.role === 'signer') + .map(u => ({ id: u.id, name: u.name || u.login, login: u.login })); + } catch { + return []; + } + } + + // Основная функция подписания + window.signTask = async function(taskId, userId) { + // Если userId не передан, используем текущего пользователя + const targetUserId = userId || currentUser?.id; + + if (!targetUserId) { + alert('❌ Не удалось определить текущего пользователя'); + return false; + } + + try { + const signers = await getSigners(); + + if (!signers.length) { + alert('❌ Подписанты не найдены в системе'); + return false; + } + + const names = signers.map(s => `• ${s.name} (${s.login})`).join('\n'); + const msg = signers.length === 1 + ? `✍️ Назначить подписанта?\n\n${names}` + : `✍️ Назначить подписантов?\n\n${names}`; + + if (!confirm(msg)) return false; + + let response; + + if (signers.length > 1) { + const url = CONFIG.replaceAllEndpoint.replace('${taskId}', taskId); + response = await fetch(url, { + method: 'PUT', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ newAssigneeIds: signers.map(s => s.id) }) + }); + } else { + const url = CONFIG.replaceEndpoint.replace('${taskId}', taskId); + response = await fetch(url, { + method: 'PUT', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + oldAssigneeId: targetUserId, + newAssigneeId: signers[0].id + }) + }); + } + + if (response.ok) { + alert(`✅ Задача ${signers.length > 1 ? 'назначена подписантам' : 'переназначена подписанту'}`); + + // Пробуем перезагрузить задачи разными способами + if (window.TasksType && typeof window.TasksType.loadTasks === 'function') { + window.TasksType.loadTasks(); + } else if (window.loadTasks && typeof window.loadTasks === 'function') { + window.loadTasks(); + } else { + // Если нет функций загрузки, просто обновляем страницу + setTimeout(() => window.location.reload(), 1500); + } + + return true; + } else { + const err = await response.json(); + alert('❌ Ошибка: ' + (err.error || 'Неизвестная ошибка')); + return false; + } + } catch (error) { + console.error('❌ Signature error:', error); + alert('❌ Сетевая ошибка: ' + error.message); + return false; + } + }; + + // Обновление статуса задачи + window.updateTaskStatus = 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) { + if (window.TasksType && typeof window.TasksType.loadTasks === 'function') { + window.TasksType.loadTasks(); + } else if (window.loadTasks && typeof window.loadTasks === 'function') { + window.loadTasks(); + } + return true; + } else { + const err = await response.json(); + alert('❌ Ошибка: ' + (err.error || 'Неизвестная ошибка')); + return false; + } + } catch (error) { + console.error('❌ Status update error:', error); + alert('❌ Сетевая ошибка'); + return false; + } + }; + + // Получение типа задачи из элемента карточки + function getTaskTypeFromCard(card) { + // Ищем элемент с типом задачи + const typeElement = card.querySelector('.task-type-badge, .task-type'); + if (typeElement) { + const classList = typeElement.className; + if (classList.includes('document') || classList.includes('Документ')) { + return 'document'; + } + } + + // Пробуем найти по тексту + const cardText = card.textContent || ''; + if (cardText.includes('Документ') || cardText.includes('document')) { + return 'document'; + } + + return null; + } + + // Функция для добавления кнопок в существующие карточки задач + window.addSignatureButtons = function() { + if (!currentUser) return; + + document.querySelectorAll('[data-task-id]').forEach(card => { + // Проверяем, есть ли уже кнопка + if (card.querySelector('.signature-btn')) return; + + // Проверяем тип задачи - только для документов + const taskType = getTaskTypeFromCard(card); + if (taskType !== CONFIG.taskTypeDocument) return; + + const taskId = card.dataset.taskId; + + // Ищем контейнер action-buttons + const actionsContainer = card.querySelector('.action-buttons'); + + if (actionsContainer) { + const btn = document.createElement('button'); + btn.className = 'signature-btn action-btn sign-btn'; + btn.innerHTML = '✍️ Подписать'; + btn.onclick = (e) => { + e.stopPropagation(); + window.signTask(taskId, currentUser.id); + }; + btn.title = 'Передать подписанту'; + actionsContainer.appendChild(btn); + } + }); + }; + + // Наблюдатель за изменениями DOM для динамической добавления кнопок + function observeDOM() { + const observer = new MutationObserver((mutations) => { + mutations.forEach((mutation) => { + if (mutation.addedNodes.length) { + window.addSignatureButtons(); + } + }); + }); + + observer.observe(document.body, { + childList: true, + subtree: true + }); + } + + // Функция для получения отображаемого имени типа задачи + function getTaskTypeDisplayName(type) { + const typeMap = { + 'document': 'Документ', + 'task': 'Задача', + 'bug': 'Ошибка', + 'request': 'Заявка' + }; + return typeMap[type] || type; + } + + // Инициализация при загрузке страницы + async function init() { + console.log('🔄 Signature module initializing...'); + + // Получаем текущего пользователя + await getCurrentUser(); + + // Добавляем кнопки на существующие карточки + setTimeout(() => { + window.addSignatureButtons(); + }, 1000); + + // Запускаем наблюдение за DOM + observeDOM(); + + // Перехватываем функцию рендеринга TasksType если она существует + if (window.TasksType && window.TasksType.renderExpandedContent) { + const originalRender = window.TasksType.renderExpandedContent; + window.TasksType.renderExpandedContent = function(task) { + let html = originalRender ? originalRender(task) : ''; + + // Добавляем кнопку подписания в развернутое содержимое только для документов + if (currentUser && task.task_type === CONFIG.taskTypeDocument && + task.assignments?.some(a => a.user_id === currentUser.id)) { + + // Создаем контейнер action-buttons если его нет + if (!html.includes('action-buttons')) { + const signButton = ` +
+ +
+ `; + + // Вставляем кнопки после заголовка + html = html.replace('
', + '
' + signButton); + } else { + // Добавляем кнопку в существующий контейнер + const signButton = ` + + `; + + html = html.replace('
', + signButton + '
'); + } + } + + return html; + }; + } + + console.log('✅ Signature module loaded'); + } + + // Запускаем инициализацию после загрузки DOM + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', init); + } else { + init(); + } + +})(); \ No newline at end of file diff --git a/public/tasks-type.js b/public/tasks-type.js index debc877..72dc7c0 100644 --- a/public/tasks-type.js +++ b/public/tasks-type.js @@ -6,6 +6,7 @@ const TasksType = (function() { let currentTasks = []; let expandedTasks = new Set(); let currentType = 'document'; // Тип по умолчанию + let currentUser = null; // Конфигурация типов задач const taskTypeConfig = { @@ -67,10 +68,185 @@ const TasksType = (function() { } }; + // Функция для получения подписантов из 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(); } // Создание секции для задач по типам @@ -86,8 +262,7 @@ const TasksType = (function() { section.innerHTML = `
-

Документы

-
@@ -132,7 +306,7 @@ const TasksType = (function() {
`; - // Добавляем секцию после основного контента или в указанное место + // Добавляем секцию после основного контента const container = document.querySelector('.container') || document.body; container.appendChild(section); @@ -160,6 +334,8 @@ const TasksType = (function() { justify-content: space-between; align-items: center; margin-bottom: 20px; + flex-wrap: wrap; + gap: 15px; } .tasks-type-header h2 { @@ -261,10 +437,13 @@ const TasksType = (function() { 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 { @@ -285,12 +464,19 @@ const TasksType = (function() { .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; } @@ -320,6 +506,7 @@ const TasksType = (function() { gap: 8px; margin-bottom: 15px; flex-wrap: wrap; + align-items: center; } .tasks-type-btn { @@ -335,6 +522,24 @@ const TasksType = (function() { 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; @@ -361,8 +566,9 @@ const TasksType = (function() { .tasks-type-assignment { display: flex; align-items: center; - padding: 8px; + padding: 10px; border-bottom: 1px solid #f0f0f0; + gap: 10px; } .tasks-type-assignment:last-child { @@ -373,9 +579,17 @@ const TasksType = (function() { width: 10px; height: 10px; border-radius: 50%; - margin-right: 10px; + 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; @@ -448,6 +662,23 @@ const TasksType = (function() { 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); @@ -542,7 +773,7 @@ const TasksType = (function() { const status = document.getElementById('tasks-type-status-filter')?.value; if (status && status !== 'all') { if (status === 'active') { - params.append('status', 'active'); + params.append('status', 'active,assigned,in_progress,overdue,rework'); } else { params.append('status', status); } @@ -608,12 +839,12 @@ const TasksType = (function() { №${task.id} ${escapeHtml(task.title)} - ${isDeleted ? 'Удалена' : ''} - ${isClosed ? 'Закрыта' : ''} + ${isDeleted ? '🗑️ Удалена' : ''} + ${isClosed ? '✅ Закрыта' : ''} ${timeLeftInfo ? `${timeLeftInfo.text}` : ''}
- Выполнить до: ${formatDateTime(task.due_date || task.created_at)} + До: ${formatDateTime(task.due_date || task.created_at)}
▼ @@ -630,36 +861,49 @@ const TasksType = (function() { // Рендеринг развернутого содержимого function renderExpandedContent(task) { + // Проверяем, является ли текущий пользователь исполнителем задачи + const isCurrentUserAssignee = task.assignments && task.assignments.some( + assignment => assignment.user_id === currentUser?.id + ); + return `
- ${currentUser && currentUser.login === 'minicrm' ? ` - - + ${currentUser && (currentUser.role === 'admin' || currentUser.role === 'tasks') ? ` + + + + + ` : ''} - - + ${isCurrentUserAssignee ? ` + + ` : ''}
- ${task.description || 'Нет описания'} + Описание:
+ ${escapeHtml(task.description) || 'Нет описания'}
${task.rework_comment ? `
- Комментарий к доработке: ${escapeHtml(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) : '
Не назначены
'} @@ -667,15 +911,15 @@ const TasksType = (function() {
- Создана: ${formatDateTime(task.start_date || task.created_at)} - | Автор: ${task.creator_name || 'Неизвестно'} - ${task.due_date ? `| Срок: ${formatDateTime(task.due_date)}` : ''} + 📅 Создана: ${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 'нет файлов'; @@ -701,8 +945,8 @@ const TasksType = (function() {
${escapeHtml(uploader.name)}:
@@ -710,27 +954,22 @@ const TasksType = (function() { `).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)} - ` : ''} + return assignments.map(assignment => { + return ` +
+ +
+ ${escapeHtml(assignment.user_name)} + ${assignment.user_id === currentUser?.id ? '(Вы)' : ''} + ${assignment.due_date ? ` + Срок: ${formatDateTime(assignment.due_date)} + ` : ''} +
-
- ${assignment.user_id === currentUser?.id ? ` - - ` : ''} -
-
- `).join(''); + `; + }).join(''); } // Публичные методы @@ -751,9 +990,11 @@ const TasksType = (function() { if (section) { // Скрываем другие секции document.querySelectorAll('.section').forEach(s => { - s.classList.remove('active'); + if (s.id !== 'tasks-type-section') { + s.style.display = 'none'; + } }); - section.classList.add('active'); + section.style.display = 'block'; } }, @@ -782,45 +1023,54 @@ const TasksType = (function() { await loadTasks(); } else { const error = await response.json(); - alert('Ошибка: ' + (error.error || 'Неизвестная ошибка')); + alert('❌ Ошибка: ' + (error.error || 'Неизвестная ошибка')); } } catch (error) { console.error('Ошибка:', error); - alert('Сетевая ошибка'); + alert('❌ Сетевая ошибка'); } }, + replaceWithSigner: replaceWithSigner, + openTaskChat: function(taskId) { window.open(`/chat?task_id=${taskId}`, '_blank'); }, openAddFileModal: function(taskId) { - if (typeof openAddFileModal === 'function') { - openAddFileModal(taskId); + if (typeof window.openAddFileModal === 'function') { + window.openAddFileModal(taskId); } else { alert('Функция добавления файлов недоступна'); } }, openEditModal: function(taskId) { - if (typeof openEditModal === 'function') { - openEditModal(taskId); + if (typeof window.openEditModal === 'function') { + window.openEditModal(taskId); } else { alert('Функция редактирования недоступна'); } }, openCopyModal: function(taskId) { - if (typeof openCopyModal === 'function') { - openCopyModal(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; } }; })(); -// Вспомогательные функции (не конфликтуют с ui.js) +// Вспомогательные функции function getTaskOverallStatus(task) { if (task.status === 'deleted') return 'deleted'; if (task.closed_at) return 'closed'; @@ -885,12 +1135,12 @@ function getTimeLeftInfo(task) { if (hoursLeft <= 24) { return { - text: `Осталось ${hoursLeft}ч`, + text: `⏰ Осталось ${hoursLeft}ч`, class: 'deadline-24h' }; } else if (hoursLeft <= 48) { return { - text: `Осталось ${hoursLeft}ч`, + text: `⏳ Осталось ${hoursLeft}ч`, class: 'deadline-48h' }; } @@ -901,7 +1151,13 @@ function getTimeLeftInfo(task) { function formatDateTime(dateTimeString) { if (!dateTimeString) return ''; const date = new Date(dateTimeString); - return date.toLocaleString('ru-RU'); + return date.toLocaleString('ru-RU', { + day: '2-digit', + month: '2-digit', + year: 'numeric', + hour: '2-digit', + minute: '2-digit' + }); } function getTaskTypeDisplayName(type) { @@ -931,18 +1187,19 @@ function escapeHtml(text) { // Инициализация при загрузке страницы document.addEventListener('DOMContentLoaded', function() { - // Проверяем, что currentUser определен (из основного скрипта) - if (typeof currentUser !== 'undefined') { + // Ждем загрузки currentUser из основного скрипта + const checkUser = setInterval(function() { + if (typeof window.currentUser !== 'undefined') { + clearInterval(checkUser); + TasksType.init(); + } + }, 100); + + // Таймаут на случай, если currentUser так и не появится + setTimeout(function() { + clearInterval(checkUser); TasksType.init(); - } else { - // Ждем загрузки currentUser - const checkUser = setInterval(function() { - if (typeof currentUser !== 'undefined') { - clearInterval(checkUser); - TasksType.init(); - } - }, 100); - } + }, 5000); }); // Экспортируем в глобальную область diff --git a/public/ui.js b/public/ui.js index c1631d8..e66f493 100644 --- a/public/ui.js +++ b/public/ui.js @@ -91,8 +91,8 @@ function renderTasks() { ${!isDeleted && !isClosed ? ` - ${currentUser && currentUser.login === 'minicrm' ? `` : ''} - ${currentUser && currentUser.login === 'kalugin.o' ? `` : ''} +${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' ? `` : ''}