817 lines
29 KiB
JavaScript
817 lines
29 KiB
JavaScript
// chat-ui.js - Клиентская часть для чата задач
|
||
|
||
class TaskChat {
|
||
constructor(taskId, taskTitle) {
|
||
this.taskId = taskId;
|
||
this.taskTitle = taskTitle;
|
||
this.messages = [];
|
||
this.currentUserId = null;
|
||
this.isLoading = false;
|
||
this.hasMore = true;
|
||
this.lastMessageDate = null;
|
||
this.replyToMessage = null;
|
||
this.autoRefreshInterval = null;
|
||
|
||
this.init();
|
||
}
|
||
|
||
async init() {
|
||
await this.loadCurrentUser();
|
||
this.createChatModal();
|
||
this.loadMessages();
|
||
this.setupAutoRefresh();
|
||
this.setupEventListeners();
|
||
}
|
||
|
||
async loadCurrentUser() {
|
||
try {
|
||
const response = await fetch('/api/user');
|
||
const data = await response.json();
|
||
this.currentUserId = data.user.id;
|
||
} catch (error) {
|
||
console.error('Ошибка загрузки пользователя:', error);
|
||
}
|
||
}
|
||
|
||
createChatModal() {
|
||
const modalHtml = `
|
||
<div class="modal" id="task-chat-modal-${this.taskId}">
|
||
<div class="modal-content chat-modal-content">
|
||
<div class="modal-header chat-header">
|
||
<div class="chat-header-actions">
|
||
<span class="chat-unread-badge" id="chat-unread-${this.taskId}" style="display: none;">0</span>
|
||
<button class="chat-refresh-btn" onclick="window.taskChats[${this.taskId}].refreshMessages()" title="Обновить">🔄</button>
|
||
<span class="close" onclick="window.taskChats[${this.taskId}].close()">×</span>
|
||
</div>
|
||
<h3>
|
||
<span class="chat-icon">💬</span>
|
||
Чат задачи №${this.taskId}: "${this.taskTitle}"
|
||
</h3>
|
||
<h3>
|
||
<span class="chat-icon"> </span>
|
||
</h3>
|
||
</div>
|
||
<div class="modal-body chat-body" style="padding: 0; display: flex; flex-direction: column; height: 500px;">
|
||
<div class="chat-messages-container" id="chat-messages-${this.taskId}">
|
||
<div class="chat-messages" id="chat-messages-list-${this.taskId}">
|
||
<div class="chat-loading" style="display: none;">Загрузка сообщений...</div>
|
||
</div>
|
||
</div>
|
||
<div class="chat-reply-info" id="chat-reply-info-${this.taskId}" style="display: none;">
|
||
<span>Ответ на сообщение <span id="reply-to-text-${this.taskId}"></span></span>
|
||
<button class="chat-cancel-reply" onclick="window.taskChats[${this.taskId}].cancelReply()">✕</button>
|
||
</div>
|
||
<div class="chat-input-area">
|
||
<textarea
|
||
class="chat-input"
|
||
id="chat-input-${this.taskId}"
|
||
placeholder="Напишите сообщение..."
|
||
rows="1"
|
||
></textarea>
|
||
<div class="chat-attachments" id="chat-attachments-${this.taskId}"></div>
|
||
<div class="chat-actions">
|
||
<button class="chat-send-btn" onclick="window.taskChats[${this.taskId}].sendMessage()">➤ Отправить ➤</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`;
|
||
|
||
// Добавляем стили для чата
|
||
this.addChatStyles();
|
||
|
||
// Добавляем модальное окно в DOM
|
||
const modalContainer = document.createElement('div');
|
||
modalContainer.innerHTML = modalHtml;
|
||
document.body.appendChild(modalContainer);
|
||
|
||
// Показываем модальное окно
|
||
setTimeout(() => {
|
||
const modal = document.getElementById(`task-chat-modal-${this.taskId}`);
|
||
modal.style.display = 'block';
|
||
|
||
// Фокус на поле ввода
|
||
document.getElementById(`chat-input-${this.taskId}`).focus();
|
||
|
||
// Настройка авто-изменения высоты textarea
|
||
this.setupTextareaAutoResize();
|
||
}, 10);
|
||
}
|
||
|
||
addChatStyles() {
|
||
if (document.getElementById('chat-styles')) return;
|
||
|
||
const styles = `
|
||
<style id="chat-styles">
|
||
.chat-modal-content {
|
||
max-width: 800px;
|
||
width: 90%;
|
||
height: 80vh;
|
||
display: flex;
|
||
flex-direction: column;
|
||
padding: 0 !important;
|
||
}
|
||
|
||
.chat-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
padding: 15px 20px;
|
||
background-color: #f8f9fa;
|
||
border-bottom: 1px solid #dee2e6;
|
||
}
|
||
|
||
.chat-header h3 {
|
||
margin: 0;
|
||
font-size: 16px;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
}
|
||
|
||
.chat-icon {
|
||
font-size: 20px;
|
||
}
|
||
|
||
.chat-header-actions {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
}
|
||
|
||
.chat-unread-badge {
|
||
background-color: #ff4444;
|
||
color: white;
|
||
border-radius: 50%;
|
||
padding: 2px 6px;
|
||
font-size: 12px;
|
||
min-width: 20px;
|
||
text-align: center;
|
||
}
|
||
|
||
.chat-refresh-btn {
|
||
background: none;
|
||
border: none;
|
||
cursor: pointer;
|
||
font-size: 18px;
|
||
padding: 4px 8px; /* Увеличили область клика */
|
||
border-radius: 4px; /* Скругленные углы при наведении */
|
||
transition: background-color 0.2s; /* Плавный переход цвета */
|
||
line-height: 1; /* Фиксируем высоту строки */
|
||
}
|
||
|
||
.chat-refresh-btn:hover {
|
||
background-color: rgba(0,0,0,0.05); /* Легкий серый фон при наведении */
|
||
opacity: 1; /* Убираем прозрачность или оставляем как есть */
|
||
}
|
||
|
||
.chat-body {
|
||
flex: 1;
|
||
overflow: hidden;
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
.chat-messages-container {
|
||
flex: 1;
|
||
overflow-y: auto;
|
||
padding: 20px;
|
||
background-color: #f5f5f5;
|
||
}
|
||
|
||
.chat-messages {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 15px;
|
||
}
|
||
|
||
.chat-message {
|
||
max-width: 80%;
|
||
padding: 10px 15px;
|
||
border-radius: 15px;
|
||
position: relative;
|
||
word-wrap: break-word;
|
||
}
|
||
|
||
.chat-message-own {
|
||
align-self: flex-end;
|
||
background-color: #007bff;
|
||
color: white;
|
||
border-bottom-right-radius: 5px;
|
||
}
|
||
|
||
.chat-message-other {
|
||
align-self: flex-start;
|
||
background-color: white;
|
||
color: #333;
|
||
border-bottom-left-radius: 5px;
|
||
box-shadow: 0 1px 2px rgba(0,0,0,0.1);
|
||
}
|
||
|
||
.chat-message-header {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
margin-bottom: 5px;
|
||
font-size: 12px;
|
||
}
|
||
|
||
.chat-message-own .chat-message-header {
|
||
color: rgba(255,255,255,0.8);
|
||
}
|
||
|
||
.chat-message-other .chat-message-header {
|
||
color: #666;
|
||
}
|
||
|
||
.chat-message-author {
|
||
font-weight: bold;
|
||
}
|
||
|
||
.chat-message-time {
|
||
font-size: 11px;
|
||
}
|
||
|
||
.chat-message-edited {
|
||
font-size: 11px;
|
||
font-style: italic;
|
||
margin-left: 5px;
|
||
}
|
||
|
||
.chat-message-text {
|
||
font-size: 14px;
|
||
line-height: 1.4;
|
||
}
|
||
|
||
.chat-message-files {
|
||
margin-top: 10px;
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 8px;
|
||
}
|
||
|
||
.chat-file {
|
||
background-color: rgba(0,0,0,0.05);
|
||
border-radius: 5px;
|
||
padding: 5px 10px;
|
||
font-size: 12px;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 5px;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.chat-message-own .chat-file {
|
||
background-color: rgba(255,255,255,0.2);
|
||
}
|
||
|
||
.chat-file:hover {
|
||
background-color: rgba(0,0,0,0.1);
|
||
}
|
||
|
||
.chat-reply-info {
|
||
padding: 10px 20px;
|
||
background-color: #e9ecef;
|
||
border-top: 1px solid #dee2e6;
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
font-size: 13px;
|
||
}
|
||
|
||
.chat-cancel-reply {
|
||
background: none;
|
||
border: none;
|
||
cursor: pointer;
|
||
font-size: 16px;
|
||
color: #666;
|
||
}
|
||
|
||
.chat-input-area {
|
||
border-top: 1px solid #dee2e6;
|
||
padding: 15px 20px;
|
||
background-color: white;
|
||
}
|
||
|
||
.chat-input {
|
||
width: 100%;
|
||
border: 1px solid #ced4da;
|
||
border-radius: 20px;
|
||
padding: 10px 15px;
|
||
font-size: 14px;
|
||
resize: none;
|
||
outline: none;
|
||
}
|
||
|
||
.chat-input:focus {
|
||
border-color: #007bff;
|
||
}
|
||
|
||
.chat-attachments {
|
||
margin-top: 10px;
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 8px;
|
||
}
|
||
|
||
.chat-attachment {
|
||
background-color: #e9ecef;
|
||
border-radius: 15px;
|
||
padding: 5px 10px;
|
||
font-size: 12px;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 5px;
|
||
}
|
||
|
||
.chat-remove-attachment {
|
||
background: none;
|
||
border: none;
|
||
cursor: pointer;
|
||
font-size: 14px;
|
||
color: #666;
|
||
}
|
||
|
||
.chat-actions {
|
||
margin-top: 10px;
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
}
|
||
|
||
.chat-attach-btn {
|
||
cursor: pointer;
|
||
font-size: 20px;
|
||
padding: 5px;
|
||
}
|
||
|
||
.chat-send-btn {
|
||
background-color: #007bff;
|
||
color: white;
|
||
border: none;
|
||
width: 100%;
|
||
cursor: pointer;
|
||
font-size: 18px;
|
||
}
|
||
|
||
.chat-send-btn:hover {
|
||
background-color: #0056b3;
|
||
}
|
||
|
||
.chat-loading {
|
||
text-align: center;
|
||
padding: 20px;
|
||
color: #666;
|
||
}
|
||
|
||
.chat-no-messages {
|
||
text-align: center;
|
||
padding: 40px 20px;
|
||
color: #999;
|
||
font-style: italic;
|
||
}
|
||
</style>
|
||
`;
|
||
|
||
document.head.insertAdjacentHTML('beforeend', styles);
|
||
}
|
||
|
||
setupTextareaAutoResize() {
|
||
const textarea = document.getElementById(`chat-input-${this.taskId}`);
|
||
textarea.addEventListener('input', function() {
|
||
this.style.height = 'auto';
|
||
this.style.height = (this.scrollHeight) + 'px';
|
||
});
|
||
}
|
||
|
||
setupEventListeners() {
|
||
// Отправка по Enter (но не с Shift)
|
||
const textarea = document.getElementById(`chat-input-${this.taskId}`);
|
||
textarea.addEventListener('keydown', (e) => {
|
||
if (e.key === 'Enter' && !e.shiftKey) {
|
||
e.preventDefault();
|
||
this.sendMessage();
|
||
}
|
||
});
|
||
|
||
// Загрузка файлов
|
||
const fileInput = document.getElementById(`chat-file-input-${this.taskId}`);
|
||
fileInput.addEventListener('change', (e) => {
|
||
this.handleFileSelect(e.target.files);
|
||
});
|
||
|
||
// Бесконечная прокрутка для загрузки старых сообщений
|
||
const messagesContainer = document.getElementById(`chat-messages-${this.taskId}`);
|
||
messagesContainer.addEventListener('scroll', () => {
|
||
if (messagesContainer.scrollTop === 0 && !this.isLoading && this.hasMore) {
|
||
this.loadMoreMessages();
|
||
}
|
||
});
|
||
}
|
||
|
||
handleFileSelect(files) {
|
||
const attachmentsContainer = document.getElementById(`chat-attachments-${this.taskId}`);
|
||
attachmentsContainer.innerHTML = '';
|
||
|
||
this.selectedFiles = Array.from(files);
|
||
|
||
this.selectedFiles.forEach((file, index) => {
|
||
const attachment = document.createElement('div');
|
||
attachment.className = 'chat-attachment';
|
||
attachment.innerHTML = `
|
||
📎 ${file.name} (${this.formatFileSize(file.size)})
|
||
<button class="chat-remove-attachment" onclick="window.taskChats[${this.taskId}].removeAttachment(${index})">✕</button>
|
||
`;
|
||
attachmentsContainer.appendChild(attachment);
|
||
});
|
||
}
|
||
|
||
removeAttachment(index) {
|
||
if (this.selectedFiles) {
|
||
this.selectedFiles.splice(index, 1);
|
||
this.handleFileSelect(this.selectedFiles);
|
||
}
|
||
}
|
||
|
||
formatFileSize(bytes) {
|
||
if (bytes === 0) return '0 Bytes';
|
||
const k = 1024;
|
||
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
|
||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
||
}
|
||
|
||
async loadMessages(before = null) {
|
||
if (this.isLoading) return;
|
||
|
||
this.isLoading = true;
|
||
const loadingEl = document.querySelector(`#chat-messages-list-${this.taskId} .chat-loading`);
|
||
if (loadingEl) loadingEl.style.display = 'block';
|
||
|
||
try {
|
||
let url = `/api/chat/tasks/${this.taskId}/messages?limit=30`;
|
||
if (before) {
|
||
url += `&before=${before}`;
|
||
}
|
||
|
||
const response = await fetch(url);
|
||
const data = await response.json();
|
||
|
||
if (data.messages && data.messages.length > 0) {
|
||
if (before) {
|
||
// Добавляем старые сообщения в начало
|
||
this.messages = [...data.messages.reverse(), ...this.messages];
|
||
} else {
|
||
// Первая загрузка
|
||
this.messages = data.messages.reverse();
|
||
}
|
||
this.hasMore = data.hasMore;
|
||
this.renderMessages();
|
||
|
||
if (!before) {
|
||
// Прокручиваем вниз при первой загрузке
|
||
this.scrollToBottom();
|
||
} else {
|
||
// Сохраняем позицию прокрутки при загрузке старых сообщений
|
||
const container = document.getElementById(`chat-messages-${this.taskId}`);
|
||
const oldHeight = container.scrollHeight;
|
||
setTimeout(() => {
|
||
container.scrollTop = container.scrollHeight - oldHeight;
|
||
}, 10);
|
||
}
|
||
} else {
|
||
if (!before) {
|
||
this.renderEmpty();
|
||
}
|
||
this.hasMore = false;
|
||
}
|
||
} catch (error) {
|
||
console.error('Ошибка загрузки сообщений:', error);
|
||
} finally {
|
||
this.isLoading = false;
|
||
if (loadingEl) loadingEl.style.display = 'none';
|
||
}
|
||
}
|
||
|
||
async loadMoreMessages() {
|
||
if (this.messages.length > 0) {
|
||
const oldestMessage = this.messages[0];
|
||
await this.loadMessages(oldestMessage.created_at);
|
||
}
|
||
}
|
||
|
||
renderMessages() {
|
||
const messagesList = document.getElementById(`chat-messages-list-${this.taskId}`);
|
||
messagesList.innerHTML = '<div class="chat-loading" style="display: none;">Загрузка сообщений...</div>';
|
||
|
||
if (this.messages.length === 0) {
|
||
this.renderEmpty();
|
||
return;
|
||
}
|
||
|
||
this.messages.forEach(message => {
|
||
const messageEl = this.createMessageElement(message);
|
||
messagesList.appendChild(messageEl);
|
||
});
|
||
}
|
||
|
||
createMessageElement(message) {
|
||
const isOwn = message.user_id === this.currentUserId;
|
||
const div = document.createElement('div');
|
||
div.className = `chat-message ${isOwn ? 'chat-message-own' : 'chat-message-other'}`;
|
||
div.dataset.messageId = message.id;
|
||
|
||
const time = new Date(message.created_at).toLocaleString('ru-RU', {
|
||
hour: '2-digit',
|
||
minute: '2-digit',
|
||
day: '2-digit',
|
||
month: '2-digit'
|
||
});
|
||
|
||
let replyHtml = '';
|
||
if (message.reply_to_message) {
|
||
replyHtml = `
|
||
<div class="chat-message-reply">
|
||
↪ Ответ ${message.reply_to_user_name}: "${message.reply_to_message.substring(0, 30)}${message.reply_to_message.length > 30 ? '...' : ''}"
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
let filesHtml = '';
|
||
if (message.files && message.files.length > 0) {
|
||
filesHtml = '<div class="chat-message-files">';
|
||
message.files.forEach(file => {
|
||
filesHtml += `
|
||
<div class="chat-file" onclick="window.taskChats[${this.taskId}].downloadFile(${file.id})">
|
||
📎 ${file.original_name} (${this.formatFileSize(file.file_size)})
|
||
</div>
|
||
`;
|
||
});
|
||
filesHtml += '</div>';
|
||
}
|
||
|
||
let actionsHtml = '';
|
||
if (isOwn || window.currentUserRole === 'admin') {
|
||
actionsHtml = `
|
||
<div class="chat-message-actions">
|
||
${isOwn ? `<button onclick="window.taskChats[${this.taskId}].editMessage(${message.id})" title="Редактировать">✎</button>` : ''}
|
||
<button onclick="window.taskChats[${this.taskId}].deleteMessage(${message.id})" title="Удалить">🗑️</button>
|
||
<!-- <button onclick="window.taskChats[${this.taskId}].replyToMessage(${message.id}, '${message.user_name.replace(/'/g, "\\'")}', '${message.message.replace(/'/g, "\\'").substring(0, 30)}')" title="Ответить">↩</button> -->
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
div.innerHTML = `
|
||
${replyHtml}
|
||
<div class="chat-message-header">
|
||
<span class="chat-message-author">${message.user_name}</span>
|
||
<span class="chat-message-time">${time}</span>
|
||
${message.is_edited ? '<span class="chat-message-edited">(ред.)</span>' : ''}
|
||
${actionsHtml}
|
||
</div>
|
||
<div class="chat-message-text">${this.escapeHtml(message.message)}</div>
|
||
${filesHtml}
|
||
`;
|
||
|
||
return div;
|
||
}
|
||
|
||
escapeHtml(text) {
|
||
const div = document.createElement('div');
|
||
div.textContent = text;
|
||
return div.innerHTML;
|
||
}
|
||
|
||
renderEmpty() {
|
||
const messagesList = document.getElementById(`chat-messages-list-${this.taskId}`);
|
||
messagesList.innerHTML = `
|
||
<div class="chat-no-messages">
|
||
💬 Нет сообщений. Напишите что-нибудь...
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
async sendMessage() {
|
||
const input = document.getElementById(`chat-input-${this.taskId}`);
|
||
const message = input.value.trim();
|
||
|
||
if (!message && (!this.selectedFiles || this.selectedFiles.length === 0)) {
|
||
return;
|
||
}
|
||
|
||
const formData = new FormData();
|
||
formData.append('message', message);
|
||
|
||
if (this.replyToMessage) {
|
||
formData.append('reply_to_id', this.replyToMessage.id);
|
||
}
|
||
|
||
if (this.selectedFiles && this.selectedFiles.length > 0) {
|
||
this.selectedFiles.forEach(file => {
|
||
formData.append('files', file);
|
||
});
|
||
}
|
||
|
||
try {
|
||
const response = await fetch(`/api/chat/tasks/${this.taskId}/messages`, {
|
||
method: 'POST',
|
||
body: formData
|
||
});
|
||
|
||
const data = await response.json();
|
||
|
||
if (data.success) {
|
||
input.value = '';
|
||
input.style.height = 'auto';
|
||
this.selectedFiles = [];
|
||
document.getElementById(`chat-attachments-${this.taskId}`).innerHTML = '';
|
||
this.cancelReply();
|
||
|
||
// Добавляем новое сообщение
|
||
this.messages.push(data.message);
|
||
this.renderMessages();
|
||
this.scrollToBottom();
|
||
}
|
||
} catch (error) {
|
||
console.error('Ошибка отправки сообщения:', error);
|
||
alert('Ошибка отправки сообщения');
|
||
}
|
||
}
|
||
|
||
async editMessage(messageId) {
|
||
const message = this.messages.find(m => m.id === messageId);
|
||
if (!message) return;
|
||
|
||
const newText = prompt('Редактировать сообщение:', message.message);
|
||
if (newText && newText.trim() !== message.message) {
|
||
try {
|
||
const response = await fetch(`/api/chat/messages/${messageId}`, {
|
||
method: 'PUT',
|
||
headers: {
|
||
'Content-Type': 'application/json'
|
||
},
|
||
body: JSON.stringify({ message: newText.trim() })
|
||
});
|
||
|
||
const data = await response.json();
|
||
|
||
if (data.success) {
|
||
message.message = newText.trim();
|
||
message.is_edited = true;
|
||
this.renderMessages();
|
||
}
|
||
} catch (error) {
|
||
console.error('Ошибка редактирования:', error);
|
||
alert('Ошибка редактирования сообщения');
|
||
}
|
||
}
|
||
}
|
||
|
||
async deleteMessage(messageId) {
|
||
if (!confirm('Удалить это сообщение?')) return;
|
||
|
||
try {
|
||
const response = await fetch(`/api/chat/messages/${messageId}`, {
|
||
method: 'DELETE'
|
||
});
|
||
|
||
const data = await response.json();
|
||
|
||
if (data.success) {
|
||
this.messages = this.messages.filter(m => m.id !== messageId);
|
||
this.renderMessages();
|
||
}
|
||
} catch (error) {
|
||
console.error('Ошибка удаления:', error);
|
||
alert('Ошибка удаления сообщения');
|
||
}
|
||
}
|
||
|
||
replyToMessage(messageId, userName, messagePreview) {
|
||
this.replyToMessage = {
|
||
id: messageId,
|
||
userName: userName,
|
||
preview: messagePreview
|
||
};
|
||
|
||
const replyInfo = document.getElementById(`chat-reply-info-${this.taskId}`);
|
||
document.getElementById(`reply-to-text-${this.taskId}`).textContent =
|
||
`${userName}: "${messagePreview}${messagePreview.length > 30 ? '...' : ''}"`;
|
||
replyInfo.style.display = 'flex';
|
||
|
||
document.getElementById(`chat-input-${this.taskId}`).focus();
|
||
}
|
||
|
||
cancelReply() {
|
||
this.replyToMessage = null;
|
||
document.getElementById(`chat-reply-info-${this.taskId}`).style.display = 'none';
|
||
}
|
||
|
||
async downloadFile(fileId) {
|
||
window.open(`/api/chat/files/${fileId}/download`, '_blank');
|
||
}
|
||
|
||
scrollToBottom() {
|
||
const container = document.getElementById(`chat-messages-${this.taskId}`);
|
||
setTimeout(() => {
|
||
container.scrollTop = container.scrollHeight;
|
||
}, 10);
|
||
}
|
||
|
||
setupAutoRefresh() {
|
||
// Обновляем непрочитанные каждые 10 секунд
|
||
this.autoRefreshInterval = setInterval(() => {
|
||
this.updateUnreadCount();
|
||
}, 10000);
|
||
}
|
||
|
||
async updateUnreadCount() {
|
||
try {
|
||
const response = await fetch(`/api/chat/tasks/${this.taskId}/unread-count`);
|
||
const data = await response.json();
|
||
|
||
const badge = document.getElementById(`chat-unread-${this.taskId}`);
|
||
if (data.unread_count > 0) {
|
||
badge.textContent = data.unread_count;
|
||
badge.style.display = 'inline';
|
||
} else {
|
||
badge.style.display = 'none';
|
||
}
|
||
} catch (error) {
|
||
console.error('Ошибка обновления непрочитанных:', error);
|
||
}
|
||
}
|
||
|
||
async markAllAsRead() {
|
||
try {
|
||
await fetch(`/api/chat/tasks/${this.taskId}/mark-read`, {
|
||
method: 'POST'
|
||
});
|
||
document.getElementById(`chat-unread-${this.taskId}`).style.display = 'none';
|
||
} catch (error) {
|
||
console.error('Ошибка отметки прочитанных:', error);
|
||
}
|
||
}
|
||
|
||
refreshMessages() {
|
||
this.messages = [];
|
||
this.loadMessages();
|
||
this.markAllAsRead();
|
||
}
|
||
|
||
close() {
|
||
if (this.autoRefreshInterval) {
|
||
clearInterval(this.autoRefreshInterval);
|
||
}
|
||
|
||
const modal = document.getElementById(`task-chat-modal-${this.taskId}`);
|
||
if (modal) {
|
||
modal.style.display = 'none';
|
||
setTimeout(() => {
|
||
modal.parentElement.remove();
|
||
}, 300);
|
||
}
|
||
}
|
||
}
|
||
|
||
// Глобальный объект для хранения экземпляров чатов
|
||
window.taskChats = window.taskChats || {};
|
||
|
||
// Функция для открытия чата (заменяет существующую openTaskChat)
|
||
function openTaskChat(taskId) {
|
||
// Находим задачу
|
||
const task = window.tasks?.find(t => t.id === taskId);
|
||
|
||
// Если уже есть открытый чат для этой задачи, просто показываем его
|
||
if (window.taskChats[taskId]) {
|
||
const existingModal = document.getElementById(`task-chat-modal-${taskId}`);
|
||
if (existingModal) {
|
||
existingModal.style.display = 'block';
|
||
window.taskChats[taskId].refreshMessages();
|
||
return;
|
||
}
|
||
}
|
||
|
||
// Создаем новый экземпляр чата
|
||
window.taskChats[taskId] = new TaskChat(taskId, task ? task.title : `Задача #${taskId}`);
|
||
}
|
||
|
||
// Функция для закрытия чата
|
||
function closeTaskChat(taskId) {
|
||
if (window.taskChats[taskId]) {
|
||
window.taskChats[taskId].close();
|
||
delete window.taskChats[taskId];
|
||
}
|
||
}
|
||
|
||
// Инициализация при загрузке страницы
|
||
document.addEventListener('DOMContentLoaded', () => {
|
||
// Получаем роль текущего пользователя для прав доступа
|
||
fetch('/api/user')
|
||
.then(response => response.json())
|
||
.then(data => {
|
||
window.currentUserRole = data.user.role;
|
||
})
|
||
.catch(error => console.error('Ошибка загрузки пользователя:', error));
|
||
}); |