чат
This commit is contained in:
817
public/chat-ui.js
Normal file
817
public/chat-ui.js
Normal file
@@ -0,0 +1,817 @@
|
||||
// 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));
|
||||
});
|
||||
@@ -449,6 +449,7 @@
|
||||
<script src="main.js"></script>
|
||||
<script src="tasks_files.js"></script>
|
||||
<script src="navbar.js"></script>
|
||||
<script src="chat-ui.js"></script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user