Show visitor online state in chat list
This commit is contained in:
@@ -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
@@ -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;
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user