mirror of
https://github.com/wasrusgen/wasrusgen1-crm.git
synced 2026-06-03 20:44:46 +00:00
1797 lines
111 KiB
HTML
1797 lines
111 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}
|
||
|
||
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}
|
||
|
||
#controls{display:flex;align-items:center;gap:12px;margin-bottom:16px;flex-wrap:wrap;justify-content:center;width:100%;max-width:700px}
|
||
#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)}
|
||
#roleSelect{padding:8px 12px;border-radius:10px;border:none;background:#fff;font-size:13px;color:#333;cursor:pointer;min-width:220px;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}
|
||
|
||
#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{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{width:22px;height:22px;color:var(--muted)}
|
||
.nav-item span{font-size:10px;color:var(--muted);font-weight:500}
|
||
.nav-item.active svg{color:var(--accent)}
|
||
.nav-item.active span{color:var(--accent)}
|
||
|
||
.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}
|
||
|
||
.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:4px solid var(--warn)}
|
||
.card.success-border{border-left:4px solid var(--success)}
|
||
.card.accent-border{border-left:4px solid var(--accent)}
|
||
.card.danger-border{border-left:4px solid var(--danger)}
|
||
|
||
.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-primary:disabled{opacity:.45;cursor:default}
|
||
.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-sm{padding:8px 14px;font-size:13px;font-weight:600;border-radius:9px;cursor:pointer;border:none;background:var(--accent);color:#fff}
|
||
|
||
.badge{display:inline-flex;align-items:center;padding:4px 10px;border-radius:20px;font-size:12px;font-weight:600;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.accent{background:var(--accent);color:#fff}
|
||
.badge.teal{background:#CCFBF1;color:#0F766E}
|
||
|
||
.chip-row{display:flex;flex-wrap:wrap;gap:6px;padding:0 16px;margin-bottom:8px}
|
||
.chip{padding:5px 10px;border-radius:20px;font-size:12px;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)}
|
||
|
||
.progress-bar{height:6px;background:#E2E8F0;border-radius:3px;overflow:hidden;flex:1}
|
||
.progress-fill{height:100%;background:var(--accent);border-radius:3px}
|
||
.progress-fill.green{background:var(--success)}
|
||
|
||
.divider{height:1px;background:rgba(0,0,0,.07);margin:8px 0}
|
||
|
||
.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}
|
||
|
||
.checklist-item{display:flex;align-items:flex-start;gap:12px;padding:12px 0;border-bottom:1px solid rgba(0,0,0,.06)}
|
||
.checklist-item:last-child{border-bottom:none}
|
||
.check-box{width:24px;height:24px;border-radius:6px;border:2px solid #CBD5E1;display:flex;align-items:center;justify-content:center;flex-shrink:0;cursor:pointer;transition:all .2s;margin-top:1px}
|
||
.check-box.done{background:var(--success);border-color:var(--success)}
|
||
.check-box.done svg{display:block}
|
||
.check-box svg{display:none;color:#fff;width:14px;height:14px}
|
||
.check-text{flex:1}
|
||
.check-text h4{font-size:14px;font-weight:600;color:var(--ink);line-height:1.3}
|
||
.check-text.done h4{text-decoration:line-through;color:var(--muted)}
|
||
|
||
.photo-slot{width:80px;height:80px;border-radius:12px;background:#F1F5F9;border:2px dashed #CBD5E1;display:flex;align-items:center;justify-content:center;flex-shrink:0;cursor:pointer}
|
||
.photo-slot svg{color:var(--muted);width:24px;height:24px}
|
||
.photo-slot.filled{background:linear-gradient(135deg,#E2E8F0,#CBD5E1);border:none;font-size:28px}
|
||
|
||
.info-row{display:flex;gap:8px;padding:10px 0;border-bottom:1px solid rgba(0,0,0,.06)}
|
||
.info-row:last-child{border-bottom:none}
|
||
.info-label{font-size:12px;color:var(--muted);font-weight:500;width:130px;flex-shrink:0}
|
||
.info-val{font-size:13px;font-weight:600;color:var(--ink);flex:1}
|
||
|
||
.earnings-card{background:linear-gradient(135deg,var(--accent),var(--accent2));border-radius:16px;padding:20px;color:#fff;margin-bottom:12px}
|
||
|
||
.stat-table{width:100%;border-collapse:collapse;font-size:13px}
|
||
.stat-table th{font-size:10px;text-transform:uppercase;letter-spacing:.05em;color:var(--muted);font-weight:600;padding:6px 0;border-bottom:1px solid rgba(0,0,0,.08);text-align:left}
|
||
.stat-table td{padding:8px 0;border-bottom:1px solid rgba(0,0,0,.05);color:var(--ink);font-size:13px}
|
||
.stat-table tr:last-child td{border-bottom:none}
|
||
|
||
/* measurer-specific */
|
||
.dim-row{display:flex;align-items:center;gap:10px;padding:10px 0;border-bottom:1px solid rgba(0,0,0,.06)}
|
||
.dim-row:last-child{border-bottom:none}
|
||
.dim-label{font-size:13px;font-weight:500;color:var(--ink);flex:1}
|
||
.dim-input-wrap{display:flex;align-items:center;gap:6px}
|
||
.dim-input{width:72px;padding:8px 10px;border:1.5px solid #E2E8F0;border-radius:10px;font-size:15px;font-weight:600;color:var(--ink);background:var(--card);text-align:right;outline:none}
|
||
.dim-unit{font-size:12px;color:var(--muted);font-weight:500}
|
||
|
||
.step-progress{display:flex;align-items:center;gap:0;padding:16px 16px 0}
|
||
.step-item{display:flex;flex-direction:column;align-items:center;flex:1;position:relative}
|
||
.step-item:not(:last-child)::after{content:'';position:absolute;top:12px;left:50%;width:100%;height:2px;background:#E2E8F0;z-index:0}
|
||
.step-item.done::after{background:var(--success)}
|
||
.step-item.active::after{background:var(--accent)}
|
||
.step-dot{width:24px;height:24px;border-radius:50%;border:2px solid #E2E8F0;background:var(--card);display:flex;align-items:center;justify-content:center;font-size:11px;font-weight:700;color:var(--muted);z-index:1;position:relative}
|
||
.step-item.done .step-dot{background:var(--success);border-color:var(--success);color:#fff}
|
||
.step-item.active .step-dot{background:var(--accent);border-color:var(--accent);color:#fff}
|
||
.step-name{font-size:10px;color:var(--muted);margin-top:4px;font-weight:500;text-align:center}
|
||
.step-item.active .step-name{color:var(--accent);font-weight:700}
|
||
.step-item.done .step-name{color:var(--success)}
|
||
|
||
/* dark overrides */
|
||
body[data-theme="dark"] .chip{background:#1F2937;border-color:#374151;color:#9CA3AF}
|
||
body[data-theme="dark"] .progress-bar{background:#374151}
|
||
body[data-theme="dark"] .page-header{background:var(--card);border-bottom:1px solid rgba(255,255,255,.08)}
|
||
body[data-theme="dark"] .bottom-nav{background:rgba(17,24,39,.95);border-color:rgba(255,255,255,.08)}
|
||
body[data-theme="dark"] #statusBar{background:var(--card)}
|
||
body[data-theme="dark"] .back-btn{background:#374151}
|
||
body[data-theme="dark"] .dim-input{background:#374151;border-color:#4B5563;color:#F9FAFB}
|
||
body[data-theme="dark"] .checklist-item{border-color:#374151}
|
||
body[data-theme="dark"] .photo-slot{background:#374151;border-color:#4B5563}
|
||
body[data-theme="dark"] .info-row{border-color:#374151}
|
||
body[data-theme="dark"] .stat-table th{border-color:#374151}
|
||
body[data-theme="dark"] .stat-table td{border-color:#374151}
|
||
body[data-theme="dark"] .step-item::after{background:#374151}
|
||
body[data-theme="dark"] .step-dot{background:#1F2937;border-color:#374151}
|
||
body[data-theme="dark"] .dim-row{border-color:#374151}
|
||
body[data-theme="dark"] .badge.gray{background:#374151;color:#9CA3AF}
|
||
|
||
@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">
|
||
<div>
|
||
<label>Роль:</label><br>
|
||
<select id="roleSelect" onchange="setRole(this.value)">
|
||
<option value="both">Оба роли (замерщик + сборщик)</option>
|
||
<option value="measurer">Только замерщик</option>
|
||
<option value="assembler">Только сборщик</option>
|
||
</select>
|
||
</div>
|
||
<div>
|
||
<label>Экран:</label><br>
|
||
<select id="screenSelect" onchange="navigate(this.value)">
|
||
<option value="home">Сегодня</option>
|
||
<option value="measurements">Мои замеры</option>
|
||
<option value="measurement_detail">Карточка замера</option>
|
||
<option value="assemblies">Мои сборки</option>
|
||
<option value="assembly_detail">Карточка сборки</option>
|
||
<option value="finances">Финансы</option>
|
||
<option value="profile">Профиль</option>
|
||
</select>
|
||
</div>
|
||
<div>
|
||
<label>Тема:</label><br>
|
||
<div id="themeButtons">
|
||
<button class="theme-btn active" data-t="zov" onclick="setTheme('zov',this)">Синяя</button>
|
||
<button class="theme-btn" data-t="radar" onclick="setTheme('radar',this)">Radar</button>
|
||
<button class="theme-btn" data-t="dark" onclick="setTheme('dark',this)">Dark</button>
|
||
</div>
|
||
</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" fill="currentColor" viewBox="0 0 16 12"><rect x="0" y="3" width="3" height="9" rx="1"/><rect x="4.5" y="2" width="3" height="10" rx="1"/><rect x="9" y="0" width="3" height="12" rx="1"/><rect x="13.5" y="1" width="2.5" height="11" rx="1" opacity=".3"/></svg>
|
||
<svg width="16" height="12" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M5 12.55a11 11 0 0114.08 0M1.42 9a16 16 0 0121.16 0M8.53 16.11a6 6 0 016.95 0M12 20h.01"/></svg>
|
||
<span>100%</span>
|
||
</div>
|
||
</div>
|
||
<div id="screen"></div>
|
||
<div id="bottomNav"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
window._workerRole = 'both';
|
||
var _currentScreen = 'home';
|
||
|
||
var SCREENS = {
|
||
home: 'Сегодня',
|
||
measurements: 'Мои замеры',
|
||
measurement_detail: 'Карточка замера',
|
||
assemblies: 'Мои сборки',
|
||
assembly_detail: 'Карточка сборки',
|
||
calendar: 'Маршрут',
|
||
finances: 'Финансы',
|
||
profile: 'Профиль',
|
||
measure_calc: 'Расчёт стоимости'
|
||
};
|
||
|
||
function setTheme(t, btn) {
|
||
document.body.setAttribute('data-theme', t);
|
||
document.querySelectorAll('.theme-btn').forEach(function(b){ b.classList.remove('active'); });
|
||
btn.classList.add('active');
|
||
}
|
||
|
||
function setRole(r) {
|
||
window._workerRole = r;
|
||
navigate('home');
|
||
}
|
||
|
||
function navigate(id) {
|
||
_currentScreen = id;
|
||
var sel = document.getElementById('screenSelect');
|
||
if (sel) sel.value = id;
|
||
document.getElementById('screen').innerHTML = renderScreen(id);
|
||
document.getElementById('bottomNav').innerHTML = navBar(id);
|
||
document.getElementById('screen').scrollTop = 0;
|
||
}
|
||
|
||
// ── АВТО-ОТЧЁТ: вызывается когда чек-лист закрыт полностью ──────────────────
|
||
function _ergoComplete(total) {
|
||
if (window._reportSent) return;
|
||
window._reportSent = true;
|
||
|
||
// Шапка чеклиста → зелёная
|
||
var lbl = document.getElementById('ergo-progress-label');
|
||
if (lbl) {
|
||
lbl.textContent = 'Кухня · Замер завершён ✓';
|
||
lbl.style.color = '#16A34A';
|
||
lbl.style.fontWeight = '700';
|
||
}
|
||
var hdrWrap = lbl && lbl.closest ? lbl.closest('[onclick]') : null;
|
||
if (hdrWrap) hdrWrap.style.background = 'rgba(34,197,94,.06)';
|
||
|
||
// Toast — «Отчёт отправлен»
|
||
var old = document.getElementById('_report-toast');
|
||
if (old) old.parentNode.removeChild(old);
|
||
|
||
var toast = document.createElement('div');
|
||
toast.id = '_report-toast';
|
||
toast.innerHTML =
|
||
'<div style="font-size:20px;line-height:1">📤</div>'
|
||
+ '<div>'
|
||
+ '<div style="font-weight:700;font-size:14px;color:#fff">Отчёт отправлен менеджеру</div>'
|
||
+ '<div style="font-size:12px;color:rgba(255,255,255,.75);margin-top:2px">Кухня · 9/9 · ' + new Date().toLocaleTimeString('ru',{hour:'2-digit',minute:'2-digit'}) + '</div>'
|
||
+ '</div>'
|
||
+ '<div style="font-size:18px">✓</div>';
|
||
toast.style.cssText = [
|
||
'position:fixed','bottom:90px','left:50%','transform:translateX(-50%) translateY(120px)',
|
||
'background:linear-gradient(135deg,#16A34A,#15803D)',
|
||
'color:#fff','border-radius:16px','padding:14px 18px',
|
||
'display:flex','align-items:center','gap:12px',
|
||
'box-shadow:0 8px 32px rgba(22,163,74,.4)',
|
||
'z-index:9999','min-width:260px','max-width:340px',
|
||
'transition:transform .4s cubic-bezier(.34,1.56,.64,1)'
|
||
].join(';');
|
||
document.body.appendChild(toast);
|
||
|
||
// Slide in
|
||
requestAnimationFrame(function(){ requestAnimationFrame(function(){
|
||
toast.style.transform = 'translateX(-50%) translateY(0)';
|
||
}); });
|
||
|
||
// Slide out после 3.5 сек
|
||
setTimeout(function(){
|
||
toast.style.transform = 'translateX(-50%) translateY(120px)';
|
||
setTimeout(function(){ if (toast.parentNode) toast.parentNode.removeChild(toast); }, 400);
|
||
}, 3500);
|
||
|
||
// Пульс на кнопке «Расчёт и оплата»
|
||
setTimeout(function(){
|
||
var btn = document.querySelector('[onclick*="measure_calc"]');
|
||
if (btn) {
|
||
btn.style.animation = 'none';
|
||
btn.style.boxShadow = '0 0 0 0 rgba(99,102,241,.6)';
|
||
btn.style.animation = '_ergoPulse 1s ease 3';
|
||
}
|
||
}, 400);
|
||
}
|
||
// Keyframes для пульса (добавляются один раз)
|
||
(function(){
|
||
if (!document.getElementById('_ergo-keyframes')) {
|
||
var s = document.createElement('style');
|
||
s.id = '_ergo-keyframes';
|
||
s.textContent = '@keyframes _ergoPulse{0%{box-shadow:0 0 0 0 rgba(99,102,241,.6)}70%{box-shadow:0 0 0 12px rgba(99,102,241,0)}100%{box-shadow:0 0 0 0 rgba(99,102,241,0)}}';
|
||
document.head.appendChild(s);
|
||
}
|
||
})();
|
||
|
||
// ── АВТО-УВЕДОМЛЕНИЕ: фото замера загружено ─────────────────────────────────
|
||
function _photoUploaded(slot) {
|
||
if (window._photoNotified) return;
|
||
window._photoNotified = true;
|
||
|
||
// Слот → показываем «uploaded» состояние
|
||
if (slot) {
|
||
slot.style.background = 'rgba(99,102,241,.12)';
|
||
slot.style.borderColor = 'var(--accent)';
|
||
}
|
||
|
||
// Toast
|
||
var old = document.getElementById('_photo-toast');
|
||
if (old) old.parentNode.removeChild(old);
|
||
|
||
var t = new Date().toLocaleTimeString('ru',{hour:'2-digit',minute:'2-digit'});
|
||
var toast = document.createElement('div');
|
||
toast.id = '_photo-toast';
|
||
toast.innerHTML =
|
||
'<div style="font-size:22px;line-height:1">📐</div>'
|
||
+ '<div style="flex:1">'
|
||
+ '<div style="font-weight:700;font-size:13px;color:#fff;margin-bottom:6px">Замер загружен · Кухня · ' + t + '</div>'
|
||
+ '<div style="display:flex;flex-direction:column;gap:3px">'
|
||
+ '<div style="font-size:12px;color:rgba(255,255,255,.9);display:flex;align-items:center;gap:5px"><span style="font-size:10px;background:rgba(255,255,255,.2);border-radius:4px;padding:1px 5px">✓</span>Менеджер уведомлён</div>'
|
||
+ '<div style="font-size:12px;color:rgba(255,255,255,.9);display:flex;align-items:center;gap:5px"><span style="font-size:10px;background:rgba(255,255,255,.2);border-radius:4px;padding:1px 5px">✓</span>Клиент получил статус в кабинете</div>'
|
||
+ '</div>'
|
||
+ '</div>';
|
||
toast.style.cssText = [
|
||
'position:fixed','bottom:90px','left:50%','transform:translateX(-50%) translateY(120px)',
|
||
'background:linear-gradient(135deg,#6366F1,#4F46E5)',
|
||
'color:#fff','border-radius:16px','padding:14px 18px',
|
||
'display:flex','align-items:flex-start','gap:12px',
|
||
'box-shadow:0 8px 32px rgba(99,102,241,.4)',
|
||
'z-index:9999','min-width:280px','max-width:340px',
|
||
'transition:transform .4s cubic-bezier(.34,1.56,.64,1)'
|
||
].join(';');
|
||
document.body.appendChild(toast);
|
||
|
||
requestAnimationFrame(function(){ requestAnimationFrame(function(){
|
||
toast.style.transform = 'translateX(-50%) translateY(0)';
|
||
}); });
|
||
|
||
setTimeout(function(){
|
||
toast.style.transform = 'translateX(-50%) translateY(120px)';
|
||
setTimeout(function(){ if (toast.parentNode) toast.parentNode.removeChild(toast); }, 400);
|
||
}, 3500);
|
||
}
|
||
|
||
// ── ФИКСАЦИЯ доп. помещения добавленного на объекте ─────────────────────────
|
||
function _showExtraRoomToast(roomName) {
|
||
var old = document.getElementById('_extra-room-toast');
|
||
if (old) old.parentNode.removeChild(old);
|
||
|
||
var toast = document.createElement('div');
|
||
toast.id = '_extra-room-toast';
|
||
toast.innerHTML =
|
||
'<div style="font-size:22px;line-height:1">🏠</div>'
|
||
+ '<div style="flex:1">'
|
||
+ '<div style="font-weight:700;font-size:13px;color:#fff;margin-bottom:4px">Доп. помещение: ' + roomName + '</div>'
|
||
+ '<div style="font-size:11px;color:rgba(255,255,255,.85);line-height:1.5">'
|
||
+ 'Зафиксировано как дополнительный заказ на объекте'
|
||
+ '</div>'
|
||
+ '</div>';
|
||
toast.style.cssText = [
|
||
'position:fixed','bottom:90px','left:50%','transform:translateX(-50%) translateY(120px)',
|
||
'background:linear-gradient(135deg,#B45309,#92400E)',
|
||
'color:#fff','border-radius:16px','padding:14px 18px',
|
||
'display:flex','align-items:flex-start','gap:12px',
|
||
'box-shadow:0 8px 32px rgba(180,83,9,.35)',
|
||
'z-index:9999','min-width:280px','max-width:340px',
|
||
'transition:transform .4s cubic-bezier(.34,1.56,.64,1)'
|
||
].join(';');
|
||
document.body.appendChild(toast);
|
||
|
||
requestAnimationFrame(function(){ requestAnimationFrame(function(){
|
||
toast.style.transform = 'translateX(-50%) translateY(0)';
|
||
}); });
|
||
setTimeout(function(){
|
||
toast.style.transform = 'translateX(-50%) translateY(120px)';
|
||
setTimeout(function(){ if (toast.parentNode) toast.parentNode.removeChild(toast); }, 400);
|
||
}, 4000);
|
||
}
|
||
|
||
function navBar(active) {
|
||
var role = window._workerRole || 'both';
|
||
var items = [];
|
||
items.push({id:'home', label:'Главная', icon:'<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"/>'});
|
||
items.push({id:'calendar', label:'Маршрут', icon:'<rect x="3" y="4" width="18" height="18" rx="2" ry="2"/><line x1="16" y1="2" x2="16" y2="6"/><line x1="8" y1="2" x2="8" y2="6"/><line x1="3" y1="10" x2="21" y2="10"/>'});
|
||
if (role === 'both' || role === 'measurer') {
|
||
items.push({id:'measurements', label:'Замеры', icon:'<polyline points="9 11 12 14 22 4"/><path d="M21 12v7a2 2 0 01-2 2H5a2 2 0 01-2-2V5a2 2 0 012-2h11"/>'});
|
||
}
|
||
if (role === 'both' || role === 'assembler') {
|
||
items.push({id:'assemblies', label:'Сборки', icon:'<path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/><polyline points="10 9 9 9 8 9"/>'});
|
||
}
|
||
items.push({id:'profile', label:'Профиль', icon:'<path d="M20 21v-2a4 4 0 00-4-4H8a4 4 0 00-4 4v2"/><circle cx="12" cy="7" r="4"/>'});
|
||
|
||
var activeId = (active === 'measurement_detail') ? 'measurements' : (active === 'assembly_detail') ? 'assemblies' : active;
|
||
|
||
return '<div class="bottom-nav">' + items.map(function(item){
|
||
var cls = (item.id === activeId) ? 'nav-item active' : 'nav-item';
|
||
return '<div class="' + cls + '" onclick="navigate(\'' + item.id + '\')">' +
|
||
'<svg width="22" height="22" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" viewBox="0 0 24 24">' + item.icon + '</svg>' +
|
||
'<span>' + item.label + '</span>' +
|
||
'</div>';
|
||
}).join('') + '</div>';
|
||
}
|
||
|
||
function renderScreen(id) {
|
||
switch(id) {
|
||
case 'home': return screenHome();
|
||
case 'calendar': return screenCalendar();
|
||
case 'measurements': return screenMeasurements();
|
||
case 'measurement_detail': return screenMeasurementDetail();
|
||
case 'measure_calc': return screenMeasureCalc();
|
||
case 'assemblies': return screenAssemblies();
|
||
case 'assembly_detail': return screenAssemblyDetail();
|
||
case 'finances': return screenFinances();
|
||
case 'profile': return screenProfile();
|
||
default: return screenHome();
|
||
}
|
||
}
|
||
|
||
function roleBadges() {
|
||
var role = window._workerRole;
|
||
var out = '';
|
||
if (role === 'both' || role === 'measurer') out += '<span class="badge blue" style="font-size:11px;padding:3px 8px">Замерщик</span> ';
|
||
if (role === 'both' || role === 'assembler') out += '<span class="badge teal" style="font-size:11px;padding:3px 8px">Сборщик</span>';
|
||
return out;
|
||
}
|
||
|
||
/* ─────────── SCREEN: HOME ─────────── */
|
||
function screenHome() {
|
||
var role = window._workerRole || 'both';
|
||
var measSection = '';
|
||
if (role === 'both' || role === 'measurer') {
|
||
measSection = `
|
||
<div class="section-label">Замеры сегодня</div>
|
||
<div style="padding:0 16px">
|
||
<div class="card accent-border" style="cursor:pointer" onclick="navigate('measurement_detail')">
|
||
<div class="row sb" style="margin-bottom:6px">
|
||
<span style="font-size:13px;font-weight:700;color:var(--ink)">09:00 · Ленина 12, кв.45</span>
|
||
<span class="badge blue">Новый</span>
|
||
</div>
|
||
<div style="font-size:13px;color:var(--muted)">Кухня угловая</div>
|
||
<div style="font-size:12px;color:var(--muted);margin-top:4px;display:flex;align-items:center;gap:4px">
|
||
<svg width="12" height="12" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M21 10c0 7-9 13-9 13S3 17 3 10a9 9 0 0118 0z"/><circle cx="12" cy="10" r="3"/></svg>
|
||
1 этаж, лифт есть
|
||
</div>
|
||
</div>
|
||
<div class="card danger-border" style="cursor:pointer" onclick="navigate('measurement_detail')">
|
||
<div class="row sb" style="margin-bottom:6px">
|
||
<span style="font-size:13px;font-weight:700;color:var(--ink)">14:30 · Мира 7, кв.18</span>
|
||
<span class="badge red">Срочно</span>
|
||
</div>
|
||
<div style="font-size:13px;color:var(--muted)">Гардероб</div>
|
||
<div style="font-size:12px;color:var(--muted);margin-top:4px;display:flex;align-items:center;gap:4px">
|
||
<svg width="12" height="12" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M21 10c0 7-9 13-9 13S3 17 3 10a9 9 0 0118 0z"/><circle cx="12" cy="10" r="3"/></svg>
|
||
3 этаж, без лифта
|
||
</div>
|
||
</div>
|
||
</div>`;
|
||
}
|
||
|
||
var asmSection = '';
|
||
if (role === 'both' || role === 'assembler') {
|
||
asmSection = `
|
||
<div class="section-label">Сборки сегодня</div>
|
||
<div style="padding:0 16px">
|
||
<div class="card warn-border" style="cursor:pointer" onclick="navigate('assembly_detail')">
|
||
<div class="row sb" style="margin-bottom:6px">
|
||
<span style="font-size:13px;font-weight:700;color:var(--ink)">#А-2847 · Черниговская 14</span>
|
||
<span class="badge yellow">В работе</span>
|
||
</div>
|
||
<div style="font-size:13px;color:var(--muted)">Кухня · 11:00</div>
|
||
<div style="display:flex;align-items:center;gap:6px;margin-top:8px">
|
||
<div class="progress-bar" style="flex:1"><div class="progress-fill" style="width:57%"></div></div>
|
||
<span style="font-size:11px;color:var(--muted)">4/7</span>
|
||
</div>
|
||
</div>
|
||
</div>`;
|
||
}
|
||
|
||
return `<div class="page">
|
||
<div style="background:var(--card);padding:20px 16px 16px;border-bottom:1px solid rgba(0,0,0,.06)">
|
||
<div class="row sb" style="margin-bottom:8px">
|
||
<div class="col">
|
||
<div style="font-size:20px;font-weight:800;color:var(--ink)">Кириллов А.В.</div>
|
||
<div style="font-size:13px;color:var(--muted);margin-top:2px">Пн, 23 мая</div>
|
||
</div>
|
||
<div class="avatar lg">КА</div>
|
||
</div>
|
||
<div style="display:flex;gap:6px;flex-wrap:wrap">${roleBadges()}</div>
|
||
</div>
|
||
|
||
${measSection}
|
||
${asmSection}
|
||
|
||
<div class="section-label">Мой заработок · Май</div>
|
||
<div style="padding:0 16px">
|
||
<div class="earnings-card">
|
||
<div style="font-size:13px;opacity:.8;margin-bottom:6px;font-weight:500">Заработано</div>
|
||
<div style="font-size:30px;font-weight:800;letter-spacing:-.5px;margin-bottom:4px">28 400 ₽</div>
|
||
<div style="font-size:12px;opacity:.75;margin-bottom:12px">план 40 000 ₽</div>
|
||
<div class="row" style="gap:10px;margin-bottom:6px">
|
||
<div class="progress-bar" style="background:rgba(255,255,255,.25)">
|
||
<div class="progress-fill" style="width:71%;background:rgba(255,255,255,.9)"></div>
|
||
</div>
|
||
<span style="font-size:12px;font-weight:700;opacity:.9">71%</span>
|
||
</div>
|
||
<div style="font-size:12px;opacity:.75">17 завершено</div>
|
||
</div>
|
||
</div>
|
||
</div>`;
|
||
}
|
||
|
||
/* ─────────── SCREEN: MEASUREMENTS ─────────── */
|
||
function screenMeasurements() {
|
||
var data = [
|
||
{time:'09:00', addr:'Ленина 12, кв.45', type:'Кухня угловая', status:'Новый', statusCls:'blue'},
|
||
{time:'14:30', addr:'Мира 7, кв.18', type:'Гардероб', status:'Срочно', statusCls:'red'},
|
||
{time:'вчера', addr:'Садовая 4, кв.12', type:'Прихожая', status:'Выполнен', statusCls:'green'},
|
||
{time:'21 мая', addr:'Юбилейная 33', type:'Кухня прямая', status:'Выполнен', statusCls:'green'},
|
||
{time:'20 мая', addr:'Пушкина 2, кв.8', type:'Гардероб', status:'В пути', statusCls:'yellow'},
|
||
];
|
||
|
||
return `<div class="page">
|
||
<div class="page-header"><h2>Мои замеры</h2></div>
|
||
<div style="height:12px"></div>
|
||
<div class="chip-row">
|
||
<div class="chip active">Все</div>
|
||
<div class="chip">Сегодня</div>
|
||
<div class="chip">Ожидают</div>
|
||
<div class="chip">Завершены</div>
|
||
</div>
|
||
<div style="padding:4px 16px 0">
|
||
${data.map(function(d){
|
||
return `<div class="card" style="cursor:pointer;margin-bottom:10px" onclick="navigate('measurement_detail')">
|
||
<div class="row sb" style="margin-bottom:6px">
|
||
<span style="font-size:13px;font-weight:700;color:var(--ink)">${d.time} · ${d.addr}</span>
|
||
<span class="badge ${d.statusCls}">${d.status}</span>
|
||
</div>
|
||
<div class="row sb">
|
||
<span style="font-size:13px;color:var(--muted)">${d.type}</span>
|
||
<svg width="16" height="16" fill="none" stroke="var(--muted)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" viewBox="0 0 24 24"><polyline points="9 18 15 12 9 6"/></svg>
|
||
</div>
|
||
</div>`;
|
||
}).join('')}
|
||
</div>
|
||
</div>`;
|
||
}
|
||
|
||
/* ─────────── SCREEN: MEASUREMENT DETAIL ─────────── */
|
||
function screenMeasurementDetail() {
|
||
// ── Состояние помещений ──────────────────────────────────────────────────
|
||
if (!window._rooms) {
|
||
window._rooms = [
|
||
{ name:'Кухня', ergoChecked:{}, photoDone:true, measureDone:true,
|
||
projectFiles:[{name:'Визуализация_кухня.jpg',size:'2.1 МБ',date:'04.05.2026'},{name:'Спецификация.pdf',size:'340 КБ',date:'04.05.2026'}] },
|
||
{ name:'Коридор', ergoChecked:{}, photoDone:false, measureDone:false,
|
||
projectFiles:[] },
|
||
];
|
||
}
|
||
if (window._activeRoom === undefined) window._activeRoom = 0;
|
||
var rooms = window._rooms;
|
||
var ri = window._activeRoom;
|
||
var cur = rooms[ri];
|
||
var roomName = cur.name;
|
||
|
||
// Статус помещения: 'done' | 'active' | 'empty'
|
||
function roomStatus(r) {
|
||
var cnt = Object.values(r.ergoChecked).filter(Boolean).length;
|
||
var ergoTotal = (ROOM_ERGO[r.name] || ROOM_ERGO['Кухня']).params.length;
|
||
if (cnt === ergoTotal && r.measureDone) return 'done';
|
||
if (cnt > 0 || r.photoDone || r.measureDone) return 'active';
|
||
return 'empty';
|
||
}
|
||
|
||
// ── Таб-помещения ────────────────────────────────────────────────────────
|
||
var ROOM_PRESETS = ['Гостиная','Спальня','Прихожая','Ванная','Санузел','Балкон','Детская','Гардероб'];
|
||
var tabsHtml = '<div style="display:flex;gap:6px;overflow-x:auto;padding:0 16px 12px;scrollbar-width:none;-ms-overflow-style:none">';
|
||
rooms.forEach(function(r, idx) {
|
||
var st = roomStatus(r);
|
||
var isActive = idx === ri;
|
||
var badge;
|
||
if (st === 'done') {
|
||
badge = isActive
|
||
? '<span style="font-size:11px;margin-left:5px;opacity:.85">✓</span>'
|
||
: '<span style="font-size:10px;font-weight:700;color:#16A34A;background:rgba(34,197,94,.12);border-radius:10px;padding:1px 6px;margin-left:5px">✓ Готово</span>';
|
||
} else if (st === 'active') {
|
||
badge = '<span style="width:7px;height:7px;border-radius:50%;background:'+(isActive?'rgba(255,255,255,.7)':'#6366F1')+';display:inline-block;margin-right:4px;flex-shrink:0"></span>';
|
||
} else {
|
||
badge = '<span style="width:7px;height:7px;border-radius:50%;background:'+(isActive?'rgba(255,255,255,.4)':'rgba(0,0,0,.18)')+';display:inline-block;margin-right:4px;flex-shrink:0"></span>';
|
||
}
|
||
// Бейдж «на объекте» для доп. помещений
|
||
var extraBadge = r.addedOnSite
|
||
? '<span style="font-size:9px;font-weight:700;background:'+(isActive?'rgba(255,255,255,.2)':'rgba(245,158,11,.15)')+';color:'+(isActive?'#fff':'#B45309')+';border-radius:8px;padding:1px 5px;margin-left:5px;letter-spacing:.02em">+ доп.</span>'
|
||
: '';
|
||
var bg = isActive ? (st==='done' ? '#16A34A' : 'var(--accent)') : 'var(--card)';
|
||
var clr = isActive ? '#fff' : 'var(--ink)';
|
||
var bdr = isActive ? 'none' : (st==='done' ? '1.5px solid rgba(34,197,94,.3)' : r.addedOnSite ? '1.5px solid rgba(245,158,11,.35)' : '1.5px solid rgba(0,0,0,.08)');
|
||
tabsHtml += '<button onclick="(function(){window._activeRoom='+idx+';window._ergoOpen=false;'
|
||
+ 'document.getElementById(\'screen\').innerHTML=renderScreen(\'measurement_detail\')})()"'
|
||
+ ' style="display:flex;align-items:center;white-space:nowrap;padding:7px 13px;border-radius:20px;border:'+bdr+';background:'+bg+';color:'+clr+';font-size:13px;font-weight:600;cursor:pointer;flex-shrink:0">'
|
||
+ (st !== 'done' ? badge : '')
|
||
+ r.name
|
||
+ (st === 'done' ? badge : '')
|
||
+ extraBadge
|
||
+ '</button>';
|
||
});
|
||
// Кнопка «+ Помещение»
|
||
tabsHtml += '<button onclick="(function(){'
|
||
+ 'var presets='+JSON.stringify(ROOM_PRESETS)+';'
|
||
+ 'var used=window._rooms.map(function(r){return r.name});'
|
||
+ 'var avail=presets.filter(function(p){return used.indexOf(p)<0});'
|
||
+ 'var name=avail.length?avail[0]:\'Помещение \'+( window._rooms.length+1);'
|
||
+ 'window._rooms.push({name:name,ergoChecked:{},photoDone:false,measureDone:false,projectFiles:[],addedOnSite:true});'
|
||
+ 'window._activeRoom=window._rooms.length-1;window._ergoOpen=false;'
|
||
+ 'document.getElementById(\'screen\').innerHTML=renderScreen(\'measurement_detail\');'
|
||
+ '_showExtraRoomToast(name);'
|
||
+ '})()"'
|
||
+ ' style="display:flex;align-items:center;gap:4px;white-space:nowrap;padding:7px 13px;border-radius:20px;border:1.5px dashed rgba(99,102,241,.4);background:rgba(99,102,241,.06);color:var(--accent);font-size:13px;font-weight:600;cursor:pointer;flex-shrink:0">'
|
||
+ '+ Помещение</button>';
|
||
tabsHtml += '</div>';
|
||
|
||
// ── Чеклист текущего помещения ───────────────────────────────────────────
|
||
var ergoRoomData = ROOM_ERGO[roomName] || ROOM_ERGO['Кухня'];
|
||
var total = ergoRoomData.params.length;
|
||
var ergoChecked = cur.ergoChecked;
|
||
var doneCnt = Object.values(ergoChecked).filter(Boolean).length;
|
||
|
||
var ergoBody = ergoRoomData.params.map(function(p, i){
|
||
var key = 'p'+i;
|
||
var done = !!ergoChecked[key];
|
||
return '<div onclick="(function(){'
|
||
+ 'var r=window._rooms[window._activeRoom];'
|
||
+ 'if(!r.ergoChecked)r.ergoChecked={};'
|
||
+ 'r.ergoChecked[\''+key+'\']=!r.ergoChecked[\''+key+'\'];'
|
||
+ 'var done=r.ergoChecked[\''+key+'\'];'
|
||
+ 'var row=document.getElementById(\'ep'+i+'\');'
|
||
+ 'var cb=document.getElementById(\'ec'+i+'\');'
|
||
+ 'row.style.opacity=done?\'0.45\':\'1\';'
|
||
+ 'cb.style.background=done?\'var(--accent)\':(\''+( p.critical ? 'rgba(239,68,68,.12)' : 'rgba(0,0,0,.06)' )+'\');'
|
||
+ 'cb.style.borderColor=done?\'var(--accent)\':(\''+( p.critical ? 'var(--danger)' : 'rgba(0,0,0,.2)' )+'\');'
|
||
+ 'cb.innerHTML=done?\'<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="3.5"><polyline points="20 6 9 17 4 12"/></svg>\':\'\';'
|
||
+ 'var cnt=Object.values(r.ergoChecked).filter(Boolean).length;'
|
||
+ 'var lbl=document.getElementById(\'ergo-progress-label\');'
|
||
+ 'if(lbl)lbl.textContent=r.name+\' · \'+cnt+\' из '+total+'\';'
|
||
+ 'if(cnt==='+total+')_ergoComplete('+total+');'
|
||
+ '})()"'
|
||
+ ' id="ep'+i+'" style="display:flex;align-items:flex-start;gap:10px;padding:9px 14px;border-bottom:1px solid rgba(0,0,0,.04);cursor:pointer;transition:.15s;opacity:'+( done?'0.45':'1' )+'">'
|
||
+ '<div id="ec'+i+'" style="width:18px;height:18px;border-radius:5px;border:1.5px solid '+( p.critical?'var(--danger)':'rgba(0,0,0,.2)' )+';background:'+( done?'var(--accent)':( p.critical?'rgba(239,68,68,.12)':'rgba(0,0,0,.06)' ) )+';flex-shrink:0;margin-top:1px;display:flex;align-items:center;justify-content:center">'
|
||
+ (done ? '<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="3.5"><polyline points="20 6 9 17 4 12"/></svg>' : '')
|
||
+ '</div>'
|
||
+ '<div style="flex:1">'
|
||
+ '<div style="font-size:13px;font-weight:'+(p.critical?'700':'500')+';color:'+(p.critical?'var(--danger)':'var(--ink)')+'">'+p.label+'</div>'
|
||
+ '<div style="font-size:11px;color:var(--muted);margin-top:1px">'+p.hint+'</div>'
|
||
+ '</div>'
|
||
+ '</div>';
|
||
}).join('');
|
||
var ergoTips = '<div style="padding:10px 14px;background:rgba(99,102,241,.04)">'
|
||
+ ergoRoomData.tips.map(function(t){ return '<div style="font-size:12px;color:var(--muted);margin-bottom:4px;line-height:1.5">💡 '+t+'</div>'; }).join('')
|
||
+ '<div style="font-size:10px;color:rgba(0,0,0,.25);margin-top:6px">'+ergoRoomData.source+'</div>'
|
||
+ '</div>';
|
||
|
||
// ── Фото замера (measure slot) ───────────────────────────────────────────
|
||
var measureSlot = cur.measureDone
|
||
? '<div style="aspect-ratio:1;border-radius:10px;overflow:hidden;position:relative;background:#E0E7FF;cursor:pointer">'
|
||
+ '<div style="width:100%;height:100%;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:4px">'
|
||
+ '<svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="rgba(99,102,241,.6)" stroke-width="1.5"><rect x="3" y="3" width="18" height="18" rx="2"/><path d="M3 9h18"/><circle cx="7.5" cy="6" r=".5" fill="rgba(99,102,241,.6)"/><path d="M7 15l3-3 2 2 3-4 3 5H7z"/></svg>'
|
||
+ '<span style="font-size:9px;color:rgba(99,102,241,.7);font-weight:600">замер.jpg</span>'
|
||
+ '</div>'
|
||
+ '<div style="position:absolute;top:4px;right:4px;width:16px;height:16px;background:rgba(0,0,0,.25);border-radius:50%;display:flex;align-items:center;justify-content:center">'
|
||
+ '<svg width="8" height="8" viewBox="0 0 10 10" fill="white"><path d="M1 1l8 8M9 1L1 9" stroke="white" stroke-width="1.5"/></svg>'
|
||
+ '</div></div>'
|
||
: '<div class="photo-slot" onclick="(function(){window._rooms[window._activeRoom].measureDone=true;_photoUploaded(this);document.getElementById(\'screen\').innerHTML=renderScreen(\'measurement_detail\')})()">'
|
||
+ '<svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>'
|
||
+ '</div>';
|
||
|
||
return `<div class="page">
|
||
<div class="page-header">
|
||
<button class="back-btn" onclick="navigate('measurements')">
|
||
<svg fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" viewBox="0 0 24 24"><polyline points="15 18 9 12 15 6"/></svg>
|
||
</button>
|
||
<h2>Карточка замера</h2>
|
||
</div>
|
||
|
||
<div class="step-progress" style="padding-bottom:16px">
|
||
<div class="step-item done">
|
||
<div class="step-dot">
|
||
<svg width="12" height="12" fill="none" stroke="currentColor" stroke-width="3" viewBox="0 0 24 24"><polyline points="20 6 9 17 4 12"/></svg>
|
||
</div>
|
||
<div class="step-name">Выезд</div>
|
||
</div>
|
||
<div class="step-item active">
|
||
<div class="step-dot">2</div>
|
||
<div class="step-name">Замер</div>
|
||
</div>
|
||
<div class="step-item">
|
||
<div class="step-dot">3</div>
|
||
<div class="step-name">Отчёт</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div style="padding:0 16px">
|
||
<div class="card accent-border">
|
||
<div style="font-size:15px;font-weight:700;color:var(--ink);margin-bottom:10px">Иванова М.С.</div>
|
||
<div class="info-row">
|
||
<div class="info-label">Телефон</div>
|
||
<div class="info-val" style="color:var(--accent)">+7 (900) 123-45-67</div>
|
||
</div>
|
||
<div class="info-row">
|
||
<div class="info-label">Адрес</div>
|
||
<div class="info-val">Ленина 12, кв.45</div>
|
||
</div>
|
||
<div class="info-row">
|
||
<div class="info-label">Этаж</div>
|
||
<div class="info-val">1 (лифт есть)</div>
|
||
</div>
|
||
<div class="info-row">
|
||
<div class="info-label">Примечания</div>
|
||
<div class="info-val" style="font-weight:400;font-size:13px;color:var(--muted)">Старая кухня демонтирована, место свободно</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Таб-помещения -->
|
||
${tabsHtml}
|
||
|
||
<div style="padding:0 16px">
|
||
<!-- Проект от менеджера — привязан к помещению -->
|
||
${(function(){
|
||
function fileIcon(name) {
|
||
var ext = (name.split('.').pop()||'').toLowerCase();
|
||
if (['jpg','jpeg','png','webp','heic','gif'].indexOf(ext)>=0) return {icon:'🖼️', bg:'linear-gradient(135deg,#e0e7ff,#c7d2fe)'};
|
||
if (ext==='pdf') return {icon:'📄', bg:'linear-gradient(135deg,#fee2e2,#fecaca)'};
|
||
if (['dwg','dxf','skp'].indexOf(ext)>=0) return {icon:'📐', bg:'linear-gradient(135deg,#d1fae5,#a7f3d0)'};
|
||
if (['doc','docx'].indexOf(ext)>=0) return {icon:'📝', bg:'linear-gradient(135deg,#dbeafe,#bfdbfe)'};
|
||
if (['xls','xlsx'].indexOf(ext)>=0) return {icon:'📊', bg:'linear-gradient(135deg,#dcfce7,#bbf7d0)'};
|
||
if (['zip','rar'].indexOf(ext)>=0) return {icon:'🗜️', bg:'linear-gradient(135deg,#fef9c3,#fef08a)'};
|
||
return {icon:'📎', bg:'linear-gradient(135deg,#f3f4f6,#e5e7eb)'};
|
||
}
|
||
var files = cur.projectFiles || [];
|
||
var header = '<div style="font-size:11px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.04em;margin:0 0 8px">Проект от менеджера · '+roomName+'</div>';
|
||
if (!files.length) {
|
||
return header
|
||
+ '<div class="card" style="padding:12px 14px;display:flex;align-items:center;justify-content:space-between;margin-bottom:4px">'
|
||
+ '<div style="font-size:13px;color:var(--muted)">Файлы не прикреплены</div>'
|
||
+ '</div>';
|
||
}
|
||
return header + files.map(function(f){
|
||
var fi = fileIcon(f.name);
|
||
return '<div class="card" style="padding:10px 12px;display:flex;align-items:center;gap:10px;margin-bottom:6px">'
|
||
+ '<div style="width:40px;height:40px;border-radius:9px;background:'+fi.bg+';display:flex;align-items:center;justify-content:center;font-size:18px;flex-shrink:0">'+fi.icon+'</div>'
|
||
+ '<div style="flex:1;min-width:0">'
|
||
+ '<div style="font-size:13px;font-weight:700;color:var(--ink);white-space:nowrap;overflow:hidden;text-overflow:ellipsis">'+f.name+'</div>'
|
||
+ '<div style="font-size:11px;color:var(--muted);margin-top:1px">'+f.date+' · '+f.size+'</div>'
|
||
+ '</div>'
|
||
+ '<button style="background:rgba(99,102,241,.1);border:none;border-radius:8px;padding:5px 10px;font-size:12px;color:var(--accent);font-weight:700;cursor:pointer;flex-shrink:0">↗</button>'
|
||
+ '</div>';
|
||
}).join('');
|
||
})()}
|
||
|
||
<!-- Кабинет помещения: чеклист -->
|
||
<div class="card" style="padding:0;overflow:hidden;margin-bottom:4px">
|
||
<div onclick="(function(){window._ergoOpen=!window._ergoOpen;var b=document.getElementById('ergo-body');var i=document.getElementById('ergo-chev');if(b){b.style.display=window._ergoOpen?'block':'none';i.style.transform=window._ergoOpen?'rotate(180deg)':''}})()"
|
||
style="padding:12px 14px;cursor:pointer;display:flex;align-items:center;gap:8px">
|
||
<span style="font-size:16px">📐</span>
|
||
<div style="flex:1">
|
||
<div style="font-size:13px;font-weight:700;color:var(--ink)">Проверьте себя по чек-листу</div>
|
||
<div id="ergo-progress-label" style="font-size:11px;color:var(--muted);margin-top:1px">${doneCnt === total ? roomName + ' · Закрыт ✓' : roomName + ' · ' + doneCnt + ' из ' + total + ' — каждая отметка = ваша ответственность'}</div>
|
||
</div>
|
||
<svg id="ergo-chev" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" style="color:var(--muted);transition:.2s"><polyline points="6 9 12 15 18 9"/></svg>
|
||
</div>
|
||
<div id="ergo-body" style="display:none;border-top:1px solid rgba(0,0,0,.06)">
|
||
${ergoBody + ergoTips}
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Фото помещения -->
|
||
<div style="font-size:11px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.04em;margin:16px 0 10px">Фото помещения</div>
|
||
<div style="display:grid;grid-template-columns:repeat(3,1fr);gap:8px;margin-bottom:16px">
|
||
${cur.photoDone
|
||
? '<div class="photo-slot filled" style="background:rgba(0,0,0,.06)">📷</div><div class="photo-slot filled" style="background:rgba(0,0,0,.06)">📷</div>'
|
||
: '<div class="photo-slot"><svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg></div><div class="photo-slot"><svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg></div>'
|
||
}
|
||
<div class="photo-slot"><svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg></div>
|
||
</div>
|
||
|
||
<!-- Замер -->
|
||
<div style="font-size:11px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.04em;margin:0 0 10px">Замер</div>
|
||
<div style="display:grid;grid-template-columns:repeat(3,1fr);gap:8px;margin-bottom:6px">
|
||
${measureSlot}
|
||
<div class="photo-slot" onclick="_photoUploaded(this)">
|
||
<svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>
|
||
</div>
|
||
<div class="photo-slot" onclick="_photoUploaded(this)">
|
||
<svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>
|
||
</div>
|
||
</div>
|
||
<div style="font-size:11px;color:var(--muted);margin-bottom:16px">Фото замера на бумаге или скриншот из программы</div>
|
||
|
||
<!-- Комментарий -->
|
||
<div style="font-size:11px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.04em;margin:0 0 8px">Комментарии · ${roomName}</div>
|
||
<textarea placeholder="Особенности, нюансы монтажа, пожелания..."
|
||
style="width:100%;border:1.5px solid rgba(0,0,0,.1);border-radius:10px;padding:10px 12px;font-size:13px;font-family:inherit;color:var(--ink);background:var(--card);outline:none;resize:none;min-height:64px;margin-bottom:16px;line-height:1.5"
|
||
>${roomName === 'Кухня' ? 'Старая кухня демонтирована, место свободно. Потолок ровный, стяжка +88 мм.' : ''}</textarea>
|
||
|
||
${(function(){
|
||
var missing = [];
|
||
var cnt = Object.values(cur.ergoChecked).filter(Boolean).length;
|
||
var ergoTotal2 = (ROOM_ERGO[roomName] || ROOM_ERGO['Кухня']).params.length;
|
||
if (cnt < ergoTotal2) missing.push({ icon:'📋', text:'Не промаркировал — не закрыл — не отправил', sub: 'Чек-лист ' + roomName + ': ' + cnt + ' из ' + ergoTotal2 + '. Каждая отметка — это ваша подпись под пунктом.' });
|
||
if (!cur.measureDone) missing.push({ icon:'📐', text:'Замер не загружен — менеджер не получил', sub: 'Без файла замера проект не запустится. Сфотографируйте бумагу или выгрузите из программы.' });
|
||
if (!cur.photoDone) missing.push({ icon:'📷', text:'Фото помещения не добавлено', sub: 'Нужно для подтверждения условий на объекте.' });
|
||
if (!missing.length) return '';
|
||
return '<div style="background:rgba(245,158,11,.07);border-left:3px solid #F59E0B;border-radius:0 10px 10px 0;padding:12px 14px;margin-bottom:14px">'
|
||
+ '<div style="font-size:11px;font-weight:700;color:#92400E;text-transform:uppercase;letter-spacing:.06em;margin-bottom:10px">Замер не закрыт</div>'
|
||
+ missing.map(function(m){
|
||
return '<div style="display:flex;gap:10px;margin-bottom:10px;align-items:flex-start">'
|
||
+ '<span style="font-size:15px;line-height:1.4;flex-shrink:0">' + m.icon + '</span>'
|
||
+ '<div><div style="font-size:13px;font-weight:700;color:var(--ink);line-height:1.3">' + m.text + '</div>'
|
||
+ '<div style="font-size:11px;color:var(--muted);margin-top:3px;line-height:1.5">' + m.sub + '</div></div>'
|
||
+ '</div>';
|
||
}).join('')
|
||
+ '</div>';
|
||
})()}
|
||
|
||
<!-- Оценка визита -->
|
||
${(function(){
|
||
var v = window._visitRating || null;
|
||
var opts = [
|
||
{key:'ok', emoji:'😊', label:'Всё штатно'},
|
||
{key:'notes', emoji:'🤔', label:'Есть нюансы'},
|
||
{key:'hard', emoji:'😤', label:'Было сложно'},
|
||
];
|
||
var btns = opts.map(function(o){
|
||
var active = v === o.key;
|
||
var bg = active
|
||
? (o.key==='ok' ? '#16A34A' : o.key==='notes' ? '#B45309' : '#DC2626')
|
||
: 'var(--card)';
|
||
var clr = active ? '#fff' : 'var(--ink)';
|
||
var bdr = active ? 'none' : '1.5px solid rgba(0,0,0,.08)';
|
||
return '<button onclick="(function(){'
|
||
+ 'window._visitRating=\''+ o.key +'\';'
|
||
+ 'document.getElementById(\'screen\').innerHTML=renderScreen(\'measurement_detail\');'
|
||
+ 'var s=Array.from(document.querySelectorAll(\'*\')).find(function(el){return el.scrollHeight>el.clientHeight+10&&getComputedStyle(el).overflowY!==\'visible\';});'
|
||
+ 'if(s)s.scrollTop=9999;'
|
||
+ '})()"'
|
||
+ ' style="flex:1;padding:10px 4px;border-radius:12px;border:'+bdr+';background:'+bg+';color:'+clr+';font-size:12px;font-weight:600;cursor:pointer;display:flex;flex-direction:column;align-items:center;gap:4px">'
|
||
+ '<span style="font-size:22px">'+o.emoji+'</span>'
|
||
+ '<span>'+o.label+'</span>'
|
||
+ '</button>';
|
||
}).join('');
|
||
|
||
var noteField = (v === 'notes' || v === 'hard')
|
||
? '<textarea placeholder="Что именно? Клиент, доступ, нестандартная ситуация..."'
|
||
+ ' style="width:100%;border:1.5px solid rgba(0,0,0,.1);border-radius:10px;padding:10px 12px;font-size:13px;font-family:inherit;color:var(--ink);background:var(--card);outline:none;resize:none;min-height:60px;margin-top:10px;line-height:1.5"></textarea>'
|
||
: '';
|
||
|
||
return '<div style="margin-bottom:16px">'
|
||
+ '<div style="font-size:11px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.04em;margin-bottom:2px">Как прошёл замер?</div>'
|
||
+ '<div style="font-size:11px;color:var(--muted);margin-bottom:10px">Только для вас — поднимается при разборе, если что-то пойдёт не так</div>'
|
||
+ '<div style="display:flex;gap:8px">' + btns + '</div>'
|
||
+ noteField
|
||
+ '</div>';
|
||
})()}
|
||
|
||
<button class="btn-primary" onclick="navigate('measure_calc')">💰 Расчёт и оплата →</button>
|
||
<button class="btn-secondary">Позвонить клиенту</button>
|
||
<div style="height:16px"></div>
|
||
</div>
|
||
</div>`;
|
||
}
|
||
|
||
/* ─────────── SCREEN: MEASURE CALC ─────────── */
|
||
function screenMeasureCalc() {
|
||
var RATE1 = 2500, RATEN = 1000;
|
||
var PRESETS = ['Кухня','Гостиная','Спальня','Прихожая','Коридор','Ванная','Санузел','Балкон / Лоджия','Кабинет','Детская','Гардероб','Столовая'];
|
||
if (!window._calcCustomRooms) window._calcCustomRooms = []; // {name, flagged}
|
||
|
||
if (!window._calcRooms) {
|
||
window._calcRooms = PRESETS.map(function(n){ return {name:n, on:false}; });
|
||
}
|
||
if (window._calcClientMode === undefined) window._calcClientMode = false;
|
||
|
||
var rooms = window._calcRooms;
|
||
var checked = rooms.filter(function(r){ return r.on; });
|
||
var n = checked.length;
|
||
var total = n === 0 ? 0 : n === 1 ? RATE1 : RATE1 + (n - 1) * RATEN;
|
||
|
||
function tog(i) {
|
||
return 'onclick="(function(){window._calcRooms[' + i + '].on=!window._calcRooms[' + i + '].on;'
|
||
+ 'document.getElementById(\'screen\').innerHTML=renderScreen(\'measure_calc\')})()"';
|
||
}
|
||
|
||
// ── CLIENT MODE (fullscreen overlay) ──────────────────────────
|
||
if (window._calcClientMode) {
|
||
if (window._calcStep === undefined) window._calcStep = 'pay'; // 'pay' | 'sign'
|
||
if (window._calcPayMethod === undefined) window._calcPayMethod = null;
|
||
|
||
var lines = checked.map(function(r, idx){
|
||
return '<div style="display:flex;justify-content:space-between;align-items:center;padding:10px 0;border-bottom:1px solid rgba(255,255,255,.12)">'
|
||
+ '<span style="font-size:15px;font-weight:600;color:rgba(255,255,255,.85)">' + (idx===0?'1-е помещение':'+ ещё помещение') + '</span>'
|
||
+ '<span style="font-size:15px;font-weight:700;color:#fff">' + (idx===0?RATE1:RATEN) + ' ₽</span>'
|
||
+ '</div>';
|
||
}).join('');
|
||
|
||
var backBtn = '<button onclick="(function(){'
|
||
+ (window._calcStep==='sign'
|
||
? 'window._calcStep=\'pay\';window._calcPayMethod=null;'
|
||
: 'window._calcClientMode=false;window._calcStep=undefined;window._calcPayMethod=null;')
|
||
+ 'document.getElementById(\'screen\').innerHTML=renderScreen(\'measure_calc\')})()"'
|
||
+ ' style="background:rgba(255,255,255,.1);border:none;border-radius:10px;width:36px;height:36px;display:flex;align-items:center;justify-content:center;cursor:pointer;flex-shrink:0">'
|
||
+ '<svg width="18" height="18" fill="none" stroke="#fff" stroke-width="2.5" viewBox="0 0 24 24"><polyline points="15 18 9 12 15 6"/></svg>'
|
||
+ '</button>';
|
||
|
||
var header = '<div style="padding:20px 24px 0;display:flex;align-items:center;gap:12px">'
|
||
+ backBtn
|
||
+ '<span style="font-size:14px;font-weight:600;color:rgba(255,255,255,.6)">'
|
||
+ (window._calcStep==='sign' ? 'Подпись клиента' : 'Стоимость замера') + '</span>'
|
||
+ '</div>';
|
||
|
||
var totalDisplay = '<div style="text-align:center;margin-bottom:28px">'
|
||
+ '<div style="font-size:13px;font-weight:700;color:rgba(255,255,255,.5);text-transform:uppercase;letter-spacing:.1em;margin-bottom:10px">К оплате</div>'
|
||
+ '<div style="font-size:60px;font-weight:900;color:#fff;line-height:1;margin-bottom:6px">' + total.toLocaleString('ru') + ' ₽</div>'
|
||
+ '<div style="font-size:14px;color:rgba(255,255,255,.5)">' + n + ' ' + (n===1?'помещение':n<5?'помещения':'помещений') + '</div>'
|
||
+ '</div>';
|
||
|
||
var breakdown = '<div style="width:100%;background:rgba(255,255,255,.08);border-radius:16px;padding:4px 16px;margin-bottom:28px">' + lines + '</div>';
|
||
|
||
// ── STEP 1: выбор способа оплаты ──
|
||
if (window._calcStep === 'pay') {
|
||
var payBtns = '<div style="display:flex;gap:12px;width:100%">'
|
||
+ '<button onclick="(function(){window._calcPayMethod=\'card\';window._calcStep=\'pay\';'
|
||
+ 'window._calcClientMode=false;window._calcStep=undefined;window._calcPayMethod=null;window._calcRooms=null;'
|
||
+ 'document.getElementById(\'screen\').innerHTML=renderScreen(\'measurements\')})()"'
|
||
+ ' style="flex:1;padding:16px 8px;background:rgba(255,255,255,.12);border:1.5px solid rgba(255,255,255,.25);border-radius:14px;color:#fff;font-size:14px;font-weight:700;cursor:pointer;display:flex;flex-direction:column;align-items:center;gap:6px">'
|
||
+ '<span style="font-size:28px">💳</span><span>Карта / QR</span>'
|
||
+ '</button>'
|
||
+ '<button onclick="(function(){window._calcStep=\'sign\';'
|
||
+ 'document.getElementById(\'screen\').innerHTML=renderScreen(\'measure_calc\')})()"'
|
||
+ ' style="flex:1;padding:16px 8px;background:rgba(255,255,255,.12);border:1.5px solid rgba(255,255,255,.25);border-radius:14px;color:#fff;font-size:14px;font-weight:700;cursor:pointer;display:flex;flex-direction:column;align-items:center;gap:6px">'
|
||
+ '<span style="font-size:28px">💵</span><span>Наличные</span>'
|
||
+ '</button>'
|
||
+ '</div>';
|
||
|
||
return '<div style="min-height:100vh;background:linear-gradient(160deg,#1e1b4b,#312e81);display:flex;flex-direction:column">'
|
||
+ header
|
||
+ '<div style="flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;padding:0 28px">'
|
||
+ totalDisplay + breakdown + payBtns
|
||
+ '</div></div>';
|
||
}
|
||
|
||
// ── STEP 2: подпись клиента (наличные) ──
|
||
if (window._calcStep === 'sign') {
|
||
var sigArea = '<div style="width:100%;background:rgba(255,255,255,.95);border-radius:16px;overflow:hidden;margin-bottom:16px">'
|
||
+ '<div style="padding:10px 16px;border-bottom:1px solid rgba(0,0,0,.08);display:flex;justify-content:space-between;align-items:center">'
|
||
+ '<span style="font-size:12px;color:#666;font-weight:600">Подпись клиента</span>'
|
||
+ '<button onclick="(function(){var cv=document.getElementById(\'sig-cv\');cv.getContext(\'2d\').clearRect(0,0,cv.width,cv.height);window._calcSigned=false})()"'
|
||
+ ' style="font-size:12px;color:#6366F1;background:none;border:none;cursor:pointer;font-weight:600">Очистить</button>'
|
||
+ '</div>'
|
||
+ '<canvas id="sig-cv" width="340" height="160" style="display:block;width:100%;height:160px;touch-action:none"></canvas>'
|
||
+ '</div>'
|
||
+ '<div style="font-size:12px;color:rgba(255,255,255,.5);text-align:center;margin-bottom:20px">'
|
||
+ 'Подписывая, клиент подтверждает согласие с результатами замера и оплату ' + total.toLocaleString('ru') + ' ₽ наличными'
|
||
+ '</div>'
|
||
+ '<button id="sig-confirm-btn" disabled'
|
||
+ ' onclick="(function(){window._calcClientMode=false;window._calcStep=undefined;window._calcPayMethod=null;window._calcRooms=null;window._calcSigned=false;'
|
||
+ 'document.getElementById(\'screen\').innerHTML=renderScreen(\'measurements\')})()"'
|
||
+ ' style="width:100%;padding:16px;background:#22c55e;color:#fff;border:none;border-radius:14px;font-size:17px;font-weight:800;cursor:pointer;opacity:.45">'
|
||
+ '✓ Оплачено и подписано</button>'
|
||
+ '<img src="x" onerror="initSigCanvas()" style="display:none">';
|
||
|
||
return '<div style="min-height:100vh;background:linear-gradient(160deg,#1e1b4b,#312e81);display:flex;flex-direction:column">'
|
||
+ header
|
||
+ '<div style="flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;padding:0 24px">'
|
||
+ '<div style="font-size:13px;font-weight:700;color:rgba(255,255,255,.5);text-transform:uppercase;letter-spacing:.08em;margin-bottom:8px">Итог: ' + total.toLocaleString('ru') + ' ₽</div>'
|
||
+ sigArea
|
||
+ '</div></div>';
|
||
}
|
||
}
|
||
|
||
// ── CALC MODE ─────────────────────────────────────────────────
|
||
// Add custom rooms from _calcCustomRooms
|
||
window._calcCustomRooms.forEach(function(cr, ci){
|
||
var idx = rooms.findIndex(function(x){return x.name===cr.name;});
|
||
if (idx === -1) rooms.push({name:cr.name, on:cr.on||false, custom:true});
|
||
});
|
||
|
||
var roomList = rooms.map(function(r, i){
|
||
return '<div ' + tog(i)
|
||
+ ' style="display:flex;align-items:center;gap:12px;padding:11px 0;border-bottom:1px solid rgba(0,0,0,.05);cursor:pointer">'
|
||
+ '<div style="width:24px;height:24px;border-radius:7px;border:1.5px solid '
|
||
+ (r.on ? 'var(--accent)' : 'rgba(0,0,0,.15)') + ';background:'
|
||
+ (r.on ? 'var(--accent)' : 'transparent') + ';display:flex;align-items:center;justify-content:center;flex-shrink:0">'
|
||
+ (r.on ? '<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="3.5"><polyline points="20 6 9 17 4 12"/></svg>' : '')
|
||
+ '</div>'
|
||
+ '<span style="flex:1;font-size:14px;color:' + (r.on ? 'var(--ink)' : 'var(--muted)') + ';font-weight:' + (r.on ? '600' : '400') + '">' + r.name + '</span>'
|
||
+ (r.on ? '<span style="font-size:13px;font-weight:700;color:var(--accent)">' + (checked.indexOf(r) === 0 ? RATE1 : RATEN) + ' ₽</span>' : '')
|
||
+ '</div>';
|
||
}).join('');
|
||
|
||
var totalBlock = '<div style="margin-top:0;background:' + (n > 0 ? 'var(--accent)' : 'rgba(0,0,0,.05)') + ';border-radius:16px;padding:18px 20px;display:flex;align-items:center;justify-content:space-between">'
|
||
+ '<div>'
|
||
+ '<div style="font-size:12px;font-weight:700;color:' + (n > 0 ? 'rgba(255,255,255,.7)' : 'var(--muted)') + ';text-transform:uppercase;letter-spacing:.05em">Итого</div>'
|
||
+ '<div style="font-size:13px;color:' + (n > 0 ? 'rgba(255,255,255,.7)' : 'var(--muted)') + ';margin-top:2px">' + n + ' ' + (n===1?'помещение':n<5?'помещения':'помещений') + (n > 1 ? ' · 1 × 2500 + ' + (n-1) + ' × 1000' : '') + '</div>'
|
||
+ '</div>'
|
||
+ '<div style="font-size:28px;font-weight:900;color:' + (n > 0 ? '#fff' : 'var(--muted)') + '">' + (n > 0 ? total.toLocaleString('ru') + ' ₽' : '—') + '</div>'
|
||
+ '</div>';
|
||
|
||
var clientBtn = n > 0
|
||
? '<button onclick="(function(){window._calcClientMode=true;document.getElementById(\'screen\').innerHTML=renderScreen(\'measure_calc\')})()"'
|
||
+ ' style="width:100%;padding:15px;background:var(--accent);color:#fff;border:none;border-radius:14px;font-size:16px;font-weight:800;cursor:pointer;margin-top:12px">'
|
||
+ '📲 Показать клиенту</button>'
|
||
: '<button disabled style="width:100%;padding:15px;background:rgba(0,0,0,.06);color:var(--muted);border:none;border-radius:14px;font-size:16px;font-weight:700;margin-top:12px">Выберите помещения</button>';
|
||
|
||
return '<div class="page">'
|
||
+ '<div class="page-header">'
|
||
+ '<button class="back-btn" onclick="navigate(\'measurement_detail\')">'
|
||
+ '<svg fill="none" stroke="currentColor" stroke-width="2.5" viewBox="0 0 24 24"><polyline points="15 18 9 12 15 6"/></svg>'
|
||
+ '</button>'
|
||
+ '<h2>Расчёт стоимости</h2>'
|
||
+ '</div>'
|
||
+ '<div style="padding:0 16px">'
|
||
+ '<div class="card">'
|
||
+ '<div style="font-size:11px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:12px">Помещения</div>'
|
||
+ roomList
|
||
+ (function(){
|
||
return '<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:8px">Нет нужного помещения? Добавьте — директор увидит и решит включить в справочник</div>'
|
||
+ '<div style="display:flex;gap:8px">'
|
||
+ '<input id="custom-room" 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(\'custom-room\').value.trim();'
|
||
+ 'if(!v)return;'
|
||
+ 'window._calcCustomRooms.push({name:v,on:true,flagged:true});'
|
||
+ 'window._calcRooms=null;'
|
||
+ 'document.getElementById(\'screen\').innerHTML=renderScreen(\'measure_calc\')})()" style="background:var(--accent);color:#fff;border:none;border-radius:8px;padding:8px 12px;font-size:13px;font-weight:700;cursor:pointer">+ Добавить</button>'
|
||
+ '</div>'
|
||
+ '</div>';
|
||
})()
|
||
+ '</div>'
|
||
+ totalBlock
|
||
+ clientBtn
|
||
+ '<div style="height:24px"></div>'
|
||
+ '</div>'
|
||
+ '</div>';
|
||
}
|
||
|
||
/* ─────────── SCREEN: ASSEMBLIES ─────────── */
|
||
function screenAssemblies() {
|
||
return `<div class="page">
|
||
<div class="page-header"><h2>Мои сборки</h2></div>
|
||
<div style="height:12px"></div>
|
||
<div class="chip-row">
|
||
<div class="chip active">Все</div>
|
||
<div class="chip">Сегодня</div>
|
||
<div class="chip">Активные</div>
|
||
<div class="chip">Завершённые</div>
|
||
</div>
|
||
<div style="padding:4px 16px 0">
|
||
<div class="card warn-border" style="cursor:pointer;margin-bottom:10px" onclick="navigate('assembly_detail')">
|
||
<div class="row sb" style="margin-bottom:6px">
|
||
<span style="font-size:14px;font-weight:700;color:var(--ink)">#А-2847</span>
|
||
<span class="badge yellow">В работе</span>
|
||
</div>
|
||
<div style="font-size:13px;color:var(--muted);margin-bottom:8px">Черниговская 14 · Кухня</div>
|
||
<div style="display:flex;align-items:center;gap:8px;margin-bottom:6px">
|
||
<div class="progress-bar"><div class="progress-fill" style="width:57%"></div></div>
|
||
<span style="font-size:11px;color:var(--muted)">4/7</span>
|
||
</div>
|
||
<div class="row sb">
|
||
<span class="badge red" style="font-size:11px">Срочно</span>
|
||
<span style="font-size:12px;color:var(--muted)">сегодня 11:00</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="card accent-border" style="cursor:pointer;margin-bottom:10px" onclick="navigate('assembly_detail')">
|
||
<div class="row sb" style="margin-bottom:6px">
|
||
<span style="font-size:14px;font-weight:700;color:var(--ink)">#А-2851</span>
|
||
<span class="badge gray">Ожидает</span>
|
||
</div>
|
||
<div style="font-size:13px;color:var(--muted);margin-bottom:6px">Ленина 8 · Гардероб</div>
|
||
<div style="font-size:12px;color:var(--muted)">25 мая</div>
|
||
</div>
|
||
|
||
<div class="card success-border" style="cursor:pointer;margin-bottom:10px" onclick="navigate('assembly_detail')">
|
||
<div class="row sb" style="margin-bottom:6px">
|
||
<span style="font-size:14px;font-weight:700;color:var(--ink)">#А-2836</span>
|
||
<span class="badge green">Завершена ✓</span>
|
||
</div>
|
||
<div style="font-size:13px;color:var(--muted)">Мира 22 · Прихожая</div>
|
||
</div>
|
||
</div>
|
||
</div>`;
|
||
}
|
||
|
||
/* ─────────── SCREEN: ASSEMBLY DETAIL ─────────── */
|
||
function screenAssemblyDetail() {
|
||
var items = [
|
||
{text:'Получить комплект на складе', done:true},
|
||
{text:'Доставка на объект', done:true},
|
||
{text:'Инструмент проверен', done:true},
|
||
{text:'Согласование с клиентом', done:true},
|
||
{text:'Сборка корпусов', done:false},
|
||
{text:'Установка фасадов и фурнитуры', done:false},
|
||
{text:'Финальная проверка + фото', done:false},
|
||
];
|
||
var doneCount = items.filter(function(i){ return i.done; }).length;
|
||
|
||
var checkHtml = items.map(function(item, idx){
|
||
var boxCls = item.done ? 'check-box done' : 'check-box';
|
||
var txtCls = item.done ? 'check-text done' : 'check-text';
|
||
return `<div class="checklist-item">
|
||
<div class="${boxCls}">
|
||
<svg fill="none" stroke="currentColor" stroke-width="3" viewBox="0 0 24 24"><polyline points="20 6 9 17 4 12"/></svg>
|
||
</div>
|
||
<div class="${txtCls}"><h4>${item.text}</h4></div>
|
||
</div>`;
|
||
}).join('');
|
||
|
||
return `<div class="page">
|
||
<div class="page-header">
|
||
<button class="back-btn" onclick="navigate('assemblies')">
|
||
<svg fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" viewBox="0 0 24 24"><polyline points="15 18 9 12 15 6"/></svg>
|
||
</button>
|
||
<h2>Сборка #А-2847</h2>
|
||
<span class="badge yellow" style="margin-left:auto">В работе</span>
|
||
</div>
|
||
|
||
<div style="padding:14px 16px 0">
|
||
<div class="card" style="margin-bottom:12px">
|
||
<div class="info-row">
|
||
<div class="info-label">Адрес</div>
|
||
<div class="info-val">Черниговская 14</div>
|
||
</div>
|
||
<div class="info-row">
|
||
<div class="info-label">Тип мебели</div>
|
||
<div class="info-val">Кухня</div>
|
||
</div>
|
||
<div class="info-row">
|
||
<div class="info-label">Время</div>
|
||
<div class="info-val">Сегодня 11:00</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div style="font-size:11px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.04em;margin-bottom:10px">
|
||
Чеклист <span style="font-size:12px;font-weight:700;color:var(--ink)">${doneCount}/${items.length}</span>
|
||
</div>
|
||
<div class="card">${checkHtml}</div>
|
||
|
||
<div style="font-size:11px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.04em;margin:16px 0 10px">Фото отчёт</div>
|
||
<div style="display:flex;gap:10px;flex-wrap:wrap;margin-bottom:16px">
|
||
<div class="photo-slot filled">📷</div>
|
||
<div class="photo-slot filled">📷</div>
|
||
<div class="photo-slot">
|
||
<svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>
|
||
</div>
|
||
<div class="photo-slot">
|
||
<svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>
|
||
</div>
|
||
<div class="photo-slot">
|
||
<svg fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>
|
||
</div>
|
||
</div>
|
||
<div style="font-size:12px;color:var(--muted);text-align:center;margin-bottom:16px">Добавить фото отчёт</div>
|
||
|
||
<button class="btn-secondary">Связаться с менеджером</button>
|
||
<button class="btn-primary" style="margin-top:8px" disabled>
|
||
Завершить сборку → (${doneCount}/${items.length})
|
||
</button>
|
||
<div style="height:16px"></div>
|
||
</div>
|
||
</div>`;
|
||
}
|
||
|
||
|
||
/* ─────────── SCREEN: CALENDAR / МАРШРУТ ─────────── */
|
||
var WORKER_HOME = 'Садовая 5, кв.12';
|
||
var KM_RATE = 4; // ₽/км
|
||
|
||
var ALL_JOBS = [
|
||
{date:'2026-05-05', type:'meas', time:'10:00', addr:'Мира 22, кв.7', name:'Кухня прямая'},
|
||
{date:'2026-05-07', type:'asm', time:'09:30', addr:'Пушкина 8', name:'#А-2836 · Прихожая'},
|
||
{date:'2026-05-12', type:'meas', time:'11:00', addr:'Ленина 45, кв.12', name:'Гардероб'},
|
||
{date:'2026-05-12', type:'asm', time:'14:00', addr:'Советская 3', name:'#А-2840 · Кухня'},
|
||
{date:'2026-05-15', type:'asm', time:'10:00', addr:'Краснодарская 7', name:'#А-2843 · Спальня'},
|
||
{date:'2026-05-19', type:'meas', time:'09:00', addr:'Красная 17, кв.33', name:'Кухня угловая'},
|
||
{date:'2026-05-19', type:'meas', time:'15:00', addr:'Кубанская 5, кв.8', name:'Спальня'},
|
||
{date:'2026-05-21', type:'asm', time:'10:00', addr:'Ленина 8', name:'#А-2851 · Гардероб'},
|
||
{date:'2026-05-23', type:'meas', time:'09:00', addr:'Ленина 12, кв.45', name:'Кухня угловая'},
|
||
{date:'2026-05-23', type:'asm', time:'11:00', addr:'Черниговская 14', name:'#А-2847 · Кухня'},
|
||
{date:'2026-05-23', type:'meas', time:'14:30', addr:'Мира 7, кв.18', name:'Гардероб'},
|
||
{date:'2026-05-25', type:'asm', time:'11:00', addr:'Садовая 33', name:'#А-2855 · Гостиная'},
|
||
{date:'2026-05-27', type:'meas', time:'10:00', addr:'Красноармейская 12', name:'Кухня прямая'},
|
||
{date:'2026-05-27', type:'asm', time:'13:00', addr:'Мира 15', name:'#А-2858 · Прихожая'},
|
||
{date:'2026-05-28', type:'meas', time:'09:30', addr:'Гагарина 45, кв.4', name:'Кухня угловая'},
|
||
{date:'2026-05-30', type:'asm', time:'10:00', addr:'Советская 22', name:'#А-2862 · Кухня'},
|
||
];
|
||
|
||
// km arrays: [home→job0, job0→job1, ..., lastJob→home]
|
||
var ROUTE_KM = {
|
||
'2026-05-05': [7.4, 8.1],
|
||
'2026-05-07': [5.2, 6.8],
|
||
'2026-05-12': [9.1, 4.2, 11.3],
|
||
'2026-05-15': [6.7, 5.9],
|
||
'2026-05-19': [8.5, 5.1, 9.8],
|
||
'2026-05-21': [7.2, 8.4],
|
||
'2026-05-23': [8.2, 3.4, 12.1, 7.3],
|
||
'2026-05-25': [6.3, 7.1],
|
||
'2026-05-27': [5.8, 3.2, 7.5],
|
||
'2026-05-28': [9.4, 10.2],
|
||
'2026-05-30': [4.8, 6.3],
|
||
};
|
||
|
||
function refreshCal() {
|
||
document.getElementById('screen').innerHTML = screenCalendar();
|
||
}
|
||
|
||
function screenCalendar() {
|
||
var role = window._workerRole || 'both';
|
||
var selDate = window._calDate || '2026-05-23';
|
||
|
||
var jobs = ALL_JOBS.filter(function(j) {
|
||
if (role === 'measurer') return j.type === 'meas';
|
||
if (role === 'assembler') return j.type === 'asm';
|
||
return true;
|
||
});
|
||
|
||
// Build day lookup
|
||
var dayMap = {};
|
||
jobs.forEach(function(j) {
|
||
if (!dayMap[j.date]) dayMap[j.date] = [];
|
||
dayMap[j.date].push(j);
|
||
});
|
||
|
||
// May 2026 calendar
|
||
var year = 2026, monthIdx = 4; // May = 4
|
||
var daysInMonth = 31;
|
||
var firstWday = new Date(year, monthIdx, 1).getDay(); // 0=Sun
|
||
var startOffset = (firstWday + 6) % 7; // Mon-first
|
||
var todayStr = '2026-05-23';
|
||
var dayNames = ['Пн','Вт','Ср','Чт','Пт','Сб','Вс'];
|
||
|
||
// Header
|
||
var html = '<div class="page">'
|
||
+ '<div style="display:flex;align-items:center;justify-content:space-between;padding:14px 16px 10px;background:var(--card);border-bottom:1px solid rgba(0,0,0,.06);position:sticky;top:0;z-index:50">'
|
||
+ '<h2 style="font-size:17px;font-weight:700;color:var(--ink)">Маршрут</h2>'
|
||
+ '<div style="font-size:14px;font-weight:700;color:var(--ink)">МАЙ 2026</div>'
|
||
+ '</div>';
|
||
|
||
// Weekday labels
|
||
html += '<div style="display:grid;grid-template-columns:repeat(7,1fr);padding:10px 10px 4px;background:var(--card)">'
|
||
+ dayNames.map(function(d,i){
|
||
return '<div style="text-align:center;font-size:10px;font-weight:700;color:' + (i>=5?'var(--warn)':'var(--muted)') + '">' + d + '</div>';
|
||
}).join('')
|
||
+ '</div>';
|
||
|
||
// Calendar grid
|
||
var totalCells = Math.ceil((startOffset + daysInMonth) / 7) * 7;
|
||
html += '<div style="display:grid;grid-template-columns:repeat(7,1fr);gap:1px;padding:0 8px 8px;background:var(--card)">';
|
||
for (var i = 0; i < totalCells; i++) {
|
||
var dayNum = i - startOffset + 1;
|
||
var valid = dayNum >= 1 && dayNum <= daysInMonth;
|
||
var ds = valid ? (year + '-05-' + String(dayNum).padStart(2,'0')) : '';
|
||
var isToday = ds === todayStr;
|
||
var isSel = ds === selDate;
|
||
var isWknd = (i % 7) >= 5;
|
||
var dayJobs = valid ? (dayMap[ds] || []) : [];
|
||
var hasMeas = dayJobs.some(function(j){ return j.type==='meas'; });
|
||
var hasAsm = dayJobs.some(function(j){ return j.type==='asm'; });
|
||
|
||
var cellBg = isSel ? 'background:var(--accent);border-radius:10px;' : '';
|
||
var numColor = isSel ? '#fff' : (isWknd ? 'var(--warn)' : 'var(--ink)');
|
||
var numW = (isToday && !isSel) ? '800' : '600';
|
||
var onclick = valid ? ' onclick="(function(){window._calDate=\'' + ds + '\';refreshCal()})()"' : '';
|
||
|
||
var todayRing = (isToday && !isSel)
|
||
? '<div style="position:absolute;inset:0;border:2px solid var(--accent);border-radius:10px;pointer-events:none"></div>'
|
||
: '';
|
||
|
||
var dots = '';
|
||
if (valid && (hasMeas || hasAsm)) {
|
||
var dotBg = isSel ? 'rgba(255,255,255,.75)' : '';
|
||
dots = '<div style="display:flex;gap:2px;justify-content:center;margin-top:3px">'
|
||
+ (hasMeas ? '<div style="width:5px;height:5px;border-radius:50%;background:' + (isSel ? dotBg : '#3B82F6') + '"></div>' : '')
|
||
+ (hasAsm ? '<div style="width:5px;height:5px;border-radius:50%;background:' + (isSel ? dotBg : '#10B981') + '"></div>' : '')
|
||
+ '</div>';
|
||
}
|
||
|
||
html += '<div' + onclick + ' style="position:relative;display:flex;flex-direction:column;align-items:center;padding:7px 2px;cursor:' + (valid?'pointer':'default') + ';' + cellBg + '">'
|
||
+ todayRing
|
||
+ (valid ? '<div style="font-size:13px;font-weight:' + numW + ';color:' + numColor + ';line-height:1">' + dayNum + '</div>' + dots : '')
|
||
+ '</div>';
|
||
}
|
||
html += '</div>';
|
||
|
||
// Legend
|
||
html += '<div style="display:flex;gap:16px;align-items:center;padding:6px 14px 10px;background:var(--card);border-bottom:1px solid rgba(0,0,0,.08)">'
|
||
+ '<div style="display:flex;align-items:center;gap:5px"><div style="width:8px;height:8px;border-radius:50%;background:#3B82F6"></div><span style="font-size:11px;color:var(--muted)">Замер</span></div>'
|
||
+ '<div style="display:flex;align-items:center;gap:5px"><div style="width:8px;height:8px;border-radius:50%;background:#10B981"></div><span style="font-size:11px;color:var(--muted)">Сборка</span></div>'
|
||
+ '<div style="flex:1"></div>'
|
||
+ '<div style="font-size:11px;color:var(--muted)">' + KM_RATE + ' ₽/км компенс.</div>'
|
||
+ '</div>';
|
||
|
||
// Route for selected day
|
||
html += '<div style="padding:12px 14px">';
|
||
var selJobs = dayMap[selDate] || [];
|
||
if (!selDate || selJobs.length === 0) {
|
||
html += '<div style="text-align:center;padding:20px;color:var(--muted);font-size:14px">Нет задач в этот день</div>';
|
||
} else {
|
||
var rKms = ROUTE_KM[selDate] || [];
|
||
var totalKm = rKms.reduce(function(a,b){return a+b;},0);
|
||
var comp = Math.round(totalKm * KM_RATE);
|
||
|
||
// Date sub-header
|
||
var sdObj = new Date(selDate + 'T00:00:00');
|
||
var wdRu = ['Вс','Пн','Вт','Ср','Чт','Пт','Сб'][sdObj.getDay()];
|
||
var mRu = ['янв','фев','мар','апр','мая','июн','июл','авг','сен','окт','ноя','дек'][sdObj.getMonth()];
|
||
html += '<div style="display:flex;align-items:baseline;gap:8px;margin-bottom:10px">'
|
||
+ '<div style="font-size:14px;font-weight:700;color:var(--ink)">' + wdRu + ', ' + sdObj.getDate() + ' ' + mRu + '</div>'
|
||
+ '<div style="font-size:12px;color:var(--muted)">' + selJobs.length + ' задач' + (totalKm>0 ? ' · ' + totalKm.toFixed(1) + ' км' : '') + '</div>'
|
||
+ '</div>';
|
||
|
||
// Route card
|
||
html += '<div class="card" style="padding:10px 14px">';
|
||
|
||
var iconMap = {meas:'📐', asm:'🔧'};
|
||
var colorMap = {meas:'#3B82F6', asm:'#10B981'};
|
||
var bgMap = {meas:'rgba(59,130,246,.1)', asm:'rgba(16,185,129,.1)'};
|
||
var nameMap = {meas:'Замер', asm:'Сборка'};
|
||
var screenMap = {meas:'measurement_detail', asm:'assembly_detail'};
|
||
|
||
// Home → first job
|
||
html += '<div style="display:flex;align-items:center;gap:10px;padding:8px 0">'
|
||
+ '<div style="width:34px;height:34px;border-radius:50%;background:rgba(0,62,126,.1);display:flex;align-items:center;justify-content:center;font-size:16px;flex-shrink:0">🏠</div>'
|
||
+ '<div><div style="font-size:13px;font-weight:700;color:var(--ink)">Дом</div>'
|
||
+ '<div style="font-size:11px;color:var(--muted)">' + WORKER_HOME + '</div></div>'
|
||
+ '</div>';
|
||
|
||
selJobs.forEach(function(job, idx) {
|
||
var kmToThis = rKms[idx] !== undefined ? rKms[idx] : 0;
|
||
// Connector line + km
|
||
html += '<div style="display:flex;align-items:center;gap:10px;padding:2px 0">'
|
||
+ '<div style="width:34px;display:flex;justify-content:center;flex-shrink:0">'
|
||
+ '<div style="width:2px;height:20px;background:rgba(0,0,0,.12);border-radius:1px"></div>'
|
||
+ '</div>'
|
||
+ '<div style="font-size:11px;color:var(--muted);font-weight:600">' + kmToThis.toFixed(1) + ' км</div>'
|
||
+ '</div>';
|
||
// Job row
|
||
var jColor = colorMap[job.type];
|
||
var jBg = bgMap[job.type];
|
||
html += '<div style="display:flex;align-items:center;gap:10px;padding:8px 0;cursor:pointer" onclick="navigate(\'' + screenMap[job.type] + '\')">'
|
||
+ '<div style="width:34px;height:34px;border-radius:50%;background:' + jBg + ';display:flex;align-items:center;justify-content:center;font-size:16px;flex-shrink:0">' + iconMap[job.type] + '</div>'
|
||
+ '<div style="flex:1;min-width:0">'
|
||
+ '<div style="display:flex;align-items:center;gap:6px;flex-wrap:wrap">'
|
||
+ '<span style="font-size:13px;font-weight:700;color:var(--ink)">' + job.time + ' · ' + job.addr + '</span>'
|
||
+ '<span style="font-size:10px;font-weight:700;color:' + jColor + ';background:' + jBg + ';border-radius:4px;padding:1px 6px">' + nameMap[job.type] + '</span>'
|
||
+ '</div>'
|
||
+ '<div style="font-size:11px;color:var(--muted);margin-top:2px">' + job.name + '</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>';
|
||
});
|
||
|
||
// Return km + home
|
||
var retKm = rKms[selJobs.length] !== undefined ? rKms[selJobs.length] : 0;
|
||
html += '<div style="display:flex;align-items:center;gap:10px;padding:2px 0">'
|
||
+ '<div style="width:34px;display:flex;justify-content:center;flex-shrink:0">'
|
||
+ '<div style="width:2px;height:20px;background:rgba(0,0,0,.12);border-radius:1px"></div>'
|
||
+ '</div>'
|
||
+ '<div style="font-size:11px;color:var(--muted);font-weight:600">' + retKm.toFixed(1) + ' км</div>'
|
||
+ '</div>';
|
||
html += '<div style="display:flex;align-items:center;gap:10px;padding:8px 0">'
|
||
+ '<div style="width:34px;height:34px;border-radius:50%;background:rgba(0,62,126,.1);display:flex;align-items:center;justify-content:center;font-size:16px;flex-shrink:0">🏠</div>'
|
||
+ '<div><div style="font-size:13px;font-weight:700;color:var(--ink)">Дом</div>'
|
||
+ '<div style="font-size:12px;color:var(--muted);margin-top:1px">'
|
||
+ 'Итого: <b style="color:var(--ink)">' + totalKm.toFixed(1) + ' км</b>'
|
||
+ ' · компенсация: <b style="color:var(--success)">' + comp + ' ₽</b>'
|
||
+ '</div></div>'
|
||
+ '</div>';
|
||
|
||
html += '</div>'; // card
|
||
}
|
||
html += '</div>'; // padding div
|
||
|
||
html += '</div>'; // page
|
||
return html;
|
||
}
|
||
|
||
/* ─────────── SCREEN: FINANCES ─────────── */
|
||
function screenFinances() {
|
||
var role = window._workerRole || 'both';
|
||
var rows = [
|
||
{date:'22 мая', obj:'Ленина 12', type:'Замер', sum:'1 200 ₽'},
|
||
{date:'21 мая', obj:'Черниговская 14', type:'Сборка', sum:'4 800 ₽'},
|
||
{date:'20 мая', obj:'Мира 7', type:'Замер', sum:'800 ₽'},
|
||
{date:'19 мая', obj:'Садовая 4', type:'Сборка', sum:'3 600 ₽'},
|
||
{date:'18 мая', obj:'Пушкина 2', type:'Сборка', sum:'2 400 ₽'},
|
||
];
|
||
|
||
// filter by role
|
||
if (role === 'measurer') rows = rows.filter(function(r){ return r.type === 'Замер'; });
|
||
if (role === 'assembler') rows = rows.filter(function(r){ return r.type === 'Сборка'; });
|
||
|
||
var measCount = (role === 'assembler') ? 0 : 8;
|
||
var asmCount = (role === 'measurer') ? 0 : 9;
|
||
var total = measCount + asmCount;
|
||
|
||
return `<div class="page">
|
||
<div class="page-header"><h2>Финансы</h2></div>
|
||
<div style="padding:16px 16px 0">
|
||
<div class="earnings-card">
|
||
<div style="font-size:13px;opacity:.8;margin-bottom:6px">Получено в мае</div>
|
||
<div style="font-size:28px;font-weight:800;letter-spacing:-.5px;margin-bottom:2px">28 400 ₽</div>
|
||
<div style="font-size:12px;opacity:.75;margin-bottom:12px">к выплате: <b>6 200 ₽</b></div>
|
||
<div class="progress-bar" style="background:rgba(255,255,255,.25)">
|
||
<div class="progress-fill" style="width:71%;background:rgba(255,255,255,.9)"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<table class="stat-table">
|
||
<thead>
|
||
<tr>
|
||
<th>Дата</th>
|
||
<th>Объект</th>
|
||
<th>Вид</th>
|
||
<th style="text-align:right">Сумма</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
${rows.map(function(r){
|
||
var badgeCls = r.type === 'Замер' ? 'blue' : 'teal';
|
||
return `<tr>
|
||
<td style="color:var(--muted)">${r.date}</td>
|
||
<td>${r.obj}</td>
|
||
<td><span class="badge ${badgeCls}" style="font-size:10px;padding:2px 7px">${r.type}</span></td>
|
||
<td style="text-align:right;font-weight:600">${r.sum}</td>
|
||
</tr>`;
|
||
}).join('')}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<div class="card" style="display:flex;justify-content:space-around;text-align:center">
|
||
${measCount > 0 ? `<div><div style="font-size:22px;font-weight:800;color:var(--ink)">${measCount}</div><div style="font-size:11px;color:var(--muted);margin-top:2px">Замеров</div></div>` : ''}
|
||
${measCount > 0 && asmCount > 0 ? '<div style="width:1px;background:rgba(0,0,0,.08)"></div>' : ''}
|
||
${asmCount > 0 ? `<div><div style="font-size:22px;font-weight:800;color:var(--ink)">${asmCount}</div><div style="font-size:11px;color:var(--muted);margin-top:2px">Сборок</div></div>` : ''}
|
||
<div style="width:1px;background:rgba(0,0,0,.08)"></div>
|
||
<div><div style="font-size:22px;font-weight:800;color:var(--ink)">${total}</div><div style="font-size:11px;color:var(--muted);margin-top:2px">Итого</div></div>
|
||
</div>
|
||
</div>
|
||
</div>`;
|
||
}
|
||
|
||
/* ─────────── SCREEN: PROFILE ─────────── */
|
||
function screenProfile() {
|
||
var role = window._workerRole || 'both';
|
||
var measStat = (role === 'assembler') ? '' : `<div class="card" style="text-align:center;flex:1">
|
||
<div style="font-size:24px;font-weight:800;color:var(--ink)">8</div>
|
||
<div style="font-size:11px;color:var(--muted);margin-top:2px">Замеров в мае</div>
|
||
</div>`;
|
||
var asmStat = (role === 'measurer') ? '' : `<div class="card" style="text-align:center;flex:1">
|
||
<div style="font-size:24px;font-weight:800;color:var(--ink)">9</div>
|
||
<div style="font-size:11px;color:var(--muted);margin-top:2px">Сборок в мае</div>
|
||
</div>`;
|
||
|
||
return `<div class="page">
|
||
<div class="page-header"><h2>Профиль</h2></div>
|
||
<div style="padding:24px 16px 0;text-align:center">
|
||
<div class="avatar lg" style="width:80px;height:80px;font-size:26px;margin:0 auto 12px">КА</div>
|
||
<div style="font-size:18px;font-weight:700;color:var(--ink);margin-bottom:6px">Кириллов Алексей Владимирович</div>
|
||
<div style="display:flex;gap:6px;justify-content:center;flex-wrap:wrap;margin-bottom:6px">${roleBadges()}</div>
|
||
<div style="font-size:13px;color:var(--muted)">Категория А</div>
|
||
</div>
|
||
|
||
<div style="padding:20px 16px 0;display:flex;gap:10px;flex-wrap:wrap">
|
||
${measStat}
|
||
${asmStat}
|
||
<div class="card" style="text-align:center;flex:1">
|
||
<div style="font-size:24px;font-weight:800;color:var(--ink)">4.8 ⭐</div>
|
||
<div style="font-size:11px;color:var(--muted);margin-top:2px">Рейтинг</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div style="padding:0 16px">
|
||
<div class="card" style="margin-top:10px">
|
||
<div class="info-row">
|
||
<div class="info-label">Домашний адрес</div>
|
||
<div class="info-val" style="display:flex;align-items:center;gap:6px">
|
||
<span>ул. Садовая, 5, кв. 12</span>
|
||
<span style="font-size:11px;color:var(--muted)">(старт маршрута)</span>
|
||
</div>
|
||
</div>
|
||
<div class="info-row">
|
||
<div class="info-label">Телефон</div>
|
||
<div class="info-val" style="color:var(--accent)">+7 (912) 456-78-90</div>
|
||
</div>
|
||
<div class="info-row">
|
||
<div class="info-label">На проекте</div>
|
||
<div class="info-val">с марта 2024</div>
|
||
</div>
|
||
<div class="info-row">
|
||
<div class="info-label">Всего работ</div>
|
||
<div class="info-val">143</div>
|
||
</div>
|
||
</div>
|
||
|
||
${role !== 'assembler' ? `
|
||
<!-- Tool kit — read-only, shown only for measurer -->
|
||
<div class="card" style="margin-top:0">
|
||
<div style="font-size:11px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:12px">Набор инструмента</div>
|
||
${[
|
||
{key:'laser', icon:'📏', label:'Лазерный дальномер', on:true},
|
||
{key:'level', icon:'📐', label:'Электронный уровень', on:true},
|
||
{key:'metal', icon:'🔍', label:'Металлодетектор', on:false},
|
||
{key:'photo', icon:'📷', label:'Фотофиксация (камера)', on:true},
|
||
{key:'tablet', icon:'💻', label:'Планшет с ПО', on:false},
|
||
{key:'blueprint',icon:'🗺️', label:'Чертёжный комплект', on:false},
|
||
].map(t => `
|
||
<div style="display:flex;align-items:center;gap:10px;padding:8px 0;border-bottom:1px solid rgba(0,0,0,.05)">
|
||
<div style="width:20px;height:20px;border-radius:5px;border:1.5px solid ${t.on ? 'var(--accent)' : 'rgba(0,0,0,.15)'};background:${t.on ? 'var(--accent)' : 'transparent'};display:flex;align-items:center;justify-content:center;flex-shrink:0">
|
||
${t.on ? '<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="3.5"><polyline points="20 6 9 17 4 12"/></svg>' : ''}
|
||
</div>
|
||
<span style="font-size:13px">${t.icon}</span>
|
||
<span style="font-size:13px;color:${t.on ? 'var(--ink)' : 'var(--muted)'};font-weight:${t.on ? '600' : '400'}">${t.label}</span>
|
||
</div>
|
||
`).join('')}
|
||
<div style="margin-top:10px;font-size:11px;color:var(--muted)">Состав набора устанавливает директор</div>
|
||
</div>` : ''}
|
||
|
||
<button class="btn-secondary" style="margin-top:4px">Связаться с диспетчером</button>
|
||
<div style="height:16px"></div>
|
||
</div>
|
||
</div>`;
|
||
}
|
||
|
||
// ── ERGO CATALOG (Рита · v1.0 · ЧЕКЛИСТ_ЗАМЕРА) ────────────
|
||
var ROOM_ERGO = {
|
||
'Кухня': {
|
||
icon: '🍳',
|
||
params: [
|
||
{label:'Ширина рабочей стены', hint:'В 3 высотах — стены не параллельны', critical:true},
|
||
{label:'Высота потолка', hint:'У стены установки, не в центре', critical:true},
|
||
{label:'V1 — Вентбокс', hint:'Ш × В + расстояние от обоих углов стены', critical:true},
|
||
{label:'Wc1 / Wh1 — Вода', hint:'Горизонталь от базы + вертикаль от пола', critical:true},
|
||
{label:'D1 / D1\' — Слив + фановая',hint:'Выпуск от стены, горизонталь и вертикаль', critical:true},
|
||
{label:'Углы (ЛН, ПН, ЛВ, ПВ)', hint:'Если ≠ 90° — пишем значение в градусах', critical:false},
|
||
{label:'R1 × N — Розетки', hint:'Высота от пола + горизонталь от базы', critical:false},
|
||
{label:'Rs1 — Силовые розетки', hint:'Под духовку, варочную — позицию точно', critical:false},
|
||
{label:'ОК1 — Окно / подоконник', hint:'Высота низа, ширина, глубина подоконника', critical:false},
|
||
],
|
||
tips: [
|
||
'V1 (вентбокс) — главная засада: замерь до мм, потом не переделать',
|
||
'Базу (БАЗА: ЛУ или ПУ) выбери с первого угла и держи на всей стене',
|
||
'Уточни у клиента встройку — она определяет высоту верхних шкафов',
|
||
],
|
||
source: 'ЧЕКЛИСТ_ЗАМЕРА v1.0'
|
||
},
|
||
'Гостиная': {
|
||
icon: '🛋️',
|
||
params: [
|
||
{label:'Ширина стены под мебель', hint:'В 3 точках — у пола, середины, потолка', critical:true},
|
||
{label:'Высота потолка', hint:'У стены + в центре (если есть перепад)', critical:true},
|
||
{label:'ДВ1 / ОК1 — Проёмы', hint:'Ширина + высота + отступ от базы до края', critical:true},
|
||
{label:'Углы (ЛН, ПН)', hint:'Если ≠ 90° — особенно углы под мебель', critical:false},
|
||
{label:'Tv1 / Net1 — ТВ, интернет', hint:'Горизонталь от базы + высота от пола', critical:false},
|
||
{label:'R1 × N — Розетки', hint:'Высота + горизонталь; будут скрыты мебелью', critical:false},
|
||
{label:'Ld1 — Освещение', hint:'Точка на потолке — горизонт и вертикаль', critical:false},
|
||
],
|
||
tips: [
|
||
'Диагональ комнаты: если разница > 20 мм — стены не прямые, пишем углы',
|
||
'Tv1/Net1 фиксируй точно — стенка закроет, потом не доберёшься',
|
||
'Уточни место ТВ у клиента — оно определяет центр симметрии стенки',
|
||
],
|
||
source: 'ЧЕКЛИСТ_ЗАМЕРА v1.0'
|
||
},
|
||
'Спальня': {
|
||
icon: '🛏️',
|
||
params: [
|
||
{label:'Ширина стены под шкаф', hint:'Полная + за дверью если угловой', critical:true},
|
||
{label:'Высота потолка у стены', hint:'Именно у стены шкафа, не в центре', critical:true},
|
||
{label:'Плинтус', hint:'Высота + глубина — шкаф стоит на нём', critical:true},
|
||
{label:'ДВ1 — Проём двери', hint:'Высота от пола влияет на петли и полку', critical:true},
|
||
{label:'Углы (ЛН, ПН)', hint:'Угловой шкаф: проверь 90° обеих стен', critical:false},
|
||
{label:'R1 — Розетки в зоне шкафа', hint:'Не закрыть — горизонталь + высота', critical:false},
|
||
{label:'Sw1 / Ld1 — Выключатель', hint:'Положение на стене, высота от пола', critical:false},
|
||
],
|
||
tips: [
|
||
'Плинтус часто забывают — без подрезки цоколя шкаф не встанет вплотную',
|
||
'Угловой шкаф: замерь обе стены и проверь угол — 90° не всегда 90°',
|
||
'Высоту потолка снимай у стены, а не в центре — там может быть перепад',
|
||
],
|
||
source: 'ЧЕКЛИСТ_ЗАМЕРА v1.0'
|
||
},
|
||
'Прихожая': {
|
||
icon: '🚪',
|
||
params: [
|
||
{label:'Ширина стены', hint:'Между коробками / стенами — от базы', critical:true},
|
||
{label:'Глубина коридора', hint:'Свободная в самом узком месте', critical:true},
|
||
{label:'Высота потолка у стены', hint:'У стены установки шкафа', critical:true},
|
||
{label:'Плинтус', hint:'Высота + глубина', critical:true},
|
||
{label:'ДВ1 — Все проёмы', hint:'Ширина + высота + отступ от базы до края', critical:true},
|
||
{label:'ЭЩ — Электрощиток', hint:'Если в зоне шкафа: размеры + расположение', critical:false},
|
||
{label:'Sw1 / Ld1 — Выключатель', hint:'Положение, высота — не закрыть шкафом', critical:false},
|
||
{label:'J1 — Распаечная коробка', hint:'Есть ли на стене шкафа — позиция', critical:false},
|
||
],
|
||
tips: [
|
||
'Минимум под шкаф-купе: 600 мм шкаф + 450 мм проход = 1050 мм свободно',
|
||
'Электрощиток закрывать нельзя — уточни его наличие в зоне шкафа сразу',
|
||
'Глубину коридора снимай в самом узком месте, не в среднем',
|
||
],
|
||
source: 'ЧЕКЛИСТ_ЗАМЕРА v1.0'
|
||
},
|
||
'Ванная': {
|
||
icon: '🚿',
|
||
params: [
|
||
{label:'D1 / D1\' — Слив + фановая',hint:'Горизонталь от угла-базы + вертикаль от пола', critical:true},
|
||
{label:'Wc1 / Wh1 — Трубы', hint:'Вынос от стены + горизонталь от базы', critical:true},
|
||
{label:'Зона под тумбу / пенал', hint:'Ширина + глубина свободной зоны', critical:true},
|
||
{label:'Высота потолка', hint:'Для зеркал, шкафов, ниш', critical:false},
|
||
{label:'Rs1 — Силовая розетка', hint:'Под стиральную машину — позиция точно', critical:false},
|
||
{label:'R1 / Sw1 — Розетка, выкл.', hint:'Высота + горизонталь от базы', critical:false},
|
||
],
|
||
tips: [
|
||
'В плитке замеряй с учётом её толщины — обычно 10–12 мм на сторону',
|
||
'D1\' (фановая труба) определяет минимальную высоту тумбы — зафиксируй точно',
|
||
'Wc1/Wh1 часто смещены от угла асимметрично — не угадывай, замерь',
|
||
],
|
||
source: 'ЧЕКЛИСТ_ЗАМЕРА v1.0'
|
||
},
|
||
'Гардероб': {
|
||
icon: '👔',
|
||
params: [
|
||
{label:'Все 4 стены × 3 точки', hint:'Ширина у пола, середины, потолка каждой стены', critical:true},
|
||
{label:'Высота потолка у каждой стены',hint:'Перепад возможен — снимай у стены', critical:true},
|
||
{label:'ДВ1 — Проём', hint:'Ширина + высота + положение на стене', critical:true},
|
||
{label:'Углы — все 4', hint:'В градусах; в гардеробе углы часто не 90°', critical:false},
|
||
{label:'V — Вентиляция', hint:'Есть ли продух, расположение, размер', critical:false},
|
||
{label:'R1 / Sw1 / Ld1', hint:'Освещение и розетки внутри — позиции', critical:false},
|
||
],
|
||
tips: [
|
||
'Гардероб — самый сложный объект: каждая стена в трёх точках, без исключений',
|
||
'Минимальная глубина секции: 550 мм (плечики) + 50 мм зазор = 600 мм',
|
||
'Уточни у клиента: зона глажки? место чемодана? — это меняет всю планировку',
|
||
],
|
||
source: 'ЧЕКЛИСТ_ЗАМЕРА v1.0'
|
||
},
|
||
'Балкон / Лоджия': {
|
||
icon: '🌿',
|
||
params: [
|
||
{label:'Высота до балконной балки', hint:'До балки — не до потолка! Критично для шкафа', critical:true},
|
||
{label:'Ширина × глубина', hint:'Ширина в 3 местах (часто трапеция)', critical:true},
|
||
{label:'Парапет', hint:'Высота + толщина + материал', critical:true},
|
||
{label:'БК1 — Балконный блок', hint:'Ширина + высота + тип открывания + вынос', critical:false},
|
||
{label:'R1 — Розетка', hint:'Есть ли, позиция', critical:false},
|
||
{label:'Ld1 — Освещение', hint:'Есть ли точка на потолке / стене', critical:false},
|
||
],
|
||
tips: [
|
||
'Балконная балка ограничивает высоту шкафа — замерь до неё, не до потолка',
|
||
'Трапеция: снимай ширину у стены дома, в середине и у остекления — три числа',
|
||
'Утеплён ли балкон — влияет на допустимые материалы мебели',
|
||
],
|
||
source: 'ЧЕКЛИСТ_ЗАМЕРА v1.0'
|
||
},
|
||
'Кабинет': {
|
||
icon: '💼',
|
||
params: [
|
||
{label:'Стена под стол / стеллаж', hint:'Ширина + высота; от какого угла база', critical:true},
|
||
{label:'Высота потолка у стены', hint:'У стены установки', critical:true},
|
||
{label:'ДВ1 — Проём', hint:'Ширина + высота + отступ от угла', critical:true},
|
||
{label:'R1 × N — Розетки', hint:'Рабочее место: мин. 4 шт. — позиции точно', critical:false},
|
||
{label:'Net1 — Интернет', hint:'Горизонталь от базы + высота от пола', critical:false},
|
||
{label:'Tv1 — ТВ / монитор', hint:'Позиция на стене если планируется', critical:false},
|
||
{label:'Ld1 — Освещение', hint:'Точка на потолке для рабочего света', critical:false},
|
||
],
|
||
tips: [
|
||
'Розетки рабочего стола закроет мебель — фиксируй точно, потом не доберёшься',
|
||
'Минимум 4 розетки + Net1 в зоне стола — уточни у клиента заранее',
|
||
'Плинтус и стяжка: если стеллаж до потолка — высота у стены критична',
|
||
],
|
||
source: 'ЧЕКЛИСТ_ЗАМЕРА v1.0'
|
||
},
|
||
'Детская': {
|
||
icon: '🧸',
|
||
params: [
|
||
{label:'Ширина стены под шкаф', hint:'Полная + за дверью если угловой', critical:true},
|
||
{label:'Высота потолка у стены', hint:'В старых домах потолок «гуляет» — снимай у стены',critical:true},
|
||
{label:'Плинтус', hint:'Высота + глубина', critical:true},
|
||
{label:'ДВ1 — Проём', hint:'Ширина + высота + отступ от угла', critical:true},
|
||
{label:'ОК1 — Окно', hint:'Высота подоконника + ширина + глубина', critical:false},
|
||
{label:'R1 — Розетки', hint:'Высота может быть нестандартной — уточни', critical:false},
|
||
{label:'Sw1 — Выключатели', hint:'У двери и у кровати — оба зафиксируй', critical:false},
|
||
],
|
||
tips: [
|
||
'Высота потолка у стены — особенно в старом доме: там перепады до 30 мм',
|
||
'Подоконник в детской часто используют как сиденье — уточни глубину у клиента',
|
||
'Розетки могут быть подняты «от детей» — зафиксируй фактическую высоту',
|
||
],
|
||
source: 'ЧЕКЛИСТ_ЗАМЕРА v1.0'
|
||
},
|
||
'Санузел': {
|
||
icon: '🪠',
|
||
params: [
|
||
{label:'D1 / D1\' — Слив + фановая',hint:'Горизонталь от угла-базы + вертикаль от пола', critical:true},
|
||
{label:'Wc1 / Wh1 — Трубы', hint:'Вынос от стены + горизонталь от базы', critical:true},
|
||
{label:'Rs1 — Силовая под стиралку', hint:'Есть ли, позиция — если совмещённый санузел', critical:true},
|
||
{label:'Зона под тумбу / машину', hint:'Ширина + глубина свободной зоны', critical:true},
|
||
{label:'Высота потолка', hint:'Для зеркала-шкафа, навесных элементов', critical:false},
|
||
{label:'R1 / Sw1 — Розетка, выкл.', hint:'Влагозащищённые зоны — позиции', critical:false},
|
||
],
|
||
tips: [
|
||
'D1\' (фановая) определяет высоту тумбы — зафиксируй без допущений',
|
||
'Стиральная машина: Rs1 обязательна, уточни зону заранее у клиента',
|
||
'В плитке — учитывай её толщину ~10–12 мм при измерении ниш и стен',
|
||
],
|
||
source: 'ЧЕКЛИСТ_ЗАМЕРА v1.0'
|
||
},
|
||
'Столовая': {
|
||
icon: '🍽️',
|
||
params: [
|
||
{label:'Зона стол + стулья', hint:'Мин. 800 мм прохода с каждой стороны стола', critical:true},
|
||
{label:'Стена под сервант / буфет', hint:'Ширина + высота; угловой — обе стены', critical:true},
|
||
{label:'Высота потолка у стены', hint:'У стены установки мебели', critical:true},
|
||
{label:'ДВ1 / ОК1 — Проёмы', hint:'Ширина + высота + отступ от базы', critical:false},
|
||
{label:'Ld1 — Освещение над столом',hint:'Точка на потолке — горизонталь и вертикаль', critical:false},
|
||
{label:'R1 × N — Розетки', hint:'Под торшер, зарядки, подсветку', critical:false},
|
||
],
|
||
tips: [
|
||
'Ld1 (светильник над столом) определяет положение стола — фиксируй точно',
|
||
'Минимум 750 мм от края стола до стены или мебели — проверь реальный размер',
|
||
'Угловой сервант: замерь обе стены и угол в градусах',
|
||
],
|
||
source: 'ЧЕКЛИСТ_ЗАМЕРА v1.0'
|
||
},
|
||
};
|
||
|
||
|
||
// Signature canvas
|
||
function initSigCanvas() {
|
||
var cv = document.getElementById('sig-cv');
|
||
if (!cv) return;
|
||
var ctx = cv.getContext('2d');
|
||
cv.width = cv.offsetWidth;
|
||
ctx.strokeStyle = '#1e1b4b';
|
||
ctx.lineWidth = 2.5;
|
||
ctx.lineCap = 'round';
|
||
ctx.lineJoin = 'round';
|
||
var drawing = false;
|
||
function pos(e) {
|
||
var r = cv.getBoundingClientRect();
|
||
var src = e.touches ? e.touches[0] : e;
|
||
return {x: (src.clientX - r.left) * (cv.width / r.width), y: (src.clientY - r.top) * (cv.height / r.height)};
|
||
}
|
||
function start(e) { e.preventDefault(); drawing = true; var p = pos(e); ctx.beginPath(); ctx.moveTo(p.x, p.y); }
|
||
function move(e) { e.preventDefault(); if (!drawing) return; var p = pos(e); ctx.lineTo(p.x, p.y); ctx.stroke(); markSigned(); }
|
||
function end(e) { drawing = false; }
|
||
function markSigned() {
|
||
window._calcSigned = true;
|
||
var btn = document.getElementById('sig-confirm-btn');
|
||
if (btn) { btn.disabled = false; btn.style.opacity = '1'; }
|
||
}
|
||
cv.addEventListener('touchstart', start, {passive:false});
|
||
cv.addEventListener('touchmove', move, {passive:false});
|
||
cv.addEventListener('touchend', end);
|
||
cv.addEventListener('mousedown', start);
|
||
cv.addEventListener('mousemove', move);
|
||
cv.addEventListener('mouseup', end);
|
||
}
|
||
|
||
// Init
|
||
navigate('home');
|
||
</script>
|
||
</body>
|
||
</html>
|
||
|
||
|