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
'
+ +'
'
+ +'
'
+ +'
'
+ +'
'+_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();