feat: hierarchy КД→Администратор→Менеджеры accordion в Персонал + _HIERARCHY data

This commit is contained in:
wasrusgen 2026-05-28 22:11:28 +03:00
parent c65d40eb54
commit 74d684346e

View File

@ -460,16 +460,37 @@ function _sOrders(){
+'</div>'; +'</div>';
} }
// ─── ВЕРТИКАЛЬ: КД → АДМИНИСТРАТОР → МЕНЕДЖЕРЫ ─────────────────────
// Единый источник истины — отражает обе стороны (КД и Администратор)
var _HIERARCHY=[
{
salonId:'lenina', salon:'Салон Ленина', color:'#3B82F6',
admin:{name:'Анна М.', short:'АМ'},
orders:27, ordersPlan:30, revenue:1537000, revenuePlan:1700000,
overdue:1, overdueRisk:98500, purchases:2, newLeads:14, status:'warn',
managers:[
{id:'ak', name:'Анна К.', short:'АК', color:'#7C3AED', visits:42, deals:14, revenue:847000, conversion:33, avg:60500, rating:4.8, active:true},
{id:'ms', name:'Мария С.', short:'МС', color:'#0891B2', visits:35, deals:9, revenue:610000, conversion:26, avg:67800, rating:4.5, active:true},
]
},
{
salonId:'pobedy', salon:'Салон Победы', color:'#8B5CF6',
admin:{name:'Ирина С.', short:'ИС'},
orders:20, ordersPlan:22, revenue:1310000, revenuePlan:1500000,
overdue:1, overdueRisk:113000, purchases:1, newLeads:12, status:'warn',
managers:[
{id:'pv', name:'Пётр В.', short:'ПВ', color:'#059669', visits:28, deals:7, revenue:490000, conversion:25, avg:70000, rating:4.3, active:true},
{id:'iv', name:'Иван В.', short:'ИВ', color:'#D97706', visits:31, deals:8, revenue:530000, conversion:26, avg:66250, rating:4.1, active:false},
]
},
];
window._mgrExpSalon = window._mgrExpSalon || null;
// ─── МЕНЕДЖЕРЫ ────────────────────────────────────────────────────── // ─── МЕНЕДЖЕРЫ ──────────────────────────────────────────────────────
function _sManagers(){ function _sManagers(){
var admins=[ var admins=_HIERARCHY.map(function(h){
{name:'Анна М.', salon:'Салон Ленина', role:'Администратор', return Object.assign({role:'Администратор'}, h);
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 sc={ok:'var(--success)',bad:'var(--danger)',warn:'var(--warn)'};
var bg={ok:'#F0FDF4',bad:'#FEF2F2',warn:'#FFFBEB'}; var bg={ok:'#F0FDF4',bad:'#FEF2F2',warn:'#FFFBEB'};
var totOrders =admins.reduce(function(s,a){return s+a.orders;},0); var totOrders =admins.reduce(function(s,a){return s+a.orders;},0);
@ -501,22 +522,34 @@ function _sManagers(){
+'</div></div>' +'</div></div>'
: '') : '')
+'<div class="section-label">Администраторы салонов</div>' +'<div class="section-label">Администраторы → Менеджеры</div>'
+'<div style="padding:0 16px">' +'<div style="padding:0 16px">'
+admins.map(function(a){ +admins.map(function(a){
var ordPct =Math.round(a.orders/a.ordersPlan*100); var ordPct =Math.round(a.orders/a.ordersPlan*100);
var revPctA=Math.round(a.revenue/a.revenuePlan*100); var revPctA=Math.round(a.revenue/a.revenuePlan*100);
var sColor =sc[a.status]; var sColor =sc[a.status];
return '<div class="card" style="padding:14px 16px;margin-bottom:10px;border-left:3px solid '+sColor+'">' var isExp = window._mgrExpSalon===a.salonId;
var mgrRevTotal=a.managers.reduce(function(s,m){return s+m.revenue;},0);
return '<div class="card" style="padding:0;margin-bottom:10px;border-left:3px solid '+sColor+';overflow:hidden">'
// ── Заголовок администратора ──
+'<div style="padding:14px 16px 10px">'
+'<div style="display:flex;align-items:flex-start;justify-content:space-between;margin-bottom:10px">' +'<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>'
+ '<div style="font-size:11px;color:var(--muted);margin-top:1px">'+a.role+' · '+a.salon+'</div></div>' + '<div style="display:flex;align-items:center;gap:8px">'
+ '<div style="width:32px;height:32px;border-radius:50%;background:'+a.color+';display:flex;align-items:center;justify-content:center;font-size:11px;font-weight:800;color:#fff">'+a.admin.short+'</div>'
+ '<div>'
+ '<div style="font-size:14px;font-weight:700;color:var(--ink)">'+a.admin.name+'</div>'
+ '<div style="font-size:11px;color:var(--muted)">'+a.role+' · '+a.salon+'</div>'
+ '</div>'
+ '</div>'
+ '</div>'
+ '<div style="background:'+bg[a.status]+';color:'+sColor+';border-radius:20px;padding:3px 10px;font-size:11px;font-weight:600">' + '<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'?'⚠ Внимание':'✕ Проблема') + (a.status==='ok'?'✓ Норма':a.status==='warn'?'⚠ Внимание':'✕ Проблема')
+ '</div>' + '</div>'
+'</div>' +'</div>'
// ── KPI салона ──
+'<div style="display:grid;grid-template-columns:repeat(3,1fr);gap:8px;margin-bottom:10px">' +'<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="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:20px;font-weight:800;color:var(--ink);line-height:1">'+a.orders+'</div>'
@ -531,20 +564,60 @@ function _sManagers(){
+ '<div style="background:var(--bg);border-radius:10px;padding:9px 10px;text-align:center">' + '<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: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: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 style="font-size:10px;color:var(--muted);margin-top:1px">'+a.managers.length+' менедж.</div>'
+ '</div>' + '</div>'
+'</div>' +'</div>'
+(a.overdue>0||a.purchases>0 +(a.overdue>0||a.purchases>0
? '<div style="display:flex;gap:8px;flex-wrap:wrap">' ? '<div style="display:flex;gap:8px;flex-wrap:wrap;margin-bottom:10px">'
+(a.overdue>0?'<div style="display:flex;align-items:center;gap:4px;background:#FEF2F2;border-radius:8px;padding:5px 10px">' +(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"></span>'
+'<span style="font-size:11px;color:#991B1B">Просрочено: <b>'+a.overdue+'</b> · риск '+Math.round(a.overdueRisk/1000)+' тыс</span></div>':'') +'<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">' +(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">📋</span>'
+'<span style="font-size:11px;color:#1E40AF">Закупки: <b>'+a.purchases+'</b> заявки</span></div>':'') +'<span style="font-size:11px;color:#1E40AF">Закупки: <b>'+a.purchases+'</b> заявки</span></div>':'')
+'</div>' +'</div>'
: '<div style="padding:5px 10px;background:#F0FDF4;border-radius:8px;font-size:11px;color:var(--success);font-weight:600">✅ Всё в норме</div>') : '')
// ── Кнопка раскрыть менеджеров ──
+'<div onclick="window._mgrExpSalon=(window._mgrExpSalon===\''+a.salonId+'\'?null:\''+a.salonId+'\');_render()" '
+ 'style="display:flex;align-items:center;justify-content:space-between;padding:8px 12px;background:rgba(0,0,0,.03);border-radius:10px;cursor:pointer;border:1px solid var(--line)">'
+ '<span style="font-size:12px;font-weight:700;color:var(--accent)">'+a.managers.length+' менеджера · '+Math.round(mgrRevTotal/1000)+' тыс ₽ выручка</span>'
+ '<span style="font-size:14px;color:var(--muted)">'+(isExp?'▲':'▼')+'</span>'
+'</div>'
+'</div>'
// ── Раскрытые менеджеры ──
+(isExp
? '<div style="background:#F8FAFF;border-top:1px solid var(--line);padding:12px 16px">'
+'<div style="font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.08em;color:var(--muted);margin-bottom:10px">Менеджеры · '+a.salon+'</div>'
+a.managers.map(function(m,mi){
return '<div style="display:flex;align-items:center;gap:10px;padding:9px 0;border-bottom:'+(mi<a.managers.length-1?'1px solid rgba(0,0,0,.05)':'none')+'">'
// Аватар
+'<div style="width:34px;height:34px;border-radius:50%;background:'+m.color+';display:flex;align-items:center;justify-content:center;font-size:11px;font-weight:800;color:#fff;flex-shrink:0">'+m.short+'</div>'
+'<div style="flex:1;min-width:0">'
+ '<div style="display:flex;align-items:center;gap:6px">'
+ '<span style="font-size:13px;font-weight:700;color:var(--ink)">'+m.name+'</span>'
+ (m.active?'<span style="font-size:9px;background:#ECFDF5;color:#065F46;border-radius:10px;padding:1px 6px;font-weight:700">online</span>':'<span style="font-size:9px;background:var(--bg);color:var(--muted);border-radius:10px;padding:1px 6px">offline</span>')
+ '</div>'
+ '<div style="display:flex;gap:12px;margin-top:4px">'
+ '<span style="font-size:11px;color:var(--muted)">Визиты: <b style="color:var(--ink)">'+m.visits+'</b></span>'
+ '<span style="font-size:11px;color:var(--muted)">Сделки: <b style="color:var(--ink)">'+m.deals+'</b></span>'
+ '<span style="font-size:11px;color:var(--muted)">Конверсия: <b style="color:'+(m.conversion>=30?'var(--success)':'var(--warn)')+'">'+m.conversion+'%</b></span>'
+ '</div>'
+'</div>'
+'<div style="text-align:right;flex-shrink:0">'
+ '<div style="font-size:13px;font-weight:800;color:var(--ink)">'+Math.round(m.revenue/1000)+' тыс</div>'
+ '<div style="display:flex;align-items:center;gap:2px;justify-content:flex-end;margin-top:2px">'
+ '<span style="color:#F59E0B;font-size:10px"></span>'
+ '<span style="font-size:11px;font-weight:700;color:var(--ink)">'+m.rating+'</span>'
+ '</div>'
+'</div>'
+'</div>';
}).join('')
+'</div>'
: '')
+'</div>'; +'</div>';
}).join('') }).join('')