Show visitor online state in chat list

This commit is contained in:
rajchales-monito
2026-07-24 12:00:55 +02:00
parent ce99d7114f
commit df70913665
3 changed files with 43 additions and 1 deletions
+3
View File
@@ -168,6 +168,9 @@ label { display: grid; gap: 6px; color: var(--muted); font-size: 13px; }
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
} }
.visitor-name-wrap .online-dot {
flex: 0 0 auto;
}
.unread-dot { .unread-dot {
width: 9px; width: 9px;
height: 9px; height: 9px;
+24 -1
View File
@@ -3,6 +3,7 @@ const state = {
snippet: "", snippet: "",
conversations: [], conversations: [],
visitors: [], visitors: [],
visitorsLoaded: false,
activeId: null, activeId: null,
active: null, active: null,
events: null, events: null,
@@ -237,12 +238,16 @@ async function loadBootstrap() {
async function loadConversations() { async function loadConversations() {
const data = await api("/api/admin/conversations"); const data = await api("/api/admin/conversations");
state.conversations = data.conversations; state.conversations = data.conversations;
syncConversationVisitors();
renderConversations(); renderConversations();
} }
async function loadVisitors() { async function loadVisitors() {
const data = await api("/api/admin/visitors"); const data = await api("/api/admin/visitors");
state.visitors = data.visitors; state.visitors = data.visitors;
state.visitorsLoaded = true;
syncConversationVisitors();
renderConversations();
renderVisitors(); renderVisitors();
} }
@@ -253,6 +258,7 @@ async function openConversation(id) {
state.active = data; state.active = data;
const index = state.conversations.findIndex((item) => item.id === id); const index = state.conversations.findIndex((item) => item.id === id);
if (index >= 0) state.conversations[index] = { ...state.conversations[index], ...data.conversation }; if (index >= 0) state.conversations[index] = { ...state.conversations[index], ...data.conversation };
syncConversationVisitors();
renderConversations(); renderConversations();
renderActive(); renderActive();
} }
@@ -271,6 +277,9 @@ function connectEvents() {
} }
if (data.type === "visitors:update") { if (data.type === "visitors:update") {
state.visitors = data.visitors; state.visitors = data.visitors;
state.visitorsLoaded = true;
syncConversationVisitors();
renderConversations();
renderVisitors(); renderVisitors();
return; return;
} }
@@ -311,7 +320,8 @@ function renderConversations() {
<span class="row visitor-row"> <span class="row visitor-row">
<span class="visitor-name-wrap"> <span class="visitor-name-wrap">
<span class="unread-dot" aria-label="${item.hasUnread ? "Neprecteno" : "Precteno"}"></span> <span class="unread-dot" aria-label="${item.hasUnread ? "Neprecteno" : "Precteno"}"></span>
<strong>${escapeHtml(item.visitor.name || item.visitor.email || "Navstevnik")}</strong> <strong>${escapeHtml(conversationVisitorName(item))}</strong>
${item.visitorOnline ? `<span class="online-dot" aria-label="Online" title="Online"></span>` : ""}
</span> </span>
${item.imageCount ? `<span class="photo-count">foto ${item.imageCount}</span>` : ""} ${item.imageCount ? `<span class="photo-count">foto ${item.imageCount}</span>` : ""}
</span> </span>
@@ -332,6 +342,19 @@ function renderConversations() {
`).join("") : `<p class="empty-list">Zadny chat pro vybrany filtr.</p>`; `).join("") : `<p class="empty-list">Zadny chat pro vybrany filtr.</p>`;
} }
function syncConversationVisitors() {
if (!state.visitorsLoaded) return;
const onlineIds = new Set(state.visitors.map((visitor) => visitor.id));
state.conversations = state.conversations.map((item) => ({
...item,
visitorOnline: onlineIds.has(item.visitorToken)
}));
}
function conversationVisitorName(item) {
return item.visitor?.name || item.visitor?.email || item.visitorLabel || "Navstevnik";
}
function renderVisitors() { function renderVisitors() {
const visitors = state.hideBots ? state.visitors.filter((visitor) => !visitor.isBot) : state.visitors; const visitors = state.hideBots ? state.visitors.filter((visitor) => !visitor.isBot) : state.visitors;
const botCount = state.visitors.filter((visitor) => visitor.isBot).length; const botCount = state.visitors.filter((visitor) => visitor.isBot).length;
+16
View File
@@ -304,6 +304,12 @@ function adminConversations(res) {
AND unread.sender_type = 'visitor' AND unread.sender_type = 'visitor'
AND datetime(unread.created_at) > datetime(COALESCE(c.admin_seen_at, '1970-01-01 00:00:00')) AND datetime(unread.created_at) > datetime(COALESCE(c.admin_seen_at, '1970-01-01 00:00:00'))
) AS has_unread, ) AS has_unread,
EXISTS(
SELECT 1 FROM visitor_sessions vs
WHERE vs.site_id = c.site_id
AND vs.visitor_token = c.visitor_token
AND datetime(vs.last_seen_at) >= datetime('now', '-90 seconds')
) AS visitor_online,
(SELECT COUNT(*) FROM conversations vc WHERE vc.site_id = c.site_id AND vc.visitor_token = c.visitor_token) AS visitor_conversation_count (SELECT COUNT(*) FROM conversations vc WHERE vc.site_id = c.site_id AND vc.visitor_token = c.visitor_token) AS visitor_conversation_count
FROM conversations c FROM conversations c
JOIN sites s ON s.id = c.site_id JOIN sites s ON s.id = c.site_id
@@ -689,6 +695,10 @@ function formatConversationSummary(row) {
siteId: row.site_id, siteId: row.site_id,
siteKey: row.site_key, siteKey: row.site_key,
status: row.status, status: row.status,
visitorToken: row.visitor_token,
visitorInitials: visitorInitials(row.visitor_token),
visitorLabel: visitorLabel(row.visitor_token),
visitorOnline: Boolean(row.visitor_online),
visitor: { visitor: {
name: row.visitor_name, name: row.visitor_name,
email: row.visitor_email, email: row.visitor_email,
@@ -765,6 +775,12 @@ function findConversation(publicId) {
AND unread.sender_type = 'visitor' AND unread.sender_type = 'visitor'
AND datetime(unread.created_at) > datetime(COALESCE(c.admin_seen_at, '1970-01-01 00:00:00')) AND datetime(unread.created_at) > datetime(COALESCE(c.admin_seen_at, '1970-01-01 00:00:00'))
) AS has_unread, ) AS has_unread,
EXISTS(
SELECT 1 FROM visitor_sessions vs
WHERE vs.site_id = c.site_id
AND vs.visitor_token = c.visitor_token
AND datetime(vs.last_seen_at) >= datetime('now', '-90 seconds')
) AS visitor_online,
(SELECT COUNT(*) FROM conversations vc WHERE vc.site_id = c.site_id AND vc.visitor_token = c.visitor_token) AS visitor_conversation_count (SELECT COUNT(*) FROM conversations vc WHERE vc.site_id = c.site_id AND vc.visitor_token = c.visitor_token) AS visitor_conversation_count
FROM conversations c FROM conversations c
JOIN sites s ON s.id = c.site_id JOIN sites s ON s.id = c.site_id