Add assembler survey Mini App

This commit is contained in:
wasrusgen 2026-05-18 15:11:44 +03:00
parent fd93783adc
commit 54f111cfab

345
survey_webapp.html Normal file
View File

@ -0,0 +1,345 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<title>Анкета сборщика · ЗОВ</title>
<script src="https://telegram.org/js/telegram-web-app.js"></script>
<style>
:root {
--tg-bg: var(--tg-theme-bg-color, #ffffff);
--tg-text: var(--tg-theme-text-color, #1e293b);
--tg-hint: var(--tg-theme-hint-color, #64748b);
--tg-link: var(--tg-theme-link-color, #4f46e5);
--tg-btn: var(--tg-theme-button-color, #4f46e5);
--tg-btn-text: var(--tg-theme-button-text-color, #ffffff);
--tg-secondary: var(--tg-theme-secondary-bg-color, #f1f5f9);
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: -apple-system, 'Segoe UI', system-ui, sans-serif;
background: var(--tg-bg);
color: var(--tg-text);
padding: 16px;
padding-bottom: 80px;
font-size: 15px;
}
.section-title {
font-size: 11px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: .6px;
color: var(--tg-hint);
margin: 20px 0 8px;
}
.field-group {
background: var(--tg-secondary);
border-radius: 12px;
overflow: hidden;
}
.field-row {
display: flex;
flex-direction: column;
padding: 12px 14px;
border-bottom: 1px solid rgba(0,0,0,.06);
}
.field-row:last-child { border-bottom: none; }
.field-label {
font-size: 11px;
color: var(--tg-hint);
margin-bottom: 4px;
font-weight: 600;
}
.field-input {
background: transparent;
border: none;
outline: none;
font-size: 15px;
color: var(--tg-text);
width: 100%;
padding: 0;
}
.field-input::placeholder { color: var(--tg-hint); }
/* Tool list */
.tool-list {
background: var(--tg-secondary);
border-radius: 12px;
overflow: hidden;
}
.tool-item {
display: flex;
align-items: center;
gap: 12px;
padding: 13px 14px;
border-bottom: 1px solid rgba(0,0,0,.06);
cursor: pointer;
transition: background .1s;
-webkit-tap-highlight-color: transparent;
}
.tool-item:last-child { border-bottom: none; }
.tool-item:active { background: rgba(0,0,0,.04); }
.tool-check {
width: 22px;
height: 22px;
border-radius: 50%;
border: 2px solid #cbd5e1;
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
transition: all .15s;
}
.tool-item.checked .tool-check {
background: var(--tg-btn);
border-color: var(--tg-btn);
}
.tool-check svg { display: none; }
.tool-item.checked .tool-check svg { display: block; }
.tool-icon { font-size: 20px; flex-shrink: 0; }
.tool-name { flex: 1; font-size: 14px; line-height: 1.3; }
.tool-pts {
font-size: 12px;
color: var(--tg-hint);
background: rgba(0,0,0,.06);
padding: 2px 7px;
border-radius: 10px;
font-weight: 600;
}
/* Score card */
.score-card {
background: var(--tg-secondary);
border-radius: 12px;
padding: 16px;
display: flex;
align-items: center;
gap: 16px;
}
.score-circle {
width: 64px;
height: 64px;
border-radius: 50%;
border: 3px solid #e2e8f0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
flex-shrink: 0;
transition: border-color .3s;
}
.score-num { font-size: 18px; font-weight: 800; line-height: 1; }
.score-max { font-size: 10px; color: var(--tg-hint); }
.score-info { flex: 1; }
.score-cat { font-size: 16px; font-weight: 700; margin-bottom: 4px; }
.score-bar-wrap {
height: 6px;
background: #e2e8f0;
border-radius: 3px;
overflow: hidden;
}
.score-bar {
height: 100%;
border-radius: 3px;
transition: width .3s, background .3s;
width: 0%;
}
.score-pct { font-size: 11px; color: var(--tg-hint); margin-top: 3px; }
/* Error */
.error-msg {
display: none;
color: #dc2626;
font-size: 12px;
padding: 8px 14px;
background: #fef2f2;
border-radius: 8px;
margin-top: 8px;
}
.error-msg.show { display: block; }
/* Bottom button */
.submit-bar {
position: fixed;
bottom: 0; left: 0; right: 0;
padding: 12px 16px;
background: var(--tg-bg);
border-top: 1px solid rgba(0,0,0,.08);
}
.submit-btn {
width: 100%;
padding: 14px;
background: var(--tg-btn);
color: var(--tg-btn-text);
border: none;
border-radius: 12px;
font-size: 15px;
font-weight: 600;
cursor: pointer;
transition: opacity .15s;
}
.submit-btn:active { opacity: .8; }
.submit-btn:disabled { opacity: .45; cursor: not-allowed; }
</style>
</head>
<body>
<div class="section-title">Личные данные</div>
<div class="field-group">
<div class="field-row">
<div class="field-label">ФИО</div>
<input class="field-input" id="fio" type="text" placeholder="Иванов Иван Иванович"
autocomplete="name" autocorrect="off">
</div>
<div class="field-row">
<div class="field-label">Дата рождения</div>
<input class="field-input" id="dob" type="text" placeholder="15.03.1990"
inputmode="numeric" maxlength="10">
</div>
</div>
<div class="error-msg" id="err">Заполните ФИО и дату рождения</div>
<div class="section-title">Инструмент</div>
<div class="tool-list" id="toolList"></div>
<div class="section-title">Результат</div>
<div class="score-card">
<div class="score-circle" id="scoreCircle">
<span class="score-num" id="scoreNum">0</span>
<span class="score-max">/ 11</span>
</div>
<div class="score-info">
<div class="score-cat" id="scoreCat">🥉 Базовый</div>
<div class="score-bar-wrap">
<div class="score-bar" id="scoreBar"></div>
</div>
<div class="score-pct" id="scorePct">0% — заполните анкету</div>
</div>
</div>
<div class="submit-bar">
<button class="submit-btn" id="submitBtn" onclick="submit()">
Отправить анкету
</button>
</div>
<script>
const tg = window.Telegram.WebApp;
tg.ready();
tg.expand();
// ─── Инструменты ─────────────────────────────────────────
const TOOLS = [
{ key: "drill", icon: "🔩", name: "Шуруповёрт / дрель", pts: 1 },
{ key: "perforator", icon: "⚒", name: "Перфоратор", pts: 1 },
{ key: "jigsaw", icon: "🪚", name: "Электролобзик", pts: 1 },
{ key: "grinder", icon: "⚙️", name: "Угловая шлифмашина (болгарка)", pts: 1 },
{ key: "router", icon: "🔧", name: "Фрезер", pts: 1 },
{ key: "laser", icon: "📐", name: "Лазерный уровень", pts: 1 },
{ key: "vacuum", icon: "🧹", name: "Строительный пылесос", pts: 1 },
{ key: "clamps", icon: "🗜", name: "Струбцины / зажимы (набор)", pts: 1 },
{ key: "hand_tools", icon: "🔨", name: "Ручной инструмент (молоток, стамески, ключи)", pts: 1 },
{ key: "car", icon: "🚗", name: "Личный автомобиль", pts: 2 },
];
const MAX_SCORE = TOOLS.reduce((s, t) => s + t.pts, 0); // 11
const checked = {};
TOOLS.forEach(t => checked[t.key] = false);
// ─── Рендер чеклиста ────────────────────────────────────
const list = document.getElementById('toolList');
TOOLS.forEach(tool => {
const el = document.createElement('div');
el.className = 'tool-item';
el.id = 'item_' + tool.key;
el.innerHTML = `
<div class="tool-check">
<svg width="12" height="10" viewBox="0 0 12 10" fill="none">
<path d="M1 5L4.5 8.5L11 1" stroke="white" stroke-width="2" stroke-linecap="round"/>
</svg>
</div>
<span class="tool-icon">${tool.icon}</span>
<span class="tool-name">${tool.name}</span>
<span class="tool-pts">${tool.pts === 2 ? '+2 б' : '+1 б'}</span>
`;
el.addEventListener('click', () => toggle(tool.key, el));
list.appendChild(el);
});
function toggle(key, el) {
checked[key] = !checked[key];
el.classList.toggle('checked', checked[key]);
updateScore();
}
// ─── Живой подсчёт ──────────────────────────────────────
function updateScore() {
const score = TOOLS.reduce((s, t) => s + (checked[t.key] ? t.pts : 0), 0);
const pct = Math.round(score / MAX_SCORE * 100);
document.getElementById('scoreNum').textContent = score;
let cat, color;
if (pct >= 80) { cat = '🥇 Мастер'; color = '#16a34a'; }
else if (pct >= 50) { cat = '🥈 Специалист'; color = '#d97706'; }
else { cat = '🥉 Базовый'; color = '#64748b'; }
document.getElementById('scoreCat').textContent = cat;
document.getElementById('scoreBar').style.width = pct + '%';
document.getElementById('scoreBar').style.background = color;
document.getElementById('scorePct').textContent = pct + '%';
document.getElementById('scoreCircle').style.borderColor = color;
document.getElementById('scoreNum').style.color = color;
}
// ─── Автоформат ДР ──────────────────────────────────────
document.getElementById('dob').addEventListener('input', function(e) {
let v = e.target.value.replace(/\D/g, '');
if (v.length > 2) v = v.slice(0,2) + '.' + v.slice(2);
if (v.length > 5) v = v.slice(0,5) + '.' + v.slice(5);
e.target.value = v.slice(0, 10);
});
// ─── Отправка ───────────────────────────────────────────
function submit() {
const fio = document.getElementById('fio').value.trim();
const dob = document.getElementById('dob').value.trim();
const errEl = document.getElementById('err');
if (!fio || !dob) {
errEl.classList.add('show');
errEl.textContent = !fio
? 'Введите ФИО'
: 'Введите дату рождения (например: 15.03.1990)';
document.getElementById(fio ? 'dob' : 'fio').focus();
return;
}
errEl.classList.remove('show');
const score = TOOLS.reduce((s, t) => s + (checked[t.key] ? t.pts : 0), 0);
const pct = Math.round(score / MAX_SCORE * 100);
let category;
if (pct >= 80) category = '🥇 Мастер';
else if (pct >= 50) category = '🥈 Специалист';
else category = '🥉 Базовый';
const payload = JSON.stringify({
fio, dob, tools: checked, score, max_score: MAX_SCORE, pct, category
});
const btn = document.getElementById('submitBtn');
btn.disabled = true;
btn.textContent = 'Отправляем...';
tg.sendData(payload);
}
// Начальный рендер
updateScore();
</script>
</body>
</html>