diff --git a/api-chat.js b/api-chat.js index 6d50a6b..f823b0e 100644 --- a/api-chat.js +++ b/api-chat.js @@ -460,7 +460,44 @@ module.exports = function(app, db, upload) { }); }); }); +// GET /api/chat/unread-summary – сводка непрочитанных сообщений по задачам +router.get('/api/chat/unread-summary', requireAuth, (req, res) => { + const userId = req.session.user.id; + const query = ` + SELECT + m.task_id, + t.title, + COUNT(*) as unread_count + FROM task_chat_messages m + JOIN tasks t ON m.task_id = t.id + LEFT JOIN task_chat_reads r ON m.id = r.message_id AND r.user_id = ? + WHERE m.user_id != ? + AND m.is_deleted = 0 + AND r.id IS NULL + GROUP BY m.task_id, t.title + ORDER BY MAX(m.created_at) DESC + LIMIT 50 + `; + + db.all(query, [userId, userId], (err, rows) => { + if (err) { + console.error('❌ Ошибка получения сводки непрочитанных:', err); + return res.status(500).json({ error: 'Ошибка получения сводки' }); + } + + const totalUnread = rows.reduce((sum, row) => sum + row.unread_count, 0); + + res.json({ + tasks: rows.map(r => ({ + taskId: r.task_id, + title: r.title, + unreadCount: r.unread_count + })), + totalUnread + }); + }); +}); // Вспомогательная функция для уведомлений участников function notifyTaskParticipants(taskId, senderId, message) { db.all(` diff --git a/public/auth.js b/public/auth.js index 80601c5..ccd8e2e 100644 --- a/public/auth.js +++ b/public/auth.js @@ -105,6 +105,7 @@ function reloadAllScripts() { 'profile.js', 'time-selector.js', 'openTaskChat.js', + 'openTaskChat2.js', 'tasks_files.js', 'navbar.js', 'chat-ui.js', diff --git a/public/openTaskChat2.js b/public/openTaskChat2.js new file mode 100644 index 0000000..b5cc212 --- /dev/null +++ b/public/openTaskChat2.js @@ -0,0 +1,130 @@ +// Показывает модальное окно со списком задач +function showUnreadNotification(tasks) { + const modalHtml = ` + + `; + + let modal = document.getElementById('unread-notification-modal'); + if (modal) modal.remove(); + + const container = document.createElement('div'); + container.innerHTML = modalHtml; + document.body.appendChild(container); + + modal = document.getElementById('unread-notification-modal'); + modal.style.display = 'block'; + + // Добавляем стили (если ещё не добавлены) + if (!document.getElementById('unread-notification-styles')) { + const style = document.createElement('style'); + style.id = 'unread-notification-styles'; + style.textContent = ` + #unread-notification-modal { + display: none; + position: fixed; + z-index: 1001; + left: 0; + top: 0; + width: 100%; + height: 100%; + background-color: rgba(0,0,0,0.5); + animation: fadeIn 0.3s; + } + #unread-notification-modal .modal-content { + animation: slideIn 0.3s ease-out; + background-color: #fefefe; + margin: 5% auto; + border-radius: 10px; + box-shadow: 0 4px 20px rgba(0,0,0,0.2); + overflow: hidden; + } + @keyframes fadeIn { + from { opacity: 0; } + to { opacity: 1; } + } + @keyframes slideIn { + from { transform: translateY(-50px); opacity: 0; } + to { transform: translateY(0); opacity: 1; } + } + `; + document.head.appendChild(style); + } +} + +function closeUnreadNotification() { + const modal = document.getElementById('unread-notification-modal'); + if (modal) { + modal.style.display = 'none'; + setTimeout(() => modal.remove(), 300); + } +} + +// Вспомогательная функция для склонения +function pluralize(count, words) { + const cases = [2, 0, 1, 1, 1, 2]; + return words[(count % 100 > 4 && count % 100 < 20) ? 2 : cases[Math.min(count % 10, 5)]]; +} + +// Экранирование HTML в заголовке задачи (защита от XSS) +function escapeHtml(unsafe) { + return unsafe + .replace(/&/g, "&") + .replace(//g, ">") + .replace(/"/g, """) + .replace(/'/g, "'"); +} + +// Открывает чат задачи и помечает все сообщения как прочитанные +function openTaskAndMarkRead(taskId) { + closeUnreadNotification(); + // Опционально: сразу отмечаем все сообщения прочитанными + fetch(`/api/chat/tasks/${taskId}/mark-read`, { method: 'POST' }) + .catch(err => console.warn('Не удалось отметить сообщения как прочитанные', err)) + .finally(() => openTaskChat(taskId)); +} + +// Проверка наличия непрочитанных сообщений +function checkUnreadMessages() { + // Не беспокоим пользователя, если страница не активна + if (document.hidden) return; + + fetch('/api/chat/unread-summary') + .then(response => { + if (response.status === 401) return null; // пользователь не авторизован + return response.json(); + }) + .then(data => { + if (data && data.totalUnread > 0) { + showUnreadNotification(data.tasks); + } + }) + .catch(err => console.error('Ошибка проверки новых сообщений:', err)); +} + +// Запуск периодической проверки (раз в 5 минут) +setInterval(checkUnreadMessages, 5 * 60 * 1000); + +// Проверка при загрузке страницы +document.addEventListener('DOMContentLoaded', checkUnreadMessages); \ No newline at end of file