mirror of
https://github.com/wasrusgen/wasrusgen1-crm.git
synced 2026-06-03 23:04:46 +00:00
- 15 HTML mockups (admin, assembler, manager, owner и др.) - CLAUDE.md с политикой работы с файлами - .claude/launch.json Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1306 lines
85 KiB
HTML
1306 lines
85 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>
|
||
<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:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,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:600px}
|
||
#controls label{color:#fff;font-size:13px;font-weight:600}
|
||
#screenSelect{padding:8px 12px;border-radius:10px;border:none;background:#fff;font-size:13px;color:#333;cursor:pointer;min-width: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)}
|
||
.nav-fab{width:52px;height:52px;background:var(--accent);border-radius:50%;display:flex;align-items:center;justify-content:center;box-shadow:0 4px 16px rgba(0,0,0,.25);cursor:pointer;margin-top:-18px;flex-shrink:0}
|
||
.nav-fab svg{color:#fff;width:24px;height:24px}
|
||
|
||
.page{padding:0 0 80px;min-height:100%}
|
||
.page-header{display:flex;align-items:center;gap:12px;padding:16px 16px 12px;background:var(--card);border-bottom:1px solid rgba(0,0,0,.06);position:sticky;top:0;z-index:50}
|
||
.page-header h2{font-size:17px;font-weight:700;color:var(--ink);flex:1}
|
||
.back-btn{width:32px;height:32px;border-radius:50%;background:var(--bg);display:flex;align-items:center;justify-content:center;cursor:pointer;border:none;flex-shrink:0}
|
||
.back-btn svg{color:var(--accent);width:18px;height:18px}
|
||
.header-action{width:36px;height:36px;border-radius:50%;background:var(--bg);display:flex;align-items:center;justify-content:center;cursor:pointer;border:none}
|
||
.header-action svg{color:var(--accent);width:20px;height:20px}
|
||
|
||
.card{background:var(--card);border-radius:16px;box-shadow:0 2px 12px rgba(0,0,0,.07);padding:16px;margin-bottom:12px}
|
||
.card.warn-border{border-left:3px solid var(--warn)}
|
||
.card.success-border{border-left:3px solid var(--success)}
|
||
.card.accent-border{border-left:3px solid var(--accent)}
|
||
.card.danger-border{border-left:3px 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-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}
|
||
.btn-sm.outline{background:transparent;border:1.5px solid var(--accent);color:var(--accent)}
|
||
.btn-sm.success{background:var(--success);color:#fff}
|
||
|
||
.badge{display:inline-flex;align-items:center;padding:3px 10px;border-radius:20px;font-size:11px;font-weight:700;letter-spacing:.02em}
|
||
.badge.blue{background:#DBEAFE;color:#1D4ED8}
|
||
.badge.yellow{background:#FEF3C7;color:#D97706}
|
||
.badge.green{background:#DCFCE7;color:#15803D}
|
||
.badge.red{background:#FEE2E2;color:#DC2626}
|
||
.badge.gray{background:#F1F5F9;color:#64748B}
|
||
|
||
.form-group{margin-bottom:14px}
|
||
.form-group label{display:block;font-size:12px;font-weight:600;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:6px}
|
||
.form-input{width:100%;padding:12px 14px;border:1.5px solid #E2E8F0;border-radius:12px;font-size:15px;color:var(--ink);background:var(--card);outline:none}
|
||
.form-textarea{width:100%;padding:12px 14px;border:1.5px solid #E2E8F0;border-radius:12px;font-size:14px;color:var(--ink);background:var(--card);outline:none;resize:none;min-height:80px}
|
||
|
||
.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}
|
||
.avatar.green{background:var(--success)}
|
||
|
||
.chip-row{display:flex;gap:8px;overflow-x:auto;padding:0 16px;margin-bottom:8px;scrollbar-width:none}
|
||
.chip-row::-webkit-scrollbar{display:none}
|
||
.chip{padding:6px 14px;border-radius:20px;font-size:13px;font-weight:600;cursor:pointer;white-space:nowrap;border:1.5px solid #E2E8F0;background:var(--card);color:var(--muted);transition:all .2s}
|
||
.chip.active{background:var(--accent);color:#fff;border-color:var(--accent)}
|
||
|
||
.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}
|
||
|
||
.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:100px;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}
|
||
|
||
/* measure-specific */
|
||
.dim-row{display:flex;align-items:center;gap:8px;margin-bottom:10px}
|
||
.dim-label{font-size:13px;font-weight:600;color:var(--ink);width:100px;flex-shrink:0}
|
||
.dim-input-wrap{display:flex;align-items:center;gap:6px;flex:1}
|
||
.dim-input{width:100%;padding:10px 12px;border:1.5px solid #E2E8F0;border-radius:10px;font-size:15px;color:var(--ink);background:var(--card);outline:none;text-align:right}
|
||
.dim-unit{font-size:12px;color:var(--muted);font-weight:600;white-space:nowrap}
|
||
|
||
.step-progress{display:flex;align-items:center;gap:0;padding:12px 16px;background:var(--card);border-bottom:1px solid rgba(0,0,0,.06)}
|
||
.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:not(:last-child)::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);position:relative;z-index:1}
|
||
.step-dot.active{border-color:var(--accent);background:var(--accent);color:#fff}
|
||
.step-dot.done{border-color:var(--accent);background:var(--accent);color:#fff}
|
||
.step-name{font-size:10px;color:var(--muted);margin-top:4px;text-align:center;font-weight:500}
|
||
.step-name.active{color:var(--accent);font-weight:700}
|
||
|
||
.grid-sketch{display:grid;grid-template-columns:repeat(5,1fr);gap:2px;background:#E2E8F0;border-radius:10px;overflow:hidden;border:2px solid #E2E8F0}
|
||
.grid-cell{aspect-ratio:1;background:var(--bg);display:flex;align-items:center;justify-content:center;font-size:9px;color:var(--muted)}
|
||
.grid-cell.wall{background:#CBD5E1}
|
||
.grid-cell.niche{background:#BFDBFE}
|
||
.grid-cell.door{background:#BBF7D0}
|
||
.grid-cell.dim-label-cell{font-size:8px;color:var(--accent);font-weight:700;background:transparent}
|
||
|
||
/* dark theme fixes */
|
||
body[data-theme="dark"] .form-input,body[data-theme="dark"] .form-textarea,body[data-theme="dark"] .dim-input{background:#374151;border-color:#4B5563;color:#F9FAFB}
|
||
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:#1F2937;border-color:#374151}
|
||
body[data-theme="dark"] .bottom-nav{background:rgba(31,41,55,.95);border-color:#374151}
|
||
body[data-theme="dark"] #statusBar{background:#1F2937}
|
||
body[data-theme="dark"] .back-btn,body[data-theme="dark"] .header-action{background:#374151}
|
||
body[data-theme="dark"] .info-row{border-color:#374151}
|
||
body[data-theme="dark"] .step-progress{background:#1F2937;border-color:#374151}
|
||
body[data-theme="dark"] .step-dot{background:#1F2937;border-color:#374151}
|
||
body[data-theme="dark"] .grid-sketch{background:#374151;border-color:#374151}
|
||
body[data-theme="dark"] .grid-cell{background:#1F2937}
|
||
body[data-theme="dark"] .grid-cell.wall{background:#4B5563}
|
||
body[data-theme="dark"] .photo-slot{background:#374151;border-color:#4B5563}
|
||
|
||
@keyframes slideFade{from{opacity:0;transform:translateY(10px)}to{opacity:1;transform:translateY(0)}}
|
||
.page{animation:slideFade .18s ease-out}
|
||
</style>
|
||
</head>
|
||
<body data-theme="zov">
|
||
<div id="crm-back-nav" style="position:fixed;top:0;left:0;right:0;z-index:9999;background:rgba(255,255,255,0.92);backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);border-bottom:1px solid rgba(0,0,0,.08);padding:8px 16px;display:flex;align-items:center">
|
||
<a href="https://wasrusgen.github.io/wasrusgen1-crm/" style="display:inline-flex;align-items:center;gap:6px;font-family:Inter,system-ui,sans-serif;font-size:13px;font-weight:600;color:#003E7E;text-decoration:none;padding:4px 12px;border-radius:8px;background:#F0F4FF;transition:background .15s" onmouseover="this.style.background='#DDE8FF'" onmouseout="this.style.background='#F0F4FF'">
|
||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M19 12H5M12 5l-7 7 7 7"/></svg>
|
||
Все кабинеты
|
||
</a>
|
||
<span style="margin-left:12px;font-family:Inter,system-ui,sans-serif;font-size:12px;color:#8A94A6">@wasrusgen1 CRM</span>
|
||
</div>
|
||
<div style="height:40px"></div>
|
||
|
||
<div id="controls">
|
||
<label>Экран:</label>
|
||
<select id="screenSelect"></select>
|
||
<div id="themeButtons">
|
||
<button class="theme-btn active" data-t="zov" onclick="setTheme('zov')">CRM</button>
|
||
<button class="theme-btn" data-t="radar" onclick="setTheme('radar')">CRM</button>
|
||
<button class="theme-btn" data-t="dark" onclick="setTheme('dark')">Dark</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="phoneWrap">
|
||
<div id="phoneFrame">
|
||
<a href="./index.html" id="in-frame-back" style="display:flex;align-items:center;gap:6px;padding:5px 14px;background:rgba(0,62,126,.06);border-bottom:1px solid rgba(0,62,126,.09);font-family:Inter,system-ui,sans-serif;font-size:11px;font-weight:700;color:#003E7E;text-decoration:none;flex-shrink:0;z-index:200"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M19 12H5M12 5l-7 7 7 7"/></svg>Мокапы кабинетов</a>
|
||
<div id="statusBar">
|
||
<span>9:41</span>
|
||
<div class="sb-right">
|
||
<svg width="16" height="12" viewBox="0 0 16 12" fill="currentColor" style="color:var(--ink)"><rect x="0" y="4" width="3" height="8" rx="1"/><rect x="4.5" y="2.5" width="3" height="9.5" rx="1"/><rect x="9" y="0.5" width="3" height="11.5" rx="1"/><rect x="13.5" y="0" width="2.5" height="12" rx="1" opacity=".3"/></svg>
|
||
<svg width="15" height="12" viewBox="0 0 15 12" fill="none" stroke="currentColor" stroke-width="1.5" style="color:var(--ink)"><path d="M7.5 3C9.5 3 11.3 3.8 12.7 5.1M2.3 5.1C3.7 3.8 5.5 3 7.5 3"/><path d="M7.5 6.5C8.7 6.5 9.8 7 10.6 7.8M4.4 7.8C5.2 7 6.3 6.5 7.5 6.5"/><circle cx="7.5" cy="10" r="1" fill="currentColor"/></svg>
|
||
<svg width="25" height="12" viewBox="0 0 25 12" fill="none" style="color:var(--ink)"><rect x="0.5" y="0.5" width="21" height="11" rx="3" stroke="currentColor" stroke-opacity=".35"/><rect x="1.5" y="1.5" width="17" height="9" rx="2" fill="currentColor"/><path d="M23 4v4a2 2 0 000-4z" fill="currentColor" fill-opacity=".4"/></svg>
|
||
</div>
|
||
</div>
|
||
<div id="screen"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<script src="data.js"></script>
|
||
<script>
|
||
const SCREENS = {
|
||
home: 'Главная',
|
||
orders: 'Заявки на замер',
|
||
order_detail: 'Карточка заявки',
|
||
measure_form: 'Форма замера',
|
||
room_sketch: 'Схема помещения',
|
||
photo_room: 'Фото помещения',
|
||
report_preview: 'Предпросмотр отчёта',
|
||
report_send: 'Отправка менеджеру',
|
||
report_done: 'Успех отправки',
|
||
history: 'История замеров',
|
||
profile: 'Профиль замерщика',
|
||
my_questionnaire: 'Моя анкета',
|
||
schedule: 'Расписание',
|
||
accept_job: 'Входящая заявка',
|
||
};
|
||
|
||
let cur = 'home';
|
||
const sel = document.getElementById('screenSelect');
|
||
Object.entries(SCREENS).forEach(([k,v]) => {
|
||
const o = document.createElement('option');
|
||
o.value = k; o.textContent = v; sel.appendChild(o);
|
||
});
|
||
sel.addEventListener('change', e => go(e.target.value));
|
||
|
||
function go(id) {
|
||
cur = id; sel.value = id;
|
||
const s = document.getElementById('screen');
|
||
s.innerHTML = render(id);
|
||
s.scrollTop = 0;
|
||
}
|
||
|
||
function setTheme(t) {
|
||
document.body.dataset.theme = t;
|
||
document.querySelectorAll('.theme-btn').forEach(b => b.classList.toggle('active', b.dataset.t === t));
|
||
}
|
||
|
||
function nav(active) {
|
||
return `<div class="bottom-nav">
|
||
<div class="nav-item ${active==='home'?'active':''}" onclick="go('home')">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 9l9-7 9 7v11a2 2 0 01-2 2H5a2 2 0 01-2-2z"/><polyline points="9 22 9 12 15 12 15 22"/></svg>
|
||
<span>Главная</span>
|
||
</div>
|
||
<div class="nav-item ${active==='orders'?'active':''}" onclick="go('orders')">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2"/><rect x="9" y="3" width="6" height="4" rx="1"/><line x1="9" y1="12" x2="15" y2="12"/><line x1="9" y1="16" x2="12" y2="16"/></svg>
|
||
<span>Заявки</span>
|
||
</div>
|
||
<div class="nav-item" style="flex:0">
|
||
<div class="nav-fab" onclick="go('accept_job')">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>
|
||
</div>
|
||
</div>
|
||
<div class="nav-item ${active==='schedule'?'active':''}" onclick="go('schedule')">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="4" width="18" height="18" rx="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"/></svg>
|
||
<span>Расписание</span>
|
||
</div>
|
||
<div class="nav-item ${active==='profile'?'active':''}" onclick="go('profile')">
|
||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M20 21v-2a4 4 0 00-4-4H8a4 4 0 00-4 4v2"/><circle cx="12" cy="7" r="4"/></svg>
|
||
<span>Профиль</span>
|
||
</div>
|
||
</div>`;
|
||
}
|
||
|
||
function stepBar(active) {
|
||
const steps = [{k:'order_detail',n:'Данные'},{k:'measure_form',n:'Размеры'},{k:'photo_room',n:'Фото'},{k:'report_preview',n:'Отчёт'}];
|
||
const idx = steps.findIndex(s => s.k === active);
|
||
return `<div class="step-progress">${steps.map((s,i) => `
|
||
<div class="step-item ${i<idx?'done':i===idx?'active':''}">
|
||
<div class="step-dot ${i<idx?'done':i===idx?'active':''}">${i<idx?'✓':(i+1)}</div>
|
||
<div class="step-name ${i===idx?'active':''}">${s.n}</div>
|
||
</div>
|
||
`).join('')}</div>`;
|
||
}
|
||
|
||
function render(id) {
|
||
switch(id) {
|
||
case 'home': return screenHome();
|
||
case 'orders': return screenOrders();
|
||
case 'order_detail': return screenOrderDetail();
|
||
case 'measure_form': return screenMeasureForm();
|
||
case 'room_sketch': return screenRoomSketch();
|
||
case 'photo_room': return screenPhotoRoom();
|
||
case 'report_preview': return screenReportPreview();
|
||
case 'report_send': return screenReportSend();
|
||
case 'report_done': return screenReportDone();
|
||
case 'history': return screenHistory();
|
||
case 'profile': return screenProfile();
|
||
case 'my_questionnaire': return screenMyQuestionnaire();
|
||
case 'accept_job': return screenAcceptJob();
|
||
case 'schedule': return screenSchedule();
|
||
default: return '<div style="padding:40px;text-align:center;color:var(--muted)">Экран в разработке</div>';
|
||
}
|
||
}
|
||
|
||
// ── GPS: прибытие на объект замера ────────────────────────────────────────────
|
||
var _MSR_GPS_RADIUS = 300; // метров
|
||
var _MSR_JOBS = {
|
||
'З-1042': {addr:'ул. Рубинштейна, 7, кв. 18', lat:59.9295, lng:30.3426}
|
||
};
|
||
window._MSR_GPS = {};
|
||
window._MSR_GPS_DEMO = 'near';
|
||
|
||
function _msrGpsDist(la1,lo1,la2,lo2){
|
||
var R=6371000,dL=(la2-la1)*Math.PI/180,dG=(lo2-lo1)*Math.PI/180;
|
||
var a=Math.sin(dL/2)*Math.sin(dL/2)+Math.cos(la1*Math.PI/180)*Math.cos(la2*Math.PI/180)*Math.sin(dG/2)*Math.sin(dG/2);
|
||
return Math.round(R*2*Math.atan2(Math.sqrt(a),Math.sqrt(1-a)));
|
||
}
|
||
function _msrFmt(m){ return m<1000?(m+' м'):(m/1000).toFixed(1)+' км'; }
|
||
function _msrGpsResult(jobId,dist){
|
||
var ok=dist<=_MSR_GPS_RADIUS;
|
||
window._MSR_GPS[jobId]={ok:ok,dist:dist};
|
||
document.getElementById('screen').innerHTML=render('order_detail');
|
||
}
|
||
function _msrGpsRequest(jobId){
|
||
window._MSR_GPS[jobId]='loading';
|
||
document.getElementById('screen').innerHTML=render('order_detail');
|
||
var job=_MSR_JOBS[jobId];
|
||
function onGot(lat,lng){ _msrGpsResult(jobId,_msrGpsDist(lat,lng,job.lat,job.lng)); }
|
||
function onFail(){ _msrGpsResult(jobId, window._MSR_GPS_DEMO==='near'?54:1510); }
|
||
var tg=window.Telegram&&Telegram.WebApp;
|
||
if(tg&&tg.LocationManager&&tg.LocationManager.isInited){
|
||
tg.LocationManager.getLocation(function(r){ r&&r.latitude?onGot(r.latitude,r.longitude):onFail(); });
|
||
} else if(navigator.geolocation){
|
||
navigator.geolocation.getCurrentPosition(
|
||
function(p){ onGot(p.coords.latitude,p.coords.longitude); },
|
||
onFail,{timeout:8000,maximumAge:15000}
|
||
);
|
||
} else { onFail(); }
|
||
}
|
||
function _msrGpsForce(jobId){
|
||
var prev=window._MSR_GPS[jobId];
|
||
window._MSR_GPS[jobId]={ok:false,dist:prev&&prev.dist||0,forced:true};
|
||
document.getElementById('screen').innerHTML=render('order_detail');
|
||
}
|
||
function _msrGpsCancelCheck(jobId){
|
||
window._MSR_GPS[jobId]=null;
|
||
document.getElementById('screen').innerHTML=render('order_detail');
|
||
}
|
||
|
||
/* ─── HOME ─── */
|
||
function screenHome() {
|
||
return `<div class="page">
|
||
<div style="background:linear-gradient(135deg,var(--accent) 0%,var(--accent2) 100%);padding:20px 16px 28px">
|
||
<div style="font-size:12px;color:rgba(255,255,255,.65);font-weight:600;text-transform:uppercase;letter-spacing:.05em;margin-bottom:4px">Четверг, 21 мая 2026</div>
|
||
<div style="font-size:22px;font-weight:800;color:#fff;margin-bottom:2px">Привет, Игорь 📐</div>
|
||
<div style="font-size:14px;color:rgba(255,255,255,.75)">3 замера сегодня · 1 активный</div>
|
||
<div style="display:flex;gap:12px;margin-top:16px">
|
||
<div style="background:rgba(255,255,255,.15);border-radius:12px;padding:10px 14px;flex:1;text-align:center">
|
||
<div style="font-size:22px;font-weight:800;color:#fff">3</div>
|
||
<div style="font-size:11px;color:rgba(255,255,255,.7)">Сегодня</div>
|
||
</div>
|
||
<div style="background:rgba(255,255,255,.15);border-radius:12px;padding:10px 14px;flex:1;text-align:center">
|
||
<div style="font-size:22px;font-weight:800;color:#fff">1</div>
|
||
<div style="font-size:11px;color:rgba(255,255,255,.7)">В процессе</div>
|
||
</div>
|
||
<div style="background:rgba(255,255,255,.15);border-radius:12px;padding:10px 14px;flex:1;text-align:center">
|
||
<div style="font-size:22px;font-weight:800;color:#fff">2</div>
|
||
<div style="font-size:11px;color:rgba(255,255,255,.7)">Ожидают</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div style="padding:0 16px;margin-top:-14px">
|
||
<div class="card accent-border" onclick="go('order_detail')" style="cursor:pointer">
|
||
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:10px">
|
||
<span class="badge yellow">В процессе</span>
|
||
<span style="font-size:12px;color:var(--muted)">Замер #З-1042</span>
|
||
</div>
|
||
<div style="font-size:16px;font-weight:700;color:var(--ink);margin-bottom:4px">Смирнова Ольга</div>
|
||
<div style="font-size:13px;color:var(--muted);margin-bottom:12px">📍 ул. Рубинштейна, 7, кв. 18 · Сегодня, 10:30</div>
|
||
<div style="margin-bottom:12px">
|
||
<div style="display:flex;justify-content:space-between;margin-bottom:4px">
|
||
<span style="font-size:12px;color:var(--muted)">Прогресс замера</span>
|
||
<span style="font-size:12px;font-weight:700;color:var(--ink)">Шаг 2 / 4</span>
|
||
</div>
|
||
<div class="progress-bar"><div class="progress-fill" style="width:50%"></div></div>
|
||
</div>
|
||
<div style="display:flex;gap:8px">
|
||
<button class="btn-sm" onclick="event.stopPropagation();go('measure_form')">Продолжить замер</button>
|
||
<button class="btn-sm outline" onclick="event.stopPropagation();go('room_sketch')">Схема</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="section-label">Расписание на сегодня</div>
|
||
<div style="padding:0 16px">
|
||
${[
|
||
{id:'З-1043',name:'Корнилов Виктор',addr:'пр. Невский, 120, кв. 5',time:'13:00–14:00',type:'Гостиная + прихожая',badge:'blue',status:'Назначен'},
|
||
{id:'З-1044',name:'Лебедева Марина',addr:'ул. Восстания, 42, кв. 31',time:'16:00–17:00',type:'Спальня + гардероб',badge:'blue',status:'Назначен'},
|
||
].map(o => `
|
||
<div class="card" onclick="go('order_detail')" style="cursor:pointer">
|
||
<div style="display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:8px">
|
||
<div>
|
||
<div style="font-size:15px;font-weight:700;color:var(--ink)">${o.name}</div>
|
||
<div style="font-size:12px;color:var(--muted);margin-top:2px">📍 ${o.addr}</div>
|
||
</div>
|
||
<span class="badge ${o.badge}">${o.status}</span>
|
||
</div>
|
||
<div style="display:flex;align-items:center;justify-content:space-between">
|
||
<span style="font-size:12px;color:var(--muted)">⏰ ${o.time} · ${o.type}</span>
|
||
<span style="font-size:12px;color:var(--muted)">#${o.id}</span>
|
||
</div>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
|
||
<div class="section-label">Статистика месяца</div>
|
||
<div style="padding:0 16px">
|
||
<div class="earnings-card">
|
||
<div style="display:flex;justify-content:space-between;align-items:flex-start">
|
||
<div>
|
||
<div style="font-size:12px;color:rgba(255,255,255,.65);margin-bottom:4px">Май 2026</div>
|
||
<div style="font-size:28px;font-weight:800">18 600 ₽</div>
|
||
<div style="font-size:13px;color:rgba(255,255,255,.7);margin-top:4px">за 31 замер</div>
|
||
</div>
|
||
<div style="text-align:right">
|
||
<div style="font-size:12px;color:rgba(255,255,255,.65);margin-bottom:4px">Рейтинг</div>
|
||
<div style="font-size:28px;font-weight:800">4.8 ⭐</div>
|
||
<div style="font-size:13px;color:rgba(255,255,255,.7);margin-top:4px">из 5.0</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
${nav('home')}</div>`;
|
||
}
|
||
|
||
/* ─── ORDERS ─── */
|
||
function screenOrders() {
|
||
return `<div class="page">
|
||
<div class="page-header">
|
||
<h2>Заявки на замер</h2>
|
||
<div class="header-action"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg></div>
|
||
</div>
|
||
<div class="chip-row" style="margin-top:12px">
|
||
<div class="chip active">Все (7)</div>
|
||
<div class="chip">Сегодня (3)</div>
|
||
<div class="chip">Новые (2)</div>
|
||
<div class="chip">Выполнены (4)</div>
|
||
</div>
|
||
<div style="padding:0 16px">
|
||
${[
|
||
{id:'З-1042',name:'Смирнова Ольга',addr:'ул. Рубинштейна, 7',time:'10:30',type:'Кухня + гостиная',badge:'yellow',status:'В процессе',date:'Сегодня'},
|
||
{id:'З-1043',name:'Корнилов Виктор',addr:'пр. Невский, 120',time:'13:00',type:'Гостиная + прихожая',badge:'blue',status:'Назначен',date:'Сегодня'},
|
||
{id:'З-1044',name:'Лебедева Марина',addr:'ул. Восстания, 42',time:'16:00',type:'Спальня + гардероб',badge:'blue',status:'Назначен',date:'Сегодня'},
|
||
{id:'З-1040',name:'Захаров Илья',addr:'пр. Литейный, 58',time:'11:00',type:'2-комнатная кв. полный',badge:'green',status:'Выполнен',date:'20.05'},
|
||
{id:'З-1038',name:'Фёдорова Анна',addr:'ул. Садовая, 33',time:'14:30',type:'Детская + спальня',badge:'green',status:'Выполнен',date:'19.05'},
|
||
{id:'З-1036',name:'Попов Сергей',addr:'пр. Просвещения, 14',time:'10:00',type:'Кухня студия',badge:'green',status:'Выполнен',date:'17.05'},
|
||
{id:'З-1045',name:'Новикова Тамара',addr:'Каменноостровский, 9',time:'—',type:'Кухня + 2 спальни',badge:'red',status:'Новая',date:'Завтра'},
|
||
].map(o => `
|
||
<div class="card" onclick="go('order_detail')" style="cursor:pointer">
|
||
<div style="display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:8px">
|
||
<div style="flex:1">
|
||
<div style="font-size:15px;font-weight:700;color:var(--ink)">${o.name}</div>
|
||
<div style="font-size:12px;color:var(--muted);margin-top:2px">📍 ${o.addr}</div>
|
||
</div>
|
||
<span class="badge ${o.badge}">${o.status}</span>
|
||
</div>
|
||
<div style="display:flex;align-items:center;justify-content:space-between">
|
||
<span style="font-size:12px;color:var(--muted)">${o.date}${o.time!=='—'?' · ⏰ '+o.time:''} · ${o.type}</span>
|
||
<span style="font-size:11px;color:var(--muted)">#${o.id}</span>
|
||
</div>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
${nav('orders')}</div>`;
|
||
}
|
||
|
||
/* ─── ORDER DETAIL ─── */
|
||
function screenOrderDetail() {
|
||
return `<div class="page">
|
||
<div class="page-header">
|
||
<button class="back-btn" onclick="go('orders')"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="15 18 9 12 15 6"/></svg></button>
|
||
<h2>Заявка #З-1042</h2>
|
||
<div class="header-action"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="1"/><circle cx="19" cy="12" r="1"/><circle cx="5" cy="12" r="1"/></svg></div>
|
||
</div>
|
||
${stepBar('order_detail')}
|
||
|
||
${(function(){
|
||
var jobId='З-1042';
|
||
var gst=window._MSR_GPS[jobId];
|
||
if(!gst){
|
||
return '<div style="background:#F0FDF4;border-bottom:1.5px solid #86EFAC;padding:12px 16px">'
|
||
+'<div style="font-size:12px;color:#059669;font-weight:700;margin-bottom:6px">📍 Подтвердите прибытие на объект</div>'
|
||
+'<div style="font-size:12px;color:var(--muted);margin-bottom:10px">ул. Рубинштейна, 7, кв. 18 · Смирнова Ольга</div>'
|
||
+'<button onclick="_msrGpsRequest(\'З-1042\')" style="width:100%;padding:10px;border-radius:10px;border:none;background:#16A34A;color:#fff;font-size:13px;font-weight:700;cursor:pointer">📍 Я на месте</button>'
|
||
+'<div style="margin-top:7px;font-size:10px;color:#94A3B8">Демо: '
|
||
+'<span onclick="window._MSR_GPS_DEMO=\'near\';document.getElementById(\'screen\').innerHTML=render(\'order_detail\')" style="cursor:pointer;font-weight:700;color:'+(window._MSR_GPS_DEMO==='near'?'#16A34A':'#94A3B8')+'">Рядом</span> · '
|
||
+'<span onclick="window._MSR_GPS_DEMO=\'far\';document.getElementById(\'screen\').innerHTML=render(\'order_detail\')" style="cursor:pointer;font-weight:700;color:'+(window._MSR_GPS_DEMO==='far'?'#DC2626':'#94A3B8')+'">Далеко</span></div>'
|
||
+'</div>';
|
||
} else if(gst==='loading'){
|
||
return '<div style="background:#F8FAFC;border-bottom:1px solid #E2E8F0;padding:14px 16px;text-align:center;font-size:14px;color:var(--muted);font-weight:600">'
|
||
+'<span style="display:inline-block;animation:spin 1s linear infinite;margin-right:8px">⏱</span>Определяем местоположение…</div>';
|
||
} else if(gst&&!gst.ok&&!gst.forced){
|
||
return '<div style="background:rgba(245,158,11,.1);border-bottom:1.5px solid rgba(245,158,11,.4);padding:13px 16px">'
|
||
+'<div style="font-size:13px;font-weight:700;color:var(--warn);margin-bottom:4px">⚠️ Вы в '+_msrFmt(gst.dist)+' от объекта</div>'
|
||
+'<div style="font-size:12px;color:var(--muted);margin-bottom:10px">ул. Рубинштейна, 7 · Допустимый радиус: 300 м</div>'
|
||
+'<div style="display:flex;gap:8px">'
|
||
+'<button onclick="_msrGpsForce(\'З-1042\')" style="flex:1;padding:9px;border-radius:10px;border:none;background:#F59E0B;color:#fff;font-size:13px;font-weight:700;cursor:pointer">Отметить всё равно</button>'
|
||
+'<button onclick="_msrGpsCancelCheck(\'З-1042\')" style="padding:9px 14px;border-radius:10px;border:1px solid #E2E8F0;background:transparent;font-size:13px;font-weight:600;color:var(--muted);cursor:pointer">Отмена</button>'
|
||
+'</div></div>';
|
||
} else {
|
||
var chip=gst.forced
|
||
?'<span style="font-size:10px;background:rgba(245,158,11,.2);color:#D97706;border-radius:6px;padding:1px 6px;font-weight:700;margin-left:6px">⚡ вручную</span>'
|
||
:'<span style="font-size:10px;background:rgba(22,163,74,.15);color:#16A34A;border-radius:6px;padding:1px 6px;font-weight:700;margin-left:6px">GPS ✓</span>';
|
||
return '<div style="background:var(--warn);padding:10px 16px;display:flex;align-items:center;gap:8px">'
|
||
+'<span style="font-size:18px">⏱</span>'
|
||
+'<div><div style="font-size:13px;font-weight:700;color:#fff;display:flex;align-items:center">В процессе · начато в 10:35'+chip+'</div>'
|
||
+'<div style="font-size:12px;color:rgba(255,255,255,.8)">Прошло 47 минут</div></div></div>';
|
||
}
|
||
})()}
|
||
|
||
<div style="padding:12px 16px 0">
|
||
<div class="card">
|
||
<div style="font-size:13px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:12px">Клиент</div>
|
||
<div class="info-row"><span class="info-label">Имя</span><span class="info-val">Смирнова Ольга</span></div>
|
||
<div class="info-row"><span class="info-label">Телефон</span><span class="info-val" style="color:var(--accent)">+7 921 312-45-67</span></div>
|
||
<div class="info-row"><span class="info-label">Адрес</span><span class="info-val">ул. Рубинштейна, 7, кв. 18</span></div>
|
||
<div class="info-row"><span class="info-label">Время</span><span class="info-val">Сегодня 10:30–12:00</span></div>
|
||
<div class="info-row"><span class="info-label">Менеджер</span><span class="info-val">Елена Морозова</span></div>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<div style="font-size:13px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:12px">Объект замера</div>
|
||
<div class="info-row"><span class="info-label">Тип</span><span class="info-val">Квартира, вторичка</span></div>
|
||
<div class="info-row"><span class="info-label">Помещения</span><span class="info-val">Кухня + гостиная</span></div>
|
||
<div class="info-row"><span class="info-label">Этаж</span><span class="info-val">4 из 6</span></div>
|
||
<div class="info-row"><span class="info-label">Лифт</span><span class="info-val">Есть</span></div>
|
||
<div class="info-row"><span class="info-label">Состояние</span><span class="info-val">После ремонта</span></div>
|
||
</div>
|
||
|
||
<!-- Документы к заявке -->
|
||
<div class="card">
|
||
<div style="font-size:13px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:10px">Документы</div>
|
||
<div style="display:flex;gap:7px">
|
||
<a href="#" onclick="event.preventDefault();alert('📋 Бриф клиента…\\nВ продакшне — открытие из облака')" style="flex:1;display:flex;align-items:center;gap:6px;padding:9px 10px;background:#EFF6FF;border:1px solid #BFDBFE;border-radius:10px;text-decoration:none">
|
||
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="#1D4ED8" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="9" y1="13" x2="15" y2="13"/><line x1="9" y1="17" x2="13" y2="17"/></svg>
|
||
<div><div style="font-size:11px;font-weight:700;color:#1D4ED8">Бриф клиента</div><div style="font-size:10px;color:#93C5FD">PDF · 0.8 МБ</div></div>
|
||
</a>
|
||
<a href="#" onclick="event.preventDefault();alert('📐 Планировка квартиры…\\nВ продакшне — открытие из облака')" style="flex:1;display:flex;align-items:center;gap:6px;padding:9px 10px;background:#F0FDF4;border:1px solid #BBF7D0;border-radius:10px;text-decoration:none">
|
||
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="#15803D" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2"/><line x1="9" y1="3" x2="9" y2="21"/><line x1="3" y1="9" x2="21" y2="9"/></svg>
|
||
<div><div style="font-size:11px;font-weight:700;color:#15803D">Планировка</div><div style="font-size:10px;color:#86EFAC">PDF · 3 листа</div></div>
|
||
</a>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="card" style="background:var(--bg)">
|
||
<div style="font-size:13px;color:var(--muted);margin-bottom:4px;font-weight:600">Примечание менеджера</div>
|
||
<div style="font-size:14px;color:var(--ink);line-height:1.5">Клиент хочет угловую кухню 2,8×3,8 м. Уточнить расположение розеток и вентиляции. Есть ниша под холодильник — обязательно замерить.</div>
|
||
</div>
|
||
|
||
<button class="btn-primary" onclick="go('measure_form')">📐 Начать замер</button>
|
||
<button class="btn-secondary" onclick="go('room_sketch')">🗺 Схема помещения</button>
|
||
</div>
|
||
${nav('orders')}</div>`;
|
||
}
|
||
|
||
/* ─── MEASURE FORM ─── */
|
||
function screenMeasureForm() {
|
||
return `<div class="page">
|
||
<div class="page-header">
|
||
<button class="back-btn" onclick="go('order_detail')"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="15 18 9 12 15 6"/></svg></button>
|
||
<h2>Форма замера</h2>
|
||
</div>
|
||
${stepBar('measure_form')}
|
||
|
||
<div style="padding:12px 16px 0">
|
||
<div style="font-size:13px;color:var(--muted);margin-bottom:16px">Замер #З-1042 · Смирнова · Кухня</div>
|
||
|
||
<div class="card">
|
||
<div style="font-size:13px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:14px">Стены</div>
|
||
|
||
${[
|
||
{label:'Стена A (фасад)', w:'3850', note:''},
|
||
{label:'Стена B (правая)', w:'4200', note:''},
|
||
{label:'Стена C (задняя)', w:'3850', note:''},
|
||
{label:'Стена D (левая)', w:'4200', note:''},
|
||
].map(s => `
|
||
<div style="margin-bottom:14px;padding-bottom:14px;border-bottom:1px solid rgba(0,0,0,.06)">
|
||
<div style="font-size:13px;font-weight:700;color:var(--ink);margin-bottom:8px">${s.label}</div>
|
||
<div class="dim-row">
|
||
<span class="dim-label">Длина</span>
|
||
<div class="dim-input-wrap">
|
||
<input type="number" class="dim-input" value="${s.w}" placeholder="0">
|
||
<span class="dim-unit">мм</span>
|
||
</div>
|
||
</div>
|
||
<div class="dim-row">
|
||
<span class="dim-label">Высота</span>
|
||
<div class="dim-input-wrap">
|
||
<input type="number" class="dim-input" value="2680" placeholder="0">
|
||
<span class="dim-unit">мм</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
|
||
<div class="card">
|
||
<div style="font-size:13px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:14px">Входная группа</div>
|
||
<div class="dim-row">
|
||
<span class="dim-label">Ширина дверного проёма</span>
|
||
<div class="dim-input-wrap">
|
||
<input type="number" class="dim-input" value="900" placeholder="0">
|
||
<span class="dim-unit">мм</span>
|
||
</div>
|
||
</div>
|
||
<div class="dim-row">
|
||
<span class="dim-label">Высота проёма</span>
|
||
<div class="dim-input-wrap">
|
||
<input type="number" class="dim-input" value="2100" placeholder="0">
|
||
<span class="dim-unit">мм</span>
|
||
</div>
|
||
</div>
|
||
<div class="dim-row">
|
||
<span class="dim-label">Откос (толщина стены)</span>
|
||
<div class="dim-input-wrap">
|
||
<input type="number" class="dim-input" value="180" placeholder="0">
|
||
<span class="dim-unit">мм</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<div style="font-size:13px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:14px">Особенности</div>
|
||
${[
|
||
{name:'Ниша под холодильник', dims:'650 × 680 × 2100', active:true},
|
||
{name:'Вентиляционный короб', dims:'300 × 250', active:true},
|
||
{name:'Трубы (диаметр / вынос)', dims:'50 мм / 80 мм', active:false},
|
||
{name:'Подоконник', dims:'1200 × 320', active:false},
|
||
].map(f => `
|
||
<div style="display:flex;align-items:center;gap:10px;margin-bottom:12px">
|
||
<div style="width:20px;height:20px;border-radius:5px;${f.active?'background:var(--accent)':'border:2px solid #CBD5E1'};display:flex;align-items:center;justify-content:center;flex-shrink:0;cursor:pointer">
|
||
${f.active?'<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="3"><polyline points="20 6 9 17 4 12"/></svg>':''}
|
||
</div>
|
||
<div style="flex:1">
|
||
<div style="font-size:14px;font-weight:600;color:var(--ink)">${f.name}</div>
|
||
${f.active?`<div style="font-size:12px;color:var(--muted)">${f.dims}</div>`:''}
|
||
</div>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label>Комментарий замерщика</label>
|
||
<textarea class="form-textarea" placeholder="Особенности, которые важно учесть при проектировании...">Ниша под холодильник нестандартная — 650 мм. Вентиляция выходит в угол, короб придётся обходить.</textarea>
|
||
</div>
|
||
|
||
<button class="btn-primary" onclick="go('room_sketch')">Далее: Схема →</button>
|
||
</div>
|
||
${nav('orders')}</div>`;
|
||
}
|
||
|
||
/* ─── ROOM SKETCH ─── */
|
||
function screenRoomSketch() {
|
||
return `<div class="page">
|
||
<div class="page-header">
|
||
<button class="back-btn" onclick="go('measure_form')"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="15 18 9 12 15 6"/></svg></button>
|
||
<h2>Схема помещения</h2>
|
||
</div>
|
||
${stepBar('measure_form')}
|
||
|
||
<div style="padding:12px 16px 0">
|
||
<div style="font-size:13px;color:var(--muted);margin-bottom:12px">Кухня · З-1042 · Смирнова</div>
|
||
|
||
<div class="card">
|
||
<div style="font-size:13px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:12px">Схема 5×5 (план)</div>
|
||
|
||
<!-- dimension labels top -->
|
||
<div style="display:grid;grid-template-columns:20px repeat(5,1fr);gap:2px;margin-bottom:2px">
|
||
<div></div>
|
||
<div style="text-align:center;font-size:9px;color:var(--accent);font-weight:700">770</div>
|
||
<div style="text-align:center;font-size:9px;color:var(--accent);font-weight:700">770</div>
|
||
<div style="text-align:center;font-size:9px;color:var(--accent);font-weight:700">770</div>
|
||
<div style="text-align:center;font-size:9px;color:var(--accent);font-weight:700">770</div>
|
||
<div style="text-align:center;font-size:9px;color:var(--accent);font-weight:700">770</div>
|
||
</div>
|
||
|
||
<div style="display:grid;grid-template-columns:20px repeat(5,1fr);gap:2px">
|
||
<!-- row labels -->
|
||
<div style="display:flex;flex-direction:column;gap:2px;justify-content:space-around">
|
||
${['840','840','840','840','840'].map(v=>`<div style="font-size:9px;color:var(--accent);font-weight:700;text-align:right;padding-right:2px">${v}</div>`).join('')}
|
||
</div>
|
||
<!-- grid cells -->
|
||
${(() => {
|
||
const grid = [
|
||
['wall','wall','wall','wall','wall'],
|
||
['wall','','','niche','wall'],
|
||
['wall','','','niche','wall'],
|
||
['wall','','','','wall'],
|
||
['wall','door','door','wall','wall'],
|
||
];
|
||
const labels = {
|
||
'0,0':'┌','0,4':'┐','4,0':'└','4,3':'🚪','1,3':'Ниша','2,3':''};
|
||
return grid.map((row,ri) =>
|
||
row.map((cell,ci) => {
|
||
const lbl = labels[ri+','+ci]||'';
|
||
return `<div class="grid-cell ${cell}" style="border-radius:${ri===0&&ci===0?'8px 0 0 0':ri===0&&ci===4?'0 8px 0 0':ri===4&&ci===0?'0 0 0 8px':ri===4&&ci===4?'0 0 8px 0':''}0">${lbl}</div>`;
|
||
}).join('')
|
||
).join('');
|
||
})()}
|
||
</div>
|
||
|
||
<div style="display:flex;gap:8px;margin-top:12px;flex-wrap:wrap">
|
||
<div style="display:flex;align-items:center;gap:4px"><div style="width:12px;height:12px;background:#CBD5E1;border-radius:2px"></div><span style="font-size:11px;color:var(--muted)">Стена</span></div>
|
||
<div style="display:flex;align-items:center;gap:4px"><div style="width:12px;height:12px;background:#BFDBFE;border-radius:2px"></div><span style="font-size:11px;color:var(--muted)">Ниша</span></div>
|
||
<div style="display:flex;align-items:center;gap:4px"><div style="width:12px;height:12px;background:#BBF7D0;border-radius:2px"></div><span style="font-size:11px;color:var(--muted)">Дверь</span></div>
|
||
</div>
|
||
</div>
|
||
|
||
<div style="display:flex;gap:8px;margin-bottom:12px">
|
||
<button class="btn-sm outline" style="flex:1">+ Ниша</button>
|
||
<button class="btn-sm outline" style="flex:1">+ Колонна</button>
|
||
<button class="btn-sm outline" style="flex:1">+ Окно</button>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<div style="font-size:13px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:10px">Сводка размеров</div>
|
||
${[
|
||
['Стена A','3 850 мм'],['Стена B','4 200 мм'],['Стена C','3 850 мм'],['Стена D','4 200 мм'],
|
||
['Высота','2 680 мм'],['Ниша Ш×Г','650 × 680 мм'],['Дверной проём','900 мм'],
|
||
].map(([l,v])=>`<div class="info-row"><span class="info-label">${l}</span><span class="info-val" style="color:var(--accent);font-weight:700">${v}</span></div>`).join('')}
|
||
</div>
|
||
|
||
<button class="btn-primary" onclick="go('photo_room')">Далее: Фото →</button>
|
||
</div>
|
||
${nav('orders')}</div>`;
|
||
}
|
||
|
||
/* ─── PHOTO ROOM ─── */
|
||
function screenPhotoRoom() {
|
||
return `<div class="page">
|
||
<div class="page-header">
|
||
<button class="back-btn" onclick="go('room_sketch')"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="15 18 9 12 15 6"/></svg></button>
|
||
<h2>Фото помещения</h2>
|
||
</div>
|
||
${stepBar('photo_room')}
|
||
|
||
<div style="padding:12px 16px 0">
|
||
<div style="font-size:13px;color:var(--muted);margin-bottom:16px">Кухня · З-1042 · Сфотографируйте все зоны</div>
|
||
|
||
${[
|
||
{zone:'🚪 Входная группа', slots:[{emoji:'📷',filled:false},{emoji:'📷',filled:false}]},
|
||
{zone:'🧱 Основная стена (А)', slots:[{emoji:'🏠',filled:true},{emoji:'📷',filled:false}]},
|
||
{zone:'🪟 Окна / световой проём', slots:[{emoji:'🪟',filled:true},{emoji:'📷',filled:false}]},
|
||
{zone:'🟫 Пол и плинтус', slots:[{emoji:'📷',filled:false},{emoji:'📷',filled:false}]},
|
||
{zone:'🔍 Детали (ниша, вентиляция)', slots:[{emoji:'📦',filled:true},{emoji:'🔧',filled:true}]},
|
||
].map(s => `
|
||
<div style="margin-bottom:16px">
|
||
<div style="font-size:13px;font-weight:700;color:var(--ink);margin-bottom:8px">${s.zone}</div>
|
||
<div style="display:flex;gap:10px;align-items:center">
|
||
${s.slots.map(sl => `
|
||
<div class="photo-slot ${sl.filled?'filled':''}" onclick="">
|
||
${sl.filled
|
||
? `<span style="font-size:28px">${sl.emoji}</span>`
|
||
: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M23 19a2 2 0 01-2 2H3a2 2 0 01-2-2V8a2 2 0 012-2h4l2-3h6l2 3h4a2 2 0 012 2z"/><circle cx="12" cy="13" r="4"/></svg>`}
|
||
</div>
|
||
`).join('')}
|
||
<div style="flex:1">
|
||
<div style="font-size:12px;color:var(--muted)">${s.slots.filter(x=>x.filled).length} из ${s.slots.length} фото</div>
|
||
<div class="progress-bar" style="margin-top:4px;width:80px"><div class="progress-fill ${s.slots.filter(x=>x.filled).length===s.slots.length?'green':''}" style="width:${s.slots.filter(x=>x.filled).length/s.slots.length*100}%"></div></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`).join('')}
|
||
|
||
<div class="form-group" style="margin-top:8px">
|
||
<label>Комментарий к фото</label>
|
||
<textarea class="form-textarea" placeholder="Пояснения к снимкам...">Ниша сфотографирована с рулеткой для масштаба. Вентиляционный короб — угол левой стены.</textarea>
|
||
</div>
|
||
|
||
<button class="btn-primary" onclick="go('report_preview')">Далее: Отчёт →</button>
|
||
</div>
|
||
${nav('orders')}</div>`;
|
||
}
|
||
|
||
/* ─── REPORT PREVIEW ─── */
|
||
function screenReportPreview() {
|
||
return `<div class="page">
|
||
<div class="page-header">
|
||
<button class="back-btn" onclick="go('photo_room')"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="15 18 9 12 15 6"/></svg></button>
|
||
<h2>Предпросмотр отчёта</h2>
|
||
</div>
|
||
${stepBar('report_preview')}
|
||
|
||
<div style="padding:12px 16px 0">
|
||
<div class="card" style="background:linear-gradient(135deg,var(--accent),var(--accent2));color:#fff">
|
||
<div style="font-size:12px;color:rgba(255,255,255,.7);margin-bottom:4px">Отчёт по замеру</div>
|
||
<div style="font-size:18px;font-weight:800">З-1042 · Смирнова Ольга</div>
|
||
<div style="font-size:13px;color:rgba(255,255,255,.75);margin-top:4px">ул. Рубинштейна, 7, кв. 18 · 21.05.2026</div>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<div style="font-size:13px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:12px">Размеры помещения</div>
|
||
${[
|
||
['Стена A','3 850 мм'],['Стена B','4 200 мм'],['Стена C','3 850 мм'],['Стена D','4 200 мм'],
|
||
['Высота потолка','2 680 мм'],['Пл. пола (расчётная)','16,2 м²'],
|
||
].map(([l,v])=>`<div class="info-row"><span class="info-label">${l}</span><span class="info-val">${v}</span></div>`).join('')}
|
||
</div>
|
||
|
||
<div class="card">
|
||
<div style="font-size:13px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:12px">Особенности</div>
|
||
${[
|
||
['Ниша Ш×Г×В','650 × 680 × 2 100 мм'],
|
||
['Вентиляционный короб','300 × 250 мм · угол стены B'],
|
||
['Дверной проём','900 × 2 100 мм · откос 180 мм'],
|
||
['Подоконник','—'],
|
||
].map(([l,v])=>`<div class="info-row"><span class="info-label">${l}</span><span class="info-val">${v}</span></div>`).join('')}
|
||
</div>
|
||
|
||
<div class="card">
|
||
<div style="font-size:13px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:10px">Итог по погонным метрам</div>
|
||
${[
|
||
['Периметр рабочей зоны','14,5 п.м.'],
|
||
['Верхние шкафы (до ниши)','11,3 п.м.'],
|
||
['Нижние шкафы','9,8 п.м.'],
|
||
['Столешница','4,0 п.м.'],
|
||
].map(([l,v])=>`
|
||
<div style="display:flex;justify-content:space-between;padding:8px 0;border-bottom:1px solid rgba(0,0,0,.06)">
|
||
<span style="font-size:13px;color:var(--ink)">${l}</span>
|
||
<span style="font-size:14px;font-weight:800;color:var(--accent)">${v}</span>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
|
||
<div class="card">
|
||
<div style="font-size:13px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:10px">Фото (5 снимков)</div>
|
||
<div style="display:flex;gap:8px;overflow-x:auto;padding-bottom:4px">
|
||
${['🏠','🪟','📦','🔧','📷'].map((e,i) => `
|
||
<div style="width:68px;height:68px;border-radius:10px;background:linear-gradient(135deg,#E2E8F0,#CBD5E1);flex-shrink:0;display:flex;align-items:center;justify-content:center;font-size:24px">${e}</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label>Примечания замерщика</label>
|
||
<div style="background:var(--bg);padding:12px;border-radius:12px;font-size:14px;color:var(--ink);line-height:1.5">Ниша под холодильник 650 мм (нестандарт). Вентиляция в угол — короб обходить. Трубы под окном — вынос 80 мм.</div>
|
||
</div>
|
||
|
||
<button class="btn-primary" onclick="go('report_send')">Отправить менеджеру →</button>
|
||
<button class="btn-secondary" onclick="go('measure_form')">← Редактировать размеры</button>
|
||
</div>
|
||
${nav('orders')}</div>`;
|
||
}
|
||
|
||
/* ─── REPORT SEND ─── */
|
||
function screenReportSend() {
|
||
return `<div class="page">
|
||
<div class="page-header">
|
||
<button class="back-btn" onclick="go('report_preview')"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="15 18 9 12 15 6"/></svg></button>
|
||
<h2>Отправка менеджеру</h2>
|
||
</div>
|
||
${stepBar('report_preview')}
|
||
|
||
<div style="padding:16px">
|
||
<div class="form-group">
|
||
<label>Кому отправить</label>
|
||
${[
|
||
{name:'Елена Морозова',role:'Менеджер заявки',active:true},
|
||
{name:'Дизайн-отдел',role:'Проектировщики',active:false},
|
||
{name:'Директор',role:'Контроль качества',active:false},
|
||
].map(r => `
|
||
<div style="display:flex;align-items:center;gap:12px;padding:12px;border-radius:12px;border:1.5px solid ${r.active?'var(--accent)':'#E2E8F0'};background:${r.active?'#EFF6FF':'transparent'};margin-bottom:8px;cursor:pointer">
|
||
<div style="width:36px;height:36px;border-radius:50%;background:var(--accent);display:flex;align-items:center;justify-content:center;color:#fff;font-size:14px;font-weight:700">${r.name[0]}</div>
|
||
<div style="flex:1">
|
||
<div style="font-size:14px;font-weight:700;color:var(--ink)">${r.name}</div>
|
||
<div style="font-size:12px;color:var(--muted)">${r.role}</div>
|
||
</div>
|
||
<div style="width:20px;height:20px;border-radius:50%;border:2px solid ${r.active?'var(--accent)':'#CBD5E1'};background:${r.active?'var(--accent)':'transparent'};display:flex;align-items:center;justify-content:center">
|
||
${r.active?'<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="3"><polyline points="20 6 9 17 4 12"/></svg>':''}
|
||
</div>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label>Комментарий к отправке</label>
|
||
<textarea class="form-textarea" placeholder="Дополнительное сообщение менеджеру...">Замер выполнен. Обратите внимание на нишу под холодильник 650 мм — нестандартный размер. Все размеры и фото прикреплены.</textarea>
|
||
</div>
|
||
|
||
<div class="card" style="background:var(--bg);margin-bottom:16px">
|
||
<div style="display:flex;align-items:center;gap:10px">
|
||
<span style="font-size:20px">📎</span>
|
||
<div style="flex:1">
|
||
<div style="font-size:14px;font-weight:600;color:var(--ink)">Отчёт З-1042</div>
|
||
<div style="font-size:12px;color:var(--muted)">PDF · размеры + схема + 5 фото · ~2.4 МБ</div>
|
||
</div>
|
||
<span class="badge green">Готов</span>
|
||
</div>
|
||
</div>
|
||
|
||
<button class="btn-primary" style="background:var(--success)" onclick="go('report_done')">📤 Отправить отчёт</button>
|
||
<button class="btn-secondary" onclick="go('report_preview')">← Предпросмотр</button>
|
||
</div>
|
||
${nav('orders')}</div>`;
|
||
}
|
||
|
||
/* ─── REPORT DONE ─── */
|
||
function screenReportDone() {
|
||
return `<div class="page">
|
||
<div style="padding:60px 24px 24px;text-align:center">
|
||
<div style="width:84px;height:84px;background:#DCFCE7;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:44px;margin:0 auto 20px">✅</div>
|
||
<div style="font-size:24px;font-weight:800;color:var(--ink);margin-bottom:8px">Отчёт отправлен!</div>
|
||
<div style="font-size:15px;color:var(--muted);line-height:1.5;margin-bottom:32px">Менеджер <b style="color:var(--ink)">Елена Морозова</b> уведомлена. Замер #З-1042 завершён.</div>
|
||
|
||
<div class="card" style="text-align:left;margin-bottom:24px">
|
||
<div class="info-row"><span class="info-label">Заявка</span><span class="info-val">#З-1042</span></div>
|
||
<div class="info-row"><span class="info-label">Клиент</span><span class="info-val">Смирнова Ольга</span></div>
|
||
<div class="info-row"><span class="info-label">Объект</span><span class="info-val">Кухня · 16,2 м²</span></div>
|
||
<div class="info-row"><span class="info-label">Отправлено</span><span class="info-val" style="color:var(--success);font-weight:800">21.05.2026, 11:22</span></div>
|
||
<div class="info-row"><span class="info-label">Получатель</span><span class="info-val">Елена Морозова</span></div>
|
||
<div class="info-row"><span class="info-label">Фото</span><span class="info-val">5 снимков</span></div>
|
||
</div>
|
||
|
||
<button class="btn-primary" onclick="go('orders')">← К списку заявок</button>
|
||
<button class="btn-secondary" onclick="go('home')">На главную</button>
|
||
</div>
|
||
${nav('orders')}</div>`;
|
||
}
|
||
|
||
/* ─── HISTORY ─── */
|
||
function screenHistory() {
|
||
return `<div class="page">
|
||
<div class="page-header">
|
||
<h2>История замеров</h2>
|
||
<div class="header-action"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg></div>
|
||
</div>
|
||
|
||
<div style="padding:12px 16px 0">
|
||
<div style="position:relative;margin-bottom:12px">
|
||
<svg style="position:absolute;left:12px;top:50%;transform:translateY(-50%);color:var(--muted)" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
|
||
<input class="form-input" style="padding-left:38px" placeholder="Поиск по клиенту или адресу...">
|
||
</div>
|
||
</div>
|
||
|
||
<div class="chip-row">
|
||
<div class="chip active">Все (47)</div>
|
||
<div class="chip">Май (31)</div>
|
||
<div class="chip">Апрель (16)</div>
|
||
</div>
|
||
|
||
<div style="padding:0 16px">
|
||
<div class="section-label" style="margin-top:4px">21 мая 2026</div>
|
||
${[
|
||
{id:'З-1042',name:'Смирнова Ольга',addr:'ул. Рубинштейна, 7',type:'Кухня',pog:'14,5 п.м.',time:'10:30'},
|
||
].map(h => `
|
||
<div class="card success-border" onclick="go('report_preview')" style="cursor:pointer">
|
||
<div style="display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:8px">
|
||
<div>
|
||
<div style="font-size:15px;font-weight:700;color:var(--ink)">${h.name}</div>
|
||
<div style="font-size:12px;color:var(--muted);margin-top:2px">📍 ${h.addr} · ${h.time}</div>
|
||
</div>
|
||
<span class="badge green">Выполнен</span>
|
||
</div>
|
||
<div style="display:flex;justify-content:space-between;align-items:center">
|
||
<span style="font-size:12px;color:var(--muted)">${h.type} · #${h.id}</span>
|
||
<span style="font-size:13px;font-weight:700;color:var(--accent)">${h.pog}</span>
|
||
</div>
|
||
</div>
|
||
`).join('')}
|
||
|
||
<div class="section-label">20 мая 2026</div>
|
||
${[
|
||
{id:'З-1040',name:'Захаров Илья',addr:'пр. Литейный, 58',type:'2-комн. кв.',pog:'38,4 п.м.',time:'11:00'},
|
||
{id:'З-1039',name:'Попова Светлана',addr:'ул. Некрасова, 22',type:'Спальня + гардероб',pog:'12,1 п.м.',time:'14:00'},
|
||
].map(h => `
|
||
<div class="card success-border" onclick="go('report_preview')" style="cursor:pointer">
|
||
<div style="display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:8px">
|
||
<div>
|
||
<div style="font-size:15px;font-weight:700;color:var(--ink)">${h.name}</div>
|
||
<div style="font-size:12px;color:var(--muted);margin-top:2px">📍 ${h.addr} · ${h.time}</div>
|
||
</div>
|
||
<span class="badge green">Выполнен</span>
|
||
</div>
|
||
<div style="display:flex;justify-content:space-between;align-items:center">
|
||
<span style="font-size:12px;color:var(--muted)">${h.type} · #${h.id}</span>
|
||
<span style="font-size:13px;font-weight:700;color:var(--accent)">${h.pog}</span>
|
||
</div>
|
||
</div>
|
||
`).join('')}
|
||
|
||
<div class="section-label">19 мая 2026</div>
|
||
${[
|
||
{id:'З-1038',name:'Фёдорова Анна',addr:'ул. Садовая, 33',type:'Детская + спальня',pog:'22,6 п.м.',time:'14:30'},
|
||
{id:'З-1037',name:'Кузнецов Роман',addr:'Каменноостровский, 67',type:'Кухня-гостиная',pog:'16,8 п.м.',time:'10:00'},
|
||
].map(h => `
|
||
<div class="card success-border" onclick="go('report_preview')" style="cursor:pointer">
|
||
<div style="display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:8px">
|
||
<div>
|
||
<div style="font-size:15px;font-weight:700;color:var(--ink)">${h.name}</div>
|
||
<div style="font-size:12px;color:var(--muted);margin-top:2px">📍 ${h.addr} · ${h.time}</div>
|
||
</div>
|
||
<span class="badge green">Выполнен</span>
|
||
</div>
|
||
<div style="display:flex;justify-content:space-between;align-items:center">
|
||
<span style="font-size:12px;color:var(--muted)">${h.type} · #${h.id}</span>
|
||
<span style="font-size:13px;font-weight:700;color:var(--accent)">${h.pog}</span>
|
||
</div>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
${nav('history')}</div>`;
|
||
}
|
||
|
||
/* ─── PROFILE ─── */
|
||
function screenProfile() {
|
||
return `<div class="page">
|
||
<div class="page-header">
|
||
<h2>Профиль</h2>
|
||
<div class="header-action"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M11 4H4a2 2 0 00-2 2v14a2 2 0 002 2h14a2 2 0 002-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 013 3L12 15l-4 1 1-4 9.5-9.5z"/></svg></div>
|
||
</div>
|
||
<div style="padding:20px 16px 0">
|
||
|
||
<div style="display:flex;align-items:center;gap:16px;margin-bottom:20px">
|
||
<div class="avatar lg" style="background:var(--accent2);color:var(--ink);font-size:24px">ИС</div>
|
||
<div>
|
||
<div style="font-size:20px;font-weight:800;color:var(--ink)">Игорь Соколов</div>
|
||
<div style="font-size:14px;color:var(--muted)">Замерщик · Старший</div>
|
||
<div style="margin-top:6px;display:flex;gap:6px">
|
||
<span class="badge green">Активен</span>
|
||
<span class="badge blue">Опыт 4 года</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Категория и загрузка -->
|
||
<div class="card" style="padding:14px 16px;margin-bottom:12px">
|
||
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:10px">
|
||
<div style="display:flex;align-items:center;gap:8px">
|
||
<span style="background:#4338CA;color:#fff;font-size:11px;font-weight:800;padding:3px 10px;border-radius:20px;letter-spacing:.03em">Кат.А</span>
|
||
<span style="font-size:12px;color:var(--muted)">Присвоена 15.01.2026</span>
|
||
</div>
|
||
<button onclick="go('my_questionnaire')" style="font-size:12px;font-weight:700;color:var(--accent);background:rgba(0,62,126,.07);border:none;border-radius:8px;padding:5px 10px;cursor:pointer">Анкета →</button>
|
||
</div>
|
||
<div style="display:flex;gap:8px;margin-bottom:10px">
|
||
<div style="flex:1;background:rgba(67,56,202,.07);border-radius:8px;padding:8px 10px;text-align:center">
|
||
<div style="font-size:18px;font-weight:800;color:#4338CA">79%</div>
|
||
<div style="font-size:10px;color:var(--muted);font-weight:600;margin-top:1px">загрузка</div>
|
||
</div>
|
||
<div style="flex:1;background:rgba(0,62,126,.07);border-radius:8px;padding:8px 10px;text-align:center">
|
||
<div style="font-size:18px;font-weight:800;color:var(--accent)">600 ₽</div>
|
||
<div style="font-size:10px;color:var(--muted);font-weight:600;margin-top:1px">ставка/замер</div>
|
||
</div>
|
||
<div style="flex:1;background:rgba(16,185,129,.07);border-radius:8px;padding:8px 10px;text-align:center">
|
||
<div style="font-size:18px;font-weight:800;color:#059669">4/д</div>
|
||
<div style="font-size:10px;color:var(--muted);font-weight:600;margin-top:1px">макс. замеров</div>
|
||
</div>
|
||
</div>
|
||
<div style="height:6px;border-radius:99px;background:rgba(0,0,0,.07);overflow:hidden">
|
||
<div style="height:100%;width:79%;border-radius:99px;background:linear-gradient(90deg,#4338CA,#6366F1)"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="earnings-card">
|
||
<div style="display:flex;justify-content:space-between">
|
||
<div>
|
||
<div style="font-size:12px;color:rgba(255,255,255,.65);margin-bottom:4px">Май 2026</div>
|
||
<div style="font-size:28px;font-weight:800">18 600 ₽</div>
|
||
</div>
|
||
<div style="text-align:right">
|
||
<div style="font-size:12px;color:rgba(255,255,255,.65);margin-bottom:4px">Рейтинг</div>
|
||
<div style="font-size:28px;font-weight:800">4.8 ⭐</div>
|
||
</div>
|
||
</div>
|
||
<div style="display:flex;gap:16px;margin-top:14px;padding-top:14px;border-top:1px solid rgba(255,255,255,.2)">
|
||
<div><div style="font-size:11px;color:rgba(255,255,255,.6)">Замеров</div><div style="font-size:16px;font-weight:700">31</div></div>
|
||
<div><div style="font-size:11px;color:rgba(255,255,255,.6)">Отчётов</div><div style="font-size:16px;font-weight:700">29</div></div>
|
||
<div><div style="font-size:11px;color:rgba(255,255,255,.6)">Всего п.м.</div><div style="font-size:16px;font-weight:700">412 п.м.</div></div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<div style="font-size:13px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:12px">Контакты</div>
|
||
<div class="info-row"><span class="info-label">Телефон</span><span class="info-val" style="color:var(--accent)">+7 921 567-89-01</span></div>
|
||
<div class="info-row"><span class="info-label">Telegram</span><span class="info-val">@sokolov_msr</span></div>
|
||
<div class="info-row"><span class="info-label">Менеджер</span><span class="info-val">Елена Морозова</span></div>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<div style="font-size:13px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:12px">Статистика</div>
|
||
<div class="info-row"><span class="info-label">Всего замеров</span><span class="info-val">247 замеров</span></div>
|
||
<div class="info-row"><span class="info-label">Ср. время замера</span><span class="info-val">48 минут</span></div>
|
||
<div class="info-row"><span class="info-label">Точность</span><span class="info-val" style="color:var(--success)">98,4%</span></div>
|
||
<div class="info-row"><span class="info-label">Отчёты вовремя</span><span class="info-val" style="color:var(--success)">97,1%</span></div>
|
||
<div class="info-row"><span class="info-label">С нами с</span><span class="info-val">Июнь 2022</span></div>
|
||
</div>
|
||
|
||
<button class="btn-secondary" style="margin-bottom:8px">🚪 Выйти</button>
|
||
</div>
|
||
${nav('profile')}</div>`;
|
||
}
|
||
|
||
/* ─── MY QUESTIONNAIRE (Measurer) ─── */
|
||
function screenMyQuestionnaire() {
|
||
if (!window._qSaved) window._qSaved = false;
|
||
if (!window._qCar) window._qCar = 'yes';
|
||
if (!window._qEquip) window._qEquip = {laser:true,tape:true,level:true,digital:false,theodolite:false,photo360:false};
|
||
if (!window._qTypes) window._qTypes = {flat:true,house:false,office:true,commercial:false,newbuild:true};
|
||
if (!window._qZones) window._qZones = {center:true,north:true,south:false,east:false,west:false};
|
||
|
||
function chk(key, store, label, note) {
|
||
var on = window[store][key];
|
||
return '<label style="display:flex;align-items:flex-start;gap:10px;padding:9px 0;border-bottom:1px solid rgba(0,0,0,.05);cursor:pointer">'
|
||
+'<div onclick="(function(){window[\''+store+'\'][\''+key+'\']=!window[\''+store+'\'][\''+key+'\'];'
|
||
+'document.getElementById(\'screen\').innerHTML=renderScreen(\'my_questionnaire\')})()"'
|
||
+' style="width:20px;height:20px;border-radius:5px;border:2px solid '+(on?'var(--accent)':'#D1D5DB')+';background:'+(on?'var(--accent)':'transparent')+';flex-shrink:0;margin-top:1px;display:flex;align-items:center;justify-content:center">'
|
||
+(on?'<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="3"><polyline points="20 6 9 17 4 12"/></svg>':'')
|
||
+'</div>'
|
||
+'<div><div style="font-size:13px;color:var(--ink);font-weight:500">'+label+'</div>'
|
||
+(note?'<div style="font-size:11px;color:var(--muted);margin-top:1px">'+note+'</div>':'')
|
||
+'</div></label>';
|
||
}
|
||
|
||
var saved = window._qSaved;
|
||
|
||
return '<div class="page">'
|
||
+'<div class="page-header">'
|
||
+'<button class="back-btn" onclick="go(\'profile\')"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="15 18 9 12 15 6"/></svg></button>'
|
||
+'<h2>Моя анкета</h2>'
|
||
+'</div>'
|
||
+'<div style="padding:0 16px 20px">'
|
||
|
||
// Info banner
|
||
+'<div style="background:rgba(0,62,126,.06);border:1px solid rgba(0,62,126,.15);border-radius:10px;padding:10px 14px;margin:12px 0;display:flex;gap:8px;align-items:flex-start">'
|
||
+'<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="var(--accent)" stroke-width="2" style="flex-shrink:0;margin-top:1px"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="12"/><line x1="12" y1="16" x2="12.01" y2="16"/></svg>'
|
||
+'<div style="font-size:12px;color:var(--ink);line-height:1.5">Данные видны директору. При изменениях (новое оборудование, зона, тип объекта) — директор получит уведомление.</div>'
|
||
+'</div>'
|
||
|
||
// Equipment
|
||
+'<div class="card" style="padding:14px 16px;margin-bottom:10px">'
|
||
+'<div style="font-size:11px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:4px">Измерительное оборудование</div>'
|
||
+'<div style="font-size:11px;color:var(--muted);margin-bottom:10px">Что есть в наличии</div>'
|
||
+ chk('laser', '_qEquip', 'Лазерный дальномер', 'основной инструмент')
|
||
+ chk('tape', '_qEquip', 'Рулетка 5–10 м', null)
|
||
+ chk('level', '_qEquip', 'Нивелир / уровень', null)
|
||
+ chk('digital', '_qEquip', 'Цифровой угломер', 'для нестандартных углов')
|
||
+ chk('theodolite', '_qEquip', 'Теодолит / тахеометр', 'для больших объектов')
|
||
+ chk('photo360', '_qEquip', '360° камера', 'для фиксации помещения')
|
||
+'</div>'
|
||
|
||
// Object types
|
||
+'<div class="card" style="padding:14px 16px;margin-bottom:10px">'
|
||
+'<div style="font-size:11px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:10px">Типы объектов</div>'
|
||
+ chk('flat', '_qTypes', 'Квартиры', null)
|
||
+ chk('house', '_qTypes', 'Частные дома / коттеджи', null)
|
||
+ chk('office', '_qTypes', 'Офисы', null)
|
||
+ chk('commercial', '_qTypes', 'Коммерция (магазины, кафе)', null)
|
||
+ chk('newbuild', '_qTypes', 'Новостройки (без отделки)', 'сложные объекты')
|
||
+'</div>'
|
||
|
||
// Transport
|
||
+'<div class="card" style="padding:14px 16px;margin-bottom:10px">'
|
||
+'<div style="font-size:11px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:10px">Личный автомобиль</div>'
|
||
+'<div style="display:flex;gap:8px">'
|
||
+'<button onclick="(function(){window._qCar=\'yes\';document.getElementById(\'screen\').innerHTML=renderScreen(\'my_questionnaire\')})()"'
|
||
+' style="flex:1;padding:12px;border-radius:10px;border:2px solid '+(window._qCar==='yes'?'var(--accent)':'rgba(0,0,0,.1)')+';background:'+(window._qCar==='yes'?'rgba(0,62,126,.07)':'transparent')+';cursor:pointer;font-size:13px;font-weight:700;color:'+(window._qCar==='yes'?'var(--accent)':'var(--muted)')+'">'
|
||
+'🚗 Есть авто</button>'
|
||
+'<button onclick="(function(){window._qCar=\'no\';document.getElementById(\'screen\').innerHTML=renderScreen(\'my_questionnaire\')})()"'
|
||
+' style="flex:1;padding:12px;border-radius:10px;border:2px solid '+(window._qCar==='no'?'var(--accent)':'rgba(0,0,0,.1)')+';background:'+(window._qCar==='no'?'rgba(0,62,126,.07)':'transparent')+';cursor:pointer;font-size:13px;font-weight:700;color:'+(window._qCar==='no'?'var(--accent)':'var(--muted)')+'">'
|
||
+'🚌 Без авто</button>'
|
||
+'</div>'
|
||
+'</div>'
|
||
|
||
// Work zones
|
||
+'<div class="card" style="padding:14px 16px;margin-bottom:16px">'
|
||
+'<div style="font-size:11px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:4px">Зона работы</div>'
|
||
+'<div style="font-size:11px;color:var(--muted);margin-bottom:10px">Комфортные районы</div>'
|
||
+ chk('center', '_qZones', 'Центр города', null)
|
||
+ chk('north', '_qZones', 'Север', null)
|
||
+ chk('south', '_qZones', 'Юг', null)
|
||
+ chk('east', '_qZones', 'Восток', null)
|
||
+ chk('west', '_qZones', 'Запад / ЗАД', null)
|
||
+'</div>'
|
||
|
||
// Save
|
||
+(saved
|
||
? '<div style="background:rgba(16,185,129,.1);border:1px solid rgba(16,185,129,.3);border-radius:12px;padding:14px;text-align:center;font-size:14px;font-weight:700;color:#065f46">✓ Сохранено · Директор уведомлён</div>'
|
||
: '<button onclick="(function(){window._qSaved=true;document.getElementById(\'screen\').innerHTML=renderScreen(\'my_questionnaire\')})()"'
|
||
+' style="width:100%;padding:16px;background:var(--accent);color:#fff;border:none;border-radius:12px;font-size:15px;font-weight:700;cursor:pointer">Сохранить изменения</button>'
|
||
)
|
||
+'<div style="text-align:center;font-size:11px;color:var(--muted);margin-top:8px">Последнее обновление: 20.05.2026 · 09:30</div>'
|
||
+'</div>'
|
||
+ nav('profile')
|
||
+'</div>';
|
||
}
|
||
|
||
/* ─── ACCEPT JOB ─── */
|
||
function screenAcceptJob() {
|
||
const bk = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M15 18l-6-6 6-6"/></svg>';
|
||
return `<div class="page">
|
||
<div class="page-header">
|
||
<button class="back-btn" onclick="go('schedule')">${bk}</button>
|
||
<h2>Новая заявка</h2>
|
||
<span class="badge yellow" style="font-size:11px">Требует ответа</span>
|
||
</div>
|
||
<div style="padding:16px">
|
||
|
||
<div class="card accent-border">
|
||
<div style="font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.05em;color:var(--muted);margin-bottom:6px">Клиент</div>
|
||
<div style="font-size:20px;font-weight:800;color:var(--ink);margin-bottom:4px">Петрова Ирина Николаевна</div>
|
||
<div style="font-size:13px;color:var(--muted)">📍 ул. Бассейная, 12, кв. 7 · 3 этаж</div>
|
||
<div style="font-size:13px;color:var(--muted);margin-top:2px">📞 +7 921 876-54-32</div>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<div class="info-row"><span class="info-label">Тип</span><span class="info-val">Кухня-гостиная · ~22 м²</span></div>
|
||
<div class="info-row"><span class="info-label">Дата/время</span><span class="info-val" style="color:var(--accent);font-weight:700">22 мая (завтра) · 14:00–15:30</span></div>
|
||
<div class="info-row"><span class="info-label">До объекта</span><span class="info-val">~25 мин · 8 км</span></div>
|
||
<div class="info-row"><span class="info-label">Заказ</span><span class="info-val">#З-1045</span></div>
|
||
<div class="info-row"><span class="info-label">Менеджер</span><span class="info-val">Елена Морозова</span></div>
|
||
</div>
|
||
|
||
<div class="card warn-border">
|
||
<div style="font-size:12px;font-weight:700;color:var(--warn);text-transform:uppercase;letter-spacing:.05em;margin-bottom:6px">⚠️ Примечание</div>
|
||
<div style="font-size:13px;color:var(--ink);line-height:1.5">Стеклопакеты нестандартные — мансардный тип. Взять удлинённые дюбели и угловые адаптеры.</div>
|
||
</div>
|
||
|
||
<div style="background:linear-gradient(135deg,#E2E8F0,#F1F5F9);border-radius:14px;height:120px;display:flex;align-items:center;justify-content:center;margin-bottom:16px;position:relative;overflow:hidden;cursor:pointer">
|
||
<div style="font-size:36px">🗺️</div>
|
||
<div style="position:absolute;bottom:10px;left:12px;background:#fff;border-radius:8px;padding:4px 10px;font-size:12px;font-weight:700;color:var(--ink);box-shadow:0 2px 8px rgba(0,0,0,.15)">📍 ул. Бассейная, 12</div>
|
||
<div style="position:absolute;top:10px;right:12px;background:#fff;border-radius:8px;padding:4px 10px;font-size:11px;font-weight:600;color:var(--muted)">8 км · ~25 мин</div>
|
||
</div>
|
||
|
||
<button class="btn-primary" style="background:var(--success);margin-bottom:8px" onclick="go('schedule')">✅ Принять заявку</button>
|
||
<button class="btn-secondary" style="border-color:var(--danger);color:var(--danger)" onclick="go('schedule')">✕ Не смогу выехать</button>
|
||
</div>
|
||
${nav('')}</div>`;
|
||
}
|
||
|
||
/* ─── SCHEDULE ─── */
|
||
function screenSchedule() {
|
||
const days = [
|
||
{d:'Пн',n:19,count:2},{d:'Вт',n:20,count:3},{d:'Ср',n:21,active:true,count:3},
|
||
{d:'Чт',n:22,count:1,new:true},{d:'Пт',n:23,count:2},{d:'Сб',n:24,count:0},{d:'Вс',n:25,count:0}
|
||
];
|
||
const today = [
|
||
{time:'09:00','end':'10:30',name:'Корнилов Виктор',addr:'пр. Невский, 120, кв. 5',type:'Гостиная + прихожая',status:'done'},
|
||
{time:'13:00','end':'14:30',name:'Смирнова Ольга',addr:'ул. Рубинштейна, 7, кв. 18',type:'Кухня',status:'active'},
|
||
{time:'16:00','end':'17:30',name:'Лебедева Марина',addr:'ул. Восстания, 42, кв. 31',type:'Спальня + гардероб',status:'upcoming'},
|
||
];
|
||
return `<div class="page">
|
||
<div class="page-header">
|
||
<h2>Расписание</h2>
|
||
<span style="font-size:13px;color:var(--muted);font-weight:500">Май 2026</span>
|
||
</div>
|
||
|
||
<div style="background:var(--card);padding:12px 16px 14px;border-bottom:1px solid rgba(0,0,0,.06);display:flex;gap:4px">
|
||
${days.map(d => `
|
||
<div style="display:flex;flex-direction:column;align-items:center;gap:4px;flex:1;cursor:pointer">
|
||
<div style="font-size:10px;color:var(--muted);font-weight:600">${d.d}</div>
|
||
<div style="width:34px;height:34px;border-radius:50%;display:flex;align-items:center;justify-content:center;background:${d.active?'var(--accent)':'transparent'}">
|
||
<span style="font-size:13px;font-weight:700;color:${d.active?'#fff':'var(--ink)'}">${d.n}</span>
|
||
</div>
|
||
${d.count>0?`<div style="width:6px;height:6px;border-radius:50%;background:${d.new?'var(--warn)':d.active?'rgba(255,255,255,.6)':'var(--accent)'}"></div>`:'<div style="height:6px"></div>'}
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
|
||
<div class="section-label">Среда, 21 мая · 3 замера</div>
|
||
<div style="padding:0 16px">
|
||
${today.map(s => `
|
||
<div class="card ${s.status==='done'?'success-border':s.status==='active'?'accent-border':''}" onclick="go('order_detail')" style="cursor:pointer">
|
||
<div style="display:flex;gap:12px;align-items:flex-start">
|
||
<div style="min-width:58px;text-align:center">
|
||
<div style="font-size:12px;font-weight:700;color:${s.status==='active'?'var(--accent)':s.status==='done'?'var(--success)':'var(--muted)'}">${s.time}</div>
|
||
<div style="width:2px;height:18px;background:${s.status==='done'?'var(--success)':s.status==='active'?'var(--accent)':'#E2E8F0'};margin:4px auto"></div>
|
||
<div style="font-size:10px;color:var(--muted)">${s.end}</div>
|
||
</div>
|
||
<div style="flex:1">
|
||
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:3px">
|
||
<div style="font-size:15px;font-weight:700;color:var(--ink)">${s.name}</div>
|
||
<span class="badge ${s.status==='done'?'green':s.status==='active'?'blue':'gray'}">${s.status==='done'?'Выполнен':s.status==='active'?'Активный':'Назначен'}</span>
|
||
</div>
|
||
<div style="font-size:12px;color:var(--muted)">📍 ${s.addr}</div>
|
||
<div style="font-size:12px;color:var(--muted);margin-top:1px">🏠 ${s.type}</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
|
||
<div class="section-label">Четверг, 22 мая · 1 замер <span class="badge yellow" style="font-size:10px;margin-left:4px">Новая</span></div>
|
||
<div style="padding:0 16px">
|
||
<div class="card warn-border" onclick="go('accept_job')" style="cursor:pointer">
|
||
<div style="display:flex;gap:12px;align-items:flex-start">
|
||
<div style="min-width:58px;text-align:center">
|
||
<div style="font-size:12px;font-weight:700;color:var(--warn)">14:00</div>
|
||
</div>
|
||
<div style="flex:1">
|
||
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:3px">
|
||
<div style="font-size:15px;font-weight:700;color:var(--ink)">Петрова Ирина</div>
|
||
<span class="badge yellow">Требует ответа</span>
|
||
</div>
|
||
<div style="font-size:12px;color:var(--muted)">📍 ул. Бассейная, 12, кв. 7</div>
|
||
<div style="font-size:12px;color:var(--muted);margin-top:1px">🏠 Кухня-гостиная · ~22 м²</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="section-label">Статистика недели</div>
|
||
<div style="padding:0 16px 8px">
|
||
<div class="card">
|
||
<div style="display:flex;justify-content:space-around;text-align:center">
|
||
<div><div style="font-size:22px;font-weight:800;color:var(--ink)">11</div><div style="font-size:11px;color:var(--muted)">Замеров</div></div>
|
||
<div><div style="font-size:22px;font-weight:800;color:var(--ink)">6 600 ₽</div><div style="font-size:11px;color:var(--muted)">Заработано</div></div>
|
||
<div><div style="font-size:22px;font-weight:800;color:var(--success)">4.9 ⭐</div><div style="font-size:11px;color:var(--muted)">Рейтинг</div></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
${nav('schedule')}</div>`;
|
||
}
|
||
|
||
// init
|
||
go('home');
|
||
</script>
|
||
</body>
|
||
</html>
|
||
|
||
|