mirror of
https://github.com/wasrusgen/zov-tech.git
synced 2026-06-03 15:04:50 +00:00
feat(podbor): drop niches; price-range from-to per cat; ventilation Y/N; priorities multi-select; brand tiers as color (no labels)
This commit is contained in:
parent
571297c017
commit
129046de07
@ -19,19 +19,6 @@ const PODBOR_BUDGET_TIERS = [
|
||||
{ key: "budget", label: "Бюджет", hint: "только нужное" },
|
||||
];
|
||||
|
||||
const PODBOR_FAMILY = [
|
||||
{ key: "single", label: "1 взрослый" },
|
||||
{ key: "couple", label: "Пара" },
|
||||
{ key: "family", label: "Семья с детьми" },
|
||||
{ key: "multigen", label: "2+ поколения" },
|
||||
];
|
||||
|
||||
const PODBOR_COOKING = [
|
||||
{ key: "daily", label: "Ежедневно" },
|
||||
{ key: "weekly", label: "3–5 раз в неделю" },
|
||||
{ key: "rare", label: "По выходным или реже" },
|
||||
];
|
||||
|
||||
const PODBOR_INFRA = {
|
||||
stove: [
|
||||
{ key: "induction", label: "Индукция / 380 В" },
|
||||
@ -40,19 +27,19 @@ const PODBOR_INFRA = {
|
||||
{ key: "any", label: "Не знаю / любой" },
|
||||
],
|
||||
vent: [
|
||||
{ key: "shaft", label: "Шахта вентиляции есть" },
|
||||
{ key: "no_shaft", label: "Только рециркуляция" },
|
||||
{ key: "unknown", label: "Не знаю" },
|
||||
{ key: "yes", label: "Да — есть выводы в вентиляцию" },
|
||||
{ key: "no", label: "Нет — рециркуляция с угольным фильтром" },
|
||||
{ key: "unknown", label: "Не знаю — менеджер уточнит" },
|
||||
],
|
||||
};
|
||||
|
||||
const PODBOR_TECHNIQUES = [
|
||||
{ key: "bake", label: "Выпечка" },
|
||||
{ key: "steam", label: "На пару" },
|
||||
{ key: "grill", label: "Гриль" },
|
||||
{ key: "wok", label: "Wok / стир-фрай" },
|
||||
{ key: "low_t", label: "Низкотемпературное" },
|
||||
{ key: "smart", label: "Умные режимы / Smart" },
|
||||
const PODBOR_PRIORITIES = [
|
||||
{ key: "balance", label: "Цена / качество" },
|
||||
{ key: "reviews", label: "Отзывы" },
|
||||
{ key: "popular", label: "Популярность бренда" },
|
||||
{ key: "design", label: "Дизайн и цвет" },
|
||||
{ key: "tech", label: "Технологичность" },
|
||||
{ key: "service", label: "Сервис и гарантия" },
|
||||
];
|
||||
|
||||
/* Бренды для каждой категории — для чипов с тирами.
|
||||
|
||||
@ -258,6 +258,72 @@
|
||||
text-align: right; color: var(--ink);
|
||||
}
|
||||
|
||||
/* ----- Price range (от — до по категориям) ----- */
|
||||
.price-list { display: flex; flex-direction: column; gap: var(--s3); }
|
||||
|
||||
.price-row {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.price-label {
|
||||
font-family: var(--font-ui);
|
||||
font-size: 13.5px;
|
||||
font-weight: 500;
|
||||
color: var(--ink-2);
|
||||
}
|
||||
|
||||
.price-inputs {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.price-inputs input {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
width: 100%;
|
||||
font-family: var(--font-mono);
|
||||
font-size: 13px;
|
||||
padding: 8px 10px;
|
||||
text-align: center;
|
||||
background: var(--paper);
|
||||
border: 1px solid var(--line);
|
||||
border-radius: var(--r-tag);
|
||||
color: var(--ink);
|
||||
}
|
||||
|
||||
.price-inputs .dash {
|
||||
font-family: var(--font-mono);
|
||||
color: var(--muted);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.price-inputs .rub {
|
||||
font-family: var(--font-mono);
|
||||
font-size: 12px;
|
||||
color: var(--muted);
|
||||
flex-shrink: 0;
|
||||
padding-left: 2px;
|
||||
}
|
||||
|
||||
.price-total {
|
||||
margin-top: var(--s2);
|
||||
padding-top: var(--s2);
|
||||
border-top: 1px solid var(--line);
|
||||
font-family: var(--font-mono);
|
||||
font-size: 11px;
|
||||
letter-spacing: 0.10em;
|
||||
text-transform: uppercase;
|
||||
color: var(--ink-2);
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.price-total strong { font-family: var(--font-ui); font-size: 14px; color: var(--ink); font-weight: 600; }
|
||||
.price-total.muted { color: var(--muted); }
|
||||
|
||||
/* ----- Option chips ----- */
|
||||
.opt-list { display: flex; flex-wrap: wrap; gap: 6px; }
|
||||
|
||||
@ -283,49 +349,86 @@
|
||||
}
|
||||
|
||||
/* ----- Brand chips ----- */
|
||||
.tier-row { display: flex; flex-direction: column; gap: 6px; padding: 8px 0; border-top: 1px solid var(--line); }
|
||||
.tier-row:first-child { border-top: none; padding-top: 0; }
|
||||
/* Тиры (Premium/Middle/Budget) различаются цветом, без явных текстовых ярлыков.
|
||||
Внутренне храним tier для аналитики «температуры» клиента. */
|
||||
|
||||
.tier-label {
|
||||
font-family: var(--font-mono);
|
||||
font-size: 9.5px;
|
||||
font-weight: 500;
|
||||
letter-spacing: 0.14em;
|
||||
text-transform: uppercase;
|
||||
color: var(--muted);
|
||||
.brand-chips {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
padding: 6px 0;
|
||||
border-top: 1px dashed var(--line);
|
||||
}
|
||||
|
||||
.brand-chips { display: flex; flex-wrap: wrap; gap: 6px; }
|
||||
.brand-chips:first-of-type {
|
||||
border-top: none;
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
.chip {
|
||||
font-family: var(--font-ui);
|
||||
font-size: 12.5px;
|
||||
font-weight: 500;
|
||||
padding: 6px 10px;
|
||||
padding: 6px 11px;
|
||||
border-radius: var(--r-tag);
|
||||
border: 1px solid var(--line-strong);
|
||||
background: var(--paper);
|
||||
color: var(--muted);
|
||||
border: 1px solid;
|
||||
cursor: pointer;
|
||||
transition: all 0.12s;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.chip.status-preferred {
|
||||
background: var(--accent-2);
|
||||
/* Базовый цвет покоя по тирам — без слов, только тёплый/холодный оттенок */
|
||||
.chip.tier-premium {
|
||||
background: var(--paper);
|
||||
color: var(--accent-2); /* walnut */
|
||||
border-color: rgba(107, 74, 43, 0.35);
|
||||
}
|
||||
|
||||
.chip.tier-middle {
|
||||
background: var(--paper);
|
||||
color: var(--ink-2);
|
||||
border-color: var(--line-strong);
|
||||
}
|
||||
|
||||
.chip.tier-budget {
|
||||
background: var(--paper);
|
||||
color: var(--muted);
|
||||
border-color: var(--line);
|
||||
}
|
||||
|
||||
/* Состояние preferred — заливка цветом тира */
|
||||
.chip.tier-premium.status-preferred {
|
||||
background: var(--accent-2); /* walnut */
|
||||
color: var(--paper);
|
||||
border-color: var(--accent-2);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.chip.tier-middle.status-preferred {
|
||||
background: var(--ink);
|
||||
color: var(--paper);
|
||||
border-color: var(--ink);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.chip.tier-budget.status-preferred {
|
||||
background: var(--muted);
|
||||
color: var(--paper);
|
||||
border-color: var(--muted);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.chip.status-preferred::before { content: "★ "; }
|
||||
|
||||
/* Состояние acceptable — обводка цветом тира, прозрачная заливка */
|
||||
.chip.status-acceptable {
|
||||
background: var(--paper-2);
|
||||
color: var(--ink);
|
||||
border-color: var(--accent-2);
|
||||
}
|
||||
|
||||
.chip.tier-premium.status-acceptable { border-color: var(--accent-2); color: var(--accent-2); }
|
||||
.chip.tier-middle.status-acceptable { border-color: var(--ink); color: var(--ink); }
|
||||
.chip.tier-budget.status-acceptable { border-color: var(--muted); color: var(--ink-2); }
|
||||
|
||||
.chip.status-acceptable::before { content: "✓ "; }
|
||||
|
||||
/* ----- Summary ----- */
|
||||
|
||||
@ -3,8 +3,8 @@
|
||||
============================================================ */
|
||||
|
||||
const Podbor = (function () {
|
||||
const STORAGE_KEY = "zov-podbor-v1";
|
||||
const STEPS = ["intro", "categories", "context", "infra", "scenario", "brands", "summary"];
|
||||
const STORAGE_KEY = "zov-podbor-v2";
|
||||
const STEPS = ["intro", "categories", "pricing", "infra", "priorities", "brands", "summary"];
|
||||
|
||||
let state = loadState();
|
||||
let root = null;
|
||||
@ -23,13 +23,11 @@ const Podbor = (function () {
|
||||
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, ... }
|
||||
categories: [], // ['fridge','hob',...]
|
||||
price_ranges: {}, // { fridge: { from: 50000, to: 120000 }, ... }
|
||||
infra: { stove: "", vent: "" },
|
||||
scenario: { family: "", cooking: "", techniques: [], guests: "" },
|
||||
brands: {}, // { fridge: {Bosch:'preferred', Liebherr:'preferred'}, ... }
|
||||
priorities: [], // ['balance','reviews',...]
|
||||
brands: {}, // { fridge: {Bosch:'preferred',...}, ... }
|
||||
notes: "",
|
||||
};
|
||||
}
|
||||
@ -72,9 +70,9 @@ const Podbor = (function () {
|
||||
switch (currentStep) {
|
||||
case "intro": screen.appendChild(renderIntro()); break;
|
||||
case "categories": screen.appendChild(renderCategories()); break;
|
||||
case "context": screen.appendChild(renderContext()); break;
|
||||
case "pricing": screen.appendChild(renderPricing()); break;
|
||||
case "infra": screen.appendChild(renderInfra()); break;
|
||||
case "scenario": screen.appendChild(renderScenario()); break;
|
||||
case "priorities": screen.appendChild(renderPriorities()); break;
|
||||
case "brands": screen.appendChild(renderBrands()); break;
|
||||
case "summary": screen.appendChild(renderSummary()); break;
|
||||
}
|
||||
@ -107,7 +105,7 @@ const Podbor = (function () {
|
||||
const idx = STEPS.indexOf(currentStep);
|
||||
const total = STEPS.length;
|
||||
const pct = Math.round(((idx + 1) / total) * 100);
|
||||
const labels = ["Старт", "Категории", "Контекст", "Инфра", "Сценарий", "Бренды", "Подбор"];
|
||||
const labels = ["Старт", "Категории", "Цена", "Инфра", "Приоритеты", "Бренды", "Подбор"];
|
||||
return el(`
|
||||
<div class="podbor-progress">
|
||||
<div class="podbor-progress-bar"><div class="bar" style="width:${pct}%"></div></div>
|
||||
@ -124,7 +122,7 @@ const Podbor = (function () {
|
||||
const node = el(`
|
||||
<section class="podbor-step">
|
||||
<h2 class="display-title">Подбор техники<br><span class="accent">для клиента</span></h2>
|
||||
<p class="lede">7 коротких шагов. Указываем категории, бюджет, инфраструктуру и предпочтения. Дальше AI сам соберёт предложение.</p>
|
||||
<p class="lede">7 коротких шагов. Категории, ценовой коридор, инфраструктура и предпочтения. AI соберёт предложение.</p>
|
||||
|
||||
<div class="form-row">
|
||||
<label class="field">
|
||||
@ -132,15 +130,11 @@ const Podbor = (function () {
|
||||
<input type="text" data-bind="client_name" value="${state.client_name || ""}" placeholder="Например: А. Пестова">
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-row two-col">
|
||||
<div class="form-row">
|
||||
<label class="field">
|
||||
<span class="field-label">Телефон</span>
|
||||
<input type="tel" data-bind="client_phone" value="${state.client_phone || ""}" placeholder="+7 ...">
|
||||
</label>
|
||||
<label class="field">
|
||||
<span class="field-label">Бюджет на технику, ₽</span>
|
||||
<input type="number" data-bind="budget_total" value="${state.budget_total || ""}" placeholder="например 350000">
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="podbor-cta-row">
|
||||
@ -189,86 +183,86 @@ const Podbor = (function () {
|
||||
return node;
|
||||
}
|
||||
|
||||
/* ===================== Step: context (ниши + бюджет по категориям) ===================== */
|
||||
/* ===================== Step: pricing (ценовой коридор по категориям) ===================== */
|
||||
|
||||
function renderContext() {
|
||||
const builtinCats = ["fridge", "hob", "oven", "dw"]; // встройка
|
||||
const niches = builtinCats.filter(c => state.categories.includes(c)).map(c => {
|
||||
function renderPricing() {
|
||||
if (!state.categories.length) {
|
||||
return el(`
|
||||
<section class="podbor-step">
|
||||
<div class="empty">Сначала выберите категории.</div>
|
||||
<div class="podbor-cta-row">
|
||||
<button class="btn-secondary" data-go="categories">Назад</button>
|
||||
</div>
|
||||
</section>
|
||||
`);
|
||||
}
|
||||
// Подсчёт суммы коридоров
|
||||
let totalFrom = 0, totalTo = 0;
|
||||
state.categories.forEach(c => {
|
||||
const r = state.price_ranges[c] || {};
|
||||
if (r.from) totalFrom += parseInt(r.from, 10) || 0;
|
||||
if (r.to) totalTo += parseInt(r.to, 10) || 0;
|
||||
});
|
||||
|
||||
const rows = state.categories.map(c => {
|
||||
const cat = PODBOR_CATEGORIES.find(x => x.key === c);
|
||||
const n = state.niches[c] || {};
|
||||
const r = state.price_ranges[c] || {};
|
||||
return `
|
||||
<div class="niche-row">
|
||||
<div class="niche-label">${cat.label}</div>
|
||||
<div class="niche-inputs">
|
||||
<input type="number" data-niche="${c}.w" value="${n.w || ""}" placeholder="Ш">
|
||||
<input type="number" data-niche="${c}.h" value="${n.h || ""}" placeholder="В">
|
||||
<input type="number" data-niche="${c}.d" value="${n.d || ""}" placeholder="Г">
|
||||
<div class="price-row">
|
||||
<div class="price-label">${cat.label}</div>
|
||||
<div class="price-inputs">
|
||||
<input type="number" inputmode="numeric" data-price="${c}.from" value="${r.from || ""}" placeholder="от">
|
||||
<span class="dash">—</span>
|
||||
<input type="number" inputmode="numeric" data-price="${c}.to" value="${r.to || ""}" placeholder="до">
|
||||
<span class="rub">₽</span>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}).join("");
|
||||
|
||||
const budgets = state.categories.map(c => {
|
||||
const cat = PODBOR_CATEGORIES.find(x => x.key === c);
|
||||
const v = state.budget_by_cat[c] || "";
|
||||
return `
|
||||
<label class="field-inline">
|
||||
<span>${cat.label}</span>
|
||||
<input type="number" data-budget="${c}" value="${v}" placeholder="₽">
|
||||
</label>
|
||||
`;
|
||||
}).join("");
|
||||
const totalLine = (totalFrom || totalTo)
|
||||
? `<div class="price-total">Итого: <strong>${formatRub(totalFrom)} — ${formatRub(totalTo)} ₽</strong></div>`
|
||||
: `<div class="price-total muted">Сумма посчитается автоматически</div>`;
|
||||
|
||||
const node = el(`
|
||||
<section class="podbor-step">
|
||||
<h2 class="display-title">Размеры<br><span class="accent">и бюджет</span></h2>
|
||||
<p class="lede">Если планируется встройка — укажите размеры ниш. Бюджет по категориям помогает AI распределить деньги.</p>
|
||||
|
||||
${niches ? `
|
||||
<div class="block">
|
||||
<div class="block-head">Ниши под встройку, мм</div>
|
||||
<div class="niche-list">${niches}</div>
|
||||
</div>
|
||||
` : ""}
|
||||
|
||||
${budgets ? `
|
||||
<div class="block">
|
||||
<div class="block-head">Бюджет по категориям, ₽</div>
|
||||
<div class="budget-list">${budgets}</div>
|
||||
</div>
|
||||
` : ""}
|
||||
|
||||
<h2 class="display-title">Ценовой<br><span class="accent">коридор</span></h2>
|
||||
<p class="lede">«От — До» по каждой категории. AI подберёт варианты, которые попадают в коридор и совокупно укладываются в общий бюджет клиента.</p>
|
||||
<div class="block">
|
||||
<div class="block-head">По категориям, ₽</div>
|
||||
<div class="price-list">${rows}</div>
|
||||
${totalLine}
|
||||
</div>
|
||||
<div class="podbor-cta-row">
|
||||
<button class="btn-secondary" data-go="categories">Назад</button>
|
||||
<button class="btn-primary" data-go="infra">Дальше</button>
|
||||
</div>
|
||||
</section>
|
||||
`);
|
||||
node.querySelectorAll("[data-niche]").forEach(inp => {
|
||||
node.querySelectorAll("[data-price]").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 });
|
||||
const [cat, key] = e.target.dataset.price.split(".");
|
||||
const next = { ...state.price_ranges, [cat]: { ...(state.price_ranges[cat] || {}), [key]: e.target.value } };
|
||||
update({ price_ranges: next });
|
||||
render();
|
||||
});
|
||||
});
|
||||
bindNav(node);
|
||||
return node;
|
||||
}
|
||||
|
||||
function formatRub(n) {
|
||||
if (!n) return "—";
|
||||
return Math.round(n).toString().replace(/\B(?=(\d{3})+(?!\d))/g, " ");
|
||||
}
|
||||
|
||||
/* ===================== Step: infra ===================== */
|
||||
|
||||
function renderInfra() {
|
||||
const node = el(`
|
||||
<section class="podbor-step">
|
||||
<h2 class="display-title">Инфраструктура<br><span class="accent">кухни</span></h2>
|
||||
<p class="lede">Газ или электрика — главный вопрос для варочной. Шахта вентиляции — для вытяжки.</p>
|
||||
<p class="lede">Газ или электрика — определит тип варочной (индукция / стеклокерамика / газ). Подключение вытяжки — нужны ли выводы или угольный фильтр.</p>
|
||||
<div class="block">
|
||||
<div class="block-head">Подключение варочной</div>
|
||||
<div class="opt-list">
|
||||
@ -278,16 +272,17 @@ const Podbor = (function () {
|
||||
</div>
|
||||
</div>
|
||||
<div class="block">
|
||||
<div class="block-head">Вентиляция для вытяжки</div>
|
||||
<div class="block-head">Вытяжка → внутридомовая вентиляция?</div>
|
||||
<div class="opt-list">
|
||||
${PODBOR_INFRA.vent.map(o => `
|
||||
<button class="opt${state.infra.vent === o.key ? " on" : ""}" data-infra="vent" data-val="${o.key}">${o.label}</button>
|
||||
`).join("")}
|
||||
</div>
|
||||
<div class="hint">Если «Нет» — менеджер закладывает угольный фильтр. Если «Да» — заранее планируем выводы.</div>
|
||||
</div>
|
||||
<div class="podbor-cta-row">
|
||||
<button class="btn-secondary" data-go="context">Назад</button>
|
||||
<button class="btn-primary" data-go="scenario">Дальше</button>
|
||||
<button class="btn-secondary" data-go="pricing">Назад</button>
|
||||
<button class="btn-primary" data-go="priorities">Дальше</button>
|
||||
</div>
|
||||
</section>
|
||||
`);
|
||||
@ -301,60 +296,34 @@ const Podbor = (function () {
|
||||
return node;
|
||||
}
|
||||
|
||||
/* ===================== Step: scenario ===================== */
|
||||
/* ===================== Step: priorities (что важно при выборе) ===================== */
|
||||
|
||||
function renderScenario() {
|
||||
function renderPriorities() {
|
||||
const node = el(`
|
||||
<section class="podbor-step">
|
||||
<h2 class="display-title">Сценарий<br><span class="accent">использования</span></h2>
|
||||
<p class="lede">Семья с детьми готовит иначе, чем пара. AI учтёт это в подборе.</p>
|
||||
|
||||
<h2 class="display-title">Что важно<br><span class="accent">при выборе?</span></h2>
|
||||
<p class="lede">Бюджет уже задал коридор. Здесь — что AI должен использовать как тай-брейк, когда варианты примерно равны по цене.</p>
|
||||
<div class="block">
|
||||
<div class="block-head">Состав семьи</div>
|
||||
<div class="block-head">Приоритеты</div>
|
||||
<div class="opt-list">
|
||||
${PODBOR_FAMILY.map(o => `
|
||||
<button class="opt${state.scenario.family === o.key ? " on" : ""}" data-scenario="family" data-val="${o.key}">${o.label}</button>
|
||||
${PODBOR_PRIORITIES.map(o => `
|
||||
<button class="opt${(state.priorities || []).includes(o.key) ? " on" : ""}" data-pri="${o.key}">${o.label}</button>
|
||||
`).join("")}
|
||||
</div>
|
||||
<div class="hint">Можно несколько · в порядке выбора</div>
|
||||
</div>
|
||||
|
||||
<div class="block">
|
||||
<div class="block-head">Частота готовки</div>
|
||||
<div class="opt-list">
|
||||
${PODBOR_COOKING.map(o => `
|
||||
<button class="opt${state.scenario.cooking === o.key ? " on" : ""}" data-scenario="cooking" data-val="${o.key}">${o.label}</button>
|
||||
`).join("")}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="block">
|
||||
<div class="block-head">Любимые техники приготовления</div>
|
||||
<div class="opt-list">
|
||||
${PODBOR_TECHNIQUES.map(o => `
|
||||
<button class="opt${(state.scenario.techniques || []).includes(o.key) ? " on" : ""}" data-tech="${o.key}">${o.label}</button>
|
||||
`).join("")}
|
||||
</div>
|
||||
<div class="hint">Можно несколько</div>
|
||||
</div>
|
||||
|
||||
<div class="podbor-cta-row">
|
||||
<button class="btn-secondary" data-go="infra">Назад</button>
|
||||
<button class="btn-primary" data-go="brands">Дальше</button>
|
||||
</div>
|
||||
</section>
|
||||
`);
|
||||
node.querySelectorAll("[data-scenario]").forEach(b => {
|
||||
node.querySelectorAll("[data-pri]").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 cur = state.priorities || [];
|
||||
const key = b.dataset.pri;
|
||||
const next = cur.includes(key) ? cur.filter(x => x !== key) : [...cur, key];
|
||||
update({ scenario: { ...state.scenario, techniques: next } });
|
||||
update({ priorities: next });
|
||||
render();
|
||||
});
|
||||
});
|
||||
@ -372,22 +341,20 @@ const Podbor = (function () {
|
||||
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) => `
|
||||
<div class="tier-row">
|
||||
<div class="tier-label">${PODBOR_BUDGET_TIERS.find(t => t.key === tier).label}</div>
|
||||
<div class="brand-chips">
|
||||
${(brands[tier] || []).map(b => {
|
||||
const status = catState[b] || "none";
|
||||
return `<button class="chip status-${status}" data-cat="${catKey}" data-brand="${b}">${b}</button>`;
|
||||
}).join("")}
|
||||
</div>
|
||||
// Тиры остаются в данных (для аналитики «температуры» клиента),
|
||||
// но визуально просто разный цветовой оттенок чипа — без явного ярлыка.
|
||||
const tierGroup = (tier) => `
|
||||
<div class="brand-chips brand-tier-${tier}">
|
||||
${(brands[tier] || []).map(b => {
|
||||
const status = catState[b] || "none";
|
||||
return `<button class="chip tier-${tier} status-${status}" data-cat="${catKey}" data-brand="${b}" data-tier="${tier}">${b}</button>`;
|
||||
}).join("")}
|
||||
</div>
|
||||
`;
|
||||
return `
|
||||
<div class="block">
|
||||
<div class="block-head">${cat.label}</div>
|
||||
<div class="hint">★ Тап — предпочтительно · ✓ Двойной — допустимо · — Третий — снять</div>
|
||||
${tierBlock("premium")}${tierBlock("middle")}${tierBlock("budget")}
|
||||
${tierGroup("premium")}${tierGroup("middle")}${tierGroup("budget")}
|
||||
</div>
|
||||
`;
|
||||
}).join("");
|
||||
@ -395,10 +362,10 @@ const Podbor = (function () {
|
||||
const node = el(`
|
||||
<section class="podbor-step">
|
||||
<h2 class="display-title">Бренды<br><span class="accent">по категориям</span></h2>
|
||||
<p class="lede">Какие марки уважаете, какие — допустимы. AI сначала пробует preferred.</p>
|
||||
<p class="lede">Тап — ★ предпочтительно. Дабл — ✓ допустимо. Третий — снять. AI сначала пробует ★, потом ✓.</p>
|
||||
${blocks}
|
||||
<div class="podbor-cta-row">
|
||||
<button class="btn-secondary" data-go="scenario">Назад</button>
|
||||
<button class="btn-secondary" data-go="priorities">Назад</button>
|
||||
<button class="btn-primary" data-go="summary">Дальше</button>
|
||||
</div>
|
||||
</section>
|
||||
@ -422,17 +389,30 @@ const Podbor = (function () {
|
||||
/* ===================== Step: summary + submit ===================== */
|
||||
|
||||
function renderSummary() {
|
||||
let totalFrom = 0, totalTo = 0;
|
||||
state.categories.forEach(c => {
|
||||
const r = state.price_ranges[c] || {};
|
||||
totalFrom += parseInt(r.from || "0", 10) || 0;
|
||||
totalTo += parseInt(r.to || "0", 10) || 0;
|
||||
});
|
||||
const totalRange = (totalFrom || totalTo)
|
||||
? `${formatRub(totalFrom)} — ${formatRub(totalTo)} ₽`
|
||||
: "—";
|
||||
const priorityLabels = (state.priorities || [])
|
||||
.map(k => PODBOR_PRIORITIES.find(p => p.key === k)?.label)
|
||||
.filter(Boolean).join(" · ");
|
||||
|
||||
const node = el(`
|
||||
<section class="podbor-step">
|
||||
<h2 class="display-title">Готово<br><span class="accent">к подбору</span></h2>
|
||||
<p class="lede">Проверьте и отправьте — AI вернёт предложение в чат с ботом.</p>
|
||||
<div class="block summary-block">
|
||||
<div class="kv"><span>Клиент</span><strong>${state.client_name || "—"}</strong></div>
|
||||
<div class="kv"><span>Бюджет</span><strong>${state.budget_total ? state.budget_total + " ₽" : "—"}</strong></div>
|
||||
<div class="kv"><span>Категорий</span><strong>${state.categories.length}</strong></div>
|
||||
<div class="kv"><span>Семья</span><strong>${PODBOR_FAMILY.find(f => f.key === state.scenario.family)?.label || "—"}</strong></div>
|
||||
<div class="kv"><span>Готовка</span><strong>${PODBOR_COOKING.find(f => f.key === state.scenario.cooking)?.label || "—"}</strong></div>
|
||||
<div class="kv"><span>Ценовой коридор</span><strong>${totalRange}</strong></div>
|
||||
<div class="kv"><span>Подключение</span><strong>${PODBOR_INFRA.stove.find(f => f.key === state.infra.stove)?.label || "—"}</strong></div>
|
||||
<div class="kv"><span>Вентиляция</span><strong>${PODBOR_INFRA.vent.find(f => f.key === state.infra.vent)?.label || "—"}</strong></div>
|
||||
<div class="kv"><span>Приоритеты</span><strong>${priorityLabels || "—"}</strong></div>
|
||||
</div>
|
||||
|
||||
<label class="field">
|
||||
|
||||
@ -12,8 +12,8 @@
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Geist:wght@400;500;600&family=Newsreader:ital,wght@0,400..600;1,400..600&family=Instrument+Serif:ital@0;1&family=JetBrains+Mono:wght@400;500&display=swap">
|
||||
<script src="https://telegram.org/js/telegram-web-app.js"></script>
|
||||
<link rel="stylesheet" href="assets/styles.css?v=20260509l">
|
||||
<link rel="stylesheet" href="assets/podbor.css?v=20260509l">
|
||||
<link rel="stylesheet" href="assets/styles.css?v=20260509m">
|
||||
<link rel="stylesheet" href="assets/podbor.css?v=20260509m">
|
||||
</head>
|
||||
<body>
|
||||
<main id="app">
|
||||
@ -21,9 +21,9 @@
|
||||
<div class="spinner"></div>
|
||||
</div>
|
||||
</main>
|
||||
<script src="assets/icons.js?v=20260509l"></script>
|
||||
<script src="assets/podbor.config.js?v=20260509l"></script>
|
||||
<script src="assets/podbor.js?v=20260509l"></script>
|
||||
<script src="assets/app.js?v=20260509l"></script>
|
||||
<script src="assets/icons.js?v=20260509m"></script>
|
||||
<script src="assets/podbor.config.js?v=20260509m"></script>
|
||||
<script src="assets/podbor.js?v=20260509m"></script>
|
||||
<script src="assets/app.js?v=20260509m"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user