mirror of
https://github.com/wasrusgen/wasrusgen1-crm.git
synced 2026-06-03 16:44:46 +00:00
3098 lines
225 KiB
HTML
3098 lines
225 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="ru">
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||
<title>@wasrusgen1 CRM — Директор</title>
|
||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&family=Montserrat:wght@700;800&display=swap" rel="stylesheet">
|
||
<style>
|
||
*{box-sizing:border-box;margin:0;padding:0}
|
||
body{background:#C8CACD;min-height:100vh;display:flex;flex-direction:column;align-items:center;padding:20px;font-family:'Inter',sans-serif}
|
||
|
||
/* THEMES */
|
||
body[data-theme="zov"] {--accent:#003E7E;--accent2:#76BD22;--bg:#F5F6F8;--card:#FFFFFF;--ink:#1A1A2E;--muted:#8A94A6;--danger:#EF4444;--warn:#F59E0B;--success:#10B981}
|
||
body[data-theme="radar"] {--accent:#4338CA;--accent2:#6366F1;--bg:#F9FAFB;--card:#FFFFFF;--ink:#111827;--muted:#6B7280;--danger:#EF4444;--warn:#F59E0B;--success:#10B981}
|
||
body[data-theme="dark"] {--accent:#4338CA;--accent2:#6366F1;--bg:#111827;--card:#1F2937;--ink:#F9FAFB;--muted:#9CA3AF;--danger:#EF4444;--warn:#F59E0B;--success:#10B981}
|
||
body[data-theme="dark-crm"] {--accent:#4A90D9;--accent2:#76BD22;--bg:#0F172A;--card:#1E293B;--ink:#F1F5F9;--muted:#94A3B8;--danger:#EF4444;--warn:#F59E0B;--success:#10B981}
|
||
|
||
/* CONTROLS */
|
||
#controls{display:flex;align-items:center;gap:12px;margin-bottom:16px;flex-wrap:wrap;justify-content:center;width:100%;max-width:600px}
|
||
#controls label{color:#fff;font-size:13px;font-weight:600}
|
||
#screenSelect{padding:8px 12px;border-radius:10px;border:none;background:#fff;font-size:13px;color:#333;cursor:pointer;min-width:200px;box-shadow:0 2px 8px rgba(0,0,0,.15)}
|
||
#themeButtons{display:flex;gap:6px}
|
||
.theme-btn{padding:7px 14px;border-radius:9px;border:2px solid transparent;font-size:12px;font-weight:700;cursor:pointer;transition:all .2s}
|
||
.theme-btn.active{border-color:#fff;transform:scale(1.05)}
|
||
.theme-btn[data-t="zov"] {background:#003E7E;color:#fff}
|
||
.theme-btn[data-t="radar"] {background:#4338CA;color:#fff}
|
||
.theme-btn[data-t="dark"] {background:#111827;color:#6366F1}
|
||
.theme-btn[data-t="dark-crm"] {background:#0F172A;color:#4A90D9}
|
||
|
||
/* PHONE FRAME */
|
||
#phoneWrap{position:relative;width:390px;flex-shrink:0}
|
||
#phoneFrame{width:390px;height:844px;background:var(--bg);border-radius:44px;overflow:hidden;box-shadow:0 24px 80px rgba(0,0,0,.4),inset 0 0 0 1px rgba(255,255,255,.15);position:relative;display:flex;flex-direction:column}
|
||
#statusBar{height:44px;background:var(--card);display:flex;align-items:center;justify-content:space-between;padding:0 24px;flex-shrink:0;font-size:13px;font-weight:600;color:var(--ink);z-index:10}
|
||
.sb-right{display:flex;align-items:center;gap:6px}
|
||
#screen{flex:1;overflow-y:auto;overflow-x:hidden;position:relative;scrollbar-width:none;background:var(--bg)}
|
||
#screen::-webkit-scrollbar{display:none}
|
||
|
||
/* BOTTOM NAV */
|
||
.bottom-nav{height:60px;background:rgba(255,255,255,.92);backdrop-filter:blur(12px);border-top:1px solid rgba(0,0,0,.06);display:flex;align-items:center;justify-content:space-around;flex-shrink:0;position:relative;z-index:100}
|
||
.nav-item{display:flex;flex-direction:column;align-items:center;gap:2px;cursor:pointer;padding:6px 10px;border-radius:10px;transition:all .15s;flex:1}
|
||
.nav-item svg,.nav-item .nav-icon{width:22px;height:22px;color:var(--muted)}
|
||
.nav-item span{font-size:10px;color:var(--muted);font-weight:500}
|
||
.nav-item.active svg,.nav-item.active .nav-icon{color:var(--accent)}
|
||
.nav-item.active span{color:var(--accent)}
|
||
|
||
/* SHARED COMPONENTS */
|
||
.page{padding:0 0 80px;min-height:100%}
|
||
.page-header{display:flex;align-items:center;gap:12px;padding:16px 16px 12px;background:var(--card);border-bottom:1px solid rgba(0,0,0,.06);position:sticky;top:0;z-index:50}
|
||
.page-header h2{font-size:17px;font-weight:700;color:var(--ink);flex:1}
|
||
.back-btn{width:32px;height:32px;border-radius:50%;background:var(--bg);display:flex;align-items:center;justify-content:center;cursor:pointer;border:none;flex-shrink:0}
|
||
.back-btn svg{color:var(--accent);width:18px;height:18px}
|
||
.header-action{width:36px;height:36px;border-radius:50%;background:var(--bg);display:flex;align-items:center;justify-content:center;cursor:pointer;border:none}
|
||
.header-action svg{color:var(--accent);width:20px;height:20px}
|
||
|
||
.card{background:var(--card);border-radius:16px;box-shadow:0 2px 12px rgba(0,0,0,.07);padding:16px;margin-bottom:12px}
|
||
.card.warn-border{border-left:3px solid var(--warn)}
|
||
.card.danger-border{border-left:3px solid var(--danger)}
|
||
.card.success-border{border-left:3px solid var(--success)}
|
||
.card.accent-border{border-left:3px solid var(--accent)}
|
||
|
||
.section-label{text-transform:uppercase;font-size:11px;letter-spacing:.06em;color:var(--muted);margin:20px 16px 8px;font-weight:600}
|
||
|
||
.btn-primary{width:100%;background:var(--accent);color:#fff;border:none;border-radius:12px;padding:14px;font-size:15px;font-weight:600;cursor:pointer;transition:opacity .2s}
|
||
.btn-primary:hover{opacity:.9}
|
||
.btn-secondary{width:100%;background:transparent;color:var(--accent);border:1.5px solid var(--accent);border-radius:12px;padding:13px;font-size:15px;font-weight:600;cursor:pointer;transition:all .2s;margin-top:8px}
|
||
.btn-secondary:hover{background:var(--accent);color:#fff}
|
||
.btn-warn{width:100%;background:var(--warn);color:#fff;border:none;border-radius:12px;padding:14px;font-size:15px;font-weight:600;cursor:pointer;transition:opacity .2s}
|
||
.btn-warn:hover{opacity:.9}
|
||
.btn-danger{width:100%;background:transparent;color:var(--danger);border:1.5px solid var(--danger);border-radius:12px;padding:13px;font-size:15px;font-weight:600;cursor:pointer;margin-top:8px}
|
||
.btn-sm{padding:8px 14px;font-size:13px;font-weight:600;border-radius:9px;cursor:pointer;border:none;background:var(--accent);color:#fff;transition:opacity .2s}
|
||
.btn-sm:hover{opacity:.85}
|
||
.btn-sm.secondary{background:transparent;border:1.5px solid var(--accent);color:var(--accent)}
|
||
.btn-sm.warn{background:var(--warn);color:#fff}
|
||
|
||
.badge{display:inline-flex;align-items:center;padding:3px 10px;border-radius:20px;font-size:11px;font-weight:700;letter-spacing:.02em}
|
||
.badge.blue{background:#DBEAFE;color:#1D4ED8}
|
||
.badge.yellow{background:#FEF3C7;color:#D97706}
|
||
.badge.green{background:#DCFCE7;color:#15803D}
|
||
.badge.red{background:#FEE2E2;color:#DC2626}
|
||
.badge.gray{background:#F1F5F9;color:#64748B}
|
||
.badge.purple{background:#EDE9FE;color:#6D28D9}
|
||
|
||
.form-group{margin-bottom:14px}
|
||
.form-group label{display:block;font-size:12px;font-weight:600;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:6px}
|
||
.form-input{width:100%;padding:12px 14px;border:1.5px solid #E2E8F0;border-radius:12px;font-size:15px;color:var(--ink);background:var(--card);outline:none;transition:border-color .2s}
|
||
.form-input:focus{border-color:var(--accent)}
|
||
.form-select{width:100%;padding:12px 14px;border:1.5px solid #E2E8F0;border-radius:12px;font-size:15px;color:var(--ink);background:var(--card);outline:none;appearance:none}
|
||
|
||
.row{display:flex;align-items:center;gap:10px}
|
||
.row.sb{justify-content:space-between}
|
||
.col{display:flex;flex-direction:column;gap:4px}
|
||
|
||
.avatar{width:40px;height:40px;border-radius:50%;background:var(--accent);display:flex;align-items:center;justify-content:center;color:#fff;font-size:15px;font-weight:700;flex-shrink:0}
|
||
.avatar.lg{width:64px;height:64px;font-size:22px}
|
||
|
||
.client-name{font-size:15px;font-weight:700;color:var(--ink)}
|
||
.client-sub{font-size:13px;color:var(--muted);margin-top:2px}
|
||
|
||
.chip-row{display:flex;gap:8px;overflow-x:auto;padding:0 16px;margin-bottom:8px;scrollbar-width:none}
|
||
.chip-row::-webkit-scrollbar{display:none}
|
||
.chip{padding:6px 14px;border-radius:20px;font-size:13px;font-weight:600;cursor:pointer;white-space:nowrap;border:1.5px solid #E2E8F0;background:var(--card);color:var(--muted);transition:all .2s}
|
||
.chip.active{background:var(--accent);color:#fff;border-color:var(--accent)}
|
||
|
||
.divider{height:1px;background:rgba(0,0,0,.07);margin:8px 0}
|
||
|
||
.search-bar{display:flex;align-items:center;gap:10px;background:var(--card);border-radius:14px;padding:10px 14px;margin:12px 16px;box-shadow:0 2px 8px rgba(0,0,0,.06)}
|
||
.search-bar svg{color:var(--muted);width:18px;height:18px;flex-shrink:0}
|
||
.search-bar input{border:none;outline:none;font-size:15px;color:var(--ink);background:transparent;flex:1}
|
||
.search-bar input::placeholder{color:var(--muted)}
|
||
|
||
/* BAR CHART */
|
||
.bar-chart{display:flex;align-items:flex-end;gap:8px;height:80px;padding:0 4px}
|
||
.bar-col{display:flex;flex-direction:column;align-items:center;gap:4px;flex:1}
|
||
.bar{width:100%;background:var(--accent);border-radius:4px 4px 0 0;opacity:.85;transition:height .3s}
|
||
.bar-label{font-size:10px;color:var(--muted);font-weight:600}
|
||
|
||
/* TOGGLE */
|
||
.toggle-row{display:flex;align-items:center;justify-content:space-between;padding:12px 0}
|
||
.toggle-label{font-size:14px;font-weight:600;color:var(--ink)}
|
||
.toggle{width:44px;height:24px;border-radius:12px;background:#E2E8F0;position:relative;cursor:pointer;transition:background .2s;flex-shrink:0}
|
||
.toggle.on{background:var(--success)}
|
||
.toggle::after{content:'';position:absolute;top:3px;left:3px;width:18px;height:18px;border-radius:50%;background:#fff;transition:transform .2s;box-shadow:0 1px 4px rgba(0,0,0,.2)}
|
||
.toggle.on::after{transform:translateX(20px)}
|
||
|
||
/* DARK MODE OVERRIDES */
|
||
body[data-theme="dark"] .divider,body[data-theme="dark-crm"] .divider{background:rgba(255,255,255,.08)}
|
||
body[data-theme="dark"] .form-input,body[data-theme="dark"] .form-select,body[data-theme="dark-crm"] .form-input,body[data-theme="dark-crm"] .form-select{background:#374151;border-color:#4B5563;color:#F9FAFB}
|
||
body[data-theme="dark"] .chip,body[data-theme="dark-crm"] .chip{background:#1F2937;border-color:#374151;color:#9CA3AF}
|
||
body[data-theme="dark"] .search-bar,body[data-theme="dark-crm"] .search-bar{background:#1F2937}
|
||
body[data-theme="dark"] .page-header,body[data-theme="dark-crm"] .page-header{background:var(--card);border-color:rgba(255,255,255,.08)}
|
||
body[data-theme="dark"] .bottom-nav,body[data-theme="dark-crm"] .bottom-nav{background:rgba(15,23,42,.95);border-color:rgba(255,255,255,.08)}
|
||
body[data-theme="dark"] #statusBar,body[data-theme="dark-crm"] #statusBar{background:var(--card)}
|
||
body[data-theme="dark"] .back-btn,body[data-theme="dark-crm"] .back-btn{background:rgba(255,255,255,.08)}
|
||
body[data-theme="dark"] .header-action,body[data-theme="dark-crm"] .header-action{background:rgba(255,255,255,.08)}
|
||
body[data-theme="dark"] .toggle,body[data-theme="dark-crm"] .toggle{background:#374151}
|
||
|
||
/* ─── FEEDBACK WIDGET ─── */
|
||
#feedbackTrigger{position:absolute;bottom:14px;left:50%;transform:translateX(-50%);display:inline-flex;align-items:center;gap:10px;background:rgba(0,0,0,.58);backdrop-filter:blur(12px);border:1px solid rgba(255,255,255,.18);border-radius:32px;padding:10px 20px;cursor:pointer;z-index:50;transition:all .2s;user-select:none;white-space:nowrap;box-shadow:0 4px 20px rgba(0,0,0,.3)}
|
||
#feedbackTrigger:hover{background:rgba(0,0,0,.72);border-color:rgba(255,255,255,.28);transform:translateX(-50%) translateY(-1px)}
|
||
#fScreenId{display:none}
|
||
.fb-sep{width:1px;height:10px;background:rgba(255,255,255,.2);flex-shrink:0}
|
||
.fb-brand{font-family:'Montserrat',sans-serif;font-weight:700;font-size:9px;letter-spacing:1.5px;color:rgba(255,255,255,.6)}
|
||
#feedbackSheet{display:none;position:absolute;inset:0;z-index:200;flex-direction:column;justify-content:flex-end}
|
||
#feedbackOverlay{position:absolute;inset:0;background:rgba(0,0,0,.4)}
|
||
#feedbackPanel{position:relative;background:#fff;border-radius:20px 20px 0 0;padding:20px 16px 28px}
|
||
.fb-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:12px}
|
||
.fb-title{font-size:15px;font-weight:700;color:#111827}
|
||
.fb-screen{font-size:11px;font-weight:600;color:#6B7280;font-family:monospace}
|
||
#feedbackText{width:100%;border:1.5px solid #E2E8F0;border-radius:12px;padding:12px;font-size:13px;height:90px;resize:none;font-family:'Inter',sans-serif;outline:none}
|
||
#feedbackText:focus{border-color:#003E7E}
|
||
.fb-actions{display:flex;gap:8px;margin-top:10px}
|
||
.fb-cancel{flex:1;background:transparent;color:#6B7280;font-size:13px;font-weight:600;border:1.5px solid #E2E8F0;border-radius:12px;padding:10px;cursor:pointer}
|
||
.fb-send{flex:2;background:#003E7E;color:#fff;font-size:13px;font-weight:700;border-radius:12px;border:none;padding:10px;cursor:pointer}
|
||
|
||
/* SLIDE FADE */
|
||
@keyframes slideFade{from{opacity:0;transform:translateY(10px)}to{opacity:1;transform:translateY(0)}}
|
||
.page{animation:slideFade .18s ease-out}
|
||
</style>
|
||
</head>
|
||
<body data-theme="zov">
|
||
<div id="crm-back-nav" style="position:fixed;top:0;left:0;right:0;z-index:9999;background:rgba(255,255,255,0.92);backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);border-bottom:1px solid rgba(0,0,0,.08);padding:8px 16px;display:flex;align-items:center">
|
||
<a href="https://wasrusgen.github.io/wasrusgen1-crm/" style="display:inline-flex;align-items:center;gap:6px;font-family:Inter,system-ui,sans-serif;font-size:13px;font-weight:600;color:#003E7E;text-decoration:none;padding:4px 12px;border-radius:8px;background:#F0F4FF;transition:background .15s" onmouseover="this.style.background='#DDE8FF'" onmouseout="this.style.background='#F0F4FF'">
|
||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M19 12H5M12 5l-7 7 7 7"/></svg>
|
||
Все кабинеты
|
||
</a>
|
||
<span style="margin-left:12px;font-family:Inter,system-ui,sans-serif;font-size:12px;color:#8A94A6">@wasrusgen1 CRM</span>
|
||
</div>
|
||
<div style="height:40px"></div>
|
||
|
||
<div id="controls">
|
||
<label>Экран:</label>
|
||
<select id="screenSelect"></select>
|
||
<div id="themeButtons">
|
||
<button class="theme-btn active" data-t="zov" onclick="setTheme('zov')">CRM</button>
|
||
<button class="theme-btn" data-t="radar" onclick="setTheme('radar')">CRM</button>
|
||
<button class="theme-btn" data-t="dark" onclick="setTheme('dark')">Dark</button>
|
||
<button class="theme-btn" data-t="dark-crm" onclick="setTheme('dark-crm')">CRM Dark</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="phoneWrap">
|
||
<div id="phoneFrame">
|
||
<a href="./index.html" id="in-frame-back" style="display:flex;align-items:center;gap:6px;padding:5px 14px;background:rgba(0,62,126,.06);border-bottom:1px solid rgba(0,62,126,.09);font-family:Inter,system-ui,sans-serif;font-size:11px;font-weight:700;color:#003E7E;text-decoration:none;flex-shrink:0;z-index:200"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M19 12H5M12 5l-7 7 7 7"/></svg>Мокапы кабинетов</a>
|
||
<div id="statusBar">
|
||
<span>9:41</span>
|
||
<div class="sb-right">
|
||
<svg width="16" height="12" viewBox="0 0 16 12" fill="currentColor" style="color:var(--ink)"><rect x="0" y="4" width="3" height="8" rx="1"/><rect x="4.5" y="2.5" width="3" height="9.5" rx="1"/><rect x="9" y="0.5" width="3" height="11.5" rx="1"/><rect x="13.5" y="0" width="2.5" height="12" rx="1" opacity=".3"/></svg>
|
||
<svg width="15" height="12" viewBox="0 0 15 12" fill="none" stroke="currentColor" stroke-width="1.5" style="color:var(--ink)"><path d="M7.5 3C9.5 3 11.3 3.8 12.7 5.1M2.3 5.1C3.7 3.8 5.5 3 7.5 3"/><path d="M7.5 6.5C8.7 6.5 9.8 7 10.6 7.8M4.4 7.8C5.2 7 6.3 6.5 7.5 6.5"/><circle cx="7.5" cy="10" r="1" fill="currentColor"/></svg>
|
||
<svg width="25" height="12" viewBox="0 0 25 12" fill="none" style="color:var(--ink)"><rect x="0.5" y="0.5" width="21" height="11" rx="3" stroke="currentColor" stroke-opacity=".35"/><rect x="1.5" y="1.5" width="17" height="9" rx="2" fill="currentColor"/><path d="M23 4v4a2 2 0 000-4z" fill="currentColor" fill-opacity=".4"/></svg>
|
||
</div>
|
||
</div>
|
||
<div id="screen"></div>
|
||
|
||
<!-- FEEDBACK TRIGGER -->
|
||
<span id="fScreenId" style="display:none">home</span>
|
||
<div id="feedbackTrigger" onclick="openFeedback()">
|
||
<svg width="16" height="13" viewBox="0 0 24 19" fill="none" stroke="rgba(255,255,255,.8)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="flex-shrink:0"><rect x="1" y="1" width="22" height="17" rx="3"/><polyline points="1,1 12,10.5 23,1"/></svg>
|
||
<div class="w1-slot" data-color="rgba(255,255,255,.82)" data-width="62" data-height="10" style="flex-shrink:0"></div>
|
||
<span style="color:rgba(255,255,255,.25);font-size:14px;font-weight:200;line-height:1">|</span>
|
||
<span class="fb-brand" style="font-size:10px;letter-spacing:2px">CRM</span>
|
||
</div>
|
||
<div id="feedbackSheet">
|
||
<div id="feedbackOverlay" onclick="closeFeedback()"></div>
|
||
<div id="feedbackPanel">
|
||
<div class="fb-header">
|
||
<div class="fb-title">Написать разработчику</div>
|
||
<span id="fSheetScreen" class="fb-screen">home</span>
|
||
</div>
|
||
<textarea id="feedbackText" placeholder="Замечание, пожелание, ошибка..."></textarea>
|
||
<div class="fb-actions">
|
||
<button class="fb-cancel" onclick="closeFeedback()">Отмена</button>
|
||
<button class="fb-send" onclick="sendFeedback()">→ Открыть Telegram</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
const SCREENS = {
|
||
home: 'Главная',
|
||
staff: 'Команда',
|
||
staff_detail: 'Карточка сотрудника',
|
||
staff_block_confirm:'Подтверждение блокировки',
|
||
staff_blocked: 'Экран заблокированного',
|
||
assemblies: 'Все сборки',
|
||
assembly_detail: 'Карточка сборки',
|
||
pricelist: 'Прейскурант',
|
||
finances: 'Финансы',
|
||
feed: 'Лента',
|
||
feed_create: 'Создать объявление',
|
||
analytics: 'Аналитика',
|
||
profile: 'Профиль',
|
||
staff_edit: 'Редактирование сотрудника',
|
||
settings: 'Настройки'
|
||
};
|
||
|
||
let currentScreen = 'home';
|
||
|
||
const sel = document.getElementById('screenSelect');
|
||
Object.entries(SCREENS).forEach(([k,v]) => {
|
||
const opt = document.createElement('option');
|
||
opt.value = k; opt.textContent = v;
|
||
sel.appendChild(opt);
|
||
});
|
||
sel.addEventListener('change', e => navigate(e.target.value));
|
||
|
||
function navigate(id) {
|
||
currentScreen = id;
|
||
sel.value = id;
|
||
document.getElementById('screen').innerHTML = '';
|
||
const html = renderScreen(id);
|
||
document.getElementById('screen').innerHTML = html;
|
||
document.getElementById('screen').scrollTop = 0;
|
||
attachHandlers();
|
||
var el = document.getElementById('fScreenId');
|
||
var el2 = document.getElementById('fSheetScreen');
|
||
var screenLabel = id + (SCREENS[id] ? ' · ' + SCREENS[id] : '');
|
||
if (el) el.textContent = id;
|
||
if (el2) el2.textContent = screenLabel;
|
||
}
|
||
|
||
// Refresh home screen without resetting scroll position (for expand/collapse toggles)
|
||
function refresh() {
|
||
var s = document.getElementById('screen');
|
||
var pos = s ? s.scrollTop : 0;
|
||
navigate('home');
|
||
if (s) s.scrollTop = pos;
|
||
}
|
||
|
||
function openFeedback() {
|
||
var screenLabel = currentScreen + (SCREENS[currentScreen] ? ' · ' + SCREENS[currentScreen] : '');
|
||
document.getElementById('fSheetScreen').textContent = screenLabel;
|
||
document.getElementById('feedbackText').value = '';
|
||
document.getElementById('feedbackSheet').style.display = 'flex';
|
||
setTimeout(function(){ document.getElementById('feedbackText').focus(); }, 100);
|
||
}
|
||
function closeFeedback() {
|
||
document.getElementById('feedbackSheet').style.display = 'none';
|
||
}
|
||
function sendFeedback() {
|
||
var text = document.getElementById('feedbackText').value.trim();
|
||
if (!text) { document.getElementById('feedbackText').focus(); return; }
|
||
var screenLabel = currentScreen + (SCREENS[currentScreen] ? ' · ' + SCREENS[currentScreen] : '');
|
||
var msg = '[@wasrusgen1 CRM / ' + screenLabel + ']\n' + text;
|
||
var ta = document.getElementById('feedbackText');
|
||
var btn = document.querySelector('.fb-send');
|
||
navigator.clipboard.writeText(msg).then(function() {
|
||
ta.value = ''; ta.placeholder = '✓ Сообщение скопировано';
|
||
ta.disabled = true; btn.textContent = '→ Открываю Telegram...';
|
||
setTimeout(function() {
|
||
window.open('https://t.me/wasrusgen1', '_blank');
|
||
closeFeedback();
|
||
ta.disabled = false; ta.placeholder = 'Замечание, пожелание, ошибка...';
|
||
btn.textContent = '→ Открыть Telegram';
|
||
}, 700);
|
||
}).catch(function() {
|
||
window.open('https://t.me/wasrusgen1', '_blank');
|
||
closeFeedback();
|
||
});
|
||
}
|
||
|
||
function setTheme(t) {
|
||
document.body.dataset.theme = t;
|
||
document.querySelectorAll('.theme-btn').forEach(b => b.classList.toggle('active', b.dataset.t === t));
|
||
}
|
||
|
||
function navBar(active) {
|
||
return `<div class="bottom-nav">
|
||
<div class="nav-item ${active==='home'?'active':''}" onclick="navigate('home')">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 9l9-7 9 7v11a2 2 0 01-2 2H5a2 2 0 01-2-2z"/><polyline points="9 22 9 12 15 12 15 22"/></svg>
|
||
<span>Главная</span>
|
||
</div>
|
||
<div class="nav-item ${active==='staff'||active==='staff_detail'?'active':''}" onclick="navigate('staff')">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M2 18a1 1 0 001 1h18a1 1 0 001-1v-2a1 1 0 00-1-1H3a1 1 0 00-1 1v2z"/><path d="M10 10V5a1 1 0 011-1h2a1 1 0 011 1v5"/><path d="M4 15v-3a8 8 0 018-8"/><path d="M20 15v-3a8 8 0 00-8-8"/></svg>
|
||
<span>Сборщики</span>
|
||
</div>
|
||
<div class="nav-item ${active==='pricelist'?'active':''}" onclick="navigate('pricelist')">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2"/><rect x="9" y="3" width="6" height="4" rx="1"/><line x1="9" y1="12" x2="15" y2="12"/><line x1="9" y1="16" x2="13" y2="16"/></svg>
|
||
<span>Прейскурант</span>
|
||
</div>
|
||
<div class="nav-item ${active==='finances'?'active':''}" onclick="navigate('finances')">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="2" y="5" width="20" height="14" rx="2"/><line x1="2" y1="10" x2="22" y2="10"/></svg>
|
||
<span>Финансы</span>
|
||
</div>
|
||
<div class="nav-item ${active==='profile'?'active':''}" onclick="navigate('profile')">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M20 21v-2a4 4 0 00-4-4H8a4 4 0 00-4 4v2"/><circle cx="12" cy="7" r="4"/></svg>
|
||
<span>Профиль</span>
|
||
</div>
|
||
</div>`;
|
||
}
|
||
|
||
function renderScreen(id) {
|
||
switch(id) {
|
||
case 'home': return screenHome();
|
||
case 'staff': return screenStaff();
|
||
case 'staff_detail': return screenStaffDetail();
|
||
case 'staff_block_confirm':return screenStaffBlockConfirm();
|
||
case 'staff_blocked': return screenStaffBlocked();
|
||
case 'staff_edit': return screenStaffEdit();
|
||
case 'assemblies': return screenAssemblies();
|
||
case 'assembly_detail': return screenAssemblyDetail();
|
||
case 'pricelist': return screenPricelist();
|
||
case 'pricelist_preview': return screenPricelistPreview();
|
||
case 'feed': return screenFeed();
|
||
case 'feed_create': return screenFeedCreate();
|
||
case 'finances': return screenFinances();
|
||
case 'analytics': return screenAnalytics();
|
||
case 'profile': return screenProfile();
|
||
case 'settings': return screenSettings();
|
||
case 'icon_showcase': return screenIconShowcase();
|
||
default: return '<div style="padding:40px;text-align:center;color:var(--muted)">Экран не найден</div>';
|
||
}
|
||
}
|
||
|
||
// ── SCREEN 1: HOME ──────────────────────────────────────────
|
||
function screenHome() {
|
||
return `<div class="page">
|
||
<!-- Header -->
|
||
<div style="background:linear-gradient(160deg,var(--accent) 0%,var(--accent) 70%,rgba(0,0,0,.08) 100%);padding:20px 16px 18px">
|
||
<div style="font-size:13px;color:rgba(255,255,255,.65);font-weight:500">Четверг, 22 мая 2026</div>
|
||
<div style="font-size:22px;font-weight:800;color:#fff;margin-top:4px">Добрый день, Руслан 👋</div>
|
||
<div style="font-size:13px;color:rgba(255,255,255,.7);margin-top:2px">Директор по сервису · @wasrusgen1 CRM</div>
|
||
</div>
|
||
|
||
<!-- KPI плитки 2×col -->
|
||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:10px;padding:14px 16px 0">
|
||
${[
|
||
{val:'1', lbl:'Рекламации', warn:true, icon:'<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>'},
|
||
{val:'48/60', lbl:'Мест на складе', warn:false, icon:'<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"/></svg>'},
|
||
{val:'4/7', lbl:'Сборщиков онлайн',warn:false,icon:'<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><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>'},
|
||
{val:'42 уп.',lbl:'Экспедиция', warn:false, icon:'<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><rect x="1" y="3" width="15" height="13" rx="1"/><path d="M16 8h4l3 3v5h-7V8z"/><circle cx="5.5" cy="18.5" r="2.5"/><circle cx="18.5" cy="18.5" r="2.5"/></svg>'},
|
||
{val:'11/12', lbl:'Замеров в срок', warn:false, icon:'<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 6H3"/><path d="M10 12H3"/><path d="M10 18H3"/><polyline points="15 13 18 16 22 10"/></svg>'},
|
||
{val:'1', lbl:'Нарушений', warn:true, icon:'<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg>'},
|
||
{val:'3', lbl:'Доп. заказов', green:true, icon:'<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><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>'},
|
||
].map(function(k,i,arr){
|
||
var col=k.warn?'var(--danger)':k.green?'#16A34A':'var(--accent)';
|
||
var brd=k.warn?';border-left:3px solid var(--danger)':k.green?';border-left:3px solid #16A34A':'';
|
||
var isLast=i===arr.length-1&&arr.length%2!==0;
|
||
var svg=k.icon.replace('stroke="currentColor"','stroke="'+col+'"');
|
||
var fs=k.val.length>5?'14':'20';
|
||
return '<div class="card" style="margin-bottom:0;padding:12px 14px'+(isLast?';grid-column:1/-1':'')+brd+'">'
|
||
+'<div style="display:flex;align-items:center;gap:8px;margin-bottom:5px">'+svg
|
||
+'<div style="font-size:'+fs+'px;font-weight:900;color:'+col+';line-height:1">'+k.val+'</div>'
|
||
+'</div>'
|
||
+'<div style="font-size:11px;color:var(--muted);font-weight:500;white-space:nowrap;overflow:hidden;text-overflow:ellipsis">'+k.lbl+'</div>'
|
||
+'</div>';
|
||
}).join('')}
|
||
</div>
|
||
|
||
<div style="padding:0 16px">
|
||
|
||
<!-- ① СЕЙЧАС — сводный алерт-блок -->
|
||
${(function(){
|
||
var alerts=[
|
||
{lvl:'red', text:'Рекламация просрочена', sub:'Сборка #А-2847 · Николаев П.В.', action:"navigate('assemblies')"},
|
||
{lvl:'yellow', text:'Замер сдан с опозданием', sub:'Козлов П.Р. · Мира 7, кв.18 · +47 мин', action:''},
|
||
{lvl:'yellow', text:'Нет связи с 08:40', sub:'Николаев П.В. · ул. Космонавтов, 44', action:"navigate('assemblies')"},
|
||
{lvl:'yellow', text:'6 дней без назначений', sub:'Николаев П.В. — диспетчер не ставил заказы', action:''},
|
||
{lvl:'red', text:'4 заказа без технолога', sub:'Ожидают распределения · старейший 3 ч.', action:''},
|
||
{lvl:'yellow', text:'Долг 34 299 ₽', sub:'Иванов Д. · просрочено 5 дней', action:"navigate('staff_detail')"},
|
||
];
|
||
var rows=alerts.map(function(a,i){
|
||
var dc=a.lvl==='red'?'var(--danger)':'var(--warn)';
|
||
var isLast=i===alerts.length-1;
|
||
return '<div onclick="'+(a.action||'')+'" style="display:flex;align-items:center;gap:10px;padding:9px 0;'+(isLast?'':'border-bottom:1px solid rgba(0,0,0,.05);')+';cursor:'+(a.action?'pointer':'default')+'">'
|
||
+'<div style="width:7px;height:7px;border-radius:50%;background:'+dc+';flex-shrink:0;margin-top:1px"></div>'
|
||
+'<div style="flex:1;min-width:0">'
|
||
+'<div style="font-size:12px;font-weight:700;color:var(--ink);line-height:1.2">'+a.text+'</div>'
|
||
+'<div style="font-size:10px;color:var(--muted);margin-top:2px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">'+a.sub+'</div>'
|
||
+'</div>'
|
||
+(a.action?'<span style="font-size:13px;color:var(--muted);flex-shrink:0">›</span>':'')
|
||
+'</div>';
|
||
}).join('');
|
||
var nRed=alerts.filter(function(a){return a.lvl==='red';}).length;
|
||
var col=window._nowCol;
|
||
return '<div style="margin-top:16px">'
|
||
+'<div onclick="(function(){window._nowCol=!window._nowCol;refresh()})()" style="display:flex;align-items:center;justify-content:space-between;margin-bottom:'+(col?0:8)+'px;cursor:pointer;padding:2px 0">'
|
||
+'<div style="font-size:10px;font-weight:700;color:var(--muted);letter-spacing:.6px">СЕЙЧАС</div>'
|
||
+'<div style="display:flex;gap:5px;align-items:center">'
|
||
+'<span style="font-size:10px;background:rgba(239,68,68,.12);color:var(--danger);border-radius:4px;padding:2px 7px;font-weight:700">'+nRed+' срочно</span>'
|
||
+'<span style="font-size:10px;background:rgba(0,0,0,.06);color:var(--muted);border-radius:4px;padding:2px 7px;font-weight:600">'+alerts.length+' всего</span>'
|
||
+'<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" style="color:var(--muted);transition:transform .2s;transform:rotate('+(col?'0':'-180')+'deg)"><polyline points="6 9 12 15 18 9"/></svg>'
|
||
+'</div>'
|
||
+'</div>'
|
||
+(col?'':'<div class="card" style="padding:2px 14px 4px">'+rows+'</div>')
|
||
+'</div>';
|
||
})()}
|
||
|
||
<!-- Рекламации — компактно + разворот -->
|
||
<div class="card" style="margin-top:20px;margin-bottom:8px;padding:12px 14px">
|
||
<div class="row sb" style="cursor:pointer;margin-bottom:0" onclick="(function(){window._reclExp=!window._reclExp;refresh()})()">
|
||
<div style="flex:1">
|
||
<div style="font-size:13px;font-weight:700;color:var(--ink);margin-bottom:5px">⚠️ Рекламации</div>
|
||
<div style="display:flex;align-items:center;gap:10px">
|
||
<span style="font-size:12px;color:var(--muted)">Сегодня: <b style="color:var(--warn)">1</b> из 5 сборок</span>
|
||
<span style="width:1px;height:12px;background:rgba(0,0,0,.1)"></span>
|
||
<span style="font-size:11px;background:rgba(239,68,68,.1);color:var(--danger);border-radius:4px;padding:2px 7px;font-weight:700">🚨 1 просрочена</span>
|
||
</div>
|
||
<div style="font-size:11px;color:var(--success);font-weight:600;margin-top:5px">↓ Тренд снижения · янв→май: 24→3</div>
|
||
</div>
|
||
<div style="font-size:12px;color:var(--accent);font-weight:600;flex-shrink:0;margin-left:8px">${window._reclExp ? '▲' : '▼'}</div>
|
||
</div>
|
||
|
||
${window._reclExp ? `
|
||
<!-- Развёрнутый вид -->
|
||
<div style="margin-top:12px;padding-top:12px;border-top:1px solid rgba(0,0,0,.06)">
|
||
|
||
<!-- Открытые: регламент vs просрочка -->
|
||
<div style="font-size:10px;font-weight:700;color:var(--muted);letter-spacing:.5px;margin-bottom:7px">ОТКРЫТЫХ: 3</div>
|
||
<div style="display:flex;gap:6px;margin-bottom:12px">
|
||
<div style="flex:1;padding:8px;background:rgba(16,185,129,.08);border-radius:8px;text-align:center">
|
||
<div style="font-size:18px;font-weight:800;color:var(--success)">2</div>
|
||
<div style="font-size:10px;color:var(--success);font-weight:600;margin-top:2px">В регламенте</div>
|
||
</div>
|
||
<div style="flex:1;padding:8px;background:rgba(239,68,68,.1);border-radius:8px;border:1px solid rgba(239,68,68,.2);text-align:center">
|
||
<div style="font-size:18px;font-weight:800;color:var(--danger)">1</div>
|
||
<div style="font-size:10px;color:var(--danger);font-weight:700;margin-top:2px">🚨 Просрочено</div>
|
||
<div style="font-size:10px;color:var(--muted);margin-top:1px">Иванов Д. · 5д</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Grouped bars + peak line -->
|
||
<div style="font-size:10px;font-weight:700;color:var(--muted);letter-spacing:.5px;margin-bottom:10px">ПО ИСТОЧНИКУ · ПОМЕСЯЧНО</div>
|
||
${(function(){
|
||
var months = ['Янв','Фев','Мар','Апр','Май'];
|
||
var groups = [
|
||
{name:'Сборщики', c:'#EF4444', data:[12,9,7,5,3]},
|
||
{name:'Фабрика', c:'#F59E0B', data:[8,6,5,4,3]},
|
||
{name:'Технологи',c:'#8B5CF6', data:[3,2,2,1,1]},
|
||
{name:'Менеджеры',c:'#3B82F6', data:[2,2,1,1,1]},
|
||
];
|
||
var W=286, H=72, padX=14, padY=8, barGap=2;
|
||
var maxV=14;
|
||
var mCount=months.length, gCount=groups.length;
|
||
var groupW=(W-2*padX)/mCount;
|
||
var barW=(groupW-barGap*(gCount+1))/gCount;
|
||
|
||
// bar rects
|
||
var bars='';
|
||
for(var mi=0;mi<mCount;mi++){
|
||
var gx=padX+mi*groupW;
|
||
for(var gi=0;gi<gCount;gi++){
|
||
var v=groups[gi].data[mi];
|
||
var bh=Math.max(2,(v/maxV)*(H-2*padY));
|
||
var bx=gx+barGap+(gi*(barW+barGap));
|
||
var by=H-padY-bh;
|
||
var op=mi===mCount-1?1:0.55;
|
||
bars+='<rect x="'+bx.toFixed(1)+'" y="'+by.toFixed(1)+'" width="'+barW.toFixed(1)+'" height="'+bh.toFixed(1)+'" rx="2" fill="'+groups[gi].c+'" opacity="'+op+'"/>';
|
||
}
|
||
}
|
||
|
||
// peak line = top of each month's tallest group (Сборщики always highest)
|
||
var peakPts=months.map(function(_,mi){
|
||
var maxGrp=groups.reduce(function(a,g){return g.data[mi]>a.data[mi]?g:a;});
|
||
var gi=groups.indexOf(maxGrp);
|
||
var gx=padX+mi*groupW;
|
||
var bx=gx+barGap+(gi*(barW+barGap))+barW/2;
|
||
var bh=(maxGrp.data[mi]/maxV)*(H-2*padY);
|
||
return [bx, H-padY-bh];
|
||
});
|
||
var linePath='M '+peakPts[0][0].toFixed(1)+','+peakPts[0][1].toFixed(1);
|
||
for(var i=1;i<peakPts.length;i++){
|
||
var cx=((peakPts[i-1][0]+peakPts[i][0])/2).toFixed(1);
|
||
linePath+=' C '+cx+','+peakPts[i-1][1].toFixed(1)+' '+cx+','+peakPts[i][1].toFixed(1)+' '+peakPts[i][0].toFixed(1)+','+peakPts[i][1].toFixed(1);
|
||
}
|
||
var dots=peakPts.map(function(p,i){
|
||
return '<circle cx="'+p[0].toFixed(1)+'" cy="'+p[1].toFixed(1)+'" r="'+(i===mCount-1?4:2.5)+'" fill="'+(i===mCount-1?'#EF4444':'white')+'" stroke="#EF4444" stroke-width="1.5"/>';
|
||
}).join('');
|
||
|
||
// month labels
|
||
var mlabels=months.map(function(m,i){
|
||
var cx=(padX+i*groupW+groupW/2).toFixed(1);
|
||
var cur=i===mCount-1;
|
||
return '<text x="'+cx+'" y="'+(H+10)+'" text-anchor="middle" font-size="9" fill="'+(cur?'var(--accent)':'rgba(0,0,0,.4)')+'" font-weight="'+(cur?700:400)+'">'+m+'</text>';
|
||
}).join('');
|
||
|
||
// grid
|
||
var grid=[0,5,10].map(function(v){
|
||
var y=(H-padY-(v/maxV)*(H-2*padY)).toFixed(1);
|
||
return '<line x1="'+padX+'" y1="'+y+'" x2="'+(W-padX)+'" y2="'+y+'" stroke="rgba(0,0,0,.05)" stroke-width="1"/>'
|
||
+'<text x="'+(padX-2)+'" y="'+(parseFloat(y)+3)+'" text-anchor="end" font-size="8" fill="rgba(0,0,0,.25)">'+v+'</text>';
|
||
}).join('');
|
||
|
||
return '<svg width="'+W+'" height="'+(H+14)+'" style="overflow:visible;display:block">'
|
||
+grid+bars
|
||
+'<path d="'+linePath+'" fill="none" stroke="#EF4444" stroke-width="1.8" stroke-dasharray="4,2" stroke-linecap="round"/>'
|
||
+dots+mlabels+'</svg>';
|
||
})()}
|
||
<!-- Легенда -->
|
||
<div style="display:flex;flex-wrap:wrap;gap:6px 12px;margin-top:10px">
|
||
${[{n:'Сборщики',c:'#EF4444'},{n:'Фабрика',c:'#F59E0B'},{n:'Технологи',c:'#8B5CF6'},{n:'Менеджеры',c:'#3B82F6'}].map(g=>`
|
||
<div style="display:flex;align-items:center;gap:4px">
|
||
<div style="width:10px;height:10px;border-radius:2px;background:${g.c}"></div>
|
||
<span style="font-size:10px;color:var(--muted)">${g.n}</span>
|
||
</div>`).join('')}
|
||
</div>
|
||
<div style="margin-top:8px">
|
||
<span style="font-size:10px;color:var(--muted)">Закрыто из прошлых периодов сегодня: <b style="color:var(--accent)">+2</b></span>
|
||
</div>
|
||
</div>
|
||
` : ''}
|
||
</div>
|
||
|
||
<!-- Склад · Поток (совмещённый flow-чарт) -->
|
||
${(function(){
|
||
var col = window._stockCol;
|
||
var chevron = '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" style="color:var(--muted);flex-shrink:0;transform:rotate('+(col?'0':'-180')+'deg)"><polyline points="6 9 12 15 18 9"/></svg>';
|
||
var header = '<div onclick="(function(){window._stockCol=!window._stockCol;refresh()})()" class="row sb" style="padding:12px 16px;cursor:pointer;border-bottom:'+(col?'none':'1px solid rgba(0,0,0,.06)')+'">'
|
||
+'<div style="display:flex;align-items:center;gap:8px">'
|
||
+'<span style="font-size:13px;font-weight:700;color:var(--ink)">📦 Склад · Поток</span>'
|
||
+'<span style="font-size:11px;background:rgba(239,68,68,.1);color:var(--danger);border-radius:4px;padding:2px 8px;font-weight:700">Договор: 12д</span>'
|
||
+(col?'<span style="font-size:11px;background:rgba(245,158,11,.12);color:#D97706;border-radius:4px;padding:2px 8px;font-weight:700">⚠ 80%</span>':'')+'</div>'+chevron+'</div>';
|
||
if (col) return '<div class="card" style="padding:0;overflow:hidden;margin-top:18px;margin-bottom:8px">'+header+'<div style="height:3px;border-radius:2px;background:linear-gradient(to right,#10B981,#FBBF24,#EF4444);clip-path:inset(0 20% 0 0);opacity:.8"></div>'+'</div>';
|
||
var chart = (function(){
|
||
var p=window._stockPeriod||'week';
|
||
var allData={
|
||
week:[
|
||
{lbl:'н-4',s:52,sp:35,dep:45,arr:62,past:true},
|
||
{lbl:'н-3',s:42,sp:52,dep:58,arr:48,past:true},
|
||
{lbl:'н-2',s:47,sp:42,dep:50,arr:55,past:true},
|
||
{lbl:'н-1',s:41,sp:47,dep:44,arr:38,past:true},
|
||
{lbl:'тек', s:48,sp:41,dep:35,arr:42,cur:true},
|
||
{lbl:'н+1',s:51,sp:48,dep:35,arr:38,future:true},
|
||
{lbl:'н+2',s:60,sp:51,dep:35,arr:45,future:true},
|
||
{lbl:'н+3',s:25,sp:60,dep:35,arr:0, future:true},
|
||
],
|
||
month:[
|
||
{lbl:'Окт',s:38,sp:22,dep:120,arr:136,past:true},
|
||
{lbl:'Ноя',s:44,sp:38,dep:105,arr:111,past:true},
|
||
{lbl:'Дек',s:55,sp:44,dep:98, arr:109,past:true},
|
||
{lbl:'Янв',s:41,sp:55,dep:142,arr:128,past:true},
|
||
{lbl:'Фев',s:36,sp:41,dep:118,arr:113,past:true},
|
||
{lbl:'Мар',s:47,sp:36,dep:125,arr:136,past:true},
|
||
{lbl:'Апр',s:52,sp:47,dep:110,arr:115,past:true},
|
||
{lbl:'Май',s:48,sp:52,dep:140,arr:136,cur:true},
|
||
{lbl:'Июн',s:51,sp:48,dep:140,arr:143,future:true},
|
||
{lbl:'Июл',s:58,sp:51,dep:130,arr:137,future:true},
|
||
]
|
||
};
|
||
var weeks=allData[p];
|
||
var n=weeks.length;
|
||
var svgW=310,svgH=136,padT=22,padB=16,padL=4,padR=4;
|
||
var cW=svgW-padL-padR,cH=svgH-padT-padB;
|
||
var maxS=60,ww=cW/n;
|
||
var bw=Math.round(ww*0.70);
|
||
var yBot=padT+cH;
|
||
var elems='';
|
||
var limY=yBot-(54/maxS)*cH;
|
||
elems+='<line x1="'+padL+'" y1="'+limY+'" x2="'+(svgW-padR)+'" y2="'+limY+'" stroke="#EF4444" stroke-width="1" stroke-dasharray="4,3" opacity="0.4"/>';
|
||
elems+='<text x="'+(padL+2)+'" y="'+(limY-2)+'" font-size="7" fill="#EF4444" opacity="0.6">90%</text>';
|
||
var tekIdx=weeks.findIndex?weeks.findIndex(function(w){return w.cur;}):4;
|
||
if(tekIdx<0) tekIdx=4;
|
||
var divX=padL+ww*tekIdx+ww/2;
|
||
elems+='<line x1="'+divX+'" y1="'+padT+'" x2="'+divX+'" y2="'+yBot+'" stroke="var(--accent)" stroke-width="1" stroke-dasharray="2,2" opacity="0.2"/>';
|
||
weeks.forEach(function(w,i){
|
||
var cx=padL+ww*i+ww/2;
|
||
var totalH=Math.round((w.s/maxS)*cH);
|
||
var grayH=Math.round((Math.max(0,w.sp-w.dep)/maxS)*cH);
|
||
var blueH=totalH-grayH;
|
||
var yGray=yBot-grayH;
|
||
var yBlue=yBot-totalH;
|
||
var rx=3;
|
||
if(grayH>0){
|
||
elems+='<rect x="'+(cx-bw/2)+'" y="'+yGray+'" width="'+bw+'" height="'+grayH+'" rx="0" fill="rgba(148,163,184,'+(w.past?0.5:0.85)+')" style="border-radius:0 0 '+rx+'px '+rx+'px"/>';
|
||
elems+='<rect x="'+(cx-bw/2)+'" y="'+(yBot-Math.min(grayH,rx))+'" width="'+bw+'" height="'+Math.min(grayH,rx)+'" rx="'+rx+'" fill="rgba(148,163,184,'+(w.past?0.5:0.85)+')" />';
|
||
}
|
||
if(blueH>0){
|
||
var blueColor=w.past?'rgba(99,102,241,.35)':(w.cur?'#6366F1':'rgba(99,102,241,.75)');
|
||
elems+='<rect x="'+(cx-bw/2)+'" y="'+yBlue+'" width="'+bw+'" height="'+blueH+'" rx="'+rx+'" fill="'+blueColor+'"/>';
|
||
if(grayH>0) elems+='<rect x="'+(cx-bw/2)+'" y="'+yGray+'" width="'+bw+'" height="'+rx+'" fill="'+blueColor+'"/>';
|
||
} else if(grayH>0) {
|
||
elems+='<rect x="'+(cx-bw/2)+'" y="'+yGray+'" width="'+bw+'" height="'+Math.min(grayH,rx)+'" rx="'+rx+'" fill="rgba(148,163,184,'+(w.past?0.5:0.85)+')"/>';
|
||
}
|
||
var numColor=w.s/maxS>=0.9?'#EF4444':(w.s/maxS>=0.7?'#F59E0B':'#10B981');
|
||
if(!w.past){
|
||
elems+='<text x="'+cx+'" y="'+(yBlue-4)+'" text-anchor="middle" font-size="'+(w.cur?11:9)+'" fill="'+numColor+'" font-weight="'+(w.cur?800:700)+'">'+w.s+'</text>';
|
||
} else {
|
||
elems+='<text x="'+cx+'" y="'+(yBlue-3)+'" text-anchor="middle" font-size="7.5" fill="rgba(0,0,0,.28)">'+w.s+'</text>';
|
||
}
|
||
if(w.dep>0 && !w.past && grayH>14){
|
||
var depY=Math.round(yGray+grayH/2+4);
|
||
elems+='<text x="'+cx+'" y="'+depY+'" text-anchor="middle" font-size="7.5" fill="rgba(255,255,255,.85)" font-weight="700">−'+w.dep+'</text>';
|
||
}
|
||
if(w.arr>0 && !w.past && blueH>14){
|
||
var arrY=Math.round(yBlue+blueH/2+4);
|
||
elems+='<text x="'+cx+'" y="'+arrY+'" text-anchor="middle" font-size="7.5" fill="rgba(255,255,255,.9)" font-weight="700">+'+w.arr+'</text>';
|
||
}
|
||
var lc=w.cur?'var(--accent)':(w.future?'rgba(0,0,0,.5)':'rgba(0,0,0,.28)');
|
||
elems+='<text x="'+cx+'" y="'+(svgH-2)+'" text-anchor="middle" font-size="8" fill="'+lc+'" font-weight="'+(w.cur?700:400)+'">'+w.lbl+'</text>';
|
||
});
|
||
return '<svg width="'+svgW+'" height="'+svgH+'" style="display:block;overflow:visible">'+elems+'</svg>';
|
||
})();
|
||
var periodBtns = (function(){
|
||
var p=window._stockPeriod||'week';
|
||
return ['week','month'].map(function(v,i){
|
||
var lbl=['Нед','Мес'][i];
|
||
var active=p===v;
|
||
return '<div onclick="(function(){window._stockPeriod=\''+v+'\';refresh()})()" style="padding:3px 10px;border-radius:4px;font-size:10px;font-weight:'+(active?700:500)+';color:'+(active?'var(--accent)':'var(--muted)')+';background:'+(active?'var(--card)':'transparent')+';box-shadow:'+(active?'0 1px 3px rgba(0,0,0,.1)':'none')+';cursor:pointer">'+lbl+'</div>';
|
||
}).join('');
|
||
})();
|
||
var kpi = '<div class="row sb" style="margin-bottom:10px">'
|
||
+'<div>'
|
||
+'<div style="font-size:22px;font-weight:800;color:var(--accent);line-height:1">48 <span style="font-size:13px;font-weight:500;color:var(--muted)">/ 60 мест</span></div>'
|
||
+'<div style="font-size:11px;color:var(--muted);margin-top:2px">Баланс нед: <span style="color:var(--success);font-weight:700">−4 упак. ↓</span></div>'
|
||
+'</div>'
|
||
+'<span style="font-size:11px;color:var(--warn);font-weight:700">⚠ 80%</span>'
|
||
+'</div>';
|
||
var legend = '<div style="display:flex;align-items:center;margin-top:6px;gap:10px">'
|
||
+'<div style="display:flex;gap:12px;align-items:center;flex:1;flex-wrap:wrap">'
|
||
+'<div style="display:flex;align-items:center;gap:4px"><div style="width:10px;height:10px;border-radius:2px;background:rgba(148,163,184,.85)"></div><span style="font-size:10px;color:var(--muted)">Переходящий остаток</span></div>'
|
||
+'<div style="display:flex;align-items:center;gap:4px"><div style="width:10px;height:10px;border-radius:2px;background:#6366F1;opacity:.8"></div><span style="font-size:10px;color:var(--muted)">Приход с фабрики</span></div>'
|
||
+'</div>'
|
||
+'<div style="display:flex;background:rgba(0,0,0,.06);border-radius:6px;padding:2px;gap:1px;flex-shrink:0">'+periodBtns+'</div>'
|
||
+'</div>';
|
||
var alertHtml = '<div style="margin-top:10px;background:rgba(239,68,68,.07);border:1px solid rgba(239,68,68,.18);border-radius:8px;padding:8px 12px;display:flex;align-items:center;justify-content:space-between;gap:8px">'
|
||
+'<div style="font-size:11px;color:var(--danger);font-weight:600;line-height:1.3">⚠ н+2: прогноз 61/60 — риск переполнения</div>'
|
||
+'<div style="font-size:11px;color:var(--accent);font-weight:700;cursor:pointer;white-space:nowrap;flex-shrink:0" onclick="(function(){window._expExp=true;window._expFromAlert=true;refresh()})()">Вывоз ↗</div>'
|
||
+'</div>';
|
||
var body = '<div style="padding:14px 16px">'
|
||
+kpi
|
||
+'<div style="margin-bottom:8px"><div style="font-size:10px;font-weight:700;color:var(--muted);letter-spacing:.5px">ПРОГНОЗ / 60 МЕСТ</div></div>'
|
||
+chart+legend+alertHtml
|
||
+'</div>';
|
||
return '<div class="card" style="padding:0;overflow:hidden;margin-top:18px;margin-bottom:8px">'+header+body+'</div>';
|
||
})()}
|
||
|
||
<!-- Загрузка команды -->
|
||
${(function(){
|
||
var col = window._teamCol;
|
||
var chevron = '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" style="color:var(--muted);flex-shrink:0;transform:rotate('+(col?'0':'-180')+'deg)"><polyline points="6 9 12 15 18 9"/></svg>';
|
||
var header = '<div onclick="(function(){window._teamCol=!window._teamCol;refresh()})()" class="row sb" style="padding:12px 14px;cursor:pointer;border-bottom:'+(col?'none':'1px solid rgba(0,0,0,.06)')+'">'
|
||
+'<div style="display:flex;align-items:center;gap:8px">'
|
||
+'<span style="font-size:13px;font-weight:700;color:var(--ink)">👥 Загрузка команды</span>'
|
||
+'<span style="font-size:11px;color:var(--muted)">22–28 мая</span>'
|
||
+(col?'<span style="font-size:11px;background:rgba(245,158,11,.12);color:#D97706;border-radius:4px;padding:2px 8px;font-weight:700">3/4 сег.</span>':'')+'</div>'+chevron+'</div>';
|
||
if (col) return '<div class="card" style="padding:0;overflow:hidden;margin-top:8px;margin-bottom:8px">'+header+'<div style="height:3px;border-radius:2px;background:linear-gradient(to right,#10B981,#FBBF24,#EF4444);clip-path:inset(0 25% 0 0);opacity:.8"></div>'+'</div>';
|
||
var cats = (function(){
|
||
var data=[
|
||
{cat:'A', label:'Кат. А', total:2, color:'#10B981',
|
||
todayCount:2, weekPct:57, freeDays:0, freeName:'', alert:null},
|
||
{cat:'Б', label:'Кат. Б', total:2, color:'#F59E0B',
|
||
todayCount:1, weekPct:43, freeDays:6, freeName:'Николаев', alert:null},
|
||
];
|
||
return data.map(function(c,ci){
|
||
var allOk=c.todayCount===c.total;
|
||
var none=c.todayCount===0;
|
||
var statusColor=allOk?'var(--success)':(none?'var(--danger)':'var(--warn)');
|
||
var statusIcon=allOk?'✓':(none?'✕':'⚠');
|
||
var isLast=ci===data.length-1;
|
||
var catColor=c.cat==='A'?'var(--success)':'var(--accent)';
|
||
var catBg=c.cat==='A'?'rgba(16,185,129,.1)':'rgba(67,56,202,.08)';
|
||
var alertRow=c.alert?'<div style="margin-top:8px;font-size:10px;color:var(--warn);background:rgba(245,158,11,.08);border-radius:6px;padding:6px 10px;line-height:1.4">⚠ '+c.alert+'</div>':'';
|
||
return '<div style="margin-bottom:'+(isLast?0:14)+'px;padding-bottom:'+(isLast?0:14)+'px;border-bottom:'+(isLast?'none':'1px solid rgba(0,0,0,.06)')+'">'
|
||
+'<div style="display:flex;align-items:center;gap:8px;margin-bottom:8px">'
|
||
+'<span style="font-size:12px;font-weight:800;color:'+catColor+';background:'+catBg+';border-radius:5px;padding:3px 10px">'+c.label+'</span>'
|
||
+'<span style="font-size:12px;color:var(--muted)">'+c.total+' чел.</span>'
|
||
+'<div style="flex:1"></div>'
|
||
+'<span style="font-size:11px;color:var(--muted)">Сегодня</span>'
|
||
+'<span style="font-size:14px;font-weight:800;color:'+statusColor+';margin-left:5px">'+c.todayCount+'/'+c.total+'</span>'
|
||
+'<span style="font-size:13px;color:'+statusColor+';margin-left:3px">'+statusIcon+'</span>'
|
||
+'</div>'
|
||
+'<div style="display:flex;align-items:center;gap:8px">'
|
||
+'<div style="flex:1;background:rgba(0,0,0,.06);border-radius:6px;height:8px;overflow:hidden">'
|
||
+'<div style="width:'+c.weekPct+'%;height:100%;background:'+c.color+';border-radius:6px;transition:width .3s"></div>'
|
||
+'</div>'
|
||
+'<span style="font-size:11px;font-weight:700;color:var(--muted);white-space:nowrap">'+c.weekPct+'% нед.</span>'
|
||
+'</div>'
|
||
+alertRow+'</div>';
|
||
}).join('');
|
||
})();
|
||
var memberGrid = (function(){
|
||
var expBtn = '<div style="margin-top:10px;padding-top:8px;border-top:1px solid rgba(0,0,0,.06)">'
|
||
+'<div style="display:flex;align-items:center;justify-content:space-between;cursor:pointer" onclick="(function(){window._teamExp=!window._teamExp;refresh()})()">'
|
||
+'<span style="font-size:11px;color:var(--muted)">Сотрудники по дням</span>'
|
||
+'<span style="font-size:11px;color:var(--accent);font-weight:600">'+(window._teamExp?'▲ Свернуть':'▼ Развернуть')+'</span>'
|
||
+'</div>';
|
||
if (!window._teamExp) return expBtn+'</div>';
|
||
var days=['Пн','Вт','Ср','Чт','Пт','Сб','Вс'];
|
||
var todayIdx=2;
|
||
var members=[
|
||
{name:'Кириллов', cat:'A', days:[1,1,1,1,0,0,0], color:'#10B981'},
|
||
{name:'Петров', cat:'A', days:[1,1,1,0,0,1,0], color:'#10B981'},
|
||
{name:'Смирнов', cat:'Б', days:[1,1,1,1,1,0,0], color:'#F59E0B'},
|
||
{name:'Николаев', cat:'Б', days:[1,0,0,0,0,0,0], color:'#EF4444'},
|
||
];
|
||
var hdr='<div style="display:grid;grid-template-columns:70px 1fr auto;gap:4px;align-items:center;margin-top:8px;margin-bottom:4px">'
|
||
+'<div></div>'
|
||
+'<div style="display:flex;justify-content:space-between;padding:0 1px">'
|
||
+days.map(function(d,i){
|
||
var isWeekend=i>=5,isToday=i===todayIdx;
|
||
return '<span style="font-size:8px;color:'+(isToday?'var(--accent)':isWeekend?'var(--warn)':'var(--muted)')+';font-weight:'+(isToday?700:500)+';width:16px;text-align:center">'+d+'</span>';
|
||
}).join('')
|
||
+'</div><span style="font-size:8px;color:var(--muted);text-align:right;padding-right:2px">св.</span></div>';
|
||
var rows=members.map(function(m){
|
||
var catColor=m.cat==='A'?'var(--success)':'var(--accent)';
|
||
var catBg=m.cat==='A'?'rgba(16,185,129,.1)':'rgba(67,56,202,.08)';
|
||
var free=m.days.filter(function(d){return d===0;}).length;
|
||
var cells=m.days.map(function(d,i){
|
||
var isWeekend=i>=5,isToday=i===todayIdx;
|
||
var bg=d?m.color:(isWeekend?'rgba(0,0,0,.04)':'var(--bg)');
|
||
var border=d?'transparent':(isWeekend?'rgba(0,0,0,.05)':'rgba(0,0,0,.1)');
|
||
var outline=isToday?'outline:2px solid var(--accent);outline-offset:1px;':'';
|
||
return '<div style="width:16px;height:20px;border-radius:3px;background:'+bg+';border:1px solid '+border+';'+outline+'flex-shrink:0"></div>';
|
||
}).join('');
|
||
return '<div style="display:grid;grid-template-columns:70px 1fr auto;gap:4px;align-items:center;padding:3px 0">'
|
||
+'<div style="display:flex;align-items:center;gap:4px">'
|
||
+'<span style="font-size:11px;font-weight:600;color:var(--ink)">'+m.name+'</span>'
|
||
+'<span style="font-size:8px;font-weight:800;color:'+catColor+';background:'+catBg+';border-radius:3px;padding:1px 3px">'+m.cat+'</span>'
|
||
+'</div>'
|
||
+'<div style="display:flex;gap:2px">'+cells+'</div>'
|
||
+'<span style="font-size:11px;font-weight:700;color:'+(free>=5?'#EF4444':free>=3?'var(--warn)':'var(--muted)')+';text-align:right">'+free+'</span>'
|
||
+'</div>';
|
||
}).join('');
|
||
return expBtn+'<div style="margin-top:4px">'+hdr+rows+'</div></div>';
|
||
})();
|
||
var body = '<div style="padding:12px 14px">'+cats+memberGrid+'</div>';
|
||
return '<div class="card" style="padding:0;overflow:hidden;margin-top:8px;margin-bottom:8px">'+header+body+'</div>';
|
||
})()}
|
||
|
||
<!-- Экспедиция — компактно + график загрузки -->
|
||
${(function(){
|
||
var col = window._expCol;
|
||
var chevron = '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" style="color:var(--muted);flex-shrink:0;transform:rotate('+(col?'0':'-180')+'deg)"><polyline points="6 9 12 15 18 9"/></svg>';
|
||
var header = '<div class="row sb" style="padding:12px 14px;cursor:pointer;border-bottom:'+(col?'none':'1px solid rgba(0,0,0,.06)')+'" onclick="(function(){window._expCol=!window._expCol;refresh()})()">'
|
||
+'<div><div style="display:flex;align-items:center;gap:8px">'
|
||
+'<span style="font-size:13px;font-weight:700;color:var(--ink)">🚚 Экспедиция · 3 ИП</span>'
|
||
+'<div style="display:flex;gap:3px;align-items:center">'
|
||
+'<div style="width:8px;height:8px;border-radius:50%;background:#10B981"></div>'
|
||
+'<div style="width:8px;height:8px;border-radius:50%;background:#3B82F6"></div>'
|
||
+'<div style="width:8px;height:8px;border-radius:50%;background:#EF4444"></div>'
|
||
+'</div></div>'
|
||
+(col?'':'<div style="font-size:12px;color:var(--muted);margin-top:3px">42 упак. · 1 проблема</div>')
|
||
+'</div>'+(col?'<span style="font-size:11px;background:rgba(239,68,68,.1);color:var(--danger);border-radius:4px;padding:2px 8px;font-weight:700;margin-right:4px">1 пробл.</span>':'')+chevron+'</div>';
|
||
|
||
if (col) return '<div class="card" style="padding:0;overflow:hidden;margin-top:8px;margin-bottom:8px">'+header+'</div>';
|
||
|
||
var p=window._expPeriod||'day';
|
||
var datasets={
|
||
day:[{d:'Пн',v:38,past:true},{d:'Вт',v:51,past:true},{d:'Ср',v:42,cur:true},{d:'Чт',v:29},{d:'Пт',v:44},{d:'Сб',v:18,w:true},{d:'Вс',v:0,w:true}],
|
||
week:[{d:'н-5',v:38,past:true},{d:'н-4',v:45,past:true},{d:'н-3',v:58,past:true},{d:'н-2',v:50,past:true},{d:'н-1',v:44,past:true},{d:'тек',v:42,cur:true},{d:'н+1',v:35},{d:'н+2',v:35}],
|
||
month:[{d:'Ноя',v:320,past:true},{d:'Дек',v:285,past:true},{d:'Янв',v:310,past:true},{d:'Фев',v:295,past:true},{d:'Мар',v:340,past:true},{d:'Апр',v:380,past:true},{d:'Май',v:210,cur:true}]
|
||
};
|
||
var items=datasets[p];
|
||
var maxV=Math.max.apply(null,items.map(function(x){return x.v;}));
|
||
var bars=items.map(function(b){
|
||
var h=b.v?Math.round(b.v/maxV*28)+6:3;
|
||
var bg=b.cur?'var(--accent)':b.past?'rgba(0,62,126,.55)':b.w?'rgba(0,0,0,.1)':'rgba(0,62,126,.2)';
|
||
var tc=b.cur?'var(--accent)':b.w?'var(--warn)':'var(--muted)';
|
||
return '<div style="flex:1;display:flex;flex-direction:column;align-items:center;gap:3px"><div style="width:100%;height:'+h+'px;background:'+bg+';border-radius:3px 3px 0 0"></div><span style="font-size:9px;color:'+tc+';font-weight:'+(b.cur?700:500)+'">'+b.d+'</span></div>';
|
||
}).join('');
|
||
var periodBtns=['day','week','month'].map(function(v,i){
|
||
var lbl=['Дни','Нед','Мес'][i]; var active=p===v;
|
||
return '<div onclick="(function(){window._expPeriod=\''+v+'\';refresh()})()" style="padding:3px 10px;border-radius:4px;font-size:10px;font-weight:'+(active?700:500)+';color:'+(active?'var(--accent)':'var(--muted)')+';background:'+(active?'var(--card)':'transparent')+';box-shadow:'+(active?'0 1px 3px rgba(0,0,0,.1)':'none')+';cursor:pointer">'+lbl+'</div>';
|
||
}).join('');
|
||
|
||
var routesHtml='';
|
||
if(window._expExp){
|
||
var fromAlert=window._expFromAlert;
|
||
var routes=[
|
||
{ip:'ИП Захаров',pkg:14,status:'blue',slbl:'В пути',time:'~13:00',priority:fromAlert},
|
||
{ip:'ИП Морозов',pkg:10,status:'red',slbl:'Проблема',time:'',priority:false},
|
||
{ip:'ИП Фёдоров',pkg:18,status:'green',slbl:'Доставлено',time:'11:20',priority:false},
|
||
];
|
||
var rHeader=fromAlert?'<div style="font-size:10px;font-weight:700;color:var(--danger);background:rgba(239,68,68,.07);border-radius:6px;padding:6px 10px;margin-bottom:8px">⚠ НУЖЕН ДОПОЛНИТЕЛЬНЫЙ ВЫВОЗ — н+2 под угрозой</div>':'';
|
||
routesHtml='<div style="margin-top:10px;padding-top:10px;border-top:1px solid rgba(0,0,0,.06)">'+rHeader+routes.map(function(e){
|
||
var extra=e.priority?'<span style="font-size:9px;color:var(--accent);font-weight:700;background:rgba(99,102,241,.1);border-radius:3px;padding:1px 5px;margin-left:4px">+вывоз</span>':'';
|
||
return '<div style="display:flex;align-items:center;justify-content:space-between;padding:7px 0;border-bottom:1px solid rgba(0,0,0,.04)">'
|
||
+'<div><span style="font-size:12px;font-weight:700;color:var(--ink)">'+e.ip+'</span>'+extra+'<span style="font-size:11px;color:var(--muted);margin-left:6px">'+e.pkg+' упак.</span></div>'
|
||
+'<div style="display:flex;align-items:center;gap:6px"><span class="badge '+e.status+'" style="font-size:10px">'+e.slbl+'</span>'+(e.time?'<span style="font-size:11px;color:var(--muted)">'+e.time+'</span>':'')+'</div></div>';
|
||
}).join('')+'</div>';
|
||
}
|
||
|
||
var body='<div style="padding:12px 14px">'
|
||
+'<div class="row sb" style="cursor:pointer;margin-bottom:8px" onclick="(function(){window._expExp=!window._expExp;refresh()})()">'
|
||
+'<span style="font-size:13px;font-weight:800;color:var(--accent)">42 упак.</span>'
|
||
+'<span style="font-size:12px;color:var(--accent);font-weight:600">'+(window._expExp?'▲':'▼ Маршруты')+'</span>'
|
||
+'</div>'
|
||
+'<div style="display:flex;align-items:flex-end;gap:5px;height:44px">'+bars+'</div>'
|
||
+'<div style="display:flex;justify-content:flex-end;margin-top:6px"><div style="display:flex;background:rgba(0,0,0,.06);border-radius:6px;padding:2px;gap:1px">'+periodBtns+'</div></div>'
|
||
+routesHtml
|
||
+'</div>';
|
||
|
||
return '<div class="card" style="padding:0;overflow:hidden;margin-top:8px;margin-bottom:8px">'+header+body+'</div>';
|
||
})()}
|
||
|
||
|
||
<!-- ② Технологи — радар, не дашборд -->
|
||
${(function(){
|
||
var col=window._techCol;
|
||
var p=window._techPeriod||'day';
|
||
var totals={day:7,week:51,month:216};
|
||
var chevron='<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" style="color:var(--muted);flex-shrink:0;transform:rotate('+(col?'0':'-180')+'deg)"><polyline points="6 9 12 15 18 9"/></svg>';
|
||
var hdr='<div class="row sb" style="padding:12px 16px;cursor:pointer;border-bottom:'+(col?'none':'1px solid rgba(0,0,0,.06)')+'" onclick="(function(){window._techCol=!window._techCol;refresh()})()">'
|
||
+'<div style="display:flex;align-items:center;gap:8px">'
|
||
+'<span style="font-size:13px;font-weight:700;color:var(--ink)">📋 Технологи</span>'
|
||
+(col?'':'<span style="font-size:11px;color:var(--muted)">'+totals[p]+' согл. · ср. 2.4ч.</span>')
|
||
+(col?'<span style="font-size:11px;background:rgba(245,158,11,.12);color:#D97706;border-radius:4px;padding:2px 8px;font-weight:700">⚠ 2/3 ОК</span>':'')+'</div>'+chevron+'</div>';
|
||
if(col) return '<div class="card" style="padding:0;overflow:hidden;margin-top:8px">'+hdr+'<div style="height:3px;border-radius:2px;background:linear-gradient(to right,#10B981,#FBBF24,#EF4444);clip-path:inset(0 33% 0 0);opacity:.8"></div>'+'</div>';
|
||
|
||
var techs=[
|
||
{name:'Петрова А.', load:6, cap:8, agreed:{day:4,week:22,month:87}, avgH:1.4, ok:true},
|
||
{name:'Иванов М.', load:5, cap:8, agreed:{day:3,week:18,month:74}, avgH:2.1, ok:true},
|
||
{name:'Сидоров К.', load:3, cap:6, agreed:{day:0,week:11,month:55}, avgH:3.8, ok:false},
|
||
];
|
||
var techRows=techs.map(function(t,i){
|
||
var agreed=t.agreed[p];
|
||
var loadPct=Math.round(t.load/t.cap*100);
|
||
var bc=t.ok?'var(--success)':'var(--warn)';
|
||
var isLast=i===techs.length-1;
|
||
return '<div style="display:flex;align-items:center;gap:10px;padding:7px 0;'+(isLast?'':'border-bottom:1px solid rgba(0,0,0,.05)')+'">'
|
||
+'<div style="flex:1;min-width:0"><div style="display:flex;align-items:center;gap:6px;margin-bottom:4px">'
|
||
+'<span style="font-size:12px;font-weight:700;color:var(--ink)">'+t.name+'</span>'
|
||
+'<span style="font-size:10px;color:var(--muted)">'+t.load+'/'+t.cap+' в работе</span></div>'
|
||
+'<div style="height:3px;background:rgba(0,0,0,.07);border-radius:2px;overflow:hidden"><div style="height:3px;background:'+bc+';width:'+loadPct+'%;border-radius:2px"></div></div></div>'
|
||
+'<div style="text-align:right;flex-shrink:0"><div style="font-size:13px;font-weight:800;color:'+bc+'">'+t.avgH+' ч.</div>'
|
||
+'<div style="font-size:10px;color:var(--muted);margin-top:1px">'+agreed+' согл.</div></div></div>';
|
||
}).join('');
|
||
var periodBtns=['day','week','month'].map(function(v,i){
|
||
var lbl=['День','Нед','Мес'][i]; var active=p===v;
|
||
return '<div onclick="(function(){window._techPeriod=\''+v+'\';refresh()})()" style="padding:3px 10px;border-radius:4px;font-size:10px;font-weight:'+(active?700:500)+';color:'+(active?'var(--accent)':'var(--muted)')+';background:'+(active?'var(--card)':'transparent')+';box-shadow:'+(active?'0 1px 3px rgba(0,0,0,.1)':'none')+';cursor:pointer">'+lbl+'</div>';
|
||
}).join('');
|
||
var body='<div style="padding:12px 16px">'
|
||
+'<div style="background:rgba(245,158,11,.06);border:1px solid rgba(245,158,11,.18);border-radius:8px;padding:9px 12px;margin-bottom:12px;display:flex;align-items:center;justify-content:space-between">'
|
||
+'<div><div style="font-size:12px;font-weight:700;color:var(--warn)">⚠ Сидоров К. — 3.8 ч.</div>'
|
||
+'<div style="font-size:10px;color:var(--muted);margin-top:2px">в 2.2× медленнее команды · 3 заказа в работе</div></div>'
|
||
+'<span style="font-size:11px;color:var(--accent);font-weight:700;cursor:pointer;white-space:nowrap">Детали →</span></div>'
|
||
+techRows
|
||
+'<div style="display:flex;align-items:center;justify-content:space-between;margin-top:10px;padding-top:8px;border-top:1px solid rgba(0,0,0,.05)">'
|
||
+'<span style="font-size:10px;color:var(--muted)">Период:</span>'
|
||
+'<div style="display:flex;background:rgba(0,0,0,.06);border-radius:6px;padding:2px;gap:1px">'+periodBtns+'</div></div>'
|
||
+'</div>';
|
||
return '<div class="card" style="padding:0;overflow:hidden;margin-top:8px">'+hdr+body+'</div>';
|
||
})()}
|
||
|
||
<button class="btn-secondary" style="margin-top:8px" onclick="navigate('analytics')">📊 Открыть аналитику</button>
|
||
</div>
|
||
${navBar('home')}
|
||
</div>`;
|
||
}
|
||
|
||
// ── SCREEN 2: STAFF ──────────────────────────────────────────
|
||
function screenStaff() {
|
||
const employees = [
|
||
{init:'КА',name:'Кириллов А.В.',role:'Сборщик',badge:'blue',active:true,sub:'Сборок в мае: 18 · Рейтинг: 4.8 ⭐'},
|
||
{init:'ПС',name:'Петров С.И.',role:'Сборщик',badge:'blue',active:true,sub:'Сборок в мае: 14 · Рейтинг: 4.9 ⭐'},
|
||
{init:'СО',name:'Смирнов О.К.',role:'Сборщик',badge:'blue',active:true,sub:'Сборок в мае: 11 · Рейтинг: 4.7 ⭐'},
|
||
{init:'ФМ',name:'Фёдорова М.Р.',role:'Замерщик',badge:'purple',active:true,sub:'Замеров: 12 · Рейтинг: 4.8 ⭐'},
|
||
{init:'НП',name:'Николаев П.В.',role:'Сборщик',badge:'yellow',active:false,sub:'Сборок в мае: 3 · Рейтинг: 4.2 ⭐'},
|
||
{init:'КД',name:'Кузнецова А.Д.',role:'Менеджер',badge:'green',active:true,sub:'Клиентов: 8 · Рейтинг: 4.9 ⭐'},
|
||
];
|
||
return `<div class="page">
|
||
<div class="page-header">
|
||
<h2 style="display:flex;align-items:center;gap:7px">
|
||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M2 18a1 1 0 001 1h18a1 1 0 001-1v-2a1 1 0 00-1-1H3a1 1 0 00-1 1v2z"/><path d="M10 10V5a1 1 0 011-1h2a1 1 0 011 1v5"/><path d="M4 15v-3a8 8 0 018-8"/><path d="M20 15v-3a8 8 0 00-8-8"/></svg>
|
||
Сборщики
|
||
</h2>
|
||
<button class="header-action">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>
|
||
</button>
|
||
</div>
|
||
<div class="search-bar">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
|
||
<input type="text" placeholder="Поиск по имени, роли...">
|
||
</div>
|
||
<div class="chip-row" style="padding:0 16px;margin-bottom:12px">
|
||
<div class="chip active">Все</div>
|
||
<div class="chip">Сборщики</div>
|
||
<div class="chip">Замерщики</div>
|
||
<div class="chip">Менеджеры</div>
|
||
</div>
|
||
<div style="padding:0 16px">
|
||
${employees.map(e => `
|
||
<div class="card" onclick="navigate('staff_detail')" style="cursor:pointer;padding:12px 14px;margin-bottom:8px">
|
||
<div class="row sb">
|
||
<div class="row" style="gap:12px;flex:1">
|
||
<div class="avatar" style="background:var(--accent)">${e.init}</div>
|
||
<div class="col" style="flex:1">
|
||
<div class="row" style="gap:6px">
|
||
<span style="font-size:14px;font-weight:700;color:var(--ink)">${e.name}</span>
|
||
<span class="badge ${e.badge}" style="font-size:10px;padding:2px 8px">${e.role}</span>
|
||
</div>
|
||
<div style="display:flex;align-items:center;gap:6px;margin-top:4px">
|
||
<div style="width:8px;height:8px;border-radius:50%;background:${e.active ? 'var(--success)' : 'var(--warn)'};flex-shrink:0"></div>
|
||
<span style="font-size:12px;color:var(--muted)">${e.sub}</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" style="color:var(--muted);flex-shrink:0"><polyline points="9 18 15 12 9 6"/></svg>
|
||
</div>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
${navBar('staff')}
|
||
</div>`;
|
||
}
|
||
|
||
// ── SCREEN 3: STAFF DETAIL ────────────────────────────────────
|
||
function screenStaffDetail() {
|
||
return `<div class="page">
|
||
<div class="page-header">
|
||
<button class="back-btn" onclick="navigate('staff')">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="15 18 9 12 15 6"/></svg>
|
||
</button>
|
||
<h2>Кириллов А.В.</h2>
|
||
<button onclick="navigate('staff_edit')" style="margin-left:auto;background:var(--accent);color:#fff;border:none;border-radius:10px;padding:7px 14px;font-size:13px;font-weight:700;cursor:pointer">Ред.</button>
|
||
</div>
|
||
<div style="padding:16px">
|
||
|
||
<!-- Avatar + name -->
|
||
<div class="card" style="text-align:center;padding:24px 16px">
|
||
<div class="avatar lg" style="margin:0 auto 12px">КА</div>
|
||
<div style="font-size:19px;font-weight:800;color:var(--ink)">Кириллов Алексей Владимирович</div>
|
||
<div style="margin-top:8px;display:flex;align-items:center;justify-content:center;gap:8px">
|
||
<span class="badge blue">Сборщик</span>
|
||
<span style="display:inline-flex;align-items:center;gap:4px;background:#DCFCE7;color:#15803D;border-radius:20px;padding:3px 10px;font-size:11px;font-weight:700">● Активен</span>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Contact -->
|
||
<div class="card">
|
||
<div style="font-size:11px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:10px">Контакт</div>
|
||
<div class="row" style="margin-bottom:8px">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="width:18px;height:18px;color:var(--muted);flex-shrink:0"><path d="M22 16.92v3a2 2 0 01-2.18 2 19.79 19.79 0 01-8.63-3.07A19.5 19.5 0 013.07 9.64a19.79 19.79 0 01-3.07-8.63A2 2 0 012.18.5h3a2 2 0 012 1.72c.127.96.361 1.903.7 2.81a2 2 0 01-.45 2.11L6.91 8.58a16 16 0 006.29 6.29l1.45-1.45a2 2 0 012.11-.45c.907.339 1.85.573 2.81.7A2 2 0 0122 16.92z"/></svg>
|
||
<span style="font-size:14px;font-weight:600;color:var(--accent)">+7 921 456-78-90</span>
|
||
</div>
|
||
<div class="row">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="width:18px;height:18px;color:var(--muted);flex-shrink:0"><path d="M21 15a2 2 0 01-2 2H7l-4 4V5a2 2 0 012-2h14a2 2 0 012 2z"/></svg>
|
||
<span style="font-size:14px;font-weight:600;color:var(--accent)">@kirillov_asm</span>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Stats -->
|
||
<div class="card">
|
||
<div style="font-size:11px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:12px">Май 2026</div>
|
||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:10px">
|
||
${[
|
||
{val:'18',lbl:'Сборок'},
|
||
{val:'4.8 ⭐',lbl:'Рейтинг'},
|
||
{val:'28 400 ₽',lbl:'Заработано'},
|
||
{val:'3 200 ₽',lbl:'Акты доп.работ'},
|
||
].map(s => `
|
||
<div style="background:var(--bg);border-radius:12px;padding:10px 12px">
|
||
<div style="font-size:${s.val.length > 6 ? '14' : '18'}px;font-weight:800;color:var(--accent)">${s.val}</div>
|
||
<div style="font-size:11px;color:var(--muted);margin-top:3px">${s.lbl}</div>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Requisites accordion -->
|
||
<div class="card" style="padding:0;overflow:hidden">
|
||
<div class="accordion-header" onclick="toggleAccordion(0)" style="padding:14px 16px;cursor:pointer;font-weight:700;font-size:14px;color:var(--ink);display:flex;align-items:center;justify-content:space-between;border-bottom:1px solid rgba(0,0,0,.06)">
|
||
<span>📄 Реквизиты ИП</span>
|
||
<span id="acc-icon-0" style="transition:transform .2s;display:inline-block">▾</span>
|
||
</div>
|
||
<div id="acc-body-0" style="display:none;padding:12px 16px 14px">
|
||
${[
|
||
{lbl:'ИНН',val:'781234567890'},
|
||
{lbl:'ОГРНИП',val:'319784700123456'},
|
||
{lbl:'Банк',val:'Сбербанк · р/с 40802...'},
|
||
].map(r => `
|
||
<div class="row" style="margin-bottom:8px">
|
||
<span style="font-size:12px;color:var(--muted);min-width:72px">${r.lbl}</span>
|
||
<span style="font-size:13px;font-weight:600;color:var(--ink)">${r.val}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ACCESS CONTROL CARD -->
|
||
<div class="card" style="border:1.5px solid rgba(239,68,68,.2);background:rgba(239,68,68,.03)">
|
||
<div class="row sb" style="margin-bottom:10px">
|
||
<div class="row" style="gap:8px">
|
||
<span style="font-size:18px">🔐</span>
|
||
<span style="font-size:14px;font-weight:700;color:var(--ink)">Доступ к системе</span>
|
||
</div>
|
||
<span style="display:inline-flex;align-items:center;gap:4px;background:#DCFCE7;color:#15803D;border-radius:20px;padding:3px 10px;font-size:11px;font-weight:700">● Активен</span>
|
||
</div>
|
||
<div class="divider"></div>
|
||
${[
|
||
{lbl:'Активен с',val:'12.03.2025'},
|
||
{lbl:'Последний вход',val:'Сегодня, 08:14'},
|
||
{lbl:'Последнее действие',val:'Открыл сборку #А-2847'},
|
||
].map(r => `
|
||
<div class="row sb" style="margin-top:8px">
|
||
<span style="font-size:12px;color:var(--muted)">${r.lbl}</span>
|
||
<span style="font-size:12px;font-weight:600;color:var(--ink)">${r.val}</span>
|
||
</div>
|
||
`).join('')}
|
||
<div style="margin-top:14px;display:flex;gap:8px">
|
||
<button onclick="navigate('staff_block_confirm')" style="flex:1;background:rgba(245,158,11,.1);color:var(--warn);border:1.5px solid var(--warn);border-radius:10px;padding:10px;font-size:12px;font-weight:700;cursor:pointer">⏸ Приостановить</button>
|
||
<button style="flex:1;background:transparent;color:var(--danger);border:1.5px solid var(--danger);border-radius:10px;padding:10px;font-size:12px;font-weight:700;cursor:pointer">🚫 Отключить</button>
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
${navBar('staff')}
|
||
</div>`;
|
||
}
|
||
|
||
// ── SCREEN 3b: STAFF EDIT ─────────────────────────────────────
|
||
function screenStaffEdit() {
|
||
// Init state on first render
|
||
if (window._editRoleMeas === undefined) window._editRoleMeas = true;
|
||
if (window._editRoleAsm === undefined) window._editRoleAsm = true;
|
||
if (window._editCatMeas === undefined) window._editCatMeas = 'A'; // 'A' | 'B'
|
||
if (window._editCatAsm === undefined) window._editCatAsm = 'A';
|
||
if (window._editRate1 === undefined) window._editRate1 = 2500;
|
||
if (window._editRateN === undefined) window._editRateN = 1000;
|
||
|
||
var meas = window._editRoleMeas;
|
||
var asm = window._editRoleAsm;
|
||
|
||
function tog(role) {
|
||
return 'onclick="(function(){'
|
||
+ 'window._edit' + role + '=!window._edit' + role + ';'
|
||
+ 'document.getElementById(\'screen\').innerHTML=renderScreen(\'staff_edit\')})()"';
|
||
}
|
||
function catBtn(stateKey, val, label, color) {
|
||
var active = window[stateKey] === val;
|
||
return '<button onclick="(function(){window.' + stateKey + '=\'' + val + '\';'
|
||
+ 'document.getElementById(\'screen\').innerHTML=renderScreen(\'staff_edit\')})()"'
|
||
+ ' style="flex:1;padding:9px 4px;font-size:12px;font-weight:700;border-radius:10px;cursor:pointer;transition:.15s;border:1.5px solid '
|
||
+ (active ? color : 'rgba(0,0,0,.1)') + ';background:'
|
||
+ (active ? color + '18' : 'transparent') + ';color:'
|
||
+ (active ? color : 'var(--muted)') + '">'
|
||
+ (active ? '● ' : '○ ') + label + '</button>';
|
||
}
|
||
|
||
var roleSection = '<div class="card">'
|
||
+ '<div style="font-size:11px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:12px">Роль</div>'
|
||
+ '<div style="display:flex;gap:10px">'
|
||
+ '<button ' + tog('RoleMeas')
|
||
+ ' style="flex:1;padding:10px;font-size:13px;font-weight:700;border-radius:10px;cursor:pointer;transition:.15s;border:1.5px solid '
|
||
+ (meas ? 'var(--accent)' : 'rgba(0,0,0,.1)') + ';background:'
|
||
+ (meas ? 'rgba(99,102,241,.1)' : 'transparent') + ';color:'
|
||
+ (meas ? 'var(--accent)' : 'var(--muted)') + '">'
|
||
+ (meas ? '✓' : '○') + ' Замерщик</button>'
|
||
+ '<button ' + tog('RoleAsm')
|
||
+ ' style="flex:1;padding:10px;font-size:13px;font-weight:700;border-radius:10px;cursor:pointer;transition:.15s;border:1.5px solid '
|
||
+ (asm ? 'var(--accent)' : 'rgba(0,0,0,.1)') + ';background:'
|
||
+ (asm ? 'rgba(99,102,241,.1)' : 'transparent') + ';color:'
|
||
+ (asm ? 'var(--accent)' : 'var(--muted)') + '">'
|
||
+ (asm ? '✓' : '○') + ' Сборщик</button>'
|
||
+ '</div>'
|
||
+ (!meas && !asm ? '<div style="margin-top:10px;font-size:12px;color:var(--danger);text-align:center">⚠ Должна быть выбрана хотя бы одна роль</div>' : '')
|
||
+ '</div>';
|
||
|
||
// Tool kit state
|
||
if (window._editTools === undefined) window._editTools = {laser:true, metal:false, photo:true, tablet:false, level:true, blueprint:false};
|
||
var TOOLS = [
|
||
{key:'laser', icon:'📏', label:'Лазерный дальномер'},
|
||
{key:'level', icon:'📐', label:'Электронный уровень'},
|
||
{key:'metal', icon:'🔍', label:'Металлодетектор'},
|
||
{key:'photo', icon:'📷', label:'Фотофиксация (камера)'},
|
||
{key:'tablet', icon:'💻', label:'Планшет с ПО'},
|
||
{key:'blueprint',icon:'🗺️',label:'Чертёжный комплект'},
|
||
];
|
||
function toolToggle(key) {
|
||
return 'onclick="(function(){window._editTools[\'' + key + '\']=!window._editTools[\'' + key + '\'];'
|
||
+ 'document.getElementById(\'screen\').innerHTML=renderScreen(\'staff_edit\')})()"';
|
||
}
|
||
|
||
var measSection = !meas ? '' : (
|
||
'<div class="card">'
|
||
+ '<div style="font-size:11px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:6px">Категория замерщика</div>'
|
||
+ '<div style="font-size:11px;color:var(--muted);margin-bottom:10px">Определяет уровень допуска и ответственности на объекте</div>'
|
||
+ '<div style="display:flex;gap:8px;margin-bottom:10px">'
|
||
+ catBtn('_editCatMeas', 'A', 'Кат. А', '#6366F1')
|
||
+ catBtn('_editCatMeas', 'B', 'Кат. Б', '#F59E0B')
|
||
+ '</div>'
|
||
+ '<div style="font-size:11px;line-height:1.6;padding:8px 10px;border-radius:8px;background:'
|
||
+ (window._editCatMeas === 'A' ? 'rgba(99,102,241,.07);color:#4338CA' : 'rgba(245,158,11,.08);color:#92400E') + '">'
|
||
+ (window._editCatMeas === 'A'
|
||
? '● Допуск на любые объекты, включая премиальные<br>● Самостоятельная работа без куратора<br>● Полная материальная ответственность'
|
||
: '● Только стандартные заявки (без премиальных)<br>● Требуется согласование нестандартных ситуаций<br>● Ограниченная ответственность')
|
||
+ '</div>'
|
||
+ '<div class="divider" style="margin:14px 0"></div>'
|
||
+ '<div style="font-size:11px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:10px">Набор инструмента</div>'
|
||
+ TOOLS.map(function(t){
|
||
var on = window._editTools[t.key];
|
||
return '<div ' + toolToggle(t.key) + ' style="display:flex;align-items:center;gap:10px;padding:9px 0;border-bottom:1px solid rgba(0,0,0,.05);cursor:pointer">'
|
||
+ '<div style="width:22px;height:22px;border-radius:6px;border:1.5px solid '
|
||
+ (on ? 'var(--accent)' : 'rgba(0,0,0,.15)') + ';background:'
|
||
+ (on ? 'var(--accent)' : 'transparent') + ';display:flex;align-items:center;justify-content:center;flex-shrink:0">'
|
||
+ (on ? '<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="3"><polyline points="20 6 9 17 4 12"/></svg>' : '')
|
||
+ '</div>'
|
||
+ '<span style="font-size:13px">' + t.icon + '</span>'
|
||
+ '<span style="font-size:13px;color:' + (on ? 'var(--ink)' : 'var(--muted)') + ';font-weight:' + (on ? '600' : '400') + '">' + t.label + '</span>'
|
||
+ '</div>';
|
||
}).join('')
|
||
+ '<div class="divider" style="margin:14px 0"></div>'
|
||
+ '<div style="font-size:11px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:4px">Ставки за замер</div>'
|
||
+ '<div style="font-size:11px;color:var(--muted);margin-bottom:12px">Устанавливаются директором · <span onclick="navigate(\'settings\')" style="color:var(--accent);font-weight:600;text-decoration:underline;cursor:pointer">Настройки → Ставки ↗</span></div>'
|
||
+ '<div style="display:flex;align-items:center;gap:10px;margin-bottom:10px">'
|
||
+ '<span style="font-size:13px;color:var(--ink);flex:1">1-е помещение</span>'
|
||
+ '<div style="display:flex;align-items:center;gap:6px">'
|
||
+ '<span style="font-size:15px;font-weight:800;color:var(--ink)">' + window._editRate1 + '</span>'
|
||
+ '<span style="font-size:13px;color:var(--muted)">₽</span>'
|
||
+ '</div></div>'
|
||
+ '<div style="display:flex;align-items:center;gap:10px">'
|
||
+ '<span style="font-size:13px;color:var(--ink);flex:1">Каждое следующее</span>'
|
||
+ '<div style="display:flex;align-items:center;gap:6px">'
|
||
+ '<span style="font-size:15px;font-weight:800;color:var(--ink)">' + window._editRateN + '</span>'
|
||
+ '<span style="font-size:13px;color:var(--muted)">₽</span>'
|
||
+ '</div></div>'
|
||
+ '</div>'
|
||
);
|
||
|
||
var asmSection = !asm ? '' : (
|
||
'<div class="card">'
|
||
+ '<div style="font-size:11px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:6px">Категория сборщика</div>'
|
||
+ '<div style="font-size:11px;color:var(--muted);margin-bottom:10px">Определяет уровень допуска и ответственности на объекте</div>'
|
||
+ '<div style="display:flex;gap:8px;margin-bottom:10px">'
|
||
+ catBtn('_editCatAsm', 'A', 'Кат. А', '#6366F1')
|
||
+ catBtn('_editCatAsm', 'B', 'Кат. Б', '#F59E0B')
|
||
+ '</div>'
|
||
+ '<div style="font-size:11px;line-height:1.6;padding:8px 10px;border-radius:8px;background:'
|
||
+ (window._editCatAsm === 'A' ? 'rgba(99,102,241,.07);color:#4338CA' : 'rgba(245,158,11,.08);color:#92400E') + '">'
|
||
+ (window._editCatAsm === 'A'
|
||
? '● Допуск на любые объекты, включая премиальные<br>● Самостоятельная работа без куратора<br>● Полная материальная ответственность'
|
||
: '● Только стандартные заявки (без премиальных)<br>● Требуется согласование нестандартных ситуаций<br>● Ограниченная ответственность')
|
||
+ '</div>'
|
||
+ '</div>'
|
||
);
|
||
|
||
var addrSection = '<div class="card" style="background:rgba(99,102,241,.04);border:1.5px solid rgba(99,102,241,.15)">'
|
||
+ '<div style="display:flex;gap:10px;align-items:flex-start">'
|
||
+ '<span style="font-size:20px;flex-shrink:0">🏠</span>'
|
||
+ '<div>'
|
||
+ '<div style="font-size:13px;font-weight:700;color:var(--ink);margin-bottom:4px">Домашний адрес</div>'
|
||
+ '<div style="font-size:12px;color:var(--muted);line-height:1.5">Сотрудник указывает самостоятельно в своём профиле. Используется как стартовая точка маршрута.</div>'
|
||
+ '</div>'
|
||
+ '</div>'
|
||
+ '</div>';
|
||
|
||
var saveBtn = '<button onclick="navigate(\'staff_detail\')"'
|
||
+ ' style="width:100%;padding:14px;background:var(--accent);color:#fff;border:none;border-radius:12px;font-size:15px;font-weight:700;cursor:pointer;margin-bottom:12px">'
|
||
+ 'Сохранить</button>';
|
||
|
||
return '<div class="page">'
|
||
+ '<div class="page-header">'
|
||
+ '<button class="back-btn" onclick="navigate(\'staff_detail\')">'
|
||
+ '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="15 18 9 12 15 6"/></svg>'
|
||
+ '</button>'
|
||
+ '<h2>Редактирование</h2>'
|
||
+ '</div>'
|
||
+ '<div style="padding:16px">'
|
||
+ roleSection
|
||
+ measSection
|
||
+ asmSection
|
||
+ addrSection
|
||
+ saveBtn
|
||
+ '</div>'
|
||
+ navBar('staff')
|
||
+ '</div>';
|
||
}
|
||
|
||
// ── SCREEN 4: STAFF BLOCK CONFIRM ─────────────────────────────
|
||
function screenStaffBlockConfirm() {
|
||
// Роль сотрудника — в реале берётся из профиля, здесь симулируем: 'asm'|'meas'|'both'
|
||
var role = window._blockStaffRole || 'both';
|
||
var isBoth = role === 'both';
|
||
var isMeas = role === 'meas' || role === 'both';
|
||
var isAsm = role === 'asm' || role === 'both';
|
||
if (window._blockTransferTo === undefined) window._blockTransferTo = 'Петров С.И.';
|
||
if (window._blockNotifyClients === undefined) window._blockNotifyClients = true;
|
||
|
||
// Симуляция переключения роли для демо
|
||
function roleBtn(r, lbl) {
|
||
var active = role === r;
|
||
return '<button onclick="(function(){window._blockStaffRole=\'' + r + '\';'
|
||
+ 'document.getElementById(\'screen\').innerHTML=renderScreen(\'staff_block_confirm\')})()"'
|
||
+ ' style="flex:1;padding:6px 4px;font-size:11px;font-weight:700;border-radius:8px;cursor:pointer;border:1.5px solid '
|
||
+ (active ? 'var(--warn)' : 'rgba(0,0,0,.1)') + ';background:'
|
||
+ (active ? 'rgba(245,158,11,.1)' : 'transparent') + ';color:'
|
||
+ (active ? 'var(--warn)' : 'var(--muted)') + '">' + lbl + '</button>';
|
||
}
|
||
|
||
// Незавершённые заявки сотрудника (моковые)
|
||
var openOrders = [];
|
||
if (isMeas) openOrders.push(
|
||
{icon:'📏', type:'Замер', addr:'Ленина 12, кв.45', date:'24.05', client:'Иванова М.С.',
|
||
rooms:['Кухня угловая','Гостиная'], actSigned:false, chat:3},
|
||
{icon:'📏', type:'Замер', addr:'Мира 7, кв.18', date:'26.05', client:'Козлов П.Р.',
|
||
rooms:[], actSigned:false, chat:0},
|
||
);
|
||
if (isAsm) openOrders.push(
|
||
{icon:'🔧', type:'Сборка #А-2847', addr:'Космонавтов 44, кв.17', date:'22.05', client:'Краснова И.С.',
|
||
rooms:['Кухня прямая'], actSigned:true, chat:7},
|
||
{icon:'🔧', type:'Сборка #А-2851', addr:'Просвещения 18, кв.55', date:'22.05', client:'Тихонов Д.Р.',
|
||
rooms:['Гостиная','Спальня','Кабинет'], actSigned:true, chat:1},
|
||
);
|
||
|
||
// Доступные замены — той же категории А
|
||
var replacements = ['Петров С.И. · Кат. А', 'Смирнов О.К. · Кат. А', 'Никитин В.Р. · Кат. А'];
|
||
|
||
var roleDesc = role === 'asm' ? 'Сборщик' :
|
||
role === 'meas' ? 'Замерщик' : 'Замерщик + Сборщик';
|
||
|
||
var descText = role === 'asm'
|
||
? 'Немедленно потеряет доступ. Незавершённые сборки перейдут в статус «Без исполнителя».'
|
||
: role === 'meas'
|
||
? 'Немедленно потеряет доступ. Незавершённые замеры перейдут в статус «Без замерщика».'
|
||
: 'Совмещает обе роли — необходимо передать незавершённые заявки другому исполнителю аналогичной категории.';
|
||
|
||
var transferBlock = !isBoth ? '' :
|
||
'<div class="card" style="border:1.5px solid rgba(245,158,11,.3);background:rgba(245,158,11,.04)">'
|
||
+ '<div style="font-size:11px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:10px">Незавершённые заявки · ' + openOrders.length + '</div>'
|
||
+ openOrders.map(function(o){
|
||
var roomTags = o.rooms.length
|
||
? o.rooms.map(function(r){ return '<span style="font-size:10px;background:rgba(99,102,241,.09);color:var(--accent);border-radius:5px;padding:2px 7px;font-weight:600">' + r + '</span>'; }).join('')
|
||
: '<span style="font-size:10px;color:var(--muted);font-style:italic">Помещения не зафиксированы</span>';
|
||
var chatBadge = o.chat > 0
|
||
? '<span style="display:inline-flex;align-items:center;gap:3px;background:rgba(16,185,129,.1);color:#065F46;border-radius:5px;padding:2px 7px;font-size:10px;font-weight:700">'
|
||
+ '💬 ' + o.chat + ' сообщ.</span>'
|
||
: '';
|
||
var actBadge = o.actSigned
|
||
? '<span style="font-size:10px;background:#DCFCE7;color:#15803D;border-radius:5px;padding:2px 7px;font-weight:700">📄 Акт подписан</span>'
|
||
: '';
|
||
return '<div style="padding:10px 0;border-bottom:1px solid rgba(0,0,0,.06)">'
|
||
+ '<div style="display:flex;align-items:flex-start;gap:8px;margin-bottom:6px">'
|
||
+ '<span style="font-size:16px;flex-shrink:0;margin-top:1px">' + o.icon + '</span>'
|
||
+ '<div style="flex:1">'
|
||
+ '<div style="font-size:13px;font-weight:700;color:var(--ink)">' + o.type + '</div>'
|
||
+ '<div style="font-size:11px;color:var(--muted);margin-top:1px">' + o.client + ' · ' + o.addr + ' · ' + o.date + '</div>'
|
||
+ '</div>'
|
||
+ '</div>'
|
||
+ '<div style="display:flex;flex-wrap:wrap;gap:4px;margin-left:24px">'
|
||
+ roomTags
|
||
+ (actBadge ? actBadge : '')
|
||
+ (chatBadge ? chatBadge : '')
|
||
+ '</div>'
|
||
+ '</div>';
|
||
}).join('')
|
||
+ '<div style="margin-top:14px">'
|
||
+ '<div style="font-size:12px;font-weight:700;color:var(--ink);margin-bottom:8px">Передать исполнителю той же категории</div>'
|
||
+ '<select onchange="window._blockTransferTo=this.value"'
|
||
+ ' style="width:100%;padding:10px 12px;border:1.5px solid rgba(0,0,0,.12);border-radius:10px;font-size:13px;font-weight:600;background:#fff;color:var(--ink);outline:none">'
|
||
+ replacements.map(function(r){
|
||
return '<option value="' + r + '" ' + (window._blockTransferTo === r ? 'selected' : '') + '>' + r + '</option>';
|
||
}).join('')
|
||
+ '</select>'
|
||
+ '<div style="font-size:11px;color:var(--muted);margin-top:6px;line-height:1.5">Все открытые заявки будут автоматически переназначены выбранному исполнителю</div>'
|
||
+ '</div>'
|
||
+ '<div style="margin-top:14px;padding-top:14px;border-top:1px solid rgba(0,0,0,.07)">'
|
||
// ── История переговоров ──
|
||
+ (function(){
|
||
var chatTotal = openOrders.reduce(function(s,o){ return s + (o.chat||0); }, 0);
|
||
if (!chatTotal) return '';
|
||
if (window._blockTransferChat === undefined) window._blockTransferChat = true;
|
||
return '<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:10px">'
|
||
+ '<div>'
|
||
+ '<div style="font-size:13px;font-weight:700;color:var(--ink)">Перенести историю переговоров</div>'
|
||
+ '<div style="font-size:11px;color:var(--muted);margin-top:2px">' + chatTotal + ' сообщений по ' + openOrders.filter(function(o){return o.chat>0;}).length + ' заявкам</div>'
|
||
+ '</div>'
|
||
+ '<div class="toggle ' + (window._blockTransferChat ? 'on' : '') + '"'
|
||
+ ' onclick="(function(){window._blockTransferChat=!window._blockTransferChat;'
|
||
+ 'document.getElementById(\'screen\').innerHTML=renderScreen(\'staff_block_confirm\')})()">'
|
||
+ '</div>'
|
||
+ '</div>'
|
||
+ (window._blockTransferChat
|
||
? '<div style="background:rgba(99,102,241,.06);border:1px solid rgba(99,102,241,.15);border-radius:10px;padding:8px 12px;font-size:11px;color:var(--muted);line-height:1.6;margin-bottom:14px">'
|
||
+ '💬 Новый исполнитель увидит всю переписку с клиентами — контекст, договорённости, фото. Клиентам будет видно кто отвечал раньше.'
|
||
+ '</div>'
|
||
: '<div style="font-size:11px;color:var(--muted);margin-bottom:14px;line-height:1.5">История не будет передана — новый исполнитель начнёт переписку с нуля.</div>')
|
||
})()
|
||
// ── Уведомить клиентов ──
|
||
+ '<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:'
|
||
+ (window._blockNotifyClients ? '10' : '0') + 'px">'
|
||
+ '<div>'
|
||
+ '<div style="font-size:13px;font-weight:700;color:var(--ink)">Уведомить клиентов</div>'
|
||
+ '<div style="font-size:11px;color:var(--muted);margin-top:2px">Telegram · ' + openOrders.length + ' клиента</div>'
|
||
+ '</div>'
|
||
+ '<div class="toggle ' + (window._blockNotifyClients ? 'on' : '') + '"'
|
||
+ ' onclick="(function(){window._blockNotifyClients=!window._blockNotifyClients;'
|
||
+ 'document.getElementById(\'screen\').innerHTML=renderScreen(\'staff_block_confirm\')})()">'
|
||
+ '</div>'
|
||
+ '</div>'
|
||
+ (window._blockNotifyClients && role !== 'meas'
|
||
? (function(){
|
||
var newName = (window._blockTransferTo||'').split(' · ')[0];
|
||
var hasMeasOrder = isBoth && openOrders.some(function(o){ return o.icon === '📏'; });
|
||
var hasAsmOrder = openOrders.some(function(o){ return o.icon === '🔧'; });
|
||
// Одно сообщение, поля зависят от ролей
|
||
var fields = '';
|
||
if (hasMeasOrder) fields += '<b>Новый замерщик:</b> ' + newName + '\n';
|
||
if (hasAsmOrder) fields += '<b>Новый сборщик:</b> ' + newName + '\n';
|
||
var exClient = hasAsmOrder ? 'Краснова И.С.' : 'Иванова М.С.';
|
||
var exDate = hasAsmOrder ? '22 мая · 08:30' : '24 мая · 10:00';
|
||
var exRef = hasAsmOrder ? 'КЧ-2847' : 'ЗМ-1247';
|
||
return '<div style="background:#F0FDF4;border:1px solid rgba(34,197,94,.2);border-radius:10px;padding:10px 12px">'
|
||
+ '<div style="font-size:10px;font-weight:700;color:#15803D;text-transform:uppercase;letter-spacing:.05em;margin-bottom:8px">Превью сообщения</div>'
|
||
+ '<div style="font-size:12px;color:#166534;line-height:1.8;white-space:pre-line">'
|
||
+ '<b>' + exRef + '</b>\n'
|
||
+ 'Здравствуйте, ' + exClient + '!\n\n'
|
||
+ 'Ваш исполнитель изменён. Договорённости и дата сохраняются.\n\n'
|
||
+ fields
|
||
+ '<b>Дата:</b> ' + exDate + '\n\n'
|
||
+ 'Вопросы — напишите в этот чат, диспетчер ответит.'
|
||
+ '</div>'
|
||
+ '</div>';
|
||
})()
|
||
: (role === 'meas' && window._blockNotifyClients
|
||
? '<div style="background:rgba(0,0,0,.04);border-radius:10px;padding:10px 12px;font-size:12px;color:var(--muted)">Замерщик — уведомление клиентам не отправляется: замер разовый, оплата получена.</div>'
|
||
: '')
|
||
)
|
||
+ '</div>'
|
||
+ '</div>';
|
||
|
||
return `<div class="page">
|
||
<div class="page-header">
|
||
<button class="back-btn" onclick="navigate('staff_detail')">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="15 18 9 12 15 6"/></svg>
|
||
</button>
|
||
<h2>Отключить сотрудника</h2>
|
||
</div>
|
||
<div style="padding:16px 16px 90px">
|
||
|
||
<!-- Warning -->
|
||
<div style="text-align:center;margin-bottom:20px">
|
||
<div style="width:64px;height:64px;border-radius:50%;background:rgba(239,68,68,.1);display:flex;align-items:center;justify-content:center;margin:0 auto 12px;font-size:32px">🚫</div>
|
||
<div style="font-size:18px;font-weight:800;color:var(--ink)">Кириллов А.В.</div>
|
||
<div style="margin-top:6px;display:inline-flex;align-items:center;gap:6px;background:rgba(0,0,0,.05);border-radius:20px;padding:4px 12px">
|
||
<span style="font-size:12px;font-weight:700;color:var(--muted)">${roleDesc} · Кат. А</span>
|
||
</div>
|
||
<div style="font-size:13px;color:var(--muted);margin-top:10px;line-height:1.5;padding:0 12px">${descText}</div>
|
||
</div>
|
||
|
||
<!-- Демо-переключатель роли -->
|
||
<div class="card" style="background:rgba(0,0,0,.02);padding:10px">
|
||
<div style="font-size:10px;color:var(--muted);margin-bottom:8px;text-align:center">Демо: роль сотрудника</div>
|
||
<div style="display:flex;gap:6px">
|
||
${roleBtn('asm','Только сборщик')}
|
||
${roleBtn('meas','Только замерщик')}
|
||
${roleBtn('both','Замерщик + Сборщик')}
|
||
</div>
|
||
</div>
|
||
|
||
${transferBlock}
|
||
|
||
<!-- Причина -->
|
||
<div class="card">
|
||
<div style="font-size:11px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:10px">Причина отключения</div>
|
||
<textarea class="form-input" rows="3" placeholder="Укажите причину (видна только руководству)..." style="margin-bottom:12px;resize:none"></textarea>
|
||
<div class="toggle-row" style="padding:0">
|
||
<span class="toggle-label" style="font-size:13px">Уведомить сотрудника в Telegram</span>
|
||
<div class="toggle on" onclick="this.classList.toggle('on')"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<button onclick="navigate('staff_blocked')"
|
||
style="width:100%;padding:14px;background:var(--danger);color:#fff;border:none;border-radius:12px;font-size:15px;font-weight:700;cursor:pointer;margin-bottom:10px">
|
||
🚫 Отключить${isBoth ? ' и передать заявки' : ''}
|
||
</button>
|
||
<button class="btn-secondary" onclick="navigate('staff_detail')">Отмена</button>
|
||
|
||
</div>
|
||
${navBar('staff')}
|
||
</div>`;
|
||
}
|
||
|
||
// ── SCREEN 5: STAFF BLOCKED ────────────────────────────────────
|
||
function screenStaffBlocked() {
|
||
return `<div class="page" style="min-height:780px;display:flex;flex-direction:column;align-items:center;justify-content:center;padding:40px 24px;background:var(--bg)">
|
||
<div style="width:80px;height:80px;border-radius:50%;background:rgba(239,68,68,.1);display:flex;align-items:center;justify-content:center;margin-bottom:20px;font-size:40px">🔒</div>
|
||
<div style="font-size:22px;font-weight:800;color:var(--ink);text-align:center;margin-bottom:10px">Доступ приостановлен</div>
|
||
<div style="font-size:15px;color:var(--muted);text-align:center;line-height:1.6;margin-bottom:8px">Обратитесь к руководителю для восстановления доступа</div>
|
||
<div style="display:flex;align-items:center;justify-content:center;gap:8px;margin-bottom:6px">
|
||
<div class="w1-slot" data-color="#003E7E" data-width="120" data-height="19"></div>
|
||
<span style="color:#CBD5E1;font-size:14px;font-weight:300">|</span>
|
||
<span style="font-family:'Montserrat',sans-serif;font-weight:700;font-size:14px;color:#003E7E;letter-spacing:2px">CRM</span>
|
||
</div>
|
||
<div style="font-size:12px;color:var(--muted);text-align:center;margin-bottom:32px">ИП Васильев Р.Г. · +7 xxx xxx-xx-xx</div>
|
||
<button class="btn-primary" style="max-width:280px" onclick="window.open('https://t.me/wasrusgen1','_blank')">Написать руководителю</button>
|
||
<button class="btn-secondary" style="max-width:280px" onclick="navigate('staff_detail')">← Назад (демо)</button>
|
||
</div>`;
|
||
}
|
||
|
||
// ── SCREEN: PRICELIST ─────────────────────────────────────────
|
||
function screenPricelist() {
|
||
var tab = window._plTab || 'asm';
|
||
var search = window._plSearch || '';
|
||
|
||
var asm = [
|
||
{cat:'Общие работы', n:1, name:'Выезд мастера в магазин по просьбе клиента (>5 км + 40 р/км)', price:800, unit:'руб.'},
|
||
{cat:'Общие работы', n:2, name:'Доп. срочный выезд мастера в течение 24 часов', price:3000, unit:'руб.'},
|
||
{cat:'Общие работы', n:48, name:'Ложный выезд / ожидание заказчика более 45 мин.', price:1500, unit:'руб.'},
|
||
{cat:'Общие работы', n:49, name:'Вынос картонной упаковки в коридор', price:-1, unit:'Бесплатно'},
|
||
{cat:'Общие работы', n:50, name:'Вынос упаковки в мусорный контейнер (лифт + контейнер в 100 м)', price:-1, unit:'Бесплатно'},
|
||
{cat:'Общие работы', n:51, name:'Выезд за КАД', price:40, unit:'р./км'},
|
||
{cat:'Подсветка', n:3, name:'Подключение LED-ленты по наружной части мебели', price:600, unit:'р./п.м.'},
|
||
{cat:'Подсветка', n:4, name:'Подключение LED-ленты внутри мебели', price:400, unit:'р./линию'},
|
||
{cat:'Подсветка', n:5, name:'Монтаж декоративных планок на стену', price:370, unit:'р./п.м.'},
|
||
{cat:'Подсветка', n:'-', name:'Фрезеровка канала для врезной подсветки', price:1000, unit:'р./п.м.'},
|
||
{cat:'Зона мойки', n:6, name:'Технологический выпил (1 выпил / 1 деталь)', price:100, unit:'руб.'},
|
||
{cat:'Зона мойки', n:7, name:'Выпил в столешнице / стенке ЛДСП под трубы', price:300, unit:'руб.'},
|
||
{cat:'Зона мойки', n:8, name:'Демонтаж обесточенной розетки', price:50, unit:'руб.'},
|
||
{cat:'Зона мойки', n:12, name:'Переделка модуля по месту (в зав. от сложности)', price:1500, unit:'руб. от'},
|
||
{cat:'Зона мойки', n:17, name:'Изготовление отверстия в керамограните (сверло заказчика)', price:300, unit:'руб.'},
|
||
{cat:'Зона мойки', n:18, name:'Демонтаж старой мойки', price:500, unit:'руб.'},
|
||
{cat:'Зона мойки', n:19, name:'Врезка накладной мойки покупателя с обработкой выпила (без подкл.)', price:800, unit:'руб.'},
|
||
{cat:'Зона мойки', n:20, name:'Установка мойки покупателя (без подключения)', price:500, unit:'руб.'},
|
||
{cat:'Зона мойки', n:21, name:'Вырез отверстия под смеситель (металл)', price:300, unit:'руб.'},
|
||
{cat:'Зона мойки', n:22, name:'Вырез отверстия под смеситель (иск. камень)', price:500, unit:'руб.'},
|
||
{cat:'Зона мойки', n:36, name:'Установка встраиваемой посудомоечной машины (без подкл.)', price:2000, unit:'руб.'},
|
||
{cat:'Зона мойки', n:37, name:'Установка встраиваемой стиральной машины (без подкл.)', price:2000, unit:'руб.'},
|
||
{cat:'Зона мойки', n:38, name:'Установка НЕ встраиваемой стиральной машины (без подкл.)', price:400, unit:'руб.'},
|
||
{cat:'Зона мойки', n:40, name:'Отверстие в компакт-плите (под варочную / мойку / розетку)', price:2000, unit:'руб.'},
|
||
{cat:'Зона мойки', n:41, name:'Установка мойки подстольного монтажа (без подкл.)', price:3000, unit:'руб.'},
|
||
{cat:'Холодильник', n:8, name:'Демонтаж обесточенной розетки', price:50, unit:'руб.'},
|
||
{cat:'Холодильник', n:39, name:'Установка холодильника покупателя без перенавески дверей (без подкл.)',price:2500, unit:'руб.'},
|
||
{cat:'Холодильник', n:43, name:'Перенавеска дверей холодильника без электроники', price:500, unit:'руб.'},
|
||
{cat:'Холодильник', n:44, name:'Перенавеска дверей холодильника с электроникой', price:800, unit:'руб.'},
|
||
{cat:'Варочная поверхность', n:6, name:'Технологический выпил (1 выпил / 1 деталь)', price:300, unit:'руб.'},
|
||
{cat:'Варочная поверхность', n:12, name:'Переделка модуля по месту', price:1000, unit:'руб. от'},
|
||
{cat:'Варочная поверхность', n:23, name:'Врезка варочной поверхности покупателя с обработкой выпила (без подкл.)', price:800, unit:'руб.'},
|
||
{cat:'Варочная поверхность', n:24, name:'Установка варочной панели покупателя (без подключения)', price:500, unit:'руб.'},
|
||
{cat:'Варочная поверхность', n:'-', name:'Вырез в столешнице под шахту / выступ', price:500, unit:'руб.'},
|
||
{cat:'Зона вытяжки', n:6, name:'Технологический выпил (1 выпил / 1 деталь)', price:100, unit:'руб.'},
|
||
{cat:'Зона вытяжки', n:9, name:'Вырез под розетку', price:200, unit:'руб.'},
|
||
{cat:'Зона вытяжки', n:25, name:'Установка купольной вытяжки 60 см покупателя (без подкл.)', price:1500, unit:'руб.'},
|
||
{cat:'Зона вытяжки', n:26, name:'Установка купольной вытяжки 90 см покупателя (без подкл.)', price:2000, unit:'руб.'},
|
||
{cat:'Зона вытяжки', n:27, name:'Установка плоской вытяжки покупателя (без подкл.)', price:300, unit:'руб.'},
|
||
{cat:'Зона вытяжки', n:28, name:'Установка встраиваемой вытяжки покупателя (без подкл.)', price:1500, unit:'руб.'},
|
||
{cat:'Зона вытяжки', n:29, name:'Установка полновстроенной вытяжки покупателя (без подкл.)', price:1500, unit:'руб.'},
|
||
{cat:'Зона вытяжки', n:30, name:'Установка островной вытяжки покупателя (без подкл.)', price:3000, unit:'руб.'},
|
||
{cat:'Зона вытяжки', n:31, name:'Подключение гофрированного воздуховода к вытяжке', price:400, unit:'р./шт.'},
|
||
{cat:'Зона вытяжки', n:32, name:'Подключение пластикового воздуховода с монтажом', price:800, unit:'р./п.м.'},
|
||
{cat:'Зона вытяжки', n:33, name:'Установка фланца', price:300, unit:'руб.'},
|
||
{cat:'Духовка / СВЧ', n:9, name:'Вырез под розетку', price:200, unit:'руб.'},
|
||
{cat:'Духовка / СВЧ', n:12, name:'Переделка модуля по месту', price:700, unit:'руб. от'},
|
||
{cat:'Духовка / СВЧ', n:34, name:'Установка духового шкафа покупателя в модуль (без подкл.)', price:600, unit:'руб.'},
|
||
{cat:'Духовка / СВЧ', n:35, name:'Установка встраиваемой СВЧ-печи покупателя в модуль (без подкл.)', price:1000, unit:'руб.'},
|
||
{cat:'Стеновая панель', n:9, name:'Вырез под розетку в стеновой панели', price:200, unit:'руб.'},
|
||
{cat:'Стеновая панель', n:10, name:'Вырез под розетку в стеновой панели (компакт-плита)', price:500, unit:'руб.'},
|
||
{cat:'Стеновая панель', n:11, name:'Перепил декоративного элемента за деталь', price:400, unit:'руб.'},
|
||
{cat:'Стеновая панель', n:12, name:'Переделка модуля по месту', price:700, unit:'руб. от'},
|
||
{cat:'Стеновая панель', n:13, name:'Врезка круглых светильников покупателя (без подкл.)', price:70, unit:'р./шт.'},
|
||
{cat:'Стеновая панель', n:14, name:'Установка релинга покупателя (1 п.м. или 1 шт.)', price:300, unit:'руб.'},
|
||
{cat:'Стеновая панель', n:15, name:'Продольный пил цоколя по вине покупателя', price:200, unit:'р./п.м.'},
|
||
{cat:'Стеновая панель', n:16, name:'Изготовление подиума под плиту (материалы заказчика)', price:400, unit:'руб.'},
|
||
{cat:'Стеновая панель', n:42, name:'Демонтаж / монтаж пристеночного плинтуса', price:200, unit:'р./п.м.'},
|
||
{cat:'Стеновая панель', n:45, name:'Присадка ручек покупателя', price:40, unit:'р./отв.'},
|
||
{cat:'Стеновая панель', n:46, name:'Установка ручек покупателя', price:40, unit:'р./шт.'},
|
||
{cat:'Стеновая панель', n:47, name:'Планировка розеток на место установки кухни', price:2500, unit:'руб.'},
|
||
{cat:'Шкаф', n:5, name:'Монтаж доборов для установки натяжного потолка', price:2000, unit:'р./п.м.'},
|
||
{cat:'Шкаф', n:8, name:'Демонтаж обесточенной розетки', price:50, unit:'руб.'},
|
||
{cat:'Шкаф', n:9, name:'Вырез под розетку / коммуникации', price:200, unit:'руб.'},
|
||
{cat:'Шкаф', n:11, name:'Перепил декоративного элемента за деталь', price:400, unit:'руб.'},
|
||
{cat:'Шкаф', n:12, name:'Переделка модуля по месту', price:2000, unit:'руб. от'},
|
||
{cat:'Шкаф', n:13, name:'Врезка круглых светильников (без подкл.)', price:70, unit:'р./шт.'},
|
||
{cat:'Шкаф', n:15, name:'Продольный пил цоколя по вине покупателя', price:200, unit:'р./п.м.'},
|
||
{cat:'Шкаф', n:45, name:'Присадка ручек покупателя', price:40, unit:'р./отв.'},
|
||
{cat:'Шкаф', n:46, name:'Установка ручек покупателя', price:40, unit:'р./шт.'},
|
||
];
|
||
|
||
var vg = [
|
||
{n:1, name:'Установка и подключение розетки', price:200, unit:'руб.'},
|
||
{n:2, name:'Подключение варочной поверхности', price:800, unit:'руб.'},
|
||
{n:3, name:'Подключение посудомоечной машины', price:1600, unit:'руб.'},
|
||
{n:4, name:'Подключение стиральной машины', price:1600, unit:'руб.'},
|
||
{n:5, name:'Установка сушильной машины', price:1000, unit:'руб.'},
|
||
{n:6, name:'Подключение смесителя', price:1600, unit:'руб.'},
|
||
{n:7, name:'Подключение слива мойки', price:1000, unit:'руб.'},
|
||
{n:8, name:'Установка и подключение измельчителя', price:2500, unit:'руб.'},
|
||
{n:9, name:'Установка и подключение фильтра для воды', price:1000, unit:'руб.'},
|
||
{n:10, name:'Установка дозатора', price:500, unit:'руб.'},
|
||
{n:11, name:'Установка фасадной петли', price:200, unit:'руб.'},
|
||
{n:12, name:'Вырез под вентрешетку', price:400, unit:'руб.'},
|
||
{n:13, name:'Вырез в ЛДСП с кромлением', price:1000, unit:'руб.'},
|
||
{n:14, name:'Переделка сантехнической подводки / водоподготовка', price:1000, unit:'руб.'},
|
||
{n:15, name:'Установка столешницы с заходом в подоконник', price:3000, unit:'руб. от'},
|
||
{n:16, name:'Монтаж и вырезы в панели в зоне инсталляции (силикон)', price:3500, unit:'руб.'},
|
||
{n:17, name:'Вырез под коммуникации (трубы)', price:200, unit:'руб. от'},
|
||
{n:18, name:'Распаковка / установка / подключение отдельностоящего холодильника', price:500, unit:'руб.'},
|
||
{n:19, name:'Монтаж вешалки заказчика', price:200, unit:'руб.'},
|
||
{n:20, name:'Монтаж брючницы на направляющей заказчика', price:700, unit:'руб.'},
|
||
{n:21, name:'Присадка и монтаж полки скрытого монтажа', price:700, unit:'руб.'},
|
||
];
|
||
|
||
function applySearch(items, catField) {
|
||
if (!search) return items;
|
||
var q = search.toLowerCase();
|
||
return items.filter(function(i) {
|
||
return i.name.toLowerCase().indexOf(q) > -1 || (catField && i[catField] && i[catField].toLowerCase().indexOf(q) > -1);
|
||
});
|
||
}
|
||
|
||
function renderCats(items) {
|
||
var cats = [], catMap = {};
|
||
items.forEach(function(i) {
|
||
if (!catMap[i.cat]) { catMap[i.cat] = []; cats.push(i.cat); }
|
||
catMap[i.cat].push(i);
|
||
});
|
||
if (cats.length === 0) return '<div style="padding:30px;text-align:center;color:var(--muted);font-size:13px">Ничего не найдено</div>';
|
||
return cats.map(function(cat) {
|
||
var rows = catMap[cat].map(function(item, idx) {
|
||
var isLast = idx === catMap[cat].length - 1;
|
||
var priceStr = item.price === -1 ? item.unit : (item.price.toLocaleString('ru') + ' ' + item.unit);
|
||
var priceColor = item.price === -1 ? '#10b981' : 'var(--accent)';
|
||
return '<div style="display:flex;align-items:center;gap:8px;padding:8px 0;' + (isLast?'':'border-bottom:1px solid rgba(0,0,0,.04)') + '">'
|
||
+ '<span style="font-size:10px;color:var(--muted);width:18px;flex-shrink:0;text-align:right">' + item.n + '</span>'
|
||
+ '<span style="flex:1;font-size:12px;color:var(--ink);line-height:1.35">' + item.name + '</span>'
|
||
+ '<span style="font-size:12px;font-weight:700;color:' + priceColor + ';white-space:nowrap;flex-shrink:0">' + priceStr + '</span>'
|
||
+ '<span style="font-size:15px;color:var(--muted);cursor:pointer;padding:0 2px;flex-shrink:0" onclick="alert(\'Редактировать: ' + item.name.replace(/['"]/g,'') + '\')">✎</span>'
|
||
+ '</div>';
|
||
}).join('');
|
||
return '<div style="margin-bottom:4px">'
|
||
+ '<div style="font-size:10px;font-weight:700;color:var(--muted);letter-spacing:.6px;padding:10px 0 4px;border-bottom:2px solid rgba(0,0,0,.06);text-transform:uppercase">' + cat + '</div>'
|
||
+ rows + '</div>';
|
||
}).join('');
|
||
}
|
||
|
||
function renderVg(items) {
|
||
if (items.length === 0) return '<div style="padding:30px;text-align:center;color:var(--muted);font-size:13px">Ничего не найдено</div>';
|
||
return items.map(function(item, idx) {
|
||
var isLast = idx === items.length - 1;
|
||
var priceStr = item.price.toLocaleString('ru') + ' ' + item.unit;
|
||
return '<div style="display:flex;align-items:center;gap:8px;padding:9px 0;' + (isLast?'':'border-bottom:1px solid rgba(99,102,241,.08)') + '">'
|
||
+ '<span style="font-size:10px;color:rgba(99,102,241,.5);width:18px;flex-shrink:0;text-align:right">' + item.n + '</span>'
|
||
+ '<span style="flex:1;font-size:12px;color:var(--ink);line-height:1.35">' + item.name + '</span>'
|
||
+ '<span style="font-size:12px;font-weight:700;color:#4338ca;white-space:nowrap;flex-shrink:0">' + priceStr + '</span>'
|
||
+ '<span style="font-size:15px;color:var(--muted);cursor:pointer;padding:0 2px;flex-shrink:0" onclick="alert(\'Редактировать: ' + item.name.replace(/['"]/g,'') + '\')">✎</span>'
|
||
+ '</div>';
|
||
}).join('');
|
||
}
|
||
|
||
var filteredAsm = applySearch(asm, 'cat');
|
||
var filteredVg = applySearch(vg, null);
|
||
var cntAsm = asm.length, cntVg = vg.length;
|
||
|
||
return `<div class="page">
|
||
<div style="background:var(--accent);padding:18px 16px 14px">
|
||
<div style="font-size:13px;color:rgba(255,255,255,.65);font-weight:500">Управление ценами</div>
|
||
<div style="font-size:20px;font-weight:800;color:#fff;margin-top:3px">Прейскурант работ</div>
|
||
<div style="font-size:12px;color:rgba(255,255,255,.6);margin-top:2px">Актуален с 01.01.2025</div>
|
||
</div>
|
||
|
||
<!-- Действия -->
|
||
<div style="display:flex;gap:8px;padding:10px 16px;background:var(--card);border-bottom:1px solid rgba(0,0,0,.07)">
|
||
<button onclick="navigate('pricelist_preview')" style="flex:1;display:flex;align-items:center;justify-content:center;gap:5px;background:rgba(239,68,68,.08);border:1px solid rgba(239,68,68,.2);color:#dc2626;border-radius:8px;padding:7px 8px;font-size:12px;font-weight:700;cursor:pointer">
|
||
↓ PDF
|
||
</button>
|
||
<button onclick="navigate('pricelist_preview')" style="flex:1;display:flex;align-items:center;justify-content:center;gap:5px;background:rgba(245,158,11,.08);border:1px solid rgba(245,158,11,.2);color:#d97706;border-radius:8px;padding:7px 8px;font-size:12px;font-weight:700;cursor:pointer">
|
||
↓ JPG
|
||
</button>
|
||
<button onclick="alert('Добавить позицию')" style="display:flex;align-items:center;justify-content:center;background:var(--accent);border:none;color:#fff;border-radius:8px;padding:7px 14px;font-size:18px;cursor:pointer;font-weight:300">+</button>
|
||
</div>
|
||
|
||
<!-- Поиск -->
|
||
<div style="padding:8px 16px;background:var(--card);border-bottom:1px solid rgba(0,0,0,.06)">
|
||
<div style="display:flex;align-items:center;background:rgba(0,0,0,.05);border-radius:8px;padding:7px 12px;gap:8px">
|
||
<span style="font-size:13px;color:var(--muted)">🔍</span>
|
||
<input type="text" placeholder="Поиск по названию или категории..." value="${search}"
|
||
oninput="(function(v){window._plSearch=v;refresh()})(this.value)"
|
||
style="border:none;background:transparent;font-size:13px;color:var(--ink);outline:none;flex:1;font-family:inherit">
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Табы -->
|
||
<div style="display:flex;padding:0 16px;background:var(--card);border-bottom:1px solid rgba(0,0,0,.08)">
|
||
<div onclick="(function(){window._plTab='asm';window._plSearch='';refresh()})()"
|
||
style="padding:10px 12px;font-size:13px;font-weight:${tab==='asm'?700:500};color:${tab==='asm'?'var(--accent)':'var(--muted)'};border-bottom:${tab==='asm'?'2px solid var(--accent)':'2px solid transparent'};cursor:pointer;margin-bottom:-1px">
|
||
Сборщик <span style="font-size:11px">(${cntAsm})</span>
|
||
</div>
|
||
<div onclick="(function(){window._plTab='vg';window._plSearch='';refresh()})()"
|
||
style="padding:10px 12px;font-size:13px;font-weight:${tab==='vg'?700:500};color:${tab==='vg'?'#4338ca':'var(--muted)'};border-bottom:${tab==='vg'?'2px solid #4338ca':'2px solid transparent'};cursor:pointer;margin-bottom:-1px">
|
||
Васильев Р.Г. <span style="font-size:11px">(${cntVg})</span>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Подзаголовок таба -->
|
||
<div style="padding:8px 16px 4px;background:var(--bg)">
|
||
${(function(){
|
||
if (tab === 'asm') return '<div style="font-size:11px;color:var(--muted)">Сборщик ведёт прайс самостоятельно · цены носят уведомительный характер</div>';
|
||
return '<div style="font-size:11px;color:rgba(99,102,241,.7)">ИП Васильев Р.Г. · услуги в рамках квалификации · устанавливает директор по сервису</div>';
|
||
})()}
|
||
</div>
|
||
|
||
<!-- Список -->
|
||
<div style="padding:0 16px 100px">
|
||
${(function(){
|
||
if (tab === 'vg') {
|
||
return '<div style="background:rgba(99,102,241,.04);border:1px solid rgba(99,102,241,.15);border-radius:10px;padding:0 12px;margin-top:8px">'
|
||
+ renderVg(filteredVg) + '</div>';
|
||
}
|
||
return renderCats(filteredAsm);
|
||
})()}
|
||
</div>
|
||
|
||
${navBar('pricelist')}
|
||
</div>`;
|
||
}
|
||
|
||
// ── SCREEN: PRICELIST PREVIEW ────────────────────────────────
|
||
function screenPricelistPreview() {
|
||
var tab = window._plTab || 'asm';
|
||
|
||
var cats_asm = [
|
||
{cat:'Общие работы', items:[
|
||
{n:1, name:'Выезд мастера в магазин по просьбе клиента (>5 км + 40 р/км)', price:'800', unit:'руб.'},
|
||
{n:2, name:'Доп. срочный выезд мастера в течение 24 часов', price:'3 000', unit:'руб.'},
|
||
{n:48, name:'Ложный выезд / ожидание заказчика более 45 мин.', price:'1 500', unit:'руб.'},
|
||
{n:49, name:'Вынос картонной упаковки в коридор', price:'—', unit:'Бесплатно'},
|
||
{n:50, name:'Вынос упаковки в мусорный контейнер', price:'—', unit:'Бесплатно'},
|
||
{n:51, name:'Выезд за КАД', price:'40', unit:'р./км'},
|
||
]},
|
||
{cat:'Подсветка', items:[
|
||
{n:3, name:'Подключение LED-ленты по наружной части мебели', price:'600', unit:'р./п.м.'},
|
||
{n:4, name:'Подключение LED-ленты внутри мебели', price:'400', unit:'р./линию'},
|
||
{n:5, name:'Монтаж декоративных планок на стену', price:'370', unit:'р./п.м.'},
|
||
{n:'-',name:'Фрезеровка канала для врезной подсветки', price:'1 000', unit:'р./п.м.'},
|
||
]},
|
||
{cat:'Зона мойки', items:[
|
||
{n:6, name:'Технологический выпил (1 выпил / 1 деталь)', price:'100', unit:'руб.'},
|
||
{n:7, name:'Выпил в столешнице / стенке ЛДСП под трубы', price:'300', unit:'руб.'},
|
||
{n:18, name:'Демонтаж старой мойки', price:'500', unit:'руб.'},
|
||
{n:19, name:'Врезка накладной мойки покупателя (без подкл.)', price:'800', unit:'руб.'},
|
||
{n:20, name:'Установка мойки покупателя (без подключения)', price:'500', unit:'руб.'},
|
||
{n:41, name:'Установка мойки подстольного монтажа', price:'3 000', unit:'руб.'},
|
||
{n:36, name:'Установка встраиваемой посудомоечной машины (без подкл.)', price:'2 000', unit:'руб.'},
|
||
]},
|
||
{cat:'Холодильник', items:[
|
||
{n:39, name:'Установка холодильника без перенавески дверей (без подкл.)', price:'2 500', unit:'руб.'},
|
||
{n:43, name:'Перенавеска дверей без электроники', price:'500', unit:'руб.'},
|
||
{n:44, name:'Перенавеска дверей с электроникой', price:'800', unit:'руб.'},
|
||
]},
|
||
{cat:'Варочная поверхность', items:[
|
||
{n:23, name:'Врезка варочной поверхности покупателя (без подкл.)', price:'800', unit:'руб.'},
|
||
{n:24, name:'Установка варочной панели покупателя (без подкл.)', price:'500', unit:'руб.'},
|
||
]},
|
||
{cat:'Зона вытяжки', items:[
|
||
{n:25, name:'Установка купольной вытяжки 60 см (без подкл.)', price:'1 500', unit:'руб.'},
|
||
{n:26, name:'Установка купольной вытяжки 90 см (без подкл.)', price:'2 000', unit:'руб.'},
|
||
{n:28, name:'Установка встраиваемой вытяжки (без подкл.)', price:'1 500', unit:'руб.'},
|
||
{n:30, name:'Установка островной вытяжки (без подкл.)', price:'3 000', unit:'руб.'},
|
||
{n:32, name:'Подключение пластикового воздуховода с монтажом', price:'800', unit:'р./п.м.'},
|
||
]},
|
||
{cat:'Духовка / СВЧ', items:[
|
||
{n:34, name:'Установка духового шкафа в модуль (без подкл.)', price:'600', unit:'руб.'},
|
||
{n:35, name:'Установка СВЧ-печи в модуль (без подкл.)', price:'1 000', unit:'руб.'},
|
||
]},
|
||
{cat:'Стеновая панель / Шкаф', items:[
|
||
{n:5, name:'Монтаж доборов для натяжного потолка', price:'2 000', unit:'р./п.м.'},
|
||
{n:13, name:'Врезка круглых светильников (без подкл.)', price:'70', unit:'р./шт.'},
|
||
{n:45, name:'Присадка ручек покупателя', price:'40', unit:'р./отв.'},
|
||
{n:46, name:'Установка ручек покупателя', price:'40', unit:'р./шт.'},
|
||
{n:47, name:'Планировка розеток', price:'2 500', unit:'руб.'},
|
||
]},
|
||
];
|
||
|
||
var items_vg = [
|
||
{n:1, name:'Установка и подключение розетки', price:'200', unit:'руб.'},
|
||
{n:2, name:'Подключение варочной поверхности', price:'800', unit:'руб.'},
|
||
{n:3, name:'Подключение посудомоечной машины', price:'1 600', unit:'руб.'},
|
||
{n:4, name:'Подключение стиральной машины', price:'1 600', unit:'руб.'},
|
||
{n:5, name:'Установка сушильной машины', price:'1 000', unit:'руб.'},
|
||
{n:6, name:'Подключение смесителя', price:'1 600', unit:'руб.'},
|
||
{n:7, name:'Подключение слива мойки', price:'1 000', unit:'руб.'},
|
||
{n:8, name:'Установка и подключение измельчителя', price:'2 500', unit:'руб.'},
|
||
{n:9, name:'Установка и подключение фильтра для воды', price:'1 000', unit:'руб.'},
|
||
{n:10, name:'Установка дозатора', price:'500', unit:'руб.'},
|
||
{n:11, name:'Установка фасадной петли', price:'200', unit:'руб.'},
|
||
{n:12, name:'Вырез под вентрешетку', price:'400', unit:'руб.'},
|
||
{n:13, name:'Вырез в ЛДСП с кромлением', price:'1 000', unit:'руб.'},
|
||
{n:14, name:'Переделка сантехнической подводки', price:'1 000', unit:'руб.'},
|
||
{n:15, name:'Установка столешницы с заходом в подоконник', price:'3 000', unit:'руб. от'},
|
||
{n:16, name:'Монтаж и вырезы в панели в зоне инсталляции', price:'3 500', unit:'руб.'},
|
||
{n:17, name:'Вырез под коммуникации (трубы)', price:'200', unit:'руб. от'},
|
||
{n:18, name:'Распаковка / установка / подключение холодильника', price:'500', unit:'руб.'},
|
||
{n:19, name:'Монтаж вешалки заказчика', price:'200', unit:'руб.'},
|
||
{n:20, name:'Монтаж брючницы на направляющей', price:'700', unit:'руб.'},
|
||
{n:21, name:'Присадка и монтаж полки скрытого монтажа', price:'700', unit:'руб.'},
|
||
];
|
||
|
||
function docRow(n, name, price, unit, last) {
|
||
var isFree = price === '—';
|
||
return '<tr style="border-bottom:' + (last?'none':'1px solid #e8eaf0') + '">'
|
||
+ '<td style="padding:5px 6px 5px 0;font-size:10px;color:#9ca3af;width:20px;vertical-align:top">' + n + '</td>'
|
||
+ '<td style="padding:5px 6px;font-size:11px;color:#1f2937;line-height:1.4;vertical-align:top">' + name + '</td>'
|
||
+ '<td style="padding:5px 0 5px 6px;font-size:11px;font-weight:700;color:' + (isFree?'#10b981':'#1f2937') + ';white-space:nowrap;text-align:right;vertical-align:top">' + (isFree ? unit : (price + ' ' + unit)) + '</td>'
|
||
+ '</tr>';
|
||
}
|
||
|
||
var asmHtml = cats_asm.map(function(c) {
|
||
var rows = c.items.map(function(it, i) {
|
||
return docRow(it.n, it.name, it.price, it.unit, i === c.items.length-1);
|
||
}).join('');
|
||
return '<div style="margin-bottom:10px">'
|
||
+ '<div style="font-size:9px;font-weight:800;letter-spacing:.8px;text-transform:uppercase;color:#6b7280;padding:6px 0 4px;border-bottom:2px solid #003E7E;margin-bottom:2px">' + c.cat + '</div>'
|
||
+ '<table style="width:100%;border-collapse:collapse">' + rows + '</table>'
|
||
+ '</div>';
|
||
}).join('');
|
||
|
||
var vgHtml = '<table style="width:100%;border-collapse:collapse">'
|
||
+ items_vg.map(function(it, i) {
|
||
return docRow(it.n, it.name, it.price, it.unit, i === items_vg.length-1);
|
||
}).join('')
|
||
+ '</table>';
|
||
|
||
var sigSvg = '<svg width="90" height="32" viewBox="0 0 90 32" fill="none" xmlns="http://www.w3.org/2000/svg">'
|
||
+ '<path d="M5 22 C10 8, 18 26, 22 14 C26 2, 28 20, 34 16 C40 12, 38 24, 44 18 C50 12, 52 28, 58 16 C62 8, 66 20, 72 15 C76 11, 80 22, 85 18" stroke="#1e3a5f" stroke-width="1.5" stroke-linecap="round" fill="none"/>'
|
||
+ '<path d="M12 26 C16 22, 20 28, 26 24" stroke="#1e3a5f" stroke-width="1" stroke-linecap="round" fill="none"/>'
|
||
+ '</svg>';
|
||
|
||
var isAsm = tab !== 'vg';
|
||
var ownerName = isAsm ? 'Кириллов А.В.' : 'Васильев Р.Г.';
|
||
var ownerRole = isAsm ? 'Мастер-сборщик' : 'ИП Васильев Р.Г. · Директор по сервису';
|
||
var docTitle = isAsm ? 'Прейскурант дополнительных работ' : 'Прейскурант услуг';
|
||
var accentCol = isAsm ? '#003E7E' : '#4338CA';
|
||
|
||
return `<div class="page">
|
||
<!-- Топбар -->
|
||
<div style="display:flex;align-items:center;gap:10px;padding:12px 16px;background:var(--card);border-bottom:1px solid rgba(0,0,0,.08)">
|
||
<div onclick="navigate('pricelist')" style="display:flex;align-items:center;gap:6px;cursor:pointer;color:var(--accent);font-size:13px;font-weight:600">
|
||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="15 18 9 12 15 6"/></svg>
|
||
Назад
|
||
</div>
|
||
<div style="flex:1;font-size:14px;font-weight:700;color:var(--ink)">Предпросмотр документа</div>
|
||
<div style="display:flex;gap:6px">
|
||
<button onclick="alert('Скачать PDF...')" style="background:#dc2626;border:none;color:#fff;border-radius:7px;padding:6px 10px;font-size:11px;font-weight:700;cursor:pointer">PDF</button>
|
||
<button onclick="alert('Скачать JPG...')" style="background:#d97706;border:none;color:#fff;border-radius:7px;padding:6px 10px;font-size:11px;font-weight:700;cursor:pointer">JPG</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Переключатель владельца -->
|
||
<div style="display:flex;gap:8px;padding:10px 16px;background:var(--bg)">
|
||
<div onclick="(function(){window._plTab='asm';refresh()})()" style="flex:1;text-align:center;padding:7px;border-radius:8px;font-size:12px;font-weight:700;cursor:pointer;background:${isAsm?'var(--accent)':'rgba(0,0,0,.05)'};color:${isAsm?'#fff':'var(--muted)'}">Сборщик</div>
|
||
<div onclick="(function(){window._plTab='vg';refresh()})()" style="flex:1;text-align:center;padding:7px;border-radius:8px;font-size:12px;font-weight:700;cursor:pointer;background:${!isAsm?'#4338CA':'rgba(0,0,0,.05)'};color:${!isAsm?'#fff':'var(--muted)'}">Васильев Р.Г.</div>
|
||
</div>
|
||
|
||
<!-- "Бумага" документа -->
|
||
<div style="padding:12px 14px 100px">
|
||
<div style="background:#fff;border-radius:10px;box-shadow:0 2px 16px rgba(0,0,0,.12);overflow:hidden">
|
||
|
||
<!-- Шапка документа -->
|
||
<div style="background:${accentCol};padding:14px 16px 12px">
|
||
<div style="display:flex;align-items:flex-start;justify-content:space-between;gap:8px">
|
||
<div>
|
||
<div style="font-size:9px;font-weight:700;letter-spacing:1.2px;color:rgba(255,255,255,.6);text-transform:uppercase;margin-bottom:3px">НАША МЕБЕЛЬ</div>
|
||
<div style="font-size:15px;font-weight:800;color:#fff;line-height:1.2">${docTitle}</div>
|
||
<div style="font-size:10px;color:rgba(255,255,255,.65);margin-top:4px">Актуален с 01.01.2025 · г. Санкт-Петербург</div>
|
||
</div>
|
||
<div style="background:rgba(255,255,255,.15);border-radius:8px;padding:8px 10px;text-align:center;flex-shrink:0">
|
||
<div style="font-size:18px;font-weight:900;color:#fff;line-height:1">${isAsm?'70':'21'}</div>
|
||
<div style="font-size:8px;color:rgba(255,255,255,.7);font-weight:600;margin-top:1px">позиций</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Тело документа -->
|
||
<div style="padding:14px 14px 12px">
|
||
${isAsm ? asmHtml : (
|
||
'<div style="font-size:9px;font-weight:800;letter-spacing:.8px;text-transform:uppercase;color:#6b7280;padding:4px 0 4px;border-bottom:2px solid #4338CA;margin-bottom:6px">Перечень услуг</div>'
|
||
+ vgHtml
|
||
)}
|
||
</div>
|
||
|
||
<!-- Дисклеймер -->
|
||
<div style="margin:0 14px;padding:8px 10px;background:#f9fafb;border-radius:6px;border-left:3px solid ${accentCol}">
|
||
<div style="font-size:9px;color:#6b7280;line-height:1.5">
|
||
Цены носят уведомительный характер. Дополнительные работы, не входящие в стандартную стоимость сборки, выполняются по согласованию с заказчиком. Подключение техники к электросети осуществляется специалистом ИП Васильев Р.Г.
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Подпись -->
|
||
<div style="padding:14px 14px 16px">
|
||
<div style="display:flex;align-items:flex-end;justify-content:space-between;gap:12px">
|
||
<div style="flex:1">
|
||
<div style="font-size:9px;color:#9ca3af;margin-bottom:4px;font-weight:600;text-transform:uppercase;letter-spacing:.5px">Ответственный</div>
|
||
<div style="font-size:11px;font-weight:700;color:#1f2937">${ownerName}</div>
|
||
<div style="font-size:10px;color:#6b7280;margin-top:1px">${ownerRole}</div>
|
||
<div style="border-top:1px solid #d1d5db;margin-top:10px;padding-top:4px;font-size:9px;color:#9ca3af">подпись</div>
|
||
</div>
|
||
<div style="text-align:center">
|
||
<div style="font-size:9px;color:#9ca3af;margin-bottom:2px;font-weight:600;text-transform:uppercase;letter-spacing:.5px">Факсимиле</div>
|
||
<div style="border:1px solid #e5e7eb;border-radius:6px;padding:4px 8px;background:#f9fafb">
|
||
${sigSvg}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div style="margin-top:12px;text-align:center;font-size:9px;color:#d1d5db">
|
||
Документ сформирован CRM ·${new Date().toLocaleDateString('ru-RU')}
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
|
||
${navBar('pricelist')}
|
||
</div>`;
|
||
}
|
||
|
||
// ── SCREEN: ASSEMBLY DETAIL ───────────────────────────────────
|
||
function screenAssemblyDetail() {
|
||
var id = window._asmId || '#А-2847';
|
||
var isProb = id === '#А-2847';
|
||
var tab = window._asmTab || 'info';
|
||
|
||
var data = {
|
||
'#А-2847': {
|
||
num:'#А-2847', status:'yellow', slbl:'Проблема',
|
||
client:'Краснова Ирина Сергеевна', phone:'+7 921 234-56-78',
|
||
addr:'ул. Космонавтов, 44, кв. 17', date:'22.05.2026',
|
||
asm:'Кириллов А.В.', asmPhone:'+7 921 456-78-90',
|
||
created:'19.05.2026', contract:'КЧ-2847',
|
||
issue:'Нет связи с 08:40. Не отвечает на звонки.',
|
||
timeline:[
|
||
{time:'08:00', ev:'Выезд на объект', done:true},
|
||
{time:'08:40', ev:'Последний сигнал GPS', done:true, warn:true},
|
||
{time:'09:15', ev:'Менеджер звонит — нет ответа', done:true, warn:true},
|
||
{time:'09:30', ev:'Директор в курсе', done:true},
|
||
{time:'—', ev:'Ожидание выхода на связь', done:false},
|
||
],
|
||
acts:[],
|
||
cost: 28400,
|
||
docs:[
|
||
{type:'meas', title:'Акт замера', measurer:'Романова Е.А.', date:'19.05.2026 11:47', rooms:['Кухня угловая'], amount:2500, signed:true, pay:'cash'},
|
||
],
|
||
},
|
||
'#А-2851': {
|
||
num:'#А-2851', status:'blue', slbl:'В работе',
|
||
client:'Тихонов Дмитрий Романович', phone:'+7 911 345-67-89',
|
||
addr:'пр. Просвещения, 18, кв. 55', date:'22.05.2026',
|
||
asm:'Петров С.И.', asmPhone:'+7 921 567-89-01',
|
||
created:'20.05.2026', contract:'КТ-2851',
|
||
issue:null,
|
||
timeline:[
|
||
{time:'09:00', ev:'Выезд на объект', done:true},
|
||
{time:'10:30', ev:'Прибыл, начал сборку', done:true},
|
||
{time:'~15:00',ev:'Ожидаемое завершение', done:false},
|
||
],
|
||
acts:[{n:36,name:'Установка посудомоечной машины',price:2000},{n:25,name:'Вытяжка купольная 60 см',price:1500}],
|
||
cost: 32400,
|
||
docs:[
|
||
{type:'meas', title:'Акт замера', measurer:'Кириллов А.В.', date:'20.05.2026 09:15', rooms:['Кухня прямая','Гостиная'], amount:3500, signed:true, pay:'cash'},
|
||
],
|
||
},
|
||
'#А-2855': {
|
||
num:'#А-2855', status:'green', slbl:'Завершена',
|
||
client:'Орлова Татьяна Викторовна', phone:'+7 921 678-90-12',
|
||
addr:'Московский пр., 101, кв. 3', date:'22.05.2026',
|
||
asm:'Смирнов О.К.', asmPhone:'+7 921 789-01-23',
|
||
created:'21.05.2026', contract:'КО-2855',
|
||
issue:null,
|
||
timeline:[
|
||
{time:'08:30', ev:'Выезд на объект', done:true},
|
||
{time:'09:50', ev:'Прибыл, начал сборку', done:true},
|
||
{time:'13:20', ev:'Завершил, акт подписан', done:true},
|
||
],
|
||
acts:[{n:19,name:'Врезка накладной мойки',price:800},{n:34,name:'Духовой шкаф в модуль',price:600}],
|
||
cost: 18200,
|
||
docs:[
|
||
{type:'meas', title:'Акт замера', measurer:'Романова Е.А.', date:'21.05.2026 14:32', rooms:['Кухня угловая'], amount:2500, signed:true, pay:'cash'},
|
||
{type:'asm', title:'Акт выполненных работ', measurer:'Смирнов О.К.', date:'22.05.2026 13:20', rooms:[], amount:18200, signed:false, pay:null},
|
||
],
|
||
},
|
||
};
|
||
|
||
var d = data[id] || data['#А-2847'];
|
||
|
||
return `<div class="page">
|
||
<div class="page-header">
|
||
<button class="back-btn" onclick="navigate('assemblies')">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="15 18 9 12 15 6"/></svg>
|
||
</button>
|
||
<h2 style="flex:1">${d.num}</h2>
|
||
<span class="badge ${d.status}" style="font-size:11px">${d.slbl}</span>
|
||
</div>
|
||
|
||
<!-- Клиент + адрес -->
|
||
<div style="background:var(--card);padding:14px 16px 10px;border-bottom:1px solid rgba(0,0,0,.06)">
|
||
<div style="font-size:16px;font-weight:800;color:var(--ink);margin-bottom:2px">${d.client}</div>
|
||
<div style="font-size:13px;color:var(--muted);margin-bottom:6px">📍 ${d.addr}</div>
|
||
<div style="display:flex;gap:8px">
|
||
<a href="tel:${d.phone.replace(/[^+\d]/g,'')}" style="flex:1;display:flex;align-items:center;justify-content:center;gap:5px;background:rgba(0,62,126,.07);border:none;border-radius:9px;padding:8px;font-size:12px;font-weight:700;color:var(--accent);text-decoration:none">
|
||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 16.92v3a2 2 0 01-2.18 2 19.79 19.79 0 01-8.63-3.07A19.5 19.5 0 013.07 9.64a19.79 19.79 0 01-3.07-8.63A2 2 0 012.18.5h3a2 2 0 012 1.72c.127.96.361 1.903.7 2.81a2 2 0 01-.45 2.11L6.91 8.58a16 16 0 006.29 6.29l1.45-1.45a2 2 0 012.11-.45c.907.339 1.85.573 2.81.7A2 2 0 0122 16.92z"/></svg>
|
||
Позвонить клиенту
|
||
</a>
|
||
<button onclick="alert('Открыть в Telegram')" style="flex:1;display:flex;align-items:center;justify-content:center;gap:5px;background:rgba(0,136,204,.08);border:none;border-radius:9px;padding:8px;font-size:12px;font-weight:700;color:#0088cc;cursor:pointer">
|
||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15a2 2 0 01-2 2H7l-4 4V5a2 2 0 012-2h14a2 2 0 012 2z"/></svg>
|
||
Написать
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
${d.issue ? `
|
||
<!-- Алерт проблемы -->
|
||
<div style="margin:10px 16px 0;background:rgba(239,68,68,.06);border:1.5px solid rgba(239,68,68,.25);border-radius:10px;padding:10px 14px;display:flex;gap:10px;align-items:flex-start">
|
||
<span style="font-size:18px;flex-shrink:0">🚨</span>
|
||
<div style="flex:1">
|
||
<div style="font-size:13px;font-weight:700;color:var(--danger)">Проблема</div>
|
||
<div style="font-size:12px;color:var(--ink);margin-top:2px;line-height:1.4">${d.issue}</div>
|
||
<div style="display:flex;gap:6px;margin-top:8px">
|
||
<a href="tel:${d.asmPhone.replace(/[^+\d]/g,'')}" style="display:flex;align-items:center;gap:4px;background:var(--danger);border-radius:7px;padding:6px 10px;font-size:11px;font-weight:700;color:#fff;text-decoration:none">
|
||
📞 Звонок сборщику
|
||
</a>
|
||
<button onclick="alert('Рекламация создана')" style="background:transparent;border:1.5px solid var(--danger);border-radius:7px;padding:6px 10px;font-size:11px;font-weight:700;color:var(--danger);cursor:pointer">+ Рекламация</button>
|
||
</div>
|
||
</div>
|
||
</div>` : ''}
|
||
|
||
<!-- Табы -->
|
||
<div style="display:flex;gap:0;padding:0 16px;background:var(--card);border-bottom:1px solid rgba(0,0,0,.07);margin-top:10px">
|
||
${[['info','Инфо'],['timeline','Хронология'],['acts','Доп.работы'],['docs','Документы']].map(([tid,tlbl]) => `
|
||
<div onclick="(function(){window._asmTab='${tid}';navigate('assembly_detail')})()"
|
||
style="padding:10px 14px;font-size:12px;font-weight:${tab===tid?700:500};color:${tab===tid?'var(--accent)':'var(--muted)'};border-bottom:${tab===tid?'2px solid var(--accent)':'2px solid transparent'};cursor:pointer;margin-bottom:-1px;white-space:nowrap">${tlbl}</div>
|
||
`).join('')}
|
||
</div>
|
||
|
||
<div style="padding:12px 16px 90px">
|
||
|
||
${tab === 'info' ? `
|
||
<!-- Сборщик -->
|
||
<div class="card" style="padding:14px;margin-bottom:10px">
|
||
<div style="font-size:10px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.5px;margin-bottom:10px">Сборщик</div>
|
||
<div style="display:flex;align-items:center;gap:10px;margin-bottom:10px">
|
||
<div class="avatar" style="background:var(--accent)">${d.asm.split(' ').map(w=>w[0]).join('').slice(0,2)}</div>
|
||
<div style="flex:1">
|
||
<div style="font-size:14px;font-weight:700;color:var(--ink)">${d.asm}</div>
|
||
<div style="display:flex;align-items:center;gap:4px;margin-top:2px">
|
||
<div style="width:7px;height:7px;border-radius:50%;background:${d.issue?'var(--danger)':'var(--success)'}"></div>
|
||
<span style="font-size:12px;color:var(--muted)">${d.issue?'Нет связи':'На объекте'}</span>
|
||
</div>
|
||
</div>
|
||
<a href="tel:${d.asmPhone.replace(/[^+\d]/g,'')}" style="display:flex;align-items:center;gap:4px;background:rgba(0,62,126,.07);border-radius:8px;padding:7px 10px;font-size:12px;font-weight:700;color:var(--accent);text-decoration:none">
|
||
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 16.92v3a2 2 0 01-2.18 2 19.79 19.79 0 01-8.63-3.07A19.5 19.5 0 013.07 9.64a19.79 19.79 0 01-3.07-8.63A2 2 0 012.18.5h3a2 2 0 012 1.72c.127.96.361 1.903.7 2.81a2 2 0 01-.45 2.11L6.91 8.58a16 16 0 006.29 6.29l1.45-1.45a2 2 0 012.11-.45c.907.339 1.85.573 2.81.7A2 2 0 0122 16.92z"/></svg>
|
||
Звонок
|
||
</a>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Детали -->
|
||
<div class="card" style="padding:14px;margin-bottom:10px">
|
||
<div style="font-size:10px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.5px;margin-bottom:10px">Детали сборки</div>
|
||
${[
|
||
{lbl:'Договор', val:d.contract},
|
||
{lbl:'Дата', val:d.date},
|
||
{lbl:'Создана', val:d.created},
|
||
{lbl:'Сумма', val:d.cost.toLocaleString('ru')+' ₽'},
|
||
].map(r => `
|
||
<div style="display:flex;align-items:center;justify-content:space-between;padding:7px 0;border-bottom:1px solid rgba(0,0,0,.04)">
|
||
<span style="font-size:12px;color:var(--muted)">${r.lbl}</span>
|
||
<span style="font-size:13px;font-weight:600;color:var(--ink)">${r.val}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
|
||
<!-- Действия директора -->
|
||
<div style="display:flex;flex-direction:column;gap:8px">
|
||
<button onclick="alert('Переназначить сборщика')" style="width:100%;background:transparent;color:var(--accent);border:1.5px solid var(--accent);border-radius:12px;padding:12px;font-size:14px;font-weight:600;cursor:pointer">
|
||
🔄 Переназначить сборщика
|
||
</button>
|
||
<button onclick="alert('Запланировать контрольный звонок клиенту')" style="width:100%;background:transparent;color:var(--muted);border:1.5px solid rgba(0,0,0,.12);border-radius:12px;padding:12px;font-size:14px;font-weight:600;cursor:pointer">
|
||
📞 Контрольный звонок клиенту
|
||
</button>
|
||
${d.status === 'green' ? `
|
||
<button onclick="alert('Отчёт по сборке')" style="width:100%;background:var(--accent);color:#fff;border:none;border-radius:12px;padding:12px;font-size:14px;font-weight:600;cursor:pointer">
|
||
📄 Скачать отчёт
|
||
</button>` : ''}
|
||
</div>
|
||
` : ''}
|
||
|
||
${tab === 'timeline' ? `
|
||
<div style="position:relative;padding-left:20px">
|
||
<!-- Вертикальная линия -->
|
||
<div style="position:absolute;left:6px;top:8px;bottom:0;width:2px;background:rgba(0,0,0,.08)"></div>
|
||
${d.timeline.map((t,i) => {
|
||
var isLast = i === d.timeline.length-1;
|
||
var dotColor = !t.done ? 'rgba(0,0,0,.15)' : t.warn ? 'var(--danger)' : 'var(--success)';
|
||
var timeColor = t.warn ? 'var(--danger)' : 'var(--muted)';
|
||
var textColor = t.warn ? 'var(--danger)' : t.done ? 'var(--ink)' : 'var(--muted)';
|
||
return `<div style="display:flex;gap:12px;align-items:flex-start;margin-bottom:${isLast?0:16}px;position:relative">
|
||
<div style="width:14px;height:14px;border-radius:50%;background:${dotColor};border:2px solid ${t.done?(t.warn?'var(--danger)':'var(--success)'):'rgba(0,0,0,.15)'};flex-shrink:0;position:relative;left:-14px;margin-top:2px;box-shadow:0 0 0 3px var(--bg)"></div>
|
||
<div style="flex:1;margin-left:-14px">
|
||
<div style="font-size:10px;font-weight:700;color:${timeColor};font-family:monospace;margin-bottom:2px">${t.time}</div>
|
||
<div style="font-size:13px;font-weight:${t.warn?700:500};color:${textColor};line-height:1.3">${t.ev}${t.warn?' ⚠':''}</div>
|
||
</div>
|
||
</div>`;
|
||
}).join('')}
|
||
</div>
|
||
` : ''}
|
||
|
||
${tab === 'docs' ? `
|
||
${(!d.docs || d.docs.length === 0) ? `
|
||
<div style="text-align:center;padding:40px 20px">
|
||
<div style="font-size:32px;margin-bottom:12px">📂</div>
|
||
<div style="font-size:14px;font-weight:600;color:var(--ink);margin-bottom:6px">Документов нет</div>
|
||
<div style="font-size:12px;color:var(--muted)">Появятся после замера и сборки</div>
|
||
</div>
|
||
` : d.docs.map((doc, di) => `
|
||
<div class="card" style="padding:0;overflow:hidden;margin-bottom:10px;border:1.5px solid ${doc.signed ? 'rgba(34,197,94,.25)' : 'rgba(245,158,11,.25)'}">
|
||
<!-- Шапка документа -->
|
||
<div style="padding:12px 14px;display:flex;align-items:center;gap:10px;border-bottom:1px solid rgba(0,0,0,.05)">
|
||
<div style="width:38px;height:38px;border-radius:10px;background:${doc.type==='meas'?'rgba(99,102,241,.1)':'rgba(16,185,129,.1)'};display:flex;align-items:center;justify-content:center;font-size:20px;flex-shrink:0">
|
||
${doc.type==='meas'?'📏':'🔧'}
|
||
</div>
|
||
<div style="flex:1">
|
||
<div style="font-size:14px;font-weight:700;color:var(--ink)">${doc.title}</div>
|
||
<div style="font-size:11px;color:var(--muted);margin-top:2px">${doc.date} · ${doc.measurer}</div>
|
||
</div>
|
||
<div style="display:flex;align-items:center;gap:4px;background:${doc.signed?'#DCFCE7':'rgba(245,158,11,.1)'};border-radius:20px;padding:3px 10px;flex-shrink:0">
|
||
<span style="font-size:16px">${doc.signed?'✅':'⏳'}</span>
|
||
<span style="font-size:11px;font-weight:700;color:${doc.signed?'#15803D':'#92400E'}">${doc.signed?'Подписан':'Ожидает'}</span>
|
||
</div>
|
||
</div>
|
||
<!-- Детали -->
|
||
<div style="padding:10px 14px">
|
||
${doc.type==='meas' && doc.rooms.length ? `
|
||
<div style="display:flex;align-items:center;gap:6px;margin-bottom:8px;flex-wrap:wrap">
|
||
<span style="font-size:11px;color:var(--muted)">Помещения:</span>
|
||
${doc.rooms.map(r=>`<span style="font-size:11px;background:rgba(99,102,241,.08);color:var(--accent);border-radius:6px;padding:2px 8px;font-weight:600">${r}</span>`).join('')}
|
||
</div>
|
||
` : ''}
|
||
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:${doc.signed?'10':'0'}px">
|
||
<div style="display:flex;align-items:center;gap:8px">
|
||
${doc.signed && doc.pay==='cash' ? `<span style="font-size:11px;background:rgba(0,0,0,.05);border-radius:6px;padding:2px 8px;color:var(--muted)">💵 Наличные</span>` : ''}
|
||
${doc.amount ? `<span style="font-size:14px;font-weight:800;color:var(--ink)">${doc.amount.toLocaleString('ru')} ₽</span>` : ''}
|
||
</div>
|
||
${doc.signed ? `
|
||
<button onclick="alert('Открыть документ: ${doc.title}\\n${doc.date}\\nПодписан клиентом · ${doc.measurer}\\nСумма: ${doc.amount?.toLocaleString('ru')} ₽')"
|
||
style="background:var(--accent);color:#fff;border:none;border-radius:8px;padding:6px 14px;font-size:12px;font-weight:700;cursor:pointer">
|
||
Просмотр →
|
||
</button>
|
||
` : `<span style="font-size:11px;color:var(--muted)">Подпись при завершении</span>`}
|
||
</div>
|
||
${doc.signed ? `
|
||
<!-- Подпись-превью -->
|
||
<div style="background:rgba(0,0,0,.03);border-radius:8px;padding:8px 12px;display:flex;align-items:center;gap:8px;border:1px dashed rgba(0,0,0,.1)">
|
||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="var(--muted)" stroke-width="2"><path d="M12 19l7-7 3 3-7 7-3-3z"/><path d="M18 13l-1.5-7.5L2 2l3.5 14.5L13 18l5-5z"/><path d="M2 2l7.586 7.586"/><circle cx="11" cy="11" r="2"/></svg>
|
||
<span style="font-size:11px;color:var(--muted)">Подпись клиента зафиксирована · согласие с результатами и оплатой</span>
|
||
</div>
|
||
` : ''}
|
||
</div>
|
||
</div>
|
||
`).join('')}
|
||
` : ''}
|
||
|
||
${tab === 'acts' ? `
|
||
${d.acts.length === 0 ? `
|
||
<div style="text-align:center;padding:40px 20px">
|
||
<div style="font-size:32px;margin-bottom:12px">📋</div>
|
||
<div style="font-size:14px;font-weight:600;color:var(--ink);margin-bottom:6px">Нет актов доп.работ</div>
|
||
<div style="font-size:12px;color:var(--muted)">Сборщик ещё не зафиксировал дополнительные работы</div>
|
||
</div>
|
||
` : `
|
||
<div class="card" style="padding:0;overflow:hidden;margin-bottom:10px">
|
||
${d.acts.map((a,i) => `
|
||
<div style="display:flex;align-items:center;gap:8px;padding:10px 14px;${i<d.acts.length-1?'border-bottom:1px solid rgba(0,0,0,.04)':''}">
|
||
<span style="font-size:10px;color:var(--muted);width:18px;flex-shrink:0;text-align:right">${a.n}</span>
|
||
<span style="flex:1;font-size:12px;color:var(--ink);line-height:1.35">${a.name}</span>
|
||
<span style="font-size:13px;font-weight:700;color:var(--accent);white-space:nowrap">${a.price.toLocaleString('ru')} ₽</span>
|
||
</div>
|
||
`).join('')}
|
||
<div style="display:flex;justify-content:space-between;padding:10px 14px;background:rgba(0,0,0,.03);border-top:2px solid rgba(0,0,0,.06)">
|
||
<span style="font-size:13px;font-weight:700;color:var(--ink)">Итого доп.работ</span>
|
||
<span style="font-size:14px;font-weight:800;color:var(--accent)">${d.acts.reduce((s,a)=>s+a.price,0).toLocaleString('ru')} ₽</span>
|
||
</div>
|
||
</div>
|
||
<button onclick="alert('Подписать акт')" class="btn-primary">✅ Подписать акт</button>
|
||
`}
|
||
` : ''}
|
||
|
||
</div>
|
||
${navBar('assemblies')}
|
||
</div>`;
|
||
}
|
||
|
||
// ── SCREEN 6: ASSEMBLIES ──────────────────────────────────────
|
||
function screenAssemblies() {
|
||
const list = [
|
||
{num:'#А-2847',client:'Краснова Ирина С.',asm:'Кириллов А.В.',date:'22.05',badge:'yellow',status:'Проблема',addr:'ул. Космонавтов, 44'},
|
||
{num:'#А-2851',client:'Тихонов Дмитрий Р.',asm:'Петров С.И.',date:'22.05',badge:'blue',status:'В работе',addr:'пр. Просвещения, 18'},
|
||
{num:'#А-2855',client:'Орлова Татьяна В.',asm:'Смирнов О.К.',date:'22.05',badge:'green',status:'Завершена',addr:'Московский пр., 101'},
|
||
{num:'#А-2841',client:'Зайцев Михаил Н.',asm:'Кириллов А.В.',date:'20.05',badge:'green',status:'Завершена',addr:'ул. Ленина, 77'},
|
||
{num:'#А-2838',client:'Фомина Светлана Г.',asm:'Петров С.И.',date:'19.05',badge:'green',status:'Завершена',addr:'пр. Ветеранов, 55'},
|
||
];
|
||
return `<div class="page">
|
||
<div class="page-header">
|
||
<h2>Сборки</h2>
|
||
</div>
|
||
<div style="padding:10px 16px 0">
|
||
<div class="chip-row" style="padding:0;margin-bottom:10px">
|
||
<div class="chip">Сегодня</div>
|
||
<div class="chip">Неделя</div>
|
||
<div class="chip active">Месяц</div>
|
||
</div>
|
||
<div class="chip-row" style="padding:0;margin-bottom:4px">
|
||
<div class="chip active">Все</div>
|
||
<div class="chip">В работе</div>
|
||
<div class="chip">Завершены</div>
|
||
<div class="chip">Проблемы</div>
|
||
</div>
|
||
</div>
|
||
<div style="padding:4px 16px">
|
||
${list.map(a => `
|
||
<div class="card" onclick="(function(){window._asmId='${a.num}';window._asmTab='info';navigate('assembly_detail')})()" style="padding:12px 14px;margin-bottom:8px;cursor:pointer">
|
||
<div class="row sb" style="margin-bottom:4px">
|
||
<span style="font-size:15px;font-weight:800;color:var(--accent)">${a.num}</span>
|
||
<div style="display:flex;align-items:center;gap:8px">
|
||
<span class="badge ${a.badge}">${a.status}</span>
|
||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" style="color:var(--muted)"><polyline points="9 18 15 12 9 6"/></svg>
|
||
</div>
|
||
</div>
|
||
<div style="font-size:14px;font-weight:700;color:var(--ink)">${a.client}</div>
|
||
<div style="font-size:12px;color:var(--muted);margin-top:2px">📍 ${a.addr}</div>
|
||
<div style="font-size:12px;color:var(--muted);margin-top:2px">🔨 ${a.asm} · ${a.date}</div>
|
||
</div>
|
||
`).join('')}
|
||
<div style="background:var(--card);border-radius:12px;padding:12px 14px;display:flex;align-items:center;justify-content:space-between;box-shadow:0 2px 8px rgba(0,0,0,.06)">
|
||
<span style="font-size:13px;color:var(--muted)">Май: <strong style="color:var(--ink)">47 сборок</strong></span>
|
||
<span style="font-size:13px;color:var(--muted)">✅ <strong style="color:var(--ink)">38</strong> · 🔵 <strong style="color:var(--ink)">9</strong></span>
|
||
</div>
|
||
</div>
|
||
${navBar('assemblies')}
|
||
</div>`;
|
||
}
|
||
|
||
// ── SCREEN 7: FINANCES ─────────────────────────────────────────
|
||
function screenFinances() {
|
||
const tab = window._finTab || 'payroll';
|
||
function setTab(t) { window._finTab = t; navigate('finances'); }
|
||
|
||
const staff = [
|
||
{init:'КА',name:'Кириллов А.В.',role:'Сборщик',done:18,rate:2500,advance:15000,deduct:2400},
|
||
{init:'ПС',name:'Петров С.И.', role:'Сборщик',done:14,rate:2500,advance:10000,deduct:0},
|
||
{init:'СО',name:'Смирнов О.К.', role:'Сборщик',done:11,rate:2500,advance:8000, deduct:1200},
|
||
{init:'НП',name:'Николаев П.В.',role:'Сборщик',done:3, rate:2500,advance:0, deduct:0},
|
||
{init:'ФМ',name:'Фёдорова М.Р.',role:'Замерщик',done:12,rate:1800,advance:5000,deduct:0},
|
||
{init:'КД',name:'Кузнецова А.Д.',role:'Менеджер',done:null,rate:45000,advance:20000,deduct:0},
|
||
];
|
||
|
||
const schedule = [
|
||
{pos:'Старший сборщик', rate:'2 800 ₽/сборка', count:1, name:'Кириллов А.В.'},
|
||
{pos:'Сборщик', rate:'2 500 ₽/сборка', count:3, name:'Петров, Смирнов, Николаев'},
|
||
{pos:'Замерщик', rate:'1 800 ₽/замер', count:1, name:'Фёдорова М.Р.'},
|
||
{pos:'Менеджер по продажам',rate:'45 000 ₽/мес', count:1, name:'Кузнецова А.Д.'},
|
||
{pos:'Диспетчер', rate:'35 000 ₽/мес', count:1, name:'— вакансия'},
|
||
];
|
||
|
||
const settlements = [
|
||
{init:'КА',name:'Кириллов А.В.',entries:[
|
||
{label:'Сборок × 18 (2 500₽)',val:45000,plus:true},
|
||
{label:'Аванс 05.05', val:-15000,plus:false},
|
||
{label:'Рекламация #Р-041', val:-2400, plus:false},
|
||
{label:'К выдаче', val:27600, plus:true,bold:true},
|
||
]},
|
||
{init:'НП',name:'Николаев П.В.',entries:[
|
||
{label:'Сборок × 3 (2 500₽)', val:7500, plus:true},
|
||
{label:'Аванс не брал', val:0, plus:true},
|
||
{label:'К выдаче', val:7500, plus:true,bold:true},
|
||
]},
|
||
];
|
||
|
||
const contractors = [
|
||
{init:'ФЕ',name:'ИП Фёдоров Е.А.',role:'Экспедитор',routes:12,pkg:187,rate:85,paid:0},
|
||
{init:'ЗА',name:'ИП Захаров А.И.', role:'Экспедитор',routes:9, pkg:143,rate:85,paid:10000},
|
||
{init:'МС',name:'ИП Морозов С.В.', role:'Экспедитор',routes:8, pkg:112,rate:85,paid:5000},
|
||
{init:'ТБ',name:'ИП Тихонов Б.Р.', role:'Технолог', routes:null,pkg:null,rate:8500,paid:8500,flat:8500,contract:'до 31.08'},
|
||
];
|
||
|
||
const fot = staff.map(s => {
|
||
const accrued = s.done ? s.done * s.rate : s.rate;
|
||
const net = accrued - s.advance - s.deduct;
|
||
return {...s, accrued, net};
|
||
});
|
||
const totalFot = fot.reduce((a,s) => a + s.accrued, 0);
|
||
const totalNet = fot.reduce((a,s) => a + s.net, 0);
|
||
|
||
const cTotal = contractors.reduce((a,c) => {
|
||
const acc = c.flat ? c.flat : c.pkg * c.rate;
|
||
return a + (acc - c.paid);
|
||
}, 0);
|
||
|
||
return `<div class="page">
|
||
<div class="page-header">
|
||
<h2>Финансы</h2>
|
||
<span style="font-size:11px;color:var(--muted);font-weight:500">Май 2026</span>
|
||
</div>
|
||
|
||
<!-- Tabs -->
|
||
<div style="display:flex;gap:0;padding:0 16px;margin-bottom:14px;border-bottom:2px solid rgba(0,0,0,.06)">
|
||
${[['payroll','Сотрудники'],['contractors','Подрядчики'],['settlements','Расчёты'],['rent','Аренда'],['schedule','Штатное']].map(([id,lbl]) => `
|
||
<div onclick="(function(){window._finTab='${id}';navigate('finances')})()" style="flex:1;text-align:center;padding:10px 1px 9px;font-size:10px;font-weight:700;cursor:pointer;color:${tab===id?'var(--accent)':'var(--muted)'};border-bottom:${tab===id?'2px solid var(--accent)':'2px solid transparent'};margin-bottom:-2px">${lbl}</div>
|
||
`).join('')}
|
||
</div>
|
||
|
||
<div style="padding:0 16px 80px">
|
||
|
||
${tab === 'payroll' ? `
|
||
<!-- Итог ФОТ -->
|
||
<div class="card" style="padding:14px 16px;margin-bottom:12px;background:linear-gradient(135deg,var(--accent),color-mix(in srgb,var(--accent) 70%,#000))">
|
||
<div style="font-size:11px;color:rgba(255,255,255,.65);font-weight:600;margin-bottom:4px">ИТОГО ФОТ МАЙ 2026</div>
|
||
<div style="font-size:26px;font-weight:800;color:#fff;line-height:1">${totalFot.toLocaleString('ru')} ₽</div>
|
||
<div style="font-size:12px;color:rgba(255,255,255,.6);margin-top:4px">К выдаче: ${totalNet.toLocaleString('ru')} ₽ · 6 сотрудников</div>
|
||
</div>
|
||
<!-- Строки ведомости -->
|
||
<div class="card" style="padding:0;overflow:hidden;margin-bottom:8px">
|
||
<div style="display:grid;grid-template-columns:1fr 60px 60px 60px;gap:0;padding:8px 12px;background:rgba(0,0,0,.03);border-bottom:1px solid rgba(0,0,0,.06)">
|
||
<span style="font-size:10px;font-weight:700;color:var(--muted)">СОТРУДНИК</span>
|
||
<span style="font-size:10px;font-weight:700;color:var(--muted);text-align:right">НАЧИСЛ.</span>
|
||
<span style="font-size:10px;font-weight:700;color:var(--muted);text-align:right">АВАНС</span>
|
||
<span style="font-size:10px;font-weight:700;color:var(--muted);text-align:right">К ВЫДАЧЕ</span>
|
||
</div>
|
||
${fot.map((s,i) => `
|
||
<div style="display:grid;grid-template-columns:1fr 60px 60px 60px;gap:0;padding:10px 12px;border-bottom:${i<fot.length-1?'1px solid rgba(0,0,0,.04)':'none'};align-items:center">
|
||
<div>
|
||
<div style="font-size:13px;font-weight:700;color:var(--ink)">${s.name.split(' ')[0]} ${s.name.split(' ')[1]||''}</div>
|
||
<div style="font-size:10px;color:var(--muted)">${s.role}${s.done!=null?' · '+s.done+' ед.':''}</div>
|
||
</div>
|
||
<div style="font-size:12px;font-weight:600;color:var(--ink);text-align:right">${(s.accrued/1000).toFixed(0)}к</div>
|
||
<div style="font-size:12px;color:var(--muted);text-align:right">${s.advance?'−'+(s.advance/1000).toFixed(0)+'к':'—'}</div>
|
||
<div style="font-size:13px;font-weight:800;color:${s.net<0?'var(--danger)':'var(--accent)'};text-align:right">${(s.net/1000).toFixed(0)}к</div>
|
||
</div>
|
||
`).join('')}
|
||
<div style="display:grid;grid-template-columns:1fr 60px 60px 60px;gap:0;padding:10px 12px;background:rgba(0,0,0,.03);border-top:2px solid rgba(0,0,0,.08)">
|
||
<span style="font-size:12px;font-weight:800;color:var(--ink)">Итого</span>
|
||
<span style="font-size:12px;font-weight:800;color:var(--ink);text-align:right">${(totalFot/1000).toFixed(0)}к</span>
|
||
<span style="font-size:12px;color:var(--muted);text-align:right"></span>
|
||
<span style="font-size:13px;font-weight:800;color:var(--accent);text-align:right">${(totalNet/1000).toFixed(0)}к</span>
|
||
</div>
|
||
</div>
|
||
<div style="font-size:11px;color:var(--muted);text-align:center;margin-top:8px">🔒 Только просмотр · ФОТ устанавливается руководством</div>
|
||
` : ''}
|
||
|
||
${tab === 'contractors' ? `
|
||
<!-- Итог к выплате -->
|
||
<div class="card" style="padding:14px 16px;margin-bottom:12px;background:linear-gradient(135deg,#1E293B,#334155)">
|
||
<div style="font-size:11px;color:rgba(255,255,255,.55);font-weight:600;margin-bottom:4px">К ВЫПЛАТЕ ПОДРЯДЧИКАМ · МАЙ 2026</div>
|
||
<div style="font-size:26px;font-weight:800;color:#fff;line-height:1">${cTotal.toLocaleString('ru')} ₽</div>
|
||
<div style="font-size:12px;color:rgba(255,255,255,.5);margin-top:4px">${contractors.length} подрядчика · Экспедиторы + Технолог</div>
|
||
</div>
|
||
|
||
${contractors.map(c => {
|
||
const accrued = c.flat ? c.flat : c.pkg * c.rate;
|
||
const due = accrued - c.paid;
|
||
return `
|
||
<div class="card" style="padding:14px;margin-bottom:8px">
|
||
<div style="display:flex;align-items:center;gap:10px;margin-bottom:10px">
|
||
<div style="width:36px;height:36px;border-radius:50%;background:var(--accent);display:flex;align-items:center;justify-content:center;font-size:12px;font-weight:800;color:#fff;flex-shrink:0">${c.init}</div>
|
||
<div style="flex:1">
|
||
<div style="font-size:13px;font-weight:700;color:var(--ink)">${c.name}</div>
|
||
<div style="font-size:11px;color:var(--muted)">${c.role}${c.contract?' · Договор '+c.contract:''}</div>
|
||
</div>
|
||
<div style="text-align:right">
|
||
<div style="font-size:15px;font-weight:800;color:${due>0?'var(--accent)':'var(--success)'}">${due.toLocaleString('ru')} ₽</div>
|
||
<div style="font-size:10px;color:var(--muted);margin-top:1px">к выплате</div>
|
||
</div>
|
||
</div>
|
||
<div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:6px">
|
||
${c.pkg!=null ? `
|
||
<div style="background:rgba(0,0,0,.03);border-radius:8px;padding:7px;text-align:center">
|
||
<div style="font-size:14px;font-weight:800;color:var(--ink)">${c.routes}</div>
|
||
<div style="font-size:10px;color:var(--muted);margin-top:1px">маршрутов</div>
|
||
</div>
|
||
<div style="background:rgba(0,0,0,.03);border-radius:8px;padding:7px;text-align:center">
|
||
<div style="font-size:14px;font-weight:800;color:var(--ink)">${c.pkg}</div>
|
||
<div style="font-size:10px;color:var(--muted);margin-top:1px">упаковок</div>
|
||
</div>
|
||
<div style="background:rgba(0,0,0,.03);border-radius:8px;padding:7px;text-align:center">
|
||
<div style="font-size:14px;font-weight:800;color:var(--ink)">${c.rate}₽</div>
|
||
<div style="font-size:10px;color:var(--muted);margin-top:1px">за упак.</div>
|
||
</div>` : `
|
||
<div style="background:rgba(0,0,0,.03);border-radius:8px;padding:7px;text-align:center;grid-column:span 2">
|
||
<div style="font-size:14px;font-weight:800;color:var(--ink)">${c.flat.toLocaleString('ru')} ₽</div>
|
||
<div style="font-size:10px;color:var(--muted);margin-top:1px">фиксированная ставка/мес</div>
|
||
</div>`}
|
||
<div style="background:${c.paid>0?'rgba(16,185,129,.08)':'rgba(0,0,0,.03)'};border-radius:8px;padding:7px;text-align:center">
|
||
<div style="font-size:14px;font-weight:800;color:${c.paid>0?'var(--success)':'var(--muted)'}">${c.paid>0?c.paid.toLocaleString('ru')+' ₽':'—'}</div>
|
||
<div style="font-size:10px;color:var(--muted);margin-top:1px">выплачено</div>
|
||
</div>
|
||
</div>
|
||
<div style="display:flex;justify-content:space-between;align-items:center;margin-top:10px;padding-top:8px;border-top:1px solid rgba(0,0,0,.06)">
|
||
<div style="font-size:12px;color:var(--muted)">Начислено: <b style="color:var(--ink)">${accrued.toLocaleString('ru')} ₽</b></div>
|
||
<span style="font-size:11px;background:${due>0?'rgba(0,62,126,.08)':'rgba(16,185,129,.08)'};color:${due>0?'var(--accent)':'var(--success)'};border-radius:6px;padding:3px 8px;font-weight:700">${due>0?'Долг: '+due.toLocaleString('ru')+' ₽':'Закрыт'}</span>
|
||
</div>
|
||
</div>`;
|
||
}).join('')}
|
||
<div style="font-size:11px;color:var(--muted);text-align:center;margin-top:4px">🔒 Выплаты проводятся через бухгалтерию</div>
|
||
` : ''}
|
||
|
||
${tab === 'schedule' ? `
|
||
<div style="font-size:12px;color:var(--muted);margin-bottom:12px;display:flex;align-items:center;gap:6px">
|
||
<span style="background:rgba(0,0,0,.06);border-radius:6px;padding:3px 8px;font-weight:600">🔒 Только просмотр</span>
|
||
<span>Устанавливается HR / руководством</span>
|
||
</div>
|
||
${schedule.map(p => `
|
||
<div class="card" style="padding:12px 14px;margin-bottom:8px">
|
||
<div class="row sb">
|
||
<div style="flex:1">
|
||
<div style="font-size:13px;font-weight:700;color:var(--ink)">${p.pos}</div>
|
||
<div style="font-size:12px;color:var(--muted);margin-top:3px">👤 ${p.name}</div>
|
||
</div>
|
||
<div style="text-align:right;flex-shrink:0;margin-left:12px">
|
||
<div style="font-size:13px;font-weight:700;color:var(--accent)">${p.rate}</div>
|
||
<div style="font-size:10px;color:var(--muted);margin-top:2px">${p.count} ед.</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`).join('')}
|
||
` : ''}
|
||
|
||
${tab === 'settlements' ? `
|
||
<div style="font-size:12px;color:var(--muted);margin-bottom:14px">Детализация взаиморасчётов · Май 2026</div>
|
||
${settlements.map(s => `
|
||
<div class="card" style="padding:14px;margin-bottom:10px">
|
||
<div style="display:flex;align-items:center;gap:10px;margin-bottom:12px">
|
||
<div style="width:36px;height:36px;border-radius:50%;background:var(--accent);display:flex;align-items:center;justify-content:center;font-size:13px;font-weight:800;color:#fff;flex-shrink:0">${s.init}</div>
|
||
<div>
|
||
<div style="font-size:14px;font-weight:700;color:var(--ink)">${s.name}</div>
|
||
<div style="font-size:11px;color:var(--muted)">Сборщик</div>
|
||
</div>
|
||
</div>
|
||
${s.entries.map((e,i) => `
|
||
<div style="display:flex;justify-content:space-between;align-items:center;padding:7px 0;${i<s.entries.length-1?'border-bottom:1px solid rgba(0,0,0,.05)':'padding-top:10px;border-top:2px solid rgba(0,0,0,.08);margin-top:4px'}">
|
||
<span style="font-size:${e.bold?'13':'12'}px;font-weight:${e.bold?700:500};color:${e.bold?'var(--ink)':'var(--muted)'}">${e.label}</span>
|
||
<span style="font-size:${e.bold?'15':'13'}px;font-weight:${e.bold?800:600};color:${e.val>0?'var(--accent)':e.val<0?'var(--danger)':'var(--muted)'}">
|
||
${e.val>0?'+':e.val<0?'':''}${e.val!==0?e.val.toLocaleString('ru')+' ₽':'—'}
|
||
</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
`).join('')}
|
||
<div style="font-size:11px;color:var(--muted);text-align:center">Показаны сборщики с детализацией · ${settlements.length} из ${staff.filter(s=>s.role==='Сборщик').length}</div>
|
||
` : ''}
|
||
|
||
${tab === 'rent' ? (() => {
|
||
const salons = [
|
||
{name:'Салон Ленина', addr:'ул. Ленина, 47', rent:65000, paid:true, due:'01.06'},
|
||
{name:'Салон Победы', addr:'пр. Победы, 12', rent:58000, paid:false, due:'25.05'},
|
||
];
|
||
const fixed = [
|
||
{cat:'Коммунальные', items:[
|
||
{name:'Электроэнергия (2 салона)', val:8400},
|
||
{name:'Интернет + телефония', val:3200},
|
||
{name:'Вода и отопление', val:4100},
|
||
]},
|
||
{cat:'Операционные', items:[
|
||
{name:'Расходники (упаковка, скотч)', val:8500},
|
||
{name:'Банковское обслуживание', val:4900},
|
||
{name:'Подписки и ПО', val:2800},
|
||
]},
|
||
];
|
||
const rentTotal = salons.reduce((a,s)=>a+s.rent,0);
|
||
const fixedTotal = fixed.reduce((a,c)=>a+c.items.reduce((b,i)=>b+i.val,0),0);
|
||
const fotTotal = totalFot;
|
||
const grandTotal = rentTotal + fixedTotal + fotTotal;
|
||
return `
|
||
<!-- Итого фикс. расходы -->
|
||
<div class="card" style="background:linear-gradient(135deg,#1E293B,#0F172A);color:#fff;padding:16px 20px;margin-bottom:12px">
|
||
<div style="font-size:11px;opacity:.55;font-weight:700;text-transform:uppercase;letter-spacing:.08em;margin-bottom:6px">РАСХОДЫ · МАЙ 2026</div>
|
||
<div style="font-size:28px;font-weight:800;letter-spacing:-.03em">${grandTotal.toLocaleString('ru')} ₽</div>
|
||
<div style="display:flex;gap:16px;margin-top:12px;font-size:12px;opacity:.75">
|
||
<div><div style="opacity:.6;font-size:10px">ФОТ</div><div style="font-weight:700">${(fotTotal/1000).toFixed(0)} тыс</div></div>
|
||
<div><div style="opacity:.6;font-size:10px">Аренда</div><div style="font-weight:700">${(rentTotal/1000).toFixed(0)} тыс</div></div>
|
||
<div><div style="opacity:.6;font-size:10px">Прочие</div><div style="font-weight:700">${(fixedTotal/1000).toFixed(0)} тыс</div></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Аренда салонов -->
|
||
<div style="font-size:11px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.06em;margin:16px 0 8px">Аренда салонов</div>
|
||
${salons.map(s=>`
|
||
<div class="card ${s.paid?'success-border':'danger-border'}" style="padding:14px 16px;margin-bottom:8px">
|
||
<div style="display:flex;justify-content:space-between;align-items:flex-start">
|
||
<div>
|
||
<div style="font-size:14px;font-weight:700;color:var(--ink)">${s.name}</div>
|
||
<div style="font-size:11px;color:var(--muted);margin-top:2px">${s.addr}</div>
|
||
<div style="margin-top:6px;display:inline-flex;align-items:center;gap:4px;padding:3px 9px;border-radius:20px;font-size:11px;font-weight:700;background:${s.paid?'#DCFCE7':'#FEF2F2'};color:${s.paid?'#15803D':'#991B1B'}">
|
||
${s.paid ? '✓ Оплачено' : '⚠ Оплатить до '+s.due}
|
||
</div>
|
||
</div>
|
||
<div style="text-align:right">
|
||
<div style="font-size:18px;font-weight:800;color:var(--ink)">${s.rent.toLocaleString('ru')} ₽</div>
|
||
<div style="font-size:11px;color:var(--muted);margin-top:2px">в месяц</div>
|
||
</div>
|
||
</div>
|
||
</div>`).join('')}
|
||
|
||
<!-- Прочие фикс. расходы -->
|
||
${fixed.map(cat=>`
|
||
<div style="font-size:11px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.06em;margin:16px 0 8px">${cat.cat}</div>
|
||
<div class="card" style="padding:0;overflow:hidden;margin-bottom:8px">
|
||
${cat.items.map((item,i)=>`
|
||
<div style="display:flex;justify-content:space-between;align-items:center;padding:11px 14px;border-bottom:${i<cat.items.length-1?'1px solid rgba(0,0,0,.05)':'none'}">
|
||
<span style="font-size:13px;color:var(--ink);font-weight:500">${item.name}</span>
|
||
<span style="font-size:13px;font-weight:700;color:var(--ink)">${item.val.toLocaleString('ru')} ₽</span>
|
||
</div>`).join('')}
|
||
<div style="display:flex;justify-content:space-between;padding:10px 14px;background:rgba(0,0,0,.03);border-top:1.5px solid rgba(0,0,0,.07)">
|
||
<span style="font-size:12px;font-weight:800;color:var(--ink)">Итого</span>
|
||
<span style="font-size:12px;font-weight:800;color:var(--accent)">${cat.items.reduce((a,i)=>a+i.val,0).toLocaleString('ru')} ₽</span>
|
||
</div>
|
||
</div>`).join('')}
|
||
|
||
<!-- ФОТ сводка по категориям -->
|
||
<div style="font-size:11px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.06em;margin:16px 0 8px">ФОТ по категориям</div>
|
||
<div class="card" style="padding:0;overflow:hidden;margin-bottom:8px">
|
||
${[
|
||
{role:'Сборщики (сдельно)', val: staff.filter(s=>s.role==='Сборщик').reduce((a,s)=>a+(s.done?s.done*s.rate:0),0)},
|
||
{role:'Замерщики (сдельно)', val: staff.filter(s=>s.role==='Замерщик').reduce((a,s)=>a+(s.done?s.done*s.rate:0),0)},
|
||
{role:'Менеджеры (оклад)', val: staff.filter(s=>s.role==='Менеджер').reduce((a,s)=>a+s.rate,0)},
|
||
].map((r,i,arr)=>`
|
||
<div style="display:flex;justify-content:space-between;align-items:center;padding:11px 14px;border-bottom:${i<arr.length-1?'1px solid rgba(0,0,0,.05)':'none'}">
|
||
<span style="font-size:13px;color:var(--ink);font-weight:500">${r.role}</span>
|
||
<span style="font-size:13px;font-weight:700;color:var(--ink)">${r.val.toLocaleString('ru')} ₽</span>
|
||
</div>`).join('')}
|
||
<div style="display:flex;justify-content:space-between;padding:10px 14px;background:rgba(0,0,0,.03);border-top:1.5px solid rgba(0,0,0,.07)">
|
||
<span style="font-size:12px;font-weight:800;color:var(--ink)">Итого ФОТ</span>
|
||
<span style="font-size:12px;font-weight:800;color:var(--accent)">${fotTotal.toLocaleString('ru')} ₽</span>
|
||
</div>
|
||
</div>
|
||
`;
|
||
})() : ''}
|
||
|
||
</div>
|
||
${navBar('finances')}
|
||
</div>`;
|
||
}
|
||
|
||
// ── SCREEN 8: FEED ─────────────────────────────────────────────
|
||
function screenFeed() {
|
||
return `<div class="page">
|
||
<div class="page-header">
|
||
<h2>Лента</h2>
|
||
<button class="header-action" onclick="navigate('feed_create')">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M11 4H4a2 2 0 00-2 2v14a2 2 0 002 2h14a2 2 0 002-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 013 3L12 15l-4 1 1-4 9.5-9.5z"/></svg>
|
||
</button>
|
||
</div>
|
||
<div style="padding:12px 16px">
|
||
|
||
<!-- Pinned -->
|
||
<div style="background:var(--accent);border-radius:16px;padding:14px 16px;margin-bottom:10px;box-shadow:0 4px 16px rgba(0,0,0,.15)">
|
||
<div class="row sb" style="margin-bottom:6px">
|
||
<span style="font-size:10px;font-weight:700;color:rgba(255,255,255,.7);text-transform:uppercase;letter-spacing:.08em">📌 Закреплено</span>
|
||
<span style="font-size:11px;color:rgba(255,255,255,.6)">20 мая</span>
|
||
</div>
|
||
<div style="font-size:14px;font-weight:700;color:#fff;margin-bottom:4px">Новые ставки с 1 июня — коэффициент +5%</div>
|
||
<div style="font-size:12px;color:rgba(255,255,255,.7)">Директор</div>
|
||
</div>
|
||
|
||
<!-- Birthday -->
|
||
<div class="card" style="margin-bottom:8px">
|
||
<div class="row sb" style="margin-bottom:6px">
|
||
<span style="font-size:11px;color:var(--muted)">Авто · 22 мая</span>
|
||
</div>
|
||
<div style="font-size:14px;font-weight:700;color:var(--ink);margin-bottom:4px">🎂 Сегодня день рождения у Кириллова Алексея! Поздравим? 🎉</div>
|
||
<button class="btn-sm" style="margin-top:6px">🎁 Поздравить</button>
|
||
</div>
|
||
|
||
<!-- Announcement -->
|
||
<div class="card" style="margin-bottom:8px">
|
||
<div class="row sb" style="margin-bottom:6px">
|
||
<span style="font-size:11px;color:var(--muted)">Руслан В. · 21 мая</span>
|
||
<span class="badge blue">Важно</span>
|
||
</div>
|
||
<div style="font-size:14px;font-weight:700;color:var(--ink)">📢 Плановое совещание в пятницу 24.05 в 18:00 — всем обязательно</div>
|
||
</div>
|
||
|
||
<!-- Champion -->
|
||
<div class="card" style="margin-bottom:8px">
|
||
<div class="row sb" style="margin-bottom:6px">
|
||
<span style="font-size:11px;color:var(--muted)">Авто · 1 мая</span>
|
||
</div>
|
||
<div style="font-size:14px;font-weight:700;color:var(--ink)">🏆 Сборщик апреля — Петров Сергей! 18 сборок, рейтинг 4.9</div>
|
||
</div>
|
||
|
||
<!-- Info -->
|
||
<div class="card">
|
||
<div class="row sb" style="margin-bottom:6px">
|
||
<span style="font-size:11px;color:var(--muted)">Менеджер · 15 мая</span>
|
||
</div>
|
||
<div style="font-size:14px;font-weight:700;color:var(--ink)">ℹ️ Обновлены шаблоны актов доп.работ</div>
|
||
</div>
|
||
|
||
</div>
|
||
${navBar('feed')}
|
||
</div>`;
|
||
}
|
||
|
||
// ── SCREEN 8: FEED CREATE ─────────────────────────────────────
|
||
function screenFeedCreate() {
|
||
return `<div class="page">
|
||
<div class="page-header">
|
||
<button class="back-btn" onclick="navigate('feed')">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="15 18 9 12 15 6"/></svg>
|
||
</button>
|
||
<h2>Новое объявление</h2>
|
||
</div>
|
||
<div style="padding:16px">
|
||
<div class="card">
|
||
<div class="form-group">
|
||
<label>Тип</label>
|
||
<select class="form-select">
|
||
<option>Объявление</option>
|
||
<option>Важное</option>
|
||
<option>Поздравление</option>
|
||
<option>Напоминание</option>
|
||
</select>
|
||
</div>
|
||
<div class="form-group">
|
||
<label>Заголовок</label>
|
||
<input class="form-input" type="text" placeholder="Краткий заголовок...">
|
||
</div>
|
||
<div class="form-group">
|
||
<label>Текст</label>
|
||
<textarea class="form-input" rows="4" placeholder="Текст объявления..."></textarea>
|
||
</div>
|
||
<div class="form-group">
|
||
<label>Кому</label>
|
||
<div class="chip-row" style="padding:0;margin-top:4px;flex-wrap:wrap;gap:6px">
|
||
<div class="chip active">Все</div>
|
||
<div class="chip">Сборщики</div>
|
||
<div class="chip">Замерщики</div>
|
||
<div class="chip">Менеджеры</div>
|
||
</div>
|
||
</div>
|
||
<div class="toggle-row" style="padding:0;margin-top:4px">
|
||
<span class="toggle-label">Закрепить вверху</span>
|
||
<div class="toggle" onclick="this.classList.toggle('on')"></div>
|
||
</div>
|
||
</div>
|
||
<button class="btn-primary">Опубликовать</button>
|
||
</div>
|
||
${navBar('feed')}
|
||
</div>`;
|
||
}
|
||
|
||
// ── SCREEN 9: ANALYTICS ───────────────────────────────────────
|
||
function screenAnalytics() {
|
||
const weeks = ['Нед. 1','Нед. 2','Нед. 3','Нед. 4'];
|
||
const vals = [8,14,13,12];
|
||
const maxV = 14;
|
||
const topAsm = [
|
||
{rank:'🥇',name:'Петров С.И.',count:18,rating:4.9,earn:'32 400 ₽'},
|
||
{rank:'🥈',name:'Кириллов А.В.',count:18,rating:4.8,earn:'28 400 ₽'},
|
||
{rank:'🥉',name:'Смирнов О.К.',count:11,rating:4.7,earn:'18 200 ₽'},
|
||
];
|
||
return `<div class="page">
|
||
<div class="page-header">
|
||
<h2>Аналитика · Май 2026</h2>
|
||
</div>
|
||
<div style="padding:12px 16px 0">
|
||
|
||
<!-- Month switcher -->
|
||
<div style="display:flex;align-items:center;justify-content:center;gap:16px;margin-bottom:12px">
|
||
<button style="background:none;border:none;font-size:20px;cursor:pointer;color:var(--muted)">◀</button>
|
||
<span style="font-size:15px;font-weight:700;color:var(--ink)">Май 2026</span>
|
||
<button style="background:none;border:none;font-size:20px;cursor:pointer;color:var(--muted)">▶</button>
|
||
</div>
|
||
|
||
<!-- KPI row -->
|
||
<div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:8px;margin-bottom:16px">
|
||
${[
|
||
{val:'284 000 ₽',sub:'+12% к апрелю',icon:'💰'},
|
||
{val:'47 / 38',sub:'Сборок / завершено',icon:'🔨'},
|
||
{val:'4.7 ⭐',sub:'Ср. оценка',icon:'⭐'},
|
||
].map(k => `
|
||
<div class="card" style="margin-bottom:0;padding:12px;text-align:center">
|
||
<div style="font-size:18px;margin-bottom:4px">${k.icon}</div>
|
||
<div style="font-size:13px;font-weight:800;color:var(--accent)">${k.val}</div>
|
||
<div style="font-size:10px;color:var(--muted);margin-top:3px;line-height:1.3">${k.sub}</div>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
|
||
<!-- Bar chart -->
|
||
<div class="section-label" style="margin:0 0 8px">Сборки по неделям</div>
|
||
<div class="card" style="padding:16px">
|
||
<div class="bar-chart">
|
||
${weeks.map((w,i) => `
|
||
<div class="bar-col">
|
||
<span style="font-size:11px;color:var(--muted);font-weight:700">${vals[i]}</span>
|
||
<div class="bar" style="height:${(vals[i]/maxV)*60}px"></div>
|
||
<span class="bar-label">${w}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Top assemblers -->
|
||
<div class="section-label" style="margin:12px 0 8px">Топ сборщиков</div>
|
||
<div class="card" style="padding:0;overflow:hidden">
|
||
<div style="display:grid;grid-template-columns:2fr 1fr 1fr 1fr;padding:8px 14px;background:var(--bg)">
|
||
${['Сборщик','Сборок','Рейтинг','Доход'].map(h => `<span style="font-size:10px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em">${h}</span>`).join('')}
|
||
</div>
|
||
${topAsm.map(a => `
|
||
<div style="display:grid;grid-template-columns:2fr 1fr 1fr 1fr;padding:10px 14px;border-top:1px solid rgba(0,0,0,.05);align-items:center">
|
||
<div style="font-size:13px;font-weight:700;color:var(--ink)">${a.rank} ${a.name.split(' ')[0]}</div>
|
||
<div style="font-size:13px;font-weight:700;color:var(--accent)">${a.count}</div>
|
||
<div style="font-size:13px;color:var(--ink)">${a.rating}</div>
|
||
<div style="font-size:11px;font-weight:600;color:var(--ink)">${a.earn}</div>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
|
||
<button class="btn-secondary" style="margin-top:16px">📥 Экспортировать отчёт</button>
|
||
</div>
|
||
${navBar('home')}
|
||
</div>`;
|
||
}
|
||
|
||
// ── SCREEN 10: PROFILE ─────────────────────────────────────────
|
||
// ── SCREEN: ICON SHOWCASE ─────────────────────────────────────
|
||
function screenIconShowcase() {
|
||
var sets = [
|
||
{
|
||
id:'a', name:'Текущий стиль', desc:'Outline · stroke-width 2 · Lucide',
|
||
icons:[
|
||
{label:'Главная', svg:'<path d="M3 9l9-7 9 7v11a2 2 0 01-2 2H5a2 2 0 01-2-2z"/><polyline points="9 22 9 12 15 12 15 22"/>'},
|
||
{label:'Сборщики', svg:'<path d="M2 18a1 1 0 001 1h18a1 1 0 001-1v-2a1 1 0 00-1-1H3a1 1 0 00-1 1v2z"/><path d="M10 10V5a1 1 0 011-1h2a1 1 0 011 1v5"/><path d="M4 15v-3a8 8 0 018-8"/><path d="M20 15v-3a8 8 0 00-8-8"/>'},
|
||
{label:'Прейскурант',svg:'<path d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2"/><rect x="9" y="3" width="6" height="4" rx="1"/><line x1="9" y1="12" x2="15" y2="12"/><line x1="9" y1="16" x2="13" y2="16"/>'},
|
||
{label:'Финансы', svg:'<line x1="12" y1="1" x2="12" y2="23"/><path d="M17 5H9.5a3.5 3.5 0 000 7h5a3.5 3.5 0 010 7H6"/>'},
|
||
{label:'Профиль', svg:'<path d="M20 21v-2a4 4 0 00-4-4H8a4 4 0 00-4 4v2"/><circle cx="12" cy="7" r="4"/>'},
|
||
],
|
||
sw:'2', fill:'none', color:'currentColor'
|
||
},
|
||
{
|
||
id:'b', name:'Bold Rounded', desc:'Outline · stroke-width 2.5 · скруглённые',
|
||
icons:[
|
||
{label:'Главная', svg:'<path d="M3 9l9-7 9 7v11a2 2 0 01-2 2H5a2 2 0 01-2-2z"/><polyline points="9 22 9 12 15 12 15 22"/>'},
|
||
{label:'Сборщики', svg:'<path d="M2 18a1 1 0 001 1h18a1 1 0 001-1v-2a1 1 0 00-1-1H3a1 1 0 00-1 1v2z"/><path d="M10 10V5a1 1 0 011-1h2a1 1 0 011 1v5"/><path d="M4 15v-3a8 8 0 018-8"/><path d="M20 15v-3a8 8 0 00-8-8"/>'},
|
||
{label:'Прейскурант',svg:'<path d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2"/><rect x="9" y="3" width="6" height="4" rx="1"/><line x1="9" y1="12" x2="15" y2="12"/><line x1="9" y1="16" x2="13" y2="16"/>'},
|
||
{label:'Финансы', svg:'<line x1="12" y1="1" x2="12" y2="23"/><path d="M17 5H9.5a3.5 3.5 0 000 7h5a3.5 3.5 0 010 7H6"/>'},
|
||
{label:'Профиль', svg:'<path d="M20 21v-2a4 4 0 00-4-4H8a4 4 0 00-4 4v2"/><circle cx="12" cy="7" r="4"/>'},
|
||
],
|
||
sw:'2.5', fill:'none', color:'currentColor'
|
||
},
|
||
{
|
||
id:'c', name:'Filled + акцент', desc:'Заливка · активный = белый на акценте',
|
||
icons:[
|
||
{label:'Главная', svg:'<path d="M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z"/>'},
|
||
{label:'Сборщики', svg:'<path d="M4 17h16v2H4v-2zm1-2V12a7 7 0 0114 0v3H5zm5-8V5h4v2h-4z"/>'},
|
||
{label:'Прейскурант',svg:'<path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8l-6-6zm-1 9H9v-2h4v2zm2 4H9v-2h6v2z"/>'},
|
||
{label:'Финансы', svg:'<path d="M11.8 10.9c-2.27-.59-3-1.2-3-2.15 0-1.09 1.01-1.85 2.7-1.85 1.78 0 2.44.85 2.5 2.1h2.21c-.07-1.72-1.12-3.3-3.21-3.81V3h-3v2.16c-1.94.42-3.5 1.68-3.5 3.61 0 2.31 1.91 3.46 4.7 4.13 2.5.6 3 1.48 3 2.41 0 .69-.49 1.79-2.7 1.79-2.06 0-2.87-.92-2.98-2.1h-2.2c.12 2.19 1.76 3.42 3.68 3.83V21h3v-2.15c1.95-.37 3.5-1.5 3.5-3.55 0-2.84-2.43-3.81-4.7-4.4z"/>'},
|
||
{label:'Профиль', svg:'<path d="M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z"/>'},
|
||
],
|
||
sw:'0', fill:'currentColor', color:'currentColor'
|
||
},
|
||
{
|
||
id:'d', name:'Duotone', desc:'Двухцветные · body + accent layer',
|
||
icons:[
|
||
{label:'Главная', svg:'<path d="M3 9l9-7 9 7v11a2 2 0 01-2 2H5a2 2 0 01-2-2z" fill="currentColor" fill-opacity=".15" stroke="currentColor" stroke-width="2"/><polyline points="9 22 9 12 15 12 15 22" fill="none" stroke="currentColor" stroke-width="2"/>'},
|
||
{label:'Сборщики', svg:'<path d="M2 18a1 1 0 001 1h18a1 1 0 001-1v-2a1 1 0 00-1-1H3a1 1 0 00-1 1v2z" fill="currentColor" fill-opacity=".2" stroke="currentColor" stroke-width="2"/><path d="M4 15v-3a8 8 0 0116 0v3" fill="currentColor" fill-opacity=".08" stroke="currentColor" stroke-width="2"/>'},
|
||
{label:'Прейскурант',svg:'<path d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2" fill="none" stroke="currentColor" stroke-width="2"/><rect x="9" y="3" width="6" height="4" rx="1" fill="currentColor" fill-opacity=".2" stroke="currentColor" stroke-width="2"/><line x1="9" y1="12" x2="15" y2="12" stroke="currentColor" stroke-width="2"/><line x1="9" y1="16" x2="13" y2="16" stroke="currentColor" stroke-width="2"/>'},
|
||
{label:'Финансы', svg:'<circle cx="12" cy="12" r="9" fill="currentColor" fill-opacity=".1" stroke="currentColor" stroke-width="2"/><line x1="12" y1="7" x2="12" y2="17" stroke="currentColor" stroke-width="2"/><path d="M15 9.5H10.5a2.5 2.5 0 000 5h3a2.5 2.5 0 010 5H8" fill="none" stroke="currentColor" stroke-width="2"/>'},
|
||
{label:'Профиль', svg:'<circle cx="12" cy="7" r="4" fill="currentColor" fill-opacity=".2" stroke="currentColor" stroke-width="2"/><path d="M4 21v-2a8 8 0 0116 0v2" fill="currentColor" fill-opacity=".1" stroke="currentColor" stroke-width="2"/>'},
|
||
],
|
||
sw:'0', fill:'none', color:'currentColor'
|
||
},
|
||
];
|
||
|
||
var sel = window._iconSet || 'a';
|
||
|
||
function renderSet(s) {
|
||
var isActive = s.id === sel;
|
||
var bg = isActive ? 'var(--accent)' : 'var(--card)';
|
||
var col = isActive ? '#fff' : 'var(--ink)';
|
||
var mutedCol = isActive ? 'rgba(255,255,255,.6)' : 'var(--muted)';
|
||
return '<div onclick="(function(){window._iconSet=\'' + s.id + '\';refresh()})()"'
|
||
+ ' style="background:' + bg + ';border-radius:12px;padding:14px 12px;cursor:pointer;border:2px solid ' + (isActive?'var(--accent)':'rgba(0,0,0,.06)') + ';margin-bottom:10px">'
|
||
+ '<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:10px">'
|
||
+ '<div>'
|
||
+ '<div style="font-size:13px;font-weight:700;color:' + col + '">' + s.name + '</div>'
|
||
+ '<div style="font-size:11px;color:' + mutedCol + ';margin-top:1px">' + s.desc + '</div>'
|
||
+ '</div>'
|
||
+ (isActive ? '<div style="font-size:11px;font-weight:700;background:rgba(255,255,255,.2);color:#fff;padding:3px 8px;border-radius:20px">✓ Выбрано</div>' : '')
|
||
+ '</div>'
|
||
+ '<div style="display:flex;justify-content:space-around;padding:10px;background:' + (isActive?'rgba(255,255,255,.12)':'rgba(0,0,0,.03)') + ';border-radius:8px">'
|
||
+ s.icons.map(function(ic) {
|
||
return '<div style="display:flex;flex-direction:column;align-items:center;gap:4px">'
|
||
+ '<svg width="22" height="22" viewBox="0 0 24 24" fill="' + s.fill + '" stroke="' + s.color + '" stroke-width="' + s.sw + '" stroke-linecap="round" stroke-linejoin="round" style="color:' + col + '">' + ic.svg + '</svg>'
|
||
+ '<span style="font-size:9px;color:' + mutedCol + '">' + ic.label + '</span>'
|
||
+ '</div>';
|
||
}).join('')
|
||
+ '</div>'
|
||
+ '</div>';
|
||
}
|
||
|
||
return `<div class="page">
|
||
<div style="display:flex;align-items:center;gap:10px;padding:12px 16px;background:var(--card);border-bottom:1px solid rgba(0,0,0,.08)">
|
||
<div onclick="navigate('home')" style="display:flex;align-items:center;gap:6px;cursor:pointer;color:var(--accent);font-size:13px;font-weight:600">
|
||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="15 18 9 12 15 6"/></svg>
|
||
Назад
|
||
</div>
|
||
<div style="flex:1;font-size:14px;font-weight:700;color:var(--ink)">Стиль пиктограмм</div>
|
||
</div>
|
||
<div style="padding:14px 16px 100px">
|
||
<div style="font-size:12px;color:var(--muted);margin-bottom:12px">Выберите стиль иконок для всего приложения. Нажмите — увидите как выглядит в навбаре.</div>
|
||
${sets.map(renderSet).join('')}
|
||
<div style="background:rgba(16,185,129,.08);border:1px solid rgba(16,185,129,.2);border-radius:10px;padding:10px 12px;margin-top:4px">
|
||
<div style="font-size:12px;color:#065f46;font-weight:600">После выбора →</div>
|
||
<div style="font-size:11px;color:#065f46;margin-top:3px">Применим выбранный стиль ко всем иконкам навбара и экранов одним обновлением</div>
|
||
</div>
|
||
</div>
|
||
${navBar('profile')}
|
||
</div>`;
|
||
}
|
||
|
||
// ── SCREEN: PROFILE ───────────────────────────────────────────
|
||
function screenSettings() {
|
||
var tab = window._settingsTab || 'rates';
|
||
|
||
// State — глобальные ставки
|
||
if (window._sRate1 === undefined) window._sRate1 = 2500;
|
||
if (window._sRateN === undefined) window._sRateN = 1000;
|
||
if (window._sKmRate === undefined) window._sKmRate = 4;
|
||
if (window._sMinKm === undefined) window._sMinKm = 5;
|
||
if (window._sSaved === undefined) window._sSaved = false;
|
||
|
||
function tabBtn(id, lbl) {
|
||
var active = tab === id;
|
||
return '<div onclick="(function(){window._settingsTab=\'' + id + '\';window._sSaved=false;'
|
||
+ 'document.getElementById(\'screen\').innerHTML=renderScreen(\'settings\')})()"'
|
||
+ ' style="padding:10px 14px;font-size:12px;font-weight:' + (active?700:500) + ';color:'
|
||
+ (active?'var(--accent)':'var(--muted)') + ';border-bottom:' + (active?'2px solid var(--accent)':'2px solid transparent')
|
||
+ ';cursor:pointer;margin-bottom:-1px;white-space:nowrap">' + lbl + '</div>';
|
||
}
|
||
|
||
function rateRow(label, hint, stateKey, step) {
|
||
return '<div style="display:flex;align-items:center;gap:10px;padding:12px 0;border-bottom:1px solid rgba(0,0,0,.05)">'
|
||
+ '<div style="flex:1">'
|
||
+ '<div style="font-size:14px;font-weight:600;color:var(--ink)">' + label + '</div>'
|
||
+ (hint ? '<div style="font-size:11px;color:var(--muted);margin-top:2px">' + hint + '</div>' : '')
|
||
+ '</div>'
|
||
+ '<div style="display:flex;align-items:center;gap:6px">'
|
||
+ '<input type="number" value="' + window[stateKey] + '" min="0" step="' + (step||100) + '"'
|
||
+ ' onchange="window.' + stateKey + '=+this.value;window._sSaved=false"'
|
||
+ ' style="width:76px;border:1.5px solid rgba(0,0,0,.12);border-radius:8px;padding:7px 8px;font-size:15px;font-weight:700;text-align:right;outline:none;color:var(--ink)">'
|
||
+ '<span style="font-size:13px;color:var(--muted)">₽</span>'
|
||
+ '</div>'
|
||
+ '</div>';
|
||
}
|
||
|
||
var ratesTab = '<div class="card">'
|
||
+ '<div style="font-size:11px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:4px">Ставки замера</div>'
|
||
+ '<div style="font-size:11px;color:var(--muted);margin-bottom:14px">Применяются ко всем замерщикам по умолчанию. Индивидуально — в карточке сотрудника.</div>'
|
||
+ rateRow('1-е помещение', 'Базовая ставка за выезд + первый замер', '_sRate1', 100)
|
||
+ rateRow('Каждое следующее', 'Доплата за каждое доп. помещение', '_sRateN', 100)
|
||
+ '<div style="margin-top:14px;background:rgba(99,102,241,.06);border-radius:10px;padding:10px 12px">'
|
||
+ '<div style="font-size:11px;font-weight:700;color:var(--accent);margin-bottom:6px">Пример расчёта</div>'
|
||
+ '<div style="font-size:12px;color:var(--muted);line-height:1.7">'
|
||
+ '1 помещение → <b style="color:var(--ink)">2 500 ₽</b><br>'
|
||
+ '2 помещения → <b style="color:var(--ink)">3 500 ₽</b><br>'
|
||
+ '3 помещения → <b style="color:var(--ink)">4 500 ₽</b>'
|
||
+ '</div>'
|
||
+ '</div>'
|
||
+ '</div>';
|
||
|
||
function kmRateRow(label, hint, stateKey, unit, step) {
|
||
return '<div style="display:flex;align-items:center;gap:10px;padding:12px 0;border-bottom:1px solid rgba(0,0,0,.05)">'
|
||
+ '<div style="flex:1">'
|
||
+ '<div style="font-size:14px;font-weight:600;color:var(--ink)">' + label + '</div>'
|
||
+ (hint ? '<div style="font-size:11px;color:var(--muted);margin-top:2px">' + hint + '</div>' : '')
|
||
+ '</div>'
|
||
+ '<div style="display:flex;align-items:center;gap:6px">'
|
||
+ '<input type="number" value="' + window[stateKey] + '" min="0" step="' + (step||1) + '"'
|
||
+ ' onchange="window.' + stateKey + '=+this.value;window._sSaved=false"'
|
||
+ ' style="width:76px;border:1.5px solid rgba(0,0,0,.12);border-radius:8px;padding:7px 8px;font-size:15px;font-weight:700;text-align:right;outline:none;color:var(--ink)">'
|
||
+ '<span style="font-size:13px;color:var(--muted)">' + unit + '</span>'
|
||
+ '</div>'
|
||
+ '</div>';
|
||
}
|
||
|
||
var routeTab = '<div class="card">'
|
||
+ '<div style="font-size:11px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:4px">Компенсация маршрута</div>'
|
||
+ '<div style="font-size:11px;color:var(--muted);margin-bottom:14px">Начисляется сотруднику за каждый км от дома до объекта и обратно.</div>'
|
||
+ kmRateRow('Ставка за км', 'Рублей за каждый километр маршрута', '_sKmRate', '₽/км', 1)
|
||
+ kmRateRow('Мин. расстояние', 'Компенсация не начисляется если ближе', '_sMinKm', 'км', 1)
|
||
+ '<div style="margin-top:14px;background:rgba(16,185,129,.06);border-radius:10px;padding:10px 12px">'
|
||
+ '<div style="font-size:11px;font-weight:700;color:#065F46;margin-bottom:6px">Пример расчёта</div>'
|
||
+ '<div style="font-size:12px;color:var(--muted);line-height:1.7">'
|
||
+ 'Маршрут 18 км → компенсация <b style="color:var(--ink)">72 ₽</b><br>'
|
||
+ 'Маршрут 4 км → <b style="color:var(--ink)">0 ₽</b> (меньше минимума)'
|
||
+ '</div>'
|
||
+ '</div>'
|
||
+ '</div>';
|
||
|
||
|
||
// ── ROOMS TAB DATA ──────────────────────────────────────────
|
||
if (!window._sRoomList) window._sRoomList = [
|
||
'Кухня','Гостиная','Спальня','Прихожая','Коридор',
|
||
'Ванная','Санузел','Балкон / Лоджия','Кабинет','Детская','Гардероб','Столовая'
|
||
];
|
||
// Pending custom rooms from field workers
|
||
if (!window._sPendingRooms) window._sPendingRooms = [
|
||
{name:'Серверная', spec:'Кириллов А.В.', count:3, id:'r1', status:null},
|
||
{name:'Кладовая', spec:'Петров С.И.', count:1, id:'r2', status:null},
|
||
{name:'Тамбур', spec:'Кириллов А.В.', count:2, id:'r3', status:null},
|
||
];
|
||
|
||
var roomsTab = (function(){
|
||
var masterList = '<div class="card">'
|
||
+ '<div style="font-size:11px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:4px">Справочник помещений</div>'
|
||
+ '<div style="font-size:11px;color:var(--muted);margin-bottom:14px">Отображается в калькуляторе замерщика на объекте</div>'
|
||
+ window._sRoomList.map(function(r, i){
|
||
return '<div style="display:flex;align-items:center;gap:10px;padding:9px 0;border-bottom:1px solid rgba(0,0,0,.05)">'
|
||
+ '<span style="font-size:14px">☰</span>'
|
||
+ '<span style="flex:1;font-size:13px;font-weight:600;color:var(--ink)">' + r + '</span>'
|
||
+ '<button onclick="(function(){window._sRoomList.splice(' + i + ',1);window._sSaved=false;document.getElementById(\'screen\').innerHTML=renderScreen(\'settings\')})()"'
|
||
+ ' style="background:none;border:none;color:var(--muted);font-size:16px;cursor:pointer;padding:0 4px;line-height:1">✕</button>'
|
||
+ '</div>';
|
||
}).join('')
|
||
+ '<div style="display:flex;gap:8px;margin-top:12px">'
|
||
+ '<input id="new-room-input" type="text" placeholder="Новое помещение..."'
|
||
+ ' style="flex:1;border:1.5px solid rgba(0,0,0,.12);border-radius:8px;padding:8px 10px;font-size:13px;outline:none">'
|
||
+ '<button onclick="(function(){var v=document.getElementById(\'new-room-input\').value.trim();if(!v)return;window._sRoomList.push(v);window._sSaved=false;document.getElementById(\'screen\').innerHTML=renderScreen(\'settings\')})()"'
|
||
+ ' style="background:var(--accent);color:#fff;border:none;border-radius:8px;padding:8px 14px;font-size:13px;font-weight:700;cursor:pointer">+ Добавить</button>'
|
||
+ '</div>'
|
||
+ '</div>';
|
||
|
||
var pending = window._sPendingRooms.filter(function(r){ return r.status === null; });
|
||
var reviewed = window._sPendingRooms.filter(function(r){ return r.status !== null; });
|
||
|
||
var pendingBlock = '<div class="card" style="border:1.5px solid rgba(99,102,241,.2);background:rgba(99,102,241,.03)">'
|
||
+ '<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:4px">'
|
||
+ '<div style="font-size:11px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em">На рассмотрении</div>'
|
||
+ (pending.length ? '<span style="font-size:11px;background:var(--accent);color:#fff;border-radius:20px;padding:2px 8px;font-weight:700">' + pending.length + '</span>' : '')
|
||
+ '</div>'
|
||
+ '<div style="font-size:11px;color:var(--muted);margin-bottom:14px">Специалисты добавили в поле на объекте. Принять — попадёт в справочник всем.</div>'
|
||
+ (pending.length === 0
|
||
? '<div style="text-align:center;padding:16px 0;font-size:13px;color:var(--muted)">Нет новых предложений</div>'
|
||
: pending.map(function(r){
|
||
return '<div style="padding:10px 0;border-bottom:1px solid rgba(0,0,0,.05)">'
|
||
+ '<div style="display:flex;align-items:flex-start;gap:8px;margin-bottom:8px">'
|
||
+ '<div style="flex:1">'
|
||
+ '<div style="font-size:14px;font-weight:700;color:var(--ink)">«' + r.name + '»</div>'
|
||
+ '<div style="font-size:11px;color:var(--muted);margin-top:2px">' + r.spec + ' · использовал ' + r.count + ' ' + (r.count===1?'раз':'раза') + '</div>'
|
||
+ '</div>'
|
||
+ '</div>'
|
||
+ '<div style="display:flex;gap:8px">'
|
||
+ '<button onclick="(function(){var p=window._sPendingRooms.find(function(x){return x.id===\'' + r.id + '\'});p.status=\'accepted\';window._sRoomList.push(\'' + r.name + '\');window._sSaved=false;document.getElementById(\'screen\').innerHTML=renderScreen(\'settings\')})()"'
|
||
+ ' style="flex:1;padding:8px;background:#DCFCE7;color:#15803D;border:1.5px solid rgba(34,197,94,.3);border-radius:9px;font-size:12px;font-weight:700;cursor:pointer">✓ В справочник</button>'
|
||
+ '<button onclick="(function(){var p=window._sPendingRooms.find(function(x){return x.id===\'' + r.id + '\'});p.status=\'local\';document.getElementById(\'screen\').innerHTML=renderScreen(\'settings\')})()"'
|
||
+ ' style="flex:1;padding:8px;background:rgba(0,0,0,.04);color:var(--muted);border:1.5px solid rgba(0,0,0,.1);border-radius:9px;font-size:12px;font-weight:700;cursor:pointer">≡ Локальное</button>'
|
||
+ '</div>'
|
||
+ '</div>';
|
||
}).join(''))
|
||
+ (reviewed.length
|
||
? '<div style="margin-top:10px;padding-top:10px;border-top:1px solid rgba(0,0,0,.06)">'
|
||
+ '<div style="font-size:11px;color:var(--muted);margin-bottom:6px">Обработано</div>'
|
||
+ reviewed.map(function(r){
|
||
return '<div style="display:flex;align-items:center;gap:8px;padding:5px 0;font-size:12px;color:var(--muted)">'
|
||
+ '<span>' + (r.status==='accepted'?'✓':'≡') + '</span>'
|
||
+ '<span style="flex:1">«' + r.name + '»</span>'
|
||
+ '<span style="font-size:11px">' + (r.status==='accepted'?'В справочнике':'Локальное') + '</span>'
|
||
+ '</div>';
|
||
}).join('')
|
||
+ '</div>'
|
||
: '')
|
||
+ '</div>';
|
||
|
||
return masterList + pendingBlock;
|
||
})();
|
||
|
||
|
||
// ── CONTENT TAB ──────────────────────────────────────────────
|
||
var ROOMS_CONTENT = [
|
||
{name:'Кухня', icon:'🍳', params:9, tips:3, status:'published', updated:'23.05.2026', source:'ЧЕКЛИСТ'},
|
||
{name:'Гостиная', icon:'🛋️', params:7, tips:3, status:'published', updated:'23.05.2026', source:'ЧЕКЛИСТ'},
|
||
{name:'Спальня', icon:'🛏️', params:7, tips:3, status:'published', updated:'23.05.2026', source:'ЧЕКЛИСТ'},
|
||
{name:'Прихожая', icon:'🚪', params:8, tips:3, status:'published', updated:'23.05.2026', source:'ЧЕКЛИСТ'},
|
||
{name:'Ванная', icon:'🚿', params:6, tips:3, status:'published', updated:'23.05.2026', source:'ЧЕКЛИСТ'},
|
||
{name:'Гардероб', icon:'👔', params:6, tips:3, status:'published', updated:'23.05.2026', source:'ЧЕКЛИСТ'},
|
||
{name:'Балкон / Лоджия', icon:'🌿', params:6, tips:3, status:'published', updated:'23.05.2026', source:'ЧЕКЛИСТ'},
|
||
{name:'Кабинет', icon:'💼', params:7, tips:3, status:'published', updated:'23.05.2026', source:'ЧЕКЛИСТ'},
|
||
{name:'Детская', icon:'🧸', params:7, tips:3, status:'published', updated:'23.05.2026', source:'ЧЕКЛИСТ'},
|
||
{name:'Санузел', icon:'🪠', params:6, tips:3, status:'published', updated:'23.05.2026', source:'ЧЕКЛИСТ'},
|
||
{name:'Столовая', icon:'🍽️', params:6, tips:3, status:'published', updated:'23.05.2026', source:'ЧЕКЛИСТ'},
|
||
];
|
||
|
||
var contentTab = '<div class="card" style="padding:0;overflow:hidden">'
|
||
+ '<div style="padding:12px 14px;border-bottom:1px solid rgba(0,0,0,.06);display:flex;align-items:center;justify-content:space-between">'
|
||
+ '<div>'
|
||
+ '<div style="font-size:13px;font-weight:700;color:var(--ink)">Справочник параметров</div>'
|
||
+ '<div style="font-size:11px;color:var(--muted);margin-top:2px">Рита · v0.1 · источник: @ergonomiks</div>'
|
||
+ '</div>'
|
||
+ '<span style="font-size:11px;background:#DCFCE7;color:#15803D;border-radius:20px;padding:3px 10px;font-weight:700">'
|
||
+ ROOMS_CONTENT.filter(function(r){return r.status==='published';}).length + ' из ' + ROOMS_CONTENT.length + ' готово</span>'
|
||
+ '</div>'
|
||
+ ROOMS_CONTENT.map(function(r){
|
||
var pub = r.status === 'published';
|
||
return '<div style="display:flex;align-items:center;gap:10px;padding:11px 14px;border-bottom:1px solid rgba(0,0,0,.04)">'
|
||
+ '<span style="font-size:20px;flex-shrink:0">' + r.icon + '</span>'
|
||
+ '<div style="flex:1">'
|
||
+ '<div style="font-size:13px;font-weight:' + (pub?'600':'400') + ';color:' + (pub?'var(--ink)':'var(--muted)') + '">' + r.name + '</div>'
|
||
+ (pub
|
||
? '<div style="font-size:11px;color:var(--muted);margin-top:2px">' + r.params + ' параметров · ' + r.tips + ' совета · ' + r.updated + '</div>'
|
||
: '<div style="font-size:11px;color:var(--muted);margin-top:2px">Не заполнено</div>')
|
||
+ '</div>'
|
||
+ '<span style="font-size:11px;font-weight:700;padding:2px 8px;border-radius:6px;flex-shrink:0;background:' + (pub?'#DCFCE7':'rgba(0,0,0,.05)') + ';color:' + (pub?'#15803D':'var(--muted)') + '">'
|
||
+ (pub ? '✓ Опубл.' : 'Черновик') + '</span>'
|
||
+ '</div>';
|
||
}).join('')
|
||
+ '</div>'
|
||
+ '<div style="background:rgba(99,102,241,.04);border:1.5px solid rgba(99,102,241,.15);border-radius:14px;padding:12px 14px;margin-top:0">'
|
||
+ '<div style="font-size:12px;font-weight:700;color:var(--accent);margin-bottom:4px">📡 Мониторинг @ergonomiks</div>'
|
||
+ '<div style="font-size:12px;color:var(--muted);line-height:1.6">Последняя проверка: сегодня · v1.0 готова: 11/11 помещений<br>Источник: ЧЕКЛИСТ_ЗАМЕРА.md · Рита</div>'
|
||
+ '</div>';
|
||
|
||
var saveBtn = '<button onclick="(function(){window._sSaved=true;'
|
||
+ 'document.getElementById(\'screen\').innerHTML=renderScreen(\'settings\')})()"'
|
||
+ ' style="width:100%;padding:14px;background:var(--accent);color:#fff;border:none;border-radius:12px;font-size:15px;font-weight:700;cursor:pointer;margin-bottom:10px">'
|
||
+ (window._sSaved ? '✓ Сохранено' : 'Сохранить изменения') + '</button>';
|
||
|
||
return '<div class="page">'
|
||
+ '<div class="page-header">'
|
||
+ '<button class="back-btn" onclick="navigate(\'profile\')">'
|
||
+ '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="15 18 9 12 15 6"/></svg>'
|
||
+ '</button>'
|
||
+ '<h2>Настройки</h2>'
|
||
+ '</div>'
|
||
+ '<div style="display:flex;gap:0;padding:0 16px;background:var(--card);border-bottom:1px solid rgba(0,0,0,.07)">'
|
||
+ tabBtn('rates', 'Ставки замера')
|
||
+ tabBtn('route', 'Маршрут')
|
||
+ tabBtn('rooms', 'Помещения')
|
||
+ tabBtn('content', 'Контент')
|
||
+ '</div>'
|
||
+ '<div style="padding:12px 16px 90px">'
|
||
+ (tab === 'rates' ? ratesTab : tab === 'route' ? routeTab : tab === 'rooms' ? roomsTab : contentTab)
|
||
+ saveBtn
|
||
+ (window._sSaved
|
||
? '<div style="text-align:center;font-size:12px;color:var(--success)">✓ Ставки сохранены и применены ко всем новым сотрудникам</div>'
|
||
: '')
|
||
+ '</div>'
|
||
+ navBar('profile')
|
||
+ '</div>';
|
||
}
|
||
|
||
function screenProfile() {
|
||
return `<div class="page">
|
||
<div class="page-header"><h2>Профиль</h2></div>
|
||
<div style="padding:16px">
|
||
|
||
<!-- Avatar -->
|
||
<div class="card" style="text-align:center;padding:28px 16px">
|
||
<div class="avatar lg" style="margin:0 auto 12px;background:var(--accent)">РВ</div>
|
||
<div style="font-size:20px;font-weight:800;color:var(--ink)">Васильев Руслан Геннадьевич</div>
|
||
<div style="margin-top:6px;display:flex;align-items:center;justify-content:center;gap:6px">
|
||
<span class="badge blue">Директор по сервису</span>
|
||
</div>
|
||
<div style="font-size:13px;color:var(--muted);margin-top:8px">@wasrusgen1 CRM</div>
|
||
</div>
|
||
|
||
<!-- Company card -->
|
||
<div class="card">
|
||
<div style="font-size:11px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:12px">Компания</div>
|
||
${[
|
||
{lbl:'Организация',val:'ИП Васильев Р.Г.'},
|
||
{lbl:'ИНН',val:'781234567890'},
|
||
{lbl:'Телефон',val:'+7 921 000-00-00'},
|
||
{lbl:'Email',val:'vasrusgen@gmail.com'},
|
||
].map(r => `
|
||
<div class="row sb" style="margin-bottom:8px">
|
||
<span style="font-size:13px;color:var(--muted)">${r.lbl}</span>
|
||
<span style="font-size:13px;font-weight:600;color:var(--ink)">${r.val}</span>
|
||
</div>
|
||
<div class="divider" style="margin:4px 0"></div>
|
||
`).join('')}
|
||
</div>
|
||
|
||
<!-- Подпись / Факсимиле -->
|
||
<div class="card" style="margin-bottom:12px">
|
||
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:14px">
|
||
<div>
|
||
<div style="font-size:11px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em">Подпись</div>
|
||
<div style="font-size:11px;color:var(--muted);margin-top:2px">Используется в актах и прейскуранте</div>
|
||
</div>
|
||
<div style="display:flex;gap:6px">
|
||
<button onclick="alert('Загрузить изображение подписи (PNG/JPG)')" style="background:rgba(0,0,0,.05);border:none;border-radius:8px;padding:6px 10px;font-size:12px;font-weight:600;color:var(--muted);cursor:pointer">↑ Загрузить</button>
|
||
<button onclick="alert('Нарисовать подпись пальцем')" style="background:var(--accent);border:none;border-radius:8px;padding:6px 10px;font-size:12px;font-weight:600;color:#fff;cursor:pointer">✏ Нарисовать</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Поле подписи — как в акте -->
|
||
<div style="border:1.5px dashed rgba(0,0,0,.15);border-radius:10px;padding:16px 20px;background:rgba(0,0,0,.02);min-height:80px;display:flex;flex-direction:column;justify-content:space-between">
|
||
<!-- SVG факсимиле (заглушка) -->
|
||
<div style="display:flex;justify-content:center;align-items:center;flex:1;padding:4px 0 10px">
|
||
<svg width="180" height="48" viewBox="0 0 180 48" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||
<path d="M8 36 C16 14, 28 42, 36 24 C42 10, 46 34, 56 28 C66 22, 62 40, 72 30 C80 22, 84 44, 96 28 C106 14, 110 38, 122 26 C132 16, 138 36, 150 28 C158 22, 164 38, 172 30" stroke="#1e3a5f" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="none"/>
|
||
<path d="M20 42 C28 36, 38 44, 50 40" stroke="#1e3a5f" stroke-width="1.5" stroke-linecap="round" fill="none"/>
|
||
<path d="M68 42 C76 36, 88 44, 98 40 C108 36, 112 42, 120 40" stroke="#1e3a5f" stroke-width="1.5" stroke-linecap="round" fill="none"/>
|
||
</svg>
|
||
</div>
|
||
<!-- Линия подписи как в акте -->
|
||
<div style="border-top:1px solid rgba(0,0,0,.2);padding-top:6px;display:flex;align-items:center;justify-content:space-between">
|
||
<span style="font-size:10px;color:var(--muted)">Васильев Р.Г.</span>
|
||
<span style="font-size:10px;color:var(--muted)">ИП · Директор по сервису</span>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Статус -->
|
||
<div style="display:flex;align-items:center;gap:6px;margin-top:10px">
|
||
<div style="width:7px;height:7px;border-radius:50%;background:#10b981;flex-shrink:0"></div>
|
||
<span style="font-size:11px;color:#065f46">Подпись сохранена · обновлена 15.01.2025</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div onclick="navigate('icon_showcase')" style="display:flex;align-items:center;justify-content:space-between;background:var(--card);border:1.5px solid rgba(0,0,0,.08);border-radius:12px;padding:13px 16px;cursor:pointer;margin-bottom:12px">
|
||
<div style="display:flex;align-items:center;gap:10px">
|
||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="var(--accent)" stroke-width="2"><rect x="3" y="3" width="7" height="7" rx="1"/><rect x="14" y="3" width="7" height="7" rx="1"/><rect x="3" y="14" width="7" height="7" rx="1"/><rect x="14" y="14" width="7" height="7" rx="1"/></svg>
|
||
<div>
|
||
<div style="font-size:13px;font-weight:600;color:var(--ink)">Стиль пиктограмм</div>
|
||
<div style="font-size:11px;color:var(--muted)">Выбрать стиль иконок приложения</div>
|
||
</div>
|
||
</div>
|
||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="var(--muted)" stroke-width="2"><polyline points="9 18 15 12 9 6"/></svg>
|
||
</div>
|
||
<button onclick="navigate('settings')" style="width:100%;background:rgba(99,102,241,.08);color:var(--accent);border:1.5px solid rgba(99,102,241,.2);border-radius:12px;padding:13px;font-size:14px;font-weight:700;cursor:pointer;margin-bottom:8px">⚙️ Настройки и ставки</button>
|
||
<button style="width:100%;background:transparent;color:var(--danger);border:1.5px solid var(--danger);border-radius:12px;padding:13px;font-size:15px;font-weight:600;cursor:pointer">Выйти из аккаунта</button>
|
||
|
||
<div style="text-align:center;padding:20px 0 4px;border-top:1px solid rgba(0,0,0,.06);margin-top:16px">
|
||
<div style="display:flex;align-items:center;justify-content:center;gap:8px;margin-bottom:6px">
|
||
<div class="w1-slot" data-color="var(--muted)" data-width="110" data-height="17"></div>
|
||
<span style="color:var(--muted);font-size:13px;font-weight:300">|</span>
|
||
<span style="font-family:'Montserrat',sans-serif;font-weight:700;font-size:13px;color:var(--muted);letter-spacing:2px">CRM</span>
|
||
</div>
|
||
<div style="font-size:11px;color:var(--muted);opacity:.6">v0.1 · Прототип</div>
|
||
</div>
|
||
</div>
|
||
${navBar('profile')}
|
||
</div>`;
|
||
}
|
||
|
||
// ── HANDLERS ──────────────────────────────────────────────────
|
||
function attachHandlers() {
|
||
// Accordion
|
||
window.toggleAccordion = function(i) {
|
||
const body = document.getElementById('acc-body-' + i);
|
||
const icon = document.getElementById('acc-icon-' + i);
|
||
if (!body) return;
|
||
const open = body.style.display !== 'none';
|
||
body.style.display = open ? 'none' : 'block';
|
||
if (icon) icon.style.transform = open ? '' : 'rotate(180deg)';
|
||
};
|
||
|
||
// Chip filter
|
||
document.querySelectorAll('.chip').forEach(chip => {
|
||
chip.addEventListener('click', function() {
|
||
const row = this.closest('.chip-row');
|
||
if (!row) return;
|
||
row.querySelectorAll('.chip').forEach(c => c.classList.remove('active'));
|
||
this.classList.add('active');
|
||
});
|
||
});
|
||
}
|
||
|
||
// Initial render
|
||
navigate('home');
|
||
</script>
|
||
<script src="wmark.js"></script>
|
||
</body>
|
||
</html>
|
||
|
||
|