фикс статистики 11
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful

This commit is contained in:
dv 2026-04-15 13:52:55 +03:00
parent 57fd5a4839
commit f2d3b5fc96

View file

@ -839,6 +839,17 @@ function renderClientUsersCard() {
const wrap = document.getElementById('stats-client-users-wrap');
if (!wrap) return;
const UNKNOWN_USER_KEY = '__unknown__';
const normalizeUserName = (value) => String(value || '')
.replace(/\u00A0/g, ' ')
.replace(/\s+/g, ' ')
.trim()
.toLowerCase();
const displayUserName = (value) => String(value || '')
.replace(/\u00A0/g, ' ')
.replace(/\s+/g, ' ')
.trim();
// createdby — строка с именем создателя, приходит прямо из API
// Базовый список клиентов берём из _allClients (все usergroup из dictionaries),
// чтобы показывать клиентов с нулём заявок тоже.
@ -848,18 +859,30 @@ function renderClientUsersCard() {
// Сначала заполняем всех известных клиентов с нулями, включая всех пользователей группы
(window._allClients || []).forEach((c) => {
const users = {};
(clientUsers[String(c.client_id)] || []).forEach((u) => { users[u] = 0; });
cMap[c.name] = { total: 0, users };
const userLabels = {};
(clientUsers[String(c.client_id)] || []).forEach((u) => {
const display = displayUserName(u);
const key = normalizeUserName(display);
if (!key) return;
users[key] = 0;
if (!userLabels[key]) userLabels[key] = display;
});
cMap[c.name] = { total: 0, users, userLabels };
});
// Затем считаем реальные заявки
allData.forEach((item) => {
const t = item.task;
const org = t._clientname || t.clientname || 'Неизвестно';
const creator = t.createdby || '—';
if (!cMap[org]) cMap[org] = { total: 0, users: {} };
const creatorDisplay = displayUserName(t.createdby);
const isUnknownCreator = !creatorDisplay || creatorDisplay === '\u2014' || creatorDisplay === '-';
const creatorKey = isUnknownCreator ? UNKNOWN_USER_KEY : normalizeUserName(creatorDisplay);
if (!cMap[org]) cMap[org] = { total: 0, users: {}, userLabels: {} };
cMap[org].total++;
cMap[org].users[creator] = (cMap[org].users[creator] || 0) + 1;
cMap[org].users[creatorKey] = (cMap[org].users[creatorKey] || 0) + 1;
if (!isUnknownCreator && !cMap[org].userLabels[creatorKey]) {
cMap[org].userLabels[creatorKey] = creatorDisplay;
}
});
const entries = Object.entries(cMap).sort((a, b) => b[1].total - a[1].total);
@ -870,9 +893,10 @@ function renderClientUsersCard() {
const pct = v.total / maxTotal * 100;
const safeName = escHtml(name).replace(/'/g, ''');
const sortedUsers = Object.entries(v.users).sort((a, b) => b[1] - a[1]);
const knownUsers = sortedUsers.filter(([u]) => u !== '—');
const unknownCount = v.users['—'] || 0;
const usersList = knownUsers.map(([uname, cnt]) => {
const knownUsers = sortedUsers.filter(([key]) => key !== UNKNOWN_USER_KEY);
const unknownCount = v.users[UNKNOWN_USER_KEY] || 0;
const usersList = knownUsers.map(([key, cnt]) => {
const uname = v.userLabels?.[key] || key;
const uPct = v.total ? cnt / v.total * 100 : 0;
return `<div style="display:flex;align-items:center;gap:6px;margin-top:3px;padding-left:4px;">
<div style="width:10px;height:10px;border-radius:50%;background:${avatarColor(uname)};flex-shrink:0;"></div>