Stabilize visitor panel ordering
This commit is contained in:
+38
-3
@@ -24,7 +24,8 @@ const state = {
|
|||||||
me: null,
|
me: null,
|
||||||
operatorPresence: {},
|
operatorPresence: {},
|
||||||
operatorTyping: {},
|
operatorTyping: {},
|
||||||
operatorPresenceTimer: null
|
operatorPresenceTimer: null,
|
||||||
|
visitorRenderKey: ""
|
||||||
};
|
};
|
||||||
|
|
||||||
const $ = (selector) => document.querySelector(selector);
|
const $ = (selector) => document.querySelector(selector);
|
||||||
@@ -411,7 +412,7 @@ async function loadConversations() {
|
|||||||
|
|
||||||
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 = stableVisitors(data.visitors || []);
|
||||||
state.visitorsLoaded = true;
|
state.visitorsLoaded = true;
|
||||||
syncConversationVisitors();
|
syncConversationVisitors();
|
||||||
renderConversations();
|
renderConversations();
|
||||||
@@ -474,7 +475,7 @@ function connectEvents() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (data.type === "visitors:update") {
|
if (data.type === "visitors:update") {
|
||||||
state.visitors = data.visitors;
|
state.visitors = stableVisitors(data.visitors || []);
|
||||||
state.visitorsLoaded = true;
|
state.visitorsLoaded = true;
|
||||||
syncConversationVisitors();
|
syncConversationVisitors();
|
||||||
renderConversations();
|
renderConversations();
|
||||||
@@ -1068,6 +1069,9 @@ function conversationVisitorName(item) {
|
|||||||
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;
|
||||||
|
const nextRenderKey = visitorListRenderKey(visitors, botCount);
|
||||||
|
if (nextRenderKey === state.visitorRenderKey) return;
|
||||||
|
state.visitorRenderKey = nextRenderKey;
|
||||||
$("#hideBotsToggle").checked = state.hideBots;
|
$("#hideBotsToggle").checked = state.hideBots;
|
||||||
$("#visitorCount").textContent = `${visitors.length} na webu${state.hideBots && botCount ? ` · ${botCount} bot` : ""}`;
|
$("#visitorCount").textContent = `${visitors.length} na webu${state.hideBots && botCount ? ` · ${botCount} bot` : ""}`;
|
||||||
$("#visitorList").innerHTML = visitors.length ? visitors.map((visitor) => `
|
$("#visitorList").innerHTML = visitors.length ? visitors.map((visitor) => `
|
||||||
@@ -1092,6 +1096,37 @@ function renderVisitors() {
|
|||||||
`).join("") : `<p class="empty-list">Zatim nikdo online.</p>`;
|
`).join("") : `<p class="empty-list">Zatim nikdo online.</p>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function stableVisitors(visitors) {
|
||||||
|
return [...visitors].sort((a, b) => {
|
||||||
|
const aFirst = Date.parse(a.firstSeenAt || "") || 0;
|
||||||
|
const bFirst = Date.parse(b.firstSeenAt || "") || 0;
|
||||||
|
if (aFirst !== bFirst) return aFirst - bFirst;
|
||||||
|
return String(a.id || "").localeCompare(String(b.id || ""));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function visitorListRenderKey(visitors, botCount) {
|
||||||
|
return JSON.stringify({
|
||||||
|
hideBots: state.hideBots,
|
||||||
|
botCount,
|
||||||
|
visitors: visitors.map((visitor) => ({
|
||||||
|
id: visitor.id,
|
||||||
|
label: visitor.label,
|
||||||
|
presence: visitor.presence,
|
||||||
|
currentUrl: visitor.currentUrl,
|
||||||
|
referrer: visitor.referrer,
|
||||||
|
lastConversationId: visitor.lastConversationId,
|
||||||
|
active: visitor.lastConversationId === state.activeId,
|
||||||
|
isBot: visitor.isBot,
|
||||||
|
botReason: visitor.botReason,
|
||||||
|
pageCount: visitor.pageCount,
|
||||||
|
visitCount: visitor.visitCount,
|
||||||
|
conversationCount: visitor.conversationCount,
|
||||||
|
sessionMinute: Math.floor(Number(visitor.sessionSeconds || 0) / 60)
|
||||||
|
}))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function presenceDot(presence) {
|
function presenceDot(presence) {
|
||||||
const value = ["active", "idle", "offline"].includes(presence) ? presence : "offline";
|
const value = ["active", "idle", "offline"].includes(presence) ? presence : "offline";
|
||||||
const label = value === "active" ? "Aktivni" : value === "idle" ? "Neaktivni" : "Offline";
|
const label = value === "active" ? "Aktivni" : value === "idle" ? "Neaktivni" : "Offline";
|
||||||
|
|||||||
@@ -1825,8 +1825,8 @@ function currentVisitors() {
|
|||||||
FROM visitor_sessions vs
|
FROM visitor_sessions vs
|
||||||
JOIN sites s ON s.id = vs.site_id
|
JOIN sites s ON s.id = vs.site_id
|
||||||
LEFT JOIN visitor_profiles vp ON vp.site_id = vs.site_id AND vp.visitor_token = vs.visitor_token
|
LEFT JOIN visitor_profiles vp ON vp.site_id = vs.site_id AND vp.visitor_token = vs.visitor_token
|
||||||
WHERE datetime(vs.last_seen_at) >= datetime('now', '-${presence.offlineSeconds} seconds')
|
WHERE datetime(COALESCE(vs.last_activity_at, vs.last_seen_at)) >= datetime('now', '-${presence.offlineSeconds} seconds')
|
||||||
ORDER BY datetime(vs.last_seen_at) DESC
|
ORDER BY datetime(vs.first_seen_at) ASC, vs.id ASC
|
||||||
LIMIT 100
|
LIMIT 100
|
||||||
`).all();
|
`).all();
|
||||||
return rows.map((row) => formatVisitorSession(row, presence));
|
return rows.map((row) => formatVisitorSession(row, presence));
|
||||||
@@ -1861,6 +1861,7 @@ function formatVisitorSession(row, thresholds = adminPresenceSettings()) {
|
|||||||
countryCode,
|
countryCode,
|
||||||
countryFlag: countryCode ? flagEmoji(countryCode) : "",
|
countryFlag: countryCode ? flagEmoji(countryCode) : "",
|
||||||
sessionSeconds: sessionSeconds(row.browsing_history_json, row.first_seen_at),
|
sessionSeconds: sessionSeconds(row.browsing_history_json, row.first_seen_at),
|
||||||
|
firstSeenAt: row.first_seen_at,
|
||||||
lastActivityAt: row.last_activity_at,
|
lastActivityAt: row.last_activity_at,
|
||||||
lastSeenAt: row.last_seen_at
|
lastSeenAt: row.last_seen_at
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user