This commit is contained in:
Lizandro Guarnizo
2026-01-23 22:12:27 -05:00
parent d9942456c8
commit 2017c8c805
8 changed files with 420 additions and 110 deletions
+159 -37
View File
@@ -448,6 +448,15 @@
<input type="text" class="form-control border-0" placeholder="Buscar conversaciones..." id="search-input">
</div>
</div>
<!-- Filtro de conversaciones: Todos / No leídos -->
<div style="padding:8px 12px; display:flex; gap:8px; align-items:center;">
<select id="conversation-filter" class="form-select form-select-sm" style="width:auto;">
<option value="all">Todos</option>
<option value="unread">No leídos</option>
</select>
<small class="text-muted">Mostrar sólo conversaciones con mensajes no leídos</small>
</div>
<div class="conversation-list" id="conversation-list">
<div class="loading">
@@ -488,6 +497,7 @@
<div id="bot-toggle-container" style="display:flex;align-items:center;gap:6px;margin-right:8px;">
<button class="btn btn-sm btn-outline-secondary" id="bot-toggle">Bot: On</button>
</div>
<button class="btn btn-sm btn-outline-secondary" id="mark-unread-btn" title="Marcar conversación como no leída" style="display:none;">Marcar no leído</button>
<button class="btn btn-sm btn-outline-danger" id="delete-conversation-btn" title="Eliminar conversación"><i class="fas fa-trash"></i></button>
</div>
</div>
@@ -537,7 +547,14 @@
this.currentConversationId = null;
this.currentUserId = null;
this.conversations = [];
// Pagination state
// Message pagination / loading state
this.messageLimit = 50;
this.loadingMessages = false;
this.hasMoreMessages = false;
this.earliestMessage = null; // timestamp of earliest loaded message
// Filter state: 'all' or 'unread'
this.conversationFilter = 'all';
// Pagination state for conversation list
this.conversationsPage = 1;
this.conversationsLimit = 50;
this.hasMoreConversations = true;
@@ -692,6 +709,17 @@
}
});
}
// Filtro: Todos / No leídos
const filterSelect = document.getElementById('conversation-filter');
if (filterSelect) {
filterSelect.value = this.conversationFilter;
filterSelect.addEventListener('change', (e) => {
this.conversationFilter = e.target.value || 'all';
// reload conversations from first page
this.loadConversations(1, false);
});
}
}
setupAutoRefresh() {
@@ -715,7 +743,7 @@
const loadMoreBtn = document.getElementById('load-more-btn');
if (loadMoreBtn) loadMoreBtn.disabled = true;
try {
const url = `api/get_conversations.php?page=${page}&limit=${this.conversationsLimit}`;
const url = `api/get_conversations.php?page=${page}&limit=${this.conversationsLimit}&filter=${encodeURIComponent(this.conversationFilter)}`;
const resp = await fetch(url);
const data = await resp.json();
console.log('Respuesta get_conversations:', data); // Debug
@@ -769,10 +797,11 @@
const container = document.getElementById('conversation-list');
if (this.conversations.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">
<i class="fas fa-comments fa-3x text-muted mb-3"></i>
<p class="text-muted">No hay conversaciones aún</p>
<p class="text-muted">${emptyMsg}</p>
</div>
`;
return;
@@ -901,10 +930,35 @@
const btn = document.getElementById('bot-toggle');
const holdIndicator = document.getElementById('hold-indicator');
const releaseBtn = document.getElementById('release-hold-btn');
const markUnreadBtn = document.getElementById('mark-unread-btn');
if (holdIndicator) {
// Show different labels depending on state
if (conv.on_hold) {
if (markUnreadBtn) {
markUnreadBtn.style.display = 'inline-block';
markUnreadBtn.onclick = async () => {
try {
const resp = await fetch('api/mark_conversation_unread.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ user_id: userId })
});
if (resp.status === 401) {
alert('Sesión expirada. Por favor inicie sesión de nuevo.');
return;
}
const json = await resp.json();
if (json && json.success) {
// Recargar lista de conversaciones para reflejar cambios
await this.loadConversations();
alert('Conversación marcada como NO leída.');
} else {
alert('Error marcando conversación como no leída');
}
} catch (e) {
console.error('Error marking conversation unread', e);
alert('Error marcando conversación como no leída');
}
};
}
holdIndicator.textContent = 'EN ESPERA';
holdIndicator.style.color = '#b85';
holdIndicator.style.display = 'inline';
@@ -1051,13 +1105,26 @@
}
}
// Cargar mensajes
await this.loadconversations(userId);
// Cargar mensajes (con paginación)
await this.loadMessages(userId, true);
// Añadir listener para scroll arriba (cargar más historial)
const chatContainer = document.getElementById('chat-conversations');
if (chatContainer) {
if (!chatContainer._infiniteScrollAdded) {
chatContainer.addEventListener('scroll', async () => {
if (chatContainer.scrollTop <= 60 && this.hasMoreMessages && !this.loadingMessages && this.currentUserId == userId) {
await this.loadMessages(userId, false);
}
});
chatContainer._infiniteScrollAdded = true;
}
}
// Cargar respuestas rápidas
await this.loadQuickReplies();
}
async loadconversations(userId, showLoading = true) {
// Compatibilidad: ahora delegamos en loadMessages
if (showLoading) {
document.getElementById('chat-conversations').innerHTML = `
<div class="loading">
@@ -1067,36 +1134,9 @@
}
try {
// Usar apiCall para incluir debug=true y manejo de auth/errores
const data = await this.apiCall(`get_user_conversations.php?user_id=${userId}`);
let messages = [];
if (!data) {
throw new Error('No autorizado o error en la petición');
}
if (Array.isArray(data)) {
messages = data;
} else if (data && data.success && Array.isArray(data.data)) {
messages = data.data;
} else if (Array.isArray(data.conversations)) {
messages = data.conversations;
}
this.conversations = messages;
this.renderconversations();
this.scrollToBottom();
// Marcar como leídos en el backend
try {
await this.apiCall('mark_conversation_read.php', { method: 'POST', body: { user_id: userId } });
// Recargar lista de conversaciones para refrescar contadores
await this.loadConversations();
} catch (e) {
console.error('Error marking conversation as read', e);
}
await this.loadMessages(userId, true);
} catch (error) {
console.error('Error loading conversations:', error);
console.error('Error loading conversations via loadMessages:', error);
document.getElementById('chat-conversations').innerHTML = `
<div class="text-center p-4">
<i class="fas fa-exclamation-triangle text-warning"></i>
@@ -1106,6 +1146,88 @@
}
}
/**
* Cargar mensajes con paginación. Si initial=true, carga el bloque más reciente; si initial=false, carga mensajes anteriores (before=this.earliestMessage).
*/
async loadMessages(userId, initial = false) {
if (this.loadingMessages) return;
this.loadingMessages = true;
const container = document.getElementById('chat-conversations');
// si cargamos más antiguos, preservar scroll
let prevScrollHeight = container ? container.scrollHeight : 0;
let prevScrollTop = container ? container.scrollTop : 0;
// indicador top cuando cargamos anteriores
let topLoader = null;
if (!initial && container) {
container.insertAdjacentHTML('afterbegin', `<div class="loading loading-top" style="text-align:center; padding:8px; font-size:12px;">Cargando mensajes anteriores...</div>`);
topLoader = container.querySelector('.loading-top');
}
try {
let url = `get_user_conversations.php?user_id=${userId}&limit=${this.messageLimit}`;
if (!initial && this.earliestMessage) {
url += `&before=${encodeURIComponent(this.earliestMessage)}`;
}
const data = await this.apiCall(url);
if (!data) throw new Error('No autorizado o error en la petición');
let messages = [];
let hasMore = false;
let earliest = null;
if (data && data.success && Array.isArray(data.data)) {
messages = data.data;
hasMore = !!data.has_more;
earliest = data.earliest || (messages[0] && messages[0].created_at) || null;
} else if (Array.isArray(data)) {
messages = data;
}
if (initial) {
this.conversations = messages;
} else {
// Prepend mensajes antiguos
this.conversations = messages.concat(this.conversations);
}
// Actualizar estado de paginación
this.hasMoreMessages = hasMore;
if (earliest) this.earliestMessage = earliest;
// Renderizar y ajustar scroll
this.renderconversations();
if (initial) {
this.scrollToBottom();
} else if (container) {
// Mantener posición: desplazar por la diferencia de heights
const newScrollHeight = container.scrollHeight;
container.scrollTop = newScrollHeight - prevScrollHeight + prevScrollTop;
}
// remover loader top si existía
if (topLoader && topLoader.parentNode) topLoader.remove();
// Marcar como leídos (comportamiento previo: marcar todos los entrantes como leídos al abrir)
if (initial) {
try {
await this.apiCall('mark_conversation_read.php', { method: 'POST', body: { user_id: userId } });
// Recargar lista de conversaciones para refrescar contadores
await this.loadConversations();
} catch (e) {
console.error('Error marking conversation as read', e);
}
}
} catch (error) {
console.error('Error en loadMessages:', error);
} finally {
this.loadingMessages = false;
}
}
renderconversations() {
const container = document.getElementById('chat-conversations');