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

This commit is contained in:
dv 2026-04-15 10:33:11 +03:00
parent 1ea444791b
commit 347b1db9ae

View file

@ -835,28 +835,34 @@ function renderClientLastTicketCard() {
}).join('')}</div>`;
}
function getTaskCreator(task) {
// Ищем создателя по lifetime: самая ранняя запись с usertype=20 (внешний/клиент).
// lifetime.data отсортирован по убыванию (новые первые), поэтому ищем с конца.
const entries = task.lifetime?.data || [];
// Сначала ищем внешнего пользователя (клиента)
for (let i = entries.length - 1; i >= 0; i--) {
if (entries[i].usertype === 20 && entries[i].username) return entries[i].username;
}
// Fallback: последняя запись (самая ранняя) с любым username
for (let i = entries.length - 1; i >= 0; i--) {
if (entries[i].username) return entries[i].username;
}
return null;
}
function renderClientUsersCard() {
const wrap = document.getElementById('stats-client-users-wrap');
if (!wrap) return;
// _clientname — организация (из справочника по clientid)
// clientname — сырое поле API: имя контакта/заявителя когда _clientname задан,
// либо имя организации если clientid не задан
// Собираем: организация → { total, users: { contactName → count } }
// Собираем: организация → { total, users: { creator → count } }
const cMap = {};
allData.forEach((item) => {
const t = item.task;
const org = t._clientname || t.clientname || 'Неизвестно';
// Контакт: clientname когда есть отдельное _clientname (org), иначе lastComment автор
let contact;
if (t._clientname && t.clientname && t._clientname !== t.clientname) {
contact = t.clientname;
} else {
contact = item.lastComment?.username || t.updatedby || '—';
}
const creator = getTaskCreator(t) || '—';
if (!cMap[org]) cMap[org] = { total: 0, users: {} };
cMap[org].total++;
cMap[org].users[contact] = (cMap[org].users[contact] || 0) + 1;
cMap[org].users[creator] = (cMap[org].users[creator] || 0) + 1;
});
const entries = Object.entries(cMap).sort((a, b) => b[1].total - a[1].total);
@ -866,15 +872,23 @@ function renderClientUsersCard() {
wrap.innerHTML = `<div style="display:flex;flex-direction:column;gap:10px;max-height:500px;overflow-y:auto;">${entries.map(([name, v]) => {
const pct = v.total / maxTotal * 100;
const safeName = escHtml(name).replace(/'/g, '&#39;');
const usersList = Object.entries(v.users).sort((a, b) => b[1] - a[1]).map(([uname, cnt]) => {
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 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>
<span style="font-size:11px;color:var(--text2);flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;" title="${escHtml(uname)}">${escHtml(uname)}</span>
<span style="font-size:11px;font-weight:600;color:var(--text);min-width:20px;text-align:right;">${cnt}</span>
<span style="font-size:10px;color:var(--text3);min-width:34px;text-align:right;">${Math.round(uPct)}%</span>
<span style="font-size:11px;font-weight:600;color:var(--text);min-width:24px;text-align:right;">${cnt}</span>
<span style="font-size:10px;color:var(--text3);min-width:36px;text-align:right;">${Math.round(uPct)}%</span>
</div>`;
}).join('');
}).join('') + (unknownCount > 0 ? `<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:var(--border2);flex-shrink:0;"></div>
<span style="font-size:11px;color:var(--text3);flex:1;">Неизвестен</span>
<span style="font-size:11px;font-weight:600;color:var(--text3);min-width:24px;text-align:right;">${unknownCount}</span>
<span style="font-size:10px;color:var(--text3);min-width:36px;text-align:right;">${Math.round(unknownCount/v.total*100)}%</span>
</div>` : '');
return `<div style="border:1px solid var(--border);border-radius:8px;padding:8px 10px;">
<div style="display:flex;align-items:center;gap:8px;cursor:pointer;" onclick="goToClientTasks('${safeName}')">
<div style="font-size:12px;font-weight:600;color:var(--text);flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;" title="${safeName}">${escHtml(name)}</div>
@ -883,7 +897,7 @@ function renderClientUsersCard() {
<div style="margin-top:5px;height:6px;background:var(--surface2);border-radius:3px;overflow:hidden;">
<div style="height:100%;width:${Math.max(pct,v.total?2:0)}%;background:var(--accent);border-radius:3px;transition:width .4s;"></div>
</div>
${v.total > 0 ? usersList : ''}
${usersList}
</div>`;
}).join('')}</div>`;
}