mirror of
https://github.com/wasrusgen/wasrusgen1-crm.git
synced 2026-06-03 15:44:45 +00:00
feat: Admin привязан к Салон Ленина — фильтр менеджеров/заявок по salon, KPI в карточках
This commit is contained in:
parent
74d684346e
commit
d90d9c27bc
@ -68,22 +68,27 @@ body[data-theme="dark"]{--accent:#4338CA;--accent2:#6366F1;--bg:#111827;--card:#
|
||||
<script>
|
||||
// ── ДАННЫЕ: ЗАЯВКИ МЕНЕДЖЕРОВ ─────────────────────────────────────────────────
|
||||
window._MGR_REQUESTS = window._MGR_REQUESTS || [
|
||||
{id:'r1', mgr:'Анна К.', color:'#7C3AED', type:'supply', prio:'high', title:'Закончились образцы ткани', body:'Нет образцов искусственной замши — теряем клиентов. Нужно срочно дозаказать у Мебельтекстиля.', created:'сегодня 09:14', status:'new'},
|
||||
{id:'r2', mgr:'Мария С.', color:'#0891B2', type:'escalate', prio:'high', title:'Конфликт с клиентом Козлов Р.', body:'Клиент требует возврат 25 000 ₽ из-за задержки поставки. Говорит, что подаст жалобу. Прошу вмешаться.', created:'сегодня 10:30', status:'new'},
|
||||
{id:'r3', mgr:'Пётр В.', color:'#059669', type:'visit', prio:'normal', title:'Нужна машина для выезда к клиенту', body:'Клиент Сидорова на Васильевском — хочет видеть образцы на дому. Нужен транспорт на 15:00.', created:'сегодня 11:05', status:'new'},
|
||||
{id:'r4', mgr:'Анна К.', color:'#7C3AED', type:'supply', prio:'normal', title:'Каталоги кухонь Hettich закончились', body:'Остался 1 экземпляр. Клиенты берут домой, не возвращают. Надо заказать 10 шт.', created:'вчера 17:22', status:'done'},
|
||||
{id:'r5', mgr:'Мария С.', color:'#0891B2', type:'schedule', prio:'normal', title:'Запрос на замену смены — 31 мая', body:'Прошу разрешить обменяться сменой с Петром: я работаю 31 мая вместо него, он — 1 июня вместо меня.', created:'вчера 14:10', status:'new'},
|
||||
{id:'r1', mgr:'Анна К.', mgrId:'ak', salon:'lenina', color:'#7C3AED', type:'supply', prio:'high', title:'Закончились образцы ткани', body:'Нет образцов искусственной замши — теряем клиентов. Нужно срочно дозаказать у Мебельтекстиля.', created:'сегодня 09:14', status:'new'},
|
||||
{id:'r2', mgr:'Мария С.', mgrId:'ms', salon:'lenina', color:'#0891B2', type:'escalate', prio:'high', title:'Конфликт с клиентом Козлов Р.', body:'Клиент требует возврат 25 000 ₽ из-за задержки поставки. Говорит, что подаст жалобу. Прошу вмешаться.', created:'сегодня 10:30', status:'new'},
|
||||
{id:'r3', mgr:'Пётр В.', mgrId:'pv', salon:'pobedy', color:'#059669', type:'visit', prio:'normal', title:'Нужна машина для выезда к клиенту', body:'Клиент Сидорова на Васильевском — хочет видеть образцы на дому. Нужен транспорт на 15:00.', created:'сегодня 11:05', status:'new'},
|
||||
{id:'r4', mgr:'Анна К.', mgrId:'ak', salon:'lenina', color:'#7C3AED', type:'supply', prio:'normal', title:'Каталоги кухонь Hettich закончились', body:'Остался 1 экземпляр. Клиенты берут домой, не возвращают. Надо заказать 10 шт.', created:'вчера 17:22', status:'done'},
|
||||
{id:'r5', mgr:'Мария С.', mgrId:'ms', salon:'lenina', color:'#0891B2', type:'schedule', prio:'normal', title:'Запрос на замену смены — 31 мая', body:'Прошу разрешить обменяться сменой с Петром: я работаю 31 мая вместо него, он — 1 июня вместо меня.', created:'вчера 14:10', status:'new'},
|
||||
];
|
||||
window._staffSubTab = window._staffSubTab || 'chess';
|
||||
|
||||
// ── ДАННЫЕ: ШАХМАТКА (расписание менеджеров × слоты) ─────────────────────────
|
||||
var _CHESS_HOURS = ['10:00','11:00','12:00','13:00','14:00','15:00','16:00','17:00','18:00','19:00','20:00'];
|
||||
// Идентичность этого кабинета: Администратор Анна М. · Салон Ленина
|
||||
var _ADMIN_IDENTITY = {name:'Анна М.', short:'АМ', salon:'Салон Ленина', salonId:'lenina'};
|
||||
// Менеджеры ЭТОГО салона (Ленина). Пётр В. и Иван В. — Салон Победы, в этом кабинете не отображаются.
|
||||
var _CHESS_MGRS = [
|
||||
{id:'ak', name:'Анна К.', short:'АК', color:'#7C3AED'},
|
||||
{id:'ms', name:'Мария С.', short:'МС', color:'#0891B2'},
|
||||
{id:'pv', name:'Пётр В.', short:'ПВ', color:'#059669'},
|
||||
{id:'iv', name:'Иван В.', short:'ИВ', color:'#D97706'},
|
||||
{id:'ak', name:'Анна К.', short:'АК', color:'#7C3AED', salon:'lenina'},
|
||||
{id:'ms', name:'Мария С.', short:'МС', color:'#0891B2', salon:'lenina'},
|
||||
{id:'pv', name:'Пётр В.', short:'ПВ', color:'#059669', salon:'pobedy'},
|
||||
{id:'iv', name:'Иван В.', short:'ИВ', color:'#D97706', salon:'pobedy'},
|
||||
];
|
||||
// Фильтр — только менеджеры своего салона
|
||||
var _MY_MGRS = _CHESS_MGRS.filter(function(m){ return m.salon===_ADMIN_IDENTITY.salonId; });
|
||||
// ячейка: {client, type, status} status: free|busy|done|noshow
|
||||
// type: consult|measure|follow|tech
|
||||
var _CHESS_DATA = {
|
||||
@ -359,7 +364,7 @@ function _navBar(){
|
||||
// Бейджи
|
||||
var needBuy = _SUPPLIES.filter(function(s){ return _stockLevel(s)==='danger'&&!s.ordered; }).length;
|
||||
var ordOverdue = (window._ORDERS||[]).filter(function(o){ return o.overdue; }).length;
|
||||
var mgrReqNew = (window._MGR_REQUESTS||[]).filter(function(r){ return r.status==='new'; }).length;
|
||||
var mgrReqNew = (window._MGR_REQUESTS||[]).filter(function(r){ return r.status==='new'&&r.salon===_ADMIN_IDENTITY.salonId; }).length;
|
||||
var shiftReqPend = (window._SHIFT_REQS||[]).filter(function(r){ return r.status==='pending'; }).length;
|
||||
var staffAlert = mgrReqNew + shiftReqPend;
|
||||
// GPS нарушения
|
||||
@ -486,9 +491,10 @@ function _screenHome(){
|
||||
+'<div style="font-size:12px;color:var(--muted)">'+invBad.map(function(i){return i.name;}).join(', ')+'</div>'
|
||||
+'</div>';
|
||||
}
|
||||
// Заявки менеджеров
|
||||
var mgrHigh = (window._MGR_REQUESTS||[]).filter(function(r){return r.status==='new'&&r.prio==='high';});
|
||||
var mgrAll = (window._MGR_REQUESTS||[]).filter(function(r){return r.status==='new';});
|
||||
// Заявки менеджеров — только своего салона
|
||||
var _myReqs = (window._MGR_REQUESTS||[]).filter(function(r){return r.salon===_ADMIN_IDENTITY.salonId;});
|
||||
var mgrHigh = _myReqs.filter(function(r){return r.status==='new'&&r.prio==='high';});
|
||||
var mgrAll = _myReqs.filter(function(r){return r.status==='new';});
|
||||
if(mgrHigh.length){
|
||||
alertsHtml += '<div style="margin:0 16px 8px;background:rgba(239,68,68,.07);border:1px solid rgba(239,68,68,.2);border-radius:14px;padding:12px 14px;cursor:pointer" onclick="window._staffSubTab=\'requests\';window._reqFilter=\'all\';_nav(\'staff\')">'
|
||||
+'<div style="font-size:13px;font-weight:700;color:var(--danger);margin-bottom:3px">🚨 Срочные заявки менеджеров — '+mgrHigh.length+'</div>'
|
||||
@ -507,23 +513,46 @@ function _screenHome(){
|
||||
}
|
||||
|
||||
// ── Менеджеры сейчас ──
|
||||
// KPI менеджеров месяца (привязаны к _ADMIN_IDENTITY.salon)
|
||||
var _MGR_KPI = {
|
||||
'ak': {visits:42, deals:14, revenue:847000, conv:33, rating:4.8},
|
||||
'ms': {visits:35, deals:9, revenue:610000, conv:26, rating:4.5},
|
||||
};
|
||||
var mgrNowHtml = '<div style="padding:0 16px;margin-bottom:4px">'
|
||||
+'<div style="font-size:11px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:8px">👥 Менеджеры сейчас</div>'
|
||||
+'<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:8px">'
|
||||
+'<div style="font-size:11px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em">👥 Мои менеджеры · '+_ADMIN_IDENTITY.salon+'</div>'
|
||||
+'<div style="font-size:10px;color:var(--accent);font-weight:700;cursor:pointer" onclick="window._staffSubTab=\'chess\';_nav(\'staff\')">Все →</div>'
|
||||
+'</div>'
|
||||
+'<div style="background:var(--card);border-radius:14px;box-shadow:0 2px 8px rgba(0,0,0,.06);overflow:hidden">'
|
||||
+_CHESS_MGRS.map(function(mgr){
|
||||
+_MY_MGRS.map(function(mgr,idx){
|
||||
var rd=_CHESS_DATA[mgr.id]||{};
|
||||
var curSlot=rd['15:00']||{};
|
||||
var nextSlot=rd['16:00']||{};
|
||||
var isOnline=!!curSlot.client;
|
||||
var kpi=_MGR_KPI[mgr.id]||{};
|
||||
var statusText=curSlot.client?'С клиентом: '+curSlot.client:'Свободен';
|
||||
var statusColor=curSlot.client?'var(--success)':'var(--muted)';
|
||||
var nextText=nextSlot.client?'→ '+nextSlot.client:'→ Свободен';
|
||||
return '<div style="display:flex;align-items:center;gap:10px;padding:10px 12px;border-bottom:1px solid rgba(0,0,0,.05);cursor:pointer" onclick="window._staffSubTab=\'chess\';_nav(\'staff\')">'
|
||||
+'<div style="width:34px;height:34px;border-radius:50%;background:'+mgr.color+';display:flex;align-items:center;justify-content:center;font-size:12px;font-weight:800;color:#fff;flex-shrink:0">'+mgr.short+'</div>'
|
||||
var statusColor=isOnline?'var(--success)':'var(--muted)';
|
||||
return '<div style="padding:10px 12px;border-bottom:'+(idx<_MY_MGRS.length-1?'1px solid rgba(0,0,0,.05)':'none')+';cursor:pointer" onclick="window._staffSubTab=\'chess\';_nav(\'staff\')">'
|
||||
+'<div style="display:flex;align-items:center;gap:10px;margin-bottom:6px">'
|
||||
+'<div style="position:relative;flex-shrink:0">'
|
||||
+'<div style="width:36px;height:36px;border-radius:50%;background:'+mgr.color+';display:flex;align-items:center;justify-content:center;font-size:12px;font-weight:800;color:#fff">'+mgr.short+'</div>'
|
||||
+'<div style="position:absolute;bottom:1px;right:1px;width:9px;height:9px;border-radius:50%;background:'+(isOnline?'var(--success)':'var(--muted)')+';border:2px solid var(--card)"></div>'
|
||||
+'</div>'
|
||||
+'<div style="flex:1;min-width:0">'
|
||||
+'<div style="font-size:13px;font-weight:700;color:var(--ink)">'+mgr.name+'</div>'
|
||||
+'<div style="font-size:11px;color:'+statusColor+';font-weight:600">'+statusText+'</div>'
|
||||
+'</div>'
|
||||
+'<div style="font-size:10px;color:var(--muted);text-align:right;flex-shrink:0">'+nextText+'</div>'
|
||||
+'<div style="text-align:right;flex-shrink:0">'
|
||||
+'<div style="font-size:11px;font-weight:800;color:var(--ink)">'+Math.round((kpi.revenue||0)/1000)+' тыс ₽</div>'
|
||||
+'<div style="font-size:10px;color:var(--muted)">выручка мес.</div>'
|
||||
+'</div>'
|
||||
+'</div>'
|
||||
+'<div style="display:flex;gap:10px;padding:0 2px">'
|
||||
+'<div style="font-size:10px;color:var(--muted)">Визитов: <b style="color:var(--ink)">'+(kpi.visits||'—')+'</b></div>'
|
||||
+'<div style="font-size:10px;color:var(--muted)">Сделок: <b style="color:var(--ink)">'+(kpi.deals||'—')+'</b></div>'
|
||||
+'<div style="font-size:10px;color:var(--muted)">Конв.: <b style="color:'+(kpi.conv>=30?'var(--success)':'var(--warn)')+'">'+(kpi.conv||'—')+'%</b></div>'
|
||||
+'<div style="font-size:10px;color:var(--muted)">★ <b style="color:var(--warn)">'+(kpi.rating||'—')+'</b></div>'
|
||||
+'</div>'
|
||||
+'</div>';
|
||||
}).join('')
|
||||
+'<div style="padding:8px 12px;background:rgba(0,62,126,.04);cursor:pointer;text-align:center;font-size:12px;font-weight:700;color:var(--accent)" onclick="window._staffSubTab=\'chess\';_nav(\'staff\')">🔲 Открыть шахматку →</div>'
|
||||
@ -1187,7 +1216,7 @@ function _confirmCloseShift(){
|
||||
function _screenStaff(){
|
||||
// ── Чип-табы: График | Заявки ──
|
||||
var sub = window._staffSubTab || 'schedule';
|
||||
var mgrNew = (window._MGR_REQUESTS||[]).filter(function(r){return r.status==='new';}).length;
|
||||
var mgrNew = (window._MGR_REQUESTS||[]).filter(function(r){return r.status==='new'&&r.salon===_ADMIN_IDENTITY.salonId;}).length;
|
||||
var shiftNew = (window._SHIFT_REQS||[]).filter(function(r){return r.status==='pending';}).length;
|
||||
var _tabs=[
|
||||
{key:'chess', label:'🔲 Шахматка'},
|
||||
@ -1634,8 +1663,8 @@ function _screenChess(){
|
||||
}).join('')
|
||||
+'</div>';
|
||||
|
||||
// Строки менеджеров
|
||||
var rows = _CHESS_MGRS.map(function(mgr){
|
||||
// Строки менеджеров — только своего салона
|
||||
var rows = _MY_MGRS.map(function(mgr){
|
||||
var rowData = _CHESS_DATA[mgr.id]||{};
|
||||
return '<div style="display:flex;align-items:stretch;border-bottom:1px solid rgba(0,0,0,.05)">'
|
||||
+'<div style="width:'+NAME_W+'px;flex-shrink:0;display:flex;align-items:center;justify-content:center;padding:4px 2px">'
|
||||
@ -1670,7 +1699,7 @@ function _screenChess(){
|
||||
var summary='<div style="padding:8px 12px;border-top:1px solid var(--line)">'
|
||||
+'<div style="font-size:11px;font-weight:700;color:var(--muted);margin-bottom:6px">ЗАНЯТОСТЬ СЕГОДНЯ</div>'
|
||||
+'<div style="display:flex;flex-direction:column;gap:5px">'
|
||||
+_CHESS_MGRS.map(function(mgr){
|
||||
+_MY_MGRS.map(function(mgr){
|
||||
var rd=_CHESS_DATA[mgr.id]||{};
|
||||
var total=_CHESS_HOURS.length;
|
||||
var busy=_CHESS_HOURS.filter(function(h){return (rd[h]||{}).client;}).length;
|
||||
@ -1755,11 +1784,11 @@ function _screenRequests(){
|
||||
var prioMap = {high:'Срочно', normal:'Обычная'};
|
||||
|
||||
var filtered = (window._MGR_REQUESTS||[]).filter(function(r){
|
||||
return filter==='all' || r.type===filter;
|
||||
return r.salon===_ADMIN_IDENTITY.salonId && (filter==='all' || r.type===filter);
|
||||
}).sort(function(a,b){ return (a.prio==='high'?0:1)-(b.prio==='high'?0:1); });
|
||||
|
||||
var chips = ['all','supply','visit','escalate','schedule'].map(function(t){
|
||||
var cnt = (window._MGR_REQUESTS||[]).filter(function(r){return (t==='all'||r.type===t)&&r.status==='new';}).length;
|
||||
var cnt = (window._MGR_REQUESTS||[]).filter(function(r){return r.salon===_ADMIN_IDENTITY.salonId&&(t==='all'||r.type===t)&&r.status==='new';}).length;
|
||||
var label = t==='all'?'Все':typeMap[t];
|
||||
return '<div onclick="window._reqFilter=\''+t+'\';_render()" style="padding:6px 13px;border-radius:20px;font-size:12px;font-weight:700;cursor:pointer;white-space:nowrap;flex-shrink:0;border:1.5px solid '+(filter===t?'var(--accent)':'var(--line)')+';background:'+(filter===t?'var(--accent)':'var(--card)')+';color:'+(filter===t?'#fff':'var(--muted)')+'">'+label+(cnt>0&&t!=='all'?' ('+cnt+')':'')+'</div>';
|
||||
}).join('');
|
||||
|
||||
Loading…
Reference in New Issue
Block a user