Add unread chat indicator

This commit is contained in:
rajchales-monito
2026-07-23 23:23:32 +02:00
parent 571770b1c3
commit f8874c86ef
3 changed files with 82 additions and 5 deletions
+33
View File
@@ -95,6 +95,8 @@ label { display: grid; gap: 6px; color: var(--muted); font-size: 13px; }
}
.conversation-item.active { background: #eaf6f1; }
.conversation-item.fresh { animation: pulse 1.2s ease-in-out 4; }
.conversation-item.unread { background: #fffdf4; }
.conversation-item.unread.active { background: #eaf6f1; }
.conversation-open {
min-width: 0;
text-align: left;
@@ -108,6 +110,31 @@ label { display: grid; gap: 6px; color: var(--muted); font-size: 13px; }
}
.conversation-open:hover { filter: none; background: #f3f7f4; }
.row { display: flex; justify-content: space-between; gap: 10px; align-items: center; }
.visitor-row { min-width: 0; }
.visitor-name-wrap {
min-width: 0;
display: inline-flex;
align-items: center;
gap: 7px;
}
.visitor-name-wrap strong {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.unread-dot {
width: 9px;
height: 9px;
flex: 0 0 auto;
border-radius: 50%;
background: #c9d4ce;
}
.conversation-item.unread .unread-dot {
background: #ff3b30;
box-shadow: 0 0 0 0 rgb(255 59 48 / 55%);
animation: unreadPulse 1.25s ease-out infinite;
}
.status {
font-size: 12px;
color: white;
@@ -492,6 +519,12 @@ pre {
50% { background: #dff7eb; }
}
@keyframes unreadPulse {
0% { box-shadow: 0 0 0 0 rgb(255 59 48 / 55%); }
70% { box-shadow: 0 0 0 8px rgb(255 59 48 / 0%); }
100% { box-shadow: 0 0 0 0 rgb(255 59 48 / 0%); }
}
@media (max-width: 820px) {
body { overflow: auto; }
.app { height: auto; min-height: 100vh; grid-template-columns: 1fr; overflow: visible; }
+10 -3
View File
@@ -199,6 +199,8 @@ async function openConversation(id) {
state.activeId = id;
const data = await api(`/api/admin/conversations/${id}`);
state.active = data;
const index = state.conversations.findIndex((item) => item.id === id);
if (index >= 0) state.conversations[index] = { ...state.conversations[index], ...data.conversation };
renderConversations();
renderActive();
}
@@ -246,10 +248,15 @@ function renderSite() {
function renderConversations() {
const conversations = filteredConversations();
$("#conversationList").innerHTML = conversations.length ? conversations.map((item) => `
<article class="conversation-item ${item.id === state.activeId ? "active" : ""} ${item.status === "new" ? "fresh" : ""}">
<article class="conversation-item ${item.id === state.activeId ? "active" : ""} ${item.hasUnread ? "unread fresh" : ""}">
<button class="conversation-open" type="button" data-open="${item.id}">
<span class="row"><strong>${escapeHtml(item.visitor.name || item.visitor.email || "Navstevnik")}</strong>${item.imageCount ? `<span class="photo-count">foto ${item.imageCount}</span>` : ""}</span>
<span class="preview">${escapeHtml(item.lastBody || "")}</span>
<span class="row visitor-row">
<span class="visitor-name-wrap">
<span class="unread-dot" aria-label="${item.hasUnread ? "Neprecteno" : "Precteno"}"></span>
<strong>${escapeHtml(item.visitor.name || item.visitor.email || "Navstevnik")}</strong>
</span>
${item.imageCount ? `<span class="photo-count">foto ${item.imageCount}</span>` : ""}
</span>
<span class="visitor-facts">
<span title="Zeme">${visitorCountry(item)}</span>
<span title="Doba na webu">${formatDuration(item.sessionSeconds)}</span>