mirror of
https://github.com/wasrusgen/zov-tech.git
synced 2026-06-03 13:24:48 +00:00
New modules: - expeditor_dashboard.js: route list (date-grouped) + act detail + signature screen - invoice.js: 3-col chip room picker, 2500₽ base + 1000₽ extra logic - act4.js, measurer_dashboard.js, finance_summary.js, client_timeline.js, feedback.js, staff_roster.js Backend: - /api/expeditor_inbox: filtered assembly list for expeditor role - /api/act4_request_otp: 6-digit OTP via Telegram, 10-min expiry - /api/act4_verify_otp: validates code, marks act as signed - /api/act4_save_signature: saves base64 canvas signature - Act4s sheet: added signature_b64, otp_code, otp_expires_at columns Tests: - tests/expeditor_scenarios.md: 11 manual test scenarios Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
346 lines
17 KiB
HTML
346 lines
17 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="ru">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>Выбор иконок — Tabler Icons (MIT)</title>
|
||
<style>
|
||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||
body {
|
||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||
background: #EFE9DF;
|
||
padding: 32px 24px;
|
||
color: #3a2a1a;
|
||
}
|
||
h1 { color: #6B4A2B; font-size: 22px; margin-bottom: 6px; }
|
||
.subtitle { color: #9E7A5A; font-size: 13px; margin-bottom: 36px; }
|
||
.section { margin-bottom: 36px; }
|
||
.section-title {
|
||
font-size: 13px; font-weight: 700; text-transform: uppercase;
|
||
letter-spacing: 0.1em; color: #6B4A2B; margin-bottom: 16px;
|
||
display: flex; align-items: center; gap: 10px;
|
||
}
|
||
.section-title::after {
|
||
content: ''; flex: 1; height: 1px; background: #C4A882;
|
||
}
|
||
.icons-row {
|
||
display: flex; flex-wrap: wrap; gap: 12px;
|
||
}
|
||
.icon-card {
|
||
background: #FBF7F0;
|
||
border: 2px solid transparent;
|
||
border-radius: 14px;
|
||
padding: 18px 14px 12px;
|
||
display: flex; flex-direction: column;
|
||
align-items: center; gap: 10px;
|
||
cursor: pointer;
|
||
transition: border-color 0.15s, box-shadow 0.15s, transform 0.1s;
|
||
width: 110px;
|
||
box-shadow: 0 2px 6px rgba(107,74,43,0.08);
|
||
user-select: none;
|
||
}
|
||
.icon-card:hover {
|
||
border-color: #C4A882;
|
||
box-shadow: 0 4px 14px rgba(107,74,43,0.15);
|
||
transform: translateY(-2px);
|
||
}
|
||
.icon-card.selected {
|
||
border-color: #6B4A2B;
|
||
background: #F0E8DC;
|
||
box-shadow: 0 4px 16px rgba(107,74,43,0.2);
|
||
}
|
||
.icon-card svg {
|
||
display: block;
|
||
}
|
||
.icon-name {
|
||
font-size: 11px; color: #9E7A5A; text-align: center;
|
||
font-weight: 500; line-height: 1.3;
|
||
}
|
||
.icon-card.selected .icon-name { color: #6B4A2B; font-weight: 700; }
|
||
|
||
.result-bar {
|
||
position: fixed; bottom: 0; left: 0; right: 0;
|
||
background: #6B4A2B; color: #FBF7F0;
|
||
padding: 14px 24px;
|
||
display: flex; align-items: center; justify-content: space-between;
|
||
font-size: 14px;
|
||
transform: translateY(100%);
|
||
transition: transform 0.25s;
|
||
}
|
||
.result-bar.visible { transform: translateY(0); }
|
||
.result-bar strong { font-weight: 700; }
|
||
.result-bar code {
|
||
background: rgba(255,255,255,0.15); border-radius: 4px;
|
||
padding: 2px 8px; font-family: monospace; font-size: 12px;
|
||
}
|
||
.result-bar .hint { color: #C4A882; font-size: 12px; }
|
||
</style>
|
||
</head>
|
||
<body>
|
||
|
||
<h1>Выбор иконок для роли</h1>
|
||
<p class="subtitle">Источник: <strong>Tabler Icons</strong> — MIT лицензия, бесплатно в коммерческих проектах. Нажмите на иконку, чтобы выбрать.</p>
|
||
|
||
<!-- ====== МЕНЕДЖЕР ====== -->
|
||
<div class="section">
|
||
<div class="section-title">Менеджер — «Я менеджер / Веду клиентов и заказы»</div>
|
||
<div class="icons-row">
|
||
|
||
<div class="icon-card" data-role="manager" data-name="user" onclick="pick(this)">
|
||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="72" height="72" fill="none" stroke="#6B4A2B" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
||
<path d="M8 7a4 4 0 1 0 8 0a4 4 0 0 0 -8 0"/>
|
||
<path d="M6 21v-2a4 4 0 0 1 4 -4h4a4 4 0 0 1 4 4v2"/>
|
||
</svg>
|
||
<div class="icon-name">Человек</div>
|
||
</div>
|
||
|
||
<div class="icon-card" data-role="manager" data-name="user-circle" onclick="pick(this)">
|
||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="72" height="72" fill="none" stroke="#6B4A2B" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
||
<path d="M3 12a9 9 0 1 0 18 0a9 9 0 1 0 -18 0"/>
|
||
<path d="M9 10a3 3 0 1 0 6 0a3 3 0 1 0 -6 0"/>
|
||
<path d="M6.168 18.849a4 4 0 0 1 3.832 -2.849h4a4 4 0 0 1 3.834 2.855"/>
|
||
</svg>
|
||
<div class="icon-name">Профиль в круге</div>
|
||
</div>
|
||
|
||
<div class="icon-card" data-role="manager" data-name="user-check" onclick="pick(this)">
|
||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="72" height="72" fill="none" stroke="#6B4A2B" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
||
<path d="M8 7a4 4 0 1 0 8 0a4 4 0 0 0 -8 0"/>
|
||
<path d="M6 21v-2a4 4 0 0 1 4 -4h4"/>
|
||
<path d="M15 19l2 2l4 -4"/>
|
||
</svg>
|
||
<div class="icon-name">Подтверждённый</div>
|
||
</div>
|
||
|
||
<div class="icon-card" data-role="manager" data-name="user-star" onclick="pick(this)">
|
||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="72" height="72" fill="none" stroke="#6B4A2B" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
||
<path d="M8 7a4 4 0 1 0 8 0a4 4 0 0 0 -8 0"/>
|
||
<path d="M6 21v-2a4 4 0 0 1 4 -4h.5"/>
|
||
<path d="M17.8 20.817l-2.172 1.138a.392 .392 0 0 1 -.568 -.41l.415 -2.411l-1.757 -1.707a.389 .389 0 0 1 .217 -.665l2.428 -.352l1.086 -2.193a.392 .392 0 0 1 .702 0l1.086 2.193l2.428 .352a.39 .39 0 0 1 .217 .665l-1.757 1.707l.414 2.41a.39 .39 0 0 1 -.567 .411z"/>
|
||
</svg>
|
||
<div class="icon-name">VIP / Звезда</div>
|
||
</div>
|
||
|
||
<div class="icon-card" data-role="manager" data-name="tie" onclick="pick(this)">
|
||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="72" height="72" fill="none" stroke="#6B4A2B" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
||
<path d="M12 22l4 -4l-2.5 -11l.993 -2.649a1 1 0 0 0 -.936 -1.351h-3.114a1 1 0 0 0 -.936 1.351l.993 2.649l-2.5 11l4 4"/>
|
||
<path d="M10.5 7h3l5 5.5"/>
|
||
</svg>
|
||
<div class="icon-name">Галстук</div>
|
||
</div>
|
||
|
||
<div class="icon-card" data-role="manager" data-name="briefcase" onclick="pick(this)">
|
||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="72" height="72" fill="none" stroke="#6B4A2B" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
||
<path d="M3 9a2 2 0 0 1 2 -2h14a2 2 0 0 1 2 2v9a2 2 0 0 1 -2 2h-14a2 2 0 0 1 -2 -2l0 -9"/>
|
||
<path d="M8 7v-2a2 2 0 0 1 2 -2h4a2 2 0 0 1 2 2v2"/>
|
||
<path d="M12 12l0 .01"/>
|
||
<path d="M3 13a20 20 0 0 0 18 0"/>
|
||
</svg>
|
||
<div class="icon-name">Портфель</div>
|
||
</div>
|
||
|
||
<div class="icon-card" data-role="manager" data-name="device-laptop" onclick="pick(this)">
|
||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="72" height="72" fill="none" stroke="#6B4A2B" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
||
<path d="M3 19l18 0"/>
|
||
<path d="M5 7a1 1 0 0 1 1 -1h12a1 1 0 0 1 1 1v8a1 1 0 0 1 -1 1h-12a1 1 0 0 1 -1 -1l0 -8"/>
|
||
</svg>
|
||
<div class="icon-name">Ноутбук</div>
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ====== КЛИЕНТ ====== -->
|
||
<div class="section">
|
||
<div class="section-title">Клиент — «Я клиент / Заказал кухню ЗОВ»</div>
|
||
<div class="icons-row">
|
||
|
||
<div class="icon-card" data-role="client" data-name="home" onclick="pick(this)">
|
||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="72" height="72" fill="none" stroke="#6B4A2B" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
||
<path d="M5 12l-2 0l9 -9l9 9l-2 0"/>
|
||
<path d="M5 12v7a2 2 0 0 0 2 2h10a2 2 0 0 0 2 -2v-7"/>
|
||
<path d="M9 21v-6a2 2 0 0 1 2 -2h2a2 2 0 0 1 2 2v6"/>
|
||
</svg>
|
||
<div class="icon-name">Дом классик</div>
|
||
</div>
|
||
|
||
<div class="icon-card" data-role="client" data-name="home-2" onclick="pick(this)">
|
||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="72" height="72" fill="none" stroke="#6B4A2B" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
||
<path d="M5 12l-2 0l9 -9l9 9l-2 0"/>
|
||
<path d="M5 12v7a2 2 0 0 0 2 2h10a2 2 0 0 0 2 -2v-7"/>
|
||
<path d="M10 12h4v4h-4l0 -4"/>
|
||
</svg>
|
||
<div class="icon-name">Дом с окном</div>
|
||
</div>
|
||
|
||
<div class="icon-card" data-role="client" data-name="smart-home" onclick="pick(this)">
|
||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="72" height="72" fill="none" stroke="#6B4A2B" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
||
<path d="M19 8.71l-5.333 -4.148a2.666 2.666 0 0 0 -3.274 0l-5.334 4.148a2.665 2.665 0 0 0 -1.029 2.105v7.2a2 2 0 0 0 2 2h12a2 2 0 0 0 2 -2v-7.2c0 -.823 -.38 -1.6 -1.03 -2.105"/>
|
||
<path d="M16 15c-2.21 1.333 -5.792 1.333 -8 0"/>
|
||
</svg>
|
||
<div class="icon-name">Смарт-дом</div>
|
||
</div>
|
||
|
||
<div class="icon-card" data-role="client" data-name="home-heart" onclick="pick(this)">
|
||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="72" height="72" fill="none" stroke="#6B4A2B" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
||
<path d="M21 12l-9 -9l-9 9h2v7a2 2 0 0 0 2 2h6"/>
|
||
<path d="M9 21v-6a2 2 0 0 1 2 -2h2c.39 0 .754 .112 1.061 .304"/>
|
||
<path d="M19 21.5l2.518 -2.58a1.74 1.74 0 0 0 0 -2.413a1.627 1.627 0 0 0 -2.346 0l-.168 .172l-.168 -.172a1.627 1.627 0 0 0 -2.346 0a1.74 1.74 0 0 0 0 2.412l2.51 2.59z"/>
|
||
</svg>
|
||
<div class="icon-name">Дом с сердцем</div>
|
||
</div>
|
||
|
||
<div class="icon-card" data-role="client" data-name="home-check" onclick="pick(this)">
|
||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="72" height="72" fill="none" stroke="#6B4A2B" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
||
<path d="M9 21v-6a2 2 0 0 1 2 -2h2a2 2 0 0 1 2 2"/>
|
||
<path d="M19 13.488v-1.488h2l-9 -9l-9 9h2v7a2 2 0 0 0 2 2h4.525"/>
|
||
<path d="M15 19l2 2l4 -4"/>
|
||
</svg>
|
||
<div class="icon-name">Дом с галкой</div>
|
||
</div>
|
||
|
||
<div class="icon-card" data-role="client" data-name="building" onclick="pick(this)">
|
||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="72" height="72" fill="none" stroke="#6B4A2B" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
||
<path d="M3 21l18 0"/>
|
||
<path d="M9 8l1 0"/><path d="M9 12l1 0"/><path d="M9 16l1 0"/>
|
||
<path d="M14 8l1 0"/><path d="M14 12l1 0"/><path d="M14 16l1 0"/>
|
||
<path d="M5 21v-16a2 2 0 0 1 2 -2h10a2 2 0 0 1 2 2v16"/>
|
||
</svg>
|
||
<div class="icon-name">Здание</div>
|
||
</div>
|
||
|
||
<div class="icon-card" data-role="client" data-name="door" onclick="pick(this)">
|
||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="72" height="72" fill="none" stroke="#6B4A2B" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
||
<path d="M14 12v.01"/>
|
||
<path d="M3 21h18"/>
|
||
<path d="M6 21v-16a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v16"/>
|
||
</svg>
|
||
<div class="icon-name">Дверь</div>
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ====== СОТРУДНИК ====== -->
|
||
<div class="section" style="padding-bottom: 80px;">
|
||
<div class="section-title">Сотрудник — «Я сотрудник / Замерщик или сборщик ЗОВ»</div>
|
||
<div class="icons-row">
|
||
|
||
<div class="icon-card" data-role="staff" data-name="helmet" onclick="pick(this)">
|
||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="72" height="72" fill="none" stroke="#6B4A2B" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
||
<path d="M12 4a9 9 0 0 1 5.656 16h-11.312a9 9 0 0 1 5.656 -16"/>
|
||
<path d="M20 9h-8.8a1 1 0 0 0 -.968 1.246c.507 2 1.596 3.418 3.268 4.254c2 1 4.333 1.5 7 1.5"/>
|
||
</svg>
|
||
<div class="icon-name">Каска</div>
|
||
</div>
|
||
|
||
<div class="icon-card" data-role="staff" data-name="tool" onclick="pick(this)">
|
||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="72" height="72" fill="none" stroke="#6B4A2B" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
||
<path d="M7 10h3v-3l-3.5 -3.5a6 6 0 0 1 8 8l6 6a2 2 0 0 1 -3 3l-6 -6a6 6 0 0 1 -8 -8l3.5 3.5"/>
|
||
</svg>
|
||
<div class="icon-name">Гаечный ключ</div>
|
||
</div>
|
||
|
||
<div class="icon-card" data-role="staff" data-name="tools" onclick="pick(this)">
|
||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="72" height="72" fill="none" stroke="#6B4A2B" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
||
<path d="M3 21h4l13 -13a1.5 1.5 0 0 0 -4 -4l-13 13v4"/>
|
||
<path d="M14.5 5.5l4 4"/>
|
||
<path d="M12 8l-5 -5l-4 4l5 5"/>
|
||
<path d="M7 8l-1.5 1.5"/>
|
||
<path d="M16 12l5 5l-4 4l-5 -5"/>
|
||
<path d="M16 17l-1.5 1.5"/>
|
||
</svg>
|
||
<div class="icon-name">Набор инструментов</div>
|
||
</div>
|
||
|
||
<div class="icon-card" data-role="staff" data-name="hammer" onclick="pick(this)">
|
||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="72" height="72" fill="none" stroke="#6B4A2B" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
||
<path d="M11.414 10l-7.383 7.418a2.091 2.091 0 0 0 0 2.967a2.11 2.11 0 0 0 2.976 0l7.407 -7.385"/>
|
||
<path d="M18.121 15.293l2.586 -2.586a1 1 0 0 0 0 -1.414l-7.586 -7.586a1 1 0 0 0 -1.414 0l-2.586 2.586a1 1 0 0 0 0 1.414l7.586 7.586a1 1 0 0 0 1.414 0z"/>
|
||
</svg>
|
||
<div class="icon-name">Молоток</div>
|
||
</div>
|
||
|
||
<div class="icon-card" data-role="staff" data-name="ruler-2" onclick="pick(this)">
|
||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="72" height="72" fill="none" stroke="#6B4A2B" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
||
<path d="M17 3l4 4l-14 14l-4 -4l14 -14"/>
|
||
<path d="M16 7l-1.5 -1.5"/>
|
||
<path d="M13 10l-1.5 -1.5"/>
|
||
<path d="M10 13l-1.5 -1.5"/>
|
||
<path d="M7 16l-1.5 -1.5"/>
|
||
</svg>
|
||
<div class="icon-name">Линейка / замер</div>
|
||
</div>
|
||
|
||
<div class="icon-card" data-role="staff" data-name="user-cog" onclick="pick(this)">
|
||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="72" height="72" fill="none" stroke="#6B4A2B" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
||
<path d="M8 7a4 4 0 1 0 8 0a4 4 0 0 0 -8 0"/>
|
||
<path d="M6 21v-2a4 4 0 0 1 4 -4h2.5"/>
|
||
<path d="M19.001 19a2 2 0 1 0 4 0a2 2 0 1 0 -4 0"/>
|
||
<path d="M19.001 15.5v1.5"/>
|
||
<path d="M19.001 21v1.5"/>
|
||
<path d="M22.032 17.25l-1.299 .75"/>
|
||
<path d="M17.27 20l-1.3 .75"/>
|
||
<path d="M15.97 17.25l1.3 .75"/>
|
||
<path d="M20.733 20l1.3 .75"/>
|
||
</svg>
|
||
<div class="icon-name">Сотрудник+настройки</div>
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Статус-бар выбора -->
|
||
<div class="result-bar" id="resultBar">
|
||
<div>
|
||
<span id="selectionText">Выберите иконки для всех трёх ролей</span><br>
|
||
<span class="hint" id="selectionHint">Нажмите на карточку</span>
|
||
</div>
|
||
<div id="selectionIcons" style="display:flex; gap:24px; align-items:center;"></div>
|
||
</div>
|
||
|
||
<script>
|
||
const selected = { manager: null, client: null, staff: null };
|
||
const roleNames = { manager: 'Менеджер', client: 'Клиент', staff: 'Сотрудник' };
|
||
|
||
function pick(card) {
|
||
const role = card.dataset.role;
|
||
const name = card.dataset.name;
|
||
|
||
// Снять выбор с предыдущей карточки той же роли
|
||
document.querySelectorAll(`.icon-card[data-role="${role}"]`).forEach(c => c.classList.remove('selected'));
|
||
|
||
card.classList.add('selected');
|
||
selected[role] = name;
|
||
|
||
updateBar();
|
||
}
|
||
|
||
function updateBar() {
|
||
const bar = document.getElementById('resultBar');
|
||
const text = document.getElementById('selectionText');
|
||
const hint = document.getElementById('selectionHint');
|
||
const iconsDiv = document.getElementById('selectionIcons');
|
||
|
||
const filled = Object.entries(selected).filter(([, v]) => v !== null);
|
||
bar.classList.add('visible');
|
||
|
||
if (filled.length === 3) {
|
||
text.textContent = '✅ Все три роли выбраны!';
|
||
hint.innerHTML = Object.entries(selected).map(([role, name]) =>
|
||
`${roleNames[role]}: <code>${name}</code>`
|
||
).join(' · ');
|
||
} else {
|
||
text.textContent = `Выбрано ${filled.length} из 3`;
|
||
hint.textContent = filled.map(([role, name]) => `${roleNames[role]}: ${name}`).join(' · ') || 'Нажмите на карточку';
|
||
}
|
||
|
||
iconsDiv.innerHTML = filled.map(([role, name]) => {
|
||
const card = document.querySelector(`.icon-card[data-role="${role}"][data-name="${name}"]`);
|
||
return card ? `<div style="text-align:center"><div style="background:rgba(255,255,255,0.15);border-radius:8px;padding:6px">${card.querySelector('svg').outerHTML.replace(/width="\d+"/, 'width="36"').replace(/height="\d+"/, 'height="36"').replace(/stroke="#6B4A2B"/g, 'stroke="#FBF7F0"')}</div><div style="font-size:10px;color:#C4A882;margin-top:4px">${roleNames[role]}</div></div>` : '';
|
||
}).join('');
|
||
}
|
||
</script>
|
||
</body>
|
||
</html>
|