diff --git a/public/css/app.css b/public/css/app.css
index f6a4396..19a5189 100644
--- a/public/css/app.css
+++ b/public/css/app.css
@@ -1037,21 +1037,28 @@ html.dark .stats-hm-cell:hover span { color: rgba(255,255,255,.95); }
}
/* ── Open-tickets week/month compare ────────────────────────── */
+.stats-open-wrap { display:flex; gap:16px; align-items:flex-start; }
+.stats-open-chart { flex:1; min-width:0; }
.stats-open-compare {
display: flex;
- align-items: center;
- gap: 12px;
- margin: 6px 0 12px;
- flex-wrap: wrap;
+ flex-direction: column;
+ background: var(--surface2);
+ border: 1px solid var(--border);
+ border-radius: var(--radius);
+ flex-shrink: 0;
+ min-width: 108px;
+ overflow: hidden;
}
.soc-item {
display: flex;
flex-direction: column;
align-items: center;
- gap: 2px;
+ gap: 3px;
+ padding: 12px 16px;
}
+.soc-divider { height:1px; background:var(--border); }
.soc-val {
- font-size: 22px;
+ font-size: 24px;
font-weight: 800;
color: var(--text);
line-height: 1;
@@ -1061,13 +1068,9 @@ html.dark .stats-hm-cell:hover span { color: rgba(255,255,255,.95); }
color: var(--text3);
text-transform: uppercase;
letter-spacing: .04em;
+ white-space: nowrap;
}
-.soc-sep {
- color: var(--text3);
- font-size: 13px;
- font-weight: 600;
- align-self: center;
-}
+.soc-sep { display: none; }
.stats-wdiff {
font-size: 11px;
font-weight: 700;
@@ -1080,7 +1083,35 @@ html.dark .stats-hm-cell:hover span { color: rgba(255,255,255,.95); }
.stats-wdiff-down { background: color-mix(in srgb,var(--green) 15%,transparent); color: var(--green); }
.stats-wdiff-flat { background: var(--surface2); color: var(--text3); }
-/* ── Activity tooltip ───────────────────────────────────────── */
+/* ── Activity hover label ────────────────────────────────────── */
+.stats-act-hover-label {
+ position: absolute;
+ top: -20px;
+ left: 50%;
+ transform: translateX(-50%);
+ display: flex;
+ align-items: center;
+ gap: 2px;
+ font-size: 9px;
+ font-weight: 700;
+ white-space: nowrap;
+ opacity: 0;
+ pointer-events: none;
+ transition: opacity .15s;
+ background: var(--surface);
+ border: 1px solid var(--border);
+ border-radius: 4px;
+ padding: 2px 5px;
+ z-index: 10;
+}
+.stats-activity-col:hover .stats-act-hover-label { opacity: 1; }
+.stats-activity-col:hover .stats-activity-bar { filter: brightness(1.2) saturate(1.15); }
+.stats-activity-bar { transition: filter .15s, height .2s; }
+.sahl-cr { color: var(--accent); }
+.sahl-sep { color: var(--text3); margin: 0 1px; }
+.sahl-cl { color: var(--green); }
+
+/* ── Activity tooltip (floating, kept for other use) ─────────── */
.stats-act-tooltip {
display: none;
position: fixed;
diff --git a/public/js/statistics.js b/public/js/statistics.js
index 9196f51..b6acf2b 100644
--- a/public/js/statistics.js
+++ b/public/js/statistics.js
@@ -169,6 +169,16 @@ function renderStatsPage() {
const HM_ORDER = [1,2,3,4,5,6,0];
const HM_LABELS = ['Пн','Вт','Ср','Чт','Пт','Сб','Вс'];
+ // Тепловая карта по месяцам (месяц × день недели)
+ const MONTH_HM = Array.from({length:12}, () => new Array(7).fill(0));
+ data.forEach(({task:t}) => {
+ if (!t.createdat) return;
+ const d = new Date(t.createdat);
+ MONTH_HM[d.getMonth()][d.getDay()]++;
+ });
+ const monthHmMax = Math.max(...MONTH_HM.flat(), 1);
+ const MONTH_NAMES = ['Янв','Фев','Мар','Апр','Май','Июн','Июл','Авг','Сен','Окт','Ноя','Дек'];
+
// 4. Без исполнителя
const noExec = data.filter(({task:t}) => !t.executor).length;
const noExecPct = total ? Math.round(noExec/total*100) : 0;
@@ -362,31 +372,35 @@ function renderStatsPage() {
📈 Открытых заявок на конец недели
-
-
-
${wOpen[WEEKS-1]}
-
сейчас
+
+
+
+
+
+
${wLabels.map((l,i)=>i%2===0?`${l}`:'').join('')}
+
-
vs
-
-
${wOpen[WEEKS-2]}
-
неделю назад
- ${wDiffBadge(wOpen[WEEKS-1]-wOpen[WEEKS-2])}
+
+
+
${wOpen[WEEKS-1]}
+
сейчас
+
+
+
+
${wOpen[WEEKS-2]}
+
неделю назад
+ ${wDiffBadge(wOpen[WEEKS-1]-wOpen[WEEKS-2])}
+
+
+
+
${openAtMonthStart}
+
нач. месяца
+ ${wDiffBadge(wOpen[WEEKS-1]-openAtMonthStart)}
+
-
·
-
-
${openAtMonthStart}
-
нач. месяца
- ${wDiffBadge(wOpen[WEEKS-1]-openAtMonthStart)}
-
-
-
-
-
-
${wLabels.map((l,i)=>i%2===0?`${l}`:'').join('')}
@@ -398,7 +412,7 @@ function renderStatsPage() {
закрыто
- ${wCreated.map((cr,i)=>{ const cl=wClosed[i],hCr=Math.round(cr/maxDual*100),hCl=Math.round(cl/maxDual*100); return'
'+(cr||cl?''+cr+'':'')+'
'+wLabels[i]+'
'; }).join('')}
+ ${wCreated.map((cr,i)=>{ const cl=wClosed[i],hCr=Math.round(cr/maxDual*100),hCl=Math.round(cl/maxDual*100); return'
'+(cr||cl?''+cr+'/'+cl+'':'')+'
'+wLabels[i]+'
'; }).join('')}
@@ -435,21 +449,40 @@ function renderStatsPage() {
-
-
-
🌡️ Тепловая карта активности (час × день недели)
-
-
- ${Array.from({length:24},(_,h)=>`
${h}
`).join('')}
+
+
+
+
🌡️ Тепловая карта активности (час × день недели)
+
+
+ ${Array.from({length:24},(_,h)=>`
${h}
`).join('')}
+
+ ${HM_ORDER.map((dayIdx,i)=>{
+ const row=HM[dayIdx];
+ return'
'+HM_LABELS[i]+'
'+row.map((v,h)=>'
'+(v||'')+'
').join('')+'
';
+ }).join('')}
+
+
меньше
+ ${[0,.2,.4,.6,.8,1].map(hi=>`
`).join('')}
+
больше
+
- ${HM_ORDER.map((dayIdx,i)=>{
- const row=HM[dayIdx];
- return'
'+HM_LABELS[i]+'
'+row.map((v,h)=>'
'+(v||'')+'
').join('')+'
';
- }).join('')}
-
-
меньше
- ${[0,.2,.4,.6,.8,1].map(hi=>`
`).join('')}
-
больше
+
+
+
🗓️ По месяцам (месяц × день нед.)
+
+
+ ${HM_ORDER.map((_,i)=>`
${HM_LABELS[i]}
`).join('')}
+
+ ${MONTH_NAMES.map((ml,mi)=>{
+ const row=MONTH_HM[mi];
+ return'
'+ml+'
'+HM_ORDER.map((di,dii)=>{const v=row[di];return'
'+(v||'')+'
';}).join('')+'
';
+ }).join('')}
+
+
меньше
+ ${[0,.2,.4,.6,.8,1].map(hi=>`
`).join('')}
+
больше
+
@@ -628,7 +661,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} — показать все
`:''}`;
}
@@ -656,6 +689,7 @@ function donutLegend(sc,uc,tot){
}
function escHtml(s){return String(s).replace(/&/g,'&').replace(//g,'>');}
+function escAttr(s){return String(s).replace(/&/g,'&').replace(//g,'>').replace(/"/g,'"').replace(/'/g,''');}
function wDiffBadge(diff){
if(diff===0) return '
= 0';