wasrusgen1-crm/docs/mockup_owner.html

930 lines
64 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>@wasrusgen1 CRM — Генеральный директор</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&family=Montserrat:wght@700;800&display=swap" rel="stylesheet">
<style>
*{box-sizing:border-box;margin:0;padding:0}
body{background:#C8CACD;min-height:100vh;display:flex;flex-direction:column;align-items:center;padding:20px;font-family:'Inter',sans-serif}
body{--accent:#003E7E;--accent2:#76BD22;--bg:#F5F6F8;--card:#FFFFFF;--ink:#1A1A2E;--muted:#8A94A6;--danger:#EF4444;--warn:#F59E0B;--success:#10B981;--s-success-bg:#ECFDF5;--s-warning-bg:#FFFBEB;--s-danger-bg:#FEF2F2;--s-info:#3B82F6;--s-info-bg:#EFF6FF}
body[data-theme="radar"]{--accent:#4338CA;--accent2:#6366F1;--bg:#F9FAFB;--card:#FFFFFF;--ink:#111827;--muted:#6B7280}
body[data-theme="dark"]{--accent:#4338CA;--accent2:#6366F1;--bg:#111827;--card:#1F2937;--ink:#F9FAFB;--muted:#9CA3AF}
#controls{display:flex;align-items:center;gap:12px;margin-bottom:16px;flex-wrap:wrap;justify-content:center;width:100%;max-width:600px}
#controls label{color:#fff;font-size:13px;font-weight:600}
#themeButtons{display:flex;gap:6px}
.theme-btn{padding:7px 14px;border-radius:9px;border:2px solid transparent;font-size:12px;font-weight:700;cursor:pointer;transition:all .2s}
.theme-btn.active{border-color:#fff;transform:scale(1.05)}
.theme-btn[data-t="zov"]{background:#003E7E;color:#fff}
.theme-btn[data-t="radar"]{background:linear-gradient(135deg,#1E1B4B,#4338CA);color:#fff}
.theme-btn[data-t="dark"]{background:#111827;color:#6366F1}
#phoneFrame{width:390px;height:844px;background:var(--bg);border-radius:44px;overflow:hidden;box-shadow:0 24px 80px rgba(0,0,0,.4),inset 0 0 0 1px rgba(255,255,255,.15);position:relative;display:flex;flex-direction:column}
#statusBar{height:44px;background:var(--card);display:flex;align-items:center;justify-content:space-between;padding:0 24px;flex-shrink:0;font-size:13px;font-weight:600;color:var(--ink);z-index:10}
.sb-r{display:flex;align-items:center;gap:6px}
#screen{flex:1;overflow-y:auto;overflow-x:hidden;scrollbar-width:none;background:var(--bg)}
#screen::-webkit-scrollbar{display:none}
.bottom-nav{height:60px;background:rgba(255,255,255,.92);backdrop-filter:blur(12px);border-top:1px solid rgba(0,0,0,.06);display:flex;align-items:center;justify-content:space-around;flex-shrink:0;z-index:100}
[data-theme="dark"] .bottom-nav{background:rgba(31,41,55,.95)}
.nav-item{display:flex;flex-direction:column;align-items:center;gap:2px;cursor:pointer;padding:5px 6px;border-radius:10px;transition:all .18s;flex:1;position:relative}
.nav-item svg{width:22px;height:22px;color:var(--muted);transition:color .18s}
.nav-item span{font-size:10px;color:var(--muted);font-weight:500;transition:color .18s}
.nav-item.active{background:rgba(0,62,126,.07)}
.nav-item.active svg,.nav-item.active span{color:var(--accent)}
.nav-item.active::after{content:'';position:absolute;bottom:4px;width:16px;height:3px;border-radius:2px;background:var(--accent);opacity:.6}
[data-theme="radar"] .nav-item.active{background:rgba(67,56,202,.07)}
[data-theme="dark"] .nav-item.active{background:rgba(99,102,241,.12)}
.page{padding:0 0 80px;min-height:100%}
.card{background:var(--card);border-radius:16px;box-shadow:0 2px 12px rgba(0,0,0,.07);padding:16px;margin-bottom:12px}
.card.warn-b{border-left:4px solid var(--warn)}
.card.ok-b{border-left:4px solid var(--success)}
.card.danger-b{border-left:4px solid var(--danger)}
.card.accent-b{border-left:4px solid var(--accent)}
.section-label{text-transform:uppercase;font-size:11px;letter-spacing:.06em;color:var(--muted);margin:20px 16px 8px;font-weight:600}
.hero-grad{background:linear-gradient(135deg,var(--accent) 0%,#005BB5 100%);padding:24px 20px 20px;color:#fff}
[data-theme="radar"] .hero-grad{background:linear-gradient(135deg,#312E81 0%,#4338CA 100%)}
[data-theme="dark"] .hero-grad{background:linear-gradient(135deg,#1E293B 0%,#312E81 100%)}
.kpi-grid{display:grid;grid-template-columns:1fr 1fr;gap:10px;padding:14px 16px}
.kpi-card{background:var(--card);border-radius:16px;padding:14px 16px;box-shadow:0 2px 10px rgba(0,0,0,.07);border:1px solid rgba(0,0,0,.05)}
.kpi-value{font-size:23px;font-weight:800;color:var(--ink);line-height:1;letter-spacing:-0.03em}
.kpi-label{font-size:11px;color:var(--muted);margin-top:5px;font-weight:500;letter-spacing:.01em}
.kpi-delta{font-size:11px;font-weight:700;margin-top:7px;letter-spacing:.01em}
.kpi-delta.up{color:var(--success)}
.kpi-delta.dn{color:var(--danger)}
.bar-wrap{display:flex;align-items:flex-end;gap:6px;height:72px;padding:0 16px}
.bar{flex:1;border-radius:4px 4px 0 0}
.bar.cur{background:var(--accent)}
.bar.prev{background:var(--accent);opacity:.25}
.bar-labels{display:flex;gap:6px;padding:4px 16px 0}
.bar-label{flex:1;text-align:center;font-size:9px;color:var(--muted);font-weight:500}
.row-item{display:flex;align-items:center;gap:12px;padding:12px 0;border-bottom:1px solid rgba(0,0,0,.05)}
.row-item:last-child{border:none}
.avatar-sm{width:38px;height:38px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:17px;flex-shrink:0}
.ri-name{font-size:14px;font-weight:600;color:var(--ink)}
.ri-sub{font-size:12px;color:var(--muted);margin-top:2px}
.badge-sm{font-size:11px;font-weight:700;padding:3px 8px;border-radius:20px;white-space:nowrap}
.bg{background:var(--s-success-bg);color:#065F46}
.bw{background:var(--s-warning-bg);color:#92400E}
.br{background:var(--s-danger-bg);color:#991B1B}
.bb{background:var(--s-info-bg);color:#1D4ED8}
.stat-row{display:flex;justify-content:space-between;align-items:center;padding:10px 0;border-bottom:1px solid rgba(0,0,0,.05)}
.stat-row:last-child{border:none;padding-bottom:0}
.stat-l{font-size:13px;color:var(--muted);font-weight:500}
.stat-v{font-size:14px;font-weight:700;color:var(--ink)}
.stat-v.g{color:var(--success)}
.stat-v.r{color:var(--danger)}
.stat-v.b{color:var(--accent)}
.prog-bg{background:rgba(0,0,0,.07);border-radius:4px;height:6px;flex:1;margin:0 10px;overflow:hidden}
.prog-fill{height:100%;border-radius:4px;background:var(--accent2)}
.btn-p{width:100%;padding:14px;background:var(--accent);color:#fff;border:none;border-radius:12px;font-size:15px;font-weight:700;cursor:pointer;margin-top:12px}
.btn-s{width:100%;padding:10px;background:transparent;color:var(--accent);border:1.5px solid var(--accent);border-radius:12px;font-size:13px;font-weight:600;cursor:pointer;margin-top:8px}
</style>
</head>
<body>
<div id="crm-back-nav" style="position:fixed;top:0;left:0;right:0;z-index:9999;background:rgba(255,255,255,0.92);backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);border-bottom:1px solid rgba(0,0,0,.08);padding:8px 16px;display:flex;align-items:center">
<a href="https://wasrusgen.github.io/wasrusgen1-crm/" style="display:inline-flex;align-items:center;gap:6px;font-family:Inter,system-ui,sans-serif;font-size:13px;font-weight:600;color:#003E7E;text-decoration:none;padding:4px 12px;border-radius:8px;background:#F0F4FF;transition:background .15s" onmouseover="this.style.background='#DDE8FF'" onmouseout="this.style.background='#F0F4FF'">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M19 12H5M12 5l-7 7 7 7"/></svg>
Все кабинеты
</a>
<span style="margin-left:12px;font-family:Inter,system-ui,sans-serif;font-size:12px;color:#8A94A6">@wasrusgen1 CRM</span>
</div>
<div style="height:40px"></div>
<div id="controls">
<label>Тема:</label>
<div id="themeButtons">
<button class="theme-btn active" data-t="zov" onclick="setTheme('zov',this)">Синяя</button>
<button class="theme-btn" data-t="radar" onclick="setTheme('radar',this)">CRM</button>
<button class="theme-btn" data-t="dark" onclick="setTheme('dark',this)">Dark</button>
</div>
</div>
<div id="phoneFrame">
<a href="./index.html" id="in-frame-back" style="display:flex;align-items:center;gap:6px;padding:5px 14px;background:rgba(0,62,126,.06);border-bottom:1px solid rgba(0,62,126,.09);font-family:Inter,system-ui,sans-serif;font-size:11px;font-weight:700;color:#003E7E;text-decoration:none;flex-shrink:0;z-index:200"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M19 12H5M12 5l-7 7 7 7"/></svg>Мокапы кабинетов</a>
<div id="statusBar">
<span>9:41</span>
<div class="sb-r">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M1.42 9a16 16 0 0 1 21.16 0M5 12.55a11 11 0 0 1 14.08 0M8.53 16.11a6 6 0 0 1 6.95 0M12 20h.01"/></svg>
<svg width="18" height="12" viewBox="0 0 27 12"><rect x="0" y="0" width="6" height="12" rx="1" fill="currentColor" opacity=".35"/><rect x="8" y="0" width="6" height="12" rx="1" fill="currentColor" opacity=".55"/><rect x="16" y="0" width="6" height="12" rx="1" fill="currentColor"/><rect x="23" y="3" width="3" height="6" rx="1" fill="currentColor" opacity=".4"/></svg>
</div>
</div>
<div id="screen"></div>
<div class="bottom-nav" id="nav"></div>
</div>
<script>
window._sc = window._sc || 'home';
function setTheme(t,btn){
document.body.removeAttribute('data-theme');
if(t!=='zov') document.body.setAttribute('data-theme',t);
document.querySelectorAll('.theme-btn').forEach(b=>b.classList.remove('active'));
if(btn) btn.classList.add('active');
}
function _nav(s){window._sc=s;_render();}
function _render(){
var sc=document.getElementById('screen');
sc.innerHTML=_rs();
sc.scrollTop=0;
document.getElementById('nav').innerHTML=_nb();
}
window._finTab = window._finTab || 'fact';
window._probExp = window._probExp || null;
if(window._monthDrill===undefined) window._monthDrill=null; // 0-5 = selected bar, null = closed
if(!window._drillSalon) window._drillSalon='all'; // 'all'|'s1'|'s2'
if(!window._salonVis) window._salonVis={s1:true,s2:true}; // toggle per salon
var _ICONS = {
// BarChart2 — floor line + clean bars
chart:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="20" x2="18" y2="10"/><line x1="12" y1="20" x2="12" y2="4"/><line x1="6" y1="20" x2="6" y2="14"/><line x1="2" y1="20" x2="22" y2="20"/></svg>',
truck:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="M5 17H3a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11a2 2 0 0 1 2 2v3"/><rect x="9" y="11" width="14" height="10" rx="1"/><circle cx="12" cy="21" r="1"/><circle cx="20" cy="21" r="1"/></svg>',
// Wallet — clean, stroked coin slot
wallet:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12V7H5a2 2 0 0 1 0-4h14v4"/><path d="M3 5v14a2 2 0 0 0 2 2h16v-5"/><circle cx="18" cy="14" r="1.5"/></svg>',
// Users — two figures, cleaner arcs
users:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></svg>',
// User single
person:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg>',
// Sliders — modern "settings" / controls icon
gear:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><line x1="4" y1="21" x2="4" y2="14"/><line x1="4" y1="10" x2="4" y2="3"/><line x1="12" y1="21" x2="12" y2="12"/><line x1="12" y1="8" x2="12" y2="3"/><line x1="20" y1="21" x2="20" y2="16"/><line x1="20" y1="12" x2="20" y2="3"/><line x1="1" y1="14" x2="7" y2="14"/><line x1="9" y1="8" x2="15" y2="8"/><line x1="17" y1="16" x2="23" y2="16"/></svg>',
// TrendingUp — clean arrow
trend:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><polyline points="22 7 13.5 15.5 8.5 10.5 2 17"/><polyline points="16 7 22 7 22 13"/></svg>'
};
// ─── ДАННЫЕ МЕСЯЦЕВ (с разбивкой по салонам) ───────────────────────
var _mdata=[
{m:'Дек',v:1.42,s1:.62,d:31,seed:1},
{m:'Янв',v:1.78,s1:.58,d:31,seed:2},
{m:'Фев',v:2.08,s1:.55,d:28,seed:3},
{m:'Мар',v:1.91,s1:.60,d:31,seed:4},
{m:'Апр',v:2.41,s1:.57,d:30,seed:5},
{m:'Май',v:2.85,s1:.54,d:31,seed:6},
];
function _genDaily(seed,totalM,ndays){
var r=[]; for(var i=0;i<ndays;i++){var s=(Math.sin(seed*31+i*17+3)*0.5+0.5)*0.8+0.1;r.push(Math.round(s*((i+seed)%7>=5?.28:1.18)*100000+18000));}
var sum=r.reduce((a,b)=>a+b,0); var sc=totalM*1e6/sum; return r.map(d=>Math.round(d*sc));
}
function _dayChart(days,color,idx){
var W=330,H=68,n=days.length;
var mx=Math.max.apply(null,days),mn=Math.min(mx*.02,Math.min.apply(null,days));
var rng=mx-mn||1;
var pts=days.map((v,i)=>[(Math.round(i/(n-1)*(W-12)+6)),Math.round(H-6-(v-mn)/rng*(H-18))]);
var line=pts.map(p=>p[0]+','+p[1]).join(' ');
var area='6,'+(H-6)+' '+line+' '+(W-6)+','+(H-6);
var bi=days.indexOf(mx); var bp=pts[bi];
return '<svg viewBox="0 0 '+W+' '+H+'" style="width:100%;height:'+H+'px;display:block">'
+'<defs><linearGradient id="dcg'+idx+'" x1="0" y1="0" x2="0" y2="1"><stop offset="0%" stop-color="'+color+'" stop-opacity=".2"/><stop offset="100%" stop-color="'+color+'" stop-opacity="0"/></linearGradient></defs>'
+'<polygon points="'+area+'" fill="url(#dcg'+idx+')"/>'
+'<polyline points="'+line+'" fill="none" stroke="'+color+'" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"/>'
+'<circle cx="'+bp[0]+'" cy="'+bp[1]+'" r="5" fill="'+color+'" opacity=".2"/>'
+'<circle cx="'+bp[0]+'" cy="'+bp[1]+'" r="3" fill="'+color+'"/>'
+'<text x="'+Math.min(bp[0]+6,W-40)+'" y="'+(bp[1]-6)+'" font-size="9" font-weight="800" fill="'+color+'">'+Math.round(mx/1000)+'тыс</text>'
+'</svg>';
}
function _drillPanel(idx){
var md=_mdata[idx];
var all=_genDaily(md.seed,md.v,md.d);
var s1=all.map(d=>Math.round(d*md.s1));
var s2=all.map((d,i)=>d-s1[i]);
var clrs={all:'#003E7E',s1:'#3B82F6',s2:'#8B5CF6'};
var days=window._drillSalon==='s1'?s1:window._drillSalon==='s2'?s2:all;
var clr=clrs[window._drillSalon]||'#003E7E';
var tot=days.reduce((a,b)=>a+b,0);
var mx=Math.max.apply(null,days);
var bestDay=days.indexOf(mx)+1;
var pills=[{k:'all',l:'Все салоны'},{k:'s1',l:'Салон Ленина'},{k:'s2',l:'Салон Победы'}]
.map(f=>{var a=window._drillSalon===f.k;return '<span onclick="window._drillSalon=\''+f.k+'\';_render()" style="display:inline-flex;padding:5px 12px;border-radius:20px;font-size:11px;font-weight:700;cursor:pointer;margin-right:6px;margin-bottom:6px;background:'+(a?clr:'rgba(0,0,0,.07)')+';color:'+(a?'#fff':'var(--muted)')+'">'+f.l+'</span>';}).join('');
var t1=s1.reduce((a,b)=>a+b,0),t2=s2.reduce((a,b)=>a+b,0),tAll=all.reduce((a,b)=>a+b,0);
return '<div style="border-top:2px solid rgba(0,62,126,.12);padding:14px 16px 4px;background:rgba(0,62,126,.03)">'
+'<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:10px">'
+ '<div style="font-size:14px;font-weight:800;color:var(--ink)">'+md.m+' &mdash; по дням</div>'
+ '<span onclick="window._monthDrill=null;_render()" style="width:26px;height:26px;display:inline-flex;align-items:center;justify-content:center;background:rgba(0,0,0,.08);border-radius:50%;font-size:16px;color:var(--muted);cursor:pointer">×</span>'
+'</div>'
+'<div style="margin-bottom:8px">'+pills+'</div>'
+_dayChart(days,clr,idx)
+'<div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:6px;margin-top:10px">'
+[{l:'Выручка',v:(tot/1e6).toFixed(2)+' М ₽'},{l:'Среднедн.',v:Math.round(tot/days.length/1000)+' тыс ₽'},{l:'Пик · '+bestDay+' '+md.m,v:Math.round(mx/1000)+' тыс ₽'}]
.map(s=>'<div style="background:var(--card);border-radius:10px;padding:8px 10px;border:1px solid rgba(0,0,0,.06)"><div style="font-size:10px;color:var(--muted);font-weight:500;margin-bottom:3px">'+s.l+'</div><div style="font-size:12px;font-weight:800;color:var(--ink);letter-spacing:-.02em">'+s.v+'</div></div>')
.join('')+'</div>'
+(window._drillSalon==='all'
?'<div style="margin-top:10px">'
+[{l:'Салон Ленина',v:t1,c:'#3B82F6'},{l:'Салон Победы',v:t2,c:'#8B5CF6'}].map(s=>{var pct=Math.round(s.v/tAll*100);return '<div style="margin-bottom:8px"><div style="display:flex;justify-content:space-between;margin-bottom:4px"><span style="font-size:12px;color:var(--ink);font-weight:600">'+s.l+'</span><span style="font-size:12px;font-weight:700;color:var(--ink)">'+(s.v/1e6).toFixed(2)+' М · '+pct+'%</span></div><div style="height:5px;background:rgba(0,0,0,.07);border-radius:3px"><div style="height:100%;background:'+s.c+';border-radius:3px;width:'+pct+'%"></div></div></div>';}).join('')
+'</div>'
:'')
+'</div>';
}
function _nb(){
var items=[
{id:'home', icon:'chart', label:'Главная'},
{id:'sales', icon:'trend', label:'Продажи'},
{id:'finance', icon:'wallet',label:'Финансы'},
{id:'team', icon:'users', label:'Команда'},
{id:'sklad', icon:'truck', label:'Склад'},
{id:'settings',icon:'gear', label:'Настройки'},
];
return items.map(it=>'<div class="nav-item'+(window._sc===it.id?' active':'')+'" onclick="_nav(\''+it.id+'\')">'+_ICONS[it.icon]+'<span>'+it.label+'</span></div>').join('');
}
function _rs(){
if(window._sc==='home') return _sHome();
if(window._sc==='sales') return _sSales();
if(window._sc==='finance') return _sFinance();
if(window._sc==='team') return _sTeam();
if(window._sc==='clients') return _sClients();
if(window._sc==='sklad') return _sSklad();
if(window._sc==='settings') return _sSettings();
return _sHome();
}
// ─── ГЛАВНАЯ ───────────────────────────────────────────────────────
function _sHome(){
var vis=window._salonVis;
// Grouped bars: max по отдельным значениям каждого салона
var allVals=_mdata.map(m=>[m.v*m.s1, m.v*(1-m.s1)]).reduce(function(a,b){return a.concat(b);},[]);
var mx=Math.max.apply(null,allVals);
var CHART_H=80; // px
// Toggle pills — клик включает/выключает салон
var toggles='<div style="display:flex;gap:7px;padding:2px 16px 12px">'
+'<div onclick="window._salonVis.s1=!window._salonVis.s1;window._monthDrill=null;_render()" style="cursor:pointer;display:inline-flex;align-items:center;gap:5px;padding:5px 12px;border-radius:20px;transition:.18s;background:'+(vis.s1?'#3B82F6':'rgba(0,0,0,.07)')+';border:1.5px solid '+(vis.s1?'#3B82F6':'rgba(0,0,0,.1)')+'"><span style="width:7px;height:7px;border-radius:50%;background:'+(vis.s1?'#fff':'#94A3B8')+';flex-shrink:0"></span><span style="font-size:11px;font-weight:700;color:'+(vis.s1?'#fff':'var(--muted)')+'">Салон Ленина</span></div>'
+'<div onclick="window._salonVis.s2=!window._salonVis.s2;window._monthDrill=null;_render()" style="cursor:pointer;display:inline-flex;align-items:center;gap:5px;padding:5px 12px;border-radius:20px;transition:.18s;background:'+(vis.s2?'#8B5CF6':'rgba(0,0,0,.07)')+';border:1.5px solid '+(vis.s2?'#8B5CF6':'rgba(0,0,0,.1)')+'"><span style="width:7px;height:7px;border-radius:50%;background:'+(vis.s2?'#fff':'#94A3B8')+';flex-shrink:0"></span><span style="font-size:11px;font-weight:700;color:'+(vis.s2?'#fff':'var(--muted)')+'">Салон Победы</span></div>'
+'</div>';
var bars=toggles
+'<div style="display:flex;align-items:flex-end;gap:4px;height:'+CHART_H+'px;padding:0 16px">'
+_mdata.map((m,i)=>{
var sel=window._monthDrill===i;
var hasOther=window._monthDrill!==null&&!sel;
var op=sel?'1':hasOther?'0.2':(i===_mdata.length-1?'1':'0.7');
var v1=m.v*m.s1; var v2=m.v*(1-m.s1);
var h1=Math.round(v1/mx*CHART_H); var h2=Math.round(v2/mx*CHART_H);
var selBorder=sel?'outline:2px solid var(--accent2);outline-offset:2px;':'';
// Группа месяца
return '<div style="flex:1;display:flex;align-items:flex-end;gap:2px;cursor:pointer;opacity:'+op+';transition:opacity .2s" onclick="window._monthDrill=(window._monthDrill==='+i+'?null:'+i+');window._drillSalon=\'all\';_render()">'
// Бар Ленина
+(vis.s1?'<div style="flex:1;height:'+h1+'px;background:#3B82F6;border-radius:3px 3px 0 0;'+selBorder+'"></div>':'<div style="flex:1"></div>')
// Бар Победы
+(vis.s2?'<div style="flex:1;height:'+h2+'px;background:#8B5CF6;border-radius:3px 3px 0 0;'+selBorder+'"></div>':'<div style="flex:1"></div>')
+'</div>';
}).join('')
+'</div>'
+'<div class="bar-labels">'+_mdata.map((m,i)=>{
var sel=window._monthDrill===i;
return '<div class="bar-label" style="cursor:pointer;'+(sel?'color:var(--accent);font-weight:800;':'')+'" onclick="window._monthDrill=(window._monthDrill==='+i+'?null:'+i+');window._drillSalon=\'all\';_render()">'
+(sel?m.v.toFixed(2)+'М':m.m)+'</div>';
}).join('')+'</div>';
return '<div class="page">'
+'<div class="hero-grad">'
+ '<div style="font-size:11px;font-weight:700;opacity:.65;text-transform:uppercase;letter-spacing:.08em;margin-bottom:6px">МАЙ 2026</div>'
+ '<div style="font-size:30px;font-weight:800;letter-spacing:-.04em">2 847 000 ₽</div>'
+ '<div style="font-size:13px;opacity:.8;margin-top:5px">Выручка · <span style="color:#76BD22;font-weight:700">▲ +18%</span> к апрелю</div>'
+ '<div style="display:flex;gap:20px;margin-top:16px;font-size:13px;opacity:.85">'
+ '<div><div style="opacity:.65;font-size:11px">Прибыль</div><div style="font-weight:700">988 000 ₽</div></div>'
+ '<div><div style="opacity:.65;font-size:11px">Маржа</div><div style="font-weight:700;color:#76BD22">34.7%</div></div>'
+ '<div><div style="opacity:.65;font-size:11px">Заказов</div><div style="font-weight:700">47</div></div>'
+ '</div>'
+'</div>'
+'<div class="kpi-grid">'
+ '<div class="kpi-card"><div class="kpi-value">34.7%</div><div class="kpi-label">Маржа (EBITDA)</div><div class="kpi-delta up">▲ +2.1 пп к апр.</div></div>'
+ '<div class="kpi-card"><div class="kpi-value">47</div><div class="kpi-label">Заказов за месяц</div><div class="kpi-delta up">▲ +6 к апрелю</div></div>'
+ '<div class="kpi-card"><div class="kpi-value">51 800</div><div class="kpi-label">Средний чек, ₽</div><div class="kpi-delta up">▲ +3 200 ₽</div></div>'
+ '<div class="kpi-card"><div class="kpi-value">4.8 / 5</div><div class="kpi-label">NPS клиентов</div><div class="kpi-delta up">▲ +0.1 пункт</div></div>'
+'</div>'
+'<div class="section-label" style="cursor:default">Выручка · последние 6 месяцев <span style="font-size:10px;color:var(--muted);font-weight:500;text-transform:none;letter-spacing:0">нажмите на бар</span></div>'
+'<div style="padding:0 16px"><div class="card" style="padding:16px 0 10px">'+bars+'</div></div>'
+(window._monthDrill!==null ? '<div style="padding:0 16px">'+_drillPanel(window._monthDrill)+'</div>' : '')
// ── ПАЙПЛАЙН (одна строка-сигнал)
+'<div style="padding:0 16px;margin-bottom:4px">'
+'<div class="card" style="padding:13px 16px;cursor:pointer;display:flex;align-items:center;gap:12px" onclick="_nav(\'sales\')">'
+ '<div style="flex:1">'
+ '<div style="font-size:13px;font-weight:700;color:var(--ink)">Пайплайн продаж · 47 заказов</div>'
+ '<div style="display:flex;gap:12px;margin-top:5px">'
+ '<span style="font-size:11px;color:var(--muted)">В работе: <b style="color:var(--ink)">2 847 000 ₽</b></span>'
+ '<span style="font-size:11px;color:var(--danger);font-weight:700">⚠ 3 просрочки · 385 тыс риск</span>'
+ '</div>'
+ '</div>'
+ '<span style="font-size:18px;color:var(--muted)"></span>'
+'</div></div>'
// ── ПРОБЛЕМЫ (кликабельные)
+'<div class="section-label">🚨 Проблемы и срывы</div>'
+'<div style="padding:0 16px">'
+_problems().map(p=>{
var exp = window._probExp===p.id;
var border = p.sev==='red'?'var(--danger)':p.sev==='warn'?'var(--warn)':'var(--success)';
var badgeCls = p.sev==='red'?'br':p.sev==='warn'?'bw':'bg';
return '<div class="card" style="margin-bottom:8px;border-left:3px solid '+border+';cursor:pointer;padding:12px 14px" onclick="window._probExp=(window._probExp===\''+p.id+'\'?null:\''+p.id+'\');_render()">'
+'<div style="display:flex;justify-content:space-between;align-items:flex-start">'
+ '<div style="flex:1">'
+ '<div style="font-size:13px;font-weight:700;color:var(--ink)">'+p.title+'</div>'
+ '<div style="font-size:12px;color:var(--muted);margin-top:3px">'+p.sub+'</div>'
+ '</div>'
+ '<div style="display:flex;align-items:center;gap:6px;flex-shrink:0;margin-left:8px">'
+ '<span class="badge-sm '+badgeCls+'">'+p.badge+'</span>'
+ '<span style="font-size:14px;color:var(--muted);transition:.2s;'+(exp?'transform:rotate(180deg)':'')+'">'+(exp?'▲':'▼')+'</span>'
+ '</div>'
+'</div>'
+(exp
? '<div style="margin-top:10px;padding-top:10px;border-top:1px solid rgba(0,0,0,.07)">'
+p.detail.map(d=>'<div style="font-size:12px;color:var(--ink);line-height:1.6;margin-bottom:4px">'+d+'</div>').join('')
+(p.action?'<button style="margin-top:8px;padding:7px 14px;background:var(--accent);color:#fff;border:none;border-radius:8px;font-size:12px;font-weight:700;cursor:pointer">'+p.action+'</button>':'')
+'</div>'
: '')
+'</div>';
}).join('')
+'</div>'
+'</div>';
}
function _problems(){
return [
{id:'p1', sev:'red', badge:'3', title:'3 заказа с просрочкой монтажа',
sub:'Самый старый — 8 дней. Причина: Иван Т. заболел',
detail:['🔴 КЧ-2841 · Просрочка 8 дн · Иван Т. (болеет)','🔴 КЧ-2798 · Просрочка 5 дн · не назначен исполнитель','🟡 ЗМ-1902 · Просрочка 3 дн · перенесён клиентом'],
action:'Назначить исполнителей →'},
{id:'p2', sev:'red', badge:'!', title:'Рекламация — ЗМ-1891',
sub:'Клиент требует переделки фасадов · 3 дня без ответа',
detail:['Клиент: Антонова Т.В.','Сборщик: Иван Т. (сейчас болеет)','Суть: перекос фасадов, зазоры > 3 мм','Статус: открыта 22.05, ответа нет'],
action:'Открыть рекламацию →'},
{id:'p3', sev:'warn', badge:'7', title:'Дебиторка: 287 000 ₽',
sub:'7 клиентов · авансы > 14 дней',
detail:['ООО «ДомСтрой» · 89 000 ₽ · 21 день','Козлов А. · 54 000 ₽ · 18 дней','Белова Н. · 38 000 ₽ · 16 дней','+ ещё 4 клиента на сумму 106 000 ₽'],
action:'Поставить задачу менеджеру →'},
{id:'p4', sev:'warn', badge:'79', title:'KPI Ольги Р. ниже нормы',
sub:'Менеджер · KPI 79 · 2-й месяц подряд',
detail:['Норма: ≥ 80','Апрель: 81 → Май: 79 (тренд вниз)','Причина: 3 потерянных лида на этапе КП','Рекомендация: встреча-разбор по скриптам'],
action:'Поставить задачу директору →'},
{id:'p5', sev:'warn', badge:'4', title:'Нет замерщика 2830 мая',
sub:'Марина С. в отпуске · 4 замера без исполнителя',
detail:['28 мая: Иванов С. (Кировский р-н)','28 мая: ООО «МебельПлюс» (Центр)','29 мая: Петрова Н. (Советский р-н)','30 мая: Зайцев И. (Ленинский р-н)'],
action:'Назначить Светлану М. →'},
{id:'p6', sev:'ok', badge:'✅', title:'Кассовый разрыв — нет',
sub:'Баланс в норме · Склад укомплектован',
detail:['Остаток на счёте: 1 240 000 ₽','Ожидаемые поступления 7 дней: +680 000 ₽','Плановые выплаты 7 дней: 320 000 ₽','Склад: 97% позиций в наличии'],
action:null},
];
}
// ─── ФИНАНСЫ ───────────────────────────────────────────────────────
function _finTabBar(){
return '<div style="display:flex;gap:0;padding:0 16px 0;margin-top:12px">'
+['fact','forecast'].map((t,i)=>{
var lbl = t==='fact'?'📊 Факт':'🔮 Прогноз';
var act = window._finTab===t;
return '<div onclick="window._finTab=\''+t+'\';_render()" style="flex:1;text-align:center;padding:9px 0;font-size:13px;font-weight:700;cursor:pointer;border-bottom:2px solid '+(act?'var(--accent)':'rgba(0,0,0,.1)')+';color:'+(act?'var(--accent)':'var(--muted)')+'">'+lbl+'</div>';
}).join('')
+'</div>';
}
function _sFinance(){
var exp=[
{label:'ФОТ', val:456000, pct:70, color:'#3B82F6'},
{label:'Логистика', val:89000, pct:14, color:'#8B5CF6'},
{label:'Аренда', val:48000, pct:7, color:'#F59E0B'},
{label:'Реклама', val:78000, pct:12, color:'#10B981'},
{label:'Прочие', val:58000, pct:9, color:'#EF4444'},
];
var hist=[
{m:'Фев',rev:2.1, pr:672},
{m:'Мар',rev:1.9, pr:589},
{m:'Апр',rev:2.4, pr:792},
{m:'Май',rev:2.85,pr:988,cur:true},
];
var header = '<div style="padding:20px 16px 0;background:var(--card);border-bottom:1px solid rgba(0,0,0,.06)">'
+'<div style="font-size:17px;font-weight:800;color:var(--ink)">Финансы</div>'
+'<div style="font-size:12px;color:var(--muted);margin-top:2px">Май 2026</div>'
+_finTabBar()
+'</div>';
if(window._finTab==='forecast') return _sFinanceForecast(header);
// ── ФАКТ ──
return '<div class="page">'+header
+'<div style="padding:16px 16px 0">'
+'<div class="card" style="background:linear-gradient(135deg,var(--accent),#005BB5);color:#fff;padding:20px 20px 16px">'
+ '<div style="font-size:11px;opacity:.7;font-weight:700;text-transform:uppercase;letter-spacing:.08em">Чистая прибыль · Май</div>'
+ '<div style="font-size:34px;font-weight:800;letter-spacing:-.04em;margin-top:4px">988 000 ₽</div>'
+ '<div style="display:flex;gap:20px;margin-top:12px;font-size:12px;opacity:.85">'
+ '<div><div style="opacity:.65">Выручка</div><div style="font-weight:700">2 847 000 ₽</div></div>'
+ '<div><div style="opacity:.65">Расходы</div><div style="font-weight:700">1 859 000 ₽</div></div>'
+ '<div><div style="opacity:.65">Маржа</div><div style="font-weight:700;color:#76BD22">34.7%</div></div>'
+ '</div>'
+'</div>'
+'</div>'
+'<div class="section-label">Структура расходов</div>'
+'<div style="padding:0 16px"><div class="card">'
+exp.map(e=>'<div style="margin-bottom:12px">'
+'<div style="display:flex;justify-content:space-between;margin-bottom:5px">'
+ '<span style="font-size:13px;color:var(--ink);font-weight:500">'+e.label+'</span>'
+ '<span style="font-size:13px;font-weight:700;color:var(--ink)">'+(e.val/1000).toFixed(0)+' тыс ₽</span>'
+'</div>'
+'<div class="prog-bg" style="margin:0"><div style="height:100%;border-radius:4px;background:'+e.color+';width:'+e.pct+'%"></div></div>'
+'</div>').join('')
+'</div></div>'
+'<div class="section-label">Динамика по месяцам</div>'
+'<div style="padding:0 16px"><div class="card" style="padding:0">'
+hist.map(h=>'<div style="display:flex;align-items:center;padding:12px 16px;border-bottom:1px solid rgba(0,0,0,.05);'+(h.cur?'background:var(--s-info-bg)':'')+'">'
+'<div style="font-size:13px;font-weight:700;color:var(--ink);width:34px">'+h.m+'</div>'
+'<div style="flex:1;padding-left:10px">'
+ '<div style="font-size:11px;color:var(--muted)">Выручка</div>'
+ '<div style="font-size:14px;font-weight:700;color:var(--ink)">'+h.rev.toFixed(2)+' М ₽</div>'
+'</div>'
+'<div style="text-align:right">'
+ '<div style="font-size:11px;color:var(--muted)">Прибыль</div>'
+ '<div style="font-size:14px;font-weight:700;color:var(--success)">'+h.pr+' тыс</div>'
+'</div>'
+(h.cur?'<div style="margin-left:8px"><span class="badge-sm bb">тек.</span></div>':'')
+'</div>').join('')
+'</div></div>'
+'</div>';
}
function _sFinanceForecast(header){
var cf=[
{label:'Остаток на счёте', val:'+1 240 000', color:'var(--success)', note:'сегодня'},
{label:'Ожидаемые поступления', val:'+1 680 000', color:'var(--success)', note:'7 дн · из воронки'},
{label:'ФОТ (аванс 1 июня)', val:'228 000', color:'var(--danger)', note:'через 5 дней'},
{label:'Подрядчики / доставка',val:'147 000', color:'var(--danger)', note:'7 дн · 3 счёта'},
{label:'Аренда + хоз.', val:'48 000', color:'var(--danger)', note:'1 июня'},
];
var pipe=[
{stage:'Договор → Монтаж', cnt:8, sum:'684 000', prob:90, color:'#10B981'},
{stage:'КП отправлено', cnt:11,sum:'912 000', prob:55, color:'#3B82F6'},
{stage:'На замере', cnt:7, sum:'490 000', prob:35, color:'#F59E0B'},
{stage:'Новые лиды', cnt:14,sum:'1 120 000',prob:15,color:'#8B5CF6'},
];
var expected = 684*0.9 + 912*0.55 + 490*0.35 + 1120*0.15;
return '<div class="page">'+header
+'<div style="padding:16px 16px 0">'
+'<div class="card" style="background:linear-gradient(135deg,#065F46,#10B981);color:#fff;padding:20px 20px 16px">'
+ '<div style="font-size:11px;opacity:.7;font-weight:700;text-transform:uppercase;letter-spacing:.08em">Прогноз · следующие 30 дней</div>'
+ '<div style="font-size:34px;font-weight:800;letter-spacing:-.04em;margin-top:4px">+2 497 000 ₽</div>'
+ '<div style="font-size:13px;opacity:.85;margin-top:6px">Ожидаемый баланс с учётом поступлений и выплат</div>'
+ '<div style="display:flex;gap:20px;margin-top:12px;font-size:12px;opacity:.85">'
+ '<div><div style="opacity:.65">Взвешенный пайплайн</div><div style="font-weight:700">'+Math.round(expected)+' тыс ₽</div></div>'
+ '<div><div style="opacity:.65">Выплаты</div><div style="font-weight:700">423 тыс ₽</div></div>'
+ '</div>'
+'</div>'
+'</div>'
+'<div class="section-label">Cash flow · 30 дней</div>'
+'<div style="padding:0 16px"><div class="card" style="padding:0">'
+cf.map(r=>'<div style="display:flex;align-items:center;padding:12px 16px;border-bottom:1px solid rgba(0,0,0,.05)">'
+'<div style="flex:1"><div style="font-size:13px;color:var(--ink);font-weight:500">'+r.label+'</div><div style="font-size:11px;color:var(--muted);margin-top:1px">'+r.note+'</div></div>'
+'<div style="font-size:14px;font-weight:800;color:'+r.color+'">'+r.val+' ₽</div>'
+'</div>').join('')
+'<div style="padding:12px 16px;border-top:2px solid rgba(0,0,0,.08);display:flex;justify-content:space-between">'
+ '<span style="font-size:13px;font-weight:700;color:var(--ink)">Итого через 30 дней</span>'
+ '<span style="font-size:14px;font-weight:800;color:var(--success)">+2 497 000 ₽</span>'
+'</div>'
+'</div></div>'
+'<div class="section-label">Взвешенный пайплайн</div>'
+'<div style="padding:0 16px"><div class="card" style="padding:0">'
+pipe.map(p=>'<div style="padding:12px 16px;border-bottom:1px solid rgba(0,0,0,.05)">'
+'<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:6px">'
+ '<div><div style="font-size:13px;font-weight:600;color:var(--ink)">'+p.stage+'</div>'
+ '<div style="font-size:11px;color:var(--muted)">'+p.cnt+' заказов · вероятность '+p.prob+'%</div></div>'
+ '<div style="text-align:right"><div style="font-size:13px;font-weight:700;color:var(--ink)">'+p.sum+' ₽</div>'
+ '<div style="font-size:11px;color:'+p.color+';font-weight:700">≈'+(parseInt(p.sum.replace(/\s/g,''))/1000*p.prob/100).toFixed(0)+' тыс</div></div>'
+'</div>'
+'<div class="prog-bg" style="margin:0"><div style="height:100%;border-radius:4px;background:'+p.color+';width:'+p.prob+'%"></div></div>'
+'</div>').join('')
+'</div></div>'
+'</div>';
}
// ─── ПРОДАЖИ ───────────────────────────────────────────────────────
function _sSales(){
var stages=[
{id:'new', label:'Новые лиды', cnt:14, sum:'1 120 000', color:'#8B5CF6', pct:100},
{id:'meet', label:'На замере', cnt:7, sum:'490 000', color:'#3B82F6', pct:50},
{id:'kp', label:'КП отправлено',cnt:11,sum:'912 000', color:'#F59E0B', pct:79},
{id:'think', label:'Думает', cnt:5, sum:'380 000', color:'#F97316', pct:36},
{id:'deal', label:'Договор', cnt:8, sum:'684 000', color:'#10B981', pct:57},
{id:'mount', label:'Монтаж', cnt:6, sum:'512 000', color:'#059669', pct:43},
{id:'lost', label:'Потерян', cnt:4, sum:'290 000', color:'#EF4444', pct:29},
];
var conv=[
{from:'Лид → Замер', pct:58, good:true},
{from:'Замер → КП', pct:94, good:true},
{from:'КП → Договор', pct:48, good:false},
{from:'Договор → Монтаж',pct:100,good:true},
];
var orders=[
{id:'КЧ-2847', client:'ООО «РемСтрой»', sum:'187 000', stage:'Монтаж', days:2, mgr:'Дмитрий К.', salon:'Ленина', warn:false},
{id:'ЗМ-2831', client:'Надежда Орлова', sum:'142 000', stage:'Договор', days:5, mgr:'Дмитрий К.', salon:'Победы', warn:false},
{id:'КЧ-2819', client:'ИП Сидоров', sum:'128 500', stage:'КП', days:1, mgr:'Ольга Р.', salon:'Ленина', warn:false},
{id:'ЗМ-2841', client:'Анна Белова', sum:'113 000', stage:'Монтаж', days:0, mgr:'Ольга Р.', salon:'Победы', warn:true},
{id:'КЧ-2798', client:'Сергей Павлов', sum:'98 500', stage:'Монтаж', days:-5, mgr:'Дмитрий К.', salon:'Ленина', warn:true},
{id:'КЧ-2789', client:'Тамара Н.', sum:'94 000', stage:'Думает', days:8, mgr:'Ольга Р.', salon:'Ленина', warn:false},
{id:'ЗМ-2774', client:'ООО «МебельПлюс»', sum:'88 000', stage:'КП', days:3, mgr:'Дмитрий К.', salon:'Победы', warn:false},
];
var stageColor={Монтаж:'#10B981',Договор:'#3B82F6',КП:'#F59E0B',Думает:'#F97316','Новый':'#8B5CF6'};
return '<div class="page">'
+'<div style="padding:20px 16px 12px;background:var(--card);border-bottom:1px solid rgba(0,0,0,.06)">'
+ '<div style="font-size:17px;font-weight:800;color:var(--ink)">Продажи</div>'
+ '<div style="font-size:12px;color:var(--muted);margin-top:2px">Май 2026 · активная воронка</div>'
+'</div>'
// KPI strip
+'<div class="kpi-grid">'
+ '<div class="kpi-card"><div class="kpi-value">47</div><div class="kpi-label">Заказов в работе</div><div class="kpi-delta up">▲ +6 к апр.</div></div>'
+ '<div class="kpi-card"><div class="kpi-value">3.6 М</div><div class="kpi-label">Сумма воронки, ₽</div><div class="kpi-delta up">▲ +14%</div></div>'
+ '<div class="kpi-card"><div class="kpi-value">48%</div><div class="kpi-label">Конверсия КП→Дог.</div><div class="kpi-delta dn">▼ 5 пп</div></div>'
+ '<div class="kpi-card"><div class="kpi-value">21 дн</div><div class="kpi-label">Ср. цикл сделки</div><div class="kpi-delta up">▼ 2 дня</div></div>'
+'</div>'
// Воронка
+'<div class="section-label">Воронка продаж</div>'
+'<div style="padding:0 16px"><div class="card" style="padding:12px 16px">'
+stages.map(s=>'<div style="margin-bottom:10px">'
+'<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:4px">'
+ '<div style="display:flex;align-items:center;gap:8px">'
+ '<span style="width:8px;height:8px;border-radius:50%;background:'+s.color+';display:inline-block;flex-shrink:0"></span>'
+ '<span style="font-size:13px;color:var(--ink);font-weight:500">'+s.label+'</span>'
+ '<span class="badge-sm" style="background:'+s.color+'18;color:'+s.color+'">'+s.cnt+'</span>'
+ '</div>'
+ '<span style="font-size:12px;font-weight:700;color:var(--ink)">'+s.sum+' ₽</span>'
+'</div>'
+'<div class="prog-bg" style="margin:0"><div style="height:100%;border-radius:4px;background:'+s.color+';width:'+s.pct+'%"></div></div>'
+'</div>').join('')
+'</div></div>'
// Конверсия
+'<div class="section-label">Конверсия по этапам</div>'
+'<div style="padding:0 16px"><div class="card" style="padding:0">'
+conv.map(c=>'<div style="display:flex;align-items:center;padding:11px 16px;border-bottom:1px solid rgba(0,0,0,.05)">'
+'<div style="flex:1;font-size:13px;color:var(--ink)">'+c.from+'</div>'
+'<div style="font-size:16px;font-weight:800;color:'+(c.good?'var(--success)':'var(--danger)')+'">'+c.pct+'%</div>'
+'</div>').join('')
+'<div style="padding:10px 16px;background:var(--s-warning-bg);border-radius:0 0 12px 12px">'
+ '<div style="font-size:12px;color:#92400E;font-weight:600">⚠️ КП→Договор 48% — ниже нормы 55%. Разобрать с менеджерами скрипты КП.</div>'
+'</div>'
+'</div></div>'
// Список заказов
+'<div class="section-label">Все заказы в работе</div>'
+'<div style="padding:0 16px"><div class="card" style="padding:0">'
+orders.map(o=>'<div style="display:flex;align-items:center;padding:11px 16px;border-bottom:1px solid rgba(0,0,0,.05)">'
+'<div style="flex:1;min-width:0">'
+ '<div style="display:flex;align-items:center;gap:6px">'
+ '<span style="font-size:12px;font-weight:700;color:var(--accent)">'+o.id+'</span>'
+ (o.warn?'<span style="font-size:10px;font-weight:700;color:var(--danger)">⚠ просрочка</span>':'')
+ '</div>'
+ '<div style="font-size:12px;color:var(--ink);font-weight:500;margin-top:2px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis">'+o.client+'</div>'
+ '<div style="font-size:11px;color:var(--muted);margin-top:1px">Салон '+o.salon+' · '+o.mgr+'</div>'
+'</div>'
+'<div style="text-align:right;flex-shrink:0;margin-left:8px">'
+ '<div style="font-size:13px;font-weight:700;color:var(--ink)">'+o.sum+' ₽</div>'
+ '<div style="display:inline-flex;margin-top:3px;background:'+(stageColor[o.stage]||'var(--accent)')+'18;border-radius:6px;padding:2px 7px">'
+ '<span style="font-size:10px;font-weight:700;color:'+(stageColor[o.stage]||'var(--accent)')+'">'+o.stage+'</span>'
+ '</div>'
+'</div>'
+'</div>').join('')
+'</div></div>'
+'</div>';
}
// ─── КОМАНДА (executive view для ГД) ──────────────────────────────
function _sTeam(){
// Данные по салонам
var salons=[
{name:'Салон Ленина', color:'#3B82F6', bg:'#EFF6FF',
staff:4, rev:'1 537 000', revMgr:'768 500', orders:27, conv:54, kpiAvg:88,
flags:1, flagDesc:'Ольга Р. · KPI 79 · 2-й мес'},
{name:'Салон Победы', color:'#8B5CF6', bg:'#F5F3FF',
staff:4, rev:'1 310 000', revMgr:'655 000', orders:20, conv:48, kpiAvg:81,
flags:2, flagDesc:'Иван Т. болеет · просрочки'},
];
// Роли агрегированно
var roles=[
{role:'Менеджеры', icon:'📋', count:2, kpi:87, norm:80, flags:1, flagDesc:'Ольга Р. ниже нормы 2 мес'},
{role:'Сборщики', icon:'🔧', count:3, kpi:83, norm:80, flags:1, flagDesc:'Иван Т. болеет · 3 просрочки'},
{role:'Замерщики', icon:'📐', count:2, kpi:89, norm:80, flags:0, flagDesc:''},
{role:'Руководство', icon:'🏢', count:1, kpi:88, norm:80, flags:1, flagDesc:'Не перераспределил заказы'},
];
// Критические флаги — только для ГД
var critFlags=[
{sev:'danger', text:'Иван Т. болеет 3-й день', sub:'3 просрочки монтажа · рекламация ЗМ-1891 без ответа'},
{sev:'warn', text:'Ольга Р. · KPI 79 · 2-й месяц подряд', sub:'Салон Победы · план/факт: 12/15'},
{sev:'warn', text:'Марина С. в отпуске 2830 мая', sub:'4 замера без замены · риск переноса'},
];
var sc={danger:{c:'var(--danger)',bg:'#FEF2F2',ic:'🔴'},warn:{c:'var(--warn)',bg:'#FFFBEB',ic:'🟡'}};
return '<div class="page">'
+'<div style="padding:20px 16px 12px;background:var(--card);border-bottom:1px solid rgba(0,0,0,.06)">'
+ '<div style="font-size:17px;font-weight:800;color:var(--ink)">Команда</div>'
+ '<div style="font-size:12px;color:var(--muted);margin-top:2px">8 сотрудников · ФОТ 456 000 ₽/мес</div>'
+'</div>'
// KPI сводка
+'<div class="kpi-grid">'
+ '<div class="kpi-card"><div class="kpi-value">16%</div><div class="kpi-label">ФОТ / выручка</div><div class="kpi-delta up">▼ 1.2 пп к апр.</div></div>'
+ '<div class="kpi-card"><div class="kpi-value">85</div><div class="kpi-label">Средний KPI</div><div class="kpi-delta up">▲ +2 к апр.</div></div>'
+ '<div class="kpi-card"><div class="kpi-value" style="color:var(--danger)">3</div><div class="kpi-label">Требуют внимания</div><div class="kpi-delta dn">из 8 сотрудников</div></div>'
+ '<div class="kpi-card"><div class="kpi-value">8 / 8</div><div class="kpi-label">Укомплектованность</div><div class="kpi-delta up">Вакансий нет</div></div>'
+'</div>'
// Сравнение салонов
+'<div class="section-label">Эффективность по салонам</div>'
+'<div style="padding:0 16px;display:grid;grid-template-columns:1fr 1fr;gap:10px">'
+salons.map(s=>`
<div class="card" style="padding:14px;border-top:3px solid ${s.color}">
<div style="font-size:12px;font-weight:800;color:${s.color};margin-bottom:8px">${s.name}</div>
<div style="font-size:11px;color:var(--muted);margin-bottom:2px">Выручка/мес</div>
<div style="font-size:15px;font-weight:800;color:var(--ink);margin-bottom:6px">${s.rev} ₽</div>
<div class="stat-row" style="padding:4px 0"><span class="stat-l" style="font-size:11px">Выр./менеджер</span><span class="stat-v" style="font-size:11px">${s.revMgr} ₽</span></div>
<div class="stat-row" style="padding:4px 0"><span class="stat-l" style="font-size:11px">Конверсия</span><span class="stat-v ${s.conv>=50?'g':'r'}" style="font-size:11px">${s.conv}%</span></div>
<div class="stat-row" style="padding:4px 0;border:none"><span class="stat-l" style="font-size:11px">Ср. KPI</span><span class="stat-v ${s.kpiAvg>=85?'g':'r'}" style="font-size:11px">${s.kpiAvg}</span></div>
${s.flags>0?`<div style="margin-top:6px;padding:5px 8px;background:#FEF2F2;border-radius:8px;font-size:10px;color:var(--danger);font-weight:600">⚠ ${s.flagDesc}</div>`:'<div style="margin-top:6px;padding:5px 8px;background:#ECFDF5;border-radius:8px;font-size:10px;color:var(--success);font-weight:600">✅ Всё в порядке</div>'}
</div>`).join('')
+'</div>'
// Команда по ролям
+'<div class="section-label">Команда по ролям</div>'
+'<div style="padding:0 16px"><div class="card" style="padding:0">'
+roles.map((r,i)=>{
var kpiColor=r.kpi>=r.norm?'var(--success)':'var(--danger)';
return '<div style="display:flex;align-items:center;padding:12px 14px;border-bottom:'+(i<roles.length-1?'1px solid rgba(0,0,0,.05)':'none')+'">'
+'<div style="font-size:20px;margin-right:10px;line-height:1">'+r.icon+'</div>'
+'<div style="flex:1">'
+ '<div style="font-size:13px;font-weight:700;color:var(--ink)">'+r.role+'</div>'
+ '<div style="font-size:11px;color:var(--muted);margin-top:1px">'+r.count+' чел</div>'
+'</div>'
+'<div style="text-align:right;margin-right:10px">'
+ '<div style="font-size:15px;font-weight:800;color:'+kpiColor+'">'+r.kpi+'</div>'
+ '<div style="font-size:9px;color:var(--muted);text-transform:uppercase">KPI</div>'
+'</div>'
+(r.flags>0
? '<div style="background:#FEF2F288;border-radius:8px;padding:4px 8px;max-width:110px;text-align:right"><div style="font-size:10px;color:var(--danger);font-weight:600;line-height:1.3">'+r.flagDesc+'</div></div>'
: '<div style="background:#ECFDF588;border-radius:8px;padding:4px 8px"><div style="font-size:10px;color:var(--success);font-weight:600">Всё ок</div></div>')
+'</div>';
}).join('')
+'</div></div>'
// Критические флаги
+'<div class="section-label">Требует решения</div>'
+'<div style="padding:0 16px">'
+critFlags.map(f=>`
<div style="display:flex;align-items:flex-start;gap:10px;background:${sc[f.sev].bg};border-radius:14px;padding:12px 14px;margin-bottom:8px">
<span style="font-size:16px;flex-shrink:0;margin-top:1px">${sc[f.sev].ic}</span>
<div style="flex:1">
<div style="font-size:13px;font-weight:600;color:var(--ink);line-height:1.35">${f.text}</div>
<div style="font-size:11px;color:var(--muted);margin-top:3px">${f.sub}</div>
</div>
</div>`).join('')
+'</div>'
+'</div>';
}
// ─── КЛИЕНТЫ ───────────────────────────────────────────────────────
function _sClients(){
var top=[
{name:'ООО «РемСтрой»', orders:8, ltv:'587 000', em:'🏢'},
{name:'Надежда Орлова', orders:5, ltv:'312 000', em:'👩'},
{name:'Анна Белова', orders:4, ltv:'243 000', em:'👩'},
{name:'Сергей Павлов', orders:3, ltv:'178 000', em:'👨'},
];
var src=[['Рекомендации',42,'#10B981'],['Авито',28,'#3B82F6'],['Instagram',18,'#8B5CF6'],['Сайт / SEO',12,'#F59E0B']];
return '<div class="page">'
+'<div style="padding:20px 16px 12px;background:var(--card);border-bottom:1px solid rgba(0,0,0,.06)">'
+ '<div style="font-size:17px;font-weight:800;color:var(--ink)">Клиенты</div>'
+ '<div style="font-size:12px;color:var(--muted);margin-top:2px">Май 2026</div>'
+'</div>'
+'<div class="kpi-grid">'
+ '<div class="kpi-card"><div class="kpi-value">312</div><div class="kpi-label">Всего клиентов</div><div class="kpi-delta up">▲ +23 за месяц</div></div>'
+ '<div class="kpi-card"><div class="kpi-value">4.8 / 5</div><div class="kpi-label">Средний NPS</div><div class="kpi-delta up">▲ +0.1 пункт</div></div>'
+ '<div class="kpi-card"><div class="kpi-value">89 000</div><div class="kpi-label">Средний LTV, ₽</div><div class="kpi-delta up">▲ +4 200</div></div>'
+ '<div class="kpi-card"><div class="kpi-value">1.8%</div><div class="kpi-label">Отток</div><div class="kpi-delta up">▼ 0.4 пп</div></div>'
+'</div>'
+'<div class="section-label">Источники привлечения</div>'
+'<div style="padding:0 16px"><div class="card">'
+src.map(s=>'<div style="display:flex;align-items:center;gap:10px;margin-bottom:12px">'
+'<div style="font-size:12px;color:var(--muted);font-weight:500;width:110px">'+s[0]+'</div>'
+'<div class="prog-bg" style="margin:0"><div style="height:100%;border-radius:4px;background:'+s[2]+';width:'+s[1]+'%"></div></div>'
+'<div style="font-size:13px;font-weight:700;color:var(--ink);width:32px;text-align:right">'+s[1]+'%</div>'
+'</div>').join('')
+'</div></div>'
+'<div class="section-label">Топ по LTV</div>'
+'<div style="padding:0 16px"><div class="card" style="padding:8px 16px">'
+top.map(c=>'<div class="row-item">'
+'<div class="avatar-sm" style="background:var(--s-info-bg);font-size:19px">'+c.em+'</div>'
+'<div style="flex:1"><div class="ri-name">'+c.name+'</div><div class="ri-sub">'+c.orders+' заказов</div></div>'
+'<div style="text-align:right"><div style="font-size:13px;font-weight:700;color:var(--ink)">'+c.ltv+' ₽</div><div style="font-size:10px;color:var(--muted)">LTV</div></div>'
+'</div>').join('')
+'</div></div>'
+'</div>';
}
// ─── СКЛАД И ДОСТАВКА (executive view для ГД) ──────────────────────
function _sSklad(){
// Факторы риска от фабрик
var factories=[
{name:'МебельМастер', orders:14, delayed:2, delayDays:3, sum:'184 000'},
{name:'КорпусПро', orders:9, delayed:0, delayDays:0, sum:'97 000'},
{name:'СтильДом', orders:5, delayed:1, delayDays:6, sum:'63 000'},
];
// Критические ситуации — только для ГД
var alerts=[
{type:'danger', text:'СтильДом задерживает ЗАК-1839 на 6 дней — монтаж под угрозой', sub:'Клиент Иванова Т. · 63 000 ₽'},
{type:'warn', text:'МебельМастер: 2 заказа без подтверждения даты отгрузки', sub:'ЗАК-1855, ЗАК-1862 · до 30 мая'},
{type:'ok', text:'Доставки сегодня: 3/5 выполнено, 1 просрочка на 2ч', sub:'ДОС-288 · Сергей Павлов · Алексей Г.'},
];
var ac={danger:{c:'var(--danger)',bg:'#FEF2F2',ic:'🔴'},warn:{c:'var(--warn)',bg:'#FFFBEB',ic:'🟡'},ok:{c:'var(--success)',bg:'#ECFDF5',ic:'🟢'}};
return `<div class="page">
<div style="padding:20px 16px 12px;background:var(--card);border-bottom:1px solid rgba(0,0,0,.06)">
<div style="font-size:17px;font-weight:800;color:var(--ink)">Склад и доставка</div>
<div style="font-size:12px;color:var(--muted);margin-top:2px">Май 2026</div>
</div>
<!-- KPI сводка -->
<div class="kpi-grid">
<div class="kpi-card">
<div class="kpi-value">78%</div>
<div class="kpi-label">В срок за май</div>
<div class="kpi-delta dn">▼ план 90%</div>
</div>
<div class="kpi-card">
<div class="kpi-value" style="color:var(--danger)">3</div>
<div class="kpi-label">Задержки фабрик</div>
<div class="kpi-delta dn">риск 147 тыс ₽</div>
</div>
<div class="kpi-card">
<div class="kpi-value">28</div>
<div class="kpi-label">Заказов в работе</div>
<div class="kpi-delta up">↑ 4 vs прош. мес</div>
</div>
<div class="kpi-card">
<div class="kpi-value">4.7</div>
<div class="kpi-label">Рейтинг доставки</div>
<div class="kpi-delta up">▲ 4.5 прош. мес</div>
</div>
</div>
<!-- Требует внимания -->
<div class="section-label">Требует внимания</div>
<div style="padding:0 16px">
${alerts.map(a=>`
<div style="display:flex;align-items:flex-start;gap:10px;background:${ac[a.type].bg};border-radius:14px;padding:12px 14px;margin-bottom:8px">
<span style="font-size:16px;flex-shrink:0;margin-top:1px">${ac[a.type].ic}</span>
<div style="flex:1;min-width:0">
<div style="font-size:13px;font-weight:600;color:var(--ink);line-height:1.35">${a.text}</div>
<div style="font-size:11px;color:var(--muted);margin-top:3px">${a.sub}</div>
</div>
</div>`).join('')}
</div>
<!-- Фабрики-поставщики -->
<div class="section-label">Фабрики-поставщики</div>
<div style="padding:0 16px">
<div class="card" style="padding:0">
${factories.map((f,i)=>`
<div style="padding:13px 14px;border-bottom:${i<factories.length-1?'1px solid rgba(0,0,0,.05)':'none'}">
<div style="display:flex;justify-content:space-between;align-items:flex-start">
<div>
<div style="font-size:14px;font-weight:700;color:var(--ink)">${f.name}</div>
<div style="font-size:11px;color:var(--muted);margin-top:2px">${f.orders} заказов · ${f.sum} ₽</div>
</div>
${f.delayed>0
? `<div style="background:#FEF2F2;border-radius:10px;padding:4px 10px;text-align:right">
<div style="font-size:13px;font-weight:800;color:var(--danger)">${f.delayed} задерж.</div>
<div style="font-size:10px;color:#991B1B">+${f.delayDays} дн avg</div>
</div>`
: `<div style="background:#ECFDF5;border-radius:10px;padding:4px 10px">
<div style="font-size:12px;font-weight:700;color:var(--success)">В норме</div>
</div>`
}
</div>
<!-- Прогресс-бар исполнения -->
<div style="margin-top:8px">
<div style="display:flex;justify-content:space-between;margin-bottom:3px">
<span style="font-size:10px;color:var(--muted)">Исполнение в срок</span>
<span style="font-size:10px;font-weight:700;color:${f.delayed>0?'var(--warn)':'var(--success)'}">${Math.round((f.orders-f.delayed)/f.orders*100)}%</span>
</div>
<div style="height:4px;background:rgba(0,0,0,.07);border-radius:2px">
<div style="height:100%;background:${f.delayed>0?'var(--warn)':'var(--success)'};border-radius:2px;width:${Math.round((f.orders-f.delayed)/f.orders*100)}%"></div>
</div>
</div>
</div>`).join('')}
</div>
</div>
<!-- Доставки сегодня (только сводка) -->
<div class="section-label">Доставки сегодня</div>
<div style="padding:0 16px">
<div class="card" style="padding:14px 16px">
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:10px">
<span style="font-size:14px;font-weight:700;color:var(--ink)">5 доставок</span>
<span style="font-size:12px;color:var(--muted)">2 экспедитора</span>
</div>
<div style="display:flex;gap:0;height:8px;border-radius:6px;overflow:hidden">
<div style="flex:3;background:#10B981" title="3 доставлено"></div>
<div style="flex:1;background:#3B82F6;margin-left:2px" title="1 в пути"></div>
<div style="flex:1;background:#94A3B8;margin-left:2px" title="1 запланировано"></div>
<div style="flex:1;background:#EF4444;margin-left:2px" title="1 просрочка"></div>
</div>
<div style="display:flex;gap:12px;margin-top:8px">
${[{c:'#10B981',l:'3 выполнено'},{c:'#3B82F6',l:'1 в пути'},{c:'#94A3B8',l:'1 план.'},{c:'#EF4444',l:'1 просрочка'}]
.map(s=>`<span style="display:flex;align-items:center;gap:4px;font-size:10px;color:var(--muted)"><span style="width:6px;height:6px;border-radius:50%;background:${s.c};display:inline-block"></span>${s.l}</span>`).join('')}
</div>
</div>
</div>
</div>`;
}
// ─── НАСТРОЙКИ ─────────────────────────────────────────────────────
function _sSettings(){
var roles=[
['👤','Собственник','Полный доступ, все отчёты','1'],
['🏢','Директор', 'Все модули, кроме биллинга','1'],
['📋','Менеджер', 'Заказы, клиенты, KB','2'],
['🔧','Сборщик', 'Свои сборки, замеры','3'],
['📐','Замерщик', 'Объекты, замеры','2'],
['👤','Клиент', 'Статус заказа, акты','—'],
];
var notifs=[
['Просрочки монтажа','Вкл','g'],
['Крупные заказы > 100 тыс','Вкл','g'],
['Дебиторка > 30 дней','Вкл','g'],
['Еженедельный P&L','Пн 09:00','b'],
['Новый крупный клиент','Вкл','g'],
];
return '<div class="page">'
+'<div style="padding:20px 16px 12px;background:var(--card);border-bottom:1px solid rgba(0,0,0,.06)">'
+ '<div style="font-size:17px;font-weight:800;color:var(--ink)">Настройки</div>'
+'</div>'
+'<div class="section-label">Тарифный план</div>'
+'<div style="padding:0 16px"><div class="card ok-b">'
+ '<div style="display:flex;justify-content:space-between;align-items:center">'
+ '<div>'
+ '<div style="font-size:15px;font-weight:700;color:var(--ink)">Тариф «Профи»</div>'
+ '<div style="font-size:12px;color:var(--muted);margin-top:2px">До 20 сотрудников · Все модули</div>'
+ '</div>'
+ '<span class="badge-sm bg">Активен</span>'
+ '</div>'
+ '<div class="stat-row" style="margin-top:12px"><span class="stat-l">Следующий платёж</span><span class="stat-v">01.06.2026</span></div>'
+ '<div class="stat-row"><span class="stat-l">Сумма</span><span class="stat-v b">4 900 ₽/мес</span></div>'
+ '<div class="stat-row"><span class="stat-l">Сотрудников</span><span class="stat-v">8 / 20</span></div>'
+'</div></div>'
+'<div class="section-label">Роли и доступ</div>'
+'<div style="padding:0 16px"><div class="card" style="padding:0">'
+roles.map(r=>'<div style="display:flex;align-items:center;padding:11px 16px;border-bottom:1px solid rgba(0,0,0,.05)">'
+'<div style="font-size:18px;margin-right:10px">'+r[0]+'</div>'
+'<div style="flex:1">'
+ '<div style="font-size:13px;font-weight:600;color:var(--ink)">'+r[1]+'</div>'
+ '<div style="font-size:11px;color:var(--muted);margin-top:1px">'+r[2]+'</div>'
+'</div>'
+'<span class="badge-sm" style="background:var(--bg);color:var(--muted);border:1px solid rgba(0,0,0,.09)">'+r[3]+'</span>'
+'</div>').join('')
+'</div></div>'
+'<div class="section-label">Уведомления</div>'
+'<div style="padding:0 16px"><div class="card" style="padding:0">'
+notifs.map(n=>'<div style="display:flex;align-items:center;padding:11px 16px;border-bottom:1px solid rgba(0,0,0,.05)">'
+'<div style="flex:1;font-size:13px;color:var(--ink)">'+n[0]+'</div>'
+'<span class="badge-sm '+(n[2]==='g'?'bg':'bb')+'">'+n[1]+'</span>'
+'</div>').join('')
+'</div></div>'
+'</div>';
}
_render();
</script>
</body>
</html>