Update conversations.php
This commit is contained in:
+55
-45
@@ -994,7 +994,8 @@
|
||||
constructor() {
|
||||
this.currentConversationId = null;
|
||||
this.currentUserId = null;
|
||||
this.conversations = [];
|
||||
this.conversationsList = []; // Lista de usuarios/conversaciones en el sidebar
|
||||
this.currentMessages = []; // Mensajes de la conversación activa
|
||||
// Track shown notifications to avoid duplicates from polling
|
||||
this._shownNotifications = new Set();
|
||||
// Track notifications the user dismissed/read locally so they don't reappear
|
||||
@@ -1213,7 +1214,7 @@
|
||||
if (fileUrl && this.currentUserId && String(this.currentUserId) === String(userId)) {
|
||||
try { openMediaLightbox(fileUrl, data.file_name || ''); } catch (e) { console.warn('openMediaLightbox failed', e); }
|
||||
} else if (userId) {
|
||||
const conv = this.conversations.find(c => c.user_id == userId);
|
||||
const conv = this.conversationsList.find(c => c.user_id == userId);
|
||||
if (conv) {
|
||||
// Prefetch messages then open conversation using preloaded data to avoid double-fetch
|
||||
try {
|
||||
@@ -1480,7 +1481,7 @@
|
||||
const j = await resp.json();
|
||||
if (j && j.success) {
|
||||
// update local model and UI
|
||||
const m = this.conversations.find(x => x.message_id == mid || x.id == mid);
|
||||
const m = this.currentMessages.find(x => x.message_id == mid || x.id == mid);
|
||||
if (m) m.reaction_emoji = emoji;
|
||||
this.updateMessageReactionInView && this.updateMessageReactionInView(mid, emoji);
|
||||
} else {
|
||||
@@ -1944,12 +1945,12 @@
|
||||
for (const m of messages) {
|
||||
try {
|
||||
// skip duplicates
|
||||
const exists = this.conversations && this.conversations.some(x => (x.message_id && m.message_id && String(x.message_id) === String(m.message_id)) || (x.id && m.id && Number(x.id) === Number(m.id)));
|
||||
const exists = this.currentMessages && this.currentMessages.some(x => (x.message_id && m.message_id && String(x.message_id) === String(m.message_id)) || (x.id && m.id && Number(x.id) === Number(m.id)));
|
||||
if (exists) continue;
|
||||
|
||||
if (nearBottomNow && !this._userScrolling) {
|
||||
// append directly and mark for render
|
||||
this.conversations.push(m);
|
||||
this.currentMessages.push(m);
|
||||
appended++;
|
||||
} else {
|
||||
// stage for later (user is reading history)
|
||||
@@ -2072,9 +2073,9 @@
|
||||
}
|
||||
|
||||
if (append) {
|
||||
this.conversations = this.conversations.concat(items);
|
||||
this.conversationsList = this.conversationsList.concat(items);
|
||||
} else {
|
||||
this.conversations = items;
|
||||
this.conversationsList = items;
|
||||
}
|
||||
|
||||
this.hasMoreConversations = hasMore;
|
||||
@@ -2103,7 +2104,7 @@
|
||||
renderConversations() {
|
||||
const container = document.getElementById('conversation-list');
|
||||
|
||||
if (this.conversations.length === 0) {
|
||||
if (this.conversationsList.length === 0) {
|
||||
const emptyMsg = this.conversationFilter === 'unread' ? 'No hay conversaciones no leídas' : 'No hay conversaciones aún';
|
||||
container.innerHTML = `
|
||||
<div class="text-center p-4">
|
||||
@@ -2115,7 +2116,7 @@
|
||||
}
|
||||
|
||||
// Ahora el backend devuelve por usuario: last_message, last_time, unread_count y avatar_url
|
||||
const html = this.conversations.map(conv => {
|
||||
const html = this.conversationsList.map(conv => {
|
||||
const isActive = conv.user_id == this.currentUserId ? 'active' : '';
|
||||
const time = this.formatTime(conv.last_time);
|
||||
const preview = this.truncateText(conv.last_message || 'Sin mensajes', 50);
|
||||
@@ -2149,7 +2150,7 @@
|
||||
|
||||
// Detectar nuevas notificaciones: comparar unread counts previos
|
||||
if (!this._prevConversations) this._prevConversations = {};
|
||||
this.conversations.forEach(c => {
|
||||
this.conversationsList.forEach(c => {
|
||||
const prev = this._prevConversations[c.user_id] || { unread_count: 0 };
|
||||
if (c.unread_count > prev.unread_count && c.user_id != this.currentUserId) {
|
||||
// nueva notificación
|
||||
@@ -2250,7 +2251,7 @@
|
||||
}
|
||||
|
||||
getConversationPhone(userId) {
|
||||
const conv = this.conversations.find(c => c.user_id === userId);
|
||||
const conv = this.conversationsList.find(c => c.user_id === userId);
|
||||
if (conv) return conv.phone_number || conv.phone || conv.user_phone || null;
|
||||
const el = document.getElementById('chat-phone');
|
||||
return el ? el.textContent.trim() : null;
|
||||
@@ -2296,7 +2297,7 @@
|
||||
document.querySelector(`[data-user-id="${userId}"]`)?.classList.add('active');
|
||||
|
||||
// Actualizar estado del toggle del bot según datos de la conversación
|
||||
const conv = this.conversations.find(c => c.user_id === userId);
|
||||
const conv = this.conversationsList.find(c => c.user_id === userId);
|
||||
if (conv) {
|
||||
const btn = document.getElementById('bot-toggle');
|
||||
const holdIndicator = document.getElementById('hold-indicator');
|
||||
@@ -2413,7 +2414,7 @@
|
||||
attendStatus.textContent = '';
|
||||
|
||||
// refresh conv reference
|
||||
const c = this.conversations.find(x => x.user_id === userId) || conv;
|
||||
const c = this.conversationsList.find(x => x.user_id === userId) || conv;
|
||||
|
||||
// Update only the in-chat status message based on server/local state
|
||||
this.getUserState(userId).then(serverState => {
|
||||
@@ -2434,7 +2435,7 @@
|
||||
|
||||
const toggleAttend = async () => {
|
||||
try {
|
||||
const c = this.conversations.find(x => x.user_id === userId) || conv;
|
||||
const c = this.conversationsList.find(x => x.user_id === userId) || conv;
|
||||
|
||||
// Preflight: obtener estado canonico del servidor con cache corta
|
||||
const serverState = await this.getUserState(userId, true);
|
||||
@@ -2728,9 +2729,10 @@
|
||||
this._fullLoadInProgress = true;
|
||||
const container = document.getElementById('chat-conversations');
|
||||
|
||||
// si cargamos más antiguos, preservar scroll
|
||||
// Guardar posición de scroll y determinar si el usuario estaba en el fondo
|
||||
let prevScrollHeight = container ? container.scrollHeight : 0;
|
||||
let prevScrollTop = container ? container.scrollTop : 0;
|
||||
const wasAtBottom = container ? ((container.scrollHeight - container.scrollTop - container.clientHeight) < 150) : true;
|
||||
|
||||
// indicador top cuando cargamos anteriores
|
||||
let topLoader = null;
|
||||
@@ -2812,7 +2814,7 @@
|
||||
}
|
||||
|
||||
// Si es carga inicial y el servidor devolvió vacío, pero ya teníamos mensajes, preservarlos.
|
||||
if (initial && Array.isArray(messages) && messages.length === 0 && Array.isArray(this.conversations) && this.conversations.length > 0) {
|
||||
if (initial && Array.isArray(messages) && messages.length === 0 && Array.isArray(this.currentMessages) && this.currentMessages.length > 0) {
|
||||
try { if (topLoader && topLoader.parentNode) topLoader.remove(); } catch(e) {}
|
||||
// Ensure container visibility is restored if it was hidden during render
|
||||
try { if (container && container.dataset && container.dataset.renderHidden) { container.style.visibility = 'visible'; delete container.dataset.renderHidden; } } catch(e) {}
|
||||
@@ -2824,13 +2826,13 @@
|
||||
|
||||
if (initial) {
|
||||
// Reemplazar sólo en carga inicial válida (puede estar vacía si no hay nada en DB)
|
||||
this.conversations = messages;
|
||||
this.currentMessages = messages;
|
||||
} else {
|
||||
// Prepend sólo si hay mensajes nuevos
|
||||
if (Array.isArray(messages) && messages.length > 0) {
|
||||
// Evitar introducir duplicados que ya existen en la vista actual
|
||||
try {
|
||||
const existingKeys = new Set((this.conversations || []).map(m => this.messageKey(m)));
|
||||
const existingKeys = new Set((this.currentMessages || []).map(m => this.messageKey(m)));
|
||||
const beforeLen = messages.length;
|
||||
messages = messages.filter(m => {
|
||||
const k = this.messageKey(m);
|
||||
@@ -2848,7 +2850,7 @@
|
||||
} catch (e) { console.warn('duplicate filtering failed', e); }
|
||||
|
||||
// Prepend mensajes antiguos
|
||||
this.conversations = messages.concat(this.conversations || []);
|
||||
this.currentMessages = messages.concat(this.currentMessages || []);
|
||||
} else {
|
||||
// No hay mensajes nuevos para añadir; nada que hacer
|
||||
}
|
||||
@@ -2860,15 +2862,15 @@
|
||||
|
||||
// Update last/latest message timestamp so we can poll deltas later
|
||||
try {
|
||||
if (Array.isArray(this.conversations) && this.conversations.length > 0) {
|
||||
const last = this.conversations[this.conversations.length - 1];
|
||||
if (Array.isArray(this.currentMessages) && this.currentMessages.length > 0) {
|
||||
const last = this.currentMessages[this.currentMessages.length - 1];
|
||||
if (last && last.created_at) this._latestMessage = last.created_at;
|
||||
}
|
||||
} catch(e) { /* ignore */ }
|
||||
|
||||
// Improved dedupe: pick the most "readable" message when duplicates exist
|
||||
try {
|
||||
if (!this.conversations || this.conversations.length === 0) {
|
||||
if (!this.currentMessages || this.currentMessages.length === 0) {
|
||||
// Nothing to dedupe
|
||||
} else {
|
||||
const best = new Map(); // key => { index, score }
|
||||
@@ -2897,8 +2899,8 @@
|
||||
return s;
|
||||
};
|
||||
|
||||
for (let i = this.conversations.length - 1; i >= 0; i--) {
|
||||
const m = this.conversations[i];
|
||||
for (let i = this.currentMessages.length - 1; i >= 0; i--) {
|
||||
const m = this.currentMessages[i];
|
||||
if (!m) continue;
|
||||
const mid = m.message_id || m.id || null;
|
||||
let key = null;
|
||||
@@ -2931,7 +2933,7 @@
|
||||
// remove from highest index down
|
||||
for (let j = uniq.length - 1; j >= 0; j--) {
|
||||
const idx = uniq[j];
|
||||
const removed = this.conversations.splice(idx, 1)[0];
|
||||
const removed = this.currentMessages.splice(idx, 1)[0];
|
||||
console.debug('Dedupe removed message at index', idx, 'removed=', removed && (removed.message_id || removed.id || removed.content || removed.media_url) );
|
||||
}
|
||||
}
|
||||
@@ -2953,7 +2955,7 @@
|
||||
// Renderizar incrementalmente para evitar parpadeos y preservar reproducción de media
|
||||
// Si cargamos paginación (initial=false), pasar la cantidad de mensajes recién obtenidos para insertarlos al inicio
|
||||
const newlyFetched = (!initial && Array.isArray(messages)) ? messages.length : 0;
|
||||
if (this.conversations && this.conversations.length > 0) {
|
||||
if (this.currentMessages && this.currentMessages.length > 0) {
|
||||
this.renderMessagesIncremental(initial, newlyFetched, follow);
|
||||
} else if (initial) {
|
||||
// Si es carga inicial y no tenemos mensajes, mostrar una vista vacía
|
||||
@@ -2967,8 +2969,17 @@
|
||||
this._fullLoadInProgress = false;
|
||||
|
||||
if (initial) {
|
||||
// Scroll to bottom only if the caller requested following behavior
|
||||
if (follow) { this.scrollToBottom(); }
|
||||
// Scroll to bottom SOLO si: (1) follow fue solicitado Y (2) el usuario estaba en el fondo O (3) es el primer mensaje
|
||||
const shouldScrollToBottom = follow && (wasAtBottom || this.currentMessages.length === messages.length);
|
||||
if (shouldScrollToBottom) {
|
||||
this.scrollToBottom();
|
||||
} else {
|
||||
// Preservar la posición relativa del scroll
|
||||
if (container && prevScrollHeight > 0) {
|
||||
const scrollPercentage = prevScrollTop / prevScrollHeight;
|
||||
container.scrollTop = container.scrollHeight * scrollPercentage;
|
||||
}
|
||||
}
|
||||
// Re-show the container (we hid it before rendering to avoid jump-to-top)
|
||||
try {
|
||||
if (container && container.dataset && container.dataset.renderHidden) {
|
||||
@@ -3043,10 +3054,10 @@
|
||||
|
||||
// Safety: remove any empty text-only messages that may have slipped into the model
|
||||
try {
|
||||
const before = this.conversations ? this.conversations.length : 0;
|
||||
if (Array.isArray(this.conversations) && this.conversations.length > 0) {
|
||||
this.conversations = this.conversations.filter(m => !this.isEmptyMessage(m));
|
||||
const removed = before - this.conversations.length;
|
||||
const before = this.currentMessages ? this.currentMessages.length : 0;
|
||||
if (Array.isArray(this.currentMessages) && this.currentMessages.length > 0) {
|
||||
this.currentMessages = this.currentMessages.filter(m => !this.isEmptyMessage(m));
|
||||
const removed = before - this.currentMessages.length;
|
||||
if (removed > 0 && console && console.info) console.info('Removed', removed, 'empty messages before render');
|
||||
}
|
||||
} catch (e) { console.warn('Failed to trim empty messages before render', e); }
|
||||
@@ -3106,8 +3117,8 @@
|
||||
|
||||
// calculate near-bottom late to decide scrolling after DOM updates (avoids stale value)
|
||||
|
||||
for (let i = 0; i < this.conversations.length; i++) {
|
||||
const msg = this.conversations[i];
|
||||
for (let i = 0; i < this.currentMessages.length; i++) {
|
||||
const msg = this.currentMessages[i];
|
||||
const mid = String(msg.message_id || msg.id || ('local_' + i));
|
||||
const existingEl = existing.get(mid);
|
||||
|
||||
@@ -3126,7 +3137,7 @@
|
||||
try {
|
||||
const rp = existingEl.querySelector('.reply-preview');
|
||||
if (msg.reply_to_message_id) {
|
||||
const previewSrc = (this.conversations.find(m => m.message_id == msg.reply_to_message_id) || {}).content || ('Mensaje ' + msg.reply_to_message_id);
|
||||
const previewSrc = (this.currentMessages.find(m => m.message_id == msg.reply_to_message_id) || {}).content || ('Mensaje ' + msg.reply_to_message_id);
|
||||
if (rp) rp.textContent = 'En respuesta a: ' + previewSrc.substring(0,140);
|
||||
else {
|
||||
const div = document.createElement('div'); div.className = 'reply-preview'; div.textContent = 'En respuesta a: ' + previewSrc.substring(0,140); existingEl.insertBefore(div, existingEl.firstChild);
|
||||
@@ -3249,7 +3260,7 @@
|
||||
} catch (e) { /* ignore staging errors */ }
|
||||
|
||||
// insert date separator if this message is the first of a new day
|
||||
const prev = (i>0) ? this.conversations[i-1] : null;
|
||||
const prev = (i>0) ? this.currentMessages[i-1] : null;
|
||||
if (!prev || !sameDay(prev.created_at, msg.created_at)) {
|
||||
const sep = document.createElement('div');
|
||||
sep.className = 'date-sep';
|
||||
@@ -3266,7 +3277,7 @@
|
||||
|
||||
let replyHtml = '';
|
||||
if (msg.reply_to_message_id) {
|
||||
const target = this.conversations.find(m => m.message_id == msg.reply_to_message_id || m.id == msg.reply_to_message_id);
|
||||
const target = this.currentMessages.find(m => m.message_id == msg.reply_to_message_id || m.id == msg.reply_to_message_id);
|
||||
const previewText = target ? (target.content || target.message_text || '').substring(0,140) : ('Mensaje ' + msg.reply_to_message_id);
|
||||
replyHtml = `<div class="reply-preview">En respuesta a: ${window.escapeHtml(previewText)}</div>`;
|
||||
}
|
||||
@@ -3748,7 +3759,7 @@
|
||||
// Append staged messages to conversation model
|
||||
const toApplyCount = this._stagedMessages.length;
|
||||
console.debug('applyStagedMessages: applying', toApplyCount, 'messages');
|
||||
this._stagedMessages.forEach(m => { this.conversations.push(m); });
|
||||
this._stagedMessages.forEach(m => { this.currentMessages.push(m); });
|
||||
|
||||
// clear staged storage
|
||||
this._stagedMessages = [];
|
||||
@@ -3760,7 +3771,7 @@
|
||||
this.scrollToBottom();
|
||||
|
||||
// Update latest timestamp
|
||||
try { const last = this.conversations[this.conversations.length - 1]; if (last && last.created_at) this._latestMessage = last.created_at; } catch(e){}
|
||||
try { const last = this.currentMessages[this.currentMessages.length - 1]; if (last && last.created_at) this._latestMessage = last.created_at; } catch(e){}
|
||||
|
||||
} catch (e) {
|
||||
console.warn('applyStagedMessages failed', e);
|
||||
@@ -3901,7 +3912,7 @@
|
||||
// If the template expects a named parameter `name`, provide it using user name or phone
|
||||
if (templateName === 'contacto_nuevo') {
|
||||
// Try to find conversation info
|
||||
let conv = this.conversations.find(c => c.user_id === this.currentUserId) || {};
|
||||
let conv = this.conversationsList.find(c => c.user_id === this.currentUserId) || {};
|
||||
let resolvedName = (conv.name || conv.full_name || '').trim();
|
||||
if (!resolvedName) {
|
||||
const nameEl = document.getElementById('chat-name-text') || document.getElementById('chat-name');
|
||||
@@ -3982,7 +3993,7 @@
|
||||
msg.message_type = options.message_type;
|
||||
}
|
||||
|
||||
this.conversations.push(msg);
|
||||
this.currentMessages.push(msg);
|
||||
this.renderMessagesIncremental();
|
||||
this.scrollToBottom();
|
||||
}
|
||||
@@ -3992,7 +4003,7 @@
|
||||
const preview = document.getElementById('reply-preview');
|
||||
const previewText = document.getElementById('reply-preview-text');
|
||||
const cancelBtn = document.getElementById('cancel-reply-btn');
|
||||
let msg = this.conversations.find(m => m.message_id == messageId || m.id == messageId);
|
||||
let msg = this.currentMessages.find(m => m.message_id == messageId || m.id == messageId);
|
||||
|
||||
const setPreviewFromMsg = (m) => {
|
||||
const text = m ? (m.content || m.message_text || (m.caption || '[Mensaje]')) : ('Mensaje ' + messageId);
|
||||
@@ -4317,11 +4328,10 @@
|
||||
}
|
||||
|
||||
// 2. Enviar mensaje con el archivo
|
||||
const user = this.conversations.find(c => c.user_id === this.currentUserId);
|
||||
const phone = user ? user.phone_number : null;
|
||||
const phone = this.getConversationPhone(this.currentUserId);
|
||||
|
||||
if (!phone) {
|
||||
throw new Error('No se encontró el número de teléfono');
|
||||
throw new Error('No se encontró el número de teléfono del usuario');
|
||||
}
|
||||
|
||||
const sendResponse = await fetch('api/send_media_message.php', {
|
||||
|
||||
Reference in New Issue
Block a user