mirror of
https://github.com/wasrusgen/wasrusgen1-crm.git
synced 2026-06-03 15:44:45 +00:00
feat: администратор — экран Заказы, редизайн Главной с KPI дня
This commit is contained in:
parent
c958cc316b
commit
d1d865b679
@ -124,6 +124,39 @@ window._INVENTORY = window._INVENTORY || [
|
|||||||
window._LAST_INVENTORY = window._LAST_INVENTORY || '15.04.2026';
|
window._LAST_INVENTORY = window._LAST_INVENTORY || '15.04.2026';
|
||||||
window._INV_SESSION = window._INV_SESSION || null;
|
window._INV_SESSION = window._INV_SESSION || null;
|
||||||
|
|
||||||
|
// ── ДАННЫЕ: ЗАКАЗЫ ────────────────────────────────────────────────────────────
|
||||||
|
var _STAGES = {
|
||||||
|
measure: {label:'Замер', color:'#8B5CF6', bg:'rgba(139,92,246,.1)'},
|
||||||
|
design: {label:'Проект', color:'#3B82F6', bg:'rgba(59,130,246,.1)'},
|
||||||
|
production: {label:'На фабрике', color:'#F59E0B', bg:'rgba(245,158,11,.1)'},
|
||||||
|
assembly: {label:'Монтаж', color:'#10B981', bg:'rgba(16,185,129,.1)'},
|
||||||
|
ready: {label:'Готов к сдаче',color:'#003E7E',bg:'rgba(0,62,126,.1)'},
|
||||||
|
done: {label:'Закрыт', color:'#76BD22', bg:'rgba(118,189,34,.1)'},
|
||||||
|
};
|
||||||
|
|
||||||
|
window._ORDERS = window._ORDERS || [
|
||||||
|
// Просроченный — оплата не пришла
|
||||||
|
{id:'o1', num:'№ 2026-0214', client:'Морозова Е.', manager:'Анна К.', type:'Шкаф-купе', amount:98500, paid:0, stage:'waiting_pay', overdue:true, daysLeft:-12, note:'Ожидает финальный платёж с 10.05'},
|
||||||
|
// Готов — ожидает сдачи
|
||||||
|
{id:'o2', num:'№ 2026-0225', client:'Лебедев К.', manager:'Пётр В.', type:'Кухня', amount:189000, paid:94500, stage:'ready', overdue:false, daysLeft:1, note:'Требует согласования даты сдачи'},
|
||||||
|
// Монтаж
|
||||||
|
{id:'o3', num:'№ 2026-0228', client:'Козлов Р.', manager:'Пётр В.', type:'Кухня', amount:215000, paid:107500, stage:'assembly', overdue:false, daysLeft:3, note:''},
|
||||||
|
{id:'o4', num:'№ 2026-0231', client:'Иванова О.', manager:'Анна К.', type:'Гардероб', amount:87000, paid:43500, stage:'assembly', overdue:false, daysLeft:5, note:''},
|
||||||
|
// На фабрике
|
||||||
|
{id:'o5', num:'№ 2026-0235', client:'Петров С.', manager:'Пётр В.', type:'Кухня', amount:178000, paid:89000, stage:'production', overdue:false, daysLeft:14, note:''},
|
||||||
|
{id:'o6', num:'№ 2026-0236', client:'Соколов Д.', manager:'Анна К.', type:'Прихожая', amount:54000, paid:27000, stage:'production', overdue:false, daysLeft:18, note:''},
|
||||||
|
// Проект
|
||||||
|
{id:'o7', num:'№ 2026-0238', client:'Фёдорова Н.', manager:'Анна К.', type:'Кухня', amount:195000, paid:97500, stage:'design', overdue:false, daysLeft:21, note:''},
|
||||||
|
// Замер
|
||||||
|
{id:'o8', num:'№ 2026-0239', client:'Новиков А.', manager:'Пётр В.', type:'Шкаф-купе', amount:112000, paid:56000, stage:'measure', overdue:false, daysLeft:25, note:''},
|
||||||
|
{id:'o9', num:'№ 2026-0240', client:'Смирнова Т.', manager:'Анна К.', type:'Гардероб', amount:76000, paid:38000, stage:'measure', overdue:false, daysLeft:28, note:''},
|
||||||
|
// Закрытые (май)
|
||||||
|
{id:'o10', num:'№ 2026-0210', client:'Попова Г.', manager:'Анна К.', type:'Прихожая', amount:45000, paid:45000, stage:'done', overdue:false, daysLeft:0, note:''},
|
||||||
|
{id:'o11', num:'№ 2026-0212', client:'Захаров М.', manager:'Пётр В.', type:'Кухня', amount:231000, paid:231000, stage:'done', overdue:false, daysLeft:0, note:''},
|
||||||
|
];
|
||||||
|
window._ordersTab = window._ordersTab || 'active'; // 'active' | 'overdue' | 'done'
|
||||||
|
window._orderSel = window._orderSel || null; // id выбранного заказа (детали)
|
||||||
|
|
||||||
// ── ДАННЫЕ: ПЕРСОНАЛ ──────────────────────────────────────────────────────────
|
// ── ДАННЫЕ: ПЕРСОНАЛ ──────────────────────────────────────────────────────────
|
||||||
var _DOW = ['Пн','Вт','Ср','Чт','Пт','Сб','Вс'];
|
var _DOW = ['Пн','Вт','Ср','Чт','Пт','Сб','Вс'];
|
||||||
var _DATES = [19,20,21,22,23,24,25]; // май 2026, неделя
|
var _DATES = [19,20,21,22,23,24,25]; // май 2026, неделя
|
||||||
@ -218,16 +251,16 @@ function _render(){
|
|||||||
// ── NAV BAR ───────────────────────────────────────────────────────────────────
|
// ── NAV BAR ───────────────────────────────────────────────────────────────────
|
||||||
function _navBar(){
|
function _navBar(){
|
||||||
var items=[
|
var items=[
|
||||||
{id:'home', icon:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/><polyline points="9 22 9 12 15 12 15 22"/></svg>', label:'Главная'},
|
{id:'home', icon:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/><polyline points="9 22 9 12 15 12 15 22"/></svg>', label:'Главная'},
|
||||||
{id:'supplies', icon:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><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>', label:'Закупки'},
|
{id:'orders', icon:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/><polyline points="10 9 9 9 8 9"/></svg>', label:'Заказы'},
|
||||||
{id:'inventory', icon:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="2" y="3" width="20" height="14" rx="2"/><path d="M8 21h8M12 17v4"/><path d="M7 8h10M7 12h6"/></svg>', label:'Инвентарь'},
|
{id:'cash', icon:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="2" y="6" width="20" height="12" rx="2"/><circle cx="12" cy="12" r="3"/><path d="M6 12h.01M18 12h.01"/></svg>', label:'Касса'},
|
||||||
{id:'staff', icon:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><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>', label:'Персонал'},
|
{id:'supplies', icon:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><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>', label:'Закупки'},
|
||||||
{id:'cash', icon:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="2" y="6" width="20" height="12" rx="2"/><circle cx="12" cy="12" r="3"/><path d="M6 12h.01M18 12h.01"/></svg>', label:'Касса'},
|
{id:'staff', icon:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><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>', label:'Персонал'},
|
||||||
];
|
];
|
||||||
// Бейджи
|
// Бейджи
|
||||||
var needBuy = _SUPPLIES.filter(function(s){ return _stockLevel(s)==='danger'&&!s.ordered; }).length;
|
var needBuy = _SUPPLIES.filter(function(s){ return _stockLevel(s)==='danger'&&!s.ordered; }).length;
|
||||||
var invBad = _INVENTORY.filter(function(i){ return i.cond==='bad'; }).length;
|
var ordOverdue = (window._ORDERS||[]).filter(function(o){ return o.overdue; }).length;
|
||||||
// GPS нарушения: плановые смены прошлых дней без факта
|
// GPS нарушения
|
||||||
var gpsIssue = 0;
|
var gpsIssue = 0;
|
||||||
_STAFF.forEach(function(st){
|
_STAFF.forEach(function(st){
|
||||||
var plan = window._PLAN[st.id]||[];
|
var plan = window._PLAN[st.id]||[];
|
||||||
@ -239,10 +272,10 @@ function _navBar(){
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
return items.map(function(it){
|
return items.map(function(it){
|
||||||
var badge = (it.id==='supplies'&&needBuy>0)
|
var badge = (it.id==='orders'&&ordOverdue>0)
|
||||||
|
? '<div style="position:absolute;top:4px;right:6px;width:16px;height:16px;border-radius:50%;background:var(--danger);color:#fff;font-size:9px;font-weight:800;display:flex;align-items:center;justify-content:center">'+ordOverdue+'</div>'
|
||||||
|
: (it.id==='supplies'&&needBuy>0)
|
||||||
? '<div style="position:absolute;top:4px;right:6px;width:16px;height:16px;border-radius:50%;background:var(--danger);color:#fff;font-size:9px;font-weight:800;display:flex;align-items:center;justify-content:center">'+needBuy+'</div>'
|
? '<div style="position:absolute;top:4px;right:6px;width:16px;height:16px;border-radius:50%;background:var(--danger);color:#fff;font-size:9px;font-weight:800;display:flex;align-items:center;justify-content:center">'+needBuy+'</div>'
|
||||||
: (it.id==='inventory'&&invBad>0)
|
|
||||||
? '<div style="position:absolute;top:4px;right:6px;width:16px;height:16px;border-radius:50%;background:var(--warn);color:#fff;font-size:9px;font-weight:800;display:flex;align-items:center;justify-content:center">'+invBad+'</div>'
|
|
||||||
: (it.id==='staff'&&gpsIssue>0)
|
: (it.id==='staff'&&gpsIssue>0)
|
||||||
? '<div style="position:absolute;top:4px;right:6px;width:16px;height:16px;border-radius:50%;background:var(--danger);color:#fff;font-size:9px;font-weight:800;display:flex;align-items:center;justify-content:center">'+gpsIssue+'</div>'
|
? '<div style="position:absolute;top:4px;right:6px;width:16px;height:16px;border-radius:50%;background:var(--danger);color:#fff;font-size:9px;font-weight:800;display:flex;align-items:center;justify-content:center">'+gpsIssue+'</div>'
|
||||||
: (it.id==='cash'&&(window._PENDING||[]).length>0)
|
: (it.id==='cash'&&(window._PENDING||[]).length>0)
|
||||||
@ -273,6 +306,7 @@ function _stockLabel(s){
|
|||||||
// ── SCREEN ROUTER ─────────────────────────────────────────────────────────────
|
// ── SCREEN ROUTER ─────────────────────────────────────────────────────────────
|
||||||
function _renderScreen(){
|
function _renderScreen(){
|
||||||
if(window._screen==='home') return _screenHome();
|
if(window._screen==='home') return _screenHome();
|
||||||
|
if(window._screen==='orders') return _screenOrders();
|
||||||
if(window._screen==='supplies') return _screenSupplies();
|
if(window._screen==='supplies') return _screenSupplies();
|
||||||
if(window._screen==='sup_new') return _screenSupplyNew();
|
if(window._screen==='sup_new') return _screenSupplyNew();
|
||||||
if(window._screen==='inventory') return _screenInventory();
|
if(window._screen==='inventory') return _screenInventory();
|
||||||
@ -284,73 +318,187 @@ function _renderScreen(){
|
|||||||
|
|
||||||
// ── HOME ──────────────────────────────────────────────────────────────────────
|
// ── HOME ──────────────────────────────────────────────────────────────────────
|
||||||
function _screenHome(){
|
function _screenHome(){
|
||||||
var needBuy = _SUPPLIES.filter(function(s){ return _stockLevel(s)!=='ok'&&!s.ordered; });
|
var needBuy = _SUPPLIES.filter(function(s){ return _stockLevel(s)!=='ok'&&!s.ordered; });
|
||||||
var ordered = _SUPPLIES.filter(function(s){ return s.ordered; });
|
var ordered = _SUPPLIES.filter(function(s){ return s.ordered; });
|
||||||
var invBad = _INVENTORY.filter(function(i){ return i.cond==='bad'; });
|
var invBad = _INVENTORY.filter(function(i){ return i.cond==='bad'; });
|
||||||
var invWarn = _INVENTORY.filter(function(i){ return i.cond==='warn'; });
|
var overdueOrd = (window._ORDERS||[]).filter(function(o){ return o.overdue; });
|
||||||
|
var activeOrd = (window._ORDERS||[]).filter(function(o){ return !o.overdue && o.stage!=='done'; });
|
||||||
|
var pendCash = (window._PENDING||[]);
|
||||||
|
|
||||||
var alertsHtml = '';
|
// ── KPI-строка дня ──
|
||||||
|
var c = _cashCalc();
|
||||||
|
var revenue = c.cashIn + c.cardIn;
|
||||||
|
var revPlan = 55000; // план на день
|
||||||
|
var revPct = Math.round((revenue/revPlan)*100);
|
||||||
|
var kpiHtml = '<div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:8px;padding:12px 16px 0">'
|
||||||
|
+'<div style="background:var(--card);border-radius:14px;padding:12px 10px;text-align:center;box-shadow:0 2px 8px rgba(0,0,0,.05);cursor:pointer" onclick="_nav(\'orders\')">'
|
||||||
|
+'<div style="font-size:22px;font-weight:900;color:'+(overdueOrd.length?'var(--danger)':'var(--ink)')+'">'+((window._ORDERS||[]).filter(function(o){return o.stage!=='done';}).length)+'</div>'
|
||||||
|
+'<div style="font-size:9px;color:var(--muted);margin-top:1px">заказов актив.</div>'
|
||||||
|
+(overdueOrd.length?'<div style="font-size:9px;color:var(--danger);font-weight:700;margin-top:2px">'+overdueOrd.length+' просроч.</div>':'')
|
||||||
|
+'</div>'
|
||||||
|
+'<div style="background:var(--card);border-radius:14px;padding:12px 10px;text-align:center;box-shadow:0 2px 8px rgba(0,0,0,.05);cursor:pointer" onclick="_nav(\'cash\')">'
|
||||||
|
+'<div style="font-size:14px;font-weight:900;color:'+(revPct>=80?'var(--success)':revPct>=50?'var(--warn)':'var(--danger)')+'">'+_fmtMoney(revenue)+'</div>'
|
||||||
|
+'<div style="font-size:9px;color:var(--muted);margin-top:1px">выручка сегодня</div>'
|
||||||
|
+'<div style="font-size:9px;color:var(--muted);margin-top:1px">план: '+_fmtMoney(revPlan)+'</div>'
|
||||||
|
+'</div>'
|
||||||
|
+'<div style="background:var(--card);border-radius:14px;padding:12px 10px;text-align:center;box-shadow:0 2px 8px rgba(0,0,0,.05);cursor:pointer" onclick="_nav(\'cash\')">'
|
||||||
|
+'<div style="font-size:14px;font-weight:900;color:var(--ink)">'+_fmtMoney(c.cashBal)+'</div>'
|
||||||
|
+'<div style="font-size:9px;color:var(--muted);margin-top:1px">в кассе</div>'
|
||||||
|
+(pendCash.length?'<div style="font-size:9px;color:var(--warn);font-weight:700;margin-top:2px">'+pendCash.length+' ожид. оплат</div>':'')
|
||||||
|
+'</div>'
|
||||||
|
+'</div>';
|
||||||
|
|
||||||
|
// ── Прогресс выручки ──
|
||||||
|
var barPct = Math.min(100,revPct);
|
||||||
|
var barCol = revPct>=80?'var(--success)':revPct>=50?'var(--warn)':'var(--danger)';
|
||||||
|
var barHtml = '<div style="padding:8px 16px 0">'
|
||||||
|
+'<div style="display:flex;justify-content:space-between;font-size:10px;color:var(--muted);margin-bottom:4px">'
|
||||||
|
+'<span>Выручка / план дня</span><span style="font-weight:700;color:'+barCol+'">'+revPct+'%</span></div>'
|
||||||
|
+'<div style="height:5px;background:var(--line);border-radius:3px;overflow:hidden">'
|
||||||
|
+'<div style="height:100%;background:'+barCol+';border-radius:3px;width:'+barPct+'%;transition:width .4s"></div>'
|
||||||
|
+'</div></div>';
|
||||||
|
|
||||||
|
// ── Алерты ──
|
||||||
|
var alertsHtml = '<div style="height:10px"></div>';
|
||||||
|
if(overdueOrd.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="_nav(\'orders\')">'
|
||||||
|
+'<div style="font-size:13px;font-weight:700;color:var(--danger);margin-bottom:3px">🚨 Просроченные заказы — '+overdueOrd.length+'</div>'
|
||||||
|
+'<div style="font-size:12px;color:var(--muted)">'+overdueOrd.map(function(o){return o.client+' · '+_fmtMoney(o.amount-o.paid)+' ₽ не оплачено';}).join('<br>')+'</div>'
|
||||||
|
+'</div>';
|
||||||
|
}
|
||||||
|
if(pendCash.length){
|
||||||
|
alertsHtml += '<div style="margin:0 16px 8px;background:rgba(245,158,11,.07);border:1px solid rgba(245,158,11,.2);border-radius:14px;padding:12px 14px;cursor:pointer" onclick="_nav(\'cash\')">'
|
||||||
|
+'<div style="font-size:13px;font-weight:700;color:var(--warn);margin-bottom:3px">⏳ Ожидают приёма в кассу — '+pendCash.length+'</div>'
|
||||||
|
+'<div style="font-size:12px;color:var(--muted)">'+pendCash.map(function(p){return p.client+' · '+_fmtMoney(p.amount);}).join(', ')+'</div>'
|
||||||
|
+'</div>';
|
||||||
|
}
|
||||||
if(needBuy.length){
|
if(needBuy.length){
|
||||||
alertsHtml += '<div style="margin:0 16px 10px;background:rgba(239,68,68,.07);border:1px solid rgba(239,68,68,.2);border-radius:14px;padding:13px 14px;cursor:pointer" onclick="_nav(\'supplies\')">'
|
alertsHtml += '<div style="margin:0 16px 8px;background:rgba(239,68,68,.06);border:1px solid rgba(239,68,68,.15);border-radius:14px;padding:12px 14px;cursor:pointer" onclick="_nav(\'supplies\')">'
|
||||||
+'<div style="font-size:13px;font-weight:700;color:var(--danger);margin-bottom:4px">🛒 Нужно закупить — '+needBuy.length+' позиций</div>'
|
+'<div style="font-size:13px;font-weight:700;color:var(--danger);margin-bottom:3px">🛒 Нужно закупить — '+needBuy.length+' позиций</div>'
|
||||||
+'<div style="font-size:12px;color:var(--muted)">'+needBuy.slice(0,3).map(function(s){return s.name;}).join(', ')+(needBuy.length>3?' и ещё '+(needBuy.length-3):'')+'</div>'
|
+'<div style="font-size:12px;color:var(--muted)">'+needBuy.slice(0,3).map(function(s){return s.name;}).join(', ')+(needBuy.length>3?' и ещё '+(needBuy.length-3):'')+'</div>'
|
||||||
+'</div>';
|
+'</div>';
|
||||||
}
|
}
|
||||||
if(ordered.length){
|
|
||||||
alertsHtml += '<div style="margin:0 16px 10px;background:rgba(245,158,11,.07);border:1px solid rgba(245,158,11,.2);border-radius:14px;padding:13px 14px">'
|
|
||||||
+'<div style="font-size:13px;font-weight:700;color:var(--warn);margin-bottom:4px">📦 Ожидается доставка — '+ordered.length+' позиции</div>'
|
|
||||||
+'<div style="font-size:12px;color:var(--muted)">'+ordered.map(function(s){return s.name+(s.orderedDate?' ('+s.orderedDate+')':'');}).join(', ')+'</div>'
|
|
||||||
+'</div>';
|
|
||||||
}
|
|
||||||
if(invBad.length){
|
if(invBad.length){
|
||||||
alertsHtml += '<div style="margin:0 16px 10px;background:rgba(239,68,68,.07);border:1px solid rgba(239,68,68,.2);border-radius:14px;padding:13px 14px;cursor:pointer" onclick="_nav(\'inventory\')">'
|
alertsHtml += '<div style="margin:0 16px 8px;background:rgba(245,158,11,.06);border:1px solid rgba(245,158,11,.15);border-radius:14px;padding:12px 14px;cursor:pointer" onclick="_nav(\'inventory\')">'
|
||||||
+'<div style="font-size:13px;font-weight:700;color:var(--danger);margin-bottom:4px">⚠️ Требует внимания — '+invBad.length+' позиции</div>'
|
+'<div style="font-size:13px;font-weight:700;color:var(--warn);margin-bottom:3px">⚠️ Инвентарь: требует внимания — '+invBad.length+'</div>'
|
||||||
+'<div style="font-size:12px;color:var(--muted)">'+invBad.map(function(i){return i.name;}).join(', ')+'</div>'
|
+'<div style="font-size:12px;color:var(--muted)">'+invBad.map(function(i){return i.name;}).join(', ')+'</div>'
|
||||||
+'</div>';
|
+'</div>';
|
||||||
}
|
}
|
||||||
|
if(!overdueOrd.length&&!pendCash.length&&!needBuy.length&&!invBad.length){
|
||||||
|
alertsHtml += '<div style="margin:0 16px 8px;background:var(--s-success-bg);border:1px solid rgba(16,185,129,.2);border-radius:14px;padding:12px 14px;text-align:center">'
|
||||||
|
+'<div style="font-size:13px;font-weight:700;color:var(--success)">✅ Всё в порядке</div>'
|
||||||
|
+'</div>';
|
||||||
|
}
|
||||||
|
|
||||||
// Быстрая статистика
|
// ── Быстрые действия ──
|
||||||
var okCount = _SUPPLIES.filter(function(s){ return _stockLevel(s)==='ok'; }).length;
|
var quickHtml = '<div style="padding:0 16px;display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-bottom:4px">'
|
||||||
var totalSup = _SUPPLIES.length;
|
+'<button onclick="_nav(\'sup_new\')" style="padding:11px;font-size:13px;font-weight:700;border-radius:14px;border:1.5px solid rgba(0,62,126,.2);background:rgba(0,62,126,.06);color:var(--accent);cursor:pointer">🛒 Создать заявку</button>'
|
||||||
var okInv = _INVENTORY.filter(function(i){ return i.cond==='ok'; }).length;
|
+'<button onclick="_startInvCheck()" style="padding:11px;font-size:13px;font-weight:700;border-radius:14px;border:1.5px solid rgba(0,62,126,.15);background:rgba(0,62,126,.05);color:var(--accent);cursor:pointer">📋 Осмотр инвентаря</button>'
|
||||||
var totalInv = _INVENTORY.length;
|
|
||||||
|
|
||||||
var statsHtml =
|
|
||||||
'<div style="display:flex;gap:10px;padding:0 16px 14px">'
|
|
||||||
+'<div style="flex:1;background:var(--card);border-radius:14px;padding:13px;text-align:center;box-shadow:0 2px 8px rgba(0,0,0,.06)" onclick="_nav(\'supplies\')">'
|
|
||||||
+'<div style="font-size:22px;font-weight:900;color:'+(needBuy.length?'var(--danger)':'var(--success)')+'">'+okCount+'/'+totalSup+'</div>'
|
|
||||||
+'<div style="font-size:11px;color:var(--muted);margin-top:2px">позиций OK</div>'
|
|
||||||
+'</div>'
|
|
||||||
+'<div style="flex:1;background:var(--card);border-radius:14px;padding:13px;text-align:center;box-shadow:0 2px 8px rgba(0,0,0,.06)" onclick="_nav(\'inventory\')">'
|
|
||||||
+'<div style="font-size:22px;font-weight:900;color:'+(invBad.length?'var(--danger)':invWarn.length?'var(--warn)':'var(--success)')+'">'+okInv+'/'+totalInv+'</div>'
|
|
||||||
+'<div style="font-size:11px;color:var(--muted);margin-top:2px">инвентарь OK</div>'
|
|
||||||
+'</div>'
|
|
||||||
+'<div style="flex:1;background:var(--card);border-radius:14px;padding:13px;text-align:center;box-shadow:0 2px 8px rgba(0,0,0,.06)">'
|
|
||||||
+'<div style="font-size:13px;font-weight:900;color:var(--muted)">'+window._LAST_INVENTORY+'</div>'
|
|
||||||
+'<div style="font-size:11px;color:var(--muted);margin-top:2px">последний осмотр</div>'
|
|
||||||
+'</div>'
|
|
||||||
+'</div>';
|
|
||||||
|
|
||||||
var quickHtml =
|
|
||||||
'<div style="padding:0 16px;display:flex;gap:10px;margin-bottom:4px">'
|
|
||||||
+'<button onclick="_nav(\'sup_new\')" class="btn-primary" style="flex:1;padding:12px;font-size:14px">🛒 Создать заявку</button>'
|
|
||||||
+'<button onclick="_startInvCheck()" style="flex:1;padding:12px;font-size:14px;background:rgba(0,62,126,.08);color:var(--accent);border:1.5px solid rgba(0,62,126,.2);border-radius:14px;font-weight:700;cursor:pointer">📋 Провести осмотр</button>'
|
|
||||||
+'</div>';
|
+'</div>';
|
||||||
|
|
||||||
return '<div class="page anim">'
|
return '<div class="page anim">'
|
||||||
+'<div style="padding:20px 16px 8px;background:var(--card);border-bottom:1px solid var(--line)">'
|
+'<div style="padding:16px 16px 12px;background:var(--card);border-bottom:1px solid var(--line)">'
|
||||||
+'<div style="font-size:12px;color:var(--muted);margin-bottom:2px">Пн, 19 мая 2026</div>'
|
+'<div style="font-size:12px;color:var(--muted);margin-bottom:2px">Чт, 22 мая 2026</div>'
|
||||||
+'<div style="font-size:22px;font-weight:800;color:var(--ink)">Добрый день, Анна 👋</div>'
|
+'<div style="font-size:22px;font-weight:800;color:var(--ink)">Добрый день, Анна 👋</div>'
|
||||||
+'<div style="font-size:13px;color:var(--muted);margin-top:2px">Администратор салона</div>'
|
+'<div style="font-size:13px;color:var(--muted);margin-top:2px">Салон Ленина · Администратор</div>'
|
||||||
+'</div>'
|
+'</div>'
|
||||||
+'<div style="height:14px"></div>'
|
+kpiHtml
|
||||||
+statsHtml
|
+barHtml
|
||||||
+(alertsHtml||'<div style="margin:0 16px 10px;font-size:13px;color:var(--muted);text-align:center;padding:8px">✅ Всё в порядке</div>')
|
+alertsHtml
|
||||||
+'<div style="height:6px"></div>'
|
|
||||||
+quickHtml
|
+quickHtml
|
||||||
+'</div>';
|
+'</div>';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── ЗАКАЗЫ ────────────────────────────────────────────────────────────────────
|
||||||
|
function _screenOrders(){
|
||||||
|
var tab = window._ordersTab;
|
||||||
|
var all = window._ORDERS||[];
|
||||||
|
var overdueList = all.filter(function(o){ return o.overdue; });
|
||||||
|
var activeList = all.filter(function(o){ return !o.overdue && o.stage!=='done'; });
|
||||||
|
var doneList = all.filter(function(o){ return o.stage==='done'; });
|
||||||
|
|
||||||
|
var list = tab==='active' ? activeList : tab==='overdue' ? overdueList : doneList;
|
||||||
|
|
||||||
|
var tabsHtml = '<div style="display:flex;gap:6px;padding:10px 16px;overflow-x:auto;scrollbar-width:none">'
|
||||||
|
+[['active','Активные',activeList.length],['overdue','Просроченные',overdueList.length],['done','Закрытые',doneList.length]].map(function(t){
|
||||||
|
var isAct = tab===t[0];
|
||||||
|
var col = t[0]==='overdue'&&t[2]>0?'var(--danger)':t[0]==='active'?'var(--accent)':'var(--success)';
|
||||||
|
return '<div style="padding:6px 14px;border-radius:20px;font-size:12px;font-weight:700;cursor:pointer;white-space:nowrap;flex-shrink:0;'
|
||||||
|
+(isAct?'background:var(--accent);color:#fff;':'background:var(--card);color:var(--muted);border:1.5px solid var(--line);')
|
||||||
|
+'" onclick="window._ordersTab=\''+t[0]+'\';_render()">'+t[1]+(t[2]>0?' <b style="opacity:.8">'+t[2]+'</b>':'')+'</div>';
|
||||||
|
}).join('')
|
||||||
|
+'</div>';
|
||||||
|
|
||||||
|
// Суммарная строка по заказам
|
||||||
|
var totalActive = activeList.length + overdueList.length;
|
||||||
|
var totalRevenue = all.filter(function(o){return o.stage!=='done'||true;}).reduce(function(s,o){return s+o.amount;},0);
|
||||||
|
var totalPaid = all.reduce(function(s,o){return s+o.paid;},0);
|
||||||
|
var totalDebt = totalRevenue - totalPaid;
|
||||||
|
|
||||||
|
var summaryHtml = '<div style="margin:0 16px 10px;background:var(--accent);border-radius:16px;padding:14px 16px;color:#fff">'
|
||||||
|
+'<div style="display:flex;justify-content:space-between;margin-bottom:8px">'
|
||||||
|
+'<div style="font-size:11px;opacity:.7">Всего заказов · Салон Ленина</div>'
|
||||||
|
+'<div style="font-size:11px;opacity:.7">Май 2026</div>'
|
||||||
|
+'</div>'
|
||||||
|
+'<div style="display:grid;grid-template-columns:repeat(3,1fr);gap:8px">'
|
||||||
|
+'<div><div style="font-size:20px;font-weight:900">'+totalActive+'</div><div style="font-size:9px;opacity:.7;margin-top:1px">активных</div></div>'
|
||||||
|
+'<div><div style="font-size:20px;font-weight:900">'+_fmtMoney(totalPaid)+'</div><div style="font-size:9px;opacity:.7;margin-top:1px">оплачено</div></div>'
|
||||||
|
+'<div><div style="font-size:20px;font-weight:900;color:rgba(255,255,255,.75)">'+_fmtMoney(totalDebt)+'</div><div style="font-size:9px;opacity:.7;margin-top:1px">к получению</div></div>'
|
||||||
|
+'</div>'
|
||||||
|
+'</div>';
|
||||||
|
|
||||||
|
var ordersHtml = list.length ? list.map(function(o){
|
||||||
|
var st = _STAGES[o.stage] || {label:o.stage, color:'#888', bg:'rgba(0,0,0,.06)'};
|
||||||
|
var debtAmt = o.amount - o.paid;
|
||||||
|
var border = o.overdue ? 'var(--danger)' : o.daysLeft<=3&&o.daysLeft>=0 ? 'var(--warn)' : 'var(--line)';
|
||||||
|
var paidPct = Math.round((o.paid/o.amount)*100);
|
||||||
|
return '<div style="background:var(--card);margin:0 16px 8px;border-radius:14px;padding:13px 14px;border-left:3px solid '+border+'">'
|
||||||
|
// Шапка карточки
|
||||||
|
+'<div style="display:flex;align-items:flex-start;justify-content:space-between;margin-bottom:8px">'
|
||||||
|
+'<div>'
|
||||||
|
+'<div style="font-size:14px;font-weight:700;color:var(--ink)">'+o.client+'</div>'
|
||||||
|
+'<div style="font-size:11px;color:var(--muted);margin-top:1px">'+o.num+' · '+o.type+' · '+o.manager+'</div>'
|
||||||
|
+'</div>'
|
||||||
|
+'<span style="padding:3px 9px;border-radius:12px;font-size:11px;font-weight:700;background:'+st.bg+';color:'+st.color+';white-space:nowrap;flex-shrink:0">'+st.label+'</span>'
|
||||||
|
+'</div>'
|
||||||
|
// Сумма + оплата
|
||||||
|
+'<div style="display:flex;gap:8px;margin-bottom:8px">'
|
||||||
|
+'<div style="flex:1;background:var(--bg);border-radius:9px;padding:8px 10px">'
|
||||||
|
+'<div style="font-size:10px;color:var(--muted);margin-bottom:2px">Сумма заказа</div>'
|
||||||
|
+'<div style="font-size:14px;font-weight:800;color:var(--ink)">'+_fmtMoney(o.amount)+'</div>'
|
||||||
|
+'</div>'
|
||||||
|
+'<div style="flex:1;background:var(--bg);border-radius:9px;padding:8px 10px">'
|
||||||
|
+'<div style="font-size:10px;color:var(--muted);margin-bottom:2px">Оплачено ('+paidPct+'%)</div>'
|
||||||
|
+'<div style="font-size:14px;font-weight:800;color:'+(o.paid>=o.amount?'var(--success)':o.overdue?'var(--danger)':'var(--ink)')+'">'+_fmtMoney(o.paid)+'</div>'
|
||||||
|
+'</div>'
|
||||||
|
+'</div>'
|
||||||
|
// Прогресс оплаты
|
||||||
|
+'<div style="height:4px;background:var(--line);border-radius:2px;overflow:hidden;margin-bottom:8px">'
|
||||||
|
+'<div style="height:100%;border-radius:2px;background:'+(o.paid>=o.amount?'var(--success)':o.overdue?'var(--danger)':'var(--accent)')+';width:'+Math.min(100,paidPct)+'%;transition:width .4s"></div>'
|
||||||
|
+'</div>'
|
||||||
|
// Статус / срок
|
||||||
|
+(o.overdue
|
||||||
|
? '<div style="font-size:12px;color:var(--danger);font-weight:700">🚨 Просрочено на '+Math.abs(o.daysLeft)+' дн. · к получению: '+_fmtMoney(debtAmt)+'</div>'
|
||||||
|
: o.note
|
||||||
|
? '<div style="font-size:12px;color:var(--warn)">⚠️ '+o.note+'</div>'
|
||||||
|
: o.daysLeft>0
|
||||||
|
? '<div style="font-size:12px;color:var(--muted)">Срок: через '+o.daysLeft+' дн.</div>'
|
||||||
|
: '')
|
||||||
|
+'</div>';
|
||||||
|
}).join('')
|
||||||
|
: '<div style="padding:32px 16px;text-align:center;color:var(--muted);font-size:13px">Заказов в этой категории нет</div>';
|
||||||
|
|
||||||
|
return '<div class="page anim">'
|
||||||
|
+'<div class="page-header">'
|
||||||
|
+'<h2>📋 Заказы</h2>'
|
||||||
|
+(overdueList.length?'<span class="pill danger">'+overdueList.length+' просроч.</span>':'<span class="pill ok">'+activeList.length+' актив.</span>')
|
||||||
|
+'</div>'
|
||||||
|
+summaryHtml
|
||||||
|
+tabsHtml
|
||||||
|
+ordersHtml
|
||||||
|
+'</div>';
|
||||||
|
}
|
||||||
|
|
||||||
// ── ЗАКУПКИ ───────────────────────────────────────────────────────────────────
|
// ── ЗАКУПКИ ───────────────────────────────────────────────────────────────────
|
||||||
function _screenSupplies(){
|
function _screenSupplies(){
|
||||||
var cat = window._supCat;
|
var cat = window._supCat;
|
||||||
@ -1287,7 +1435,7 @@ function _toast(msg, bg){
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ── INIT — hash navigation (file:// friendly) ─────────────────────────────────
|
// ── INIT — hash navigation (file:// friendly) ─────────────────────────────────
|
||||||
var _adminHashMap={'#home':'home','#supplies':'supplies','#inventory':'inventory','#staff':'staff','#cash':'cash'};
|
var _adminHashMap={'#home':'home','#orders':'orders','#supplies':'supplies','#inventory':'inventory','#staff':'staff','#cash':'cash'};
|
||||||
var _initScreen = _adminHashMap[location.hash] || 'cash';
|
var _initScreen = _adminHashMap[location.hash] || 'cash';
|
||||||
window._screen = _initScreen;
|
window._screen = _initScreen;
|
||||||
_render();
|
_render();
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user