/* ============================================================ Подбор техники — render, state, navigation, submit ============================================================ */ const Podbor = (function () { const STORAGE_KEY = "zov-podbor-v1"; const STEPS = ["intro", "categories", "context", "infra", "scenario", "brands", "summary"]; let state = loadState(); let root = null; let currentStep = "intro"; function loadState() { try { const raw = localStorage.getItem(STORAGE_KEY); if (raw) return JSON.parse(raw); } catch (e) {} return defaultState(); } function defaultState() { return { client_name: "", client_phone: "", address: "", budget_total: "", categories: [], // ['fridge','hob',...] niches: {}, // { fridge:{w,h,d}, hob:{w,d}, oven:{w,h,d}, dw:{w,h,d} } budget_by_cat: {},// { fridge:80000, hob:50000, ... } infra: { stove: "", vent: "" }, scenario: { family: "", cooking: "", techniques: [], guests: "" }, brands: {}, // { fridge: {Bosch:'preferred', Liebherr:'preferred'}, ... } notes: "", }; } function saveState() { try { localStorage.setItem(STORAGE_KEY, JSON.stringify(state)); } catch (e) {} } function update(patch) { state = { ...state, ...patch }; saveState(); } /* ===================== Render entry ===================== */ function mount(container) { root = container; document.body.classList.remove("has-bottom-nav"); const oldNav = document.getElementById("bottom-nav"); if (oldNav) oldNav.remove(); render(); } function go(step) { if (!STEPS.includes(step)) return; currentStep = step; render(); window.scrollTo({ top: 0, behavior: "smooth" }); haptic && haptic("impact"); } function render() { if (!root) return; root.innerHTML = ""; root.appendChild(renderHeader()); root.appendChild(renderProgress()); const screen = el(`
`); root.appendChild(screen); switch (currentStep) { case "intro": screen.appendChild(renderIntro()); break; case "categories": screen.appendChild(renderCategories()); break; case "context": screen.appendChild(renderContext()); break; case "infra": screen.appendChild(renderInfra()); break; case "scenario": screen.appendChild(renderScenario()); break; case "brands": screen.appendChild(renderBrands()); break; case "summary": screen.appendChild(renderSummary()); break; } } /* ===================== Header & progress ===================== */ function renderHeader() { const h = el(`
Подбор техники
`); h.querySelector(".podbor-back").addEventListener("click", () => { const idx = STEPS.indexOf(currentStep); if (idx <= 0) { // Выход из подбора в главный экран кабинета location.hash = ""; location.reload(); } else { go(STEPS[idx - 1]); } }); return h; } function renderProgress() { const idx = STEPS.indexOf(currentStep); const total = STEPS.length; const pct = Math.round(((idx + 1) / total) * 100); const labels = ["Старт", "Категории", "Контекст", "Инфра", "Сценарий", "Бренды", "Подбор"]; return el(`
${labels[idx]}${idx + 1}/${total}
`); } /* ===================== Step: intro ===================== */ function renderIntro() { const node = el(`

Подбор техники
для клиента

7 коротких шагов. Указываем категории, бюджет, инфраструктуру и предпочтения. Дальше AI сам соберёт предложение.

`); bindInputs(node); bindNav(node); return node; } /* ===================== Step: categories ===================== */ function renderCategories() { const grid = PODBOR_CATEGORIES.map(c => ` `).join(""); const node = el(`

Какую технику
подбираем?

Выберите все категории, что нужно подобрать клиенту.

${grid}
`); node.querySelectorAll(".cat-card").forEach(card => { card.addEventListener("click", () => { const cat = card.dataset.cat; const next = state.categories.includes(cat) ? state.categories.filter(x => x !== cat) : [...state.categories, cat]; update({ categories: next }); render(); }); }); bindNav(node); return node; } /* ===================== Step: context (ниши + бюджет по категориям) ===================== */ function renderContext() { const builtinCats = ["fridge", "hob", "oven", "dw"]; // встройка const niches = builtinCats.filter(c => state.categories.includes(c)).map(c => { const cat = PODBOR_CATEGORIES.find(x => x.key === c); const n = state.niches[c] || {}; return `
${cat.label}
`; }).join(""); const budgets = state.categories.map(c => { const cat = PODBOR_CATEGORIES.find(x => x.key === c); const v = state.budget_by_cat[c] || ""; return ` `; }).join(""); const node = el(`

Размеры
и бюджет

Если планируется встройка — укажите размеры ниш. Бюджет по категориям помогает AI распределить деньги.

${niches ? `
Ниши под встройку, мм
${niches}
` : ""} ${budgets ? `
Бюджет по категориям, ₽
${budgets}
` : ""}
`); node.querySelectorAll("[data-niche]").forEach(inp => { inp.addEventListener("input", e => { const [cat, dim] = e.target.dataset.niche.split("."); const next = { ...state.niches, [cat]: { ...(state.niches[cat] || {}), [dim]: e.target.value } }; update({ niches: next }); }); }); node.querySelectorAll("[data-budget]").forEach(inp => { inp.addEventListener("input", e => { const cat = e.target.dataset.budget; const next = { ...state.budget_by_cat, [cat]: e.target.value }; update({ budget_by_cat: next }); }); }); bindNav(node); return node; } /* ===================== Step: infra ===================== */ function renderInfra() { const node = el(`

Инфраструктура
кухни

Газ или электрика — главный вопрос для варочной. Шахта вентиляции — для вытяжки.

Подключение варочной
${PODBOR_INFRA.stove.map(o => ` `).join("")}
Вентиляция для вытяжки
${PODBOR_INFRA.vent.map(o => ` `).join("")}
`); node.querySelectorAll("[data-infra]").forEach(b => { b.addEventListener("click", () => { update({ infra: { ...state.infra, [b.dataset.infra]: b.dataset.val } }); render(); }); }); bindNav(node); return node; } /* ===================== Step: scenario ===================== */ function renderScenario() { const node = el(`

Сценарий
использования

Семья с детьми готовит иначе, чем пара. AI учтёт это в подборе.

Состав семьи
${PODBOR_FAMILY.map(o => ` `).join("")}
Частота готовки
${PODBOR_COOKING.map(o => ` `).join("")}
Любимые техники приготовления
${PODBOR_TECHNIQUES.map(o => ` `).join("")}
Можно несколько
`); node.querySelectorAll("[data-scenario]").forEach(b => { b.addEventListener("click", () => { update({ scenario: { ...state.scenario, [b.dataset.scenario]: b.dataset.val } }); render(); }); }); node.querySelectorAll("[data-tech]").forEach(b => { b.addEventListener("click", () => { const cur = state.scenario.techniques || []; const key = b.dataset.tech; const next = cur.includes(key) ? cur.filter(x => x !== key) : [...cur, key]; update({ scenario: { ...state.scenario, techniques: next } }); render(); }); }); bindNav(node); return node; } /* ===================== Step: brands ===================== */ function renderBrands() { if (!state.categories.length) { return el(`
Сначала выберите категории.
`); } const blocks = state.categories.map(catKey => { const cat = PODBOR_CATEGORIES.find(x => x.key === catKey); const brands = PODBOR_BRANDS[catKey] || { premium: [], middle: [], budget: [] }; const catState = state.brands[catKey] || {}; const tierBlock = (tier) => `
${PODBOR_BUDGET_TIERS.find(t => t.key === tier).label}
${(brands[tier] || []).map(b => { const status = catState[b] || "none"; return ``; }).join("")}
`; return `
${cat.label}
★ Тап — предпочтительно · ✓ Двойной — допустимо · — Третий — снять
${tierBlock("premium")}${tierBlock("middle")}${tierBlock("budget")}
`; }).join(""); const node = el(`

Бренды
по категориям

Какие марки уважаете, какие — допустимы. AI сначала пробует preferred.

${blocks}
`); node.querySelectorAll(".chip[data-brand]").forEach(c => { c.addEventListener("click", () => { const catKey = c.dataset.cat, brand = c.dataset.brand; const cur = (state.brands[catKey] || {})[brand] || "none"; const nextStatus = cur === "none" ? "preferred" : cur === "preferred" ? "acceptable" : "none"; const catBrands = { ...(state.brands[catKey] || {}) }; if (nextStatus === "none") delete catBrands[brand]; else catBrands[brand] = nextStatus; update({ brands: { ...state.brands, [catKey]: catBrands } }); render(); }); }); bindNav(node); return node; } /* ===================== Step: summary + submit ===================== */ function renderSummary() { const node = el(`

Готово
к подбору

Проверьте и отправьте — AI вернёт предложение в чат с ботом.

Клиент${state.client_name || "—"}
Бюджет${state.budget_total ? state.budget_total + " ₽" : "—"}
Категорий${state.categories.length}
Семья${PODBOR_FAMILY.find(f => f.key === state.scenario.family)?.label || "—"}
Готовка${PODBOR_COOKING.find(f => f.key === state.scenario.cooking)?.label || "—"}
Подключение${PODBOR_INFRA.stove.find(f => f.key === state.infra.stove)?.label || "—"}
`); bindInputs(node); bindNav(node); node.querySelector("#submitBtn").addEventListener("click", () => onSubmit(node)); return node; } /* ===================== Submit ===================== */ async function onSubmit(node) { const btn = node.querySelector("#submitBtn"); const result = node.querySelector("#submitResult"); btn.disabled = true; btn.innerHTML = ' AI думает...'; result.innerHTML = ""; if (!BACKEND_URL) { result.innerHTML = `
BACKEND_URL не настроен (dev-режим).
`; btn.disabled = false; btn.textContent = "Отправить · AI подберёт"; return; } try { const res = await fetch(`${BACKEND_URL}?path=podbor`, { method: "POST", body: JSON.stringify({ initData: tg?.initData || "", checklist: state, client_name: state.client_name, }), }); const data = await res.json(); if (data.error) { result.innerHTML = `
Ошибка: ${data.error}
`; } else { result.innerHTML = `
${ICONS.check}
Подбор отправлен в чат бота
Лид #${(data.id || "").slice(0, 6)} · откройте Telegram
`; haptic && haptic("success"); } } catch (e) { result.innerHTML = `
Сеть: ${e.message}
`; } btn.disabled = false; btn.textContent = "Отправить ещё раз"; } /* ===================== Helpers ===================== */ function bindInputs(node) { node.querySelectorAll("[data-bind]").forEach(inp => { inp.addEventListener("input", e => { update({ [e.target.dataset.bind]: e.target.value }); }); }); } function bindNav(node) { node.querySelectorAll("[data-go]").forEach(b => { b.addEventListener("click", () => go(b.dataset.go)); }); } return { mount, go, getState: () => state, reset: () => { state = defaultState(); saveState(); render(); } }; })();