wasrusgen1-crm/docs/mockup_commercial.html

799 lines
55 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 4px;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:9px;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}
.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}
.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}
.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}
.kpi-delta{font-size:11px;font-weight:700;margin-top:7px}
.kpi-delta.up{color:var(--success)}
.kpi-delta.dn{color:var(--danger)}
.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}
.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}
.pill-row{display:flex;gap:6px;padding:0 16px;margin-bottom:12px;flex-wrap:wrap}
.pill{padding:5px 12px;border-radius:20px;font-size:11px;font-weight:700;cursor:pointer;transition:all .18s;border:1.5px solid transparent}
.pill.active{background:var(--accent);color:#fff}
.pill.inactive{background:rgba(0,0,0,.05);color:var(--muted)}
</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-size:12px;color:#8A94A6;font-family:Inter,system-ui,sans-serif">Коммерческий директор</span>
</div>
<div style="height:44px"></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">
<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';
window._ordFilter = window._ordFilter || 'all';
window._ordSalon = window._ordSalon || 'all';
window._prcFilter = window._prcFilter || 'pending';
window._mgrExp = window._mgrExp || null;
window._period = window._period || 'Май';
window._prcDone = window._prcDone || {};
function _toast(msg,col){
var t=document.createElement('div');
t.textContent=msg;
t.style.cssText='position:fixed;bottom:90px;left:50%;transform:translateX(-50%);background:'+(col||'#1A1A2E')+';color:#fff;padding:10px 20px;border-radius:20px;font-size:13px;font-weight:600;z-index:9999;white-space:nowrap;box-shadow:0 4px 20px rgba(0,0,0,.25)';
document.body.appendChild(t);
setTimeout(function(){t.style.opacity='0';t.style.transition='opacity .4s';},1800);
setTimeout(function(){t.remove();},2200);
}
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();
}
// ─── ИКОНКИ ────────────────────────────────────────────────────────
var _ICONS={
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>',
funnel:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><polygon points="22 3 2 3 10 12.46 10 19 14 21 14 12.46 22 3"/></svg>',
list:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><line x1="8" y1="6" x2="21" y2="6"/><line x1="8" y1="12" x2="21" y2="12"/><line x1="8" y1="18" x2="21" y2="18"/><line x1="3" y1="6" x2="3.01" y2="6"/><line x1="3" y1="12" x2="3.01" y2="12"/><line x1="3" y1="18" x2="3.01" y2="18"/></svg>',
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>',
bag:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="M6 2L3 6v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V6l-3-4z"/><line x1="3" y1="6" x2="21" y2="6"/><path d="M16 10a4 4 0 0 1-8 0"/></svg>',
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>',
check:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>',
alert:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><triangle/><path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>',
};
// ─── НАВ ────────────────────────────────────────────────────────────
function _nb(){
var items=[
{id:'home', icon:'chart', label:'Главная'},
{id:'funnel', icon:'funnel', label:'Воронка'},
{id:'orders', icon:'list', label:'Заказы'},
{id:'managers',icon:'users', label:'Персонал'},
{id:'purchases',icon:'bag', label:'Закупки'},
{id:'finance', icon:'wallet', 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==='funnel') return _sFunnel();
if(window._sc==='orders') return _sOrders();
if(window._sc==='managers') return _sManagers();
if(window._sc==='purchases')return _sPurchases();
if(window._sc==='finance') return _sFinance();
return _sHome();
}
// ─── ДАННЫЕ ─────────────────────────────────────────────────────────
var _orders=[
{id:'КЧ-2847', client:'ООО «РемСтрой»', sum:187000, stage:'mount', salon:'Ленина', mgr:'Дмитрий К.', date:'22 мая', overdue:false},
{id:'ЗМ-2831', client:'Надежда Орлова', sum:142000, stage:'contract',salon:'Победы', mgr:'Ольга Р.', date:'19 мая', overdue:false},
{id:'КЧ-2819', client:'ИП Сидоров', sum:128500, stage:'kp', salon:'Ленина', mgr:'Ольга Р.', date:'18 мая', overdue:false},
{id:'ЗМ-2841', client:'Анна Белова', sum:113000, stage:'mount', salon:'Победы', mgr:'Ольга Р.', date:'14 мая', overdue:true},
{id:'КЧ-2798', client:'Сергей Павлов', sum:98500, stage:'mount', salon:'Ленина', mgr:'Дмитрий К.', date:'13 мая', overdue:true},
{id:'ЗМ-2856', client:'Марина Фролова', sum:87000, stage:'kp', salon:'Победы', mgr:'Дмитрий К.', date:'24 мая', overdue:false},
{id:'КЧ-2861', client:'ООО «ДомСтрой»', sum:76500, stage:'contract',salon:'Ленина', mgr:'Дмитрий К.', date:'23 мая', overdue:false},
{id:'ЗМ-2863', client:'Алексей Громов', sum:68000, stage:'lead', salon:'Победы', mgr:'Ольга Р.', date:'25 мая', overdue:false},
{id:'КЧ-2867', client:'Татьяна Соколова', sum:54000, stage:'kp', salon:'Ленина', mgr:'Дмитрий К.', date:'26 мая', overdue:false},
{id:'ЗМ-2871', client:'Виктор Попов', sum:48500, stage:'lead', salon:'Победы', mgr:'Ольга Р.', date:'27 мая', overdue:false},
];
var _stageMap={
lead: {label:'Лид', color:'#94A3B8', bg:'#F1F5F9'},
kp: {label:'КП', color:'#F59E0B', bg:'#FFFBEB'},
contract:{label:'Договор', color:'#3B82F6', bg:'#EFF6FF'},
mount: {label:'Монтаж', color:'#10B981', bg:'#ECFDF5'},
done: {label:'Выполнен', color:'#76BD22', bg:'#F0FDF4'},
};
// ─── ГЛАВНАЯ ────────────────────────────────────────────────────────
var _PERIODS={
'Май': {plan:3200000, fact:2847000, orders:47, avgCheck:51800, overdue:2, newLeads:26, growth:18,
convs:[{from:'Лид',to:'КП',pct:61,norm:65},{from:'КП',to:'Договор',pct:48,norm:55},{from:'Договор',to:'Монтаж',pct:91,norm:85}],
sources:[{name:'Рекомендации',pct:42,cnt:11,color:'#10B981'},{name:'Авито',pct:28,cnt:7,color:'#3B82F6'},{name:'Instagram',pct:18,cnt:5,color:'#8B5CF6'},{name:'Сайт / SEO',pct:12,cnt:3,color:'#F59E0B'}]},
'Апр': {plan:2900000, fact:2410000, orders:41, avgCheck:48600, overdue:1, newLeads:22, growth:8,
convs:[{from:'Лид',to:'КП',pct:65,norm:65},{from:'КП',to:'Договор',pct:52,norm:55},{from:'Договор',to:'Монтаж',pct:88,norm:85}],
sources:[{name:'Рекомендации',pct:38,cnt:9,color:'#10B981'},{name:'Авито',pct:32,cnt:7,color:'#3B82F6'},{name:'Instagram',pct:20,cnt:5,color:'#8B5CF6'},{name:'Сайт / SEO',pct:10,cnt:2,color:'#F59E0B'}]},
'Мар': {plan:2700000, fact:2231000, orders:36, avgCheck:45300, overdue:3, newLeads:18, growth:-2,
convs:[{from:'Лид',to:'КП',pct:58,norm:65},{from:'КП',to:'Договор',pct:45,norm:55},{from:'Договор',to:'Монтаж',pct:83,norm:85}],
sources:[{name:'Рекомендации',pct:40,cnt:7,color:'#10B981'},{name:'Авито',pct:25,cnt:5,color:'#3B82F6'},{name:'Instagram',pct:22,cnt:4,color:'#8B5CF6'},{name:'Сайт / SEO',pct:13,cnt:2,color:'#F59E0B'}]},
};
function _sHome(){
var d=_PERIODS[window._period]||_PERIODS['Май'];
var totalPlan=d.plan, totalFact=d.fact;
var pct=Math.round(totalFact/totalPlan*100);
var overdue=d.overdue;
var overdueSum=_orders.filter(function(o){return o.overdue;}).reduce(function(s,o){return s+o.sum;},0);
var sources=d.sources;
var convs=d.convs;
var periodLabels=['Май','Апр','Мар'];
var prevP={'Май':'апрелю','Апр':'марту','Мар':'февралю'};
return '<div class="page">'
// Период-свитчер
+'<div style="display:flex;gap:6px;padding:10px 16px 0;background:var(--card);border-bottom:1px solid rgba(0,0,0,.06)">'
+periodLabels.map(function(p){
var act=window._period===p;
return '<div onclick="window._period=\''+p+'\';_render()" style="padding:7px 18px;border-radius:20px 20px 0 0;font-size:12px;font-weight:700;cursor:pointer;border-bottom:2px solid '+(act?'var(--accent)':'transparent')+';color:'+(act?'var(--accent)':'var(--muted)')+'">'+p+'</div>';
}).join('')
+'</div>'
+'<div class="hero-grad">'
+ '<div style="font-size:11px;font-weight:700;opacity:.65;text-transform:uppercase;letter-spacing:.08em;margin-bottom:6px">'+window._period.toUpperCase()+' 2026 · Коммерческий директор</div>'
+ '<div style="font-size:28px;font-weight:800;letter-spacing:-.03em">'+totalFact.toLocaleString('ru')+' ₽</div>'
+ '<div style="font-size:13px;opacity:.8;margin-top:4px">Выручка · <span style="color:'+(d.growth>=0?'#76BD22':'#FCA5A5')+';font-weight:700">'+(d.growth>=0?'▲ +':'▼ ')+Math.abs(d.growth)+'%</span> к '+prevP[window._period]+'</div>'
+ '<div style="margin-top:14px">'
+ '<div style="display:flex;justify-content:space-between;margin-bottom:5px">'
+ '<span style="font-size:11px;opacity:.7">План: '+totalPlan.toLocaleString('ru')+' ₽</span>'
+ '<span style="font-size:12px;font-weight:800;color:'+(pct>=90?'#76BD22':'#FCD34D')+'">'+pct+'%</span>'
+ '</div>'
+ '<div style="height:6px;background:rgba(255,255,255,.2);border-radius:3px;overflow:hidden">'
+ '<div style="height:100%;width:'+pct+'%;background:'+(pct>=90?'#76BD22':'#FCD34D')+';border-radius:3px;transition:.4s"></div>'
+ '</div>'
+ '</div>'
+'</div>'
+'<div class="kpi-grid">'
+ '<div class="kpi-card"><div class="kpi-value">'+d.orders+'</div><div class="kpi-label">Заказов в работе</div><div class="kpi-delta up">план '+Math.round(d.plan/1000)+' тыс</div></div>'
+ '<div class="kpi-card"><div class="kpi-value">'+Math.round(d.avgCheck/1000)+' тыс ₽</div><div class="kpi-label">Средний чек</div><div class="kpi-delta up">▲ факт</div></div>'
+ '<div class="kpi-card"><div class="kpi-value" style="color:var(--danger)">'+overdue+'</div><div class="kpi-label">Просрочено</div><div class="kpi-delta dn">'+Math.round(overdueSum/1000)+' тыс риск</div></div>'
+ '<div class="kpi-card"><div class="kpi-value">'+d.newLeads+'</div><div class="kpi-label">Новых лидов</div><div class="kpi-delta up">за '+window._period+'</div></div>'
+'</div>'
// Конверсии — быстрый взгляд
+'<div class="section-label">Конверсии · '+window._period+'</div>'
+'<div style="padding:0 16px"><div class="card" style="padding:14px 16px">'
+convs.map(function(c){
var ok=c.pct>=c.norm;
return '<div style="display:flex;align-items:center;padding:7px 0;border-bottom:1px solid rgba(0,0,0,.05)">'
+'<div style="width:130px;font-size:12px;color:var(--muted)">'+c.from+' → '+c.to+'</div>'
+'<div style="flex:1;height:5px;background:rgba(0,0,0,.07);border-radius:3px;overflow:hidden;margin:0 10px">'
+ '<div style="height:100%;width:'+c.pct+'%;background:'+(ok?'var(--success)':'var(--danger)')+';border-radius:3px"></div>'
+'</div>'
+'<div style="font-size:13px;font-weight:800;color:'+(ok?'var(--success)':'var(--danger)')+'">'+c.pct+'%</div>'
+'<div style="font-size:10px;color:var(--muted);margin-left:4px;width:46px">норма '+c.norm+'%</div>'
+'</div>';
}).join('')
+'</div></div>'
// Источники лидов
+'<div class="section-label">Источники лидов · '+window._period+'</div>'
+'<div style="padding:0 16px"><div class="card" style="padding:14px 16px">'
+sources.map(function(s){
return '<div style="display:flex;align-items:center;gap:10px;margin-bottom:10px">'
+'<div style="width:100px;font-size:12px;color:var(--muted);font-weight:500">'+s.name+'</div>'
+'<div style="flex:1;height:6px;background:rgba(0,0,0,.07);border-radius:3px;overflow:hidden">'
+ '<div style="height:100%;width:'+s.pct+'%;background:'+s.color+';border-radius:3px"></div>'
+'</div>'
+'<div style="font-size:12px;font-weight:700;color:var(--ink);width:28px;text-align:right">'+s.pct+'%</div>'
+'<div style="font-size:11px;color:var(--muted);width:32px;text-align:right">'+s.cnt+' лид</div>'
+'</div>';
}).join('')
+'</div></div>'
// Просроченные заказы
+(overdue>0
? '<div class="section-label">🔴 Просроченные заказы</div>'
+'<div style="padding:0 16px">'
+_orders.filter(o=>o.overdue).map(function(o){
return '<div class="card" style="padding:12px 14px;margin-bottom:8px;border-left:3px solid var(--danger)">'
+'<div style="display:flex;justify-content:space-between;align-items:flex-start">'
+ '<div>'
+ '<div style="display:flex;align-items:center;gap:6px">'
+ '<span style="font-size:12px;font-weight:700;color:var(--accent)">'+o.id+'</span>'
+ '<span style="font-size:10px;font-weight:700;color:var(--danger)">⚠ просрочка</span>'
+ '</div>'
+ '<div style="font-size:13px;font-weight:600;color:var(--ink);margin-top:2px">'+o.client+'</div>'
+ '<div style="font-size:11px;color:var(--muted);margin-top:1px">'+o.mgr+' · Салон '+o.salon+'</div>'
+ '</div>'
+ '<div style="text-align:right">'
+ '<div style="font-size:13px;font-weight:700;color:var(--ink)">'+o.sum.toLocaleString('ru')+' ₽</div>'
+ '<div style="font-size:10px;color:var(--muted);margin-top:2px">с '+o.date+'</div>'
+ '</div>'
+'</div>'
+'</div>';
}).join('')
+'</div>'
: '')
+'</div>';
}
// ─── ВОРОНКА ────────────────────────────────────────────────────────
function _sFunnel(){
var stages=[
{id:'lead', label:'Лиды', cnt:26, sum:1340000, norm:null, conv:null, normConv:null},
{id:'kp', label:'КП', cnt:16, sum:877500, norm:null, conv:61, normConv:65},
{id:'contract',label:'Договор', cnt:11, sum:624000, norm:null, conv:48, normConv:55},
{id:'mount', label:'Монтаж', cnt:10, sum:568000, norm:null, conv:91, normConv:85},
{id:'done', label:'Выполнен', cnt:47, sum:2847000, norm:null, conv:100, normConv:95},
];
var maxCnt=stages[0].cnt;
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">26</div><div class="kpi-label">Лидов за месяц</div><div class="kpi-delta up">▲ +4 к апр.</div></div>'
+ '<div class="kpi-card"><div class="kpi-value" style="color:'+(48>=55?'var(--success)':'var(--danger)')+'">48%</div><div class="kpi-label">КП→Договор</div><div class="kpi-delta dn">норма 55%</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">9.2</div><div class="kpi-label">Дней до договора</div><div class="kpi-delta up">▼ 1.3 дн</div></div>'
+'</div>'
+'<div class="section-label">Стадии воронки</div>'
+'<div style="padding:0 16px">'
+stages.map(function(st,i){
var w=Math.round(st.cnt/maxCnt*100);
var sc=_stageMap[st.id];
var convOk=st.conv===null?null:st.conv>=st.normConv;
return '<div style="margin-bottom:6px">'
// Бар воронки
+'<div style="display:flex;align-items:center;gap:10px;margin-bottom:4px">'
+ '<div style="width:72px;font-size:11px;font-weight:700;color:'+sc.color+'">'+st.label+'</div>'
+ '<div style="flex:1;height:32px;background:'+sc.bg+';border-radius:8px;overflow:hidden;position:relative">'
+ '<div style="height:100%;width:'+w+'%;background:'+sc.color+'22;border-radius:8px;position:absolute"></div>'
+ '<div style="position:absolute;left:10px;top:50%;transform:translateY(-50%);display:flex;align-items:baseline;gap:6px">'
+ '<span style="font-size:15px;font-weight:800;color:'+sc.color+'">'+st.cnt+'</span>'
+ '<span style="font-size:10px;color:var(--muted)">заказов</span>'
+ '</div>'
+ '<div style="position:absolute;right:10px;top:50%;transform:translateY(-50%);font-size:11px;font-weight:700;color:var(--muted)">'+Math.round(st.sum/1000)+' тыс ₽</div>'
+ '</div>'
+'</div>'
// Конверсия между стадиями
+(i>0 && st.conv!==null
? '<div style="display:flex;align-items:center;gap:6px;padding:0 0 4px 76px">'
+ '<div style="width:1px;height:10px;background:rgba(0,0,0,.1);margin-left:16px"></div>'
+ '<span style="font-size:11px;font-weight:700;color:'+(convOk?'var(--success)':'var(--danger)')+'">'+st.conv+'%</span>'
+ '<span style="font-size:10px;color:var(--muted)">конверсия · норма '+st.normConv+'%</span>'
+ '<span style="font-size:10px;font-weight:700;color:'+(convOk?'var(--success)':'var(--danger)')+'">'+( convOk?'✓':'↓ -'+(st.normConv-st.conv)+'пп')+'</span>'
+'</div>'
: '')
+'</div>';
}).join('')
+'</div>'
// По салонам
+'<div class="section-label">Воронка по салонам</div>'
+'<div style="padding:0 16px"><div class="card" style="padding:14px 16px">'
+[
{name:'Салон Ленина', color:'#3B82F6', leads:15, kp:9, contracts:7, done:27, conv:47},
{name:'Салон Победы', color:'#8B5CF6', leads:11, kp:7, contracts:4, done:20, conv:36},
].map(function(s){
return '<div style="margin-bottom:14px;padding-bottom:14px;border-bottom:1px solid rgba(0,0,0,.05)">'
+'<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:8px">'
+ '<span style="font-size:13px;font-weight:700;color:'+s.color+'">'+s.name+'</span>'
+ '<span style="font-size:11px;color:var(--muted)">КП→Дог: <b style="color:'+(s.conv>=55?'var(--success)':'var(--danger)')+'">'+s.conv+'%</b></span>'
+'</div>'
+'<div style="display:flex;gap:6px">'
+[
{l:'Лиды', v:s.leads, c:'#94A3B8'},
{l:'КП', v:s.kp, c:'#F59E0B'},
{l:'Дог.', v:s.contracts,c:'#3B82F6'},
{l:'Сдано', v:s.done, c:'#10B981'},
].map(function(st){
return '<div style="flex:1;text-align:center">'
+'<div style="font-size:16px;font-weight:800;color:'+st.c+'">'+st.v+'</div>'
+'<div style="font-size:9px;color:var(--muted);margin-top:2px">'+st.l+'</div>'
+'</div>';
}).join('')
+'</div>'
+'</div>';
}).join('')
+'</div></div>'
+'</div>';
}
// ─── ЗАКАЗЫ ─────────────────────────────────────────────────────────
function _sOrders(){
var stageFilters=[
{id:'all', label:'Все'},
{id:'overdue', label:'⚠ Просрочка'},
{id:'mount', label:'Монтаж'},
{id:'contract', label:'Договор'},
{id:'kp', label:'КП'},
{id:'lead', label:'Лид'},
];
var salons=[{id:'all',label:'Все салоны'},{id:'Ленина',label:'Ленина'},{id:'Победы',label:'Победы'}];
var filtered=_orders.filter(function(o){
var sf=window._ordFilter;
var ss=window._ordSalon;
var stageOk = sf==='all' ? true : sf==='overdue' ? o.overdue : o.stage===sf;
var salonOk = ss==='all' ? true : o.salon===ss;
return stageOk && salonOk;
});
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">'+filtered.length+' из '+_orders.length+' · май 2026</div>'
+'</div>'
// Фильтр по стадии
+'<div style="overflow-x:auto;scrollbar-width:none;padding:10px 16px 0">'
+'<div style="display:flex;gap:6px;width:max-content">'
+stageFilters.map(function(f){
var act=window._ordFilter===f.id;
return '<div onclick="window._ordFilter=\''+f.id+'\';_render()" style="padding:5px 12px;border-radius:20px;font-size:11px;font-weight:700;cursor:pointer;white-space:nowrap;'
+(act?'background:var(--accent);color:#fff':'background:rgba(0,0,0,.06);color:var(--muted)')+'">'+f.label+'</div>';
}).join('')
+'</div></div>'
// Фильтр по салону
+'<div style="display:flex;gap:6px;padding:8px 16px 12px">'
+salons.map(function(s){
var act=window._ordSalon===s.id;
return '<div onclick="window._ordSalon=\''+s.id+'\';_render()" style="padding:4px 10px;border-radius:20px;font-size:11px;font-weight:600;cursor:pointer;'
+(act?'background:var(--accent);color:#fff':'background:rgba(0,0,0,.06);color:var(--muted)')+'">'+s.label+'</div>';
}).join('')
+'</div>'
// Список
+'<div style="padding:0 16px"><div class="card" style="padding:0">'
+(filtered.length===0
? '<div style="padding:24px;text-align:center;color:var(--muted);font-size:13px">Нет заказов по фильтру</div>'
: filtered.map(function(o,i){
var sc=_stageMap[o.stage];
return '<div style="display:flex;align-items:center;padding:12px 14px;border-bottom:'+(i<filtered.length-1?'1px solid rgba(0,0,0,.05)':'none')+';cursor:pointer">'
+'<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.overdue?'<span style="font-size:10px;font-weight:700;color:var(--danger)">⚠ просрочка</span>':'')
+ '</div>'
+ '<div style="font-size:13px;font-weight:600;color:var(--ink);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.mgr+' · Салон '+o.salon+'</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.toLocaleString('ru')+' ₽</div>'
+ '<div style="display:inline-flex;margin-top:3px;background:'+sc.bg+';border-radius:6px;padding:2px 7px">'
+ '<span style="font-size:10px;font-weight:700;color:'+sc.color+'">'+sc.label+'</span>'
+ '</div>'
+'</div>'
+'</div>';
}).join(''))
+'</div></div>'
+'</div>';
}
// ─── МЕНЕДЖЕРЫ ──────────────────────────────────────────────────────
function _sManagers(){
var admins=[
{name:'Анна М.', salon:'Салон Ленина', role:'Администратор',
orders:27, ordersPlan:30, revenue:1537000, revenuePlan:1700000,
overdue:1, overdueRisk:98500, purchases:2, managers:2, newLeads:14, status:'warn'},
{name:'Ирина С.', salon:'Салон Победы', role:'Администратор',
orders:20, ordersPlan:22, revenue:1310000, revenuePlan:1500000,
overdue:1, overdueRisk:113000, purchases:1, managers:1, newLeads:12, status:'warn'},
];
var sc={ok:'var(--success)',bad:'var(--danger)',warn:'var(--warn)'};
var bg={ok:'#F0FDF4',bad:'#FEF2F2',warn:'#FFFBEB'};
var totOrders =admins.reduce(function(s,a){return s+a.orders;},0);
var totPlan =admins.reduce(function(s,a){return s+a.ordersPlan;},0);
var totRev =admins.reduce(function(s,a){return s+a.revenue;},0);
var totRevPlan=admins.reduce(function(s,a){return s+a.revenuePlan;},0);
var totOverdue=admins.reduce(function(s,a){return s+a.overdue;},0);
var totPurch =admins.reduce(function(s,a){return s+a.purchases;},0);
var revPct =Math.round(totRev/totRevPlan*100);
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">'+admins.length+' администратора · май 2026</div>'
+'</div>'
+'<div class="kpi-grid">'
+ '<div class="kpi-card"><div class="kpi-value">'+totOrders+'</div><div class="kpi-label">Заказов всего</div>'
+ '<div class="kpi-delta '+(totOrders>=totPlan?'up':'dn')+'">'+totOrders+' из '+totPlan+' по плану</div></div>'
+ '<div class="kpi-card"><div class="kpi-value" style="font-size:18px">'+(totRev/1000000).toFixed(1)+' млн</div>'
+ '<div class="kpi-label">Выручка факт</div>'
+ '<div class="kpi-delta '+(revPct>=100?'up':'dn')+'">'+revPct+'% от плана</div></div>'
+'</div>'
+(totOverdue>0||totPurch>0
? '<div style="padding:0 16px 4px"><div style="background:#FFFBEB;border-radius:12px;padding:11px 14px;display:flex;gap:16px;flex-wrap:wrap">'
+(totOverdue>0?'<span style="font-size:12px;color:#92400E">⚠ Просрочено: <b>'+totOverdue+'</b> заказа</span>':'')
+(totPurch>0?'<span style="font-size:12px;color:#1D4ED8">📋 Закупки: <b>'+totPurch+'</b> ждут</span>':'')
+'</div></div>'
: '')
+'<div class="section-label">Администраторы салонов</div>'
+'<div style="padding:0 16px">'
+admins.map(function(a){
var ordPct =Math.round(a.orders/a.ordersPlan*100);
var revPctA=Math.round(a.revenue/a.revenuePlan*100);
var sColor =sc[a.status];
return '<div class="card" style="padding:14px 16px;margin-bottom:10px;border-left:3px solid '+sColor+'">'
+'<div style="display:flex;align-items:flex-start;justify-content:space-between;margin-bottom:10px">'
+ '<div><div style="font-size:15px;font-weight:700;color:var(--ink)">'+a.name+'</div>'
+ '<div style="font-size:11px;color:var(--muted);margin-top:1px">'+a.role+' · '+a.salon+'</div></div>'
+ '<div style="background:'+bg[a.status]+';color:'+sColor+';border-radius:20px;padding:3px 10px;font-size:11px;font-weight:600">'
+ (a.status==='ok'?'✓ Норма':a.status==='warn'?'⚠ Внимание':'✕ Проблема')
+ '</div>'
+'</div>'
+'<div style="display:grid;grid-template-columns:repeat(3,1fr);gap:8px;margin-bottom:10px">'
+ '<div style="background:var(--bg);border-radius:10px;padding:9px 10px;text-align:center">'
+ '<div style="font-size:20px;font-weight:800;color:var(--ink);line-height:1">'+a.orders+'</div>'
+ '<div style="font-size:9px;color:var(--muted);margin-top:2px;text-transform:uppercase;letter-spacing:.04em">заказов</div>'
+ '<div style="font-size:10px;color:'+(ordPct>=100?'var(--success)':'var(--danger)')+';font-weight:600;margin-top:1px">'+ordPct+'% плана</div>'
+ '</div>'
+ '<div style="background:var(--bg);border-radius:10px;padding:9px 10px;text-align:center">'
+ '<div style="font-size:15px;font-weight:800;color:var(--ink);line-height:1">'+(a.revenue/1000).toLocaleString()+' тыс</div>'
+ '<div style="font-size:9px;color:var(--muted);margin-top:2px;text-transform:uppercase;letter-spacing:.04em">выручка ₽</div>'
+ '<div style="font-size:10px;color:'+(revPctA>=100?'var(--success)':'var(--danger)')+';font-weight:600;margin-top:1px">'+revPctA+'% плана</div>'
+ '</div>'
+ '<div style="background:var(--bg);border-radius:10px;padding:9px 10px;text-align:center">'
+ '<div style="font-size:20px;font-weight:800;color:var(--accent);line-height:1">'+a.newLeads+'</div>'
+ '<div style="font-size:9px;color:var(--muted);margin-top:2px;text-transform:uppercase;letter-spacing:.04em">лидов</div>'
+ '<div style="font-size:10px;color:var(--muted);margin-top:1px">'+a.managers+' менедж.</div>'
+ '</div>'
+'</div>'
+(a.overdue>0||a.purchases>0
? '<div style="display:flex;gap:8px;flex-wrap:wrap">'
+(a.overdue>0?'<div style="display:flex;align-items:center;gap:4px;background:#FEF2F2;border-radius:8px;padding:5px 10px">'
+'<span style="font-size:11px">⚠️</span>'
+'<span style="font-size:11px;color:#991B1B">Просрочено: <b>'+a.overdue+'</b> · риск '+Math.round(a.overdueRisk/1000)+' тыс ₽</span></div>':'')
+(a.purchases>0?'<div style="display:flex;align-items:center;gap:4px;background:#EFF6FF;border-radius:8px;padding:5px 10px">'
+'<span style="font-size:11px">📋</span>'
+'<span style="font-size:11px;color:#1E40AF">Закупки: <b>'+a.purchases+'</b> заявки</span></div>':'')
+'</div>'
: '<div style="padding:5px 10px;background:#F0FDF4;border-radius:8px;font-size:11px;color:var(--success);font-weight:600">✅ Всё в норме</div>')
+'</div>';
}).join('')
+'</div>'
+'</div>';
}
// ─── ЗАКУПКИ ─────────────────────────────────────────────────────────
function _sPurchases(){
var pending=[
{id:'ЗАК-041', salon:'Ленина', who:'Анна М.', item:'Расходники (скотч, упаковка)', sum:4800, date:'26 мая', urgent:false},
{id:'ЗАК-042', salon:'Победы', who:'Ирина С.', item:'Картридж + бумага А4 (3 пач)', sum:3200, date:'27 мая', urgent:false},
{id:'ЗАК-043', salon:'Ленина', who:'Анна М.', item:'Чистящие средства · 12 позиций', sum:6700, date:'27 мая', urgent:true},
];
// Расходы по категориям: апрель vs май
var cats=[
{name:'Расходники', apr:18400, may:44200, norm:20000},
{name:'Хозтовары', apr:8200, may:9100, norm:10000},
{name:'Канцелярия', apr:3100, may:2800, norm:4000},
{name:'Реклама/ПОС', apr:12000, may:14500, norm:15000},
];
var maxVal=Math.max.apply(null, cats.map(function(c){return Math.max(c.apr,c.may);}));
// Аномалии
var anomalies=cats.filter(function(c){return c.may>c.norm*1.2;});
var tabs=[{id:'pending',label:'На согласовании ('+pending.length+')'},{id:'analytics',label:'Аналитика'}];
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" style="color:var(--warn)">'+pending.length+'</div><div class="kpi-label">Ожидают решения</div><div class="kpi-delta dn">14 700 ₽ суммарно</div></div>'
+ '<div class="kpi-card"><div class="kpi-value" style="color:var(--danger)">'+anomalies.length+'</div><div class="kpi-label">Аномалий расходов</div><div class="kpi-delta dn">выше нормы на 20%+</div></div>'
+ '<div class="kpi-card"><div class="kpi-value">74 300 ₽</div><div class="kpi-label">Расходов за май</div><div class="kpi-delta dn">▲ +34% к апр.</div></div>'
+ '<div class="kpi-card"><div class="kpi-value">55 400 ₽</div><div class="kpi-label">Расходов за апрель</div><div class="kpi-delta up">В норме</div></div>'
+'</div>'
// Таб-переключатель
+'<div style="display:flex;padding:0 16px;gap:6px;margin-bottom:4px">'
+tabs.map(function(t){
var act=window._prcFilter===t.id;
return '<div onclick="window._prcFilter=\''+t.id+'\';_render()" style="padding:7px 14px;border-radius:20px;font-size:11px;font-weight:700;cursor:pointer;'
+(act?'background:var(--accent);color:#fff':'background:rgba(0,0,0,.06);color:var(--muted)')+'">'+t.label+'</div>';
}).join('')
+'</div>'
// Заявки на согласование
+(window._prcFilter==='pending'
? '<div style="padding:0 16px">'
+pending.map(function(p){
return '<div class="card" style="padding:14px;margin-bottom:8px;'+(p.urgent?'border-left:3px solid var(--warn)':'')+'">'
+'<div style="display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:8px">'
+ '<div>'
+ '<div style="display:flex;align-items:center;gap:6px">'
+ '<span style="font-size:12px;font-weight:700;color:var(--accent)">'+p.id+'</span>'
+ (p.urgent?'<span style="font-size:10px;font-weight:700;color:var(--warn)">⚡ срочно</span>':'')
+ '</div>'
+ '<div style="font-size:11px;color:var(--muted);margin-top:2px">Салон '+p.salon+' · '+p.who+' · '+p.date+'</div>'
+ '</div>'
+ '<div style="font-size:15px;font-weight:800;color:var(--ink)">'+p.sum.toLocaleString('ru')+' ₽</div>'
+'</div>'
+'<div style="font-size:13px;color:var(--ink);margin-bottom:12px">'+p.item+'</div>'
+(window._prcDone[p.id]
? '<div style="padding:7px 12px;background:#ECFDF5;border-radius:10px;font-size:12px;font-weight:700;color:var(--success);text-align:center">'
+(window._prcDone[p.id]==='ok'?'✅ Согласовано':'❌ Отклонено')
+'</div>'
: '<div style="display:flex;gap:8px">'
+'<button onclick="window._prcDone[\''+p.id+'\']=\'ok\';_toast(\'✅ Согласовано: '+p.id+'\',\'#10B981\');_render()" style="flex:1;padding:8px;background:var(--success);color:#fff;border:none;border-radius:10px;font-size:12px;font-weight:700;cursor:pointer">✓ Согласовать</button>'
+'<button onclick="window._prcDone[\''+p.id+'\']=\'no\';_toast(\'❌ Отклонено: '+p.id+'\',\'#EF4444\');_render()" style="flex:1;padding:8px;background:var(--s-danger-bg);color:var(--danger);border:none;border-radius:10px;font-size:12px;font-weight:700;cursor:pointer">✕ Отклонить</button>'
+'</div>')
+'</div>';
}).join('')
+'</div>'
// Аналитика расходов
: '<div style="padding:0 16px">'
// Аномалии
+(anomalies.length>0
? '<div style="margin-bottom:12px">'
+anomalies.map(function(a){
var diff=Math.round((a.may-a.norm)/a.norm*100);
return '<div style="display:flex;align-items:center;gap:10px;background:#FEF2F2;border-radius:12px;padding:10px 12px;margin-bottom:6px">'
+'<span style="font-size:20px">🚨</span>'
+'<div style="flex:1">'
+ '<div style="font-size:13px;font-weight:700;color:var(--ink)">'+a.name+': +'+diff+'% выше нормы</div>'
+ '<div style="font-size:11px;color:var(--muted);margin-top:1px">'+a.may.toLocaleString('ru')+' ₽ / норма '+a.norm.toLocaleString('ru')+' ₽</div>'
+'</div>'
+'</div>';
}).join('')
+'</div>'
: '')
// График категорий апр vs май
+'<div class="card" style="padding:14px 16px">'
+ '<div style="font-size:12px;font-weight:700;color:var(--ink);margin-bottom:12px">Расходы по категориям</div>'
+ '<div style="display:flex;gap:12px;margin-bottom:8px">'
+ '<div style="display:flex;align-items:center;gap:5px"><span style="width:8px;height:8px;border-radius:2px;background:#94A3B8;display:inline-block"></span><span style="font-size:10px;color:var(--muted)">Апрель</span></div>'
+ '<div style="display:flex;align-items:center;gap:5px"><span style="width:8px;height:8px;border-radius:2px;background:var(--accent);display:inline-block"></span><span style="font-size:10px;color:var(--muted)">Май</span></div>'
+ '<div style="display:flex;align-items:center;gap:5px"><span style="width:8px;height:8px;border-radius:2px;background:var(--warn);display:inline-block;opacity:.5"></span><span style="font-size:10px;color:var(--muted)">Норма</span></div>'
+ '</div>'
+cats.map(function(c){
var aprW=Math.round(c.apr/maxVal*100);
var mayW=Math.round(c.may/maxVal*100);
var normW=Math.round(c.norm/maxVal*100);
var over=c.may>c.norm*1.2;
return '<div style="margin-bottom:12px">'
+'<div style="display:flex;justify-content:space-between;margin-bottom:4px">'
+ '<span style="font-size:12px;font-weight:600;color:var(--ink)">'+c.name+'</span>'
+ '<span style="font-size:11px;font-weight:700;color:'+(over?'var(--danger)':'var(--muted)')+'">'+c.may.toLocaleString('ru')+' ₽'+(over?' ⚠':'')+'</span>'
+'</div>'
// Апрель
+'<div style="height:5px;background:rgba(0,0,0,.06);border-radius:3px;margin-bottom:3px;overflow:hidden">'
+ '<div style="height:100%;width:'+aprW+'%;background:#94A3B8;border-radius:3px"></div>'
+'</div>'
// Май
+'<div style="height:5px;background:rgba(0,0,0,.06);border-radius:3px;margin-bottom:3px;overflow:hidden">'
+ '<div style="height:100%;width:'+mayW+'%;background:'+(over?'var(--danger)':'var(--accent)')+';border-radius:3px"></div>'
+'</div>'
// Норма-маркер
+'<div style="position:relative;height:2px">'
+ '<div style="position:absolute;left:'+normW+'%;top:-4px;width:2px;height:10px;background:var(--warn);border-radius:1px"></div>'
+'</div>'
+'</div>';
}).join('')
+'</div>'
+'</div>')
+'</div>';
}
// ─── ФИНАНСЫ ────────────────────────────────────────────────────────
function _sFinance(){
var salons=[
{name:'Салон Ленина', color:'#3B82F6', plan:1700000, fact:1537000, prev:1280000},
{name:'Салон Победы', color:'#8B5CF6', plan:1500000, fact:1310000, prev:1180000},
];
var debitors=[
{name:'ООО «ДомСтрой»', sum:89000, days:21, mgr:'Дмитрий К.'},
{name:'Козлов А.С.', sum:54000, days:18, mgr:'Ольга Р.'},
{name:'Белова Н.В.', sum:38000, days:16, mgr:'Ольга Р.'},
{name:'Прочие (4)', sum:106000,days:14, mgr:'—'},
];
var totalDebt=debitors.reduce(function(s,d){return s+d.sum;},0);
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">2 847 000 ₽</div><div class="kpi-label">Выручка факт</div><div class="kpi-delta dn">▼ 11% к плану</div></div>'
+ '<div class="kpi-card"><div class="kpi-value">3 200 000 ₽</div><div class="kpi-label">Выручка план</div><div class="kpi-delta up">▲ +18% к апр.</div></div>'
+ '<div class="kpi-card"><div class="kpi-value" style="color:var(--danger)">'+Math.round(totalDebt/1000)+' тыс</div><div class="kpi-label">Дебиторка</div><div class="kpi-delta dn">7 клиентов</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>'
// План/факт по салонам
+'<div class="section-label">Выполнение плана по салонам</div>'
+'<div style="padding:0 16px">'
+salons.map(function(s){
var pct=Math.round(s.fact/s.plan*100);
var growth=Math.round((s.fact-s.prev)/s.prev*100);
return '<div class="card" style="padding:14px 16px;margin-bottom:10px">'
+'<div style="display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:10px">'
+ '<div>'
+ '<div style="font-size:14px;font-weight:700;color:'+s.color+'">'+s.name+'</div>'
+ '<div style="font-size:11px;color:var(--muted);margin-top:2px">Факт: <b style="color:var(--ink)">'+s.fact.toLocaleString('ru')+' ₽</b></div>'
+ '</div>'
+ '<div style="text-align:right">'
+ '<div style="font-size:20px;font-weight:800;color:'+(pct>=95?'var(--success)':pct>=80?'var(--warn)':'var(--danger)')+'">'+pct+'%</div>'
+ '<div style="font-size:10px;color:var(--muted)">от плана</div>'
+ '</div>'
+'</div>'
// Прогресс
+'<div style="height:8px;background:rgba(0,0,0,.07);border-radius:4px;overflow:hidden;margin-bottom:6px">'
+ '<div style="height:100%;width:'+Math.min(pct,100)+'%;background:'+s.color+';border-radius:4px;transition:.4s"></div>'
+'</div>'
+'<div style="display:flex;justify-content:space-between">'
+ '<span style="font-size:10px;color:var(--muted)">План: '+s.plan.toLocaleString('ru')+' ₽</span>'
+ '<span style="font-size:10px;font-weight:700;color:'+(growth>0?'var(--success)':'var(--danger)')+'">'+( growth>0?'▲ +':'')+growth+'% к апр.</span>'
+'</div>'
+'</div>';
}).join('')
+'</div>'
// P&L
+'<div class="section-label">P&amp;L · май 2026</div>'
+'<div style="padding:0 16px"><div class="card" style="padding:14px 16px">'
+[
{label:'Выручка', v:2847000, prev:2410000, type:'rev'},
{label:'Себестоимость', v:-1280000,prev:-1090000,type:'cost'},
{label:'Валовая прибыль', v:1567000, prev:1320000, type:'profit',bold:true},
{label:'Операц. расходы', v:-418000, prev:-380000, type:'cost'},
{label:'Зарплата и %', v:-512000, prev:-442000, type:'cost'},
{label:'EBITDA', v:637000, prev:498000, type:'ebitda',bold:true},
].map(function(row,i){
var delta=row.prev?Math.round((row.v-row.prev)/Math.abs(row.prev)*100):0;
var isPos=row.v>=0;
var color=row.type==='profit'||row.type==='ebitda'?'var(--accent)':row.type==='rev'?'var(--success)':'var(--muted)';
return '<div style="display:flex;align-items:center;padding:8px 0;border-bottom:'+(i<5?'1px solid rgba(0,0,0,.05)':'none')+'">'
+'<div style="flex:1;font-size:'+(row.bold?'13':'12')+'px;font-weight:'+(row.bold?'700':'500')+';color:'+(row.bold?'var(--ink)':'var(--muted)')+'">'+row.label+'</div>'
+'<div style="font-size:'+(row.bold?'14':'13')+'px;font-weight:'+(row.bold?'800':'600')+';color:'+color+';margin-right:10px">'+(isPos?'':'')+Math.abs(row.v).toLocaleString('ru')+' ₽</div>'
+'<div style="font-size:10px;font-weight:700;color:'+(delta>=0?'var(--success)':'var(--danger)');width:46px;text-align:right">'+(delta>=0?'▲ +':'▼ ')+Math.abs(delta)+'%</div>'
+'</div>';
}).join('')
+'</div></div>'
// Маржинальность
+'<div style="padding:0 16px 8px"><div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:8px">'
+[
{label:'Валов. маржа',v:'55%',ok:true},
{label:'EBITDA маржа',v:'22%',ok:true},
{label:'Рост к апр.',v:'+18%',ok:true},
].map(function(m){
return '<div style="background:'+(m.ok?'#ECFDF5':'#FEF2F2')+';border-radius:12px;padding:10px;text-align:center">'
+'<div style="font-size:18px;font-weight:800;color:'+(m.ok?'var(--success)':'var(--danger)')+'">'+m.v+'</div>'
+'<div style="font-size:9px;color:var(--muted);margin-top:2px;font-weight:600">'+m.label+'</div>'
+'</div>';
}).join('')
+'</div></div>'
// Дебиторка
+'<div class="section-label">Дебиторская задолженность · '+totalDebt.toLocaleString('ru')+' ₽</div>'
+'<div style="padding:0 16px"><div class="card" style="padding:0">'
+debitors.map(function(d,i){
var urgentColor=d.days>=21?'var(--danger)':d.days>=16?'var(--warn)':'var(--muted)';
return '<div style="display:flex;align-items:center;padding:12px 14px;border-bottom:'+(i<debitors.length-1?'1px solid rgba(0,0,0,.05)':'none')+'">'
+'<div style="flex:1;min-width:0">'
+ '<div style="font-size:13px;font-weight:600;color:var(--ink);white-space:nowrap;overflow:hidden;text-overflow:ellipsis">'+d.name+'</div>'
+ '<div style="font-size:11px;color:var(--muted);margin-top:1px">'+d.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)">'+d.sum.toLocaleString('ru')+' ₽</div>'
+ '<div style="font-size:10px;font-weight:700;color:'+urgentColor+';margin-top:1px">'+d.days+' дней</div>'
+'</div>'
+'</div>';
}).join('')
+'</div></div>'
+'</div>';
}
// ─── СТАРТ ──────────────────────────────────────────────────────────
window.addEventListener('DOMContentLoaded', function(){_render();});
</script>
</body>
</html>