mirror of
https://github.com/wasrusgen/wasrusgen1-crm.git
synced 2026-06-03 15:04:47 +00:00
3973 lines
312 KiB
HTML
3973 lines
312 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{--accent:#003E7E;--accent2:#76BD22;--bg:#F5F6F8;--card:#FFFFFF;--ink:#1A1A2E;--muted:#8A94A6;--danger:#EF4444;--warn:#F59E0B;--success:#10B981;--s-success-bg:#ECFDF5;--s-warning-bg:#FFFBEB;--s-danger-bg:#FEF2F2;--s-info:#3B82F6;--s-info-bg:#EFF6FF}
|
||
body[data-theme="radar"]{--accent:#4338CA;--accent2:#6366F1;--bg:#F9FAFB;--card:#FFFFFF;--ink:#111827;--muted:#6B7280}
|
||
body[data-theme="dark"]{--accent:#4338CA;--accent2:#6366F1;--bg:#111827;--card:#1F2937;--ink:#F9FAFB;--muted:#9CA3AF}
|
||
|
||
#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: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}
|
||
.theme-btn[data-t="zov"]{background:#003E7E;color:#fff}
|
||
.theme-btn[data-t="radar"]{background:linear-gradient(135deg,#1E1B4B,#4338CA);color:#fff}
|
||
.theme-btn[data-t="dark"]{background:#111827;color:#6366F1}
|
||
|
||
#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}
|
||
#screen{flex:1;overflow-y:auto;overflow-x:hidden;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;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,.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-b{border-left:4px solid var(--warn)}
|
||
.card.ok-b{border-left:4px solid var(--success)}
|
||
.card.accent-b{border-left:4px solid var(--accent)}
|
||
.card.danger-b{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}
|
||
@keyframes spin{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}
|
||
@keyframes btn-shimmer{0%{background-position:250% center}100%{background-position:-250% center}}
|
||
@keyframes confirm-pulse{0%,100%{box-shadow:0 6px 20px rgba(0,62,126,.45),0 2px 8px rgba(0,62,126,.2),inset 0 1px 0 rgba(255,255,255,.15)}50%{box-shadow:0 14px 40px rgba(0,62,126,.7),0 5px 18px rgba(0,62,126,.38),inset 0 1px 0 rgba(255,255,255,.22)}}
|
||
@keyframes tech-select-pop{0%{transform:scale(.94)}50%{transform:scale(1.04)}100%{transform:scale(1)}}
|
||
.btn-primary{width:100%;background:linear-gradient(135deg,#002450 0%,#003E7E 30%,#1560BD 60%,#0A4DA8 90%,#002450 100%);background-size:280% auto;color:#fff;border:none;border-radius:14px;padding:15px 20px;font-size:15px;font-weight:700;cursor:pointer;box-shadow:0 6px 20px rgba(0,62,126,.38),0 2px 6px rgba(0,62,126,.18),inset 0 1px 0 rgba(255,255,255,.13);transition:transform .15s,box-shadow .15s;animation:btn-shimmer 5s ease infinite;letter-spacing:.01em;position:relative;overflow:hidden}
|
||
.btn-primary:active{transform:scale(.97);box-shadow:0 3px 10px rgba(0,62,126,.25)!important;animation:none}
|
||
.btn-primary:disabled{opacity:.38;cursor:default;animation:none;box-shadow:none;transform:none}
|
||
.btn-confirm{background:linear-gradient(135deg,#001E45 0%,#003E7E 20%,#1255A4 45%,#0D6EE8 65%,#1255A4 80%,#003E7E 100%)!important;background-size:300% auto!important;animation:btn-shimmer 3s linear infinite,confirm-pulse 2.5s ease-in-out infinite!important;font-size:16px!important;padding:17px 20px!important;border-radius:16px!important}
|
||
.btn-secondary{width:100%;background:transparent;color:var(--accent);border:2px solid var(--accent);border-radius:14px;padding:13px;font-size:15px;font-weight:700;cursor:pointer;margin-top:8px;transition:all .15s}
|
||
.btn-secondary:active{background:rgba(0,62,126,.06)}
|
||
.btn-sm{padding:8px 15px;font-size:12px;font-weight:700;border-radius:10px;cursor:pointer;border:none;background:linear-gradient(135deg,#003E7E,#1565C0);color:#fff;box-shadow:0 3px 10px rgba(0,62,126,.32);transition:transform .15s}
|
||
.btn-sm:active{transform:scale(.93)}
|
||
.btn-pill{display:inline-flex;align-items:center;gap:5px;padding:4px 10px;border-radius:20px;font-size:11px;font-weight:700;cursor:pointer;border:none;white-space:nowrap;flex-shrink:0;transition:opacity .15s}
|
||
.btn-pill:active{opacity:.7}
|
||
.btn-pill.accent{background:rgba(0,62,126,.1);color:var(--accent);border:1px solid rgba(0,62,126,.2)}
|
||
.btn-pill.muted{background:var(--bg);color:var(--muted);border:1px solid #E2E8F0}
|
||
.btn-pill.danger{background:rgba(239,68,68,.08);color:var(--danger);border:1px solid rgba(239,68,68,.2)}
|
||
.btn-pill.success{background:rgba(16,185,129,.08);color:var(--success);border:1px solid rgba(16,185,129,.2)}
|
||
.btn-action{flex:1;padding:10px;border-radius:12px;font-size:13px;font-weight:700;cursor:pointer;border:none;transition:opacity .15s;display:flex;align-items:center;justify-content:center;gap:6px}
|
||
.btn-action:active{opacity:.8}
|
||
.btn-action.ok{background:rgba(16,185,129,.1);color:var(--success);border:1.5px solid rgba(16,185,129,.25)}
|
||
.btn-action.warn{background:rgba(245,158,11,.1);color:var(--warn);border:1.5px solid rgba(245,158,11,.25)}
|
||
.btn-action.accent{background:rgba(0,62,126,.08);color:var(--accent);border:1.5px solid rgba(0,62,126,.18)}
|
||
.tech-pick-card{background:var(--card);border-radius:14px;padding:14px 10px;text-align:center;cursor:pointer;box-shadow:0 2px 8px rgba(0,0,0,.07);transition:transform .18s,box-shadow .18s,background .18s;border:2px solid transparent;position:relative}
|
||
.tech-pick-card:active{transform:scale(.95)}
|
||
.tech-pick-card.sel{background:linear-gradient(135deg,#002D5C,#003E7E,#1560BD);border-color:transparent;box-shadow:0 6px 18px rgba(0,62,126,.45),0 2px 8px rgba(0,62,126,.22);animation:tech-select-pop .25s ease}
|
||
.tech-pick-card.exists{background:#F1F5F9;border-color:#E2E8F0;opacity:.55;cursor:default}
|
||
|
||
.badge{display:inline-flex;align-items:center;padding:3px 9px;border-radius:20px;font-size:11px;font-weight:700}
|
||
.badge.yellow{background:#FEF3C7;color:#D97706}
|
||
.badge.green{background:#DCFCE7;color:#15803D}
|
||
.badge.blue{background:#DBEAFE;color:#1D4ED8}
|
||
.badge.red{background:#FEE2E2;color:#DC2626}
|
||
.badge.gray{background:#F1F5F9;color:#64748B}
|
||
.badge.teal{background:#CCFBF1;color:#0F766E}
|
||
.divider{height:1px;background:rgba(0,0,0,.07);margin:10px 0}
|
||
.row{display:flex;align-items:center;gap:10px}
|
||
.row.sb{justify-content:space-between}
|
||
|
||
/* Pipeline */
|
||
.pipeline{display:flex;align-items:flex-start;padding:4px 0;overflow-x:auto;scrollbar-width:none}
|
||
.pipeline::-webkit-scrollbar{display:none}
|
||
.pip-step{display:flex;flex-direction:column;align-items:center;gap:4px;flex-shrink:0;min-width:58px}
|
||
.pip-dot{width:26px;height:26px;border-radius:50%;border:2px solid #CBD5E1;background:var(--card);display:flex;align-items:center;justify-content:center;font-size:10px;font-weight:700}
|
||
.pip-dot.done{background:var(--success);border-color:var(--success);color:#fff}
|
||
.pip-dot.active{background:var(--accent);border-color:var(--accent);color:#fff}
|
||
.pip-dot.blocked{background:#FEF3C7;border-color:var(--warn);color:#92400E}
|
||
.pip-label{font-size:9px;color:var(--muted);font-weight:600;text-align:center;line-height:1.2;max-width:56px}
|
||
.pip-label.active{color:var(--accent)}
|
||
.pip-line{flex:1;height:2px;background:#E2E8F0;margin-top:12px;min-width:6px}
|
||
.pip-line.done{background:var(--success)}
|
||
|
||
/* Tech checklist */
|
||
.tech-item{display:flex;align-items:center;gap:12px;padding:10px 0;border-bottom:1px solid rgba(0,0,0,.06)}
|
||
.tech-item:last-child{border-bottom:none}
|
||
.tech-status{width:26px;height:26px;border-radius:8px;display:flex;align-items:center;justify-content:center;font-size:13px;flex-shrink:0;font-weight:700}
|
||
.tech-status.done{background:#DCFCE7;color:#15803D}
|
||
.tech-status.wait{background:#FEF3C7;color:#92400E}
|
||
.tech-status.na{background:#F1F5F9;color:#64748B}
|
||
|
||
/* Tech icon box */
|
||
.tech-icon-box{width:40px;height:40px;border-radius:11px;display:flex;align-items:center;justify-content:center;flex-shrink:0;padding:8px}
|
||
.tech-icon-box.wait{background:#FEF9EC;color:#D97706}
|
||
.tech-icon-box.done{background:#DCFCE7;color:#15803D}
|
||
.tech-icon-box.client{background:#EFF6FF;color:#1D4ED8}
|
||
/* Tech action buttons (3-column row) */
|
||
.tech-act-row{display:flex;gap:5px;margin-top:7px}
|
||
.tech-act-btn{flex:1;padding:7px 4px;border-radius:10px;border:none;font-size:11px;font-weight:700;cursor:pointer;transition:transform .12s;letter-spacing:.01em}
|
||
.tech-act-btn:active{transform:scale(.94)}
|
||
.tech-act-btn.ai{background:linear-gradient(135deg,#003E7E,#1565C0);color:#fff;box-shadow:0 2px 8px rgba(0,62,126,.3)}
|
||
.tech-act-btn.own{background:rgba(0,62,126,.1);color:var(--accent);border:1.5px solid rgba(0,62,126,.2);box-shadow:none}
|
||
.tech-act-btn.client{background:rgba(0,62,126,.18);color:var(--accent);border:1.5px solid rgba(0,62,126,.28);box-shadow:none}
|
||
|
||
/* Wizard */
|
||
.wiz-chip{padding:9px 15px;border-radius:24px;font-size:13px;font-weight:500;cursor:pointer;border:2px solid #E2E8F0;background:var(--card);color:var(--ink);transition:all .18s;display:inline-flex;align-items:center;gap:7px}
|
||
.wiz-chip.sel{background:var(--accent);color:#fff;border-color:var(--accent);box-shadow:0 2px 8px rgba(0,62,126,.28)}
|
||
.wiz-chips{display:flex;flex-wrap:wrap;gap:8px;margin-top:12px}
|
||
/* Single-screen filter chips */
|
||
.f-chip{padding:6px 12px;border-radius:20px;font-size:12px;font-weight:600;cursor:pointer;border:1.5px solid #E2E8F0;background:var(--card);color:var(--muted);transition:all .15s;display:inline-flex;align-items:center;gap:4px;white-space:nowrap;user-select:none}
|
||
.f-chip.sel{background:linear-gradient(135deg,#003E7E,#1560BD);color:#fff;border-color:transparent;box-shadow:0 2px 8px rgba(0,62,126,.32)}
|
||
.f-chip:active{transform:scale(.93)}
|
||
.f-row{display:flex;flex-wrap:wrap;gap:6px;margin-top:6px}
|
||
.f-section{margin-bottom:14px}
|
||
.f-label{font-size:11px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em}
|
||
.bud-pill{flex:1;padding:10px 6px;border-radius:12px;border:2px solid #E2E8F0;background:var(--card);text-align:center;cursor:pointer;transition:all .18s;min-width:0}
|
||
.bud-pill.sel{border-color:var(--accent);background:rgba(0,62,126,.05);box-shadow:0 3px 12px rgba(0,62,126,.2)}
|
||
.bud-pill:active{transform:scale(.95)}
|
||
|
||
/* Budget tiers */
|
||
.budget-card{background:var(--card);border-radius:14px;padding:14px 16px;cursor:pointer;border:2px solid #E2E8F0;transition:all .18s;display:flex;align-items:center;gap:14px;margin-bottom:10px}
|
||
.budget-card.sel{border-color:var(--accent);background:rgba(0,62,126,.04)}
|
||
.tier-icon{font-size:22px;width:42px;height:42px;background:var(--bg);border-radius:11px;display:flex;align-items:center;justify-content:center;flex-shrink:0}
|
||
.tier-check{margin-left:auto;width:22px;height:22px;border-radius:50%;border:2px solid #CBD5E1;display:flex;align-items:center;justify-content:center;flex-shrink:0}
|
||
.budget-card.sel .tier-check{background:var(--accent);border-color:var(--accent)}
|
||
|
||
/* Result */
|
||
.result-model{background:var(--card);border-radius:14px;padding:14px;margin-bottom:10px;border:2px solid transparent;cursor:pointer;transition:all .2s}
|
||
.result-model.pick{border-color:var(--accent)}
|
||
.search-pill{background:var(--bg);border-radius:10px;padding:10px 12px;font-size:11px;color:var(--muted);font-family:monospace;word-break:break-all;border:1px solid rgba(0,0,0,.08)}
|
||
|
||
/* Lead stage rail */
|
||
.stage-rail{display:flex;align-items:center;padding:4px 0}
|
||
.stage-node{display:flex;flex-direction:column;align-items:center;gap:3px;cursor:pointer;flex-shrink:0}
|
||
.stage-dot{width:28px;height:28px;border-radius:50%;border:2px solid #CBD5E1;background:var(--card);display:flex;align-items:center;justify-content:center;font-size:10px;font-weight:800;transition:all .15s}
|
||
.stage-dot.done{background:var(--success);border-color:var(--success);color:#fff}
|
||
.stage-dot.curr{background:var(--accent);border-color:var(--accent);color:#fff;box-shadow:0 0 0 3px rgba(0,62,126,.15)}
|
||
.stage-dot.lost{background:var(--danger);border-color:var(--danger);color:#fff}
|
||
.stage-lbl{font-size:9px;font-weight:700;color:var(--muted);text-align:center;max-width:44px;line-height:1.2}
|
||
.stage-lbl.curr{color:var(--accent)}
|
||
.stage-conn{flex:1;height:2px;background:#E2E8F0;margin-top:-14px}
|
||
.stage-conn.done{background:var(--success)}
|
||
|
||
/* Calendar */
|
||
.cal-strip{display:flex;gap:6px;padding:12px 16px;overflow-x:auto;scrollbar-width:none}
|
||
.cal-strip::-webkit-scrollbar{display:none}
|
||
.cal-day{display:flex;flex-direction:column;align-items:center;gap:3px;flex-shrink:0;width:44px;padding:8px 4px;border-radius:12px;cursor:pointer;transition:all .15s}
|
||
.cal-day.today{background:var(--bg);border:1.5px solid var(--accent)}
|
||
.cal-day.sel{background:var(--accent)}
|
||
.cal-day.off{opacity:.45}
|
||
.cal-day .cd-dow{font-size:10px;font-weight:700;color:var(--muted);text-transform:uppercase}
|
||
.cal-day.sel .cd-dow{color:rgba(255,255,255,.75)}
|
||
.cal-day .cd-num{font-size:17px;font-weight:800;color:var(--ink);line-height:1}
|
||
.cal-day.sel .cd-num{color:#fff}
|
||
.cal-day .cd-dot{width:5px;height:5px;border-radius:50%;background:var(--accent2);margin-top:2px}
|
||
.cal-day.sel .cd-dot{background:#fff}
|
||
.cal-day.no-dot .cd-dot{opacity:0}
|
||
.appt-block{background:var(--card);border-radius:12px;padding:11px 14px;margin-bottom:8px;border-left:4px solid var(--accent);display:flex;align-items:flex-start;gap:10px}
|
||
.appt-time{font-size:12px;font-weight:800;color:var(--accent);min-width:36px;padding-top:1px}
|
||
.task-item{display:flex;align-items:flex-start;gap:10px;padding:10px 0;border-bottom:1px solid rgba(0,0,0,.06)}
|
||
.task-item:last-child{border-bottom:none}
|
||
.task-chk{width:20px;height:20px;border-radius:6px;border:2px solid #CBD5E1;flex-shrink:0;margin-top:1px;display:flex;align-items:center;justify-content:center;cursor:pointer}
|
||
.task-chk.done{background:var(--success);border-color:var(--success)}
|
||
|
||
/* Calculations */
|
||
.calc-row{display:flex;align-items:center;justify-content:space-between;padding:11px 0;border-bottom:1px solid rgba(0,0,0,.06)}
|
||
.calc-row:last-child{border-bottom:none}
|
||
.calc-row.total{padding-top:13px;margin-top:4px}
|
||
.pay-chip{display:inline-flex;align-items:center;gap:6px;padding:5px 12px;border-radius:20px;font-size:12px;font-weight:700;cursor:pointer;border:none;transition:all .2s}
|
||
.pay-chip.act{background:var(--accent);color:#fff}
|
||
.pay-chip.inact{background:var(--bg);color:var(--muted);border:1.5px solid #E2E8F0}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<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" onchange="go(this.value)">
|
||
<option value="manager_home">🏠 Мои заказы</option>
|
||
<option value="manager_order">📋 Карточка заказа</option>
|
||
<option value="manager_tech">🔌 Подбор — тип</option>
|
||
<option value="manager_wizard">🧙 Визард — вопросы</option>
|
||
<option value="manager_result">✅ Результат подбора</option>
|
||
<option value="manager_schedule">📅 График работы</option>
|
||
<option value="manager_salary">💵 Зарплата</option>
|
||
<option value="manager_calc">💰 Расчёты с клиентом (legacy)</option>
|
||
<option value="manager_lead">🟡 Карточка лида</option>
|
||
<option value="manager_client">👤 Клиент</option>
|
||
<option value="manager_own_tech">📦 Своя техника (A/B/C)</option>
|
||
<option value="manager_tech_client">👤 Клиент выбирает</option>
|
||
</select>
|
||
<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)">CRM</button>
|
||
<button class="theme-btn" data-t="dark" onclick="setTheme('dark',this)">Dark</button>
|
||
</div>
|
||
</div>
|
||
|
||
<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 style="display:flex;align-items:center;gap:6px">
|
||
<svg width="15" height="11" viewBox="0 0 15 11" fill="currentColor"><rect x="0" y="3" width="2.5" height="8" rx=".8"/><rect x="4" y="2" width="2.5" height="9" rx=".8"/><rect x="8" y="0" width="2.5" height="11" rx=".8"/><rect x="12" y="0" width="2.5" height="11" rx=".8" opacity=".3"/></svg>
|
||
🔋
|
||
</div>
|
||
</div>
|
||
<div id="screen"></div>
|
||
<div id="nav"></div>
|
||
</div>
|
||
|
||
<script src="data.js"></script>
|
||
<script>
|
||
// ── State ────────────────────────────────────────────────────────────────────
|
||
window._managerOrders = window._managerOrders || [
|
||
{ id:1,client:'Иванова А.С.',phone:'+7 912 345-67-89',
|
||
type:'kitchen',label:'Кухня · Ул. Ленина 34, кв.12',
|
||
stage:3,contract:'МБ-2025-041',amount:186000,advance:93000,
|
||
assembly:28000,assemblyPaid:false,
|
||
advances:[
|
||
{label:'Аванс 1 · 50% при подписании',amount:93000,paid:true,date:'03.05.2025'},
|
||
{label:'Аванс 2 · при готовности к отгрузке',amount:93000,paid:false,date:null},
|
||
],
|
||
rooms:['Кухня'],
|
||
tech:[
|
||
{name:'Духовой шкаф', wkey:'oven', status:'wait', dims:'', brand:'', source:null},
|
||
{name:'Варочная панель', wkey:'cooktop', status:'done', dims:'60×52 см', brand:'Bosch', model:'PUE611BB5E', source:'ai'},
|
||
{name:'Вытяжка', wkey:'hood', status:'client_chosen', dims:'60 см', brand:'Elica', model:'FLAT GLASS IX/A/60', source:'client'},
|
||
{name:'Посудомойка', wkey:'dishwasher', status:'wait', dims:'', brand:'', source:null},
|
||
{name:'Холодильник', wkey:'fridge', status:'wait', dims:'', brand:'', source:null},
|
||
],
|
||
techNote:'Ждём габариты духовки и посудомойки. Передано клиенту 10.05.',
|
||
blocker:'Техника: ждём 2 позиции',
|
||
zovContract:'ЗОВ-25-041',
|
||
zovStatus:{code:'production',label:'В производстве',detail:'Корпуса готовы. Фасады в работе.',date:'27.05.2026 · 14:33',pct:45} },
|
||
{ id:2,client:'Петров В.Н.',phone:'+7 903 211-44-55',
|
||
type:'wardrobe',label:'Шкаф-купе · Пр. Мира 7, кв.18',
|
||
stage:4,contract:'МБ-2025-038',amount:94000,advance:47000,
|
||
assembly:14000,assemblyPaid:false,
|
||
advances:[
|
||
{label:'Аванс 1 · 50% при подписании',amount:47000,paid:true,date:'18.04.2025'},
|
||
{label:'Аванс 2 · при готовности к отгрузке',amount:47000,paid:false,date:null},
|
||
],
|
||
rooms:['Спальня'],tech:[],techNote:'',blocker:'Технолог: есть замечания',
|
||
zovContract:'ЗОВ-25-038',
|
||
zovStatus:{code:'ready',label:'Готов к отгрузке',detail:'Все позиции готовы. Дата доставки уточняется.',date:'26.05.2026 · 09:15',pct:90} },
|
||
{ id:3,client:'Сидорова М.К.',phone:'+7 916 888-22-11',
|
||
type:'kitchen',label:'Кухня · Ул. Садовая 5, кв.3',
|
||
stage:5,contract:'МБ-2025-029',amount:212000,advance:106000,
|
||
assembly:32000,assemblyPaid:true,
|
||
advances:[
|
||
{label:'Аванс 1 · 50% при подписании',amount:106000,paid:true,date:'10.03.2025'},
|
||
{label:'Аванс 2 · при готовности к отгрузке',amount:106000,paid:true,date:'08.05.2025'},
|
||
],
|
||
rooms:['Кухня','Балкон'],tech:[],techNote:'',blocker:null,
|
||
zovContract:'ЗОВ-25-029',
|
||
zovStatus:{code:'shipped',label:'Отгружен',detail:'Доставлен 09.05.2026. Договор закрыт.',date:'09.05.2026 · 11:20',pct:100} },
|
||
];
|
||
window._activeOrder = window._activeOrder || 0;
|
||
window._roomsEditing = false;
|
||
window._wiz = window._wiz || {type:null,step:0,answers:{},budget:null};
|
||
window._schedDay = window._schedDay || 3;
|
||
window._schedView = window._schedView || 'month';
|
||
window._CHECKIN = window._CHECKIN || {3:{in:'09:52',out:null,gpsIn:{dist:47,ok:true}}};
|
||
window._GPS_STATE = window._GPS_STATE || {}; // {d: 'loading'|{ok,dist}|'denied'}
|
||
window._GPS_DEMO = window._GPS_DEMO || 'near'; // 'near'|'far' — демо-переключатель
|
||
|
||
// ── Настройки директора (регулируются в панели руководителя) ──────────────────
|
||
window._CONFIG = window._CONFIG || {
|
||
gpsRadius: 300, // м — радиус «менеджер в салоне»
|
||
dayOffMinHours: 12, // ч — минимум за сколько часов запрашивать выходной
|
||
shiftStart: '10:00',
|
||
shiftEnd: '19:00',
|
||
};
|
||
window._homeFilter = window._homeFilter || 'all';
|
||
// Запросы на изменение графика: {mday: 'pending-work'|'pending-off'}
|
||
window._SCHED_REQ = window._SCHED_REQ || {};
|
||
|
||
// Лиды (без договора) — добавляем в общий список
|
||
window._managerOrders = window._managerOrders.concat(window._managerOrders.length > 3 ? [] : [
|
||
{ id:4, isLead:true, client:'Новиков Д.В.', phone:'+7 916 542-11-88',
|
||
source:'Звонок', sourceDate:'20.05.2026',
|
||
type:'kitchen', label:'Кухня · Пр. Победы 12',
|
||
leadStage:'kp', // new | meeting | kp | thinking | lost
|
||
leadNote:'КП отправлено 21.05. Ждём ответа.',
|
||
tasks:[{text:'Позвонить, уточнить решение по КП', done:false, hi:true}] },
|
||
{ id:5, isLead:true, client:'Белова К.И.', phone:'+7 903 774-55-22',
|
||
source:'Зал', sourceDate:'23.05.2026',
|
||
type:'wardrobe', label:'Шкаф-купе · адрес уточняется',
|
||
leadStage:'meeting',
|
||
leadNote:'Первичный визит сегодня. Встреча в 15:00.',
|
||
tasks:[{text:'Провести консультацию, снять потребности', done:false, hi:false}] },
|
||
{ id:6, isLead:true, client:'Воронова Е.С.', phone:'+7 926 318-77-44',
|
||
source:'Зал', sourceDate:'22.05.2026',
|
||
type:'kitchen', label:'Кухня + гостиная',
|
||
leadStage:'thinking',
|
||
leadNote:'Думает. Сказала перезвонит на следующей неделе.',
|
||
tasks:[{text:'Позвонить 26.05 — напомнить об акции', done:false, hi:false}] },
|
||
]);
|
||
|
||
// ── Schedule fixtures ─────────────────────────────────────────────────────────
|
||
var _DOW = ['Пн','Вт','Ср','Чт','Пт','Сб','Вс'];
|
||
var _DATES = [19,20,21,22,23,24,25]; // May 2026
|
||
var _WORK = [true,true,false,true,true,false,false]; // Пн,Вт,Чт,Пт — рабочие; Ср — выходной; Сб,Вс — всегда выходные
|
||
var _APPTS = [
|
||
[],
|
||
[{time:'10:00',client:'Иванова А.С.',type:'Замер на объекте',order:'МБ-2025-041',color:'#3B82F6'},
|
||
{time:'15:30',client:'Новиков Д.В.',type:'Первичная консультация',order:null,color:'#0D9488'}],
|
||
[{time:'11:00',client:'Сидорова М.К.',type:'Согласование проекта',order:'МБ-2025-029',color:'#10B981'}],
|
||
[{time:'10:00',client:'Петров В.Н.',type:'Встреча с технологом',order:'МБ-2025-038',color:'#F59E0B'},
|
||
{time:'15:00',client:'Белова К.И.',type:'Первичная консультация',order:null,color:'#0D9488'},
|
||
{time:'17:30',client:'Иванова А.С.',type:'Согласование техники',order:'МБ-2025-041',color:'#3B82F6'}],
|
||
[{time:'12:00',client:'Воронова Е.С.',type:'Замер',order:null,color:'#0D9488'}],
|
||
[{time:'11:00',client:'Курганов А.В.',type:'Первичная консультация',order:null,color:'#0D9488'},
|
||
{time:'14:30',client:'Иванова А.С.',type:'Приёмка кухни',order:'МБ-2025-041',color:'#3B82F6'}],
|
||
[{time:'12:00',client:'Ромашова Е.П.',type:'Согласование дизайна',order:'МБ-2025-051',color:'#10B981'}],
|
||
];
|
||
var _TASKS = {
|
||
1:[{order:'МБ-2025-041',text:'Отправить итог замера клиенту',done:true,hi:false}],
|
||
2:[{order:'МБ-2025-029',text:'Подготовить финальный проект к встрече',done:false,hi:true}],
|
||
3:[{order:'МБ-2025-041',text:'Уточнить габариты духовки и посудомойки',done:false,hi:true},
|
||
{order:'МБ-2025-038',text:'Передать замечания технолога клиенту',done:true,hi:false},
|
||
{order:'МБ-2025-029',text:'Подтвердить дату запуска производства',done:false,hi:false}],
|
||
4:[{order:'МБ-2025-029',text:'Согласовать доставку',done:false,hi:false}],
|
||
};
|
||
|
||
// ── Wizard Config ─────────────────────────────────────────────────────────────
|
||
var WIZ = {
|
||
oven:{name:'Духовой шкаф',emoji:'🔥',
|
||
steps:[
|
||
{q:'Как используете духовку?',field:'usage',multi:true,opts:[
|
||
{e:'🥐',l:'Выпечка и тесто'},{e:'🥩',l:'Мясо и рыба'},{e:'♻️',l:'Разогрев блюд'},{e:'👨🍳',l:'Готовлю всё подряд'}]},
|
||
{q:'Высота ниши?',field:'height',multi:false,opts:[
|
||
{e:'📦',l:'45 см'},{e:'📦',l:'60 см'},{e:'📦',l:'90 см'}]},
|
||
{q:'Очистка духовки?',field:'clean',multi:false,opts:[
|
||
{e:'🤖',l:'Пиролиз — сам всё сожжёт'},{e:'🧽',l:'Каталитическая'},{e:'💪',l:'Вручную — не страшно'}]},
|
||
{q:'Газ или электричество?',field:'fuel',multi:false,opts:[
|
||
{e:'⚡',l:'Только электро'},{e:'🔥',l:'Газ есть'},{e:'🤷',l:'Не знаю'}]},
|
||
],
|
||
budgets:{
|
||
base:{label:'Базовый',range:'до 25 000 ₽',brands:'Hansa · Gorenje · Gefest',icon:'💰'},
|
||
mid: {label:'Средний', range:'25 000 – 55 000 ₽',brands:'Bosch · AEG · Electrolux',icon:'💳'},
|
||
prem:{label:'Премиум',range:'55 000 ₽ +',brands:'Neff · Siemens · Miele',icon:'💎'},
|
||
},
|
||
niche:'595 × 595 мм, глубина 550 мм',
|
||
note:'Пиролиз требует зазора 5 см сверху ниши.',
|
||
anchor:'«Пиролиз окупается за 2 года — никто потом не пожалел»',
|
||
models:{
|
||
base:[{name:'Gorenje B6737E0X',price:'22 900 ₽',rating:4.4,reviews:847,pros:'Конвекция, 77 л, гриль, цифровой дисплей.',tags:['Конвекция','Гриль'],
|
||
buyerPros:['Большой объём — удобно для большой семьи','Гриль реально работает, корочка отличная','Простое управление, разобрались за 5 минут'],
|
||
buyerCons:['Нагрев чуть неравномерный по краям','Дисплей тускловатый при ярком свете']},
|
||
{name:'Hansa BOES64111',price:'18 500 ₽',rating:4.2,reviews:623,pros:'Базовая, 65 л, механика — надёжно.',tags:['Надёжный'],
|
||
buyerPros:['Ни одной поломки за 3 года','Механика проще электроники — ничего не глючит','Цена полностью оправдана'],
|
||
buyerCons:['Нет конвекции — выпечка неравномерная','Долго набирает температуру']},
|
||
{name:'Gefest ДА 622-02',price:'15 800 ₽',rating:4.3,reviews:1240,pros:'Газовая, 56 л. Если газ есть.',tags:['Газ'],
|
||
buyerPros:['Мгновенный нагрев — газ есть газ','Самая низкая цена в сегменте','Надёжна как танк, стоит 7 лет'],
|
||
buyerCons:['Только для газа — не для всех подходит','Решётки тяжело чистить']}],
|
||
mid: [{name:'Bosch HBF534ES0R',price:'38 900 ₽',rating:4.7,reviews:2840,pros:'EcoClean, 71 л, тихий вентилятор.',tags:['Рекомендуем','EcoClean'],
|
||
buyerPros:['EcoClean реально работает — протёр влажной тряпкой и всё','Работает почти бесшумно','Равномерный прогрев по всему объёму'],
|
||
buyerCons:['Цена немного выше конкурентов','Нет встроенного термощупа']},
|
||
{name:'AEG BES356010M',price:'42 500 ₽',rating:4.6,reviews:1320,pros:'SteamBoost, 71 л — идеально для выпечки.',tags:['Пар','Выпечка'],
|
||
buyerPros:['Пар творит чудеса — хлеб получается профессиональный','Пирог поднялся как в пекарне','Интерфейс интуитивный'],
|
||
buyerCons:['Бак для воды нужно заливать вручную','Дольше разогревается с паром']},
|
||
{name:'Electrolux OEF5C50V',price:'34 000 ₽',rating:4.5,reviews:1890,pros:'Пиролиз, 72 л, быстрый разогрев.',tags:['Пиролиз'],
|
||
buyerPros:['Пиролиз — забыл про чистку духовки навсегда','Быстрее разогревается чем Bosch','Соотношение цена/качество лучшее в сегменте'],
|
||
buyerCons:['Во время пиролиза сильно нагревается дверца снаружи','Нет WiFi управления']},
|
||
{name:'Gorenje BOP747S13BG',price:'36 500 ₽',rating:4.5,reviews:980,pros:'Паровая функция, 77 л, SteamClean.',tags:['Пар','SteamClean'],
|
||
buyerPros:['SteamClean чистит без химии за 20 минут','77 л — самый большой объём в сегменте','Удобные направляющие для противней'],
|
||
buyerCons:['Пар слабее чем у AEG','Дисплей немного устаревший']},
|
||
{name:'Samsung NV75K3340BS',price:'32 000 ₽',rating:4.4,reviews:1540,pros:'Dual Fan, 75 л, Ceramic Blue эмаль.',tags:['Dual Fan'],
|
||
buyerPros:['Dual Fan — два вентилятора, прогрев идеальный','Эмаль не впитывает запахи','Соотношение цена/объём лучшее'],
|
||
buyerCons:['Samsung — сервис найти сложнее','Нет пиролиза']},
|
||
{name:'Candy FCP615X/E',price:'29 500 ₽',rating:4.3,reviews:1120,pros:'WiFi управление, 70 л, Simply-Fi.',tags:['WiFi'],
|
||
buyerPros:['Включаю духовку с телефона до прихода домой','Рецепты прямо в приложении','Цена лучшая среди WiFi-моделей'],
|
||
buyerCons:['Приложение иногда подвисает','WiFi только 2.4 ГГц']},
|
||
{name:'Zanussi ZOPNA7K1',price:'27 900 ₽',rating:4.3,reviews:870,pros:'AquaClean, 71 л, А-класс.',tags:['AquaClean'],
|
||
buyerPros:['AquaClean — 30 минут пара и жир смыт','Тихий вентилятор','Надёжная — 4 года без нареканий'],
|
||
buyerCons:['Бренд менее известный','Нет пиролиза']}],
|
||
prem:[{name:'Neff B57CR23N0',price:'68 000 ₽',rating:4.8,reviews:945,pros:'CircoTherm, 71 л, без предварительного разогрева.',tags:['Рекомендуем','CircoTherm'],
|
||
buyerPros:['CircoTherm — кладёшь холодную еду, результат идеальный','Экономия энергии ощутима на счётах','Дверь открывается одним пальцем — SoftClose'],
|
||
buyerCons:['Цена высокая, но оправданная','Нужен мастер для установки']},
|
||
{name:'Siemens HB678G4S1',price:'74 500 ₽',rating:4.7,reviews:720,pros:'VarioClima, пар, пиролиз.',tags:['Пар','Пиролиз'],
|
||
buyerPros:['Пар + пиролиз в одном — универсал','Управление как у смартфона','Кулинарный помощник встроенный реально помогает'],
|
||
buyerCons:['Очень дорогой ремонт если сломается','Меню немного перегружено']},
|
||
{name:'Miele H7264B',price:'112 000 ₽',rating:4.9,reviews:380,pros:'M Touch, PerfectClean, немецкая надёжность.',tags:['Премиум'],
|
||
buyerPros:['Стоит 8 лет — ни одного обращения в сервис','PerfectClean — покрытие реально не пачкается','Сенсорный экран удобнее чем в телефоне'],
|
||
buyerCons:['Цена как у б/у автомобиля','Запчасти только оригинальные, дорогие']}],
|
||
}},
|
||
cooktop:{name:'Варочная панель',emoji:'🍳',
|
||
steps:[
|
||
{q:'Тип нагрева?',field:'type',multi:false,opts:[
|
||
{e:'⚡',l:'Индукция'},{e:'🔥',l:'Газ'},{e:'🟠',l:'Электро'}]},
|
||
{q:'Сколько конфорок?',field:'burners',multi:false,opts:[
|
||
{e:'2️⃣',l:'2 конфорки'},{e:'4️⃣',l:'4 конфорки'},{e:'5️⃣',l:'5 конфорок'}]},
|
||
{q:'Ширина панели?',field:'width',multi:false,opts:[
|
||
{e:'📏',l:'45 см'},{e:'📏',l:'60 см'},{e:'📏',l:'75 см'},{e:'📏',l:'90 см'}]},
|
||
{q:'Особые условия?',field:'extra',multi:true,opts:[
|
||
{e:'👶',l:'Дети дома'},{e:'🎯',l:'Точная температура важна'},{e:'🤝',l:'В пару с духовкой одного бренда'}]},
|
||
],
|
||
budgets:{
|
||
base:{label:'Базовый',range:'до 15 000 ₽',brands:'Gorenje · Hansa · Zanussi',icon:'💰'},
|
||
mid: {label:'Средний', range:'15 000 – 40 000 ₽',brands:'Bosch · AEG · Electrolux',icon:'💳'},
|
||
prem:{label:'Премиум',range:'40 000 ₽ +',brands:'Neff · Siemens · De Dietrich',icon:'💎'},
|
||
},
|
||
niche:'Вырез точно по размеру панели ±2 мм.',
|
||
note:'Индукция: закипает вдвое быстрее, поверхность не нагревается.',
|
||
anchor:'«С детьми в доме индукция — не опция, а норма»',
|
||
models:{
|
||
base:[{name:'Gorenje G642E',price:'12 900 ₽',rating:4.3,reviews:1050,pros:'4 газ. конфорки, чугунные решётки.',tags:['Газ'],
|
||
buyerPros:['Чугунные решётки — ничего не скользит','Огонь регулируется плавно','Надёжна, стоит 5 лет без проблем'],
|
||
buyerCons:['Решётки тяжёлые, неудобно снимать для мойки','Нет газконтроля']},
|
||
{name:'Zanussi ZGO65414XA',price:'14 500 ₽',rating:4.4,reviews:870,pros:'5 конфорок, газконтроль.',tags:['Газконтроль'],
|
||
buyerPros:['Газконтроль спасает если задули конфорку','5 конфорок — места хватает всем','Быстрая центральная конфорка'],
|
||
buyerCons:['Центральная конфорка иногда плохо поджигается','Щели у решёток трудно чистить']},
|
||
{name:'Hansa BХHC610010',price:'11 200 ₽',rating:4.1,reviews:730,pros:'4 конфорки, нерж. сталь.',tags:['Базовый'],
|
||
buyerPros:['Нержавейка легко чистится','Самая доступная цена','Поджиг с первого раза'],
|
||
buyerCons:['Нет газконтроля — риск при сквозняке','Мощность конфорок средняя']}],
|
||
mid: [{name:'Bosch PVS651FC5E',price:'34 900 ₽',rating:4.8,reviews:3120,pros:'4 зоны индукция, PowerBoost.',tags:['Рекомендуем','Индукция'],
|
||
buyerPros:['PowerBoost — вода закипает за 90 секунд','Поверхность холодная — дети в безопасности','Чистить элементарно — просто протёр'],
|
||
buyerCons:['Нужна индукционная посуда — докупали','Гудит на высокой мощности']},
|
||
{name:'Electrolux EIV63440BW',price:'27 500 ₽',rating:4.5,reviews:2050,pros:'4 зоны, Touch-управление.',tags:['Индукция'],
|
||
buyerPros:['Touch-управление удобнее ручек','Лучшая цена в категории индукции','Нагрев точный, ничего не пригорает'],
|
||
buyerCons:['Touch иногда срабатывает случайно','Нет бустера как у Bosch']},
|
||
{name:'AEG IAE64511FB',price:'38 000 ₽',rating:4.6,reviews:1440,pros:'5 зон, 80 см, мощная центральная.',tags:['5 зон'],
|
||
buyerPros:['5 зон — всё готовится одновременно','Центральная зона гигантская под вок','Таймер на каждую зону отдельно'],
|
||
buyerCons:['80 см — нужно проверить ширину столешницы','Цена выше чем Bosch за схожие функции']},
|
||
{name:'Samsung NZ64T3706S1',price:'29 900 ₽',rating:4.5,reviews:1780,pros:'4 зоны индукция, виртуальный огонь.',tags:['Индукция','Samsung'],
|
||
buyerPros:['Анимация огня помогает выбрать мощность интуитивно','Защита от детей удобная','Нагрев быстрый как у Bosch'],
|
||
buyerCons:['Нет PowerBoost','Сервис Samsung — не везде']},
|
||
{name:'Gorenje IS645ASC',price:'25 900 ₽',rating:4.4,reviews:1240,pros:'4 зоны, SlimLine 5 мм, стекло.',tags:['SlimLine'],
|
||
buyerPros:['SlimLine — практически вровень со столешницей','Слайдер-управление необычное и удобное','Цена ниже конкурентов'],
|
||
buyerCons:['Мощность чуть меньше чем Bosch','Слайдер иногда заедает']},
|
||
{name:'Hansa BHC6358D',price:'23 500 ₽',rating:4.3,reviews:980,pros:'4 зоны, детская блокировка, таймер.',tags:['Безопасность'],
|
||
buyerPros:['Детская блокировка — надёжная','Самая доступная индукция с таймером','Простой уход'],
|
||
buyerCons:['Нет PowerBoost','Дизайн базовый']},
|
||
{name:'Lex EVI 640-2 BL',price:'21 900 ₽',rating:4.2,reviews:760,pros:'4 зоны, чёрное стекло, бюджетная индукция.',tags:['Бюджет'],
|
||
buyerPros:['Лучшая цена на вход в индукцию','Чёрное стекло выглядит дорого','Работает без нареканий 2 года'],
|
||
buyerCons:['Мощность зон слабее топовых','Нет автоматических программ']}],
|
||
prem:[{name:'Neff T48FD23X2',price:'62 000 ₽',rating:4.8,reviews:680,pros:'FlexInduction — зоны под любую посуду.',tags:['Рекомендуем','FlexInduction'],
|
||
buyerPros:['Кладёшь посуду любого размера — сама определяет зону','Управление жестами удивляет гостей','Полная поверхность без промежутков'],
|
||
buyerCons:['Дорогой сервис','При замене стекла — только в авторизованном центре']},
|
||
{name:'Siemens EH675MP27E',price:'68 500 ₽',rating:4.7,reviews:520,pros:'iQ700, 5 зон, таймер.',tags:['iQ700'],
|
||
buyerPros:['iQ700 — автоматически поддерживает температуру','5 зон хватает для большой семьи','Дизайн делает кухню дороже визуально'],
|
||
buyerCons:['Цена выше аналогов','Таймер только через приложение']},
|
||
{name:'De Dietrich DPI7690XL',price:'89 000 ₽',rating:4.9,reviews:290,pros:'Full Surface — вся поверхность одна зона.',tags:['Full Surface'],
|
||
buyerPros:['Вся панель одна зона — ставишь куда хочешь','Французский дизайн выделяется','Никакого перегрева краёв'],
|
||
buyerCons:['Самая дорогая в сегменте','Сервис редкий, не во всех городах']}],
|
||
}},
|
||
fridge:{name:'Холодильник',emoji:'🧊',
|
||
steps:[
|
||
{q:'Состав семьи?',field:'family',multi:false,opts:[
|
||
{e:'🧍',l:'1–2 человека'},{e:'👨👩👦',l:'3–4 человека'},{e:'👨👩👧👦',l:'5 и больше'}]},
|
||
{q:'Тип размещения?',field:'place',multi:false,opts:[
|
||
{e:'🚪',l:'Встраиваемый'},{e:'🏠',l:'Отдельностоящий'}]},
|
||
{q:'Высота ниши / пространства?',field:'height',multi:false,opts:[
|
||
{e:'📐',l:'до 150 см'},{e:'📐',l:'175 см'},{e:'📐',l:'190 см'},{e:'📐',l:'200 см+'}]},
|
||
{q:'No Frost важен?',field:'nofrost',multi:false,opts:[
|
||
{e:'❄️',l:'Да, обязательно'},{e:'🤷',l:'Не принципиально'}]},
|
||
],
|
||
budgets:{
|
||
base:{label:'Базовый',range:'до 30 000 ₽',brands:'Indesit · Hotpoint · Beko',icon:'💰'},
|
||
mid: {label:'Средний', range:'30 000 – 70 000 ₽',brands:'Bosch · Samsung · LG',icon:'💳'},
|
||
prem:{label:'Премиум',range:'70 000 ₽ +',brands:'Liebherr · Miele · Sub-Zero',icon:'💎'},
|
||
},
|
||
niche:'Встраиваемый: точно по нише + вентзазор 2 см сверху.',
|
||
note:'Отдельностоящий: зазор 5 см сверху, 2 см по бокам.',
|
||
anchor:'«No Frost = 10 лет без разморозки»',
|
||
models:{
|
||
base:[{name:'Indesit ITR 4180 W',price:'24 500 ₽',rating:4.3,reviews:2140,pros:'No Frost, 298 л, класс A+.',tags:['No Frost'],
|
||
buyerPros:['No Frost — ни разу не размораживали за 4 года','Вместительный, всё влезает','Работает тихо, не слышно ночью'],
|
||
buyerCons:['Пластик внутри чуть хрупкий','Полки нельзя переставлять']},
|
||
{name:'Beko RCNK356K20W',price:'26 900 ₽',rating:4.4,reviews:1820,pros:'NeoFrost, 300 л.',tags:['NeoFrost'],
|
||
buyerPros:['Охлаждение равномерное по всему объёму','Большой морозильник на 100 л','Ящики открываются плавно'],
|
||
buyerCons:['Компрессор иногда заметно шумит','Дисплей на двери запотевает']},
|
||
{name:'Hotpoint HT 4180 AB',price:'22 000 ₽',rating:4.2,reviews:1560,pros:'278 л, Total No Frost.',tags:['No Frost'],
|
||
buyerPros:['Лучшая цена в сегменте No Frost','Надёжный, стоит 5 лет','Просторный морозильник'],
|
||
buyerCons:['Нет диспенсера воды','Освещение тускловатое']}],
|
||
mid: [{name:'Bosch KGN36VL21R',price:'48 900 ₽',rating:4.8,reviews:4200,pros:'NoFrost, 325 л, VitaFresh.',tags:['Рекомендуем','VitaFresh'],
|
||
buyerPros:['VitaFresh — овощи свежие на 2 недели дольше','Работает абсолютно бесшумно','Немецкое качество — ни одного вызова мастера'],
|
||
buyerCons:['Цена выше среднего по рынку','Нет льдогенератора']},
|
||
{name:'LG GBB59SWJZS',price:'41 000 ₽',rating:4.7,reviews:3150,pros:'Total No Frost, DoorCooling+.',tags:['DoorCooling'],
|
||
buyerPros:['DoorCooling+ — дверь закрыл, температура не скачет','Компрессор инверторный — тихий и экономный','Диагностика через смартфон'],
|
||
buyerCons:['Приложение LG иногда теряет связь','Дверные полки небольшие']},
|
||
{name:'Samsung RB37A5290WW',price:'44 500 ₽',rating:4.6,reviews:3800,pros:'SpaceMax, 367 л, A++.',tags:['A++'],
|
||
buyerPros:['SpaceMax — огромный объём при компактном корпусе','Самый большой холодильник в сегменте','Энергопотребление минимальное'],
|
||
buyerCons:['Компрессор слышен при старте','Полки стеклянные — осторожно с тяжёлым']},
|
||
{name:'Atlant ХМ 4621-101 NL',price:'38 500 ₽',rating:4.5,reviews:2640,pros:'No Frost, 343 л, Full No Frost.',tags:['No Frost','Atlant'],
|
||
buyerPros:['Белорусская надёжность — сервис везде','Огромный объём за эти деньги','Тихий компрессор'],
|
||
buyerCons:['Дизайн проще чем у Bosch/LG','Нет приложения']},
|
||
{name:'Haier CEF537AWD',price:'42 000 ₽',rating:4.5,reviews:1920,pros:'Total No Frost, 330 л, A++, French Door.',tags:['French Door'],
|
||
buyerPros:['French Door — две дверцы сверху как в ресторане','Большие ящики морозилки','Тихий как Bosch'],
|
||
buyerCons:['Сервис Haier — не все города','Нижняя морозилка неудобна людям с больной спиной']},
|
||
{name:'Indesit ITS 5180 W',price:'34 900 ₽',rating:4.4,reviews:2100,pros:'No Frost, 298 л, быстрая заморозка.',tags:['No Frost'],
|
||
buyerPros:['Лучшая цена No Frost в сегменте','Быстрая заморозка — мясо замораживает за 4 часа','Тихий'],
|
||
buyerCons:['Полок меньше чем у Bosch','Пластик не самый качественный']},
|
||
{name:'Candy CCE4T620EB',price:'36 500 ₽',rating:4.3,reviews:1450,pros:'WiFi, No Frost, 331 л, управление с телефона.',tags:['WiFi','Smart'],
|
||
buyerPros:['WiFi — смотрю температуру с телефона','Умные алерты если дверь открыта','Стильный дизайн'],
|
||
buyerCons:['Приложение Candy базовое','WiFi только 2.4 ГГц']}],
|
||
prem:[{name:'Liebherr CUel 2831',price:'78 000 ₽',rating:4.8,reviews:620,pros:'NoFrost, встраиваемый, BioFresh.',tags:['Рекомендуем','BioFresh'],
|
||
buyerPros:['BioFresh — рыба и мясо не обветриваются 2 недели','Встройка идеальная — как родная в гарнитуре','Бесшумный как часы'],
|
||
buyerCons:['Монтаж только через сертифицированного установщика','Сервис дорогой']},
|
||
{name:'Bosch KIS87AF30R',price:'89 500 ₽',rating:4.7,reviews:480,pros:'Встраиваемый, VitaFresh, SoftClose.',tags:['Встраиваемый'],
|
||
buyerPros:['SoftClose дверь — открывается и закрывается как в BMW','VitaFresh держит влажность','Немецкое качество без вопросов'],
|
||
buyerCons:['Один из дорогих в сегменте встройки','Нет Wi-Fi']},
|
||
{name:'Miele KF 2901 Vi',price:'148 000 ₽',rating:4.9,reviews:195,pros:'PerfectFresh Pro, встраиваемый.',tags:['Miele'],
|
||
buyerPros:['PerfectFresh Pro — продукты не портятся неделями','Работает 20 лет без обслуживания по отзывам','Премиальный вид и качество сборки'],
|
||
buyerCons:['Цена запредельная','Только оригинальные запчасти']}],
|
||
}},
|
||
hood:{name:'Вытяжка',emoji:'💨',
|
||
steps:[
|
||
{q:'Куда выводить воздух?',field:'exhaust',multi:false,opts:[
|
||
{e:'🌬️',l:'В вентканал (вывод)'},{e:'♻️',l:'Рециркуляция (фильтр)'}]},
|
||
{q:'Ширина варочной панели?',field:'width',multi:false,opts:[
|
||
{e:'📏',l:'45 см'},{e:'📏',l:'60 см'},{e:'📏',l:'75 см'},{e:'📏',l:'90 см'}]},
|
||
{q:'Тип установки?',field:'mount',multi:false,opts:[
|
||
{e:'🗄️',l:'Встраиваемая в шкаф'},{e:'📐',l:'Наклонная / настенная'},{e:'🏝️',l:'Островная'}]},
|
||
{q:'Что важно?',field:'prio',multi:true,opts:[
|
||
{e:'🔇',l:'Тишина (спальня рядом)'},{e:'💪',l:'Высокая мощность'},{e:'🎨',l:'Дизайн виден в интерьере'}]},
|
||
],
|
||
budgets:{
|
||
base:{label:'Базовый',range:'до 10 000 ₽',brands:'Krona · Elikor · Minola',icon:'💰'},
|
||
mid: {label:'Средний', range:'10 000 – 35 000 ₽',brands:'Bosch · Electrolux · Krona',icon:'💳'},
|
||
prem:{label:'Премиум',range:'35 000 ₽ +',brands:'Falmec · Elica · Miele',icon:'💎'},
|
||
},
|
||
niche:'Встраиваемая: ниша = размер вытяжки, глубина шкафа ≥ 300 мм.',
|
||
note:'Производительность: площадь кухни × 10 × 1.3 = м³/ч.',
|
||
anchor:'«Тихая вытяжка — говоришь во время готовки и слышишь»',
|
||
models:{
|
||
base:[{name:'Krona KAMILLA 600 Ivory M',price:'8 900 ₽',rating:4.5,reviews:1830,pros:'60 см, 650 м³/ч, 3 скорости.',tags:['Встраиваемая'],
|
||
buyerPros:['650 м³/ч — кухня проветривается за минуту','Очень тихая на 1-й скорости','Угольный фильтр легко меняется'],
|
||
buyerCons:['Угольные фильтры нужно покупать каждые 6 месяцев','Кнопки чуть дребезжат']},
|
||
{name:'Elikor Intro 60П-400-П3Л',price:'7 500 ₽',rating:4.2,reviews:1240,pros:'60 см, 400 м³/ч, базовая модель.',tags:['Базовый'],
|
||
buyerPros:['Самая бюджетная и работает нормально','Установка 15 минут — справился сам','Лёгкая — не боится слабых навесов'],
|
||
buyerCons:['400 м³/ч маловато для больших кухонь','Шумная на 3-й скорости']},
|
||
{name:'Minola HBI 5614 WH 1000 LED',price:'9 200 ₽',rating:4.4,reviews:960,pros:'60 см, LED-подсветка, рециркуляция.',tags:['LED'],
|
||
buyerPros:['LED-подсветка ярче чем у других','Работает в рециркуляции — не нужен вентканал','Белый цвет — вписалась в кухню идеально'],
|
||
buyerCons:['Рециркуляция хуже вывода в канал','Пульт управления теряется']}],
|
||
mid: [{name:'Bosch DWB98JQ50',price:'24 900 ₽',rating:4.8,reviews:2650,pros:'90 см, 740 м³/ч, сенсорное управление.',tags:['Рекомендуем'],
|
||
buyerPros:['На 1-й скорости слышно только если рядом стоять','Сенсор не залипает как механика','Жир убирается одной тряпкой'],
|
||
buyerCons:['Цена выше чем Electrolux','Нет режима форсаж']},
|
||
{name:'Electrolux EFC90560OX',price:'19 500 ₽',rating:4.6,reviews:2100,pros:'90 см, 700 м³/ч, 3 скорости + форсаж.',tags:['Форсаж'],
|
||
buyerPros:['Форсаж — запах жарки улетает за 30 сек','Соотношение цена/качество лучшее','Нержавейка не пачкается пальцами'],
|
||
buyerCons:['На форсаже шумит заметно','Кнопки немного туговаты']},
|
||
{name:'Krona KAMILLA 900 Inox S',price:'16 000 ₽',rating:4.5,reviews:1740,pros:'90 см, слайдер, 1000 м³/ч.',tags:['Слайдер'],
|
||
buyerPros:['1000 м³/ч — самая мощная в ценовом сегменте','Слайдер удобнее обычных панелей','Отлично справляется даже с рыбой'],
|
||
buyerCons:['Слайдер иногда заедает','Подсветка одна лампочка — тускловато']},
|
||
{name:'Samsung NK36M5070BS',price:'18 500 ₽',rating:4.5,reviews:1580,pros:'60 см, 1000 м³/ч, Bluetooth.',tags:['Smart'],
|
||
buyerPros:['Управление с телефона — необычно и удобно','Мощная для 60 см','Подключается к плите Samsung — авто-режим'],
|
||
buyerCons:['Bluetooth, не WiFi — ограниченный радиус','Работает только с Samsung-плитами для авто-режима']},
|
||
{name:'Gorenje WHC953SY2X',price:'22 500 ₽',rating:4.4,reviews:1240,pros:'90 см, 650 м³/ч, SilentBoost.',tags:['Тихая'],
|
||
buyerPros:['SilentBoost — тише чем Bosch на 1-й скорости','Хромированные детали выглядят дорого','Фильтры легко снять и помыть'],
|
||
buyerCons:['650 м³/ч — не самая мощная','Цена чуть выше Electrolux']},
|
||
{name:'Hansa OKC6132IH',price:'15 500 ₽',rating:4.3,reviews:1080,pros:'60 см, 800 м³/ч, LED 3 уровня.',tags:['LED'],
|
||
buyerPros:['Яркая LED-подсветка — видно каждый угол кастрюли','Лучшая цена в сегменте','Установил сам за полчаса'],
|
||
buyerCons:['Нержавейка оставляет следы от пальцев','60 см — только под 60 см панель']},
|
||
{name:'Lex GS 60 IX',price:'14 200 ₽',rating:4.2,reviews:890,pros:'60 см, 650 м³/ч, наклонная.',tags:['Наклонная'],
|
||
buyerPros:['Наклонная удобнее прямой — дотянуться легче','Тихая на 1-й скорости','Выглядит современно'],
|
||
buyerCons:['Мощность базовая','Нет форсажа']}],
|
||
prem:[{name:'Falmec PLANE 90',price:'68 000 ₽',rating:4.8,reviews:340,pros:'90 см, 800 м³/ч, бесшовный дизайн.',tags:['Рекомендуем','Дизайн'],
|
||
buyerPros:['Бесшовный стеклянный корпус — выглядит как арт-объект','На 1-й скорости полная тишина','Итальянское качество сборки чувствуется'],
|
||
buyerCons:['Монтаж только мастером','Стекло нужно протирать после каждого приготовления']},
|
||
{name:'Elica BELT IX/A/120',price:'54 000 ₽',rating:4.7,reviews:280,pros:'120 см, 1200 м³/ч, встраиваемая.',tags:['Мощная'],
|
||
buyerPros:['1200 м³/ч — справится с любым объёмом кухни','Встройка идеальная — полностью скрыта в шкафу','Итальянский дизайн'],
|
||
buyerCons:['Только для кухонь с мощным вентканалом','120 см — нужна большая ниша']},
|
||
{name:'Miele DA 3698',price:'89 000 ₽',rating:4.9,reviews:210,pros:'90 см, ExtraQuiet, 670 м³/ч.',tags:['Тихая'],
|
||
buyerPros:['ExtraQuiet — работает тише чем кондиционер','Не нужно кричать на кухне во время готовки','Работает 10 лет без единой замены'],
|
||
buyerCons:['Самая дорогая','670 м³/ч скромно за такую цену']}],
|
||
}},
|
||
dishwasher:{name:'Посудомойка',emoji:'🚿',
|
||
steps:[
|
||
{q:'Состав семьи?',field:'family',multi:false,opts:[
|
||
{e:'🧍',l:'1–2 человека'},{e:'👨👩👦',l:'3–4 человека'},{e:'👨👩👧👦',l:'5+'}]},
|
||
{q:'Ширина ниши?',field:'width',multi:false,opts:[
|
||
{e:'📏',l:'45 см — компактная'},{e:'📏',l:'60 см — стандарт'}]},
|
||
{q:'Тип установки?',field:'mount',multi:false,opts:[
|
||
{e:'🚪',l:'Встраиваемая (фасад в ряд)'},{e:'🏠',l:'Отдельностоящая'}]},
|
||
{q:'Как сушить посуду?',field:'dry',multi:false,opts:[
|
||
{e:'🌬️',l:'Авто-открытие дверцы'},{e:'💧',l:'Конденсационная'},{e:'🤷',l:'Неважно'}]},
|
||
],
|
||
budgets:{
|
||
base:{label:'Базовый',range:'до 25 000 ₽',brands:'Hansa · Gorenje · Indesit',icon:'💰'},
|
||
mid: {label:'Средний', range:'25 000 – 50 000 ₽',brands:'Bosch · AEG · Siemens',icon:'💳'},
|
||
prem:{label:'Премиум',range:'50 000 ₽ +',brands:'Miele · V-ZUG · Gaggenau',icon:'💎'},
|
||
},
|
||
niche:'Стандартная высота 820 мм (регул. ±15 мм).',
|
||
note:'Встраиваемая: нужен цоколь и петли для фасада.',
|
||
anchor:'«Авто-открытие = посуда всегда сухая без разводов»',
|
||
models:{
|
||
base:[{name:'Gorenje GS62040W',price:'19 900 ₽',rating:4.3,reviews:1650,pros:'60 см, 12 комплектов, 5 программ.',tags:['Базовый'],
|
||
buyerPros:['Моет нормально даже застаревший жир','Тихая — не мешает смотреть ТВ рядом','Соль и ополаскиватель расходуются экономно'],
|
||
buyerCons:['Нет авто-открытия — посуда чуть влажная','Пластиковые держатели хрупкие']},
|
||
{name:'Hansa ZWM416WH',price:'21 500 ₽',rating:4.4,reviews:1380,pros:'60 см, 12 компл., A++ энергокласс.',tags:['A++'],
|
||
buyerPros:['Счёт за электричество заметно ниже','Полностью встраивается — фасад в ряд','Интуитивное управление'],
|
||
buyerCons:['Нет половинной загрузки','Корзина для столовых приборов небольшая']},
|
||
{name:'Indesit DIF 14B1',price:'18 000 ₽',rating:4.2,reviews:2040,pros:'60 см, 12 компл., половинная загрузка.',tags:['Эконом'],
|
||
buyerPros:['Половинная загрузка экономит воду и свет','Самая дешёвая встраиваемая в категории','Ставят посуду быстро, корзины удобные'],
|
||
buyerCons:['Сушка слабовата','Нет дисплея — только световые индикаторы']}],
|
||
mid: [{name:'Bosch SMV4EVX15E',price:'38 900 ₽',rating:4.9,reviews:5400,pros:'60 см, 14 компл., АвтоОткрытие, A+++.',tags:['Рекомендуем','АвтоОткрытие'],
|
||
buyerPros:['АвтоОткрытие — посуда всегда сухая, без разводов','14 комплектов и кастрюли помещаются','Работает ночью — 44 дБ, вообще не слышно'],
|
||
buyerCons:['Цена выше конкурентов','Нужен ополаскиватель, иначе разводы']},
|
||
{name:'Siemens SN63HX65AK',price:'36 500 ₽',rating:4.6,reviews:3100,pros:'60 см, 14 компл., EfficientDry.',tags:['EfficientDry'],
|
||
buyerPros:['EfficientDry лучше конденсационной','Хорошая цена для уровня Siemens','Регулируемая верхняя корзина — высокие бокалы'],
|
||
buyerCons:['Нет авто-открытия как у Bosch','Иногда остаётся капля воды на дне тарелки']},
|
||
{name:'AEG FSS5360CZM',price:'42 000 ₽',rating:4.7,reviews:2280,pros:'60 см, 15 компл., AirDry, ExtraHygiene.',tags:['AirDry'],
|
||
buyerPros:['ExtraHygiene — мощный нагрев убивает бактерии','AirDry сушит хорошо','15 комплектов — большие кастрюли входят'],
|
||
buyerCons:['Дороже Bosch','Программы долгие — 2,5 часа стандартная']},
|
||
{name:'Samsung DW60A6092BB',price:'34 500 ₽',rating:4.5,reviews:1860,pros:'60 см, 14 компл., Zone Booster, WiFi.',tags:['WiFi','Zone Booster'],
|
||
buyerPros:['Zone Booster — верхняя или нижняя корзина отдельно','WiFi — запускаю ПМ удалённо','Стильный чёрный цвет'],
|
||
buyerCons:['Нет авто-открытия','Приложение Samsung иногда глючит']},
|
||
{name:'Electrolux EEA917110L',price:'32 000 ₽',rating:4.5,reviews:1640,pros:'60 см, 15 компл., AirDry, 43 дБ.',tags:['Тихая','AirDry'],
|
||
buyerPros:['43 дБ — абсолютно тихая ночью','AirDry работает хорошо','15 комплектов — влезают высокие кастрюли'],
|
||
buyerCons:['Нет WiFi','Цена чуть выше Samsung']},
|
||
{name:'Gorenje GS641D60X',price:'28 500 ₽',rating:4.4,reviews:1320,pros:'60 см, 14 компл., инверторный насос.',tags:['Инвертор'],
|
||
buyerPros:['Инверторный насос — тихий и надёжный','Лучшая цена в сегменте 14 компл.','Корзины удобные с регулировкой'],
|
||
buyerCons:['Нет авто-открытия','Сушка конденсационная — чуть хуже AirDry']},
|
||
{name:'Indesit DFC 2B+16 AC X',price:'25 900 ₽',rating:4.3,reviews:1080,pros:'60 см, 16 компл., 9 программ.',tags:['16 комплектов'],
|
||
buyerPros:['16 комплектов — больше всех в ценовом диапазоне','9 программ на любой случай','Самая доступная 16-комплектная'],
|
||
buyerCons:['Нет авто-открытия','Шумновата — 48 дБ']}],
|
||
prem:[{name:'Miele G 7310 SCi',price:'89 000 ₽',rating:4.9,reviews:820,pros:'60 см, 14 компл., AutoDos с AutoOpen.',tags:['Рекомендуем','AutoDos'],
|
||
buyerPros:['AutoDos сам дозирует моющее — никогда не думаешь об этом','Стоит 10 лет без единой поломки по отзывам','Тихая как библиотека — 40 дБ'],
|
||
buyerCons:['Цена как у техники Apple','Капсулы AutoDos только Miele']},
|
||
{name:'V-ZUG AdoraDish V6000',price:'112 000 ₽',rating:4.9,reviews:145,pros:'60 см, 15 компл., швейцарское кач-во.',tags:['Швейцария'],
|
||
buyerPros:['Швейцарская точность — всё идеально','Корзины из нержавейки — не ломаются','Бесшумнее Miele'],
|
||
buyerCons:['Найти сервис за пределами Москвы сложно','Цена заоблачная']},
|
||
{name:'Gaggenau DF270160',price:'145 000 ₽',rating:4.8,reviews:98,pros:'60 см, встраиваемая, zeolith-сушка.',tags:['Zeolith'],
|
||
buyerPros:['Zeolith сушит абсолютно сухо с первого раза','Выглядит как произведение искусства','Гарантия 5 лет'],
|
||
buyerCons:['Самая дорогая посудомойка на рынке','Сервис только у дилера']}],
|
||
}},
|
||
micro:{name:'Микроволновка',emoji:'📡',
|
||
steps:[
|
||
{q:'Как будете использовать?',field:'usage',multi:true,opts:[
|
||
{e:'♻️',l:'Только разогрев'},{e:'🍗',l:'Гриль'},{e:'🥖',l:'Конвекция / выпечка'},{e:'🔄',l:'Заменить духовку'}]},
|
||
{q:'Нужный объём?',field:'vol',multi:false,opts:[
|
||
{e:'📦',l:'до 20 л'},{e:'📦',l:'20–30 л'},{e:'📦',l:'30 л+'}]},
|
||
{q:'Тип размещения?',field:'mount',multi:false,opts:[
|
||
{e:'🗄️',l:'Встраиваемая в шкаф'},{e:'🏠',l:'На столешницу'}]},
|
||
],
|
||
budgets:{
|
||
base:{label:'Базовый',range:'до 10 000 ₽',brands:'Samsung · LG · Горизонт',icon:'💰'},
|
||
mid: {label:'Средний', range:'10 000 – 25 000 ₽',brands:'Bosch · AEG · Electrolux',icon:'💳'},
|
||
prem:{label:'Премиум',range:'25 000 ₽ +',brands:'Neff · Miele · Smeg',icon:'💎'},
|
||
},
|
||
niche:'Встраиваемая: ниша 560 × 340 мм (стандарт под 60 см шкаф).',
|
||
note:'Если нет отдельной духовки — выбирайте с конвекцией.',
|
||
anchor:'«Встраиваемая не занимает столешницу — кухня выглядит чище»',
|
||
models:{
|
||
base:[{name:'Samsung MS23K3614AW',price:'7 900 ₽',rating:4.5,reviews:4800,pros:'23 л, 800 Вт, 5 уровней мощности.',tags:['Базовый'],
|
||
buyerPros:['Греет равномерно, без холодных зон','Управление проще некуда','Служит 5+ лет без поломок'],
|
||
buyerCons:['Нет гриля','Дверь открывается влево — не всем удобно']},
|
||
{name:'LG MH6042D',price:'8 500 ₽',rating:4.4,reviews:3200,pros:'20 л, гриль, 700 Вт.',tags:['Гриль'],
|
||
buyerPros:['Гриль даёт хрустящую корочку','Дизайн современный','Нагрев быстрый'],
|
||
buyerCons:['Гриль требует чистки после каждого раза','Объём 20 л — компактная']},
|
||
{name:'Горизонт МВ720-1479',price:'5 800 ₽',rating:4.1,reviews:2100,pros:'20 л, 700 Вт, механика.',tags:['Эконом'],
|
||
buyerPros:['Самая дешёвая — работает как надо','Механика не ломается годами','Сделано в Беларуси — сервис везде'],
|
||
buyerCons:['Шумновата','Нет таймера с точностью до секунды']}],
|
||
mid: [{name:'Bosch BFL554MW0',price:'16 900 ₽',rating:4.7,reviews:1840,pros:'Встраиваемая, 21 л, HotAir.',tags:['Рекомендуем','Встраиваемая'],
|
||
buyerPros:['HotAir распределяет тепло равномерно — как конвекция','Встройка идеально вписалась в гарнитур','Работает тихо'],
|
||
buyerCons:['Объём 21 л — небольшой','Встройка требует точных размеров ниши']},
|
||
{name:'Electrolux EMT25203C',price:'14 000 ₽',rating:4.5,reviews:1560,pros:'Встраиваемая, 25 л, инвертор.',tags:['Инвертор'],
|
||
buyerPros:['Инвертор — тихая и экономичная','Лучшая цена среди встраиваемых','25 л — оптимальный объём'],
|
||
buyerCons:['Нет гриля','Подсветка слабая']},
|
||
{name:'AEG MBE2658SEM',price:'19 500 ₽',rating:4.6,reviews:1320,pros:'Встраиваемая, 26 л, гриль.',tags:['Встраиваемая','Гриль'],
|
||
buyerPros:['26 л — большой, влезает целая курица','Гриль работает отлично','Дизайн строгий, вписывается в любой стиль'],
|
||
buyerCons:['Дороже Bosch','Чистить гриль неудобно']},
|
||
{name:'Samsung MS23DG4504AW',price:'12 500 ₽',rating:4.6,reviews:2100,pros:'23 л, 800 Вт, Anti-Bacterial, настенная.',tags:['Anti-Bacterial'],
|
||
buyerPros:['Anti-Bacterial покрытие — гигиенично','Греет равномерно без холодных зон','Вписывается под любой шкаф'],
|
||
buyerCons:['Настенная, не встраиваемая','Нет гриля']},
|
||
{name:'LG MH6042D',price:'11 000 ₽',rating:4.4,reviews:3200,pros:'20 л, гриль, I-Wave инвертор.',tags:['Гриль','Инвертор'],
|
||
buyerPros:['I-Wave — равномерный нагрев без холодных пятен','Гриль с хрустящей корочкой','Тихая — инвертор'],
|
||
buyerCons:['20 л — компактная','Нет встройки']},
|
||
{name:'Gorenje MO6240SY2X',price:'13 900 ₽',rating:4.4,reviews:970,pros:'20 л, гриль, конвекция, 900 Вт.',tags:['Конвекция','Гриль'],
|
||
buyerPros:['Конвекция + гриль — мини-духовка для небольших блюд','900 Вт — самая мощная в классе','Удобное зеркальное стекло'],
|
||
buyerCons:['Нет встройки в шкаф','Шумновата на конвекции']},
|
||
{name:'Candy CMW20TNDB',price:'10 900 ₽',rating:4.2,reviews:1480,pros:'20 л, инвертор, 5 уровней, сенсор.',tags:['Инвертор','Сенсор'],
|
||
buyerPros:['Сенсор удобнее колёсика','Инвертор тихий','Лучшая цена с инвертором'],
|
||
buyerCons:['Нет гриля','Объём небольшой']}],
|
||
prem:[{name:'Neff HLAWG25S1',price:'34 000 ₽',rating:4.8,reviews:650,pros:'Встраиваемая, 25 л, конвекция, гриль.',tags:['Рекомендуем'],
|
||
buyerPros:['Конвекция + гриль — заменяет духовку для мелких блюд','Тихая абсолютно','Немецкое качество — ни одного нарекания'],
|
||
buyerCons:['Цена высокая','Ниша должна быть точно по размеру']},
|
||
{name:'Miele M 7244 TC',price:'58 000 ₽',rating:4.9,reviews:320,pros:'Встраиваемая, 26 л, M Touch, CleanSteel.',tags:['Miele'],
|
||
buyerPros:['M Touch — управление одним касанием','CleanSteel не остаётся следов от пальцев','Работает бесшумно как тень'],
|
||
buyerCons:['Самая дорогая в сегменте','Ремонт только у авторизованного сервиса']},
|
||
{name:'Smeg FMI325X',price:'42 000 ₽',rating:4.7,reviews:410,pros:'Встраиваемая, 25 л, дизайн 50-х.',tags:['Дизайн'],
|
||
buyerPros:['Ретро-дизайн — гости спрашивают где купили','Качество сборки итальянское — ощущается','Греет равномерно'],
|
||
buyerCons:['Покупают в первую очередь за дизайн','Нет инвертора']}],
|
||
}},
|
||
coffee:{name:'Кофемашина',emoji:'☕',
|
||
steps:[
|
||
{q:'Кто пьёт кофе дома?',field:'who',multi:true,opts:[
|
||
{e:'🧍',l:'Только я'},{e:'👫',l:'Двое'},{e:'👨👩👧',l:'Вся семья'},{e:'🎉',l:'Часто гости'}]},
|
||
{q:'Какой кофе пьёте?',field:'type',multi:true,opts:[
|
||
{e:'☕',l:'Эспрессо'},{e:'🥛',l:'Капучино / латте'},{e:'🫖',l:'Американо'},{e:'🧋',l:'Всё разное'}]},
|
||
{q:'Формат?',field:'format',multi:false,opts:[
|
||
{e:'💊',l:'Капсулы — просто и быстро'},{e:'🌾',l:'Зерно — автоматически'},{e:'🎭',l:'Рожковая — ритуал'}]},
|
||
{q:'Место установки?',field:'mount',multi:false,opts:[
|
||
{e:'🗄️',l:'Встраиваемая в шкаф'},{e:'🏠',l:'На столешницу'}]},
|
||
],
|
||
budgets:{
|
||
base:{label:'Базовый',range:'до 15 000 ₽',brands:'Nespresso · Dolce Gusto · Bosh Tassimo',icon:'💰'},
|
||
mid: {label:'Средний', range:'15 000 – 60 000 ₽',brands:"De'Longhi · Philips · Melitta",icon:'💳'},
|
||
prem:{label:'Премиум',range:'60 000 ₽ +',brands:'Jura · Miele · Siemens EQ',icon:'💎'},
|
||
},
|
||
niche:'Встраиваемая: ниша 45 × 45 см, глубина 550 мм.',
|
||
note:'Капсула ≈ 25₽/чашка, зерно ≈ 8₽ — зерновая окупается за год.',
|
||
anchor:'«Встраиваемая кофемашина — гости спрашивают: это из кофейни?»',
|
||
models:{
|
||
base:[{name:'Nespresso Vertuo Pop',price:'7 990 ₽',pros:'Капсульная, 5 размеров напитка.',tags:['Капсульная']},
|
||
{name:"De'Longhi EN 80.B",price:'11 500 ₽',pros:'Nespresso-совместимая, компактная.',tags:['Компактная']},
|
||
{name:'Bosch TAS3102',price:'6 500 ₽',pros:'Tassimo, многообразие вкусов.',tags:['Tassimo']}],
|
||
mid: [{name:"De'Longhi ECAM22.110.B",price:'28 900 ₽',pros:'Зерновая автомат, капучинатор, 1450 Вт.',tags:['Рекомендуем','Автомат']},
|
||
{name:'Philips EP2224/10',price:'24 500 ₽',pros:'Зерновая, 1 кофемолка, LatteGo.',tags:['LatteGo']},
|
||
{name:'Melitta Barista TS Smart',price:'42 000 ₽',pros:'Wi-Fi управление, 21 напиток.',tags:['Wi-Fi']}],
|
||
prem:[{name:'Jura E8 (EB)',price:'78 000 ₽',pros:'Встраиваемая, P.E.P. экстракция, 17 напитков.',tags:['Рекомендуем','Встраиваемая']},
|
||
{name:'Miele CVA 7440',price:'142 000 ₽',pros:'Встраиваемая, AromaticSystem, CupSensor.',tags:['Встраиваемая']},
|
||
{name:'Siemens TI9553X1RW',price:'89 000 ₽',pros:'iQ700, встраиваемая, autoMilk Clean.',tags:['iQ700']}],
|
||
}},
|
||
steamer:{name:'Пароварка',emoji:'💧',
|
||
steps:[
|
||
{q:'Для кого важен пар?',field:'who',multi:true,opts:[
|
||
{e:'👶',l:'Детское питание'},{e:'🥗',l:'ЗОЖ и диета'},{e:'🍽️',l:'Разнообразие блюд'},{e:'👩⚕️',l:'По медпоказаниям'}]},
|
||
{q:'Духовка уже выбрана?',field:'hasoven',multi:false,opts:[
|
||
{e:'✅',l:'Да, отдельная духовка'},{e:'🔄',l:'Духовка с функцией пара'},{e:'❌',l:'Нет духовки'}]},
|
||
{q:'Высота ниши?',field:'height',multi:false,opts:[
|
||
{e:'📦',l:'45 см'},{e:'📦',l:'60 см'}]},
|
||
],
|
||
budgets:{
|
||
base:{label:'Базовый',range:'до 40 000 ₽',brands:'AEG · Electrolux · Gorenje',icon:'💰'},
|
||
mid: {label:'Средний', range:'40 000 – 80 000 ₽',brands:'Bosch · Neff · Siemens',icon:'💳'},
|
||
prem:{label:'Премиум',range:'80 000 ₽ +',brands:'Miele · V-ZUG · Gaggenau',icon:'💎'},
|
||
},
|
||
niche:'Стандартно в колонне: пароварка 45 см + духовка 60 см.',
|
||
note:'Если у духовки уже есть пар — уточните нужна ли отдельная.',
|
||
anchor:'«Паровой шкаф + духовка = профессиональная кухня дома»',
|
||
models:{
|
||
base:[{name:'AEG BS7304021M',price:'38 500 ₽',pros:'45 см, SurroundCook, 33 л.',tags:['AEG']},
|
||
{name:'Electrolux EOB8956AOX',price:'34 000 ₽',pros:'60 см, SteamBake, электрическая.',tags:['SteamBake']},
|
||
{name:'Gorenje BCS798S24X',price:'29 000 ₽',pros:'60 см, пар + конвекция.',tags:['Конвекция']}],
|
||
mid: [{name:'Bosch CDG634BS1',price:'62 000 ₽',pros:'45 см, ActiveSteam, 34 л.',tags:['Рекомендуем','ActiveSteam']},
|
||
{name:'Neff C17FS22H0',price:'68 000 ₽',pros:'45 см, CircoTherm с паром.',tags:['CircoTherm']},
|
||
{name:'Siemens CS536G1S1',price:'58 000 ₽',pros:'45 см, cooKing, 31 л.',tags:['cooKing']}],
|
||
prem:[{name:'Miele DGC 7840',price:'112 000 ₽',pros:'45 см, DualSteam, PerfectClean.',tags:['Рекомендуем','DualSteam']},
|
||
{name:'V-ZUG SteamCombi V6000',price:'145 000 ₽',pros:'Швейцарское качество, 45 см.',tags:['Швейцария']},
|
||
{name:'Gaggenau BS 474 111',price:'168 000 ₽',pros:'45 см, FullSteam, 43 л.',tags:['Gaggenau']}],
|
||
}},
|
||
freezer:{name:'Морозильник',emoji:'🧊',
|
||
steps:[
|
||
{q:'Для чего морозильник?',field:'usage',multi:true,opts:[
|
||
{e:'🥕',l:'Заготовки / дача'},{e:'🐟',l:'Рыбалка / охота'},{e:'➕',l:'Доп. объём к холодильнику'},{e:'🛒',l:'Редкие закупки'}]},
|
||
{q:'Тип морозильника?',field:'type',multi:false,opts:[
|
||
{e:'🗄️',l:'Встраиваемый ящик'},{e:'🏠',l:'Вертикальный отдельный'},{e:'📦',l:'Ларь (горизонтальный)'}]},
|
||
{q:'Объём?',field:'vol',multi:false,opts:[
|
||
{e:'📦',l:'до 100 л'},{e:'📦',l:'100–200 л'},{e:'📦',l:'200 л+'}]},
|
||
],
|
||
budgets:{
|
||
base:{label:'Базовый',range:'до 20 000 ₽',brands:'Indesit · Hotpoint · Beko',icon:'💰'},
|
||
mid: {label:'Средний', range:'20 000 – 45 000 ₽',brands:'Bosch · Samsung · Liebherr',icon:'💳'},
|
||
prem:{label:'Премиум',range:'45 000 ₽ +',brands:'Liebherr · Miele · Sub-Zero',icon:'💎'},
|
||
},
|
||
niche:'Встраиваемый: выдвижной ящик 60 см.',
|
||
note:'Ларь — не встраивается в мебель, нужно место вне кухни.',
|
||
anchor:'«Морозильник = покупать раз в неделю, а не каждый день»',
|
||
models:{
|
||
base:[{name:'Indesit EF 16 D1',price:'14 500 ₽',pros:'85 л, A+, 4 режима.',tags:['Базовый']},
|
||
{name:'Beko FN 1170',price:'16 000 ₽',pros:'97 л, No Frost, класс A+.',tags:['No Frost']},
|
||
{name:'Hotpoint-Ariston HFZ 612 B',price:'12 900 ₽',pros:'119 л, ларь, компрессорный.',tags:['Ларь']}],
|
||
mid: [{name:'Bosch GSD36VWEP',price:'32 000 ₽',pros:'242 л, No Frost, VitaFresh, A+++.',tags:['Рекомендуем','No Frost']},
|
||
{name:'Samsung RZ32M7120WW',price:'28 500 ₽',pros:'315 л, No Frost, SuperFreezing.',tags:['SuperFreezing']},
|
||
{name:'Liebherr FKDv 4523',price:'41 000 ₽',pros:'242 л, SmartFrost, alarm.',tags:['SmartFrost']}],
|
||
prem:[{name:'Liebherr GNef 5023',price:'58 000 ₽',pros:'357 л, NoFrost, SuperFrost, A+++.',tags:['Рекомендуем','NoFrost']},
|
||
{name:'Miele F 2811 SF',price:'89 000 ₽',pros:'277 л, SoftClose, SuperFrost.',tags:['Miele']},
|
||
{name:'Sub-Zero UW24',price:'145 000 ₽',pros:'Встраиваемый, 60 л, американское кач-во.',tags:['Sub-Zero']}],
|
||
}},
|
||
wine:{name:'Винный шкаф',emoji:'🍷',
|
||
steps:[
|
||
{q:'Сколько бутылок храните?',field:'bottles',multi:false,opts:[
|
||
{e:'🍾',l:'до 20 бутылок'},{e:'🍾',l:'20–50 бутылок'},{e:'🏆',l:'50+ (коллекция)'}]},
|
||
{q:'Красное и белое вместе?',field:'zones',multi:false,opts:[
|
||
{e:'✅',l:'Да, разные температуры'},{e:'❌',l:'Нет, один вид вина'}]},
|
||
{q:'Где установить?',field:'place',multi:false,opts:[
|
||
{e:'🚪',l:'Под столешницу встраиваемый'},{e:'🏠',l:'Отдельностоящий'}]},
|
||
{q:'Что важно?',field:'style',multi:true,opts:[
|
||
{e:'🪟',l:'Стеклянная дверь'},{e:'💡',l:'Подсветка коллекции'},{e:'🔇',l:'Тихая работа'}]},
|
||
],
|
||
budgets:{
|
||
base:{label:'Базовый',range:'до 25 000 ₽',brands:'Libhof · Cavanova · Gemlux',icon:'💰'},
|
||
mid: {label:'Средний', range:'25 000 – 60 000 ₽',brands:'Dunavox · Haier · Caso',icon:'💳'},
|
||
prem:{label:'Премиум',range:'60 000 ₽ +',brands:'Liebherr · Miele · EuroCave',icon:'💎'},
|
||
},
|
||
niche:'Встраиваемый под столешницу: 60 × 82 см.',
|
||
note:'Две зоны: 8–12°С (белое / игристое) и 14–18°С (красное).',
|
||
anchor:'«Гости видят коллекцию — это уже часть интерьера»',
|
||
models:{
|
||
base:[{name:'Libhof CFD-28',price:'19 900 ₽',pros:'28 бутылок, 2 зоны, LED.',tags:['2 зоны']},
|
||
{name:'Gemlux GL-WC28DF',price:'18 500 ₽',pros:'28 бутылок, компрессорный.',tags:['Компрессор']},
|
||
{name:'Cavanova OW-008T2',price:'22 000 ₽',pros:'8 бутылок, термоэлектрический.',tags:['Компактный']}],
|
||
mid: [{name:'Dunavox DXFH-20.62',price:'38 900 ₽',pros:'20 бутылок, 2 зоны, стекло.',tags:['Рекомендуем','2 зоны']},
|
||
{name:'Haier WS50GDA',price:'34 500 ₽',pros:'50 бутылок, стекло, LED, 2 зоны.',tags:['50 бутылок']},
|
||
{name:'Caso WineComfort 660 Smart',price:'42 000 ₽',pros:'66 бутылок, Wi-Fi, 3 зоны.',tags:['Wi-Fi','3 зоны']}],
|
||
prem:[{name:'Liebherr WKes 553 GrandCru',price:'78 000 ₽',pros:'48 бутылок, встраиваемый, 2 зоны.',tags:['Рекомендуем','Встраиваемый']},
|
||
{name:'EuroCave V-REVEL-L',price:'112 000 ₽',pros:'141 бутылка, монтаж в нишу.',tags:['141 бутылка']},
|
||
{name:'Miele KWT 6722 iGS',price:'198 000 ₽',pros:'83 бутылки, 3 зоны, интегрируется в кухню.',tags:['Miele']}],
|
||
}},
|
||
};
|
||
|
||
var TECH_TYPES = [
|
||
{key:'oven', label:'Духовой шкаф'},
|
||
{key:'cooktop', label:'Варочная панель'},
|
||
{key:'fridge', label:'Холодильник'},
|
||
{key:'hood', label:'Вытяжка'},
|
||
{key:'dishwasher',label:'Посудомойка'},
|
||
{key:'micro', label:'Микроволновка'},
|
||
{key:'coffee', label:'Кофемашина'},
|
||
{key:'steamer', label:'Пароварка'},
|
||
{key:'freezer', label:'Морозильник'},
|
||
{key:'wine', label:'Винный шкаф'},
|
||
];
|
||
var TECH_ICONS = {
|
||
oven: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" width="100%" height="100%"><rect x="2" y="4" width="20" height="16" rx="2"/><rect x="6" y="8" width="12" height="9" rx="1"/><circle cx="7.5" cy="6" r=".8" fill="currentColor" stroke="none"/><circle cx="12" cy="6" r=".8" fill="currentColor" stroke="none"/><circle cx="16.5" cy="6" r=".8" fill="currentColor" stroke="none"/></svg>',
|
||
cooktop: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" width="100%" height="100%"><rect x="2" y="4" width="20" height="16" rx="2"/><circle cx="8.5" cy="11" r="2.5"/><circle cx="15.5" cy="11" r="2.5"/><circle cx="8.5" cy="17" r="1.3"/><circle cx="15.5" cy="17" r="1.3"/></svg>',
|
||
fridge: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" width="100%" height="100%"><rect x="5" y="2" width="14" height="20" rx="2"/><line x1="5" y1="9" x2="19" y2="9"/><line x1="9" y1="5.5" x2="9" y2="8"/><line x1="9" y1="13" x2="9" y2="19"/></svg>',
|
||
hood: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" width="100%" height="100%"><path d="M4 4L8 14h8l4-10z"/><rect x="8" y="14" width="8" height="3" rx="1"/><line x1="10" y1="17" x2="10" y2="21"/><line x1="14" y1="17" x2="14" y2="21"/></svg>',
|
||
dishwasher:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" width="100%" height="100%"><rect x="3" y="3" width="18" height="18" rx="2"/><line x1="3" y1="8" x2="21" y2="8"/><path d="M8 14Q12 11 16 14"/><path d="M8 18Q12 15 16 18"/><circle cx="12" cy="5.5" r=".8" fill="currentColor" stroke="none"/></svg>',
|
||
micro: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" width="100%" height="100%"><rect x="2" y="6" width="20" height="12" rx="2"/><rect x="4" y="8" width="12" height="8" rx="1"/><circle cx="20.2" cy="10" r=".8" fill="currentColor" stroke="none"/><circle cx="20.2" cy="12" r=".8" fill="currentColor" stroke="none"/><circle cx="20.2" cy="14" r=".8" fill="currentColor" stroke="none"/></svg>',
|
||
coffee: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" width="100%" height="100%"><rect x="5" y="3" width="14" height="18" rx="2"/><rect x="8" y="7" width="8" height="5" rx="1"/><path d="M19 9Q22 9 22 11Q22 13 19 13"/><line x1="10" y1="17" x2="14" y2="17"/></svg>',
|
||
steamer: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" width="100%" height="100%"><rect x="3" y="10" width="18" height="10" rx="2"/><rect x="5" y="5" width="14" height="5" rx="1"/><line x1="8" y1="2" x2="8" y2="5"/><line x1="12" y1="2" x2="12" y2="5"/><line x1="16" y1="2" x2="16" y2="5"/></svg>',
|
||
freezer: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" width="100%" height="100%"><rect x="5" y="2" width="14" height="20" rx="2"/><line x1="5" y1="15" x2="19" y2="15"/><line x1="9" y1="17" x2="9" y2="21"/><line x1="12" y1="5" x2="12" y2="13"/><line x1="9.5" y1="7" x2="14.5" y2="11"/><line x1="14.5" y1="7" x2="9.5" y2="11"/></svg>',
|
||
wine: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" width="100%" height="100%"><rect x="2" y="2" width="20" height="20" rx="2"/><line x1="2" y1="7" x2="22" y2="7"/><path d="M8 12Q8 10 9 10Q10 10 10 12L10 18Q10 19 9 19Q8 19 8 18Z"/><path d="M14 11Q14 9 15 9Q16 9 16 11L16 18Q16 19 15 19Q14 19 14 18Z"/><line x1="9" y1="4" x2="9" y2="7"/><line x1="15" y1="4" x2="15" y2="7"/></svg>',
|
||
};
|
||
function _techIcon(key,size){
|
||
size=size||24;
|
||
return '<div style="width:'+size+'px;height:'+size+'px;flex-shrink:0">'+(TECH_ICONS[key]||TECH_ICONS['oven'])+'</div>';
|
||
}
|
||
|
||
// ── renderScreen ──────────────────────────────────────────────────────────────
|
||
// ── CALC EDIT HELPERS — live inline ──────────────────────────────────────────
|
||
function _refreshCalc(){ document.getElementById('screen').innerHTML=renderScreen('manager_order'); }
|
||
|
||
function _liveContract(oi, val) {
|
||
var v = parseInt(val, 10);
|
||
window._managerOrders[oi].amount = (v > 0) ? v : 0;
|
||
}
|
||
function _liveAdvAmt(oi, ai, val) {
|
||
var v = parseInt(val, 10);
|
||
window._managerOrders[oi].advances[ai].amount = (v > 0) ? v : 0;
|
||
}
|
||
function _liveAdvDate(oi, ai, val) {
|
||
window._managerOrders[oi].advances[ai].date = val.trim() || null;
|
||
}
|
||
function _liveAsm(oi, val) {
|
||
var v = parseInt(val, 10);
|
||
window._managerOrders[oi].assembly = (v > 0) ? v : 0;
|
||
}
|
||
function _toggleAdvPaid(oi, ai, paid) {
|
||
window._managerOrders[oi].advances[ai].paid = paid;
|
||
if (paid && !window._managerOrders[oi].advances[ai].date) {
|
||
var d=new Date(); window._managerOrders[oi].advances[ai].date =
|
||
String(d.getDate()).padStart(2,'0')+'.'+String(d.getMonth()+1).padStart(2,'0')+'.'+d.getFullYear();
|
||
}
|
||
_refreshCalc();
|
||
}
|
||
function _toggleAsm(oi, paid) {
|
||
window._managerOrders[oi].assemblyPaid = paid;
|
||
_refreshCalc();
|
||
}
|
||
|
||
// ── LEAD CARD ─────────────────────────────────────────────────────────────────
|
||
var _LEAD_STAGES = [
|
||
{key:'new', label:'Новый', short:'Новый'},
|
||
{key:'meeting', label:'Встреча', short:'Встреча'},
|
||
{key:'kp', label:'КП', short:'КП'},
|
||
{key:'thinking', label:'Думает', short:'Думает'},
|
||
{key:'contract', label:'Договор', short:'Договор'},
|
||
{key:'lost', label:'Отказ', short:'Отказ'},
|
||
];
|
||
var _LEAD_TYPE_LABELS = {kitchen:'Кухня',wardrobe:'Шкаф-купе',hallway:'Прихожая',other:'Другое'};
|
||
var _APPT_TYPES = ['Консультация','Замер','Показ проекта','Подписание договора'];
|
||
|
||
function screenLead() {
|
||
var o = window._managerOrders[window._activeOrder];
|
||
if (!o || !o.isLead) return '<div style="padding:32px">Лид не найден</div>';
|
||
var oi = window._activeOrder;
|
||
var bk = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M15 18l-6-6 6-6"/></svg>';
|
||
|
||
// ── Стадия-рейл ──
|
||
var si = _LEAD_STAGES.findIndex(function(s){ return s.key===o.leadStage; });
|
||
var rail = '<div style="display:flex;align-items:flex-start;padding:16px 16px 4px">';
|
||
_LEAD_STAGES.filter(function(s){ return s.key!=='lost'; }).forEach(function(s,idx,arr){
|
||
var sIdx = _LEAD_STAGES.findIndex(function(x){ return x.key===s.key; });
|
||
var isDone = sIdx < si && o.leadStage!=='lost';
|
||
var isCurr = s.key===o.leadStage;
|
||
var dotCls = 'stage-dot'+(isDone?' done':isCurr?' curr':'');
|
||
var lblCls = 'stage-lbl'+(isCurr?' curr':'');
|
||
var inner = isDone?'✓':(idx+1);
|
||
rail += '<div class="stage-node" onclick="_setLeadStage('+oi+',\''+s.key+'\')">'
|
||
+'<div class="'+dotCls+'">'+inner+'</div>'
|
||
+'<div class="'+lblCls+'">'+s.short+'</div></div>';
|
||
if (idx < arr.length-1)
|
||
rail += '<div class="stage-conn'+(isDone?' done':'')+'"></div>';
|
||
});
|
||
rail += '</div>';
|
||
|
||
// ── Задачи ──
|
||
var tasks = o.tasks||[];
|
||
var taskHtml = tasks.length
|
||
? tasks.map(function(t,ti){
|
||
var chk = t.done
|
||
? '<div style="width:20px;height:20px;border-radius:6px;background:var(--success);border:2px solid var(--success);flex-shrink:0;display:flex;align-items:center;justify-content:center;cursor:pointer" onclick="_leadTaskToggle('+oi+','+ti+')">'
|
||
+'<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="width:20px;height:20px;border-radius:6px;border:2px solid #CBD5E1;flex-shrink:0;cursor:pointer" onclick="_leadTaskToggle('+oi+','+ti+')"></div>';
|
||
return '<div style="display:flex;align-items:flex-start;gap:10px;padding:10px 0;border-bottom:1px solid rgba(0,0,0,.06)">'
|
||
+chk
|
||
+'<div style="flex:1">'
|
||
+(t.hi?'<span style="font-size:10px;font-weight:800;background:#FEF3C7;color:#92400E;border-radius:6px;padding:1px 7px;margin-right:6px">Срочно</span>':'')
|
||
+'<span style="font-size:13px;font-weight:600;color:'+(t.done?'var(--muted)':'var(--ink)')+(t.done?';text-decoration:line-through':'')+'">'
|
||
+t.text+'</span>'
|
||
+'</div>'
|
||
+'<div style="font-size:11px;color:var(--muted);cursor:pointer;padding:2px 4px" onclick="_leadTaskDelete('+oi+','+ti+')">✕</div>'
|
||
+'</div>';
|
||
}).join('')
|
||
: '<div style="padding:12px 0;font-size:13px;color:var(--muted)">Задач нет</div>';
|
||
|
||
// ── Встречи из _APPTS ──
|
||
var apptHtml = '';
|
||
_APPTS.forEach(function(dayAppts, di){
|
||
dayAppts.forEach(function(a){
|
||
if (!a.order && a.client===o.client || (a.client===o.client)) {
|
||
apptHtml += '<div style="display:flex;align-items:center;gap:10px;padding:9px 0;border-bottom:1px solid rgba(0,0,0,.06)">'
|
||
+'<div style="width:42px;height:42px;border-radius:10px;background:#EEF2FF;display:flex;flex-direction:column;align-items:center;justify-content:center;flex-shrink:0">'
|
||
+'<div style="font-size:10px;font-weight:700;color:#4338CA">'+_DOW[di]+'</div>'
|
||
+'<div style="font-size:14px;font-weight:900;color:#4338CA">'+_DATES[di]+'</div>'
|
||
+'</div>'
|
||
+'<div style="flex:1">'
|
||
+'<div style="font-size:13px;font-weight:700;color:var(--ink)">'+a.type+'</div>'
|
||
+'<div style="font-size:11px;color:var(--muted)">'+a.time+'</div>'
|
||
+'</div></div>';
|
||
}
|
||
});
|
||
});
|
||
if (!apptHtml) apptHtml = '<div style="padding:10px 0;font-size:13px;color:var(--muted)">Встреч не запланировано</div>';
|
||
|
||
// ── Источник ──
|
||
var srcColor = o.source==='Зал'?'#0D9488':'#7C3AED';
|
||
var srcBg = o.source==='Зал'?'#CCFBF1':'#EDE9FE';
|
||
var ldStage = _LEAD_STAGES.find(function(s){return s.key===o.leadStage;})||{};
|
||
var ldBadgeC = {new:'#1D4ED8',meeting:'#0D9488',kp:'#D97706',thinking:'#64748B',lost:'#DC2626'}[o.leadStage]||'#64748B';
|
||
var ldBadgeBg= {new:'#DBEAFE',meeting:'#CCFBF1',kp:'#FEF3C7',thinking:'#F1F5F9',lost:'#FEE2E2'}[o.leadStage]||'#F1F5F9';
|
||
|
||
return '<div class="page">'
|
||
|
||
// Header
|
||
+'<div class="page-header">'
|
||
+'<button class="back-btn" onclick="document.getElementById(\'screen\').innerHTML=renderScreen(\'manager_home\');document.getElementById(\'nav\').innerHTML=navBar()">'+bk+'</button>'
|
||
+'<div style="flex:1">'
|
||
+'<div style="font-size:17px;font-weight:800;color:var(--ink)">'+o.client+'</div>'
|
||
+'<div style="font-size:11px;color:var(--muted)">'+o.phone+'</div>'
|
||
+'</div>'
|
||
+'<a href="tel:'+o.phone+'" style="width:34px;height:34px;border-radius:10px;background:#EEF2FF;display:flex;align-items:center;justify-content:center;text-decoration:none">'
|
||
+'<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="#4338CA" stroke-width="2.5"><path d="M22 16.92v3a2 2 0 01-2.18 2 19.79 19.79 0 01-8.63-3.07A19.5 19.5 0 013.07 9.81 19.79 19.79 0 01 0 1.18 2 2 0 012 1h3a2 2 0 012 1.72 12.84 12.84 0 00.7 2.81 2 2 0 01-.45 2.11L6.09 8.91a16 16 0 006 6l1.27-1.27a2 2 0 012.11-.45 12.84 12.84 0 002.81.7A2 2 0 0122 16.92z"/></svg>'
|
||
+'</a></div>'
|
||
|
||
// Мета-строка
|
||
+'<div style="padding:10px 16px;display:flex;gap:8px;flex-wrap:wrap;border-bottom:1px solid rgba(0,0,0,.06)">'
|
||
+'<span style="font-size:11px;font-weight:700;padding:3px 10px;border-radius:20px;background:'+srcBg+';color:'+srcColor+'">'+o.source+'</span>'
|
||
+'<span style="font-size:11px;color:var(--muted);padding:3px 0">'+o.sourceDate+'</span>'
|
||
+'<span style="font-size:11px;font-weight:700;padding:3px 10px;border-radius:20px;background:'+ldBadgeBg+';color:'+ldBadgeC+'">'+ldStage.label+'</span>'
|
||
+'<span style="font-size:11px;color:var(--muted);padding:3px 0">'+(_LEAD_TYPE_LABELS[o.type]||o.type)+' · '+o.label+'</span>'
|
||
+'</div>'
|
||
|
||
// Стадия рейл
|
||
+rail
|
||
|
||
+'<div style="padding:0 16px">'
|
||
|
||
// Заметка
|
||
+'<div class="section-label" style="margin:12px 0 6px">Заметка</div>'
|
||
+'<textarea id="lead_note_'+oi+'" onchange="_saveLeadNote('+oi+',this.value)" '
|
||
+'style="width:100%;border:2px solid #E2E8F0;border-radius:12px;padding:11px 14px;font-size:13px;color:var(--ink);font-family:Inter,sans-serif;resize:none;outline:none;background:var(--bg);line-height:1.5" rows="2" placeholder="Добавить заметку...">'+(o.leadNote||'')+'</textarea>'
|
||
|
||
// Задачи
|
||
+'<div class="section-label" style="margin:14px 0 6px">Задачи</div>'
|
||
+'<div class="card" style="padding:0 16px">'+taskHtml+'</div>'
|
||
+'<button onclick="_addLeadTask('+oi+')" style="display:flex;align-items:center;gap:6px;background:none;border:1.5px dashed #CBD5E1;border-radius:10px;padding:8px 14px;font-size:12px;font-weight:700;color:var(--muted);cursor:pointer;margin-top:8px;width:100%;justify-content:center">+ Добавить задачу</button>'
|
||
|
||
// Встречи
|
||
+'<div class="section-label" style="margin:14px 0 6px">Встречи</div>'
|
||
+'<div class="card" style="padding:0 16px">'+apptHtml+'</div>'
|
||
+'<button onclick="_addLeadAppt('+oi+')" style="display:flex;align-items:center;gap:6px;background:none;border:1.5px dashed #CBD5E1;border-radius:10px;padding:8px 14px;font-size:12px;font-weight:700;color:var(--muted);cursor:pointer;margin-top:8px;width:100%;justify-content:center">+ Запланировать встречу</button>'
|
||
|
||
// Конверсионная кнопка
|
||
+'<div style="margin:20px 0 8px">'
|
||
+'<button onclick="_convertToOrder('+oi+')" '
|
||
+'class="btn-primary">'
|
||
+'✍️ Договор подписан → создать заказ'
|
||
+'</button>'
|
||
+'<button onclick="_lostLead('+oi+')" '
|
||
+'style="width:100%;background:none;color:var(--muted);border:none;padding:10px;font-size:12px;font-weight:600;cursor:pointer;margin-top:4px">'
|
||
+'Отметить как отказ'
|
||
+'</button>'
|
||
+'</div>'
|
||
+'</div></div>';
|
||
}
|
||
|
||
// Хелперы лида
|
||
function _setLeadStage(oi, key) {
|
||
window._managerOrders[oi].leadStage = key;
|
||
// Автозадача при смене этапа
|
||
var autoTasks = {
|
||
meeting: {text:'Подготовить замерный лист', hi:false},
|
||
kp: {text:'Отправить КП клиенту', hi:true},
|
||
thinking:{text:'Позвонить через 3 дня — уточнить решение', hi:false},
|
||
};
|
||
if (autoTasks[key]) {
|
||
var o = window._managerOrders[oi];
|
||
var already = (o.tasks||[]).some(function(t){ return t.text===autoTasks[key].text; });
|
||
if (!already) {
|
||
if (!o.tasks) o.tasks=[];
|
||
o.tasks.push({text:autoTasks[key].text, done:false, hi:autoTasks[key].hi, auto:true});
|
||
}
|
||
}
|
||
document.getElementById('screen').innerHTML=renderScreen('manager_lead');
|
||
_toast('Этап: '+key,'var(--accent)');
|
||
}
|
||
function _leadTaskToggle(oi, ti) {
|
||
window._managerOrders[oi].tasks[ti].done = !window._managerOrders[oi].tasks[ti].done;
|
||
document.getElementById('screen').innerHTML=renderScreen('manager_lead');
|
||
}
|
||
function _leadTaskDelete(oi, ti) {
|
||
window._managerOrders[oi].tasks.splice(ti,1);
|
||
document.getElementById('screen').innerHTML=renderScreen('manager_lead');
|
||
}
|
||
function _saveLeadNote(oi, val) {
|
||
window._managerOrders[oi].leadNote = val;
|
||
}
|
||
function _addLeadTask(oi) {
|
||
var txt = prompt('Текст задачи:');
|
||
if (!txt) return;
|
||
if (!window._managerOrders[oi].tasks) window._managerOrders[oi].tasks=[];
|
||
window._managerOrders[oi].tasks.push({text:txt.trim(), done:false, hi:false});
|
||
document.getElementById('screen').innerHTML=renderScreen('manager_lead');
|
||
}
|
||
function _addLeadAppt(oi) {
|
||
var o = window._managerOrders[oi];
|
||
_toast('📅 Встреча добавлена в график','var(--accent)');
|
||
}
|
||
function _convertToOrder(oi) {
|
||
var o = window._managerOrders[oi];
|
||
o.isLead = false;
|
||
o.leadStage = 'contract';
|
||
o.stage = 1;
|
||
o.contract = 'МБ-2026-' + String(100 + oi).padStart(3,'0');
|
||
o.amount = 0;
|
||
o.advance = 0;
|
||
o.assembly = 0;
|
||
o.assemblyPaid = false;
|
||
o.advances = [
|
||
{label:'Аванс 1 · 50% при подписании', amount:0, paid:false, date:null},
|
||
{label:'Аванс 2 · при готовности к отгрузке', amount:0, paid:false, date:null},
|
||
];
|
||
o.tech = [];
|
||
o.blocker = null;
|
||
o.rooms = [];
|
||
o.zovContract = '';
|
||
o.zovStatus = null;
|
||
window._activeOrder = oi;
|
||
window._roomsEditing = true;
|
||
document.getElementById('screen').innerHTML=renderScreen('manager_order');
|
||
document.getElementById('nav').innerHTML=navBar();
|
||
_toast('✅ Лид переведён в заказ '+o.contract,'var(--success)');
|
||
}
|
||
function _lostLead(oi) {
|
||
window._managerOrders[oi].leadStage='lost';
|
||
document.getElementById('screen').innerHTML=renderScreen('manager_home');
|
||
document.getElementById('nav').innerHTML=navBar();
|
||
_toast('Отмечен как отказ','var(--muted)');
|
||
}
|
||
|
||
// ── GPS ────────────────────────────────────────────────────────────────────────
|
||
var _SALON = {lat:55.7494,lng:37.6172,name:'Салон на Арбате',addr:'ул. Арбат, 34'};
|
||
var _GPS_RADIUS = window._CONFIG.gpsRadius; // из настроек директора
|
||
function _gpsDist(lat1,lng1,lat2,lng2){
|
||
var R=6371000,dL=(lat2-lat1)*Math.PI/180,dG=(lng2-lng1)*Math.PI/180;
|
||
var a=Math.sin(dL/2)*Math.sin(dL/2)+Math.cos(lat1*Math.PI/180)*Math.cos(lat2*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 _gpsResult(d, dist, action){
|
||
var ok = dist<=_GPS_RADIUS;
|
||
window._GPS_STATE[d] = {ok:ok, dist:dist};
|
||
if(ok){ action=='checkin'?_schedCheckin(d):_schedCheckout(d); }
|
||
else { document.getElementById('screen').innerHTML=renderScreen('manager_schedule'); }
|
||
}
|
||
function _gpsRequest(d, action){
|
||
window._GPS_STATE[d]='loading';
|
||
document.getElementById('screen').innerHTML=renderScreen('manager_schedule');
|
||
|
||
function onGot(lat,lng){ _gpsResult(d,_gpsDist(lat,lng,_SALON.lat,_SALON.lng),action); }
|
||
function onFail(){ _gpsResult(d, window._GPS_DEMO==='near'?47:1420, action); }
|
||
|
||
// 1. Telegram LocationManager (Bot API 8.0+, production path)
|
||
var tg = window.Telegram&&Telegram.WebApp;
|
||
if(tg&&tg.LocationManager){
|
||
tg.LocationManager.init(function(){
|
||
if(!tg.LocationManager.isLocationAvailable){ onFail(); return; }
|
||
tg.LocationManager.getLocation(function(loc){ loc?onGot(loc.latitude,loc.longitude):onFail(); });
|
||
});
|
||
}
|
||
// 2. Старый TG API (Bot API <8.0)
|
||
else if(tg&&tg.requestLocation){
|
||
tg.requestLocation(function(loc){ loc?onGot(loc.latitude,loc.longitude):onFail(); });
|
||
}
|
||
// 3. Браузер (разработка / веб-превью)
|
||
else if(navigator.geolocation){
|
||
navigator.geolocation.getCurrentPosition(
|
||
function(p){ onGot(p.coords.latitude,p.coords.longitude); },
|
||
onFail, {timeout:8000,enableHighAccuracy:true}
|
||
);
|
||
}
|
||
// 4. Демо-режим (мокап без GPS)
|
||
else{ onFail(); }
|
||
}
|
||
function _gpsForce(d, action){ // принудительно, несмотря на расстояние
|
||
window._GPS_STATE[d]={ok:false,dist:window._GPS_STATE[d]&&window._GPS_STATE[d].dist||0,forced:true};
|
||
action=='checkin'?_schedCheckin(d):_schedCheckout(d);
|
||
}
|
||
function _gpsCancelCheck(d){
|
||
window._GPS_STATE[d]=null;
|
||
document.getElementById('screen').innerHTML=renderScreen('manager_schedule');
|
||
}
|
||
function _toggleGpsDemo(){
|
||
window._GPS_DEMO = window._GPS_DEMO==='near'?'far':'near';
|
||
document.getElementById('screen').innerHTML=renderScreen('manager_schedule');
|
||
}
|
||
|
||
// ── SCHEDULE ──────────────────────────────────────────────────────────────────
|
||
function _schedSelectDay(i){
|
||
window._schedDay=i;
|
||
document.getElementById('screen').innerHTML=renderScreen('manager_schedule');
|
||
}
|
||
function _schedCheckin(d){
|
||
var now=new Date();
|
||
var t=String(now.getHours()).padStart(2,'0')+':'+String(now.getMinutes()).padStart(2,'0');
|
||
var gps = (window._GPS_STATE[d]&&typeof window._GPS_STATE[d]==='object') ? window._GPS_STATE[d] : null;
|
||
window._CHECKIN[d]={in:t,out:null,gpsIn:gps};
|
||
window._GPS_STATE[d]=null;
|
||
document.getElementById('screen').innerHTML=renderScreen('manager_schedule');
|
||
}
|
||
function _schedCheckout(d){
|
||
if(window._CHECKIN[d]){
|
||
var now=new Date();
|
||
var t=String(now.getHours()).padStart(2,'0')+':'+String(now.getMinutes()).padStart(2,'0');
|
||
var gps = (window._GPS_STATE[d]&&typeof window._GPS_STATE[d]==='object') ? window._GPS_STATE[d] : null;
|
||
window._CHECKIN[d].out=t;
|
||
window._CHECKIN[d].gpsOut=gps;
|
||
window._GPS_STATE[d]=null;
|
||
}
|
||
document.getElementById('screen').innerHTML=renderScreen('manager_schedule');
|
||
}
|
||
function _schedCancelCheckin(d){
|
||
delete window._CHECKIN[d];
|
||
document.getElementById('screen').innerHTML=renderScreen('manager_schedule');
|
||
}
|
||
function _schedReqShift(mday){
|
||
window._SCHED_REQ[mday]='pending-work';
|
||
document.getElementById('screen').innerHTML=renderScreen('manager_schedule');
|
||
}
|
||
function _schedReqOff(mday){
|
||
window._SCHED_REQ[mday]='pending-off';
|
||
document.getElementById('screen').innerHTML=renderScreen('manager_schedule');
|
||
}
|
||
function _schedCancelReq(mday){
|
||
delete window._SCHED_REQ[mday];
|
||
document.getElementById('screen').innerHTML=renderScreen('manager_schedule');
|
||
}
|
||
|
||
function _mFmt(m){ return m>=1000?(m/1000).toFixed(1)+' км':m+' м'; }
|
||
|
||
// helper: получить смену по дате мая (1–31); May 1,2026 = Thu = dow-index 3
|
||
function _mayDow(date){ return (date+2)%7; } // 0=Пн…6=Вс
|
||
// helper: demo-данные есть только для недели 19–25 (индексы 0–6)
|
||
function _mayAppts(date){ var wi=date-19; return (wi>=0&&wi<=6)?(_APPTS[wi]||[]):[]; }
|
||
function _mayCi(date) { var wi=date-19; return (wi>=0&&wi<=6)?window._CHECKIN[wi]:null; }
|
||
function _mayTasks(date){ var wi=date-19; return (wi>=0&&wi<=6)?(_TASKS[wi]||[]):[]; }
|
||
// глобальный выбранный день месяца (1–31, null = не выбран в месяце)
|
||
// _schedDay — week-index 0–6; для месяца используем _schedMday (1–31)
|
||
window._schedMday = window._schedMday || 22; // today = 22 мая
|
||
|
||
function _schedSelectMday(md){
|
||
window._schedMday = md;
|
||
// синхронизируем недельный индекс если это demo-неделя
|
||
var wi = md-19;
|
||
if(wi>=0&&wi<=6) window._schedDay=wi;
|
||
document.getElementById('screen').innerHTML=renderScreen('manager_schedule');
|
||
}
|
||
function _schedToggleView(v){
|
||
window._schedView=v;
|
||
document.getElementById('screen').innerHTML=renderScreen('manager_schedule');
|
||
}
|
||
|
||
// Общий рендер блока смены + таймлайна по дате мая
|
||
function _renderShiftDetail(mday){
|
||
var dow = _mayDow(mday);
|
||
var isWorkDay = _WORK[dow];
|
||
var ci = _mayCi(mday);
|
||
var wi = mday-19; // demo-неделя 19–25
|
||
var req = window._SCHED_REQ[mday]; // 'pending-work' | 'pending-off' | undefined
|
||
var dowShort = ['Пн','Вт','Ср','Чт','Пт','Сб','Вс'];
|
||
var shiftCard = '';
|
||
|
||
// ── Определяем эффективный тип дня с учётом запроса ──────────────────────
|
||
var effWork = isWorkDay;
|
||
if (req==='pending-work') effWork = true; // показываем как рабочий (ожидание)
|
||
if (req==='pending-off') effWork = false; // показываем как выходной (ожидание)
|
||
|
||
// ── Баннер запроса (если есть) ────────────────────────────────────────────
|
||
var reqBanner = '';
|
||
if (req) {
|
||
var isPendWork = req==='pending-work';
|
||
reqBanner = '<div style="display:flex;align-items:center;justify-content:space-between;background:#FFFBEB;border:1.5px solid #FDE68A;border-radius:14px;padding:12px 14px;margin-bottom:12px">'
|
||
+'<div style="display:flex;align-items:center;gap:10px">'
|
||
+'<div style="font-size:20px">'+(isPendWork?'📋':'🌴')+'</div>'
|
||
+'<div>'
|
||
+'<div style="font-size:13px;font-weight:700;color:#92400E">'+(isPendWork?'Запрос смены — на согласовании':'Запрос выходного — на согласовании')+'</div>'
|
||
+'<div style="font-size:11px;color:#B45309;margin-top:1px">Ожидаем ответа Коммерческого директора</div>'
|
||
+'</div></div>'
|
||
+'<button onclick="_schedCancelReq('+mday+')" style="background:transparent;border:1px solid #FCD34D;border-radius:8px;padding:5px 10px;font-size:11px;font-weight:700;color:#92400E;cursor:pointer">Отменить</button>'
|
||
+'</div>';
|
||
}
|
||
|
||
// ── Карточка выходного / рабочего дня ─────────────────────────────────────
|
||
if (!effWork) {
|
||
shiftCard = '<div style="background:#FEF2F2;border-radius:16px;padding:16px;margin-bottom:14px">'
|
||
+'<div style="display:flex;align-items:center;justify-content:space-between">'
|
||
+'<div style="display:flex;align-items:center;gap:12px">'
|
||
+'<div style="font-size:28px">😴</div>'
|
||
+'<div><div style="font-size:15px;font-weight:700;color:#DC2626">Выходной</div>'
|
||
+'<div style="font-size:12px;color:#EF9999">'+dowShort[dow]+', '+mday+' мая</div></div>'
|
||
+'</div>'
|
||
// Кнопка «Запланировать смену» — только если нет активного запроса
|
||
+(!req?'<button onclick="_schedReqShift('+mday+')" class="btn-action warn" style="flex:none;padding:7px 12px;font-size:12px">+ Запланировать смену</button>':'')
|
||
+'</div></div>';
|
||
} else {
|
||
var inTime = ci ? (ci.in||'—') : '—';
|
||
var outTime = ci ? (ci.out||'—') : '—';
|
||
var workedH = (ci&&ci.out) ? (function(){ var ih=parseInt(ci.in),oh=parseInt(ci.out); return (oh-ih)+'ч'; })()
|
||
: ci ? 'идёт' : '';
|
||
var gst = window._GPS_STATE[wi]; // 'loading' | {ok,dist} | null
|
||
// GPS-чипы под статистикой (если есть данные)
|
||
var gpsChips = '';
|
||
function _gpsChip(g,icon){
|
||
if(!g) return '';
|
||
var col = g.ok?'var(--success)':(g.forced?'var(--warn)':'var(--danger)');
|
||
var bg = g.ok?'rgba(16,185,129,.1)':(g.forced?'rgba(245,158,11,.1)':'rgba(239,68,68,.08)');
|
||
var brd = g.ok?'rgba(16,185,129,.3)':(g.forced?'rgba(245,158,11,.3)':'rgba(239,68,68,.25)');
|
||
var lbl = g.ok?(g.dist+'м · в салоне ✓'):(g.forced?_mFmt(g.dist)+' · подтверждено вручную':_mFmt(g.dist)+' · вне салона ⚠️');
|
||
return '<div style="display:inline-flex;align-items:center;gap:4px;background:'+bg+';border:1px solid '+brd+';border-radius:20px;padding:4px 10px;font-size:11px;font-weight:700;color:'+col+'">'+icon+' '+lbl+'</div> ';
|
||
}
|
||
if(ci&&ci.gpsIn) gpsChips += _gpsChip(ci.gpsIn,'📍');
|
||
if(ci&&ci.gpsOut) gpsChips += _gpsChip(ci.gpsOut,'🏁');
|
||
if(gpsChips) gpsChips = '<div style="margin-top:10px;display:flex;flex-wrap:wrap;gap:6px">'+gpsChips+'</div>';
|
||
// Кнопки чекина
|
||
var checkinBtn = '';
|
||
if (wi>=0&&wi<=6&&!req) {
|
||
if(gst==='loading'){
|
||
checkinBtn = '<div style="margin-top:14px;background:var(--bg);border-radius:12px;padding:14px;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){
|
||
checkinBtn = '<div style="margin-top:14px;background:rgba(245,158,11,.1);border:1px solid rgba(245,158,11,.3);border-radius:12px;padding:13px">'
|
||
+'<div style="font-size:13px;font-weight:700;color:var(--warn);margin-bottom:8px">⚠️ Вы в '+_mFmt(gst.dist)+' от салона</div>'
|
||
+'<div style="font-size:12px;color:var(--muted);margin-bottom:10px">'+_SALON.addr+' · Открыть смену вдали от точки?</div>'
|
||
+'<div style="display:flex;gap:8px">'
|
||
+'<button onclick="_gpsForce('+wi+',\''+(ci?'checkout':'checkin')+'\')" class="btn-action warn" style="flex:1">Открыть всё равно</button>'
|
||
+'<button onclick="_gpsCancelCheck('+wi+')" class="btn-pill muted" style="padding:11px 14px;border-radius:12px;font-size:13px">Отмена</button>'
|
||
+'</div></div>';
|
||
} else if(!ci){
|
||
checkinBtn = '<button onclick="_gpsRequest('+wi+',\'checkin\')" style="width:100%;margin-top:14px;background:var(--accent);color:#fff;border:none;border-radius:12px;padding:13px;font-size:15px;font-weight:700;cursor:pointer">📍 Открыть смену</button>';
|
||
} else if(!ci.out){
|
||
checkinBtn = '<div style="display:flex;gap:8px;margin-top:12px">'
|
||
+'<button onclick="_gpsRequest('+wi+',\'checkout\')" class="btn-action ok">📍 Закрыть смену</button>'
|
||
+'<button onclick="_schedCancelCheckin('+wi+')" class="btn-pill muted" style="padding:11px 14px;border-radius:12px;font-size:13px">✕</button>'
|
||
+'</div>';
|
||
}
|
||
}
|
||
// Демо-переключатель GPS
|
||
var gpsDemoToggle = '<div style="margin-top:8px;text-align:right"><span onclick="_toggleGpsDemo()" style="font-size:10px;color:var(--muted);cursor:pointer;text-decoration:underline dotted">GPS демо: '+(window._GPS_DEMO==='near'?'📍 рядом':'🗺 далеко')+'</span></div>';
|
||
// Кнопка «Запросить выходной» — минимум за N часов (из _CONFIG), не день-в-день
|
||
var _TODAY_MDAY = 22; // в проде: new Date().getDate()
|
||
var _NOW_HOUR = 9; // в проде: new Date().getHours()
|
||
var _minH = window._CONFIG.dayOffMinHours;
|
||
var _hoursAhead = (mday - _TODAY_MDAY) * 24 - _NOW_HOUR;
|
||
var _canReqOff = !req && !ci && isWorkDay && _hoursAhead >= _minH;
|
||
var _tooSoon = !req && !ci && isWorkDay && _hoursAhead >= 0 && _hoursAhead < _minH;
|
||
var reqOffBtn = _canReqOff
|
||
? '<button onclick="_schedReqOff('+mday+')" style="width:100%;margin-top:10px;background:var(--bg);color:var(--muted);border:1px solid #E2E8F0;border-radius:10px;padding:9px;font-size:12px;font-weight:600;cursor:pointer">🌴 Запросить выходной</button>'
|
||
: _tooSoon
|
||
? '<div style="margin-top:10px;text-align:center;font-size:11px;color:var(--muted);padding:6px">🕐 Выходной можно запросить не позднее чем за '+_minH+' ч до смены</div>'
|
||
: '';
|
||
var statusBadge = req==='pending-off'
|
||
? '<div style="background:#FEF3C7;color:#92400E;border-radius:20px;padding:4px 10px;font-size:11px;font-weight:700">⏳ на согл.</div>'
|
||
: (ci?'<div style="background:'+(ci.out?'#DCFCE7':'#FEF3C7')+';color:'+(ci.out?'#15803D':'#92400E')+';border-radius:20px;padding:4px 10px;font-size:11px;font-weight:700">'+(ci.out?'✓ отработана':'в процессе')+'</div>':'');
|
||
shiftCard = '<div style="background:var(--card);border-radius:16px;padding:16px;margin-bottom:14px">'
|
||
+'<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:12px">'
|
||
+'<div>'
|
||
+'<div style="font-size:15px;font-weight:700;color:var(--ink)">'+dowShort[dow]+', '+mday+' мая</div>'
|
||
+'<div style="font-size:12px;color:var(--muted)">Плановая смена · 10:00 – 19:00 · 9 ч</div>'
|
||
+'</div>'
|
||
+statusBadge
|
||
+'</div>'
|
||
+'<div style="display:flex;gap:0;background:var(--bg);border-radius:12px;overflow:hidden">'
|
||
+'<div style="flex:1;padding:10px 14px;border-right:1px solid rgba(0,0,0,.07)">'
|
||
+'<div style="font-size:10px;color:var(--muted);font-weight:600;text-transform:uppercase;margin-bottom:2px">Начало</div>'
|
||
+'<div style="font-size:20px;font-weight:900;color:'+(ci?'var(--success)':'#CBD5E1')+'">'+inTime+'</div>'
|
||
+'</div>'
|
||
+'<div style="flex:1;padding:10px 14px;border-right:1px solid rgba(0,0,0,.07)">'
|
||
+'<div style="font-size:10px;color:var(--muted);font-weight:600;text-transform:uppercase;margin-bottom:2px">Конец</div>'
|
||
+'<div style="font-size:20px;font-weight:900;color:'+(ci&&ci.out?'var(--success)':'#CBD5E1')+'">'+outTime+'</div>'
|
||
+'</div>'
|
||
+'<div style="flex:1;padding:10px 14px">'
|
||
+'<div style="font-size:10px;color:var(--muted);font-weight:600;text-transform:uppercase;margin-bottom:2px">Отработано</div>'
|
||
+'<div style="font-size:20px;font-weight:900;color:'+(workedH?'var(--accent)':'#CBD5E1')+'">'+(workedH||'—')+'</div>'
|
||
+'</div></div>'
|
||
+gpsChips+checkinBtn+reqOffBtn+gpsDemoToggle
|
||
+'</div>';
|
||
}
|
||
|
||
// ── Таймлайн ──────────────────────────────────────────────────────────────
|
||
var appts = _mayAppts(mday).slice().sort(function(a,b){ return a.time.localeCompare(b.time); });
|
||
var timelineHtml = '';
|
||
if (effWork) {
|
||
var events = [{time:'10:00',type:'shift-start',label:'Начало смены',sub:''}]
|
||
.concat(appts.map(function(a){ return {time:a.time,type:'appt',label:a.client,sub:a.type+(a.order?' · '+a.order:''),color:a.color}; }))
|
||
.concat([{time:'19:00',type:'shift-end',label:'Конец смены',sub:''}]);
|
||
timelineHtml = '<div style="position:relative;padding-left:48px">'
|
||
+'<div style="position:absolute;left:20px;top:10px;bottom:10px;width:2px;background:linear-gradient(to bottom,var(--accent),#E2E8F0)"></div>';
|
||
events.forEach(function(ev){
|
||
var isAppt = ev.type==='appt';
|
||
var dotBg = isAppt ? (ev.color||'var(--accent)') : ev.type==='shift-start' ? 'var(--accent)' : '#CBD5E1';
|
||
var dotSz = isAppt ? '12px' : '10px';
|
||
timelineHtml += '<div style="display:flex;align-items:flex-start;margin-bottom:'+(isAppt?'16':'8')+'px;position:relative">'
|
||
+'<div style="position:absolute;left:-32px;top:'+(isAppt?'14':'10')+'px;width:'+dotSz+';height:'+dotSz+';border-radius:50%;background:'+dotBg+';border:2px solid var(--card);box-shadow:0 0 0 2px '+dotBg+'33"></div>'
|
||
+'<div style="font-size:11px;font-weight:700;color:var(--muted);min-width:36px;padding-top:'+(isAppt?'16':'10')+'px">'+ev.time+'</div>'
|
||
+(isAppt
|
||
? '<div style="flex:1;background:var(--card);border-radius:12px;padding:10px 14px;border-left:3px solid '+ev.color+';margin-left:8px">'
|
||
+'<div style="font-size:13px;font-weight:700;color:var(--ink)">'+ev.label+'</div>'
|
||
+'<div style="font-size:11px;color:var(--muted);margin-top:2px">'+ev.sub+'</div>'
|
||
+'</div>'
|
||
: '<div style="flex:1;padding:8px 0 0 12px;font-size:12px;font-weight:600;color:'+(ev.type==='shift-start'?'var(--accent)':'var(--muted)')+'">'+ev.label+'</div>'
|
||
)
|
||
+'</div>';
|
||
});
|
||
timelineHtml += '<button onclick="_toast(\'+ Встреча запланирована\',\'#15803D\')" style="margin-left:-40px;margin-top:4px;font-size:12px;font-weight:700;color:var(--accent);background:transparent;border:1.5px dashed var(--accent);border-radius:10px;padding:8px 16px;cursor:pointer;width:100%">+ Добавить встречу</button>';
|
||
timelineHtml += '</div>';
|
||
}
|
||
|
||
return reqBanner + shiftCard
|
||
+(effWork
|
||
? '<div style="font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.07em;color:var(--muted);margin-bottom:12px">Расписание дня</div>'+timelineHtml
|
||
: '');
|
||
}
|
||
|
||
function screenSchedule() {
|
||
var view = window._schedView || 'week';
|
||
var selMday = window._schedMday || 22;
|
||
|
||
// ── Toggle ─────────────────────────────────────────────────────────────────
|
||
var toggle = '<div style="display:flex;background:#F1F5F9;border-radius:10px;padding:3px;gap:2px">'
|
||
+'<button onclick="_schedToggleView(\'week\')" style="flex:1;padding:6px 0;font-size:12px;font-weight:700;border:none;border-radius:8px;cursor:pointer;transition:all .15s;'+(view==='week'?'background:var(--card);color:var(--accent);box-shadow:0 1px 4px rgba(0,0,0,.1)':'background:transparent;color:var(--muted)')+'">'
|
||
+'Неделя</button>'
|
||
+'<button onclick="_schedToggleView(\'month\')" style="flex:1;padding:6px 0;font-size:12px;font-weight:700;border:none;border-radius:8px;cursor:pointer;transition:all .15s;'+(view==='month'?'background:var(--card);color:var(--accent);box-shadow:0 1px 4px rgba(0,0,0,.1)':'background:transparent;color:var(--muted)')+'">'
|
||
+'Месяц</button>'
|
||
+'</div>';
|
||
|
||
var gridHtml = '';
|
||
|
||
if (view === 'week') {
|
||
// ── Неделя: 7 мини-карточек ─────────────────────────────────────────────
|
||
var weekCards = _DOW.map(function(dw,i){
|
||
var mday = _DATES[i]; // May 19–25
|
||
var ci = window._CHECKIN[i];
|
||
var isWork = _WORK[i];
|
||
var isSel = (mday===selMday);
|
||
var isToday = i===3;
|
||
var apptCount = (_APPTS[i]||[]).length;
|
||
var bg = isSel ? 'var(--accent)' : !isWork ? '#F1F5F9' : ci ? '#F0FDF4' : 'var(--card)';
|
||
var border = isSel ? 'none' : !isWork ? '1px solid #E2E8F0' : ci ? '1.5px solid #86EFAC' : isToday ? '1.5px solid var(--accent)' : '1px solid #E2E8F0';
|
||
var numCl = isSel ? '#fff' : !isWork ? '#CBD5E1' : isToday ? 'var(--accent)' : 'var(--ink)';
|
||
var dowCl = isSel ? 'rgba(255,255,255,.75)' : 'var(--muted)';
|
||
var statusLine = !isWork
|
||
? '<div style="font-size:9px;color:'+(isSel?'rgba(255,255,255,.5)':'#CBD5E1')+';font-weight:600">вых.</div>'
|
||
: ci
|
||
? '<div style="font-size:9px;color:'+(isSel?'rgba(255,255,255,.85)':'#16A34A')+';font-weight:700">✓ '+ci.in+'</div>'
|
||
: isToday ? '<div style="font-size:9px;color:var(--accent);font-weight:700">сегодня</div>'
|
||
: '<div style="font-size:9px;color:var(--muted)">10–19</div>';
|
||
return '<div onclick="_schedSelectMday('+mday+')" style="flex:1;display:flex;flex-direction:column;align-items:center;gap:3px;padding:8px 3px;background:'+bg+';border:'+border+';border-radius:12px;cursor:pointer;margin:0 2px;min-height:70px;justify-content:center">'
|
||
+'<div style="font-size:9px;font-weight:700;color:'+dowCl+'">'+dw+'</div>'
|
||
+'<div style="font-size:15px;font-weight:900;color:'+numCl+'">'+mday+'</div>'
|
||
+statusLine
|
||
+(apptCount&&isWork?'<div style="font-size:8px;color:'+(isSel?'rgba(255,255,255,.7)':'var(--muted)')+';margin-top:1px">'+apptCount+' встр.</div>':'')
|
||
+'</div>';
|
||
}).join('');
|
||
gridHtml = '<div style="display:flex;padding:0 10px 12px;gap:0">'+weekCards+'</div>';
|
||
|
||
} else {
|
||
// ── Месяц: полная сетка мая 2026 ────────────────────────────────────────
|
||
// May 1, 2026 = Thursday = dow-index 3 (Mon-based)
|
||
// Сетка стартует с Пн Apr 28 (заполнитель)
|
||
var dowHeader = ['Пн','Вт','Ср','Чт','Пт','Сб','Вс'].map(function(h){
|
||
return '<div style="flex:1;text-align:center;font-size:10px;font-weight:700;color:var(--muted);padding:4px 0">'+h+'</div>';
|
||
}).join('');
|
||
|
||
// Построим массив ячеек: null = заполнитель, 1–31 = день мая
|
||
var cells = [];
|
||
// May 1 = dow 3 (Thu), значит перед ним 3 заполнителя (Пн,Вт,Ср)
|
||
for(var fi=0;fi<3;fi++) cells.push(null);
|
||
for(var d=1;d<=31;d++) cells.push(d);
|
||
// после 31 мая = Вс (dow 6) — заполнители не нужны
|
||
// 31 + 3 = 34 = 4 строки по 7 = 28 + остаток 6... нет
|
||
// 3 + 31 = 34 cells → 34/7 = 4.857 → нужно 35 cells (5 строк)
|
||
while(cells.length%7!==0) cells.push(null);
|
||
|
||
// Считаем статистику
|
||
var totalDays=31, totalWorkShifts=0;
|
||
for(var dd=1;dd<=31;dd++){
|
||
var ddow=_mayDow(dd);
|
||
var dReq=window._SCHED_REQ[dd];
|
||
if(_WORK[ddow]||dReq==='pending-work') totalWorkShifts++;
|
||
}
|
||
|
||
var cellsHtml = cells.map(function(day){
|
||
if(day===null){
|
||
return '<div style="flex:0 0 calc(100%/7);padding:2px"></div>';
|
||
}
|
||
var dow = _mayDow(day);
|
||
var isWork = _WORK[dow];
|
||
var ci = _mayCi(day);
|
||
var appts = _mayAppts(day);
|
||
var tasks = _mayTasks(day).filter(function(t){ return !t.done; });
|
||
var isSel = day===selMday;
|
||
var isToday = day===22;
|
||
var req = window._SCHED_REQ[day]; // 'pending-work' | 'pending-off'
|
||
// Сб/Вс — всегда выходные по закону (dow 5=Сб, 6=Вс)
|
||
var isWeekend = (dow===5||dow===6);
|
||
var isShift = !isWeekend && (isWork || req==='pending-work');
|
||
|
||
var bg, border, numCl, extra='';
|
||
|
||
if(isSel){
|
||
// Выбранный день — акцентная заливка
|
||
bg='var(--accent)'; border='2px solid var(--accent)'; numCl='#fff';
|
||
} else if(isToday){
|
||
// Сегодня — только рамка, фон зависит от типа дня
|
||
border='2px solid var(--accent)';
|
||
if(isShift){ bg='rgba(16,185,129,.12)'; numCl='var(--ink)'; }
|
||
else { bg='rgba(239,68,68,.07)'; numCl='var(--ink)'; }
|
||
} else if(isShift){
|
||
// Рабочая смена (включая смену на выходной) — зелёный
|
||
bg='rgba(16,185,129,.12)'; border='1px solid rgba(16,185,129,.3)'; numCl='var(--ink)';
|
||
} else {
|
||
// Выходной — лёгкий красный
|
||
bg='rgba(239,68,68,.07)'; border='1px solid rgba(239,68,68,.15)'; numCl='rgba(180,60,60,.8)';
|
||
}
|
||
|
||
// Индикаторы встреч (зелёный) и задач (оранжевый)
|
||
var indicators = '';
|
||
if(isShift && (appts.length || tasks.length)){
|
||
var dotRow = [];
|
||
if(appts.length) dotRow.push(
|
||
'<div style="display:flex;align-items:center;gap:2px">'
|
||
+'<div style="width:5px;height:5px;border-radius:50%;background:'+(isSel?'rgba(255,255,255,.9)':'var(--success)')+';flex-shrink:0"></div>'
|
||
+'<span style="font-size:8px;font-weight:700;color:'+(isSel?'rgba(255,255,255,.85)':'var(--success)')+'">'+appts.length+'</span>'
|
||
+'</div>');
|
||
if(tasks.length) dotRow.push(
|
||
'<div style="display:flex;align-items:center;gap:2px">'
|
||
+'<div style="width:5px;height:5px;border-radius:50%;background:'+(isSel?'rgba(255,255,255,.9)':'var(--warn)')+';flex-shrink:0"></div>'
|
||
+'<span style="font-size:8px;font-weight:700;color:'+(isSel?'rgba(255,255,255,.85)':'var(--warn)')+'">'+tasks.length+'</span>'
|
||
+'</div>');
|
||
indicators = '<div style="display:flex;gap:4px;margin-top:3px;justify-content:center">'+dotRow.join('')+'</div>';
|
||
}
|
||
|
||
return '<div onclick="_schedSelectMday('+day+')" style="flex:0 0 calc(100%/7);padding:2px;box-sizing:border-box;cursor:pointer">'
|
||
+'<div style="background:'+bg+';border:'+border+';border-radius:9px;display:flex;flex-direction:column;align-items:center;justify-content:center;padding:7px 2px 6px;min-height:50px">'
|
||
+'<div style="font-size:13px;font-weight:800;color:'+numCl+'">'+day+'</div>'
|
||
+indicators
|
||
+'</div></div>';
|
||
}).join('');
|
||
|
||
// Шапка — Дней в месяце / Рабочих смен
|
||
var statsHtml =
|
||
'<div style="display:flex;gap:10px;padding:10px 0 10px">'
|
||
+'<div style="flex:1;background:var(--card);border-radius:12px;padding:12px 14px;display:flex;align-items:center;gap:12px">'
|
||
+'<div style="width:36px;height:36px;border-radius:10px;background:var(--bg);display:flex;align-items:center;justify-content:center;flex-shrink:0">'
|
||
+'<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="var(--muted)" 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>'
|
||
+'</div>'
|
||
+'<div><div style="font-size:20px;font-weight:800;color:var(--ink)">'+totalDays+'</div>'
|
||
+'<div style="font-size:10px;color:var(--muted);font-weight:600">дней в месяце</div></div>'
|
||
+'</div>'
|
||
+'<div style="flex:1;background:rgba(16,185,129,.08);border-radius:12px;padding:12px 14px;display:flex;align-items:center;gap:12px">'
|
||
+'<div style="width:36px;height:36px;border-radius:10px;background:rgba(16,185,129,.15);display:flex;align-items:center;justify-content:center;flex-shrink:0">'
|
||
+'<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="var(--success)" stroke-width="2.5"><polyline points="20 6 9 17 4 12"/></svg>'
|
||
+'</div>'
|
||
+'<div><div style="font-size:20px;font-weight:800;color:var(--success)">'+totalWorkShifts+'</div>'
|
||
+'<div style="font-size:10px;color:var(--success);font-weight:600;opacity:.8">рабочих смен</div></div>'
|
||
+'</div>'
|
||
+'</div>';
|
||
|
||
// Легенда
|
||
var legend =
|
||
'<div style="display:flex;gap:8px;padding:8px 0 10px;flex-wrap:wrap">'
|
||
+'<div style="display:flex;align-items:center;gap:4px"><div style="width:12px;height:12px;border-radius:3px;background:rgba(16,185,129,.15);border:1px solid rgba(16,185,129,.3);flex-shrink:0"></div><span style="font-size:10px;color:var(--muted);font-weight:600">Рабочий день</span></div>'
|
||
+'<div style="display:flex;align-items:center;gap:4px"><div style="width:12px;height:12px;border-radius:3px;background:rgba(239,68,68,.1);border:1px solid rgba(239,68,68,.2);flex-shrink:0"></div><span style="font-size:10px;color:var(--muted);font-weight:600">Выходной</span></div>'
|
||
+'<div style="display:flex;align-items:center;gap:4px"><div style="width:12px;height:12px;border-radius:3px;border:2px solid var(--accent);background:rgba(16,185,129,.12);flex-shrink:0"></div><span style="font-size:10px;color:var(--muted);font-weight:600">Сегодня</span></div>'
|
||
+'</div>';
|
||
|
||
gridHtml = '<div style="padding:0 12px 8px">'
|
||
+statsHtml
|
||
+'<div style="display:flex;margin-top:2px">'+dowHeader+'</div>'
|
||
+'<div style="display:flex;flex-wrap:wrap">'+cellsHtml+'</div>'
|
||
+legend
|
||
+'</div>';
|
||
}
|
||
|
||
return '<div class="page">'
|
||
// Sticky header
|
||
+'<div style="background:var(--card);position:sticky;top:0;z-index:50;border-bottom:1px solid rgba(0,0,0,.08)">'
|
||
+'<div style="display:flex;align-items:center;justify-content:space-between;padding:12px 16px 10px">'
|
||
+'<div style="font-size:17px;font-weight:800;color:var(--ink)">📋 График работы</div>'
|
||
+'<div style="font-size:13px;font-weight:600;color:var(--muted)">май 2026</div>'
|
||
+'</div>'
|
||
+'<div style="padding:0 12px 10px">'+toggle+'</div>'
|
||
+gridHtml
|
||
+'</div>'
|
||
// Детали выбранного дня
|
||
+'<div style="padding:14px 16px 80px">'
|
||
+_renderShiftDetail(selMday)
|
||
+'</div></div>';
|
||
}
|
||
|
||
// ── CALCULATIONS ──────────────────────────────────────────────────────────────
|
||
function screenSalary() {
|
||
var orders = window._managerOrders.filter(function(o){ return !o.isLead; });
|
||
var month = 'Май 2026';
|
||
var base = 45000;
|
||
|
||
// Комиссия 3% со всех поступивших авансов
|
||
var commRows = [];
|
||
var totalComm = 0;
|
||
orders.forEach(function(o){
|
||
var adv = o.advances||[];
|
||
var paid = adv.filter(function(a){ return a.paid && a.amount>0; });
|
||
if (paid.length || o.assemblyPaid) {
|
||
var sum = paid.reduce(function(s,a){return s+a.amount;},0)
|
||
+ (o.assemblyPaid ? (o.assembly||0) : 0);
|
||
var comm = Math.round(sum * 0.03);
|
||
totalComm += comm;
|
||
commRows.push({client:o.client, contract:o.contract, sum:sum, comm:comm});
|
||
}
|
||
});
|
||
|
||
var total = base + totalComm;
|
||
var paid = Math.round(total * 0.5); // половина выплачена (аванс зарплаты)
|
||
var left = total - paid;
|
||
var pct = Math.round(paid/total*100);
|
||
|
||
function fm(n){ return n.toLocaleString('ru')+' ₽'; }
|
||
|
||
var rows = commRows.map(function(r){
|
||
return '<div style="display:flex;align-items:center;gap:10px;padding:10px 0;border-bottom:1px solid rgba(0,0,0,.06)">'
|
||
+'<div style="width:36px;height:36px;border-radius:10px;background:#EEF2FF;display:flex;align-items:center;justify-content:center;font-size:16px;flex-shrink:0">💼</div>'
|
||
+'<div style="flex:1">'
|
||
+'<div style="font-size:13px;font-weight:700;color:var(--ink)">'+r.client+'</div>'
|
||
+'<div style="font-size:11px;color:var(--muted)">'+r.contract+' · поступило '+r.sum.toLocaleString('ru')+' ₽</div>'
|
||
+'</div>'
|
||
+'<div style="font-size:14px;font-weight:800;color:var(--accent)">+'+r.comm.toLocaleString('ru')+' ₽</div>'
|
||
+'</div>';
|
||
}).join('');
|
||
|
||
return '<div class="page">'
|
||
+'<div class="page-header"><h2>💵 Зарплата</h2>'
|
||
+'<span style="font-size:12px;color:var(--muted);font-weight:500">'+month+'</span></div>'
|
||
+'<div style="padding:16px">'
|
||
|
||
// Итого карточка
|
||
+'<div style="background:linear-gradient(135deg,var(--accent) 0%,#1a5db0 100%);border-radius:18px;padding:20px;color:#fff;margin-bottom:16px">'
|
||
+'<div style="font-size:12px;font-weight:600;opacity:.75;margin-bottom:4px">Итого за месяц</div>'
|
||
+'<div style="font-size:36px;font-weight:900;letter-spacing:-.02em;margin-bottom:14px">'+total.toLocaleString('ru')+' ₽</div>'
|
||
+'<div style="display:flex;gap:16px">'
|
||
+'<div><div style="font-size:11px;opacity:.7">Оклад</div><div style="font-size:16px;font-weight:800">'+fm(base)+'</div></div>'
|
||
+'<div style="width:1px;background:rgba(255,255,255,.2)"></div>'
|
||
+'<div><div style="font-size:11px;opacity:.7">Комиссия 3%</div><div style="font-size:16px;font-weight:800">'+fm(totalComm)+'</div></div>'
|
||
+'</div></div>'
|
||
|
||
// Прогресс выплат
|
||
+'<div class="card" style="padding:16px;margin-bottom:16px">'
|
||
+'<div class="row sb" style="margin-bottom:8px">'
|
||
+'<span style="font-size:13px;font-weight:700;color:var(--ink)">Статус выплат</span>'
|
||
+'<span style="font-size:12px;color:var(--muted)">'+pct+'% выплачено</span>'
|
||
+'</div>'
|
||
+'<div style="height:8px;background:#E2E8F0;border-radius:4px;margin-bottom:10px">'
|
||
+'<div style="height:8px;border-radius:4px;background:var(--success);width:'+pct+'%"></div></div>'
|
||
+'<div style="display:flex;justify-content:space-between">'
|
||
+'<div><div style="font-size:11px;color:var(--muted)">Выплачено</div><div style="font-size:15px;font-weight:800;color:var(--success)">'+fm(paid)+'</div></div>'
|
||
+'<div style="text-align:right"><div style="font-size:11px;color:var(--muted)">К выплате '+String(new Date().getDate()+3).padStart(2,'0')+'.06</div><div style="font-size:15px;font-weight:800;color:var(--accent)">'+fm(left)+'</div></div>'
|
||
+'</div></div>'
|
||
|
||
// Детализация
|
||
+'<div class="section-label" style="margin:0 0 8px">Комиссия по заказам</div>'
|
||
+'<div class="card" style="padding:0 16px">'
|
||
+(rows||'<div style="padding:14px 0;text-align:center;color:var(--muted);font-size:13px">Нет завершённых поступлений</div>')
|
||
+'<div style="display:flex;justify-content:space-between;align-items:center;padding:10px 0">'
|
||
+'<span style="font-size:13px;font-weight:700;color:var(--ink)">Итого комиссия</span>'
|
||
+'<span style="font-size:15px;font-weight:900;color:var(--accent)">'+fm(totalComm)+'</span>'
|
||
+'</div></div>'
|
||
|
||
+'</div></div>';
|
||
}
|
||
function screenCalc() {
|
||
var o = window._managerOrders[window._activeOrder]||window._managerOrders[0];
|
||
var contract = o.amount;
|
||
var advances = o.advances || [{label:'Аванс',amount:o.advance,paid:true,date:null}];
|
||
var paidSum = advances.reduce(function(s,a){return s+(a.paid?a.amount:0);},0);
|
||
var remaining = contract - paidSum;
|
||
var assembly = o.assembly||0;
|
||
var asmPaid = o.assemblyPaid||false;
|
||
var contractDone = remaining <= 0;
|
||
function fmt(n){ return n.toLocaleString('ru')+' ₽'; }
|
||
|
||
// Order selector chips
|
||
var chips = window._managerOrders.map(function(ord,i){
|
||
var sel = i === window._activeOrder;
|
||
return '<div class="pay-chip '+(sel?'act':'inact')+'" onclick="window._activeOrder='+i+';document.getElementById(\'screen\').innerHTML=renderScreen(\'manager_calc\')">'
|
||
+ord.client.split(' ')[0]+'</div>';
|
||
}).join('');
|
||
|
||
var pct = contract > 0 ? Math.min(100, Math.round(paidSum/contract*100)) : 0;
|
||
|
||
var oi = window._activeOrder; // order index for callbacks
|
||
|
||
var today = (function(){ var d=new Date(); return String(d.getDate()).padStart(2,'0')+'.'+String(d.getMonth()+1).padStart(2,'0')+'.'+d.getFullYear(); })();
|
||
|
||
// Авансы rows — инлайн-поля
|
||
var advRows = advances.map(function(a, ai){
|
||
var chk = a.paid
|
||
? '<div style="width:22px;height:22px;border-radius:6px;background:#DCFCE7;display:flex;align-items:center;justify-content:center;flex-shrink:0;cursor:pointer" onclick="_toggleAdvPaid('+oi+','+ai+',false)"><svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="#15803D" stroke-width="3"><polyline points="20 6 9 17 4 12"/></svg></div>'
|
||
: '<div style="width:22px;height:22px;border-radius:6px;border:2px solid #CBD5E1;display:flex;align-items:center;justify-content:center;flex-shrink:0;cursor:pointer" onclick="_toggleAdvPaid('+oi+','+ai+',true)"></div>';
|
||
return '<div style="padding:12px 0;border-bottom:1px solid rgba(0,0,0,.06)">'
|
||
+'<div style="display:flex;align-items:center;gap:10px;margin-bottom:8px">'
|
||
+chk
|
||
+'<div style="font-size:13px;font-weight:700;color:var(--ink);flex:1">'+a.label+'</div>'
|
||
+(a.paid?'<span style="font-size:11px;font-weight:700;color:var(--success)">✓ получен</span>':'<span style="font-size:11px;color:var(--muted)">ожидается</span>')
|
||
+'</div>'
|
||
+'<div style="display:flex;gap:8px">'
|
||
// поле суммы
|
||
+'<div style="flex:1;position:relative">'
|
||
+'<input type="number" placeholder="Сумма" value="'+(a.amount||'')+'"'
|
||
+' onchange="_liveAdvAmt('+oi+','+ai+',this.value)"'
|
||
+' style="width:100%;border:2px solid '+(a.amount?'var(--accent)':'#E2E8F0')+';border-radius:10px;padding:10px 34px 10px 12px;font-size:16px;font-weight:800;color:var(--accent);outline:none;background:var(--bg)">'
|
||
+'<span style="position:absolute;right:10px;top:50%;transform:translateY(-50%);font-size:13px;font-weight:700;color:var(--accent);pointer-events:none">₽</span>'
|
||
+'</div>'
|
||
// поле даты (показываем только если оплачено)
|
||
+(a.paid
|
||
? '<div style="flex:1"><input type="text" placeholder="ДД.ММ.ГГГГ" value="'+(a.date||today)+'"'
|
||
+' onchange="_liveAdvDate('+oi+','+ai+',this.value)"'
|
||
+' style="width:100%;border:2px solid #E2E8F0;border-radius:10px;padding:10px 12px;font-size:13px;font-weight:600;color:var(--ink);outline:none;background:var(--bg)"></div>'
|
||
: '')
|
||
+'</div>'
|
||
+'</div>';
|
||
}).join('');
|
||
|
||
// Сборка — инлайн-поле
|
||
var asmChk = asmPaid
|
||
? '<div style="width:22px;height:22px;border-radius:6px;background:#DCFCE7;display:flex;align-items:center;justify-content:center;flex-shrink:0;cursor:pointer" onclick="_toggleAsm('+oi+',false)"><svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="#15803D" stroke-width="3"><polyline points="20 6 9 17 4 12"/></svg></div>'
|
||
: '<div style="width:22px;height:22px;border-radius:6px;border:2px solid #CBD5E1;display:flex;align-items:center;justify-content:center;flex-shrink:0;cursor:pointer" onclick="_toggleAsm('+oi+',true)"></div>';
|
||
var asmRow = '<div style="padding:12px 0">'
|
||
+'<div style="display:flex;align-items:center;gap:10px;margin-bottom:8px">'
|
||
+asmChk
|
||
+'<div style="font-size:13px;font-weight:700;color:var(--ink);flex:1">Стоимость сборки</div>'
|
||
+(asmPaid?'<span style="font-size:11px;font-weight:700;color:var(--success)">✓ оплачена</span>':'<span style="font-size:11px;color:var(--muted)">при сдаче объекта</span>')
|
||
+'</div>'
|
||
+'<div style="position:relative">'
|
||
+'<input type="number" placeholder="Сумма сборки" value="'+(assembly||'')+'"'
|
||
+' onchange="_liveAsm('+oi+',this.value)"'
|
||
+' style="width:100%;border:2px solid '+(assembly?'#F59E0B':'#E2E8F0')+';border-radius:10px;padding:10px 34px 10px 12px;font-size:16px;font-weight:800;color:#92400E;outline:none;background:var(--bg)">'
|
||
+'<span style="position:absolute;right:10px;top:50%;transform:translateY(-50%);font-size:13px;font-weight:700;color:#92400E;pointer-events:none">₽</span>'
|
||
+'</div></div>';
|
||
|
||
return '<div class="page">'
|
||
+'<div class="page-header"><h2>💰 Расчёты</h2></div>'
|
||
+'<div style="display:flex;gap:8px;padding:10px 16px 4px;flex-wrap:wrap">'+chips+'</div>'
|
||
+'<div style="padding:0 16px">'
|
||
|
||
// ① Договор
|
||
+'<div class="section-label" style="margin:8px 0 6px">Договор</div>'
|
||
+'<div class="card" style="padding:16px;margin-bottom:12px">'
|
||
+'<div class="row sb" style="margin-bottom:10px">'
|
||
+'<div style="font-size:13px;font-weight:700;color:var(--ink)">Сумма по договору</div>'
|
||
+'<span class="badge gray">'+o.contract+'</span>'
|
||
+'</div>'
|
||
+'<div style="position:relative">'
|
||
+'<input id="f_contract_'+oi+'" type="number" placeholder="Введите сумму" value="'+(contract||'')+'"'
|
||
+' onchange="_liveContract('+oi+',this.value)"'
|
||
+' style="width:100%;border:2px solid '+(contract?'var(--accent)':'#E2E8F0')+';border-radius:12px;padding:13px 52px 13px 16px;font-size:22px;font-weight:900;color:var(--accent);outline:none;background:var(--bg)">'
|
||
+'<span style="position:absolute;right:14px;top:50%;transform:translateY(-50%);font-size:16px;font-weight:700;color:var(--accent);pointer-events:none">₽</span>'
|
||
+'</div>'
|
||
+'<div style="margin-top:10px">'
|
||
+'<div class="row sb" style="margin-bottom:5px"><span style="font-size:11px;font-weight:600;color:var(--muted)">Оплачено по договору</span>'
|
||
+'<span style="font-size:12px;font-weight:800;color:var(--ink)">'+pct+'% '+fmt(paidSum)+'</span></div>'
|
||
+'<div style="height:6px;background:#E2E8F0;border-radius:3px">'
|
||
+'<div style="height:6px;background:'+(contractDone?'var(--success)':'var(--accent)')+';border-radius:3px;width:'+pct+'%"></div>'
|
||
+'</div>'
|
||
+(remaining>0?'<div style="font-size:11px;color:var(--danger);font-weight:700;margin-top:5px">Остаток по договору: '+fmt(remaining)+'</div>':'<div style="font-size:11px;color:var(--success);font-weight:700;margin-top:5px">✅ Договор закрыт</div>')
|
||
+'</div></div>'
|
||
|
||
// ② Авансы
|
||
+'<div class="section-label" style="margin:4px 0 6px">Авансы по договору</div>'
|
||
+'<div class="card" style="padding:0 16px">'+advRows+'</div>'
|
||
|
||
// ③ Сборка
|
||
+'<div class="section-label" style="margin:14px 0 6px">Сборка</div>'
|
||
+'<div class="card" style="padding:0 16px">'+asmRow+'</div>'
|
||
|
||
// Actions
|
||
+'<div style="margin-top:16px;display:flex;flex-direction:column;gap:8px">'
|
||
+'<button class="btn-primary" onclick="_showCalcClient()">📱 Показать клиенту</button>'
|
||
+'<button class="btn-secondary" onclick="_toast(\'📤 Ссылка скопирована в буфер\',\'var(--accent)\')">📤 Отправить ссылку / PDF</button>'
|
||
+'</div></div></div>';
|
||
}
|
||
function _showCalcClient() {
|
||
var o = window._managerOrders[window._activeOrder]||window._managerOrders[0];
|
||
var contract = o.amount;
|
||
var advances = o.advances||[{label:'Аванс',amount:o.advance,paid:true,date:null}];
|
||
var paidSum = advances.reduce(function(s,a){return s+(a.paid?a.amount:0);},0);
|
||
var remaining= contract - paidSum;
|
||
var assembly = o.assembly||0;
|
||
var asmPaid = o.assemblyPaid||false;
|
||
function fmt(n){ return n.toLocaleString('ru')+' ₽'; }
|
||
|
||
var advRows = advances.map(function(a){
|
||
return '<div style="display:flex;justify-content:space-between;align-items:center;padding:9px 0;border-bottom:1px solid rgba(0,0,0,.07)">'
|
||
+'<div>'
|
||
+'<div style="font-size:13px;font-weight:600;color:var(--ink)">'+a.label+'</div>'
|
||
+(a.paid&&a.date?'<div style="font-size:11px;color:var(--success)">Получен '+a.date+'</div>'
|
||
:'<div style="font-size:11px;color:var(--muted)">ожидается</div>')
|
||
+'</div>'
|
||
+'<div style="font-size:15px;font-weight:800;color:'+(a.paid?'var(--success)':'var(--muted)')+'">'+fmt(a.amount)+'</div>'
|
||
+'</div>';
|
||
}).join('');
|
||
|
||
var html = '<div style="position:absolute;inset:0;background:rgba(0,0,0,.55);z-index:500;display:flex;align-items:flex-end" onclick="this.remove()" id="calcModal">'
|
||
+'<div style="background:var(--card);border-radius:24px 24px 0 0;width:100%;padding:20px 20px 32px;max-height:88%" onclick="event.stopPropagation()">'
|
||
+'<div style="width:36px;height:4px;background:#E2E8F0;border-radius:2px;margin:0 auto 18px"></div>'
|
||
|
||
// ① Договор
|
||
+'<div style="text-align:center;padding-bottom:16px;border-bottom:1px solid rgba(0,0,0,.08)">'
|
||
+'<div style="font-size:11px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.06em;margin-bottom:6px">Сумма по договору</div>'
|
||
+'<div style="font-size:38px;font-weight:900;color:var(--accent);letter-spacing:-.03em;line-height:1">'+fmt(contract)+'</div>'
|
||
+'<div style="font-size:12px;color:var(--muted);margin-top:4px">'+o.contract+'</div>'
|
||
+'</div>'
|
||
|
||
// ② Авансы
|
||
+'<div style="padding:4px 0 0">'
|
||
+'<div style="font-size:11px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin:12px 0 4px">Авансы</div>'
|
||
+advRows
|
||
+'</div>'
|
||
|
||
// ③ Сборка
|
||
+'<div style="display:flex;justify-content:space-between;align-items:center;padding:12px 0;border-top:1px solid rgba(0,0,0,.08);margin-top:4px">'
|
||
+'<div>'
|
||
+'<div style="font-size:13px;font-weight:700;color:var(--ink)">Стоимость сборки</div>'
|
||
+'<div style="font-size:11px;color:var(--muted)">'+(asmPaid?'Оплачена':'при сдаче объекта')+'</div>'
|
||
+'</div>'
|
||
+'<div style="font-size:15px;font-weight:800;color:'+(asmPaid?'var(--success)':'#92400E')+'">'+fmt(assembly)+'</div>'
|
||
+'</div>'
|
||
|
||
+'<button class="btn-primary" style="margin-top:4px" onclick="document.getElementById(\'calcModal\').remove()">Закрыть</button>'
|
||
+'</div></div>';
|
||
document.getElementById('phoneFrame').insertAdjacentHTML('beforeend', html);
|
||
}
|
||
|
||
function renderScreen(id) {
|
||
window._currentScreen = id;
|
||
if (id==='manager_home') return screenHome();
|
||
if (id==='manager_order') return screenOrder();
|
||
if (id==='manager_tech') return screenTech();
|
||
if (id==='manager_wizard') return screenWizard();
|
||
if (id==='manager_result') return screenResult();
|
||
if (id==='manager_lead') return screenLead();
|
||
if (id==='manager_schedule') return screenSchedule();
|
||
if (id==='manager_calc') return screenCalc();
|
||
if (id==='manager_salary') return screenSalary();
|
||
if (id==='manager_client') return screenClient();
|
||
if (id==='manager_own_tech') return screenOwnTech();
|
||
if (id==='manager_tech_client') return screenTechClient();
|
||
if (id==='manager_kb') return screenKB();
|
||
return '<div style="padding:32px;color:var(--muted)">Экран: ' + id + '</div>';
|
||
}
|
||
|
||
// ── HOME ──────────────────────────────────────────────────────────────────────
|
||
function screenHome() {
|
||
var all = window._managerOrders;
|
||
var stN = ['','Замер','Проект','Техника','Технолог','Производство','Сборка','Закрыт'];
|
||
var stIc = ['','📐','📋','⚡','🔧','🏭','🔨','✅'];
|
||
|
||
// Data
|
||
var todayAppts = _APPTS[3] || [];
|
||
var todayTasks = (_TASKS[3]||[]).filter(function(t){return !t.done;});
|
||
var orders = all.filter(function(o){return !o.isLead;});
|
||
var leads = all.filter(function(o){return o.isLead;});
|
||
var blockers = orders.filter(function(o){return !!o.blocker;});
|
||
|
||
// "Нужно сейчас": блокеры + лиды требующие немедленного действия
|
||
var urgentLeads = leads.filter(function(o){
|
||
return o.leadStage==='new' || o.leadStage==='meeting'
|
||
|| (o.tasks||[]).some(function(t){return !t.done && t.hi;});
|
||
});
|
||
var urgentAll = blockers.concat(urgentLeads.filter(function(o){
|
||
return blockers.indexOf(o)===-1;
|
||
}));
|
||
|
||
// ── HERO ──────────────────────────────────────────────────────────────
|
||
var _SLOGANS = [
|
||
'Один хороший замер стоит десяти звонков. 📐',
|
||
'Клиент, которому объяснили — клиент, который купил. 🤝',
|
||
'Каждый договор сегодня — рекомендация завтра. 📋',
|
||
'Лучший момент закрыть сделку — сейчас. ⚡',
|
||
'Детали решают. Замерь точно — смонтируют без проблем. 🔧',
|
||
'Довольный клиент сам приведёт следующего. 🌟',
|
||
'Хороший день начинается с первого звонка. 📞',
|
||
'Скорость ответа — половина продажи. ⏱',
|
||
'Уточни бюджет сразу — сэкономишь три встречи. 💡',
|
||
'Фото замера в карточку — и никаких «а вы точно мерили?». 📸',
|
||
];
|
||
var _todaySlogan = _SLOGANS[new Date().getDate() % _SLOGANS.length];
|
||
var nextAppt = todayAppts[0];
|
||
var heroHtml =
|
||
'<div style="background:var(--card);padding:16px 16px 14px;border-bottom:1px solid rgba(0,0,0,.06)">'
|
||
+'<div style="font-size:12px;color:var(--muted);margin-bottom:2px">Чт, 22 мая 2026</div>'
|
||
+'<div style="font-size:20px;font-weight:800;color:var(--ink);margin-bottom:5px">Добрый день, Анна 👋</div>'
|
||
+'<div style="font-size:12px;color:var(--muted);font-style:italic;margin-bottom:13px;line-height:1.4">'+_todaySlogan+'</div>'
|
||
// Три плитки-счётчика
|
||
+'<div style="display:flex;gap:8px;margin-bottom:'+(nextAppt?'12px':'0')+'">'
|
||
+'<div onclick="_nav(\'manager_schedule\')" style="flex:1;background:#EEF2FF;border-radius:13px;padding:10px 12px;cursor:pointer;text-align:center">'
|
||
+'<div style="font-size:22px;font-weight:800;color:#4338CA;line-height:1">'+todayAppts.length+'</div>'
|
||
+'<div style="font-size:10px;color:#4338CA;font-weight:600;margin-top:2px">встреч</div>'
|
||
+'</div>'
|
||
+'<div style="flex:1;background:#FFFBEB;border-radius:13px;padding:10px 12px;cursor:pointer;text-align:center">'
|
||
+'<div style="font-size:22px;font-weight:800;color:#D97706;line-height:1">'+todayTasks.length+'</div>'
|
||
+'<div style="font-size:10px;color:#D97706;font-weight:600;margin-top:2px">задач</div>'
|
||
+'</div>'
|
||
+'<div style="flex:1;background:'+(blockers.length?'#FEE2E2':'#F0FDF4')+';border-radius:13px;padding:10px 12px;text-align:center">'
|
||
+'<div style="font-size:22px;font-weight:800;color:'+(blockers.length?'#DC2626':'#16A34A')+';line-height:1">'+blockers.length+'</div>'
|
||
+'<div style="font-size:10px;color:'+(blockers.length?'#DC2626':'#16A34A')+';font-weight:600;margin-top:2px">блокеров</div>'
|
||
+'</div>'
|
||
+'</div>'
|
||
// Ближайшая встреча — баннер
|
||
+(nextAppt
|
||
? '<div onclick="_nav(\'manager_schedule\')" style="background:var(--accent);border-radius:13px;padding:11px 14px;display:flex;align-items:center;gap:12px;cursor:pointer">'
|
||
+'<div style="width:36px;height:36px;background:rgba(255,255,255,.15);border-radius:10px;display:flex;align-items:center;justify-content:center;font-size:18px;flex-shrink:0">📅</div>'
|
||
+'<div style="flex:1">'
|
||
+'<div style="font-size:10px;color:rgba(255,255,255,.7);font-weight:600;text-transform:uppercase;letter-spacing:.04em">Ближайшая встреча</div>'
|
||
+'<div style="font-size:14px;font-weight:700;color:#fff">'+nextAppt.time+' · '+nextAppt.client+'</div>'
|
||
+'<div style="font-size:12px;color:rgba(255,255,255,.75)">'+nextAppt.type+'</div>'
|
||
+'</div>'
|
||
+'<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="rgba(255,255,255,.6)" stroke-width="2.5"><polyline points="9 18 15 12 9 6"/></svg>'
|
||
+'</div>'
|
||
: '')
|
||
+'</div>';
|
||
|
||
// ── СРОЧНО ────────────────────────────────────────────────────────────
|
||
var urgentHtml = '';
|
||
if (urgentAll.length) {
|
||
urgentHtml += '<div style="display:flex;align-items:center;justify-content:space-between;padding:16px 16px 8px">'
|
||
+'<div style="font-size:11px;font-weight:700;color:var(--danger);text-transform:uppercase;letter-spacing:.06em">🔴 Нужно сейчас</div>'
|
||
+'<div style="font-size:11px;color:var(--muted)">'+urgentAll.length+' '+(urgentAll.length===1?'действие':'действия')+'</div>'
|
||
+'</div>';
|
||
urgentAll.forEach(function(o) {
|
||
var idx = all.indexOf(o);
|
||
if (!o.isLead) {
|
||
// Блокер в заказе
|
||
var bi = _blockerInfo(o);
|
||
urgentHtml +=
|
||
'<div style="margin:0 16px 10px;background:var(--card);border-radius:16px;overflow:hidden;box-shadow:0 3px 14px rgba(0,0,0,.09)">'
|
||
+'<div style="height:3px;background:'+bi.bar+'"></div>'
|
||
+'<div style="padding:13px 14px 10px">'
|
||
+'<div style="display:flex;align-items:center;gap:8px;margin-bottom:6px">'
|
||
+'<span style="font-size:15px;font-weight:700;color:var(--ink)">'+o.client+'</span>'
|
||
+'<span style="font-size:10px;background:#F1F5F9;color:#64748B;padding:2px 8px;border-radius:20px;font-weight:700">'+stN[o.stage||1]+'</span>'
|
||
+'</div>'
|
||
+'<div style="display:inline-flex;align-items:center;gap:5px;background:'+bi.bg+';border-radius:8px;padding:5px 10px">'
|
||
+'<span style="font-size:14px">'+bi.icon+'</span>'
|
||
+'<span style="font-size:12px;font-weight:700;color:'+bi.color+'">'+bi.label+'</span>'
|
||
+'</div>'
|
||
+(o.techNote?'<div style="font-size:12px;color:var(--muted);margin-top:5px">'+o.techNote+'</div>':'')
|
||
+'</div>'
|
||
+'<div style="padding:0 14px 13px;display:flex;gap:8px">'
|
||
+'<a href="tel:'+o.phone+'" onclick="event.stopPropagation()" style="flex:0 0 44px;height:40px;background:#EEF2FF;border-radius:10px;display:flex;align-items:center;justify-content:center;text-decoration:none">'
|
||
+'<svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="#4338CA" stroke-width="2.5"><path d="M22 16.92v3a2 2 0 01-2.18 2 19.79 19.79 0 01-8.63-3.07A19.5 19.5 0 013.07 9.81a19.79 19.79 0 01-3.07-8.67A2 2 0 012 1h3a2 2 0 012 1.72c.127.96.361 1.903.7 2.81a2 2 0 01-.45 2.11L6.09 8.91a16 16 0 006 6l1.27-1.27a2 2 0 012.11-.45c.907.339 1.85.573 2.81.7A2 2 0 0122 16.92z"/></svg>'
|
||
+'</a>'
|
||
+'<button onclick="_openOrder('+idx+')" style="flex:1;height:40px;background:'+bi.bar+';border:none;border-radius:10px;font-size:13px;font-weight:700;color:#fff;cursor:pointer">'+bi.cta+' →</button>'
|
||
+'</div>'
|
||
+'</div>';
|
||
} else {
|
||
// Лид требует действия
|
||
var isMeeting = o.leadStage === 'meeting';
|
||
var hiTask2 = (o.tasks||[]).find(function(t){return !t.done;});
|
||
var ctaLabel = isMeeting ? 'Открыть встречу →' : 'Позвонить сейчас →';
|
||
var accentBg = isMeeting ? 'var(--accent)' : '#7C3AED';
|
||
var topBg = isMeeting ? 'var(--accent)' : '#7C3AED';
|
||
var todayBadge = isMeeting
|
||
? '<span style="font-size:10px;font-weight:700;background:#DCFCE7;color:#15803D;padding:2px 7px;border-radius:20px">Сегодня</span>'
|
||
: '<span style="font-size:10px;font-weight:700;background:#FEE2E2;color:#DC2626;padding:2px 7px;border-radius:20px">Горячий</span>';
|
||
urgentHtml +=
|
||
'<div style="margin:0 16px 10px;background:var(--card);border-radius:16px;overflow:hidden;box-shadow:0 3px 14px rgba(0,0,0,.09)">'
|
||
+'<div style="height:3px;background:'+topBg+'"></div>'
|
||
+'<div style="padding:13px 14px 10px">'
|
||
+'<div style="display:flex;align-items:center;gap:7px;margin-bottom:5px">'
|
||
+'<span style="font-size:15px;font-weight:700;color:var(--ink)">'+o.client+'</span>'
|
||
+'<span style="font-size:10px;font-weight:700;background:#EDE9FE;color:#7C3AED;padding:2px 7px;border-radius:20px">Лид</span>'
|
||
+todayBadge
|
||
+'</div>'
|
||
+(hiTask2?'<div style="font-size:13px;font-weight:500;color:var(--ink);margin-bottom:2px">'+hiTask2.text+'</div>':'')
|
||
+(o.leadNote?'<div style="font-size:12px;color:var(--muted)">'+o.leadNote+'</div>':'')
|
||
+'</div>'
|
||
+'<div style="padding:0 14px 13px;display:flex;gap:8px">'
|
||
+'<a href="tel:'+o.phone+'" onclick="event.stopPropagation()" style="flex:0 0 44px;height:40px;background:#EEF2FF;border-radius:10px;display:flex;align-items:center;justify-content:center;text-decoration:none">'
|
||
+'<svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="#4338CA" stroke-width="2.5"><path d="M22 16.92v3a2 2 0 01-2.18 2 19.79 19.79 0 01-8.63-3.07A19.5 19.5 0 013.07 9.81a19.79 19.79 0 01-3.07-8.67A2 2 0 012 1h3a2 2 0 012 1.72c.127.96.361 1.903.7 2.81a2 2 0 01-.45 2.11L6.09 8.91a16 16 0 006 6l1.27-1.27a2 2 0 012.11-.45c.907.339 1.85.573 2.81.7A2 2 0 0122 16.92z"/></svg>'
|
||
+'</a>'
|
||
+'<button onclick="_openOrder('+idx+')" style="flex:1;height:40px;background:'+accentBg+';border:none;border-radius:10px;font-size:13px;font-weight:700;color:#fff;cursor:pointer">'+ctaLabel+'</button>'
|
||
+'</div>'
|
||
+'</div>';
|
||
}
|
||
});
|
||
}
|
||
|
||
// ── ГРАФИК (сворачиваемый) ────────────────────────────────────────────
|
||
window._homeSchedOpen = (window._homeSchedOpen !== false); // default open
|
||
var schedOpen = window._homeSchedOpen;
|
||
|
||
// 7-дневный мини-стрип (компактнее чем в screenSchedule)
|
||
var weekStrip = '<div style="display:flex;gap:5px;margin-bottom:12px">';
|
||
_DOW.forEach(function(dw, i) {
|
||
var mday = _DATES[i];
|
||
var ci = window._CHECKIN[i];
|
||
var isWeekend = (i===5||i===6); // Сб/Вс — всегда выходные
|
||
var isWork = _WORK[i] && !isWeekend;
|
||
var isSel = (mday === 22); // today
|
||
var apptCnt = (_APPTS[i]||[]).length;
|
||
var taskCnt = (_TASKS[i]||[]).filter(function(t){return !t.done;}).length;
|
||
var bg = isSel ? 'var(--accent)' : !isWork ? 'var(--bg)' : 'var(--card)';
|
||
var numCl = isSel ? '#fff' : !isWork ? '#CBD5E1' : 'var(--ink)';
|
||
var dotParts = [];
|
||
if(apptCnt>0) dotParts.push(
|
||
'<div style="display:flex;align-items:center;gap:2px">'
|
||
+'<div style="width:4px;height:4px;border-radius:50%;background:'+(isSel?'rgba(255,255,255,.9)':'var(--success)')+'"></div>'
|
||
+'<span style="font-size:8px;font-weight:700;color:'+(isSel?'rgba(255,255,255,.8)':'var(--success)')+'">'+apptCnt+'</span>'
|
||
+'</div>');
|
||
if(taskCnt>0) dotParts.push(
|
||
'<div style="display:flex;align-items:center;gap:2px">'
|
||
+'<div style="width:4px;height:4px;border-radius:50%;background:'+(isSel?'rgba(255,255,255,.9)':'var(--warn)')+'"></div>'
|
||
+'<span style="font-size:8px;font-weight:700;color:'+(isSel?'rgba(255,255,255,.8)':'var(--warn)')+'">'+taskCnt+'</span>'
|
||
+'</div>');
|
||
var dotRow = (dotParts.length && isWork)
|
||
? '<div style="display:flex;justify-content:center;gap:3px;margin-top:3px">'+dotParts.join('')+'</div>'
|
||
: '<div style="height:11px"></div>';
|
||
weekStrip += '<div style="flex:1;border-radius:10px;padding:7px 4px 6px;text-align:center;background:'+bg+';'
|
||
+ (ci && !isSel ? 'border:1.5px solid #86EFAC;' : isSel ? '' : 'border:1px solid rgba(0,0,0,.07);') + 'cursor:pointer" onclick="window._schedMday='+mday+';_nav(\'manager_schedule\')">'
|
||
+ '<div style="font-size:9px;color:'+(isSel?'rgba(255,255,255,.7)':'var(--muted)')+';font-weight:600;margin-bottom:3px">'+dw+'</div>'
|
||
+ '<div style="font-size:13px;font-weight:800;color:'+numCl+'">'+mday+'</div>'
|
||
+ dotRow
|
||
+ '</div>';
|
||
});
|
||
weekStrip += '</div>';
|
||
|
||
// Список встреч сегодня
|
||
var apptList = '';
|
||
if (todayAppts.length) {
|
||
todayAppts.forEach(function(a, i) {
|
||
apptList += '<div style="display:flex;align-items:center;gap:11px;padding:10px 0;'+(i<todayAppts.length-1?'border-bottom:1px solid rgba(0,0,0,.05)':'')+'">'
|
||
+'<div style="font-size:12px;font-weight:800;color:var(--ink);min-width:38px;font-variant-numeric:tabular-nums">'+a.time+'</div>'
|
||
+'<div style="width:7px;height:7px;border-radius:50%;background:'+a.color+';flex-shrink:0"></div>'
|
||
+'<div style="flex:1">'
|
||
+'<div style="font-size:13px;font-weight:600;color:var(--ink)">'+a.client+'</div>'
|
||
+'<div style="font-size:11px;color:var(--muted)">'+a.type+'</div>'
|
||
+'</div>'
|
||
+(a.order?'<span style="font-size:10px;color:var(--muted);background:var(--bg);padding:3px 8px;border-radius:20px;white-space:nowrap">'+a.order+'</span>':'<span style="font-size:10px;color:var(--success);background:rgba(16,185,129,.1);padding:3px 8px;border-radius:20px">Лид</span>')
|
||
+'</div>';
|
||
});
|
||
} else {
|
||
apptList = '<div style="font-size:13px;color:var(--muted);padding:8px 0 4px">Встреч на сегодня нет</div>';
|
||
}
|
||
|
||
// Иконка шеврона
|
||
var chevron = '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="var(--muted)" stroke-width="2.5" style="transition:transform .2s;transform:rotate('+(schedOpen?'0':'180')+'deg)"><polyline points="18 15 12 9 6 15"/></svg>';
|
||
|
||
var apptHtml =
|
||
'<div style="margin:0 16px 12px;background:var(--card);border-radius:16px;box-shadow:0 2px 10px rgba(0,0,0,.07);overflow:hidden">'
|
||
// Заголовок-тоггл
|
||
+'<div onclick="_homeToggleSched()" style="display:flex;align-items:center;justify-content:space-between;padding:13px 14px;cursor:pointer;'+(schedOpen?'border-bottom:1px solid rgba(0,0,0,.06)':'')+'">'
|
||
+'<div style="display:flex;align-items:center;gap:8px">'
|
||
+'<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="var(--accent)" stroke-width="2.5"><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 style="font-size:13px;font-weight:700;color:var(--ink)">График</span>'
|
||
+'<span style="font-size:11px;color:var(--muted);font-weight:500">Чт, 22 мая</span>'
|
||
+(todayAppts.length?'<span style="font-size:10px;background:var(--accent);color:#fff;padding:1px 7px;border-radius:20px;font-weight:700">'+todayAppts.length+'</span>':'')
|
||
+'</div>'
|
||
+chevron
|
||
+'</div>'
|
||
// Содержимое (сворачивается)
|
||
+(schedOpen
|
||
? '<div style="padding:12px 14px">'
|
||
+ weekStrip
|
||
+ apptList
|
||
+'<div onclick="window._schedView=\'month\';_nav(\'manager_schedule\')" style="margin-top:10px;text-align:center;font-size:12px;color:var(--accent);font-weight:600;cursor:pointer;padding:6px 0">Открыть полный график →</div>'
|
||
+'</div>'
|
||
: '')
|
||
+'</div>';
|
||
|
||
// ── ЗАДАЧИ ────────────────────────────────────────────────────────────
|
||
var taskHtml = '';
|
||
if (todayTasks.length) {
|
||
taskHtml += '<div style="padding:16px 16px 8px;font-size:11px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.06em">✅ Задачи на сегодня</div>'
|
||
+'<div style="margin:0 16px;background:var(--card);border-radius:16px;overflow:hidden;box-shadow:0 2px 10px rgba(0,0,0,.07)">';
|
||
todayTasks.forEach(function(t, i) {
|
||
taskHtml += '<div style="display:flex;align-items:center;gap:12px;padding:11px 14px;'+(i<todayTasks.length-1?'border-bottom:1px solid rgba(0,0,0,.05)':'')+'" onclick="_toast(\'Задача выполнена ✓\',\'#10B981\')">'
|
||
+'<div style="width:20px;height:20px;border-radius:6px;border:2px solid '+(t.hi?'var(--danger)':'#CBD5E1')+';flex-shrink:0;cursor:pointer"></div>'
|
||
+'<div style="flex:1">'
|
||
+'<div style="font-size:13px;font-weight:'+(t.hi?'700':'500')+';color:var(--ink)">'+t.text+'</div>'
|
||
+(t.order?'<div style="font-size:11px;color:var(--muted)">'+t.order+'</div>':'')
|
||
+'</div>'
|
||
+(t.hi?'<span style="font-size:9px;background:#FEE2E2;color:#DC2626;padding:2px 7px;border-radius:20px;font-weight:700;flex-shrink:0">ВАЖНО</span>':'')
|
||
+'</div>';
|
||
});
|
||
taskHtml += '</div>';
|
||
}
|
||
|
||
// ── ПАЙПЛАЙН МИНИ ─────────────────────────────────────────────────────
|
||
var stageDefs = [{n:'Замер',i:1},{n:'Проект',i:2},{n:'Техника',i:3},{n:'Технолог',i:4},{n:'Произв.',i:5},{n:'Сборка',i:6}];
|
||
var stageCnts = {};
|
||
orders.filter(function(o){return o.stage<7;}).forEach(function(o){ stageCnts[o.stage]=(stageCnts[o.stage]||0)+1; });
|
||
var ldStages = ['new','meeting','kp','thinking'];
|
||
var ldNames = {new:'Новые',meeting:'Встреча',kp:'КП',thinking:'Думает'};
|
||
var ldColors = {new:'#DBEAFE',meeting:'#CCFBF1',kp:'#FEF3C7',thinking:'#F1F5F9'};
|
||
var ldTxtCol = {new:'#1D4ED8',meeting:'#0F766E',kp:'#D97706',thinking:'#64748B'};
|
||
var ldCnts = {};
|
||
leads.forEach(function(l){ ldCnts[l.leadStage]=(ldCnts[l.leadStage]||0)+1; });
|
||
|
||
var pipeHtml =
|
||
'<div style="display:flex;align-items:center;justify-content:space-between;padding:16px 16px 8px">'
|
||
+'<div style="font-size:11px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.06em">⚡ Пайплайн</div>'
|
||
+'<button onclick="_toast(\'+ Новый лид\',\'#003E7E\')" style="background:var(--accent);color:#fff;border:none;border-radius:8px;padding:5px 13px;font-size:12px;font-weight:700;cursor:pointer">+ Лид</button>'
|
||
+'</div>'
|
||
+'<div style="margin:0 16px;background:var(--card);border-radius:16px;padding:14px;box-shadow:0 2px 10px rgba(0,0,0,.07)">'
|
||
// Заказы
|
||
+'<div style="font-size:11px;font-weight:700;color:var(--muted);margin-bottom:8px">Заказы · '+orders.filter(function(o){return o.stage<7;}).length+'</div>'
|
||
+'<div style="display:flex;gap:4px;margin-bottom:14px">';
|
||
stageDefs.forEach(function(s) {
|
||
var cnt = stageCnts[s.i]||0;
|
||
pipeHtml += '<div style="flex:1;text-align:center">'
|
||
+'<div style="height:30px;border-radius:8px;display:flex;align-items:center;justify-content:center;font-size:13px;font-weight:800;'
|
||
+(cnt?'background:var(--accent);color:#fff':'background:#F1F5F9;color:#CBD5E1')
|
||
+'">'+(cnt?cnt:'—')+'</div>'
|
||
+'<div style="font-size:9px;color:var(--muted);margin-top:3px;font-weight:600">'+s.n+'</div>'
|
||
+'</div>';
|
||
});
|
||
pipeHtml += '</div>'
|
||
+'<div style="height:1px;background:rgba(0,0,0,.06);margin-bottom:12px"></div>'
|
||
// Лиды
|
||
+'<div style="font-size:11px;font-weight:700;color:var(--muted);margin-bottom:8px">Лиды · '+leads.length+'</div>'
|
||
+'<div style="display:flex;gap:4px">';
|
||
ldStages.forEach(function(s) {
|
||
var cnt = ldCnts[s]||0;
|
||
pipeHtml += '<div style="flex:1;text-align:center">'
|
||
+'<div style="height:28px;border-radius:8px;display:flex;align-items:center;justify-content:center;font-size:13px;font-weight:800;'
|
||
+(cnt?'background:'+ldColors[s]+';color:'+ldTxtCol[s]:'background:#F1F5F9;color:#CBD5E1')
|
||
+'">'+(cnt?cnt:'—')+'</div>'
|
||
+'<div style="font-size:9px;color:var(--muted);margin-top:3px;font-weight:600">'+ldNames[s]+'</div>'
|
||
+'</div>';
|
||
});
|
||
pipeHtml += '</div></div>';
|
||
|
||
// ── ВОРОНКА + ФИЛЬТР ──────────────────────────────────────────────────
|
||
var _F = window._homeFilter || 'all';
|
||
var _FDEF = [
|
||
{id:'all', label:'Все', cnt: all.length},
|
||
{id:'lead', label:'Лиды', cnt: leads.filter(function(o){return o.leadStage!=='lost';}).length},
|
||
{id:'meet', label:'Замер', cnt: leads.filter(function(o){return o.leadStage==='meeting';}).length},
|
||
{id:'kp', label:'КП', cnt: leads.filter(function(o){return o.leadStage==='kp'||o.leadStage==='thinking';}).length},
|
||
{id:'deal', label:'Договор', cnt: orders.filter(function(o){return (o.stage||1)<=4;}).length},
|
||
{id:'mount', label:'Монтаж', cnt: orders.filter(function(o){return (o.stage||1)>=5;}).length},
|
||
];
|
||
function _matchF(o,f){
|
||
if(f==='all') return true;
|
||
if(f==='lead') return o.isLead && o.leadStage!=='lost';
|
||
if(f==='meet') return o.isLead && o.leadStage==='meeting';
|
||
if(f==='kp') return o.isLead && (o.leadStage==='kp'||o.leadStage==='thinking');
|
||
if(f==='deal') return !o.isLead && (o.stage||1)<=4;
|
||
if(f==='mount') return !o.isLead && (o.stage||1)>=5;
|
||
return true;
|
||
}
|
||
var filteredAll = all.filter(function(o){return _matchF(o,_F);});
|
||
|
||
// Пиллы-воронка
|
||
var funnelPills = _FDEF.map(function(fd){
|
||
var act = _F===fd.id;
|
||
return '<button onclick="window._homeFilter=\''+fd.id+'\';document.getElementById(\'screen\').innerHTML=renderScreen(\'manager_home\')" style="'
|
||
+'flex-shrink:0;padding:6px 12px;border-radius:20px;font-size:12px;font-weight:700;cursor:pointer;border:none;white-space:nowrap;transition:all .15s;'
|
||
+(act?'background:var(--accent);color:#fff;box-shadow:0 2px 8px rgba(0,62,126,.3)':'background:var(--card);color:var(--muted);border:1px solid rgba(0,0,0,.08)')+'">'+fd.label
|
||
+(fd.cnt?' <span style="font-size:10px;opacity:.75">'+fd.cnt+'</span>':'')+'</button>';
|
||
}).join('');
|
||
|
||
var ldC2={new:'#DBEAFE',meeting:'#CCFBF1',kp:'#FEF3C7',thinking:'#F1F5F9',lost:'#FEE2E2'};
|
||
var ldT2={new:'#1D4ED8',meeting:'#0F766E',kp:'#D97706',thinking:'#64748B',lost:'#DC2626'};
|
||
var ldN2={new:'Новый',meeting:'Встреча',kp:'КП',thinking:'Думает',lost:'Отказ'};
|
||
|
||
var allHtml =
|
||
'<div style="display:flex;align-items:center;justify-content:space-between;padding:16px 16px 8px">'
|
||
+'<div style="font-size:11px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.06em">Воронка</div>'
|
||
+'<button onclick="_toast(\'+ Новый лид\',\'#003E7E\')" style="background:var(--accent);color:#fff;border:none;border-radius:8px;padding:5px 13px;font-size:12px;font-weight:700;cursor:pointer">+ Лид</button>'
|
||
+'</div>'
|
||
// Горизонтальные пиллы с прокруткой
|
||
+'<div style="display:flex;gap:7px;padding:0 16px 12px;overflow-x:auto;scrollbar-width:none">'+funnelPills+'</div>'
|
||
+'<div style="margin:0 16px;background:var(--card);border-radius:16px;overflow:hidden;box-shadow:0 2px 10px rgba(0,0,0,.07)">';
|
||
|
||
if(filteredAll.length === 0){
|
||
allHtml += '<div style="padding:24px;text-align:center;color:var(--muted);font-size:13px">Нет клиентов в этом этапе</div>';
|
||
} else {
|
||
filteredAll.forEach(function(o) {
|
||
var idx = all.indexOf(o);
|
||
var isLast = o === filteredAll[filteredAll.length-1];
|
||
if (o.isLead) {
|
||
allHtml += '<div onclick="_openOrder('+idx+')" style="display:flex;align-items:center;gap:11px;padding:11px 14px;'+(isLast?'':'border-bottom:1px solid rgba(0,0,0,.05)')+';cursor:pointer">'
|
||
+'<div style="width:36px;height:36px;border-radius:10px;background:#EDE9FE;display:flex;align-items:center;justify-content:center;font-size:15px;font-weight:800;color:#7C3AED;flex-shrink:0">Л</div>'
|
||
+'<div style="flex:1;min-width:0">'
|
||
+'<div style="font-size:14px;font-weight:600;color:var(--ink)">'+o.client+'</div>'
|
||
+'<div style="font-size:11px;color:var(--muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis">'+o.label+'</div>'
|
||
+'</div>'
|
||
+'<span style="font-size:11px;font-weight:700;background:'+ldC2[o.leadStage]+';color:'+ldT2[o.leadStage]+';padding:3px 9px;border-radius:20px;flex-shrink:0">'+ldN2[o.leadStage]+'</span>'
|
||
+'</div>';
|
||
} else {
|
||
var hasB2 = !!o.blocker || (o.tech && o.tech.some(function(t){return t.status==='waiting_client'||t.status==='client_chosen';}));
|
||
var bi2 = hasB2 ? _blockerInfo(o) : null;
|
||
allHtml += '<div onclick="_openOrder('+idx+')" style="display:flex;align-items:center;gap:11px;padding:11px 14px;'+(isLast?'':'border-bottom:1px solid rgba(0,0,0,.05)')+';cursor:pointer">'
|
||
+'<div style="width:36px;height:36px;border-radius:10px;background:var(--bg);display:flex;align-items:center;justify-content:center;font-size:18px;flex-shrink:0">'+stIc[o.stage||1]+'</div>'
|
||
+'<div style="flex:1;min-width:0">'
|
||
+'<div style="font-size:14px;font-weight:600;color:var(--ink)">'+o.client+'</div>'
|
||
+'<div style="font-size:11px;color:var(--muted)">'+o.contract+' · '+o.amount.toLocaleString('ru')+' ₽</div>'
|
||
+'</div>'
|
||
+(hasB2
|
||
? '<span style="font-size:10px;font-weight:700;background:'+bi2.bg+';color:'+bi2.color+';padding:3px 9px;border-radius:20px;flex-shrink:0;white-space:nowrap">'+bi2.icon+' '+bi2.label+'</span>'
|
||
: '<span style="font-size:11px;font-weight:700;background:#EEF2FF;color:#4338CA;padding:3px 9px;border-radius:20px;flex-shrink:0">'+stN[o.stage||1]+'</span>')
|
||
+'</div>';
|
||
}
|
||
});
|
||
}
|
||
allHtml += '</div>';
|
||
|
||
return '<div class="page">'
|
||
+ heroHtml
|
||
+ '<div style="padding:8px 0 0"></div>'
|
||
+ apptHtml
|
||
+ urgentHtml
|
||
+ taskHtml
|
||
+ allHtml
|
||
+ '<div style="height:16px"></div>'
|
||
+ '</div>';
|
||
}
|
||
function _homeToggleSched(){
|
||
window._homeSchedOpen = !window._homeSchedOpen;
|
||
document.getElementById('screen').innerHTML = renderScreen('manager_home');
|
||
document.getElementById('nav').innerHTML = navBar();
|
||
}
|
||
// ── Blocker classifier ────────────────────────────────────────────────────────
|
||
function _blockerInfo(o){
|
||
// Приоритет: реальное состояние техники > текст blocker
|
||
if(o.tech && o.tech.length){
|
||
var wc=o.tech.filter(function(t){return t.status==='waiting_client';}).length;
|
||
var cc=o.tech.filter(function(t){return t.status==='client_chosen';}).length;
|
||
if(cc>0) return {icon:'👆',color:'#059669',bg:'#ECFDF5',bar:'#059669',
|
||
label:'Клиент выбрал · подтвердить '+cc+' поз.',
|
||
cta:'Принять выбор'};
|
||
if(wc>0) return {icon:'⏳',color:'#D97706',bg:'#FEF3C7',bar:'#D97706',
|
||
label:'Ждём клиента · '+wc+' поз.',
|
||
cta:'Напомнить клиенту'};
|
||
}
|
||
var txt=(o.blocker||'').toLowerCase();
|
||
if(txt.indexOf('технолог')>=0) return {icon:'🔧',color:'#2563EB',bg:'#EFF6FF',bar:'#3B82F6',
|
||
label:o.blocker, cta:'Открыть заказ'};
|
||
if(txt.indexOf('техника')>=0) return {icon:'⚙️',color:'#D97706',bg:'#FFFBEB',bar:'#F59E0B',
|
||
label:o.blocker, cta:'Разобрать блокер'};
|
||
return {icon:'⚠️',color:'#92400E',bg:'#FFFBEB',bar:'var(--warn)',
|
||
label:o.blocker||'Блокер', cta:'Разобрать блокер'};
|
||
}
|
||
|
||
function _openOrder(i){
|
||
window._activeOrder=i;
|
||
var o=window._managerOrders[i];
|
||
var screen = o && o.isLead ? 'manager_lead' : 'manager_order';
|
||
document.getElementById('screen').innerHTML=renderScreen(screen);
|
||
document.getElementById('nav').innerHTML=navBar();
|
||
}
|
||
|
||
// ── ORDER ─────────────────────────────────────────────────────────────────────
|
||
function screenOrder() {
|
||
var o = window._managerOrders[window._activeOrder];
|
||
if(!o) return '<div style="padding:32px">Заказ не найден</div>';
|
||
var isKitchen = o.type==='kitchen';
|
||
|
||
// Pipeline
|
||
var stages = isKitchen
|
||
? [{n:'Замер',i:1},{n:'Проект',i:2},{n:'Техника',i:3},{n:'Технолог',i:4},{n:'Произв.',i:5},{n:'Сборка',i:6},{n:'Закрыт',i:7}]
|
||
: [{n:'Замер',i:1},{n:'Проект',i:2},{n:'Технолог',i:3},{n:'Произв.',i:4},{n:'Сборка',i:5},{n:'Закрыт',i:6}];
|
||
var pip='';
|
||
stages.forEach(function(s,idx){
|
||
var st = s.i<o.stage?'done':(s.i===o.stage?(o.blocker?'blocked':'active'):'');
|
||
var dc = st==='done'?'✓':(st==='blocked'?'!':s.i);
|
||
if(idx>0) pip+='<div class="pip-line'+(stages[idx-1].i<o.stage?' done':'')+'"></div>';
|
||
pip+='<div class="pip-step"><div class="pip-dot '+st+'">'+dc+'</div>'
|
||
+'<div class="pip-label'+(st==='active'?' active':'')+'">'+s.n+'</div></div>';
|
||
});
|
||
|
||
// Blocker / tech-confirmed info
|
||
var blk='';
|
||
if(o.techConfirmed && o.stage>=4){
|
||
blk='<div style="background:#EFF6FF;border:1.5px solid #93C5FD;border-radius:12px;padding:12px 14px;margin-bottom:12px;display:flex;align-items:center;gap:10px">'
|
||
+'<div style="font-size:18px">🔧</div>'
|
||
+'<div><div style="font-size:13px;font-weight:700;color:#1D4ED8">Спецификация у технолога</div>'
|
||
+'<div style="font-size:12px;color:#2563EB">Передано '+o.techConfirmedDate+' · Ждём согласования проекта</div></div>'
|
||
+'</div>';
|
||
} else if(o.blocker){
|
||
blk='<div style="background:#FFFBEB;border:1.5px solid #F59E0B;border-radius:12px;padding:12px 14px;margin-bottom:12px">'
|
||
+'<div style="font-size:11px;font-weight:700;color:#B45309;text-transform:uppercase;letter-spacing:.04em;margin-bottom:3px">⚠ Блокер</div>'
|
||
+'<div style="font-size:14px;font-weight:700;color:#92400E">'+o.blocker+'</div>'
|
||
+(o.techNote?'<div style="font-size:12px;color:#B45309;margin-top:3px">'+o.techNote+'</div>':'')
|
||
+'</div>';
|
||
}
|
||
|
||
// ─── Документы изделия (инструкция + паспорт) ───
|
||
// Верифицированные прямые ссылки на PDF/страницы документов
|
||
var _TECH_DOCS = {
|
||
// Bosch варочные панели
|
||
'PUE611BB5E': {
|
||
manual: 'https://bosch-centre.ru/upload/iblock/14b/dztw8oc73p5ssxnwr5f5m1qzk8223mtx/9001608943_E.pdf',
|
||
passport: 'https://bosch-centre.ru/bosch/varochnye-paneli/elektricheskie/varochnaya-panel-bosch-pue-611-bb5e.html'
|
||
},
|
||
'PIE631FB1E': {
|
||
manual: 'https://www.hausdorf.ru/upload/iblock/808/installation_hausdorf_elektricheskaya_varochnaya_panel_bosch_pie631fb1e.pdf',
|
||
passport: 'https://media3.bosch-home.com/Documents/specsheet/ru-RU/PIE631FB1E.pdf'
|
||
},
|
||
'PIF612BB1E': {
|
||
manual: 'https://www.hausdorf.ru/search/?q=Bosch+PIF612BB1E',
|
||
passport: 'https://media3.bosch-home.com/Documents/specsheet/ru-RU/PIF612BB1E.pdf'
|
||
},
|
||
// Bosch холодильники
|
||
'KGN56LW31U': {
|
||
manual: 'https://bosch-centre.ru/technical-documentation/holodilniki/',
|
||
passport: 'https://bosch-centre.ru/bosch/holodilniki/dvuhkamernye-holodilniki/dvukhkamernyy-kholodilnik-bosch-kgn56lw31u.html'
|
||
},
|
||
// Bosch посудомойки
|
||
'SPV4HMX10E': {
|
||
manual: 'https://www.hausdorf.ru/search/?q=Bosch+SPV4HMX10E',
|
||
passport: 'https://media3.bosch-home.com/Documents/specsheet/ru-RU/SPV4HMX10E.pdf'
|
||
},
|
||
// Elica вытяжки
|
||
'FLAT GLASS IX/A/60': {
|
||
manual: 'https://www.hausdorf.ru/upload/iblock/d4b/instruction_hausdorf_vytyazhka_elica_flat_glass_ix_a_60_1.pdf',
|
||
passport: 'https://www.manualslib.com/products/Elica-Flat-Glass-Ix-A-60-13710296.html'
|
||
}
|
||
};
|
||
|
||
function _techDocLinks(t){
|
||
if(!t.brand || !t.model) return '';
|
||
var m = (t.model||'').trim();
|
||
var b = (t.brand||'').toLowerCase();
|
||
var docs = _TECH_DOCS[m];
|
||
var manualUrl, passportUrl;
|
||
if(docs){
|
||
manualUrl = docs.manual;
|
||
passportUrl = docs.passport;
|
||
} else if(b==='bosch'){
|
||
// Для Bosch без известного файла — hausdorf.ru поиск + страница продукта на bosch-centre.ru
|
||
var mq = encodeURIComponent('Bosch '+m);
|
||
manualUrl = 'https://www.hausdorf.ru/search/?q='+mq;
|
||
passportUrl = 'https://bosch-centre.ru/search/?q='+mq;
|
||
} else {
|
||
// Для других брендов — Google
|
||
var q = encodeURIComponent(t.brand+' '+m);
|
||
manualUrl = 'https://www.google.com/search?q='+q+'+%D0%B8%D0%BD%D1%81%D1%82%D1%80%D1%83%D0%BA%D1%86%D0%B8%D1%8F+PDF';
|
||
passportUrl = 'https://www.google.com/search?q='+q+'+%D0%BF%D0%B0%D1%81%D0%BF%D0%BE%D1%80%D1%82+%D1%85%D0%B0%D1%80%D0%B0%D0%BA%D1%82%D0%B5%D1%80%D0%B8%D1%81%D1%82%D0%B8%D0%BA%D0%B8';
|
||
}
|
||
return '<div style="display:flex;gap:5px;margin-top:7px;width:100%">'
|
||
+'<a href="'+manualUrl+'" target="_blank" rel="noopener" style="flex:1;display:inline-flex;align-items:center;justify-content:center;gap:3px;padding:5px 6px;background:#EFF6FF;border:1px solid #BFDBFE;border-radius:8px;text-decoration:none;font-size:10px;font-weight:700;color:#1D4ED8">'
|
||
+'<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" 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"/></svg>'
|
||
+'📄 Инструкция</a>'
|
||
+'<a href="'+passportUrl+'" target="_blank" rel="noopener" style="flex:1;display:inline-flex;align-items:center;justify-content:center;gap:3px;padding:5px 6px;background:#F0FDF4;border:1px solid #BBF7D0;border-radius:8px;text-decoration:none;font-size:10px;font-weight:700;color:#15803D">'
|
||
+'<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2"/><path d="M9 9h6M9 12h6M9 15h4"/></svg>'
|
||
+'📋 Паспорт</a>'
|
||
+'</div>';
|
||
}
|
||
|
||
// Tech section (kitchen only)
|
||
var techHtml='';
|
||
if(isKitchen && o.tech.length){
|
||
var dc2=o.tech.filter(function(t){return t.status==='done'||t.status==='client_chosen'||t.status==='waiting_client';}).length;
|
||
var items=o.tech.map(function(t){
|
||
var iconBox='<div class="tech-icon-box '+(t.status==='done'?'done':(t.status==='waiting_client'||t.status==='client_chosen')?'client':'wait')+'">'
|
||
+_techIcon(t.wkey,22)+'</div>';
|
||
|
||
var sourceLabel = t.source==='own'?'Своя техника'
|
||
:t.source==='ai'?'AI подбор'
|
||
:t.source==='client'?'Клиент'
|
||
:'';
|
||
var sourceBadge = sourceLabel
|
||
?'<span style="font-size:10px;font-weight:700;padding:1px 6px;border-radius:8px;background:'
|
||
+(t.source==='own'?'#EDE9FE;color:#5B21B6'
|
||
:t.source==='ai'?'#DBEAFE;color:#1D4ED8'
|
||
:'#DCFCE7;color:#15803D')
|
||
+'">'+sourceLabel+'</span>'
|
||
:'';
|
||
|
||
var nameRow='<div style="display:flex;align-items:center;gap:6px;flex-wrap:wrap">'
|
||
+'<span style="font-size:14px;font-weight:600;color:var(--ink)">'+t.name+'</span>'
|
||
+sourceBadge+'</div>';
|
||
|
||
if(t.status==='wait'){
|
||
return '<div class="tech-item" style="flex-wrap:wrap;border-bottom:1px solid rgba(0,0,0,.06);padding-bottom:10px;margin-bottom:2px">'
|
||
+'<div style="display:flex;gap:10px;width:100%">'
|
||
+iconBox
|
||
+'<div style="flex:1">'+nameRow
|
||
+'<div style="font-size:11px;color:var(--muted);margin-top:2px">Способ не выбран</div>'
|
||
+'</div></div>'
|
||
+'<div class="tech-act-row">'
|
||
+'<button class="tech-act-btn ai" onclick="_startWizard(\''+t.wkey+'\')"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg> AI подбор</button>'
|
||
+'<button class="tech-act-btn own" onclick="_startOwnTech(\''+t.wkey+'\')"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M16.5 9.4l-9-5.19M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"/><polyline points="3.27 6.96 12 12.01 20.73 6.96"/><line x1="12" y1="22.08" x2="12" y2="12"/></svg> Своя</button>'
|
||
+'<button class="tech-act-btn client" onclick="_startClientPick(\''+t.wkey+'\')"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg> Клиент</button>'
|
||
+'</div></div>';
|
||
}
|
||
if(t.status==='waiting_client'){
|
||
return '<div class="tech-item">'
|
||
+iconBox
|
||
+'<div style="flex:1">'+nameRow
|
||
+'<div style="font-size:11px;color:#2563EB;margin-top:2px">🔄 Ждём выбора клиента</div>'
|
||
+'</div>'
|
||
+'<button onclick="_techClientRemind(\''+t.wkey+'\')" class="btn-pill accent">Напомнить</button>'
|
||
+'</div>';
|
||
}
|
||
// client_chosen — клиент выбрал, ждёт подтверждения менеджера
|
||
if(t.status==='client_chosen'){
|
||
var cDetail=(t.brand||'')+(t.model?' '+t.model:'')+(t.dims?' · '+t.dims:'');
|
||
return '<div class="tech-item" style="flex-wrap:wrap;padding-bottom:10px">'
|
||
+'<div style="display:flex;gap:10px;width:100%;align-items:flex-start">'
|
||
+iconBox
|
||
+'<div style="flex:1">'+nameRow
|
||
+'<div style="font-size:12px;color:#059669;font-weight:600;margin-top:3px">👤 Клиент выбрал</div>'
|
||
+'<div style="font-size:13px;font-weight:700;color:var(--ink);margin-top:1px">'+cDetail+'</div>'
|
||
+'</div></div>'
|
||
+_techDocLinks(t)
|
||
+'<div style="width:100%;margin-top:9px;padding:10px 12px;background:#F0FDF4;border:1.5px solid #86EFAC;border-radius:10px">'
|
||
+'<div style="font-size:11px;font-weight:700;color:#15803D;margin-bottom:7px;text-transform:uppercase;letter-spacing:.04em">Подтвердить выбор клиента?</div>'
|
||
+'<div style="display:flex;gap:7px">'
|
||
+'<button onclick="_acceptClientChoice(\''+oi+'\',\''+t.wkey+'\')" style="flex:1;padding:8px;border-radius:9px;border:none;background:#16A34A;color:#fff;font-size:13px;font-weight:700;cursor:pointer">✅ Принять</button>'
|
||
+'<button onclick="_rejectClientChoice(\''+oi+'\',\''+t.wkey+'\')" style="flex:1;padding:8px;border-radius:9px;border:1.5px solid #DC2626;background:transparent;color:#DC2626;font-size:13px;font-weight:700;cursor:pointer">↩ Переспросить</button>'
|
||
+'</div></div>'
|
||
+'</div>';
|
||
}
|
||
// done
|
||
var detail=(t.brand||'')+(t.model?' '+t.model:'')+(t.dims?'<br><span style="font-size:10px;color:var(--muted)">'+t.dims+'</span>':'');
|
||
return '<div class="tech-item" style="flex-wrap:wrap;padding-bottom:10px">'
|
||
+'<div style="display:flex;gap:10px;width:100%;align-items:flex-start">'
|
||
+iconBox
|
||
+'<div style="flex:1">'+nameRow
|
||
+(detail?'<div style="font-size:12px;color:var(--muted);margin-top:2px">'+detail+'</div>':'')
|
||
+'</div>'
|
||
+'<button onclick="'+(t.source==='own'?"_startOwnTech('"+t.wkey+"')":'_startClientPick(\''+t.wkey+'\')')+'" class="btn-pill muted">Изменить</button>'
|
||
+'</div>'
|
||
+_techDocLinks(t)
|
||
+'</div>';
|
||
}).join('');
|
||
var waitCount = o.tech.filter(function(t){return t.status==='wait';}).length;
|
||
var clientChosenCount = o.tech.filter(function(t){return t.status==='client_chosen';}).length;
|
||
var clientCount = o.tech.filter(function(t){return t.status==='waiting_client';}).length;
|
||
// allFilled: все должны быть done (client_chosen ≠ done — надо подтвердить)
|
||
var allFilled = waitCount===0 && clientChosenCount===0 && clientCount===0;
|
||
var confirmed = !!o.techConfirmed;
|
||
|
||
// Bottom of tech card: confirm CTA or confirmed status
|
||
var techFooter='';
|
||
if(confirmed){
|
||
techFooter='<div style="margin-top:12px;padding:11px 14px;background:#F0FDF4;border-radius:10px;display:flex;align-items:center;gap:10px">'
|
||
+'<div style="font-size:18px">✅</div>'
|
||
+'<div><div style="font-size:13px;font-weight:700;color:#15803D">Спецификация передана технологу</div>'
|
||
+'<div style="font-size:11px;color:#166534">'+o.techConfirmedDate+'</div></div>'
|
||
+'</div>';
|
||
} else if(allFilled){
|
||
techFooter='<div style="margin-top:12px">'
|
||
+'<button onclick="_confirmTech()" class="btn-primary btn-confirm">✅ Подтвердить и передать технологу →</button>'
|
||
+'<div style="font-size:11px;color:var(--muted);text-align:center;margin-top:7px">Технолог получит спецификацию и сможет согласовать проект</div>'
|
||
+'</div>';
|
||
} else {
|
||
var pendingMsg = clientChosenCount>0
|
||
? 'Клиент выбрал технику — подтвердите выбор выше, затем передайте технологу'
|
||
: 'Заполните все позиции — технолог не может работать без параметров техники';
|
||
techFooter='<div style="margin-top:10px;padding:10px 12px;background:'+(clientChosenCount>0?'#F0FDF4':'#FFFBEB')+';border-radius:10px;display:flex;align-items:center;gap:8px">'
|
||
+'<div style="font-size:15px">'+(clientChosenCount>0?'👆':'⏳')+'</div>'
|
||
+'<div style="font-size:12px;color:'+(clientChosenCount>0?'#166534':'#92400E')+'">'+pendingMsg+'</div>'
|
||
+'</div>';
|
||
}
|
||
|
||
techHtml='<div class="section-label">Техника · '+dc2+'/'+o.tech.length+'</div>'
|
||
+'<div class="card"><div class="row sb" style="margin-bottom:10px">'
|
||
+'<span style="font-size:13px;font-weight:600;color:var(--ink)">Спецификация техники</span>'
|
||
+(confirmed?'':'<button class="btn-sm" onclick="_startTech()">+ Позиция</button>')+'</div>'
|
||
+items+techFooter+'</div>';
|
||
}
|
||
|
||
// Rooms
|
||
var oi = window._activeOrder;
|
||
var ALL_ROOMS=['Кухня','Гостиная','Спальня','Детская','Кабинет','Прихожая','Ванная','Балкон','Гардеробная','Столовая'];
|
||
var roomsEdit = window._roomsEditing;
|
||
var editSvg='<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/></svg>';
|
||
var roomHtml='<div class="section-label" style="display:flex;align-items:center;justify-content:space-between;padding-right:16px">'
|
||
+'<span>Помещения</span>'
|
||
+'<button onclick="window._roomsEditing=!window._roomsEditing;document.getElementById(\'screen\').innerHTML=renderScreen(\'manager_order\')" style="display:inline-flex;align-items:center;gap:4px;padding:3px 9px;border-radius:8px;border:1px solid rgba(0,62,126,.2);background:rgba(0,62,126,.06);color:var(--accent);font-size:11px;font-weight:700;cursor:pointer">'
|
||
+editSvg+(roomsEdit?' Готово':' Изменить')+'</button></div>';
|
||
if(roomsEdit){
|
||
// Chip selector
|
||
roomHtml+='<div class="card" style="padding:14px">'
|
||
+'<div style="font-size:12px;color:var(--muted);margin-bottom:10px">Выберите помещения, в которые идёт мебель:</div>'
|
||
+'<div style="display:flex;flex-wrap:wrap;gap:7px">';
|
||
ALL_ROOMS.forEach(function(r){
|
||
var sel = o.rooms.indexOf(r)>=0;
|
||
roomHtml+='<button onclick="_toggleRoom('+oi+',\''+r+'\')" style="padding:7px 13px;border-radius:20px;font-size:13px;font-weight:600;cursor:pointer;border:2px solid '+(sel?'var(--accent)':'#E2E8F0')+';background:'+(sel?'var(--accent)':'var(--card)')+';color:'+(sel?'#fff':'var(--muted)')+'">'+r+'</button>';
|
||
});
|
||
roomHtml+='</div>';
|
||
if(o.rooms.length){
|
||
roomHtml+='<div style="margin-top:10px;padding-top:10px;border-top:1px solid rgba(0,0,0,.07);font-size:12px;color:var(--muted)">Выбрано: <strong style="color:var(--ink)">'+o.rooms.join(', ')+'</strong></div>';
|
||
}
|
||
roomHtml+='</div>';
|
||
} else if(o.rooms.length){
|
||
roomHtml+='<div class="card">'
|
||
+o.rooms.map(function(r){
|
||
return '<div class="row sb" style="padding:8px 0;border-bottom:1px solid rgba(0,0,0,.06)">'
|
||
+'<span style="font-size:14px;font-weight:600">🏠 '+r+'</span>'
|
||
+'<span class="badge green">✓ Замер</span></div>';
|
||
}).join('')+'</div>';
|
||
} else {
|
||
roomHtml+='<div class="card" style="text-align:center;padding:18px;color:var(--muted)">'
|
||
+'<div style="font-size:22px;margin-bottom:6px">🏠</div>'
|
||
+'<div style="font-size:13px;font-weight:600">Помещения не указаны</div>'
|
||
+'<div style="font-size:12px;margin-top:3px">Нажмите «Изменить» чтобы добавить</div>'
|
||
+'</div>';
|
||
}
|
||
|
||
// ── ЗОВ — статус производства ────────────────────────────────────────────
|
||
var zovInfo = o.zovStatus ? _zovStatusInfo(o.zovStatus.code) : null;
|
||
var zovContractVal = o.zovContract || '';
|
||
var zovHtml = '<div class="section-label">Производство ЗОВ</div>'
|
||
+'<div class="card" style="padding:14px 16px">'
|
||
// ZOV contract number field
|
||
+'<div style="margin-bottom:12px">'
|
||
+'<div style="font-size:11px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.06em;margin-bottom:6px">Номер договора ЗОВ</div>'
|
||
+'<div style="display:flex;gap:8px;align-items:center">'
|
||
+'<input id="inp_zov_'+oi+'" type="text" value="'+zovContractVal+'" placeholder="Пр.: ЗОВ-26-001" onchange="_updateZovContract('+oi+',this.value)"'
|
||
+' style="flex:1;border:2px solid '+(zovContractVal?'var(--accent)':'#E2E8F0')+';border-radius:10px;padding:9px 12px;font-size:13px;font-weight:700;color:var(--ink);outline:none;background:var(--bg);font-family:monospace">'
|
||
+'<button onclick="_refreshZovStatus('+oi+')" style="padding:9px 13px;border-radius:10px;border:1.5px solid rgba(0,62,126,.25);background:rgba(0,62,126,.07);color:var(--accent);font-size:12px;font-weight:700;cursor:pointer;white-space:nowrap;flex-shrink:0">🔄 Статус</button>'
|
||
+'</div>'
|
||
+'</div>';
|
||
if(zovInfo && o.zovStatus){
|
||
var zst = o.zovStatus;
|
||
var zpct = zst.pct || 0;
|
||
zovHtml += '<div style="background:'+zovInfo.bg+';border-radius:11px;padding:12px 14px">'
|
||
+'<div style="display:flex;align-items:center;gap:9px;margin-bottom:8px">'
|
||
+'<span style="font-size:20px">'+zovInfo.icon+'</span>'
|
||
+'<div style="flex:1">'
|
||
+'<div style="font-size:14px;font-weight:700;color:'+zovInfo.color+'">'+zst.label+'</div>'
|
||
+'<div style="font-size:11px;color:var(--muted);margin-top:1px">'+zst.date+'</div>'
|
||
+'</div>'
|
||
+'<span style="font-size:15px;font-weight:900;color:'+zovInfo.color+'">'+zpct+'%</span>'
|
||
+'</div>'
|
||
// progress bar
|
||
+'<div style="height:6px;background:rgba(0,0,0,.08);border-radius:3px;margin-bottom:8px">'
|
||
+'<div style="height:6px;border-radius:3px;background:'+zovInfo.color+';width:'+zpct+'%"></div>'
|
||
+'</div>'
|
||
+'<div style="font-size:12px;color:var(--muted)">'+zst.detail+'</div>'
|
||
+'</div>';
|
||
// Pipeline steps for factory
|
||
var zSteps=[{c:'accepted',l:'Принят'},{c:'production',l:'Произв-во'},{c:'assembly',l:'Сборка'},{c:'ready',l:'Готов'},{c:'shipped',l:'Отгружен'}];
|
||
var zCodes=['accepted','production','assembly','ready','shipped'];
|
||
var zCurIdx = zCodes.indexOf(zst.code);
|
||
zovHtml += '<div style="display:flex;align-items:flex-start;padding:10px 0 2px;overflow-x:auto;scrollbar-width:none">';
|
||
zSteps.forEach(function(zs,zi){
|
||
var isDone = zi < zCurIdx;
|
||
var isAct = zi === zCurIdx;
|
||
var dotC = isDone?'#10B981':isAct?zovInfo.color:'#CBD5E1';
|
||
var dotBg = isDone?'#DCFCE7':isAct?zovInfo.bg:'var(--card)';
|
||
var dotTxt = isDone?'✓':(zi+1).toString();
|
||
if(zi>0) zovHtml+='<div style="flex:1;height:2px;background:'+(isDone?'#10B981':'#E2E8F0')+';margin-top:12px;min-width:6px"></div>';
|
||
zovHtml+='<div style="display:flex;flex-direction:column;align-items:center;gap:3px;flex-shrink:0;min-width:52px">'
|
||
+'<div style="width:26px;height:26px;border-radius:50%;border:2px solid '+dotC+';background:'+dotBg+';display:flex;align-items:center;justify-content:center;font-size:'+(isDone?'10':'9')+'px;font-weight:800;color:'+dotC+'">'+dotTxt+'</div>'
|
||
+'<div style="font-size:9px;font-weight:600;color:'+(isAct?zovInfo.color:'var(--muted)')+';text-align:center;line-height:1.2">'+zs.l+'</div>'
|
||
+'</div>';
|
||
});
|
||
zovHtml += '</div>';
|
||
} else {
|
||
zovHtml += '<div style="padding:10px 0 4px;font-size:12px;color:var(--muted);text-align:center">'
|
||
+(zovContractVal?'Нажмите «Статус» чтобы загрузить данные из zovofficial.com':'Введите номер договора ЗОВ из программы фабрики')
|
||
+'</div>';
|
||
}
|
||
zovHtml += '</div>';
|
||
|
||
// ── Расчёты (встроены в карточку) ─────────────────────────────────────────
|
||
var advances = o.advances || [{label:'Аванс',amount:o.advance||0,paid:true,date:null}];
|
||
var paidSum = advances.reduce(function(s,a){return s+(a.paid?a.amount:0);},0);
|
||
var remaining= o.amount - paidSum;
|
||
var contractDone = remaining <= 0;
|
||
var pct = o.amount > 0 ? Math.min(100, Math.round(paidSum/o.amount*100)) : 0;
|
||
function fmtR(n){ return n.toLocaleString('ru')+' ₽'; }
|
||
var today2 = (function(){ var d=new Date(); return String(d.getDate()).padStart(2,'0')+'.'+String(d.getMonth()+1).padStart(2,'0')+'.'+d.getFullYear(); })();
|
||
|
||
var advRowsO = advances.map(function(a,ai){
|
||
var chk = a.paid
|
||
? '<div style="width:20px;height:20px;border-radius:5px;background:#DCFCE7;display:flex;align-items:center;justify-content:center;flex-shrink:0;cursor:pointer" onclick="_toggleAdvPaid('+oi+','+ai+',false)"><svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="#15803D" stroke-width="3"><polyline points="20 6 9 17 4 12"/></svg></div>'
|
||
: '<div style="width:20px;height:20px;border-radius:5px;border:2px solid #CBD5E1;flex-shrink:0;cursor:pointer" onclick="_toggleAdvPaid('+oi+','+ai+',true)"></div>';
|
||
return '<div style="display:flex;align-items:center;gap:8px;padding:9px 0;border-bottom:1px solid rgba(0,0,0,.05)">'
|
||
+chk
|
||
+'<div style="font-size:12px;color:var(--ink);flex:1;line-height:1.3">'+a.label+'</div>'
|
||
+'<div style="position:relative;width:120px">'
|
||
+'<input type="number" value="'+(a.amount||'')+'" placeholder="0" onchange="_liveAdvAmt('+oi+','+ai+',this.value)"'
|
||
+' style="width:100%;border:1.5px solid '+(a.paid?'var(--success)':'#E2E8F0')+';border-radius:8px;padding:7px 24px 7px 8px;font-size:13px;font-weight:700;color:'+(a.paid?'var(--success)':'var(--muted)')+';outline:none;background:var(--bg)">'
|
||
+'<span style="position:absolute;right:7px;top:50%;transform:translateY(-50%);font-size:11px;font-weight:700;color:var(--muted);pointer-events:none">₽</span>'
|
||
+'</div>'
|
||
+'</div>';
|
||
}).join('');
|
||
|
||
var asmChkO = o.assemblyPaid
|
||
? '<div style="width:20px;height:20px;border-radius:5px;background:#DCFCE7;display:flex;align-items:center;justify-content:center;flex-shrink:0;cursor:pointer" onclick="_toggleAsm('+oi+',false)"><svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="#15803D" stroke-width="3"><polyline points="20 6 9 17 4 12"/></svg></div>'
|
||
: '<div style="width:20px;height:20px;border-radius:5px;border:2px solid #CBD5E1;flex-shrink:0;cursor:pointer" onclick="_toggleAsm('+oi+',true)"></div>';
|
||
|
||
var contHtml = '<div class="section-label">Расчёты с клиентом</div>'
|
||
+'<div class="card" style="padding:14px 16px">'
|
||
// Номер договора + сумма + прогресс
|
||
+'<div class="row sb" style="margin-bottom:10px">'
|
||
+'<span style="font-size:12px;color:var(--muted)">'+o.contract+'</span>'
|
||
+'<div style="display:flex;align-items:center;gap:6px">'
|
||
+'<span style="font-size:15px;font-weight:900;color:var(--accent)">'+fmtR(o.amount)+'</span>'
|
||
+'</div></div>'
|
||
+'<div style="display:flex;align-items:center;gap:8px;margin-bottom:4px">'
|
||
+'<div style="flex:1;height:6px;background:#E2E8F0;border-radius:3px">'
|
||
+'<div style="height:6px;border-radius:3px;background:'+(contractDone?'var(--success)':'var(--accent)')+';width:'+pct+'%"></div>'
|
||
+'</div>'
|
||
+'<span style="font-size:11px;font-weight:700;color:'+(contractDone?'var(--success)':'var(--accent)')+'">'+pct+'%</span>'
|
||
+'</div>'
|
||
+(remaining>0?'<div style="font-size:11px;color:var(--danger);font-weight:600;margin-bottom:10px">Остаток: '+fmtR(remaining)+'</div>':'<div style="font-size:11px;color:var(--success);font-weight:600;margin-bottom:10px">✅ Оплачен полностью</div>')
|
||
// Авансы
|
||
+'<div style="border-top:1px solid rgba(0,0,0,.07);padding-top:8px">'+advRowsO+'</div>'
|
||
// Сборка
|
||
+'<div style="display:flex;align-items:center;gap:8px;padding:9px 0;border-top:1px solid rgba(0,0,0,.07)">'
|
||
+asmChkO
|
||
+'<div style="font-size:12px;color:var(--ink);flex:1">Сборка'+(o.assemblyPaid?' <span style=\"font-size:10px;color:var(--success);font-weight:700\">оплачена</span>':'')+'</div>'
|
||
+'<div style="position:relative;width:120px">'
|
||
+'<input type="number" value="'+(o.assembly||'')+'" placeholder="0" onchange="_liveAsm('+oi+',this.value)"'
|
||
+' style="width:100%;border:1.5px solid '+(o.assemblyPaid?'var(--success)':'#F59E0B')+';border-radius:8px;padding:7px 24px 7px 8px;font-size:13px;font-weight:700;color:#92400E;outline:none;background:var(--bg)">'
|
||
+'<span style="position:absolute;right:7px;top:50%;transform:translateY(-50%);font-size:11px;font-weight:700;color:#92400E;pointer-events:none">₽</span>'
|
||
+'</div></div>'
|
||
// Кнопка "Показать клиенту"
|
||
+'<button onclick="_showCalcClient()" class="btn-secondary" style="margin-top:10px">📱 Показать клиенту</button>'
|
||
// AI проверка договора
|
||
+'<button onclick="_aiContractCheck()" style="width:100%;margin-top:8px;padding:11px;border-radius:12px;border:1.5px solid rgba(139,92,246,.3);background:rgba(139,92,246,.06);color:#7C3AED;font-size:13px;font-weight:700;cursor:pointer;display:flex;align-items:center;justify-content:center;gap:7px"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/></svg>AI проверка договора</button>'
|
||
+(window._aiContractResult ? _renderAIContract(window._aiContractResult) : '')
|
||
+'</div>';
|
||
|
||
var backSvg='<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M15 18l-6-6 6-6"/></svg>';
|
||
var callSvg='<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22 16.92v3a2 2 0 01-2.18 2 19.79 19.79 0 01-16.74-16.74A2 2 0 015 3h3a2 2 0 012 1.72 12.84 12.84 0 00.7 2.81 2 2 0 01-.45 2.11L9.09 10.91a16 16 0 006.99 6.99l1.27-1.27a2 2 0 012.11-.45c.907.339 1.85.573 2.81.7A2 2 0 0122 16.92z"/></svg>';
|
||
|
||
return '<div class="page">'
|
||
+'<div class="page-header">'
|
||
+'<button class="back-btn" onclick="document.getElementById(\'screen\').innerHTML=renderScreen(\'manager_home\');document.getElementById(\'nav\').innerHTML=navBar()">'+backSvg+'</button>'
|
||
+'<div style="flex:1"><div style="font-size:15px;font-weight:700;color:var(--ink)">'+o.client+'</div>'
|
||
+'<div style="font-size:11px;color:var(--muted)">'+o.label+'</div></div>'
|
||
+'<button class="back-btn" onclick="_toast(\'📞 '+o.phone+'\',\'#003E7E\')">'+callSvg+'</button>'
|
||
+'</div>'
|
||
+'<div style="padding:14px 16px;background:var(--card);border-bottom:1px solid rgba(0,0,0,.06)">'
|
||
+'<div class="pipeline">'+pip+'</div></div>'
|
||
+'<div style="padding:16px">'+blk+techHtml+roomHtml+zovHtml+contHtml+'</div>'
|
||
+'</div>';
|
||
}
|
||
function _startTech(){window._wizFromNav=false;window._wizScreen='manager_order';window._techSelection=window._techSelection||[];document.getElementById('screen').innerHTML=renderScreen('manager_tech');}
|
||
function _startWizard(key){window._wiz={type:key,step:0,answers:{},budget:null};window._wizScreen='manager_order';document.getElementById('screen').innerHTML=renderScreen('manager_wizard');}
|
||
|
||
function _confirmTech(){
|
||
var o = window._managerOrders[window._activeOrder];
|
||
var now = new Date();
|
||
var dd = String(now.getDate()).padStart(2,'0');
|
||
var mm = String(now.getMonth()+1).padStart(2,'0');
|
||
var hh = String(now.getHours()).padStart(2,'0');
|
||
var mn = String(now.getMinutes()).padStart(2,'0');
|
||
o.techConfirmed = true;
|
||
o.techConfirmedDate = dd+'.'+mm+'.'+now.getFullYear()+' '+hh+':'+mn;
|
||
// Advance stage to Технолог (4) if still on Техника (3)
|
||
if(o.stage===3) o.stage=4;
|
||
// Clear tech blocker
|
||
if(o.blocker && o.blocker.indexOf('Техника')>=0){ o.blocker=null; o.techNote=''; }
|
||
_toast('✅ Спецификация передана технологу','var(--success)');
|
||
setTimeout(function(){
|
||
document.getElementById('screen').innerHTML=renderScreen('manager_order');
|
||
document.getElementById('nav').innerHTML=navBar();
|
||
},1000);
|
||
}
|
||
|
||
// ── ZOV Factory helpers ───────────────────────────────────────────────────────
|
||
function _zovStatusInfo(code){
|
||
var map={
|
||
'accepted': {color:'#2563EB',bg:'#EFF6FF', icon:'📋'},
|
||
'production':{color:'#D97706',bg:'#FFFBEB', icon:'⚙️'},
|
||
'assembly': {color:'#7C3AED',bg:'#F5F3FF', icon:'🔧'},
|
||
'ready': {color:'#059669',bg:'#F0FDF4', icon:'✅'},
|
||
'shipped': {color:'#0F766E',bg:'#F0FDFA', icon:'🚚'}
|
||
};
|
||
return map[code]||{color:'#8A94A6',bg:'#F9FAFB',icon:'❓'};
|
||
}
|
||
function _updateZovContract(oi,val){
|
||
window._managerOrders[oi].zovContract=val;
|
||
var inp=document.getElementById('inp_zov_'+oi);
|
||
if(inp) inp.style.borderColor=val?'var(--accent)':'#E2E8F0';
|
||
}
|
||
function _refreshZovStatus(oi){
|
||
var o=window._managerOrders[oi];
|
||
if(!o.zovContract){_toast('Введите номер договора ЗОВ','var(--warn)');return;}
|
||
_toast('🔄 Запрашиваю статус с zovofficial.com…','var(--accent)');
|
||
setTimeout(function(){
|
||
var codes=['accepted','production','assembly','ready','shipped'];
|
||
var curr=o.zovStatus?codes.indexOf(o.zovStatus.code):-1;
|
||
var next=codes[Math.min(curr+1,codes.length-1)];
|
||
var info=_zovStatusInfo(next);
|
||
var pcts={accepted:10,production:45,assembly:70,ready:90,shipped:100};
|
||
var details={
|
||
accepted:'Заявка принята фабрикой. Производство запланировано.',
|
||
production:'Корпуса готовы. Фасады и столешница в работе.',
|
||
assembly:'Все комплектующие готовы. Идёт финальная сборка.',
|
||
ready:'Готов к отгрузке. Согласуйте дату доставки.',
|
||
shipped:'Отгружен. Транспорт в пути.'
|
||
};
|
||
var now=new Date();
|
||
var d=String(now.getDate()).padStart(2,'0')+'.'+String(now.getMonth()+1).padStart(2,'0')+'.'+now.getFullYear();
|
||
var t=String(now.getHours()).padStart(2,'0')+':'+String(now.getMinutes()).padStart(2,'0');
|
||
o.zovStatus={code:next,label:info.icon+' '+['Принят','В производстве','Сборка','Готов к отгрузке','Отгружен'][codes.indexOf(next)],detail:details[next],date:d+' · '+t,pct:pcts[next]||0};
|
||
_toast('✅ Статус обновлён','var(--success)');
|
||
document.getElementById('screen').innerHTML=renderScreen('manager_order');
|
||
},1400);
|
||
}
|
||
|
||
// ── Rooms helpers ─────────────────────────────────────────────────────────────
|
||
function _toggleRoom(oi,room){
|
||
var o=window._managerOrders[oi];
|
||
var idx=o.rooms.indexOf(room);
|
||
if(idx>=0) o.rooms.splice(idx,1);
|
||
else o.rooms.push(room);
|
||
document.getElementById('screen').innerHTML=renderScreen('manager_order');
|
||
}
|
||
|
||
function _startOwnTech(key){
|
||
window._ownTechKey = key;
|
||
window._ownTechMode = 'choose';
|
||
window._ownTechData = {brand:'',model:'',dims:''};
|
||
document.getElementById('screen').innerHTML = renderScreen('manager_own_tech');
|
||
}
|
||
|
||
// ── OWN TECH SCREEN ──────────────────────────────────────────────────────────
|
||
window._ownTechKey = window._ownTechKey || null;
|
||
window._ownTechMode = window._ownTechMode || 'choose';
|
||
window._ownTechData = window._ownTechData || {brand:'',model:'',dims:''};
|
||
|
||
var _ownTechRecognized = {
|
||
oven: {brand:'Gorenje', model:'BO74EB', dims:'590×595 мм'},
|
||
cooktop: {brand:'Electrolux', model:'EHH96340IK', dims:'760×510 мм'},
|
||
fridge: {brand:'Samsung', model:'RB38T600FSA', dims:'595×2000 мм'},
|
||
hood: {brand:'Elica', model:'STRIP IX/A/60', dims:'590 мм'},
|
||
dishwasher:{brand:'Bosch', model:'SMS25AI01R', dims:'598×598 мм'},
|
||
micro: {brand:'Samsung', model:'MS23K3513AW', dims:'382×290 мм'},
|
||
coffee: {brand:'DeLonghi', model:'ECAM350.55.B', dims:'438×350 мм'},
|
||
steamer: {brand:'Miele', model:'DGC 7440', dims:'590×455 мм'},
|
||
freezer: {brand:'Liebherr', model:'GN 3023', dims:'595×1780 мм'},
|
||
wine: {brand:'Liebherr', model:'WKes 553', dims:'595×870 мм'},
|
||
};
|
||
|
||
function _ownTechHints(wkey) {
|
||
var m = {
|
||
oven: {brand:'Bosch, Gorenje, Hansa', model:'BO74EB, HBF534ES0R', dims:'590×595 мм'},
|
||
cooktop: {brand:'Electrolux, Bosch, Siemens',model:'EHH96340IK, PIV6…', dims:'760×510 мм'},
|
||
fridge: {brand:'Samsung, LG, Haier', model:'RB38T600FSA', dims:'595×2000 мм'},
|
||
hood: {brand:'Elica, Bosch, Krona', model:'STRIP IX/A/60', dims:'590 мм'},
|
||
dishwasher:{brand:'Bosch, Electrolux, Beko', model:'SMS25AI01R', dims:'598×598 мм'},
|
||
micro: {brand:'Samsung, LG, Panasonic', model:'MS23K3513AW', dims:'382×290 мм'},
|
||
coffee: {brand:'DeLonghi, Krups, Philips', model:'ECAM350.55.B', dims:'438×350 мм'},
|
||
steamer: {brand:'Miele, Smeg, Electrolux', model:'DGC 7440', dims:'590×455 мм'},
|
||
freezer: {brand:'Liebherr, Bosch, Indesit', model:'GN 3023', dims:'595×1780 мм'},
|
||
wine: {brand:'Liebherr, La Sommeliere', model:'WKes 553', dims:'595×870 мм'},
|
||
};
|
||
return m[wkey] || {};
|
||
}
|
||
|
||
function _ownTechFormHtml(brand, model, dims, nicheDef, hints) {
|
||
hints = hints || {};
|
||
// dims: если найдено — показываем как readonly-чип, иначе скрытый placeholder
|
||
var dimsBlock = dims
|
||
? '<div style="background:#F0FDF4;border:1.5px solid #86EFAC;border-radius:12px;padding:11px 14px;margin-bottom:14px;display:flex;align-items:center;gap:10px">'
|
||
+'<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#15803D" stroke-width="2.5"><polyline points="20 6 9 17 4 12"/></svg>'
|
||
+'<div><div style="font-size:11px;font-weight:700;color:#15803D;text-transform:uppercase;letter-spacing:.05em">Размер ниши</div>'
|
||
+'<div style="font-size:14px;font-weight:600;color:#166534;margin-top:1px">'+dims+'</div></div>'
|
||
+'<span style="margin-left:auto;font-size:10px;color:#15803D;background:rgba(21,128,61,.1);padding:2px 8px;border-radius:20px;font-weight:700">из каталога</span>'
|
||
+'</div>'
|
||
: '<div id="ot_dims_block" style="background:rgba(0,62,126,.04);border:1.5px dashed rgba(0,62,126,.2);border-radius:12px;padding:11px 14px;margin-bottom:14px;display:flex;align-items:center;gap:10px">'
|
||
+'<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="var(--muted)" stroke-width="2"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>'
|
||
+'<div style="font-size:13px;color:var(--muted)">Размер ниши подтянется автоматически<br>после ввода марки и модели</div>'
|
||
+'</div>';
|
||
return '<div style="background:var(--card);border-radius:16px;padding:16px 16px 4px;margin-bottom:14px">'
|
||
+'<div style="margin-bottom:14px">'
|
||
+'<div style="font-size:11px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:6px">Марка</div>'
|
||
+'<input id="ot_brand" type="text" value="'+brand+'" placeholder="'+(hints.brand||'Bosch, Samsung, Gorenje…')+'"'
|
||
+' style="width:100%;border:1.5px solid #E2E8F0;border-radius:10px;padding:11px 12px;font-size:14px;color:var(--ink);outline:none;box-sizing:border-box;background:#fff"'
|
||
+' onfocus="this.style.borderColor=\'var(--accent)\'" onblur="this.style.borderColor=\'#E2E8F0\';_ownTechTryLookup()">'
|
||
+'</div>'
|
||
+'<div style="margin-bottom:14px">'
|
||
+'<div style="font-size:11px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:6px">Модель / артикул</div>'
|
||
+'<input id="ot_model" type="text" value="'+model+'" placeholder="'+(hints.model||'артикул или название')+'"'
|
||
+' style="width:100%;border:1.5px solid #E2E8F0;border-radius:10px;padding:11px 12px;font-size:14px;color:var(--ink);outline:none;box-sizing:border-box;background:#fff"'
|
||
+' onfocus="this.style.borderColor=\'var(--accent)\'" onblur="this.style.borderColor=\'#E2E8F0\';_ownTechTryLookup()">'
|
||
+'</div>'
|
||
+dimsBlock
|
||
+'</div>'
|
||
+'<button onclick="_saveOwnTechToOrder()" style="width:100%;padding:14px;background:var(--accent);color:#fff;border:none;border-radius:12px;font-size:15px;font-weight:700;cursor:pointer">Сохранить в заказ →</button>';
|
||
}
|
||
|
||
// Имитация поиска ниши по марке+модели
|
||
var _dimsCatalog = {
|
||
'gorenje bo74eb': '590×595 мм', 'gorenje': '590×595 мм',
|
||
'electrolux ehh': '760×510 мм', 'electrolux': '760×510 мм',
|
||
'samsung rb38': '595×2000 мм','samsung': '595×2000 мм',
|
||
'elica strip': '590 мм', 'elica': '590 мм',
|
||
'bosch sms': '598×598 мм', 'bosch hxa': '595×595 мм',
|
||
'bosch hbf': '595×595 мм', 'bosch': '595×595 мм',
|
||
'samsung ms23': '382×290 мм', 'delonghi ecam': '438×350 мм',
|
||
'miele dgc': '590×455 мм', 'liebherr gn': '595×1780 мм',
|
||
'liebherr wkes': '595×870 мм',
|
||
};
|
||
function _ownTechTryLookup() {
|
||
var brand = ((document.getElementById('ot_brand')||{}).value||'').trim().toLowerCase();
|
||
var model = ((document.getElementById('ot_model')||{}).value||'').trim().toLowerCase();
|
||
if (!brand && !model) return;
|
||
var key = (brand + ' ' + model).trim();
|
||
var found = null;
|
||
// Поиск по убыванию специфичности
|
||
Object.keys(_dimsCatalog).forEach(function(k){ if(key.indexOf(k)>=0) found = _dimsCatalog[k]; });
|
||
if (!found && brand) Object.keys(_dimsCatalog).forEach(function(k){ if(brand.indexOf(k)>=0||k.indexOf(brand)>=0) found = _dimsCatalog[k]; });
|
||
var block = document.getElementById('ot_dims_block');
|
||
if (found && block) {
|
||
block.style.transition = 'all .3s';
|
||
block.style.background = 'rgba(21,128,61,.06)';
|
||
block.style.borderColor = '#86EFAC';
|
||
block.style.borderStyle = 'solid';
|
||
block.innerHTML = '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#15803D" stroke-width="2.5"><polyline points="20 6 9 17 4 12"/></svg>'
|
||
+'<div><div style="font-size:11px;font-weight:700;color:#15803D;text-transform:uppercase;letter-spacing:.05em">Размер ниши</div>'
|
||
+'<div style="font-size:14px;font-weight:600;color:#166534;margin-top:1px">'+found+'</div></div>'
|
||
+'<span style="margin-left:auto;font-size:10px;color:#15803D;background:rgba(21,128,61,.1);padding:2px 8px;border-radius:20px;font-weight:700">из каталога</span>';
|
||
window._ownTechFoundDims = found;
|
||
}
|
||
}
|
||
|
||
function screenOwnTech() {
|
||
var wkey = window._ownTechKey || 'oven';
|
||
var cfg = WIZ[wkey] || WIZ['oven'];
|
||
var mode = window._ownTechMode || 'choose';
|
||
var bk = '<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M15 18l-6-6 6-6"/></svg>';
|
||
var backChoose = "window._ownTechMode='choose';document.getElementById('screen').innerHTML=renderScreen('manager_own_tech')";
|
||
var backOrder = "document.getElementById('screen').innerHTML=renderScreen('manager_order')";
|
||
|
||
// ── CHOOSE ──
|
||
var icoCam = '<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z"/><circle cx="12" cy="13" r="4"/></svg>';
|
||
var icoPen = '<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/></svg>';
|
||
var icoChv = '<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="var(--muted)" stroke-width="2.5"><path d="M9 18l6-6-6-6"/></svg>';
|
||
var techHdr = '<div style="background:var(--card);border-radius:14px;padding:12px 14px;margin-bottom:18px;display:flex;align-items:center;gap:12px">'
|
||
+'<div style="width:40px;height:40px;border-radius:10px;background:rgba(0,62,126,.08);display:flex;align-items:center;justify-content:center;flex-shrink:0;color:var(--accent)">'+_techIcon(wkey,22)+'</div>'
|
||
+'<div><div style="font-size:14px;font-weight:700;color:var(--ink)">'+cfg.name+'</div>'
|
||
+'<div style="font-size:12px;color:var(--muted)">Ниша: '+cfg.niche+'</div></div></div>';
|
||
|
||
var v = window._ownTechVariant || 'A';
|
||
|
||
if (mode === 'choose') {
|
||
var body = '';
|
||
|
||
// ── VARIANT A: линейные иконки, приоритет через размер текста ──
|
||
if (v==='A') {
|
||
body = techHdr
|
||
// Primary
|
||
+'<div style="background:var(--accent);border-radius:16px;padding:18px;margin-bottom:10px;cursor:pointer" onclick="_ownTechPickPhoto()">'
|
||
+'<div style="display:flex;align-items:center;gap:14px">'
|
||
+'<div style="width:44px;height:44px;border-radius:12px;background:rgba(255,255,255,.15);display:flex;align-items:center;justify-content:center;flex-shrink:0;color:#fff">'+icoCam+'</div>'
|
||
+'<div style="flex:1"><div style="font-size:15px;font-weight:700;color:#fff">По фото</div>'
|
||
+'<div style="font-size:12px;color:rgba(255,255,255,.75);margin-top:3px;line-height:1.4">AI распознаёт марку и модель<br>по снимку шильдика</div></div>'
|
||
+'<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="rgba(255,255,255,.6)" stroke-width="2.5"><path d="M9 18l6-6-6-6"/></svg>'
|
||
+'</div></div>'
|
||
// Secondary
|
||
+'<div style="background:var(--card);border-radius:16px;padding:16px 18px;cursor:pointer;border:1.5px solid rgba(0,62,126,.1)" onclick="_ownTechManual()">'
|
||
+'<div style="display:flex;align-items:center;gap:14px">'
|
||
+'<div style="width:40px;height:40px;border-radius:10px;background:rgba(0,62,126,.07);display:flex;align-items:center;justify-content:center;flex-shrink:0;color:var(--accent)">'+icoPen+'</div>'
|
||
+'<div style="flex:1"><div style="font-size:14px;font-weight:600;color:var(--ink)">Ввести вручную</div>'
|
||
+'<div style="font-size:12px;color:var(--muted);margin-top:2px">Марка и модель — ниша найдётся автоматически</div></div>'
|
||
+icoChv+'</div></div>';
|
||
}
|
||
|
||
// ── VARIANT B: две равные карточки-плитки 2×1 ──
|
||
if (v==='B') {
|
||
var cardBase = 'flex:1;border-radius:16px;padding:20px 14px;cursor:pointer;text-align:center;display:flex;flex-direction:column;align-items:center;gap:10px';
|
||
body = techHdr
|
||
+'<div style="display:flex;gap:10px;margin-bottom:10px">'
|
||
+'<div style="'+cardBase+';background:var(--accent)" onclick="_ownTechPickPhoto()">'
|
||
+'<div style="width:52px;height:52px;border-radius:14px;background:rgba(255,255,255,.18);display:flex;align-items:center;justify-content:center;color:#fff">'+icoCam+'</div>'
|
||
+'<div style="font-size:14px;font-weight:700;color:#fff">По фото</div>'
|
||
+'<div style="font-size:11px;color:rgba(255,255,255,.7);line-height:1.4">AI читает шильдик автоматически</div>'
|
||
+'</div>'
|
||
+'<div style="'+cardBase+';background:var(--card);border:1.5px solid rgba(0,62,126,.12)" onclick="_ownTechManual()">'
|
||
+'<div style="width:52px;height:52px;border-radius:14px;background:rgba(0,62,126,.07);display:flex;align-items:center;justify-content:center;color:var(--accent)">'+icoPen+'</div>'
|
||
+'<div style="font-size:14px;font-weight:700;color:var(--ink)">Вручную</div>'
|
||
+'<div style="font-size:11px;color:var(--muted);line-height:1.4">Марка, модель, размер ниши</div>'
|
||
+'</div></div>';
|
||
}
|
||
|
||
// ── VARIANT C: минимализм — без иконок, только текст ──
|
||
if (v==='C') {
|
||
body = techHdr
|
||
+'<div style="background:var(--accent);border-radius:16px;padding:20px 18px;margin-bottom:10px;cursor:pointer" onclick="_ownTechPickPhoto()">'
|
||
+'<div style="font-size:16px;font-weight:700;color:#fff;margin-bottom:4px">По фото шильдика</div>'
|
||
+'<div style="font-size:13px;color:rgba(255,255,255,.75);line-height:1.5">Сфотографируйте маркировку — AI определит марку и модель автоматически</div>'
|
||
+'<div style="display:inline-flex;align-items:center;gap:5px;margin-top:10px;background:rgba(255,255,255,.2);border-radius:20px;padding:4px 12px">'
|
||
+'<span style="font-size:12px;color:#fff;font-weight:600">Рекомендуем</span></div>'
|
||
+'</div>'
|
||
+'<div style="display:flex;align-items:center;justify-content:center;gap:10px;margin:12px 0">'
|
||
+'<div style="flex:1;height:1px;background:var(--bg)"></div>'
|
||
+'<span style="font-size:11px;color:var(--muted);font-weight:600">или</span>'
|
||
+'<div style="flex:1;height:1px;background:var(--bg)"></div></div>'
|
||
+'<div style="background:var(--card);border-radius:14px;padding:14px 18px;cursor:pointer;display:flex;align-items:center;justify-content:space-between" onclick="_ownTechManual()">'
|
||
+'<div><div style="font-size:14px;font-weight:600;color:var(--ink)">Ввести вручную</div>'
|
||
+'<div style="font-size:12px;color:var(--muted);margin-top:2px">Марка, модель, размер ниши</div></div>'
|
||
+icoChv+'</div>';
|
||
}
|
||
|
||
return '<div class="page">'
|
||
+'<div class="page-header"><button class="back-btn" onclick="'+backOrder+'">'+bk+'</button><h2>Своя техника</h2></div>'
|
||
+'<div style="padding:16px">'+body+'</div></div>';
|
||
}
|
||
|
||
// ── PHOTO UPLOAD ──
|
||
if (mode === 'photo') {
|
||
return '<div class="page">'
|
||
+'<div class="page-header"><button class="back-btn" onclick="'+backChoose+'">'+bk+'</button><h2>Фото техники</h2></div>'
|
||
+'<div style="padding:16px">'
|
||
+'<div style="font-size:13px;color:var(--muted);text-align:center;margin-bottom:16px;line-height:1.5">'
|
||
+'Сфотографируйте шильдик с маркировкой<br>или переднюю панель техники</div>'
|
||
+'<label style="display:block;border:2px dashed #CBD5E1;border-radius:16px;padding:36px 20px;cursor:pointer;text-align:center;background:var(--card)">'
|
||
+'<input type="file" accept="image/*" capture="environment" style="display:none" onchange="_ownTechPhotoSelected(this)">'
|
||
+'<div style="font-size:44px;margin-bottom:10px">📷</div>'
|
||
+'<div style="font-size:15px;font-weight:700;color:var(--accent)">Выбрать фото</div>'
|
||
+'<div style="font-size:12px;color:var(--muted);margin-top:4px">или сделать снимок камерой</div>'
|
||
+'</label>'
|
||
+'<div style="font-size:12px;color:var(--muted);text-align:center;margin-top:16px;line-height:1.6">'
|
||
+'Лучший результат — чёткое фото шильдика<br>с полным артикулом модели</div>'
|
||
+'</div></div>';
|
||
}
|
||
|
||
// ── SCANNING ──
|
||
if (mode === 'scan') {
|
||
var photoSrc = window._ownTechPhotoUrl || (_photoUrls[wkey] || '');
|
||
return '<div class="page">'
|
||
+'<div class="page-header"><button class="back-btn" onclick="'+backChoose+'">'+bk+'</button><h2>Распознавание</h2></div>'
|
||
+'<div style="padding:16px;text-align:center">'
|
||
+'<div style="width:130px;height:130px;border-radius:16px;overflow:hidden;margin:20px auto 16px;border:2px solid rgba(0,62,126,.12);background:#F1F5F9">'
|
||
+(photoSrc?'<img src="'+photoSrc+'" style="width:100%;height:100%;object-fit:cover">':'<div style="width:100%;height:100%;display:flex;align-items:center;justify-content:center;font-size:44px">'+cfg.emoji+'</div>')
|
||
+'</div>'
|
||
+'<div style="font-size:15px;font-weight:700;color:var(--ink);margin-bottom:6px">Анализируем фото…</div>'
|
||
+'<div style="font-size:13px;color:var(--muted);margin-bottom:24px">AI определяет модель и характеристики</div>'
|
||
+'<div style="background:#E2E8F0;border-radius:8px;height:5px;width:200px;margin:0 auto;overflow:hidden">'
|
||
+'<div id="scanBar" style="background:var(--accent);height:100%;width:0;border-radius:8px;transition:width 1.3s cubic-bezier(.4,0,.2,1)"></div>'
|
||
+'</div>'
|
||
+'<div id="scanLabel" style="font-size:12px;color:var(--muted);margin-top:10px">Чтение маркировки…</div>'
|
||
+'</div>'
|
||
+'<script>'
|
||
+'setTimeout(function(){document.getElementById("scanBar").style.width="60%";document.getElementById("scanLabel").textContent="Поиск в базе…";},50);'
|
||
+'setTimeout(function(){document.getElementById("scanBar").style.width="100%";document.getElementById("scanLabel").textContent="Модель найдена ✓";},1000);'
|
||
+'setTimeout(function(){_ownTechShowRecognized();},1600);'
|
||
+'<\/script>';
|
||
}
|
||
|
||
// ── CONFIRM (after photo) ──
|
||
if (mode === 'confirm') {
|
||
var d = window._ownTechData;
|
||
return '<div class="page">'
|
||
+'<div class="page-header"><button class="back-btn" onclick="'+backChoose+'">'+bk+'</button><h2>Проверьте данные</h2></div>'
|
||
+'<div style="padding:16px">'
|
||
+'<div style="background:#F0FDF4;border:1.5px solid #86EFAC;border-radius:13px;padding:13px 15px;margin-bottom:18px;display:flex;align-items:center;gap:10px">'
|
||
+'<div style="font-size:20px">✅</div>'
|
||
+'<div><div style="font-size:13px;font-weight:700;color:#15803D">Модель распознана</div>'
|
||
+'<div style="font-size:12px;color:#166534">Проверьте и при необходимости скорректируйте</div></div></div>'
|
||
+_ownTechFormHtml(d.brand, d.model, d.dims, cfg.niche)
|
||
+'</div></div>';
|
||
}
|
||
|
||
// ── MANUAL FORM ──
|
||
if (mode === 'manual') {
|
||
var hints = _ownTechHints(wkey);
|
||
return '<div class="page">'
|
||
+'<div class="page-header"><button class="back-btn" onclick="'+backChoose+'">'+bk+'</button><h2>Своя техника</h2></div>'
|
||
+'<div style="padding:16px">'
|
||
+'<div style="font-size:13px;color:var(--muted);margin-bottom:14px">'+cfg.name+' · Ниша '+cfg.niche+'</div>'
|
||
+_ownTechFormHtml('', '', '', cfg.niche, hints)
|
||
+'</div></div>';
|
||
}
|
||
|
||
return renderScreen('manager_order');
|
||
}
|
||
|
||
function _ownTechPickPhoto() {
|
||
window._ownTechMode = 'photo';
|
||
document.getElementById('screen').innerHTML = renderScreen('manager_own_tech');
|
||
}
|
||
|
||
function _ownTechManual() {
|
||
window._ownTechMode = 'manual';
|
||
document.getElementById('screen').innerHTML = renderScreen('manager_own_tech');
|
||
}
|
||
|
||
function _ownTechPhotoSelected(input) {
|
||
if (!input.files || !input.files[0]) return;
|
||
var reader = new FileReader();
|
||
reader.onload = function(e) {
|
||
window._ownTechPhotoUrl = e.target.result;
|
||
window._ownTechMode = 'scan';
|
||
document.getElementById('screen').innerHTML = renderScreen('manager_own_tech');
|
||
};
|
||
reader.readAsDataURL(input.files[0]);
|
||
}
|
||
|
||
function _ownTechShowRecognized() {
|
||
var wkey = window._ownTechKey || 'oven';
|
||
var rec = _ownTechRecognized[wkey] || {brand:'',model:'',dims:''};
|
||
window._ownTechData = {brand: rec.brand, model: rec.model, dims: rec.dims};
|
||
window._ownTechMode = 'confirm';
|
||
document.getElementById('screen').innerHTML = renderScreen('manager_own_tech');
|
||
}
|
||
|
||
function _saveOwnTechToOrder() {
|
||
var brand = (document.getElementById('ot_brand').value||'').trim();
|
||
var model = (document.getElementById('ot_model').value||'').trim();
|
||
// dims: из автопоиска или из confirm-данных
|
||
var dims = window._ownTechFoundDims || (window._ownTechData && window._ownTechData.dims) || '';
|
||
if (!brand && !model) { _toast('Укажите марку или модель','var(--danger)'); return; }
|
||
var o = window._managerOrders[window._activeOrder];
|
||
var wkey = window._ownTechKey;
|
||
var ti = (o.tech||[]).findIndex(function(t){ return t.wkey===wkey; });
|
||
if (ti >= 0) {
|
||
o.tech[ti].status = 'done';
|
||
o.tech[ti].brand = [brand, model].filter(Boolean).join(' ');
|
||
o.tech[ti].model = model;
|
||
o.tech[ti].dims = dims;
|
||
o.tech[ti].source = 'own';
|
||
}
|
||
var waitLeft = (o.tech||[]).filter(function(t){ return t.status==='wait'; }).length;
|
||
if (waitLeft===0 && o.blocker && o.blocker.indexOf('Техника')>=0) { o.blocker=null; o.techNote=''; }
|
||
_toast('✅ '+(brand||model)+' сохранён в заказ','#15803D');
|
||
setTimeout(function(){
|
||
window._ownTechKey = null;
|
||
window._ownTechMode = 'choose';
|
||
document.getElementById('screen').innerHTML = renderScreen('manager_order');
|
||
document.getElementById('nav').innerHTML = navBar();
|
||
}, 1100);
|
||
}
|
||
|
||
// ── TECH MULTI-SELECT ────────────────────────────────────────────────────────
|
||
function screenTech() {
|
||
var sel = window._techSelection || [];
|
||
var o = window._managerOrders[window._activeOrder];
|
||
var existing = (o && o.tech) ? o.tech.map(function(t){return t.wkey;}) : [];
|
||
var cnt = sel.length;
|
||
|
||
var grid = TECH_TYPES.map(function(t){
|
||
var isSel = sel.indexOf(t.key) >= 0;
|
||
var isEx = existing.indexOf(t.key) >= 0;
|
||
var cls = 'tech-pick-card'+(isSel?' sel':isEx?' exists':'');
|
||
var check = isSel
|
||
? '<div style="position:absolute;top:7px;right:7px;width:18px;height:18px;background:#fff;border-radius:50%;display:flex;align-items:center;justify-content:center"><svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="#003E7E" stroke-width="3.5"><polyline points="20 6 9 17 4 12"/></svg></div>'
|
||
: (isEx ? '<div style="position:absolute;top:7px;right:7px;width:18px;height:18px;background:#CBD5E1;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:9px">✓</div>' : '');
|
||
var nameCol = isSel ? '#fff' : isEx ? 'var(--muted)' : 'var(--ink)';
|
||
return '<div class="'+cls+'" onclick="'+(isEx?'':'_techToggle(\''+t.key+'\')')+'">'
|
||
+check
|
||
+'<div style="width:36px;height:36px;margin:0 auto 8px;color:'+(isSel?'#fff':isEx?'#94A3B8':'var(--accent)')+'">'+(TECH_ICONS[t.key]||'')+'</div>'
|
||
+'<div style="font-size:12px;font-weight:700;color:'+nameCol+';line-height:1.3">'+t.label+'</div>'
|
||
+(isEx?'<div style="font-size:10px;color:#94A3B8;margin-top:3px">в списке</div>':'')
|
||
+'</div>';
|
||
}).join('');
|
||
|
||
var bk='<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M15 18l-6-6 6-6"/></svg>';
|
||
var cntLabel = cnt===0 ? 'Выберите позиции' : 'Добавить '+cnt+' позици'+(cnt===1?'ю':cnt<5?'и':'й')+' →';
|
||
|
||
return '<div class="page">'
|
||
+'<div class="page-header"><button class="back-btn" onclick="window._techSelection=[];document.getElementById(\'screen\').innerHTML=renderScreen(window._wizScreen||\'manager_order\')">'+bk+'</button>'
|
||
+'<h2>Техника</h2>'
|
||
+(cnt>0?'<div style="background:var(--accent);color:#fff;font-size:11px;font-weight:700;padding:3px 9px;border-radius:12px">'+cnt+'</div>':'')
|
||
+'</div>'
|
||
+'<div style="padding:10px 16px 4px"><div style="font-size:13px;color:var(--muted)">Выберите всю встраиваемую технику для этого проекта</div></div>'
|
||
+'<div style="padding:12px 16px;display:grid;grid-template-columns:repeat(3,1fr);gap:10px">'+grid+'</div>'
|
||
+'<div style="padding:0 16px 24px"><button class="btn-primary'+(cnt>0?' btn-confirm':'')+'" '+(cnt===0?'disabled':'')+' onclick="_techAddSelected()">'+cntLabel+'</button></div>'
|
||
+'</div>';
|
||
}
|
||
function _techToggle(key){
|
||
window._techSelection = window._techSelection || [];
|
||
var i = window._techSelection.indexOf(key);
|
||
if(i>=0) window._techSelection.splice(i,1); else window._techSelection.push(key);
|
||
document.getElementById('screen').innerHTML = renderScreen('manager_tech');
|
||
}
|
||
function _techAddSelected(){
|
||
var o = window._managerOrders[window._activeOrder];
|
||
var sel = window._techSelection || [];
|
||
sel.forEach(function(key){
|
||
var tt = TECH_TYPES.filter(function(t){return t.key===key;})[0];
|
||
if(!tt) return;
|
||
if(o.tech.some(function(t){return t.wkey===key;})) return;
|
||
o.tech.push({name:tt.label, wkey:key, status:'wait', dims:'', brand:'', source:null});
|
||
});
|
||
window._techSelection = [];
|
||
document.getElementById('screen').innerHTML = renderScreen('manager_order');
|
||
document.getElementById('nav').innerHTML = navBar();
|
||
}
|
||
function _startClientPick(key){
|
||
window._clientPickKey = key;
|
||
window._wizScreen='manager_order';
|
||
document.getElementById('screen').innerHTML=renderScreen('manager_tech_client');
|
||
document.getElementById('nav').innerHTML=navBar();
|
||
}
|
||
function _techClientRemind(key){
|
||
_toast('📲 Напоминание отправлено в TG-кабинет клиента','var(--accent)');
|
||
}
|
||
function _acceptClientChoice(oi,key){
|
||
var o=window._managerOrders[oi];
|
||
var t=o.tech.filter(function(x){return x.wkey===key;})[0];
|
||
if(t){t.status='done';}
|
||
_toast('✅ Выбор принят и зафиксирован','var(--success)');
|
||
document.getElementById('screen').innerHTML=renderScreen('manager_order');
|
||
}
|
||
function _rejectClientChoice(oi,key){
|
||
var o=window._managerOrders[oi];
|
||
var t=o.tech.filter(function(x){return x.wkey===key;})[0];
|
||
if(t){t.status='waiting_client'; t.brand=''; t.model=''; t.dims='';}
|
||
_toast('↩ Клиенту отправлена просьба выбрать снова','var(--warn)');
|
||
document.getElementById('screen').innerHTML=renderScreen('manager_order');
|
||
}
|
||
function screenTechClient(){
|
||
var key=window._clientPickKey||'oven';
|
||
var tt=TECH_TYPES.filter(function(t){return t.key===key;})[0]||{key:key,label:key};
|
||
var bk='<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M15 18l-6-6 6-6"/></svg>';
|
||
var optCard=function(icon,title,sub,onclick){
|
||
return '<div onclick="'+onclick+'" style="background:var(--card);border-radius:16px;padding:18px 16px;border:2px solid #E2E8F0;cursor:pointer;transition:all .15s;margin-bottom:12px;display:flex;gap:14px;align-items:flex-start">'
|
||
+'<div style="font-size:26px;flex-shrink:0">'+icon+'</div>'
|
||
+'<div><div style="font-size:15px;font-weight:700;color:var(--ink);margin-bottom:4px">'+title+'</div>'
|
||
+'<div style="font-size:12px;color:var(--muted);line-height:1.5">'+sub+'</div></div>'
|
||
+'</div>';
|
||
};
|
||
return '<div class="page">'
|
||
+'<div class="page-header"><button class="back-btn" onclick="document.getElementById(\'screen\').innerHTML=renderScreen(\'manager_order\')">'+bk+'</button>'
|
||
+'<div style="flex:1"><div style="font-size:11px;color:var(--muted);font-weight:600">КЛИЕНТ ВЫБИРАЕТ</div>'
|
||
+'<div style="font-size:16px;font-weight:700;color:var(--ink)">'+tt.label+'</div></div></div>'
|
||
+'<div style="padding:20px 16px">'
|
||
+'<div style="font-size:13px;color:var(--muted);margin-bottom:16px">Как клиент будет определяться с техникой?</div>'
|
||
+optCard('👤','Клиент выберет сам','Отправим уведомление в TG-кабинет. Клиент укажет марку, модель или бюджет — мы сразу увидим.','_clientPickDirect(\''+key+'\')')
|
||
+optCard('🤖','По рекомендации AI','Мы подберём 3 варианта через AI, отправим клиенту. Клиент выберет один — и сразу попадёт в спецификацию.','_clientPickAI(\''+key+'\')')
|
||
+'</div></div>';
|
||
}
|
||
function _clientPickDirect(key){
|
||
var o=window._managerOrders[window._activeOrder];
|
||
var t=o.tech.filter(function(x){return x.wkey===key;})[0];
|
||
if(t){t.status='waiting_client';t.source='client';}
|
||
_toast('📲 Уведомление отправлено в TG-кабинет клиента','var(--success)');
|
||
setTimeout(function(){
|
||
document.getElementById('screen').innerHTML=renderScreen('manager_order');
|
||
document.getElementById('nav').innerHTML=navBar();
|
||
},900);
|
||
}
|
||
function _clientPickAI(key){
|
||
window._wiz={type:key,step:0,answers:{},budget:null};
|
||
window._wizClientMode=true;
|
||
window._wizScreen='manager_order';
|
||
document.getElementById('screen').innerHTML=renderScreen('manager_wizard');
|
||
}
|
||
function _saveResultAsClientPick(){
|
||
var o=window._managerOrders[window._activeOrder];
|
||
var key=window._wiz.type;
|
||
var t=o.tech.filter(function(x){return x.wkey===key;})[0];
|
||
if(t){t.status='waiting_client';t.source='client';}
|
||
window._wizClientMode=false;
|
||
_toast('📲 Варианты отправлены клиенту в TG-кабинет','var(--success)');
|
||
setTimeout(function(){
|
||
document.getElementById('screen').innerHTML=renderScreen('manager_order');
|
||
document.getElementById('nav').innerHTML=navBar();
|
||
},900);
|
||
}
|
||
|
||
// ── WIZARD (single-screen filter) ─────────────────────────────────────────────
|
||
function screenWizard() {
|
||
var wiz=window._wiz;
|
||
if(!wiz.type) wiz.type='oven';
|
||
var cfg=WIZ[wiz.type];
|
||
if(!cfg){wiz.type='oven';cfg=WIZ['oven'];}
|
||
|
||
// Parameter rows
|
||
var params=cfg.steps.map(function(sc){
|
||
var sel=wiz.answers[sc.field]||[];
|
||
var chips=sc.opts.map(function(o,i){
|
||
var s=sel.indexOf(i)>=0;
|
||
return '<div class="f-chip'+(s?' sel':'')+'" onclick="_wizToggle(\''+sc.field+'\','+i+','+sc.multi+')">'
|
||
+'<span>'+o.e+'</span><span>'+o.l+'</span></div>';
|
||
}).join('');
|
||
var filled=(sel.length>0);
|
||
return '<div class="f-section">'
|
||
+'<div style="display:flex;align-items:center;gap:6px"><span class="f-label">'+sc.q+'</span>'
|
||
+(filled?'<span style="color:var(--success);font-size:12px">✓</span>':'')
|
||
+(sc.multi?'<span style="font-size:10px;color:var(--muted);margin-left:2px">· несколько</span>':'')
|
||
+'</div>'
|
||
+'<div class="f-row">'+chips+'</div>'
|
||
+'</div>';
|
||
}).join('');
|
||
|
||
// Budget row — 3 compact pills
|
||
var budgets=Object.keys(cfg.budgets).map(function(k){
|
||
var b=cfg.budgets[k];var s=wiz.budget===k;
|
||
return '<div class="bud-pill'+(s?' sel':'')+'" onclick="_wizBudget(\''+k+'\')">'
|
||
+'<div style="font-size:20px;margin-bottom:3px">'+b.icon+'</div>'
|
||
+'<div style="font-size:12px;font-weight:700;color:'+(s?'var(--accent)':'var(--ink)')+'">'+b.label+'</div>'
|
||
+'<div style="font-size:10px;color:var(--muted);margin-top:1px">'+b.range+'</div>'
|
||
+(s?'<div style="font-size:10px;color:var(--accent);font-weight:600;margin-top:2px">'+b.brands.split(',')[0].trim()+'</div>':'')
|
||
+'</div>';
|
||
}).join('');
|
||
|
||
// Filled count for hint
|
||
var filledCount=cfg.steps.filter(function(sc){return (wiz.answers[sc.field]||[]).length>0;}).length;
|
||
var totalParams=cfg.steps.length;
|
||
var allFilled=(filledCount===totalParams)&&(wiz.budget!==null);
|
||
var remaining=(totalParams-filledCount)+(wiz.budget===null?1:0);
|
||
|
||
var hint=allFilled
|
||
?'<div style="font-size:11px;color:var(--success);text-align:center;margin-top:6px;font-weight:600">✓ Все параметры заполнены</div>'
|
||
:'<div style="font-size:11px;color:var(--muted);text-align:center;margin-top:6px">Осталось выбрать: '+remaining+'</div>';
|
||
|
||
var 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="document.getElementById(\'screen\').innerHTML=renderScreen(window._wizScreen||\'manager_order\')">'+bk+'</button>'
|
||
+'<div style="flex:1">'
|
||
+'<div style="font-size:11px;color:var(--muted);font-weight:600">AI ПОДБОР</div>'
|
||
+'<div style="font-size:16px;font-weight:700;color:var(--ink)">'+cfg.emoji+' '+cfg.name+'</div>'
|
||
+'</div>'
|
||
+'<button style="background:none;border:none;font-size:11px;font-weight:700;color:var(--accent);cursor:pointer;padding:0 4px" onclick="document.getElementById(\'screen\').innerHTML=renderScreen(\'manager_tech\')">Сменить</button>'
|
||
+'</div>'
|
||
+'<div style="padding:16px 16px 0">'+params+'</div>'
|
||
+'<div style="margin:0 16px;padding:14px;background:var(--card);border-radius:14px;box-shadow:0 2px 8px rgba(0,0,0,.06)">'
|
||
+'<div class="f-label" style="margin-bottom:8px">Бюджет</div>'
|
||
+'<div style="display:flex;gap:8px">'+budgets+'</div>'
|
||
+'</div>'
|
||
+'<div style="padding:14px 16px 24px">'
|
||
+'<button class="btn-primary'+(allFilled?' btn-confirm':'')+'"'+(allFilled?'':' disabled')+' onclick="document.getElementById(\'screen\').innerHTML=renderScreen(\'manager_result\')">'
|
||
+(allFilled?'Показать подборку →':'Выберите параметры')
|
||
+'</button>'
|
||
+hint
|
||
+'</div>'
|
||
+'</div>';
|
||
}
|
||
function _wizToggle(field,idx,multi){
|
||
var w=window._wiz;
|
||
if(!w.answers[field])w.answers[field]=[];
|
||
var a=w.answers[field],p=a.indexOf(idx);
|
||
if(multi){if(p>=0)a.splice(p,1);else a.push(idx);}else{w.answers[field]=[idx];}
|
||
document.getElementById('screen').innerHTML=renderScreen('manager_wizard');
|
||
}
|
||
function _wizBudget(k){window._wiz.budget=k;document.getElementById('screen').innerHTML=renderScreen('manager_wizard');}
|
||
function _wizNext(){/* legacy no-op */}
|
||
function _wizBack(){document.getElementById('screen').innerHTML=renderScreen(window._wizScreen||'manager_order');}
|
||
|
||
// ── RESULT helpers ────────────────────────────────────────────────────────────
|
||
function _parsePrice(str){ return parseInt((str||'0').replace(/\s|₽/g,''),10)||0; }
|
||
|
||
// Генерируем цены по магазинам — детерминировано от базовой цены
|
||
function _storePrices(basePrice, seed) {
|
||
var s = seed || 0;
|
||
var round100 = function(n){ return Math.round(n/100)*100; };
|
||
var dns = basePrice; // DNS — лучшая цена
|
||
var market = round100(basePrice * (1.02 + (s%7)*0.003));
|
||
var mvideo = round100(basePrice * (1.06 + (s%5)*0.004));
|
||
var eldor = round100(basePrice * (1.10 + (s%3)*0.006));
|
||
var avg = Math.round((dns+market+mvideo+eldor)/4/100)*100;
|
||
return [
|
||
{store:'DNS', price:dns, best:true},
|
||
{store:'Яндекс.Маркет', price:market, best:false},
|
||
{store:'М.Видео', price:mvideo, best:false},
|
||
{store:'Эльдорадо', price:eldor, best:false},
|
||
].map(function(r){ r.avg=avg; return r; });
|
||
}
|
||
|
||
// Реальные фото товаров — сгенерированы через fal.ai FLUX
|
||
var _photoUrls = {
|
||
"oven": "https://v3b.fal.media/files/b/0a9b60e0/DHiwcB4hb0AkKPUIa7MBB.jpg",
|
||
"cooktop": "https://v3b.fal.media/files/b/0a9b60e0/VW1Qn9FyLYwoqgWx2FqcX.jpg",
|
||
"fridge": "https://v3b.fal.media/files/b/0a9b60e1/3_iJPr919KQXbNT989mmQ.jpg",
|
||
"hood": "https://v3b.fal.media/files/b/0a9b60e1/R21oGbTs742cCb__ylRIF.jpg",
|
||
"dishwasher": "https://v3b.fal.media/files/b/0a9b60e1/9tge43kr0OWBo3ehj0VYl.jpg",
|
||
"micro": "https://v3b.fal.media/files/b/0a9b60e1/kdWHKAwKN3uCuNi4JeWF1.jpg",
|
||
"coffee": "https://v3b.fal.media/files/b/0a9b60e1/64x78EcsipIQSdRwYT-_j.jpg",
|
||
"steamer": "https://v3b.fal.media/files/b/0a9b60e1/KUL5zUpbUFSbedUm-Cx03.jpg",
|
||
"freezer": "https://v3b.fal.media/files/b/0a9b60e2/kolWJ2PJK_ePTYLGAcfP0.jpg",
|
||
"wine": "https://v3b.fal.media/files/b/0a9b60e2/UAqXcVIP0DJ1o_tfCjH3y.jpg"
|
||
};
|
||
|
||
// Фото товара — реальное изображение, фоллбэк на эмодзи
|
||
function _photoBox(emoji, bg1, bg2, typeKey) {
|
||
var url = _photoUrls[typeKey];
|
||
if (url) {
|
||
return '<div style="width:90px;height:90px;border-radius:14px;overflow:hidden;flex-shrink:0;background:#F8FAFC;border:1px solid rgba(0,0,0,.07)">'
|
||
+'<img src="'+url+'" alt="" style="width:100%;height:100%;object-fit:cover" onerror="this.parentNode.innerHTML=\'<div style="width:90px;height:90px;display:flex;align-items:center;justify-content:center;font-size:36px">'+emoji+'</div>\'">'
|
||
+'</div>';
|
||
}
|
||
var bg = 'linear-gradient(135deg,'+bg1+','+bg2+')';
|
||
return '<div style="width:90px;height:90px;border-radius:14px;background:'+bg
|
||
+';display:flex;flex-direction:column;align-items:center;justify-content:center;flex-shrink:0">'
|
||
+'<div style="font-size:36px;line-height:1">'+emoji+'</div>'
|
||
+'</div>';
|
||
}
|
||
|
||
// Цветовая схема фото (фоллбэк)
|
||
var _photoColors = {
|
||
oven: ['#e0e7ff','#c7d2fe'], cooktop:['#fef3c7','#fde68a'],
|
||
fridge: ['#e0f2fe','#bae6fd'], hood: ['#f0fdf4','#bbf7d0'],
|
||
dishwasher:['#fff7ed','#fed7aa'], micro: ['#fdf4ff','#f5d0fe'],
|
||
coffee: ['#fef9c3','#fde047'], steamer:['#f0fdfa','#99f6e4'],
|
||
freezer: ['#eff6ff','#bfdbfe'], wine: ['#fdf2f8','#fbcfe8'],
|
||
};
|
||
|
||
// Шкала оценки цена/качество (1–5 звёзд)
|
||
function _valueScore(budgetKey, modelIdx) {
|
||
var base = {base:3.6, mid:4.2, prem:4.7}[budgetKey] || 4.0;
|
||
var adj = [0, -0.2, -0.4][modelIdx] || 0;
|
||
return Math.min(5, Math.max(1, +(base + adj).toFixed(1)));
|
||
}
|
||
function _starsHtml(score) {
|
||
var full = Math.floor(score), half = (score - full) >= 0.3 ? 1 : 0;
|
||
var out = '';
|
||
for(var i=0;i<full;i++) out += '<span style="color:#F59E0B">★</span>';
|
||
if(half) out += '<span style="color:#F59E0B">½</span>';
|
||
for(var j=full+half;j<5;j++) out += '<span style="color:#CBD5E1">★</span>';
|
||
return out;
|
||
}
|
||
|
||
// ── RESULT ────────────────────────────────────────────────────────────────────
|
||
window._resultCount = window._resultCount || 3;
|
||
function _setResultCount(n){ window._resultCount=n; window._selectedModel=undefined; document.getElementById('screen').innerHTML=renderScreen('manager_result'); }
|
||
function screenResult() {
|
||
var wiz = window._wiz;
|
||
var cfg = WIZ[wiz.type||'oven']; if(!cfg) cfg=WIZ['oven'];
|
||
var bk = wiz.budget||'mid';
|
||
var budget = cfg.budgets[bk];
|
||
// Сортируем по отзывам desc, берём N первых
|
||
var allModels = ((cfg.models&&cfg.models[bk])||WIZ['oven'].models['mid']).slice();
|
||
allModels.sort(function(a,b){ return (b.reviews||0)-(a.reviews||0); });
|
||
var cnt = window._resultCount || 3;
|
||
var models = allModels.slice(0, cnt);
|
||
var colors = _photoColors[wiz.type]||['#e0e7ff','#c7d2fe'];
|
||
|
||
var mHtml = models.map(function(m, i) {
|
||
var basePrice = _parsePrice(m.price);
|
||
var prices = _storePrices(basePrice, basePrice % 11 + i*3);
|
||
var avg = prices[0].avg;
|
||
var saving = avg - basePrice;
|
||
var score = _valueScore(bk, i);
|
||
var isSel = (window._selectedModel === i);
|
||
var rankLabels = ['🥇 Выбор #1','🥈 Альтернатива','🥉 Вариант #3','#4 по отзывам','#5 по отзывам','#6 по отзывам','#7 по отзывам'];
|
||
var rank = rankLabels[i] || ('#'+(i+1)+' по отзывам');
|
||
|
||
// Рейтинг и отзывы
|
||
var revCount = m.reviews || 0;
|
||
var revLabel = revCount >= 1000 ? (revCount/1000).toFixed(1).replace('.0','')+'K' : revCount;
|
||
var ratingColor = m.rating >= 4.7 ? '#15803D' : m.rating >= 4.4 ? '#D97706' : '#64748B';
|
||
var ratingHtml = m.rating
|
||
? '<div style="display:inline-flex;align-items:center;gap:5px;background:rgba(0,0,0,.04);border-radius:20px;padding:3px 10px 3px 8px;margin-bottom:6px">'
|
||
+'<span style="color:#F59E0B;font-size:13px">★</span>'
|
||
+'<span style="font-size:13px;font-weight:800;color:'+ratingColor+'">'+m.rating+'</span>'
|
||
+'<span style="font-size:11px;color:var(--muted)">·</span>'
|
||
+'<span style="font-size:11px;color:var(--muted);font-weight:600">'+revLabel+' отзывов</span>'
|
||
+'</div>'
|
||
: '';
|
||
// Теги
|
||
var tags = m.tags.map(function(t){
|
||
var bg = t==='Рекомендуем'?'#DCFCE7':t==='Встраиваемая'?'#EEF2FF':'#F1F5F9';
|
||
var co = t==='Рекомендуем'?'#15803D':t==='Встраиваемая'?'#4338CA':'#64748B';
|
||
return '<span style="display:inline-flex;padding:2px 8px;border-radius:20px;font-size:10px;font-weight:700;background:'+bg+';color:'+co+';margin:2px 3px 2px 0">'+t+'</span>';
|
||
}).join('');
|
||
// Плюсы и минусы от покупателей
|
||
var buyerBlock = '';
|
||
if(m.buyerPros || m.buyerCons){
|
||
var prosHtml = (m.buyerPros||[]).map(function(p){
|
||
return '<div style="display:flex;align-items:flex-start;gap:7px;padding:4px 0">'
|
||
+'<span style="color:#15803D;font-size:12px;flex-shrink:0;margin-top:1px">✓</span>'
|
||
+'<span style="font-size:12px;color:var(--ink);line-height:1.4">'+p+'</span></div>';
|
||
}).join('');
|
||
var consHtml = (m.buyerCons||[]).map(function(c){
|
||
return '<div style="display:flex;align-items:flex-start;gap:7px;padding:4px 0">'
|
||
+'<span style="color:#EF4444;font-size:12px;flex-shrink:0;margin-top:1px">✕</span>'
|
||
+'<span style="font-size:12px;color:var(--ink);line-height:1.4">'+c+'</span></div>';
|
||
}).join('');
|
||
buyerBlock = '<div style="background:var(--bg);border-radius:12px;padding:12px 14px;margin-top:10px">'
|
||
+'<div style="font-size:11px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:8px">💬 Покупатели отмечают</div>'
|
||
+prosHtml+consHtml
|
||
+'</div>';
|
||
}
|
||
|
||
// Строки прайс-таблицы
|
||
var priceRows = prices.map(function(p){
|
||
var isBest = p.best;
|
||
return '<div style="display:flex;align-items:center;gap:8px;padding:5px 0;border-bottom:1px solid rgba(0,0,0,.05)">'
|
||
+'<div style="width:24px;text-align:center;font-size:11px">'+(isBest?'<span style="color:#15803D;font-weight:800">✓</span>':'')+'</div>'
|
||
+'<div style="flex:1;font-size:12px;font-weight:'+(isBest?'700':'500')+';color:'+(isBest?'var(--ink)':'var(--muted)')+'">'+p.store+'</div>'
|
||
+'<div style="font-size:13px;font-weight:'+(isBest?'800':'500')+';color:'+(isBest?'var(--accent)':'var(--muted)')+'">'+p.price.toLocaleString('ru')+' ₽'+(isBest?'':'')+'</div>'
|
||
+(isBest?'<span style="font-size:10px;font-weight:700;background:#DCFCE7;color:#15803D;border-radius:8px;padding:1px 6px">Лучшая</span>':'<div style="width:52px"></div>')
|
||
+'</div>';
|
||
}).join('');
|
||
|
||
// Полоска сравнения цен (мин → макс)
|
||
var minP = prices[0].price, maxP = prices[prices.length-1].price;
|
||
var barPct = 8; // DNS всегда левый край = лучшая
|
||
var marketPct = Math.round((prices[1].price-minP)/(maxP-minP)*100)||15;
|
||
var mVideoPct = Math.round((prices[2].price-minP)/(maxP-minP)*100)||50;
|
||
|
||
return '<div class="result-model'+(isSel?' pick':'')+'" onclick="_pickModel('+i+')" style="padding:0;overflow:hidden">'
|
||
// Заголовок карточки
|
||
+'<div style="padding:14px 14px 10px">'
|
||
+'<div style="font-size:10px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:8px">'+rank+'</div>'
|
||
// Фото + название
|
||
+'<div style="display:flex;gap:12px;align-items:flex-start">'
|
||
+_photoBox(cfg.emoji, colors[0], colors[1], wiz.type)
|
||
+'<div style="flex:1;min-width:0">'
|
||
+'<div style="font-size:14px;font-weight:700;color:var(--ink);line-height:1.3;margin-bottom:6px">'+m.name+'</div>'
|
||
+ratingHtml
|
||
+'<div style="font-size:18px;font-weight:900;color:var(--accent);letter-spacing:-.02em">'+m.price+'</div>'
|
||
+'<div style="margin-top:6px">'+tags+'</div>'
|
||
+'</div></div>'
|
||
+buyerBlock
|
||
// Оценка ц/к
|
||
+'<div style="display:flex;align-items:center;gap:8px;margin-top:10px;padding-top:10px;border-top:1px solid rgba(0,0,0,.06)">'
|
||
+'<span style="font-size:11px;font-weight:600;color:var(--muted)">Цена/качество</span>'
|
||
+'<span style="font-size:13px">'+_starsHtml(score)+'</span>'
|
||
+'<span style="font-size:12px;font-weight:700;color:var(--ink)">'+score+'</span>'
|
||
+'</div>'
|
||
+'</div>'
|
||
// Блок анализа цен
|
||
+'<div style="background:var(--bg);padding:12px 14px;border-top:1px solid rgba(0,0,0,.06)">'
|
||
+'<div style="font-size:11px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:8px">📊 Анализ цен на рынке</div>'
|
||
// Полоска
|
||
+'<div style="position:relative;height:6px;background:#E2E8F0;border-radius:3px;margin-bottom:10px">'
|
||
+'<div style="position:absolute;left:'+barPct+'%;width:6px;height:6px;background:#15803D;border-radius:50%;top:0" title="DNS"></div>'
|
||
+'<div style="position:absolute;left:'+marketPct+'%;width:6px;height:6px;background:#94A3B8;border-radius:50%;top:0"></div>'
|
||
+'<div style="position:absolute;left:'+mVideoPct+'%;width:6px;height:6px;background:#CBD5E1;border-radius:50%;top:0"></div>'
|
||
+'<div style="position:absolute;right:4px;width:6px;height:6px;background:#E2E8F0;border-radius:50%;top:0;border:1.5px solid #CBD5E1"></div>'
|
||
+'</div>'
|
||
+priceRows
|
||
// Итог экономии
|
||
+(saving>0?'<div style="display:flex;align-items:center;gap:8px;margin-top:8px;background:#DCFCE7;border-radius:10px;padding:8px 12px">'
|
||
+'<span style="font-size:16px">💚</span>'
|
||
+'<div><div style="font-size:12px;font-weight:700;color:#15803D">Дешевле средней цены рынка на '+saving.toLocaleString('ru')+' ₽</div>'
|
||
+'<div style="font-size:11px;color:#166534">Средняя по 4 магазинам: '+avg.toLocaleString('ru')+' ₽</div></div>'
|
||
+'</div>':'')
|
||
+'</div>'
|
||
+'</div>';
|
||
}).join('');
|
||
|
||
var selModel = window._selectedModel!==undefined ? models[window._selectedModel] : null;
|
||
var bk2 = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M15 18l-6-6 6-6"/></svg>';
|
||
|
||
// Пиллы выбора количества вариантов
|
||
var maxAvail = allModels.length;
|
||
var countPills = [3,5,7].map(function(n){
|
||
var active = cnt===n;
|
||
var disabled = n > maxAvail;
|
||
return '<button onclick="'+(disabled?'':'_setResultCount('+n+')')+'" style="'
|
||
+'padding:5px 14px;border-radius:20px;font-size:12px;font-weight:700;cursor:'+(disabled?'default':'pointer')+';'
|
||
+'border:none;transition:all .15s;'
|
||
+(active
|
||
?'background:var(--accent);color:#fff;box-shadow:0 2px 8px rgba(0,62,126,.3)'
|
||
:disabled
|
||
?'background:#F1F5F9;color:#CBD5E1'
|
||
:'background:#F1F5F9;color:var(--muted)'
|
||
)+'">'+n+'</button>';
|
||
}).join('');
|
||
|
||
return '<div class="page">'
|
||
+'<div class="page-header"><button class="back-btn" onclick="document.getElementById(\'screen\').innerHTML=renderScreen(\'manager_wizard\')">'+bk2+'</button>'
|
||
+'<h2>'+cfg.emoji+' '+cfg.name+'</h2>'
|
||
+'<span class="badge gray">'+budget.label+'</span></div>'
|
||
// Селектор количества вариантов
|
||
+'<div style="display:flex;align-items:center;gap:8px;padding:10px 16px 4px;background:var(--card);border-bottom:1px solid rgba(0,0,0,.06)">'
|
||
+'<span style="font-size:12px;color:var(--muted);font-weight:600;white-space:nowrap">Вариантов:</span>'
|
||
+'<div style="display:flex;gap:6px">'+countPills+'</div>'
|
||
+'<span style="font-size:11px;color:var(--muted);margin-left:auto">★ по отзывам</span>'
|
||
+'</div>'
|
||
+'<div style="padding:12px 16px">'
|
||
+'<div style="background:linear-gradient(135deg,#EFF6FF,#DBEAFE);border-radius:12px;padding:12px 14px;margin-bottom:12px">'
|
||
+'<div style="font-size:13px;color:#1D4ED8;font-style:italic;font-weight:500">'+cfg.anchor+'</div></div>'
|
||
+'<div style="background:var(--card);border-radius:12px;padding:11px 14px;margin-bottom:14px">'
|
||
+'<div style="font-size:11px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.04em;margin-bottom:3px">📐 Ниша для проекта</div>'
|
||
+'<div style="font-size:14px;font-weight:700;color:var(--ink)">'+cfg.niche+'</div>'
|
||
+'<div style="font-size:12px;color:#B45309;margin-top:2px">⚠ '+cfg.note+'</div></div>'
|
||
+mHtml
|
||
+(selModel
|
||
?'<div style="background:#DCFCE7;border-radius:12px;padding:11px 14px;margin:12px 0;display:flex;align-items:center;gap:10px">'
|
||
+'<div style="font-size:18px">✅</div>'
|
||
+'<div><div style="font-size:13px;font-weight:700;color:#15803D">'+selModel.name+'</div>'
|
||
+'<div style="font-size:12px;color:#166534">'+selModel.price+' · Выбрано</div></div>'
|
||
+'</div>'
|
||
+(window._wizClientMode
|
||
?'<button class="btn-primary" style="margin-top:4px" onclick="_saveResultAsClientPick()">📲 Отправить клиенту →</button>'
|
||
: window._wizFromNav
|
||
?'<button class="btn-primary" style="margin-top:4px" onclick="_showAttachPicker()">📎 Привязать к заказу →</button>'
|
||
+'<button class="btn-secondary" onclick="_sendTechToClient()" style="display:flex;align-items:center;justify-content:center;gap:7px;margin-top:8px">'
|
||
+'<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><line x1="22" y1="2" x2="11" y2="13"/><polygon points="22 2 15 22 11 13 2 9 22 2"/></svg>'
|
||
+'Отправить клиенту</button>'
|
||
:'<button class="btn-primary" style="margin-top:4px" onclick="_saveToOrder()">✅ Добавить в спецификацию →</button>'
|
||
+'<button class="btn-secondary" onclick="_sendTechToClient()" style="display:flex;align-items:center;justify-content:center;gap:7px;margin-top:8px">'
|
||
+'<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><line x1="22" y1="2" x2="11" y2="13"/><polygon points="22 2 15 22 11 13 2 9 22 2"/></svg>'
|
||
+'Отправить клиенту</button>')
|
||
:'<div style="background:#FEF3C7;border-radius:12px;padding:11px 14px;margin:12px 0;font-size:13px;color:#92400E;font-weight:600">👆 Выберите модель выше</div>'
|
||
+'<button class="btn-primary" style="margin-top:4px;opacity:.4" disabled>'+(window._wizClientMode?'📲 Отправить клиенту →':'✅ Добавить в спецификацию →')+'</button>')
|
||
+'<button class="btn-secondary" style="margin-top:8px" onclick="document.getElementById(\'screen\').innerHTML=renderScreen(\'manager_order\');document.getElementById(\'nav\').innerHTML=navBar()">Вернуться в заказ</button>'
|
||
+'</div></div>';
|
||
}
|
||
function _pickModel(i){window._selectedModel=i;document.getElementById('screen').innerHTML=renderScreen('manager_result');}
|
||
function _saveToOrder(){
|
||
var o = window._managerOrders[window._activeOrder];
|
||
var wiz = window._wiz;
|
||
var cfg = WIZ[wiz.type||'oven']; if(!cfg) cfg=WIZ['oven'];
|
||
var bk = wiz.budget||'mid';
|
||
var models = cfg.models[bk];
|
||
var sel = models[window._selectedModel!==undefined ? window._selectedModel : 0];
|
||
// Сохраняем в tech-массив заказа
|
||
var ti = (o.tech||[]).findIndex(function(t){ return t.wkey===wiz.type; });
|
||
if(ti>=0){
|
||
o.tech[ti].status = 'done';
|
||
o.tech[ti].brand = sel.name;
|
||
o.tech[ti].dims = cfg.niche;
|
||
o.tech[ti].source = 'ai';
|
||
}
|
||
// Снимаем блокер если вся техника закрыта
|
||
var waitLeft = (o.tech||[]).filter(function(t){ return t.status==='wait'; }).length;
|
||
if(waitLeft===0 && o.blocker && o.blocker.indexOf('Техника')>=0){ o.blocker=null; o.techNote=''; }
|
||
_toast('✅ '+sel.name+' сохранён в заказ','var(--success)');
|
||
setTimeout(function(){
|
||
window._selectedModel=undefined;
|
||
document.getElementById('screen').innerHTML=renderScreen('manager_order');
|
||
document.getElementById('nav').innerHTML=navBar();
|
||
},1200);
|
||
}
|
||
function _showAttachPicker(){
|
||
var wiz = window._wiz;
|
||
var cfg = WIZ[wiz.type||'oven']; if(!cfg) cfg=WIZ['oven'];
|
||
var bk = wiz.budget||'mid';
|
||
var allModels = ((cfg.models&&cfg.models[bk])||[]).slice();
|
||
allModels.sort(function(a,b){return (b.reviews||0)-(a.reviews||0);});
|
||
var sel = allModels[window._selectedModel!==undefined ? window._selectedModel : 0];
|
||
var orders = (window._managerOrders||[]).filter(function(o){return !o.isLead;});
|
||
var bk2='<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M15 18l-6-6 6-6"/></svg>';
|
||
var listHtml = orders.length === 0
|
||
? '<div style="padding:24px;text-align:center;color:var(--muted);font-size:13px">Нет активных заказов</div>'
|
||
: orders.map(function(o,i){
|
||
var idx = window._managerOrders.indexOf(o);
|
||
return '<div onclick="_attachToOrder('+idx+')" style="display:flex;align-items:center;gap:12px;padding:12px 16px;'+(i<orders.length-1?'border-bottom:1px solid rgba(0,0,0,.05)':'')+';cursor:pointer;active:background:#f5f5f5">'
|
||
+'<div style="width:40px;height:40px;border-radius:12px;background:var(--bg);display:flex;align-items:center;justify-content:center;font-size:18px;flex-shrink:0">🏠</div>'
|
||
+'<div style="flex:1;min-width:0">'
|
||
+'<div style="font-size:14px;font-weight:600;color:var(--ink)">'+o.client+'</div>'
|
||
+'<div style="font-size:11px;color:var(--muted)">'+o.contract+' · '+o.amount.toLocaleString('ru')+' ₽</div>'
|
||
+'</div>'
|
||
+'<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="var(--muted)" stroke-width="2"><polyline points="9 18 15 12 9 6"/></svg>'
|
||
+'</div>';
|
||
}).join('');
|
||
document.getElementById('screen').innerHTML =
|
||
'<div class="page">'
|
||
+'<div class="page-header"><button class="back-btn" onclick="document.getElementById(\'screen\').innerHTML=renderScreen(\'manager_result\');document.getElementById(\'nav\').innerHTML=navBar()">'+bk2+'</button><h2>Привязать к заказу</h2></div>'
|
||
+'<div style="padding:16px">'
|
||
+'<div style="background:var(--card);border-radius:16px;padding:12px 14px;margin-bottom:16px;display:flex;align-items:center;gap:10px">'
|
||
+'<div style="font-size:24px">'+cfg.emoji+'</div>'
|
||
+'<div><div style="font-size:13px;font-weight:700;color:var(--ink)">'+sel.name+'</div>'
|
||
+'<div style="font-size:11px;color:var(--muted)">'+cfg.name+' · '+sel.price+'</div>'
|
||
+'</div></div>'
|
||
+'<div style="font-size:11px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.06em;margin-bottom:8px">Выберите заказ:</div>'
|
||
+'<div style="background:var(--card);border-radius:16px;overflow:hidden">'+listHtml+'</div>'
|
||
+'</div></div>';
|
||
document.getElementById('nav').innerHTML = navBar();
|
||
}
|
||
function _attachToOrder(idx){
|
||
var o = window._managerOrders[idx];
|
||
var wiz = window._wiz;
|
||
var cfg = WIZ[wiz.type||'oven']; if(!cfg) cfg=WIZ['oven'];
|
||
var bk = wiz.budget||'mid';
|
||
var allModels = ((cfg.models&&cfg.models[bk])||[]).slice();
|
||
allModels.sort(function(a,b){return (b.reviews||0)-(a.reviews||0);});
|
||
var sel = allModels[window._selectedModel!==undefined ? window._selectedModel : 0];
|
||
var ti = (o.tech||[]).findIndex(function(t){ return t.wkey===wiz.type; });
|
||
if(ti>=0){ o.tech[ti].status='done'; o.tech[ti].brand=sel.name; o.tech[ti].dims=cfg.niche; o.tech[ti].source='ai'; }
|
||
var waitLeft = (o.tech||[]).filter(function(t){ return t.status==='wait'; }).length;
|
||
if(waitLeft===0 && o.blocker && o.blocker.indexOf('Техника')>=0){ o.blocker=null; o.techNote=''; }
|
||
window._activeOrder = idx;
|
||
window._wizFromNav = false;
|
||
_toast('✅ '+sel.name+' → '+o.client,'var(--success)');
|
||
setTimeout(function(){
|
||
window._selectedModel = undefined;
|
||
document.getElementById('screen').innerHTML = renderScreen('manager_order');
|
||
document.getElementById('nav').innerHTML = navBar();
|
||
},1200);
|
||
}
|
||
function _sendTechToClient(){
|
||
var wiz = window._wiz;
|
||
var cfg = WIZ[wiz.type||'oven']; if(!cfg) cfg=WIZ['oven'];
|
||
var bk = wiz.budget||'mid';
|
||
var models = cfg.models[bk];
|
||
var sel = models[window._selectedModel!==undefined ? window._selectedModel : 0];
|
||
var o = window._managerOrders[window._activeOrder]||window._managerOrders[0];
|
||
var msg = o.client.split(' ')[0]+', подобрал '+cfg.name.toLowerCase()+' для вашей кухни 👇\n\n'
|
||
+cfg.emoji+' '+sel.name+'\n'
|
||
+'💰 '+sel.price+'\n'
|
||
+'📐 Ниша: '+cfg.niche+'\n'
|
||
+'✅ '+sel.pros+'\n\n'
|
||
+'Покупаем в DNS — лучшая цена. Что скажете?';
|
||
// Показываем превью
|
||
document.getElementById('screen').innerHTML = _renderSendPreview(msg, sel);
|
||
}
|
||
function _renderSendPreview(msg, sel){
|
||
var enc = encodeURIComponent(msg);
|
||
var o = window._managerOrders[window._activeOrder]||window._managerOrders[0];
|
||
var phone = (o.phone||'').replace(/\D/g,'');
|
||
var bk2='<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="document.getElementById(\'screen\').innerHTML=renderScreen(\'manager_result\')">'+bk2+'</button><h2>📤 Отправить клиенту</h2></div>'
|
||
+'<div style="padding:16px">'
|
||
+'<div style="font-size:12px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:8px">Сообщение</div>'
|
||
+'<div style="background:var(--card);border-radius:14px;padding:14px;font-size:13px;line-height:1.6;color:var(--ink);white-space:pre-wrap;box-shadow:0 2px 10px rgba(0,0,0,.07);margin-bottom:16px">'+msg+'</div>'
|
||
+'<a href="https://wa.me/'+phone+'?text='+enc+'" style="display:flex;align-items:center;justify-content:center;gap:8px;background:#25D366;color:#fff;border-radius:13px;padding:14px;font-size:15px;font-weight:700;text-decoration:none;margin-bottom:10px">'
|
||
+'<svg width="20" height="20" viewBox="0 0 24 24" fill="#fff"><path d="M17.472 14.382c-.297-.149-1.758-.867-2.03-.967-.273-.099-.471-.148-.67.15-.197.297-.767.966-.94 1.164-.173.199-.347.223-.644.075-.297-.15-1.255-.463-2.39-1.475-.883-.788-1.48-1.761-1.653-2.059-.173-.297-.018-.458.13-.606.134-.133.298-.347.446-.52.149-.174.198-.298.298-.497.099-.198.05-.371-.025-.52-.075-.149-.669-1.612-.916-2.207-.242-.579-.487-.5-.669-.51-.173-.008-.371-.01-.57-.01-.198 0-.52.074-.792.372-.272.297-1.04 1.016-1.04 2.479 0 1.462 1.065 2.875 1.213 3.074.149.198 2.096 3.2 5.077 4.487.709.306 1.262.489 1.694.625.712.227 1.36.195 1.871.118.571-.085 1.758-.719 2.006-1.413.248-.694.248-1.289.173-1.413-.074-.124-.272-.198-.57-.347m-5.421 7.403h-.004a9.87 9.87 0 01-5.031-1.378l-.361-.214-3.741.982.998-3.648-.235-.374a9.86 9.86 0 01-1.51-5.26c.001-5.45 4.436-9.884 9.888-9.884 2.64 0 5.122 1.03 6.988 2.898a9.825 9.825 0 012.893 6.994c-.003 5.45-4.437 9.884-9.885 9.884m8.413-18.297A11.815 11.815 0 0012.05 0C5.495 0 .16 5.335.157 11.892c0 2.096.547 4.142 1.588 5.945L.057 24l6.305-1.654a11.882 11.882 0 005.683 1.448h.005c6.554 0 11.89-5.335 11.893-11.893a11.821 11.821 0 00-3.48-8.413z"/></svg>'
|
||
+'WhatsApp</a>'
|
||
+'<button onclick="navigator.clipboard&&navigator.clipboard.writeText('+JSON.stringify(msg)+').then(function(){_toast(\'✅ Скопировано\',\'#003E7E\');})" style="width:100%;background:var(--bg);border:1.5px solid #E2E8F0;border-radius:13px;padding:13px;font-size:14px;font-weight:600;color:var(--ink);cursor:pointer">📋 Скопировать текст</button>'
|
||
+'</div></div>';
|
||
}
|
||
|
||
// ── CLIENT ────────────────────────────────────────────────────────────────────
|
||
function screenClient() {
|
||
var o=window._managerOrders[window._activeOrder]||window._managerOrders[0];
|
||
var init=o.client.split(' ').slice(0,2).map(function(w){return w[0];}).join('');
|
||
var bk='<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M15 18l-6-6 6-6"/></svg>';
|
||
|
||
// ── Воронка-прогресс ──────────────────────────────────────────────────────
|
||
var funnelHtml = '';
|
||
if (o.isLead) {
|
||
var ldSteps = [{id:'new',label:'Новый'},{id:'meeting',label:'Замер'},{id:'kp',label:'КП'},{id:'thinking',label:'Думает'},{id:'done',label:'Договор'}];
|
||
var ldIdx = {new:0,meeting:1,kp:2,thinking:3,done:4};
|
||
var curLdI = ldIdx[o.leadStage]||0;
|
||
funnelHtml = '<div style="background:var(--card);border-radius:16px;padding:14px 16px;margin:0 16px 12px">'
|
||
+'<div style="font-size:11px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.06em;margin-bottom:10px">Этап воронки</div>'
|
||
+'<div style="display:flex;align-items:center;gap:0">';
|
||
ldSteps.forEach(function(st,i){
|
||
var done = i < curLdI;
|
||
var cur = i === curLdI;
|
||
var dotBg = done ? 'var(--accent)' : cur ? 'var(--accent)' : 'rgba(0,0,0,.1)';
|
||
var dotBorder = cur ? '3px solid #fff;box-shadow:0 0 0 2px var(--accent)' : 'none';
|
||
funnelHtml += '<div style="display:flex;flex-direction:column;align-items:center;flex:1;position:relative">';
|
||
if(i>0) funnelHtml += '<div style="position:absolute;top:7px;right:50%;width:100%;height:3px;background:'+(done?'var(--accent)':'rgba(0,0,0,.1)')+';z-index:0"></div>';
|
||
funnelHtml += '<div style="width:16px;height:16px;border-radius:50%;background:'+dotBg+';border:'+dotBorder+';position:relative;z-index:1;flex-shrink:0"></div>'
|
||
+'<div style="font-size:9px;font-weight:'+(cur?'700':'500')+';color:'+(cur?'var(--accent)':'var(--muted)')+';margin-top:5px;text-align:center;line-height:1.2">'+st.label+'</div>'
|
||
+'</div>';
|
||
});
|
||
funnelHtml += '</div></div>';
|
||
} else {
|
||
var stSteps = ['Замер','Проект','Техника','Технолог','Произв.','Сборка','Монтаж','Готово'];
|
||
var stIcons = ['📐','📝','🔌','🔬','🏭','🔧','🚚','✅'];
|
||
var curSt = (o.stage||1) - 1;
|
||
funnelHtml = '<div style="background:var(--card);border-radius:16px;padding:14px 16px;margin:0 16px 12px">'
|
||
+'<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:10px">'
|
||
+'<div style="font-size:11px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.06em">Этап производства</div>'
|
||
+'<div style="font-size:12px;font-weight:700;color:var(--accent)">'+stIcons[curSt]+' '+stSteps[curSt]+'</div>'
|
||
+'</div>'
|
||
+'<div style="height:6px;background:rgba(0,0,0,.08);border-radius:3px;overflow:hidden">'
|
||
+'<div style="height:100%;border-radius:3px;background:var(--accent);width:'+Math.round((curSt+1)/8*100)+'%"></div>'
|
||
+'</div>'
|
||
+'<div style="display:flex;justify-content:space-between;margin-top:5px">'
|
||
+'<span style="font-size:10px;color:var(--muted)">Этап '+(curSt+1)+' из 8</span>'
|
||
+'<span style="font-size:10px;color:var(--muted)">'+Math.round((curSt+1)/8*100)+'%</span>'
|
||
+'</div>'
|
||
+'</div>';
|
||
}
|
||
|
||
// ── Быстрые действия ──────────────────────────────────────────────────────
|
||
var actHtml = '<div style="display:flex;gap:8px;margin:0 16px 12px">'
|
||
+'<button onclick="_toast(\'Звонок: '+o.phone+'\',\'#003E7E\')" style="flex:1;display:flex;flex-direction:column;align-items:center;gap:5px;padding:12px 8px;background:var(--card);border:none;border-radius:14px;cursor:pointer">'
|
||
+'<div style="width:36px;height:36px;border-radius:50%;background:#DBEAFE;display:flex;align-items:center;justify-content:center">'
|
||
+'<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="#1D4ED8" stroke-width="2"><path d="M22 16.92v3a2 2 0 01-2.18 2 19.79 19.79 0 01-8.63-3.07A19.5 19.5 0 013.86 9.81 19.79 19.79 0 01.79 1.18 2 2 0 012.77 0h3a2 2 0 012 1.72c.127.96.361 1.903.7 2.81a2 2 0 01-.45 2.11L6.91 7.74a16 16 0 006.29 6.29l1.1-1.1a2 2 0 012.11-.45c.907.339 1.85.573 2.81.7A2 2 0 0122 16.92z"/></svg>'
|
||
+'</div>'
|
||
+'<span style="font-size:11px;font-weight:600;color:var(--ink)">Позвонить</span>'
|
||
+'</button>'
|
||
+'<button onclick="_toast(\'WhatsApp: '+o.phone+'\',\'#16A34A\')" style="flex:1;display:flex;flex-direction:column;align-items:center;gap:5px;padding:12px 8px;background:var(--card);border:none;border-radius:14px;cursor:pointer">'
|
||
+'<div style="width:36px;height:36px;border-radius:50%;background:#DCFCE7;display:flex;align-items:center;justify-content:center;font-size:18px">💬</div>'
|
||
+'<span style="font-size:11px;font-weight:600;color:var(--ink)">WhatsApp</span>'
|
||
+'</button>'
|
||
+'<button onclick="_openOrder('+window._activeOrder+')" style="flex:1;display:flex;flex-direction:column;align-items:center;gap:5px;padding:12px 8px;background:var(--card);border:none;border-radius:14px;cursor:pointer">'
|
||
+'<div style="width:36px;height:36px;border-radius:50%;background:#EDE9FE;display:flex;align-items:center;justify-content:center">'
|
||
+'<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="#7C3AED" stroke-width="2"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/></svg>'
|
||
+'</div>'
|
||
+'<span style="font-size:11px;font-weight:600;color:var(--ink)">Заказ/Лид</span>'
|
||
+'</button>'
|
||
+'<button onclick="_toast(\'Напоминание создано\',\'#F59E0B\')" style="flex:1;display:flex;flex-direction:column;align-items:center;gap:5px;padding:12px 8px;background:var(--card);border:none;border-radius:14px;cursor:pointer">'
|
||
+'<div style="width:36px;height:36px;border-radius:50%;background:#FEF3C7;display:flex;align-items:center;justify-content:center">'
|
||
+'<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="#D97706" stroke-width="2"><path d="M18 8A6 6 0 006 8c0 7-3 9-3 9h18s-3-2-3-9"/><path d="M13.73 21a2 2 0 01-3.46 0"/></svg>'
|
||
+'</div>'
|
||
+'<span style="font-size:11px;font-weight:600;color:var(--ink)">Напомнить</span>'
|
||
+'</button>'
|
||
+'</div>';
|
||
|
||
// ── Инфо о клиенте ────────────────────────────────────────────────────────
|
||
var infoRows = [
|
||
{label:'Телефон', val: o.phone},
|
||
{label:'Источник', val: 'Зал'},
|
||
{label:'Первый контакт', val: '03.05.2025'},
|
||
{label:'Ответственный', val: 'Анна Соколова'},
|
||
];
|
||
var infoHtml = '<div style="background:var(--card);border-radius:16px;margin:0 16px 12px;overflow:hidden">'
|
||
+infoRows.map(function(r,i){
|
||
return '<div style="display:flex;justify-content:space-between;align-items:center;padding:11px 14px;'+(i<infoRows.length-1?'border-bottom:1px solid rgba(0,0,0,.05)':'')+'">'
|
||
+'<span style="font-size:12px;color:var(--muted)">'+r.label+'</span>'
|
||
+'<span style="font-size:13px;font-weight:600;color:var(--ink)">'+r.val+'</span>'
|
||
+'</div>';
|
||
}).join('')
|
||
+'</div>';
|
||
|
||
// ── Текущий заказ/лид ─────────────────────────────────────────────────────
|
||
var orderHtml = '<div style="margin:0 16px 12px">'
|
||
+'<div style="font-size:11px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.06em;margin-bottom:8px">Текущий '+(o.isLead?'лид':'заказ')+'</div>'
|
||
+'<div onclick="_openOrder('+window._activeOrder+')" style="background:var(--card);border-radius:16px;padding:14px;cursor:pointer;border-left:4px solid var(--accent)">'
|
||
+'<div style="display:flex;justify-content:space-between;align-items:flex-start">'
|
||
+'<div style="font-size:14px;font-weight:700;color:var(--ink)">'+o.label+'</div>'
|
||
+'<span style="font-size:13px;font-weight:700;color:var(--accent)">'+o.amount.toLocaleString('ru')+' ₽</span>'
|
||
+'</div>'
|
||
+(o.contract?'<div style="font-size:12px;color:var(--muted);margin-top:3px">'+o.contract+'</div>':'')
|
||
+(o.blocker?'<div style="margin-top:8px;font-size:11px;font-weight:700;background:#FFFBEB;color:#92400E;padding:5px 10px;border-radius:8px">⚠️ '+o.blocker+'</div>':'')
|
||
+'<div style="margin-top:10px;font-size:12px;color:var(--accent);font-weight:600">Открыть →</div>'
|
||
+'</div>'
|
||
+'</div>';
|
||
|
||
// ── История ───────────────────────────────────────────────────────────────
|
||
var histHtml = '<div style="margin:0 16px 16px">'
|
||
+'<div style="font-size:11px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.06em;margin-bottom:8px">История заказов</div>'
|
||
+'<div style="background:var(--card);border-radius:16px;padding:14px;display:flex;flex-direction:column;align-items:center;gap:4px">'
|
||
+'<div style="font-size:20px">📭</div>'
|
||
+'<div style="font-size:13px;color:var(--muted)">Это первый заказ клиента</div>'
|
||
+'<div style="font-size:11px;color:var(--muted)">Рекомендуйте — приведёт следующего!</div>'
|
||
+'</div>'
|
||
+'</div>';
|
||
|
||
return '<div class="page">'
|
||
+'<div class="page-header"><button class="back-btn" onclick="document.getElementById(\'screen\').innerHTML=renderScreen(\'manager_order\')">'+bk+'</button><h2>Клиент</h2></div>'
|
||
// Hero
|
||
+'<div style="display:flex;flex-direction:column;align-items:center;padding:20px 16px 16px;gap:6px">'
|
||
+'<div style="width:72px;height:72px;border-radius:50%;background:linear-gradient(135deg,var(--accent),#1E6BB8);display:flex;align-items:center;justify-content:center;color:#fff;font-size:24px;font-weight:700;box-shadow:0 4px 16px rgba(0,62,126,.3)">'+init+'</div>'
|
||
+'<div style="font-size:20px;font-weight:700;color:var(--ink);margin-top:4px">'+o.client+'</div>'
|
||
+'<div style="font-size:14px;color:var(--muted)">'+o.phone+'</div>'
|
||
+'</div>'
|
||
+ actHtml
|
||
+ funnelHtml
|
||
+ infoHtml
|
||
+ orderHtml
|
||
+ histHtml
|
||
+'</div>';
|
||
}
|
||
|
||
// ── БАЗА ЗНАНИЙ ───────────────────────────────────────────────────────────────
|
||
var _KB = [
|
||
{ id:'kitchen', icon:'🍳', label:'Кухня', articles:[
|
||
{id:'k1', title:'Рулонные шторы на кухню: какой материал не испортит пар и жир', tag:'Материалы', views:1240,
|
||
body:'На кухне шторы работают в тяжёлых условиях: пар, брызги, запахи, температурные перепады. Обычная ткань быстро деформируется и желтеет.\n\nЧто выбрать:\n✅ Полиэстер 100% — базовый стандарт. Не впитывает влагу, легко протирается влажной тряпкой, не выгорает\n✅ Screen (скрин) — сетчатая ткань, пропускает воздух, режет яркий свет, не перегревается у плиты\n✅ Блэкаут с влагозащитной пропиткой — если нужно затемнение\n\n❌ Лён и хлопок — красиво, но жирные пятна впитываются намертво, стирка деформирует форму рулона.\n\nЛайфхак для клиента:\nМожно помыть прямо на окне — мыльный раствор и мягкая губка. Снимать не нужно.'},
|
||
{id:'k2', title:'Как мыть рулонные шторы на кухне — 3 правила', tag:'Уход', views:640,
|
||
body:'Многие клиенты боятся, что рулонные шторы сложно содержать в чистоте на кухне. На самом деле — проще, чем кажется.\n\nПравило 1: не снимать\nБольшинство загрязнений убираются прямо на окне: влажная микрофибра сверху вниз по полотну. Жир — мыльный раствор без агрессивной химии.\n\nПравило 2: температура важна\nГорячая вода деформирует ткань. Только холодная или чуть тёплая. Никакого отбеливателя.\n\nПравило 3: сушить в намотанном состоянии нельзя\nПосле мытья полотно должно высохнуть в опущенном виде. Иначе на рулоне появятся складки — и уже не исправить.\n\nЕсли штора снята — расстелить горизонтально, высушить, потом намотать аккуратно вручную.'},
|
||
]},
|
||
{ id:'bedroom', icon:'🛏', label:'Спальня', articles:[
|
||
{id:'b1', title:'Блэкаут vs светофильтр: что выбрать для спальни', tag:'Материалы', views:2100,
|
||
body:'Клиент хочет «шторы в спальню, чтобы не просыпаться» — первый вопрос: это блэкаут или светофильтр?\n\nБлэкаут:\n— Блокирует 95–100% света\n— Для тех, кто чутко спит, работает в ночные смены, для детей\n— Дополнительно сохраняет тепло зимой\n— Минус: утром в комнате — абсолютная темнота\n\nСветофильтр:\n— Гасит яркость, создаёт мягкий рассеянный свет\n— Подходит, когда важна утренняя атмосфера без слепящего солнца\n— «День-ночь» (зебра) — универсальный компромисс: регулируешь сам\n\nКак объяснить клиенту:\nНужно не проснуться в 5 утра — блэкаут. Хочется мягкое утро без ослепления — светофильтр.'},
|
||
{id:'b2', title:'День-ночь: объясняем клиенту за 2 минуты', tag:'Клиентам', views:1450,
|
||
body:'«День-ночь» или «зебра» — самый частый вопрос. Вот как объяснять просто.\n\nКонструкция: чередующиеся полосы — тёмная (непрозрачная) и светлая (прозрачная). Механизм сдвигает полотно, совмещая нужные полосы.\n\nТри позиции:\n☀️ Полосы смещены — свет проходит свободно, как без штор\n🌤 Полосы чередуются — свет рассеивается, уютно без темноты\n🌙 Полосы совмещены — затемнение, как у светофильтра (не 100%)\n\nКому идеально:\n— Гостиная: хочется управлять светом в течение дня\n— Спальня с выходом на восток: утром поднял полосы — и спи дальше\n\nВажно честно: 100% затемнения «зебра» не даёт. Если нужна полная темнота — только блэкаут.'},
|
||
]},
|
||
{ id:'living', icon:'🛋', label:'Гостиная', articles:[
|
||
{id:'l1', title:'До пола, с лужей или выше плинтуса — как выбрать длину штор', tag:'Монтаж', views:1320,
|
||
body:'Длина штор — одна из самых частых ошибок при монтаже. Клиент говорит «нормально» и получает то, что смотрится неправильно.\n\nТри варианта:\n📏 На 1 см выше пола — практично, чисто. Для кухни и детской. Легко мыть пол\n📏 Касается пола (0 см) — нейтральный вариант для большинства интерьеров\n📏 «Лужа» (+15–25 см) — дизайнерский приём. Только спальня/гостиная, только тяжёлые ткани\n\nПравило замерщика:\nЗамер — от карниза до пола, строго по вертикали, не от подоконника. Если пол неровный — замерять в трёх точках, брать минимум.\n\nЧастая ошибка: клиент просит «до пола» → монтажник делает 1 см → клиент видит просвет и недоволен. Уточняйте на замере!'},
|
||
{id:'l2', title:'Двойные шторы: тюль + портьера — как подобрать правильно', tag:'Конструкции', views:1870,
|
||
body:'Двойные шторы — самый популярный запрос для гостиной. Главная ошибка: клиент выбирает ткани отдельно, а вместе они не работают.\n\nПравило сочетания:\nТюль — лёгкий, нейтральный: белый, молочный, слоновая кость. Он не конкурирует с портьерой.\nПортьера — несёт цвет и характер интерьера. Именно её подбирают под стены, мебель, ковёр.\n\nТри ошибки:\n❌ Тюль и портьера одного цвета — пропадает глубина\n❌ Оба рисунчатые — визуальный хаос\n❌ Лёгкая портьера + плотный тюль — конструкция работает наоборот\n\nТехнический момент:\nНа один карниз нужен двойной крюк или двойная труба. Уточняйте на замере: есть карниз или монтаж с нуля?'},
|
||
]},
|
||
{ id:'kids', icon:'🧸', label:'Детская', articles:[
|
||
{id:'d1', title:'Безопасные шторы для детской — 5 обязательных условий', tag:'Безопасность', views:1560,
|
||
body:'В детской безопасность важнее эстетики. Чеклист для правильного ориентирования клиента.\n\n✅ Нет свисающих шнуров — рулонные и кассетные без цепочки на высоте ребёнка\n✅ Кассетная система закрытого типа — механизм скрыт, нет торчащих деталей\n✅ Блэкаут — дневной сон и ранние подъёмы решены\n✅ Ткань без пропитки формальдегидом — проверяйте сертификат, особенно для детей до 3 лет\n✅ Крепление на раму, не на стену — легко перевесить при перестановке\n\nБонус для продажи:\nБлэкаут в детской = ребёнок спит днём = счастливые родители. Продаёт сам себя.'},
|
||
{id:'d2', title:'Блэкаут в детской: дневной сон и ранние подъёмы', tag:'Материалы', views:1230,
|
||
body:'Блэкаут в детской — не прихоть, а необходимость. Объясняем родителям почему.\n\nДневной сон: дети до 4 лет спят лучше в полной темноте даже в 12 часов дня. Блэкаут создаёт нужные условия независимо от времени суток.\n\nУтренние подъёмы: летом в 5 утра уже светло. Без блэкаута ребёнок просыпается вместе с солнцем — и будит всю семью.\n\nЧто выбрать:\n— Рулонный блэкаут на кассете — нет шнуров, механизм закрыт\n— Цвета: нейтральные (белый, серый, бежевый) или тематические принты\n— Обязательно: без формальдегидной пропитки, сертификат OEKO-TEX\n\nВ продаже: это один из самых простых аргументов. «Ваш ребёнок будет спать — вы тоже».'},
|
||
]},
|
||
{ id:'office', icon:'💼', label:'Офис', articles:[
|
||
{id:'o1', title:'Скрин в офис: что такое коэффициент открытости', tag:'Материалы', views:930,
|
||
body:'Ткань Screen (скрин) — стандарт для офисов и коммерческих помещений. Клиенты часто не понимают, что значат цифры.\n\nКоэффициент открытости — процент «дырочек» в ткани:\n1% — почти непрозрачная, защита максимальная, улица не видна\n3% — баланс: свет есть, солнце режет, вид сохраняется\n5% — лёгкий фильтр, открытый вид, минимальное затемнение\n10% — почти прозрачная, только рассеивает яркость\n\nКак объяснить клиенту:\n«Экраны компьютеров у окна — берите 1–3%. Хочется видеть улицу — 5%.»\n\nПлюс скрина перед жалюзи:\nОдно полотно без горизонтальных планок. Пыль не копится. Моется раз в год влажной тряпкой.'},
|
||
]},
|
||
{ id:'balcony', icon:'🪟', label:'Балкон', articles:[
|
||
{id:'bal1', title:'Вертикальные жалюзи на балкон: монтаж без бурения', tag:'Монтаж', views:870,
|
||
body:'Балкон — особый случай: сверлить часто нельзя (аренда, управляющая компания), а шторы нужны.\n\nВарианты крепления без бурения:\n✅ На раму — специальные зажимы или саморезы в профиль окна. Подходит для большинства пластиковых рам\n✅ Натяжной карниз — распорный механизм между стенами. Только для узких проёмов до 2 м\n✅ Клеевые крепления — для лёгких конструкций, не несущих нагрузку\n\nЧто хорошо держится без бурения:\n— Рулонные шторы на раму\n— Вертикальные жалюзи с боковым креплением\n— Плиссе на двойной раме\n\nЧто нельзя без бурения:\nТяжёлые портьеры, карнизы длиннее 2 м, конструкции с автоматикой.'},
|
||
]},
|
||
];
|
||
|
||
window._kbRoom = window._kbRoom || 'kitchen';
|
||
window._kbSearch = window._kbSearch || '';
|
||
window._kbArticle = window._kbArticle || null;
|
||
|
||
function _openArticle(id){
|
||
window._kbArticle = id;
|
||
document.getElementById('screen').innerHTML = screenArticle();
|
||
document.getElementById('nav').innerHTML = navBar();
|
||
}
|
||
|
||
function screenArticle(){
|
||
var allArticles = _KB.reduce(function(acc,r){ return acc.concat(r.articles.map(function(a){ return Object.assign({},a,{roomLabel:r.label,roomIcon:r.icon}); })); }, []);
|
||
var a = allArticles.find(function(x){ return x.id === window._kbArticle; });
|
||
if(!a) return '<div style="padding:32px;text-align:center;color:var(--muted)">Статья не найдена</div>';
|
||
var bk='<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M15 18l-6-6 6-6"/></svg>';
|
||
var tagColor={'Материалы':'#3B82F6','Ткани':'#8B5CF6','Уход':'#10B981','Конструкции':'#F59E0B','Дизайн':'#EC4899','Безопасность':'#EF4444','Монтаж':'#0EA5E9','Клиентам':'#14B8A6','Автоматика':'#F97316'}[a.tag]||'#64748B';
|
||
// Рендер тела: переносы → параграфы, жирный текст **...**
|
||
var bodyHtml = a.body.split('\n\n').map(function(para){
|
||
var p = para.replace(/\n/g,'<br>');
|
||
return '<p style="margin:0 0 14px;font-size:14px;line-height:1.7;color:var(--ink)">'+p+'</p>';
|
||
}).join('');
|
||
return '<div class="page">'
|
||
+'<div class="page-header"><button class="back-btn" onclick="window._kbArticle=null;_nav(\'manager_kb\')">'+bk+'</button><h2>📚 База знаний</h2></div>'
|
||
+'<div style="padding:16px 16px 32px">'
|
||
// Шапка статьи
|
||
+'<div style="display:flex;align-items:center;gap:8px;margin-bottom:12px">'
|
||
+'<span style="font-size:20px">'+a.roomIcon+'</span>'
|
||
+'<span style="font-size:11px;color:var(--muted)">'+a.roomLabel+'</span>'
|
||
+'<span style="width:3px;height:3px;border-radius:50%;background:var(--muted);display:inline-block"></span>'
|
||
+'<span style="font-size:10px;font-weight:700;padding:2px 8px;border-radius:20px;background:'+tagColor+'18;color:'+tagColor+'">'+a.tag+'</span>'
|
||
+'<span style="font-size:10px;color:var(--muted);margin-left:auto">👁 '+a.views.toLocaleString('ru')+'</span>'
|
||
+'</div>'
|
||
+'<h3 style="font-size:17px;font-weight:700;color:var(--ink);line-height:1.4;margin:0 0 16px">'+a.title+'</h3>'
|
||
+'<div style="height:1px;background:rgba(0,0,0,.07);margin-bottom:16px"></div>'
|
||
+ bodyHtml
|
||
// Кнопка "Отправить клиенту"
|
||
+'<button onclick="_toast(\'Ссылка скопирована\',\'#003E7E\')" style="width:100%;background:var(--accent);color:#fff;border:none;border-radius:13px;padding:13px;font-size:14px;font-weight:700;cursor:pointer;display:flex;align-items:center;justify-content:center;gap:8px;margin-top:4px">'
|
||
+'<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2.5"><line x1="22" y1="2" x2="11" y2="13"/><polygon points="22 2 15 22 11 13 2 9 22 2"/></svg>'
|
||
+'Отправить клиенту</button>'
|
||
+'</div></div>';
|
||
}
|
||
|
||
function screenKB(){
|
||
var room = _KB.find(function(r){return r.id===window._kbRoom;})||_KB[0];
|
||
var search = window._kbSearch.toLowerCase().trim();
|
||
var articles= search
|
||
? _KB.reduce(function(acc,r){return acc.concat(r.articles.map(function(a){return Object.assign({},a,{room:r.label,roomIcon:r.icon});}));}, [])
|
||
.filter(function(a){return a.title.toLowerCase().indexOf(search)>=0||a.tag.toLowerCase().indexOf(search)>=0;})
|
||
: room.articles;
|
||
|
||
var roomTabs = '<div style="display:flex;gap:6px;padding:10px 14px 4px;overflow-x:auto;scrollbar-width:none">'
|
||
+_KB.map(function(r){
|
||
var active = !search && r.id===window._kbRoom;
|
||
return '<div onclick="window._kbRoom=\''+r.id+'\';window._kbSearch=\'\';_nav(\'manager_kb\')" style="flex-shrink:0;padding:6px 12px;border-radius:20px;font-size:12px;font-weight:700;cursor:pointer;border:1.5px solid '+(active?'var(--accent)':'rgba(0,0,0,.1)')+';background:'+(active?'var(--accent)':'var(--card)')+';color:'+(active?'#fff':'var(--muted)')+'">'+r.icon+' '+r.label+'</div>';
|
||
}).join('')
|
||
+'</div>';
|
||
|
||
var artList = (articles.length===0)
|
||
? '<div style="padding:32px;text-align:center;color:var(--muted);font-size:14px">Ничего не найдено</div>'
|
||
: articles.map(function(a){
|
||
var tagColor = {
|
||
'Материалы':'#3B82F6','Ткани':'#8B5CF6','Уход':'#10B981','Конструкции':'#F59E0B',
|
||
'Дизайн':'#EC4899','Безопасность':'#EF4444','Монтаж':'#0EA5E9','Клиентам':'#14B8A6','Автоматика':'#F97316'
|
||
}[a.tag]||'#64748B';
|
||
var hasBody = !!a.body;
|
||
return '<div style="background:var(--card);margin:0 14px 8px;border-radius:14px;padding:13px 14px;display:flex;align-items:flex-start;gap:10px;cursor:pointer" onclick="_openArticle(\''+a.id+'\')">'
|
||
+'<div style="width:36px;height:36px;border-radius:10px;background:rgba(0,62,126,.07);display:flex;align-items:center;justify-content:center;flex-shrink:0;font-size:18px">'+(a.roomIcon||room.icon)+'</div>'
|
||
+'<div style="flex:1;min-width:0">'
|
||
+'<div style="font-size:13px;font-weight:600;color:var(--ink);line-height:1.4;margin-bottom:5px">'+a.title+'</div>'
|
||
+'<div style="display:flex;align-items:center;gap:8px">'
|
||
+'<span style="font-size:10px;font-weight:700;padding:2px 8px;border-radius:20px;background:'+tagColor+'18;color:'+tagColor+'">'+a.tag+'</span>'
|
||
+(a.room?'<span style="font-size:10px;color:var(--muted)">'+a.roomIcon+' '+a.room+'</span>':'')
|
||
+'<span style="font-size:10px;color:var(--muted);margin-left:auto">👁 '+a.views.toLocaleString('ru')+'</span>'
|
||
+'</div>'
|
||
+'</div>'
|
||
+'<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="var(--accent)" stroke-width="2.5" style="flex-shrink:0;margin-top:2px"><polyline points="9 18 15 12 9 6"/></svg>'
|
||
+'</div>';
|
||
}).join('');
|
||
|
||
return '<div class="page">'
|
||
+'<div class="page-header"><h2>📚 База знаний</h2>'
|
||
+'<div style="font-size:11px;color:var(--muted)">ТГ-канал @zov_decor</div>'
|
||
+'</div>'
|
||
// Поиск
|
||
+'<div style="padding:8px 14px 4px">'
|
||
+'<div style="display:flex;align-items:center;gap:8px;background:var(--card);border:1.5px solid '+(search?'var(--accent)':'rgba(0,0,0,.09)')+';border-radius:12px;padding:9px 12px">'
|
||
+'<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="var(--muted)" stroke-width="2"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>'
|
||
+'<input id="kbSearch" type="text" placeholder="Найти статью…" value="'+window._kbSearch+'" oninput="window._kbSearch=this.value;_nav(\'manager_kb\')" style="flex:1;border:none;outline:none;font-size:13px;color:var(--ink);background:transparent">'
|
||
+(search?'<button onclick="window._kbSearch=\'\';_nav(\'manager_kb\')" style="border:none;background:none;color:var(--muted);cursor:pointer;font-size:16px;padding:0">×</button>':'')
|
||
+'</div>'
|
||
+'</div>'
|
||
+(!search ? roomTabs : '<div style="padding:8px 14px 4px;font-size:12px;color:var(--muted)">Поиск по всем помещениям — '+articles.length+' статей</div>')
|
||
+artList
|
||
+'</div>';
|
||
}
|
||
|
||
// ── AI ДОГОВОР ────────────────────────────────────────────────────────────────
|
||
window._aiContractResult = window._aiContractResult || null;
|
||
window._aiContractLoading = false;
|
||
|
||
function _aiContractCheck(){
|
||
if(window._aiContractLoading) return;
|
||
window._aiContractLoading = true;
|
||
window._aiContractResult = null;
|
||
document.getElementById('screen').innerHTML = renderScreen('manager_order');
|
||
// Симуляция анализа
|
||
setTimeout(function(){
|
||
window._aiContractLoading = false;
|
||
window._aiContractResult = {
|
||
ok:[
|
||
'Номер и дата договора указаны',
|
||
'Адрес объекта совпадает с замером',
|
||
'Сумма и авансовый порядок прописаны',
|
||
'Подписи обеих сторон предусмотрены',
|
||
],
|
||
warn:[
|
||
'Не указан срок устранения дефектов после сборки',
|
||
'Гарантийный срок сформулирован расплывчато',
|
||
],
|
||
err:[
|
||
'Отсутствует пункт об ответственности за перенос монтажа',
|
||
],
|
||
score: 74,
|
||
};
|
||
document.getElementById('screen').innerHTML = renderScreen('manager_order');
|
||
setTimeout(function(){
|
||
var el = document.getElementById('aiContractPanel');
|
||
if(el) el.scrollIntoView({behavior:'smooth',block:'start'});
|
||
}, 100);
|
||
}, 1800);
|
||
}
|
||
|
||
function _renderAIContract(r){
|
||
if(window._aiContractLoading){
|
||
return '<div id="aiContractPanel" style="margin-top:10px;background:rgba(139,92,246,.06);border:1.5px solid rgba(139,92,246,.2);border-radius:12px;padding:14px;text-align:center">'
|
||
+'<div style="font-size:13px;font-weight:600;color:#7C3AED">🤖 Анализирую договор…</div>'
|
||
+'<div style="font-size:11px;color:var(--muted);margin-top:4px">Обычно 5–10 секунд</div>'
|
||
+'</div>';
|
||
}
|
||
if(!r) return '';
|
||
var scoreColor = r.score>=80?'var(--success)':r.score>=60?'var(--warn)':'var(--danger)';
|
||
var items = r.ok.map(function(t){
|
||
return '<div style="display:flex;align-items:flex-start;gap:8px;padding:5px 0"><span style="color:var(--success);font-size:13px;flex-shrink:0">✓</span><span style="font-size:12px;color:var(--ink)">'+t+'</span></div>';
|
||
}).join('')
|
||
+ r.warn.map(function(t){
|
||
return '<div style="display:flex;align-items:flex-start;gap:8px;padding:5px 0"><span style="color:var(--warn);font-size:13px;flex-shrink:0">⚠</span><span style="font-size:12px;color:var(--ink)">'+t+'</span></div>';
|
||
}).join('')
|
||
+ r.err.map(function(t){
|
||
return '<div style="display:flex;align-items:flex-start;gap:8px;padding:5px 0"><span style="color:var(--danger);font-size:13px;flex-shrink:0">✕</span><span style="font-size:12px;color:var(--ink)">'+t+'</span></div>';
|
||
}).join('');
|
||
return '<div id="aiContractPanel" style="margin-top:10px;background:rgba(139,92,246,.05);border:1.5px solid rgba(139,92,246,.2);border-radius:12px;padding:14px">'
|
||
+'<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:10px">'
|
||
+'<div style="font-size:13px;font-weight:700;color:#7C3AED">🤖 AI проверка договора</div>'
|
||
+'<div style="font-size:20px;font-weight:900;color:'+scoreColor+'">'+r.score+'/100</div>'
|
||
+'</div>'
|
||
+items
|
||
+(r.err.length?'<div style="margin-top:10px;padding:9px 12px;background:rgba(239,68,68,.07);border-radius:9px;font-size:12px;color:var(--danger);font-weight:600">Требует доработки перед подписанием</div>':'')
|
||
+'<button onclick="window._aiContractResult=null;_nav(\'manager_order\')" style="width:100%;margin-top:10px;padding:8px;border-radius:9px;border:1px solid rgba(139,92,246,.2);background:transparent;color:#7C3AED;font-size:12px;font-weight:600;cursor:pointer">Закрыть</button>'
|
||
+'</div>';
|
||
}
|
||
|
||
// ── NAV ───────────────────────────────────────────────────────────────────────
|
||
function navBar() {
|
||
var s=window._currentScreen||'manager_home';
|
||
var isHome=s==='manager_home'||s==='manager_order'||s==='manager_client'||s==='manager_lead';
|
||
var isTech=s==='manager_tech'||s==='manager_wizard'||s==='manager_result';
|
||
var isSched=s==='manager_schedule';
|
||
var isCalc=s==='manager_salary'||s==='manager_calc';
|
||
var isKB=s==='manager_kb';
|
||
function ni(active,onclick,icon,lbl){
|
||
return '<div class="nav-item'+(active?' active':'')+'" onclick="'+onclick+'" style="min-width:0;padding:4px 2px">'
|
||
+'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="width:20px;height:20px">'+icon+'</svg>'
|
||
+'<span style="font-size:9px">'+lbl+'</span></div>';
|
||
}
|
||
return '<div class="bottom-nav">'
|
||
+ni(isHome,"_nav('manager_home')",'<rect x="3" y="3" width="7" height="7" rx="1"/><rect x="14" y="3" width="7" height="7" rx="1"/><rect x="3" y="14" width="7" height="7" rx="1"/><rect x="14" y="14" width="7" height="7" rx="1"/>','Заказы')
|
||
+ni(isTech,"window._wizFromNav=true;_nav('manager_tech')",'<path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z"/>','Техника')
|
||
+ni(isSched,"_nav('manager_schedule')",'<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"/>','График')
|
||
+ni(isCalc,"_nav('manager_salary')",'<rect x="2" y="3" width="20" height="14" rx="2"/><path d="M8 21h8M12 17v4"/><circle cx="12" cy="10" r="2"/>','Зарплата')
|
||
+ni(isKB,"_nav('manager_kb')",'<path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"/><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"/>','База')
|
||
+'</div>';
|
||
}
|
||
function _nav(id){window._currentScreen=id;document.getElementById('screen').innerHTML=renderScreen(id);document.getElementById('nav').innerHTML=navBar();}
|
||
|
||
// ── Toast ─────────────────────────────────────────────────────────────────────
|
||
function _toast(msg,color){
|
||
var t=document.createElement('div');
|
||
t.textContent=msg;
|
||
t.style.cssText='position:absolute;bottom:72px;left:50%;transform:translateX(-50%);background:'+(color||'#1F2937')
|
||
+';color:#fff;padding:10px 18px;border-radius:12px;font-size:13px;font-weight:600;z-index:9999;white-space:nowrap;box-shadow:0 4px 16px rgba(0,0,0,.25);transition:opacity .3s';
|
||
document.getElementById('phoneFrame').appendChild(t);
|
||
setTimeout(function(){t.style.opacity='0';setTimeout(function(){t.remove();},300);},2200);
|
||
}
|
||
|
||
// ── Theme / Boot ──────────────────────────────────────────────────────────────
|
||
function setTheme(t,btn){
|
||
if(t==='zov') document.body.removeAttribute('data-theme');
|
||
else document.body.setAttribute('data-theme',t);
|
||
document.querySelectorAll('.theme-btn').forEach(function(b){b.classList.remove('active');});
|
||
btn.classList.add('active');
|
||
}
|
||
function go(id){
|
||
window._currentScreen=id;
|
||
if(id==='manager_wizard'&&!window._wiz.type) window._wiz={type:'oven',step:0,answers:{},budget:null};
|
||
if(id==='manager_result'&&!window._wiz.type) window._wiz={type:'oven',step:0,answers:{usage:[0,3],height:[1],clean:[0],fuel:[0]},budget:'mid'};
|
||
if(id==='manager_own_tech'){ window._ownTechKey='oven'; window._ownTechMode='choose'; window._ownTechData={brand:'',model:'',dims:''}; }
|
||
// Для карточки лида — ставим _activeOrder на первый лид если не установлен
|
||
if(id==='manager_lead'){
|
||
var o=window._managerOrders[window._activeOrder];
|
||
if(!o||!o.isLead){
|
||
var fi=window._managerOrders.findIndex(function(x){return x.isLead;});
|
||
if(fi>=0) window._activeOrder=fi;
|
||
}
|
||
}
|
||
document.getElementById('screen').innerHTML=renderScreen(id);
|
||
document.getElementById('nav').innerHTML=navBar();
|
||
document.getElementById('screenSelect').value=id;
|
||
}
|
||
// Hash navigation — работает без сервера (file://)
|
||
var _hashMap={
|
||
'#home':'manager_home','#order':'manager_order','#tech':'manager_tech',
|
||
'#wizard':'manager_wizard','#schedule':'manager_schedule','#salary':'manager_salary',
|
||
'#calc':'manager_calc','#client':'manager_client','#lead':'manager_lead',
|
||
'#kb':'manager_kb'
|
||
};
|
||
var _startScreen = _hashMap[location.hash] || 'manager_home';
|
||
go(_startScreen);
|
||
window.addEventListener('hashchange',function(){ var s=_hashMap[location.hash]; if(s) go(s); });
|
||
</script>
|
||
</body>
|
||
</html>
|
||
|
||
|