mirror of
https://github.com/wasrusgen/wasrusgen1-crm.git
synced 2026-06-03 17:44:46 +00:00
feat: hierarchy КД→Администратор→Менеджеры accordion в Персонал + _HIERARCHY data
This commit is contained in:
parent
c65d40eb54
commit
74d684346e
@ -460,16 +460,37 @@ function _sOrders(){
|
||||
+'</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(){
|
||||
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 admins=_HIERARCHY.map(function(h){
|
||||
return Object.assign({role:'Администратор'}, h);
|
||||
});
|
||||
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);
|
||||
@ -501,22 +522,34 @@ function _sManagers(){
|
||||
+'</div></div>'
|
||||
: '')
|
||||
|
||||
+'<div class="section-label">Администраторы салонов</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+'">'
|
||||
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><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>'
|
||||
+ '<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">'
|
||||
+ (a.status==='ok'?'✓ Норма':a.status==='warn'?'⚠ Внимание':'✕ Проблема')
|
||||
+ '</div>'
|
||||
+'</div>'
|
||||
|
||||
// ── KPI салона ──
|
||||
+'<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>'
|
||||
@ -531,20 +564,60 @@ function _sManagers(){
|
||||
+ '<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 style="font-size:10px;color:var(--muted);margin-top:1px">'+a.managers.length+' менедж.</div>'
|
||||
+ '</div>'
|
||||
+'</div>'
|
||||
|
||||
+(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">'
|
||||
+'<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">⚠</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 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>';
|
||||
}).join('')
|
||||
|
||||
Loading…
Reference in New Issue
Block a user