From 0189a4ab9815af561280310f84e76b250dc984e2 Mon Sep 17 00:00:00 2001 From: dv Date: Tue, 14 Apr 2026 16:54:15 +0300 Subject: [PATCH] =?UTF-8?q?=D1=84=D0=B8=D0=BA=D1=81=20=D0=BD=D0=B5=D0=B8?= =?UTF-8?q?=D0=B7=D0=B2=D0=B5=D1=81=D1=82=D0=BD=D0=BE=20+=20=D0=B4=D0=BE?= =?UTF-8?q?=D0=BF=20=D1=81=D1=82=D0=B0=D1=82=D0=B8=D1=81=D1=82=D0=B8=D0=BA?= =?UTF-8?q?=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/js/statistics.js | 80 ++++++++++++++++++++++++++++++++++++++--- public/js/ui.js | 7 +++- 2 files changed, 81 insertions(+), 6 deletions(-) diff --git a/public/js/statistics.js b/public/js/statistics.js index bfd162c..50d325c 100644 --- a/public/js/statistics.js +++ b/public/js/statistics.js @@ -2,10 +2,11 @@ // СТРАНИЦА СТАТИСТИКИ // ══════════════════════════════════════════════════════════════════ -let statsPageOpen = false; -let clientsExpanded = false; -let clientsOnlyOpen = false; -let timingPeriodIdx = 4; +let statsPageOpen = false; +let clientsExpanded = false; +let clientsOnlyOpen = false; +let timingPeriodIdx = 4; +let clientLastTicketMode = 'created'; // 'created' | 'updated' const STATS_CACHE_KEY = 'intradesk_stats_html_v2'; function saveStatsCache() { @@ -409,6 +410,18 @@ function renderStatsPage() {
+ +
+
+ 🕐 Последняя заявка по клиентам +
+ + +
+
+
+
+ @@ -549,6 +562,7 @@ function renderStatsPage() { `; renderClientsList(); + renderClientLastTicketCard(); saveStatsCache(); } @@ -606,7 +620,7 @@ function renderClientsList() { if(visible.length===0){wrap.innerHTML=`
${clientsOnlyOpen?'Нет клиентов с открытыми заявками':'Нет повторных обращений'}
`;return;} wrap.innerHTML=`
- ${visible.map(([name,v])=>{ const dv=clientsOnlyOpen?v.open:v.count,pct=dv/maxV*100,opPct=v.count?v.open/v.count*100:0; return'
'+escHtml(name)+'
'+v.count+''+(v.open?''+v.open+' откр.':'')+'
'; }).join('')} + ${visible.map(([name,v])=>{ const dv=clientsOnlyOpen?v.open:v.count,pct=dv/maxV*100,opPct=v.count?v.open/v.count*100:0; return'
'+escHtml(name)+'
'+v.count+''+(v.open?''+v.open+' откр.':'')+'
'; }).join('')}
${!clientsExpanded&&hidden>0?`
+ ещё ${hidden} — показать все
`:''}`; } @@ -640,3 +654,59 @@ function hideLineTip(){const t=document.getElementById('line-tooltip');if(t)t.st function openTaskById(id){const i=allData.findIndex(x=>x.task.id===id);if(i!==-1){closeStatsPage();setTimeout(()=>openDetail(i),100);}} function onDataLoaded(){if(statsPageOpen)renderStatsPage();} + +function goToClientTasks(clientName) { + const filterVal = (clientName === 'Неизвестно') ? '__UNKNOWN__' : clientName; + closeStatsPage(); + refreshAdvancedFilters(); + const sel = document.getElementById('filter-object-select'); + if (sel) { + [...sel.options].forEach(opt => { opt.selected = String(opt.value) === filterVal; }); + updateAdvancedFilterLabel('filter-object-select'); + buildAdvancedFilterPanel('filter-object-select'); + } + renderTable(); +} + +function fmtAgo(ms) { + const s = Math.round(ms / 1000); + if (s < 60) return `${s} сек назад`; + const m = Math.round(s / 60); + if (m < 60) return `${m} мин назад`; + const h = Math.round(m / 60); + if (h < 24) return `${h} ч назад`; + const d = Math.round(h / 24); + if (d < 30) return `${d} дн назад`; + const mo = Math.round(d / 30); + if (mo < 12) return `${mo} мес назад`; + return `${Math.round(mo / 12)} г назад`; +} + +function renderClientLastTicketCard() { + const wrap = document.getElementById('stats-client-last-ticket-wrap'); + if (!wrap) return; + const now = Date.now(); + const cMap = {}; + allData.forEach(({task: t}) => { + const name = t._clientname || t.clientname || 'Неизвестно'; + const cr = t.createdat ? new Date(t.createdat).getTime() : 0; + const upd = t.updatedat ? new Date(t.updatedat).getTime() : 0; + if (!cMap[name]) cMap[name] = { created: 0, updated: 0 }; + if (cr > cMap[name].created) cMap[name].created = cr; + if (upd > cMap[name].updated) cMap[name].updated = upd; + }); + const field = clientLastTicketMode; + const entries = Object.entries(cMap) + .filter(([, v]) => v[field] > 0) + .sort((a, b) => b[1][field] - a[1][field]); + if (!entries.length) { wrap.innerHTML = '
Нет данных
'; return; } + const maxAge = now - entries[entries.length - 1][1][field]; + wrap.innerHTML = `
${entries.map(([name, v]) => { + const ts = v[field]; + const age = now - ts; + const pct = maxAge > 0 ? Math.max((1 - age / maxAge) * 100, 2) : 100; + const color = age < 86400000 ? '#0d9488' : age < 7 * 86400000 ? 'var(--accent)' : age < 30 * 86400000 ? '#d97706' : 'var(--red)'; + const dateStr = new Date(ts).toLocaleDateString('ru-RU', { day: 'numeric', month: 'short', year: 'numeric' }); + return `
${escHtml(name)}
${fmtAgo(age)}
${dateStr}
`; + }).join('')}
`; +} diff --git a/public/js/ui.js b/public/js/ui.js index 37b9735..0a4af68 100644 --- a/public/js/ui.js +++ b/public/js/ui.js @@ -256,6 +256,7 @@ function refreshAdvancedFilters(){ } const objectName=String(task._clientname||task.clientname||task.organization||'').trim(); if(objectName) objectMap.set(objectName, objectName); + else objectMap.set('__UNKNOWN__', 'Неизвестно'); const intraId=String(task.statusid||task.status||'').trim(); const intraName=String(task._statusname||task.statusname||intraId).trim(); if(intraId) intraMap.set(intraId, intraName||intraId); @@ -593,7 +594,11 @@ function renderTable(keepPage){ if(selectedExecutors.size&& !selectedExecutors.has(String(item.task.executor||''))) return false; if(selectedObjects.size){ const objectName=String(item.task._clientname||item.task.clientname||item.task.organization||'').trim(); - if(!objectName || !selectedObjects.has(objectName)) return false; + if(!objectName){ + if(!selectedObjects.has('__UNKNOWN__')) return false; + } else { + if(!selectedObjects.has(objectName)) return false; + } } if(selectedIntra.size){ const intraId=String(item.task.statusid||item.task.status||'').trim();