From dc5fd6e2724b7d213efb63d20f4435a04ef6a59c Mon Sep 17 00:00:00 2001 From: Lizandro Guarnizo <77708265+lizandrogd@users.noreply.github.com> Date: Fri, 24 Jul 2026 09:58:08 -0500 Subject: [PATCH] feat(chat): separadores de fecha estilo WhatsApp MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Muestra "Hoy", "Ayer" o la fecha entre grupos de mensajes de días distintos. El timestamp del bubble ahora solo muestra la hora. Co-Authored-By: Claude Sonnet 4.6 --- modules/turnero/views/chat.php | 70 ++++++++++++++++++++++++++++++---- 1 file changed, 62 insertions(+), 8 deletions(-) diff --git a/modules/turnero/views/chat.php b/modules/turnero/views/chat.php index be38f4f..8c16ae4 100644 --- a/modules/turnero/views/chat.php +++ b/modules/turnero/views/chat.php @@ -152,6 +152,11 @@ html, body { height: 100%; overflow: hidden; font-family: -apple-system, BlinkMa .msg-time { font-size: .68rem; color: #aaa; text-align: right; margin-top: 3px; } +.date-sep { + align-self: center; background: #e9ecef; color: #6c757d; + font-size: .73rem; font-weight: 600; padding: 3px 12px; + border-radius: 99px; margin: 10px 0; user-select: none; +} .msg-type-badge { font-size: .7rem; color: #888; font-style: italic; } @@ -743,6 +748,8 @@ async function pollMessages() { } // ── Render mensajes ─────────────────────────────────────────────────────────── +let _lastRenderedDay = null; + function renderMessages(msgs, prepend) { const area = document.getElementById('messagesArea'); area.innerHTML = ''; @@ -755,7 +762,16 @@ function renderMessages(msgs, prepend) { loadBtn.innerHTML = ' Ver mensajes anteriores'; area.appendChild(loadBtn); - msgs.forEach(m => area.appendChild(buildBubble(m))); + let lastDay = null; + msgs.forEach(m => { + const day = m.created_at ? msgDay(m.created_at) : null; + if (day && day !== lastDay) { + area.appendChild(buildDateSep(dateLabel(m.created_at))); + lastDay = day; + } + area.appendChild(buildBubble(m)); + }); + _lastRenderedDay = lastDay; area.scrollTop = area.scrollHeight; } @@ -763,15 +779,39 @@ function prependMessages(msgs) { const area = document.getElementById('messagesArea'); const oldTop = area.scrollHeight - area.scrollTop; const loadBtn = document.getElementById('loadMoreBtn'); - msgs.forEach(m => area.insertBefore(buildBubble(m), loadBtn.nextSibling)); + // Día del primer mensaje que ya estaba en pantalla (para no duplicar separador) + const firstBubble = loadBtn.nextSibling; + const firstExistingDay = firstBubble?.dataset?.msgId + ? msgDay(msgs[msgs.length - 1]?.created_at || '') // comparar con el último del lote nuevo + : null; + let lastDay = null; + let insertRef = loadBtn.nextSibling; + msgs.forEach(m => { + const day = m.created_at ? msgDay(m.created_at) : null; + if (day && day !== lastDay) { + area.insertBefore(buildDateSep(dateLabel(m.created_at)), insertRef); + lastDay = day; + } + area.insertBefore(buildBubble(m), insertRef); + insertRef = area.querySelector(`[data-msg-id="${m.id}"]`)?.nextSibling || insertRef; + }); + // Eliminar separador duplicado si el último día del lote coincide con el primero ya visible + const seps = area.querySelectorAll('.date-sep'); + for (let i = 0; i < seps.length - 1; i++) { + if (seps[i].textContent === seps[i+1].textContent) seps[i+1].remove(); + } if (!state.hasMore) loadBtn.style.display = 'none'; area.scrollTop = area.scrollHeight - oldTop; } function appendMessage(msg) { - const area = document.getElementById('messagesArea'); - const bubble = buildBubble(msg); - area.appendChild(bubble); + const area = document.getElementById('messagesArea'); + const day = msg.created_at ? msgDay(msg.created_at) : null; + if (day && day !== _lastRenderedDay) { + area.appendChild(buildDateSep(dateLabel(msg.created_at))); + _lastRenderedDay = day; + } + area.appendChild(buildBubble(msg)); area.scrollTop = area.scrollHeight; } @@ -1415,6 +1455,22 @@ function escHtml(str) { return String(str ?? '').replace(/&/g,'&').replace(//g,'>').replace(/"/g,'"'); } function nl2br(str) { return str.replace(/\n/g, '
'); } +function dateLabel(ts) { + const d = new Date(ts.replace(' ', 'T')); + const now = new Date(); + const yesterday = new Date(now); yesterday.setDate(now.getDate() - 1); + if (d.toDateString() === now.toDateString()) return 'Hoy'; + if (d.toDateString() === yesterday.toDateString()) return 'Ayer'; + return d.toLocaleDateString('es-CO', { weekday:'short', day:'2-digit', month:'short' }); +} +function buildDateSep(label) { + const el = document.createElement('div'); + el.className = 'date-sep'; + el.textContent = label; + return el; +} +function msgDay(ts) { return new Date(ts.replace(' ', 'T')).toDateString(); } + function formatTime(ts) { const d = new Date(ts.replace(' ', 'T')); const now = new Date(); @@ -1425,9 +1481,7 @@ function formatTime(ts) { } function formatTimeFull(ts) { const d = new Date(ts.replace(' ', 'T')); - const t = d.toLocaleTimeString('es-CO', { hour: '2-digit', minute: '2-digit' }); - if (d.toDateString() === new Date().toDateString()) return t; - return d.toLocaleDateString('es-CO', { day: '2-digit', month: '2-digit' }) + ' ' + t; + return d.toLocaleTimeString('es-CO', { hour: '2-digit', minute: '2-digit' }); }