// Single chat view const ChatView = { chatId: null, otherId: null, otherName: null, ws: null, async render(root, params) { this.chatId = params.chatId; this.otherId = params.otherId; this.otherName = params.otherName; root.innerHTML = `
${escapeHtml(this.otherName || '')}
`; document.getElementById('chat-back').onclick = () => App.showView('chats'); document.getElementById('chat-profile').onclick = () => App.openUserProfile(this.otherId, this.otherName); document.getElementById('chat-review').onclick = () => App.openReview(this.chatId, this.otherId, this.otherName); document.getElementById('msg-input').addEventListener('keydown', (e) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); this.send(); } }); document.getElementById('msg-send').onclick = () => this.send(); this.loadMessages(); this.setupWS(); this.markRead(); }, async loadMessages() { try { const r = await API.ChatApi.listMessages(this.chatId); const box = document.getElementById('messages'); box.innerHTML = ''; r.messages.forEach((m) => this.appendMessage(m)); box.scrollTop = box.scrollHeight; } catch (e) { console.warn(e); } }, appendMessage(m) { const box = document.getElementById('messages'); const me = API.Store.user && m.sender_id === API.Store.user.id; const div = document.createElement('div'); div.className = 'bubble ' + (me ? 'mine' : 'theirs'); div.dataset.id = m.id; if (m.deleted) { div.innerHTML = 'Удалено'; } else { // ✓✓ если прочитано собеседником, ✓ если только доставлено const readMark = me ? (m.read_at ? '✓✓' : '') : ''; div.innerHTML = `
${escapeHtml(m.body)}
${formatTime(m.created_at)} ${m.edited ? 'ред.' : ''} ${readMark}
`; if (me) { div.onclick = () => this.onMessageClick(m); } } box.appendChild(div); box.scrollTop = box.scrollHeight; }, onMessageClick(m) { TG_APP.showConfirm('Удалить сообщение?').then((ok) => { if (ok) this.deleteMessage(m); }); }, async deleteMessage(m) { try { await API.ChatApi.deleteMessage(m.id); const el = document.querySelector(`.bubble[data-id="${m.id}"]`); if (el) el.innerHTML = 'Удалено'; } catch (e) { TG_APP.showAlert('Ошибка: ' + e.message); } }, setupWS() { if (!API.Store.access) return; const url = (window.API_BASE || 'https://api.buhapp.mygoodservice.ru').replace(/^http/, 'ws') + '/ws?token=' + API.Store.access; try { this.ws = new WebSocket(url); this.ws.onopen = () => console.log('WS connected'); this.ws.onmessage = (ev) => { try { const d = JSON.parse(ev.data); if (d.type === 'message' && d.payload.chat_id === this.chatId) { this.appendMessage(d.payload); } else if (d.type === 'message_edited' && d.payload.chat_id === this.chatId) { this.updateMessage(d.payload); } else if (d.type === 'message_read' && d.payload.chat_id === this.chatId) { this.markMessagesRead(); } } catch {} }; this.ws.onclose = () => setTimeout(() => this.setupWS(), 3000); this.ws.onerror = (e) => console.warn('WS error', e); } catch (e) { console.warn(e); } }, async markMessagesRead() { try { const r = await API.ChatApi.listMessages(this.chatId); r.messages.forEach((m) => { if (m.read_at) { const el = document.querySelector(`.bubble[data-id="${m.id}"] .read-tick`); if (el) { el.classList.add('read'); el.textContent = '✓✓'; } } }); } catch {} }, updateMessage(p) { const el = document.querySelector(`.bubble[data-id="${p.id}"]`); if (el) { el.querySelector('div:first-child') && (el.querySelector('div:first-child').textContent = p.body); const meta = el.querySelector('.meta'); if (meta && !meta.querySelector('.edited')) { const ed = document.createElement('span'); ed.className = 'edited'; ed.textContent = 'ред.'; meta.appendChild(ed); } } }, async send() { const inp = document.getElementById('msg-input'); const body = inp.value.trim(); if (!body) return; inp.value = ''; try { await API.ChatApi.sendMessage(this.chatId, body); } catch (e) { TG_APP.showAlert('Ошибка: ' + e.message); } }, async markRead() { try { await API.ChatApi.markRead(this.chatId); } catch {} }, cleanup() { if (this.ws) { this.ws.close(); this.ws = null; } }, }; window.ChatView = ChatView;