diff --git a/docs/mockup_admin.html b/docs/mockup_admin.html index 9699811..9e1233c 100644 --- a/docs/mockup_admin.html +++ b/docs/mockup_admin.html @@ -124,6 +124,39 @@ window._INVENTORY = window._INVENTORY || [ window._LAST_INVENTORY = window._LAST_INVENTORY || '15.04.2026'; 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 _DATES = [19,20,21,22,23,24,25]; // май 2026, неделя @@ -218,16 +251,16 @@ function _render(){ // ── NAV BAR ─────────────────────────────────────────────────────────────────── function _navBar(){ var items=[ - {id:'home', icon:'', label:'Главная'}, - {id:'supplies', icon:'', label:'Закупки'}, - {id:'inventory', icon:'', label:'Инвентарь'}, - {id:'staff', icon:'', label:'Персонал'}, - {id:'cash', icon:'', label:'Касса'}, + {id:'home', icon:'', label:'Главная'}, + {id:'orders', icon:'', label:'Заказы'}, + {id:'cash', icon:'', label:'Касса'}, + {id:'supplies', icon:'', label:'Закупки'}, + {id:'staff', icon:'', label:'Персонал'}, ]; // Бейджи - var needBuy = _SUPPLIES.filter(function(s){ return _stockLevel(s)==='danger'&&!s.ordered; }).length; - var invBad = _INVENTORY.filter(function(i){ return i.cond==='bad'; }).length; - // GPS нарушения: плановые смены прошлых дней без факта + var needBuy = _SUPPLIES.filter(function(s){ return _stockLevel(s)==='danger'&&!s.ordered; }).length; + var ordOverdue = (window._ORDERS||[]).filter(function(o){ return o.overdue; }).length; + // GPS нарушения var gpsIssue = 0; _STAFF.forEach(function(st){ var plan = window._PLAN[st.id]||[]; @@ -239,10 +272,10 @@ function _navBar(){ }); }); return items.map(function(it){ - var badge = (it.id==='supplies'&&needBuy>0) + var badge = (it.id==='orders'&&ordOverdue>0) + ? '
'+ordOverdue+'
' + : (it.id==='supplies'&&needBuy>0) ? '
'+needBuy+'
' - : (it.id==='inventory'&&invBad>0) - ? '
'+invBad+'
' : (it.id==='staff'&&gpsIssue>0) ? '
'+gpsIssue+'
' : (it.id==='cash'&&(window._PENDING||[]).length>0) @@ -273,6 +306,7 @@ function _stockLabel(s){ // ── SCREEN ROUTER ───────────────────────────────────────────────────────────── function _renderScreen(){ if(window._screen==='home') return _screenHome(); + if(window._screen==='orders') return _screenOrders(); if(window._screen==='supplies') return _screenSupplies(); if(window._screen==='sup_new') return _screenSupplyNew(); if(window._screen==='inventory') return _screenInventory(); @@ -284,73 +318,187 @@ function _renderScreen(){ // ── HOME ────────────────────────────────────────────────────────────────────── function _screenHome(){ - var needBuy = _SUPPLIES.filter(function(s){ return _stockLevel(s)!=='ok'&&!s.ordered; }); - var ordered = _SUPPLIES.filter(function(s){ return s.ordered; }); - var invBad = _INVENTORY.filter(function(i){ return i.cond==='bad'; }); - var invWarn = _INVENTORY.filter(function(i){ return i.cond==='warn'; }); + var needBuy = _SUPPLIES.filter(function(s){ return _stockLevel(s)!=='ok'&&!s.ordered; }); + var ordered = _SUPPLIES.filter(function(s){ return s.ordered; }); + var invBad = _INVENTORY.filter(function(i){ return i.cond==='bad'; }); + 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 = '
' + +'
' + +'
'+((window._ORDERS||[]).filter(function(o){return o.stage!=='done';}).length)+'
' + +'
заказов актив.
' + +(overdueOrd.length?'
'+overdueOrd.length+' просроч.
':'') + +'
' + +'
' + +'
'+_fmtMoney(revenue)+'
' + +'
выручка сегодня
' + +'
план: '+_fmtMoney(revPlan)+'
' + +'
' + +'
' + +'
'+_fmtMoney(c.cashBal)+'
' + +'
в кассе
' + +(pendCash.length?'
'+pendCash.length+' ожид. оплат
':'') + +'
' + +'
'; + + // ── Прогресс выручки ── + var barPct = Math.min(100,revPct); + var barCol = revPct>=80?'var(--success)':revPct>=50?'var(--warn)':'var(--danger)'; + var barHtml = '
' + +'
' + +'Выручка / план дня'+revPct+'%
' + +'
' + +'
' + +'
'; + + // ── Алерты ── + var alertsHtml = '
'; + if(overdueOrd.length){ + alertsHtml += '
' + +'
🚨 Просроченные заказы — '+overdueOrd.length+'
' + +'
'+overdueOrd.map(function(o){return o.client+' · '+_fmtMoney(o.amount-o.paid)+' ₽ не оплачено';}).join('
')+'
' + +'
'; + } + if(pendCash.length){ + alertsHtml += '
' + +'
⏳ Ожидают приёма в кассу — '+pendCash.length+'
' + +'
'+pendCash.map(function(p){return p.client+' · '+_fmtMoney(p.amount);}).join(', ')+'
' + +'
'; + } if(needBuy.length){ - alertsHtml += '
' - +'
🛒 Нужно закупить — '+needBuy.length+' позиций
' + alertsHtml += '
' + +'
🛒 Нужно закупить — '+needBuy.length+' позиций
' +'
'+needBuy.slice(0,3).map(function(s){return s.name;}).join(', ')+(needBuy.length>3?' и ещё '+(needBuy.length-3):'')+'
' +'
'; } - if(ordered.length){ - alertsHtml += '
' - +'
📦 Ожидается доставка — '+ordered.length+' позиции
' - +'
'+ordered.map(function(s){return s.name+(s.orderedDate?' ('+s.orderedDate+')':'');}).join(', ')+'
' - +'
'; - } if(invBad.length){ - alertsHtml += '
' - +'
⚠️ Требует внимания — '+invBad.length+' позиции
' + alertsHtml += '
' + +'
⚠️ Инвентарь: требует внимания — '+invBad.length+'
' +'
'+invBad.map(function(i){return i.name;}).join(', ')+'
' +'
'; } + if(!overdueOrd.length&&!pendCash.length&&!needBuy.length&&!invBad.length){ + alertsHtml += '
' + +'
✅ Всё в порядке
' + +'
'; + } - // Быстрая статистика - var okCount = _SUPPLIES.filter(function(s){ return _stockLevel(s)==='ok'; }).length; - var totalSup = _SUPPLIES.length; - var okInv = _INVENTORY.filter(function(i){ return i.cond==='ok'; }).length; - var totalInv = _INVENTORY.length; - - var statsHtml = - '
' - +'
' - +'
'+okCount+'/'+totalSup+'
' - +'
позиций OK
' - +'
' - +'
' - +'
'+okInv+'/'+totalInv+'
' - +'
инвентарь OK
' - +'
' - +'
' - +'
'+window._LAST_INVENTORY+'
' - +'
последний осмотр
' - +'
' - +'
'; - - var quickHtml = - '
' - +'' - +'' + // ── Быстрые действия ── + var quickHtml = '
' + +'' + +'' +'
'; return '
' - +'
' - +'
Пн, 19 мая 2026
' + +'
' + +'
Чт, 22 мая 2026
' +'
Добрый день, Анна 👋
' - +'
Администратор салона
' + +'
Салон Ленина · Администратор
' +'
' - +'
' - +statsHtml - +(alertsHtml||'
✅ Всё в порядке
') - +'
' + +kpiHtml + +barHtml + +alertsHtml +quickHtml +'
'; } +// ── ЗАКАЗЫ ──────────────────────────────────────────────────────────────────── +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 = '
' + +[['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 '
'+t[1]+(t[2]>0?' '+t[2]+'':'')+'
'; + }).join('') + +'
'; + + // Суммарная строка по заказам + 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 = '
' + +'
' + +'
Всего заказов · Салон Ленина
' + +'
Май 2026
' + +'
' + +'
' + +'
'+totalActive+'
активных
' + +'
'+_fmtMoney(totalPaid)+'
оплачено
' + +'
'+_fmtMoney(totalDebt)+'
к получению
' + +'
' + +'
'; + + 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 '
' + // Шапка карточки + +'
' + +'
' + +'
'+o.client+'
' + +'
'+o.num+' · '+o.type+' · '+o.manager+'
' + +'
' + +''+st.label+'' + +'
' + // Сумма + оплата + +'
' + +'
' + +'
Сумма заказа
' + +'
'+_fmtMoney(o.amount)+'
' + +'
' + +'
' + +'
Оплачено ('+paidPct+'%)
' + +'
'+_fmtMoney(o.paid)+'
' + +'
' + +'
' + // Прогресс оплаты + +'
' + +'
' + +'
' + // Статус / срок + +(o.overdue + ? '
🚨 Просрочено на '+Math.abs(o.daysLeft)+' дн. · к получению: '+_fmtMoney(debtAmt)+'
' + : o.note + ? '
⚠️ '+o.note+'
' + : o.daysLeft>0 + ? '
Срок: через '+o.daysLeft+' дн.
' + : '') + +'
'; + }).join('') + : '
Заказов в этой категории нет
'; + + return '
' + +'' + +summaryHtml + +tabsHtml + +ordersHtml + +'
'; +} + // ── ЗАКУПКИ ─────────────────────────────────────────────────────────────────── function _screenSupplies(){ var cat = window._supCat; @@ -1287,7 +1435,7 @@ function _toast(msg, bg){ } // ── 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'; window._screen = _initScreen; _render();