Update conversations.php

This commit is contained in:
Lizandro Guarnizo
2026-01-23 23:41:43 -05:00
parent 702423d2ae
commit bb2a53c842
+100 -5
View File
@@ -772,6 +772,10 @@
this.conversations = [];
// Track shown notifications to avoid duplicates from polling
this._shownNotifications = new Set();
// Track notifications the user dismissed/read locally so they don't reappear
this._dismissedNotifications = new Set();
// Map of pending ack notifications to retry marking as read (nid => notification)
this._pendingAck = new Map();
// Message pagination / loading state
this.messageLimit = 50;
this.loadingMessages = false;
@@ -793,6 +797,8 @@
this.setupEventListeners();
this.setupAutoRefresh();
this.setupNotificationPolling();
// start background retry loop to ack dismissed notifications on server
this.setupNotificationAckRetry && this.setupNotificationAckRetry();
}
setupNotificationPolling() {
@@ -820,6 +826,48 @@
}
}
// Try to mark a notification as read on the server. Returns true on success.
async ackNotification(notification) {
if (!notification || !notification.id) return true; // nothing to ack on server
try {
const resp = await fetch('api/mark_notification_read.php', {
method: 'POST',
headers: {'Content-Type':'application/json'},
body: JSON.stringify({ id: notification.id })
});
if (!resp.ok) {
console.warn('ackNotification: server responded with', resp.status);
return false;
}
const j = await resp.json().catch(() => null);
return !!(j && j.success) || resp.ok;
} catch (e) {
console.warn('ackNotification failed', e);
return false;
}
}
setupNotificationAckRetry() {
if (this._ackInterval) return;
this._ackInterval = setInterval(async () => {
if (this._pendingAck.size === 0) return;
for (const [nid, notification] of Array.from(this._pendingAck.entries())) {
try {
const ok = await this.ackNotification(notification);
if (ok) {
this._pendingAck.delete(nid);
this._dismissedNotifications.add(nid);
console.debug('Ack retry succeeded for', nid);
} else {
console.debug('Ack retry still failing for', nid);
}
} catch (e) {
console.warn('Ack retry error for', nid, e);
}
}
}, 30000); // every 30s
}
async showNotificationToast(notification) {
// Create or reuse container
const containerId = 'notification-toasts';
@@ -860,7 +908,7 @@
const actions = document.createElement('div');
actions.className = 'nt-actions';
const removeToast = () => {
const removeToastLocal = () => {
if (toast.parentNode) toast.remove();
if (this._shownNotifications.has(nid)) this._shownNotifications.delete(nid);
};
@@ -868,6 +916,33 @@
const openBtn = document.createElement('button');
openBtn.className = notification.cool ? 'btn btn-sm btn-light' : 'btn btn-sm btn-primary';
openBtn.textContent = 'Abrir';
openBtn.onclick = async () => {
// Try ack on server; if fails, schedule retry. Always mark as dismissed locally so it won't reappear.
try {
const ok = await this.ackNotification(notification);
if (ok) {
this._dismissedNotifications.add(nid);
this._pendingAck.delete(nid);
} else {
this._pendingAck.set(nid, notification);
}
} catch (e) { this._pendingAck.set(nid, notification); }
removeToastLocal();
// navigate to conv if present
let data = {};
try { data = notification.data ? JSON.parse(notification.data) : {}; } catch(e) {}
const userId = data.user_id || notification.user_id;
if (userId) {
const conv = this.conversations.find(c => c.user_id == userId);
if (conv) {
this.openConversation(conv.user_id, conv.name, conv.phone_number);
} else {
this.openConversation(userId, 'Usuario', '');
}
}
};
openBtn.className = notification.cool ? 'btn btn-sm btn-light' : 'btn btn-sm btn-primary';
openBtn.textContent = 'Abrir';
openBtn.onclick = async () => {
try {
await fetch('api/mark_notification_read.php', {method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify({id: notification.id})});
@@ -892,9 +967,15 @@
dismiss.title = 'Descartar';
dismiss.onclick = async () => {
try {
await fetch('api/mark_notification_read.php', {method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify({id: notification.id})});
} catch(e) { console.warn('mark_notification_read failed', e); }
removeToast();
const ok = await this.ackNotification(notification);
if (ok) {
this._dismissedNotifications.add(nid);
this._pendingAck.delete(nid);
} else {
this._pendingAck.set(nid, notification);
}
} catch(e) { this._pendingAck.set(nid, notification); }
removeToastLocal();
};
actions.appendChild(openBtn);
@@ -908,7 +989,21 @@
let timeout = notification.duration ? Number(notification.duration) : (notification.cool ? 15000 : 8000);
if (!isFinite(timeout) || timeout <= 0) timeout = (notification.cool ? 15000 : 8000);
timeout = Math.max(timeout, _minToastDuration);
setTimeout(removeToast, timeout);
setTimeout(() => {
// on automatic timeout, attempt ack and schedule retry if needed
(async () => {
try {
const ok = await this.ackNotification(notification);
if (ok) {
this._dismissedNotifications.add(nid);
this._pendingAck.delete(nid);
} else {
this._pendingAck.set(nid, notification);
}
} catch (e) { this._pendingAck.set(nid, notification); }
removeToastLocal();
})();
}, timeout);
}
setupEventListeners() {