Stabilize visitor panel ordering

This commit is contained in:
rajchales-monito
2026-07-30 13:43:28 +02:00
parent 4afc88559f
commit 349f42346e
2 changed files with 41 additions and 5 deletions
+38 -3
View File
@@ -24,7 +24,8 @@ const state = {
me: null,
operatorPresence: {},
operatorTyping: {},
operatorPresenceTimer: null
operatorPresenceTimer: null,
visitorRenderKey: ""
};
const $ = (selector) => document.querySelector(selector);
@@ -411,7 +412,7 @@ async function loadConversations() {
async function loadVisitors() {
const data = await api("/api/admin/visitors");
state.visitors = data.visitors;
state.visitors = stableVisitors(data.visitors || []);
state.visitorsLoaded = true;
syncConversationVisitors();
renderConversations();
@@ -474,7 +475,7 @@ function connectEvents() {
return;
}
if (data.type === "visitors:update") {
state.visitors = data.visitors;
state.visitors = stableVisitors(data.visitors || []);
state.visitorsLoaded = true;
syncConversationVisitors();
renderConversations();
@@ -1068,6 +1069,9 @@ function conversationVisitorName(item) {
function renderVisitors() {
const visitors = state.hideBots ? state.visitors.filter((visitor) => !visitor.isBot) : state.visitors;
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;
$("#visitorCount").textContent = `${visitors.length} na webu${state.hideBots && botCount ? ` · ${botCount} bot` : ""}`;
$("#visitorList").innerHTML = visitors.length ? visitors.map((visitor) => `
@@ -1092,6 +1096,37 @@ function renderVisitors() {
`).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) {
const value = ["active", "idle", "offline"].includes(presence) ? presence : "offline";
const label = value === "active" ? "Aktivni" : value === "idle" ? "Neaktivni" : "Offline";