v1: регистрация на тимбилдинг — форма + админ-список (localStorage, ДС KT AI)
This commit is contained in:
parent
0929453596
commit
fafdd09be8
289
index.html
Normal file
289
index.html
Normal file
@ -0,0 +1,289 @@
|
||||
<!DOCTYPE html>
|
||||
<!-- Дизайн: Стандартный дизайн. Сохраняй все пять CSS; подробности в design.md. -->
|
||||
<html lang="ru" data-theme="light">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>Тимбилдинг ДЦБ Астана — регистрация</title>
|
||||
<link rel="stylesheet" href="design-system/kt-ai-fonts.css">
|
||||
<link rel="stylesheet" href="design-system/kt-ai-tokens.css">
|
||||
<link rel="stylesheet" href="design-system/kt-ai-components.css">
|
||||
<link rel="stylesheet" href="design-system/kt-ai-page.css">
|
||||
<link rel="stylesheet" href="design-system/vibe-theme.css">
|
||||
<link rel="stylesheet" href="style.css">
|
||||
<script src="design-system/icons/kt-ai-icons.js" defer></script>
|
||||
<script src="script.js" defer></script>
|
||||
</head>
|
||||
<body class="kt-ai-app">
|
||||
|
||||
<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" style="position:absolute;width:0;height:0;overflow:hidden" data-sprite-embedded>
|
||||
<symbol id="home" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M15 21v-8a1 1 0 0 0-1-1h-4a1 1 0 0 0-1 1v8"/>
|
||||
<path d="M3 10a2 2 0 0 1 .709-1.528l7-6a2 2 0 0 1 2.582 0l7 6A2 2 0 0 1 21 10v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/>
|
||||
</symbol><symbol id="check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M20 6 9 17l-5-5"/>
|
||||
</symbol><symbol id="checklist" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="m3 17 2 2 4-4"/>
|
||||
<path d="m3 7 2 2 4-4"/>
|
||||
<path d="M13 6h8"/>
|
||||
<path d="M13 12h8"/>
|
||||
<path d="M13 18h8"/>
|
||||
</symbol><symbol id="chart" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M3 3v16a2 2 0 0 0 2 2h16"/>
|
||||
<path d="M7 16h8"/>
|
||||
<path d="M7 11h12"/>
|
||||
<path d="M7 6h3"/>
|
||||
</symbol><symbol id="clock" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
|
||||
<circle cx="12" cy="12" r="10"/>
|
||||
<path d="M12 6v6l4 2"/>
|
||||
</symbol><symbol id="info" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
|
||||
<circle cx="12" cy="12" r="10"/>
|
||||
<path d="M12 16v-4"/>
|
||||
<path d="M12 8h.01"/>
|
||||
</symbol><symbol id="user" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2"/>
|
||||
<circle cx="12" cy="7" r="4"/>
|
||||
</symbol><symbol id="users" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/>
|
||||
<path d="M16 3.128a4 4 0 0 1 0 7.744"/>
|
||||
<path d="M22 21v-2a4 4 0 0 0-3-3.87"/>
|
||||
<circle cx="9" cy="7" r="4"/>
|
||||
</symbol><symbol id="building" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M12 7V3"/>
|
||||
<path d="M3 21V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2v16"/>
|
||||
<path d="M2 21h20"/>
|
||||
<path d="M9 7v2"/>
|
||||
<path d="M15 7v2"/>
|
||||
<path d="M9 11v2"/>
|
||||
<path d="M15 11v2"/>
|
||||
<path d="M9 15v2"/>
|
||||
<path d="M15 15v2"/>
|
||||
</symbol><symbol id="pin" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M12 17v5"/>
|
||||
<path d="M9 10.76a2 2 0 0 1-1.11 1.79l-1.78.9A2 2 0 0 0 5 15.24V16a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-.76a2 2 0 0 0-1.11-1.79l-1.78-.9A2 2 0 0 1 15 10.76V7a1 1 0 0 1 1-1 2 2 0 0 0 0-4H8a2 2 0 0 0 0 4 1 1 0 0 1 1 1z"/>
|
||||
</symbol><symbol id="search" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="m21 21-4.34-4.34"/>
|
||||
<circle cx="11" cy="11" r="8"/>
|
||||
</symbol><symbol id="calendar" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M8 2v4"/>
|
||||
<path d="M16 2v4"/>
|
||||
<rect width="18" height="18" x="3" y="4" rx="2"/>
|
||||
<path d="M3 10h18"/>
|
||||
</symbol><symbol id="arrowLeft" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="m12 19-7-7 7-7"/>
|
||||
<path d="M19 12H5"/>
|
||||
</symbol><symbol id="chevronRight" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="m9 18 6-6-6-6"/>
|
||||
</symbol><symbol id="x" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M18 6 6 18"/>
|
||||
<path d="m6 6 12 12"/>
|
||||
</symbol><symbol id="sparkles" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M9.937 15.5A2 2 0 0 0 8.5 14.063l-6.135-1.582a.5.5 0 0 1 0-.962L8.5 9.936A2 2 0 0 0 9.937 8.5l1.582-6.135a.5.5 0 0 1 .963 0L14.063 8.5A2 2 0 0 0 15.5 9.937l6.135 1.581a.5.5 0 0 1 0 .964L15.5 14.063a2 2 0 0 0-1.437 1.437l-1.582 6.135a.5.5 0 0 1-.963 0z"/>
|
||||
<path d="M20 3v4"/>
|
||||
<path d="M22 5h-4"/>
|
||||
</symbol>
|
||||
</svg>
|
||||
|
||||
<!-- ═══════════════ ВИД 1: УЧАСТНИКАМ ═══════════════ -->
|
||||
<div id="view-participant">
|
||||
|
||||
<header class="kt-ai-pagehead">
|
||||
<div class="kt-ai-wrap kt-ai-pagehead-in tb-pagehead-in">
|
||||
<span class="tb-brand">
|
||||
<img class="tb-logo" src="https://code.vibe42.kz/tore/aitu/kt-logo.svg" alt="Казахтелеком" height="28">
|
||||
<span class="tb-brand-name">Тимбилдинг ДЦБ Астана</span>
|
||||
</span>
|
||||
<span class="kt-ai-spacer"></span>
|
||||
<a class="kt-ai-btn" data-variant="ghost" href="#reg">Регистрация</a>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<section class="kt-ai-hero" data-align="center">
|
||||
<div class="kt-ai-wrap" data-width="narrow">
|
||||
<span class="kt-ai-chip" data-tone="green">18 сентября · суббота</span>
|
||||
<h1 style="margin-top:var(--kt-ai-space-6)">Тимбилдинг <span class="vibe-highlight">ДЦБ Астана</span></h1>
|
||||
<p class="kt-ai-hero-sub">День вне привычной рабочей обстановки: командные активности, общение, отдых — и просто классное время вместе. Место: зона отдыха «Лазурный берег», Косшы.</p>
|
||||
<div class="kt-ai-hero-actions">
|
||||
<a class="kt-ai-btn" data-variant="primary" data-size="lg" href="#reg">Зарегистрироваться</a>
|
||||
<a class="kt-ai-btn" data-size="lg" href="#info">Расписание</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="info" class="kt-ai-section" data-divider="true">
|
||||
<div class="kt-ai-wrap">
|
||||
<div class="kt-ai-section-head">
|
||||
<h2>Как всё будет проходить</h2>
|
||||
<p>Три остановки — офис, дорога, база. Всё остальное позади.</p>
|
||||
</div>
|
||||
<div class="kt-ai-grid" style="margin-top:var(--kt-ai-space-9)">
|
||||
<article class="kt-ai-tile">
|
||||
<span class="kt-ai-tile-icon"><svg class="kt-icon" aria-hidden="true"><use href="#clock"></use></svg></span>
|
||||
<h3>08:30</h3>
|
||||
<p>Приходим на работу как обычно — БЦ «Асылтау».</p>
|
||||
</article>
|
||||
<article class="kt-ai-tile">
|
||||
<span class="kt-ai-tile-icon"><svg class="kt-icon" aria-hidden="true"><use href="#building"></use></svg></span>
|
||||
<h3>10:30</h3>
|
||||
<p>Выезжаем от БЦ «Асылтау», ул. Алматы, 1.</p>
|
||||
</article>
|
||||
<article class="kt-ai-tile">
|
||||
<span class="kt-ai-tile-icon"><svg class="kt-icon" aria-hidden="true"><use href="#pin"></use></svg></span>
|
||||
<h3>День на базе</h3>
|
||||
<p>Зона отдыха «Лазурный берег», г. Косшы, ул. Айтебе би, 32.</p>
|
||||
</article>
|
||||
<article class="kt-ai-tile">
|
||||
<span class="kt-ai-tile-icon"><svg class="kt-icon" aria-hidden="true"><use href="#arrowLeft"></use></svg></span>
|
||||
<h3>18:00</h3>
|
||||
<p>Обратный выезд с базы отдыха — ориентировочно.</p>
|
||||
</article>
|
||||
</div>
|
||||
<div class="kt-ai-tile tb-note" style="margin-top:var(--kt-ai-space-8)">
|
||||
<p class="kt-ai-t-muted" style="margin:0">Берите с собой хорошее настроение — остальное организуем на месте.</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="reg" class="kt-ai-section" data-divider="true">
|
||||
<div class="kt-ai-wrap" data-width="narrow">
|
||||
<div class="kt-ai-section-head">
|
||||
<h2>Регистрация</h2>
|
||||
<p>Оставьте контакты — и вы в списке участников. Подтверждение появится у администратора мероприятия.</p>
|
||||
</div>
|
||||
<div class="kt-ai-tile" style="margin-top:var(--kt-ai-space-8)">
|
||||
<form id="regform" novalidate>
|
||||
<div class="kt-ai-field">
|
||||
<label for="f-name">Имя</label>
|
||||
<input class="kt-ai-input" id="f-name" type="text" placeholder="Данияр" autocomplete="given-name">
|
||||
</div>
|
||||
<div class="kt-ai-field">
|
||||
<label for="f-lastname">Фамилия</label>
|
||||
<input class="kt-ai-input" id="f-lastname" type="text" placeholder="Орынбаев" autocomplete="family-name">
|
||||
</div>
|
||||
<div class="kt-ai-field">
|
||||
<label for="f-phone">Телефон</label>
|
||||
<input class="kt-ai-input" id="f-phone" type="tel" placeholder="+7" autocomplete="tel">
|
||||
<span class="hint">Номер для связи в день мероприятия.</span>
|
||||
</div>
|
||||
<button class="kt-ai-btn" data-variant="primary" data-size="lg" type="submit">Зарегистрироваться</button>
|
||||
</form>
|
||||
<div id="regdone" class="kt-ai-empty" hidden style="background-image:none">
|
||||
<span class="kt-ai-empty-ic"><svg class="kt-icon" aria-hidden="true"><use href="#check"></use></svg></span>
|
||||
<div class="title">Регистрация принята</div>
|
||||
<p id="regdone-text">Вы в списке участников. До встречи 18 сентября!</p>
|
||||
<button class="kt-ai-btn" type="button" id="regdone-again">Зарегистрировать ещё одного</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<footer class="kt-ai-wrap kt-ai-pagefoot">
|
||||
<span>© 2026 Тимбилдинг ДЦБ Астана</span>
|
||||
<span class="kt-ai-spacer"></span>
|
||||
<span class="tb-admin-link">Участникам</span>
|
||||
<span class="tb-admin-link tb-admin-link-strong">Администратор</span>
|
||||
</footer>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- ═══════════════ ВИД 2: АДМИНИСТРАТОРАМ ═══════════════ -->
|
||||
<div id="view-admin" hidden>
|
||||
|
||||
<div class="kt-ai-shell">
|
||||
<aside class="kt-ai-sidebar">
|
||||
<div class="tb-brand tb-brand-v">
|
||||
<img class="tb-logo" src="https://code.vibe42.kz/tore/aitu/kt-logo.svg" alt="Казахтелеком" height="22">
|
||||
<span class="brand-label">ДЦБ Астана</span>
|
||||
</div>
|
||||
<nav>
|
||||
<div class="kt-ai-nav-group">
|
||||
<a class="kt-ai-nav-item" data-nav="reg" href="#view-participant"><svg class="kt-icon" aria-hidden="true"><use href="#user"></use></svg>Регистрация</a>
|
||||
<a class="kt-ai-nav-item" data-active="true"><svg class="kt-icon" aria-hidden="true"><use href="#users"></use></svg>Участники<span class="count" id="nav-count"></span></a>
|
||||
<a class="kt-ai-nav-item" data-nav="info" href="#view-participant#info"><svg class="kt-icon" aria-hidden="true"><use href="#calendar"></use></svg>Событие</a>
|
||||
</div>
|
||||
</nav>
|
||||
<div class="kt-ai-spacer-tb"></div>
|
||||
<div class="kt-ai-card tb-admin-card">
|
||||
<strong>Список для команды</strong>
|
||||
<p class="kt-ai-t-muted">Зарегистрированные участники и их подтверждение.</p>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<main>
|
||||
<header class="kt-ai-topbar">
|
||||
<div class="kt-ai-breadcrumb"><span class="kt-ai-t-muted">Тимбилдинг · 18.09.2026</span>
|
||||
<svg class="kt-icon" aria-hidden="true"><use href="#chevronRight"></use></svg>
|
||||
<strong>Участники</strong>
|
||||
</div>
|
||||
<span class="spacer"></span>
|
||||
<div class="kt-ai-search-field">
|
||||
<input class="kt-ai-search" id="adm-search" type="search" placeholder="Имя или телефон…" aria-label="Поиск по участникам">
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="kt-ai-kpi-strip" id="adm-kpis" style="padding-inline:var(--kt-ai-space-8)">
|
||||
<div class="kt-ai-kpi"><span class="label">Всего участников</span><span class="value" id="kpi-total">—</span><span class="hint">зарегистрировано на 18 сентября</span></div>
|
||||
<div class="kt-ai-kpi"><span class="label">Подтверждены</span><span class="value" id="kpi-confirmed">—</span><span class="hint">место на трансфере зафиксировано</span></div>
|
||||
<div class="kt-ai-kpi"><span class="label">Ждут подтверждения</span><span class="value" id="kpi-waiting">—</span><span class="hint">требуют внимания администратора</span></div>
|
||||
</div>
|
||||
|
||||
<div class="kt-ai-toolbar" style="padding:0 var(--kt-ai-space-8)">
|
||||
<div class="kt-ai-filterbar">
|
||||
<button class="kt-ai-filter-chip" data-status="all" data-active="true">Все</button>
|
||||
<button class="kt-ai-filter-chip" data-status="confirmed">Подтверждены</button>
|
||||
<button class="kt-ai-filter-chip" data-status="waiting">Ждут</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="kt-ai-table-wrap" style="margin:0 var(--kt-ai-space-8) var(--kt-ai-space-8)">
|
||||
<table class="kt-ai-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="text-align:left">Участник</th>
|
||||
<th style="text-align:left">Телефон</th>
|
||||
<th style="text-align:left">Зарегистрирован</th>
|
||||
<th style="text-align:left">Статус</th>
|
||||
<th style="width:1%;padding-right:0"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="adm-tbody"></tbody>
|
||||
</table>
|
||||
<div class="kt-ai-empty" id="adm-empty" hidden>
|
||||
<span class="kt-ai-empty-ic"><svg class="kt-icon" aria-hidden="true"><use href="#search"></use></svg></span>
|
||||
<div class="title" id="adm-empty-title">Никого нет</div>
|
||||
<p id="adm-empty-text">Попробуйте изменить запрос или фильтр.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</main>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Панель участника -->
|
||||
<aside class="kt-ai-drawer" id="drawer" hidden>
|
||||
<div class="drawer-head">
|
||||
<span class="kt-ai-t-muted" id="d-kicker">Участник</span>
|
||||
<strong id="d-name">—</strong>
|
||||
<span class="drawer-close kt-ai-tooltip" data-tip="Закрыть" role="button" tabindex="0" aria-label="Закрыть"><svg class="kt-icon" aria-hidden="true"><use href="#x"></use></svg></span>
|
||||
</div>
|
||||
<div class="drawer-body kt-ai-v" data-gap="sm">
|
||||
<div class="kt-ai-prop"><span class="k">Телефон</span><span class="v" id="d-phone" data-empty="true">—</span></div>
|
||||
<div class="kt-ai-prop"><span class="k">Зарегистрирован</span><span class="v" id="d-date">—</span></div>
|
||||
<div class="kt-ai-prop"><span class="k">Статус</span><span class="v" id="d-status"></span></div>
|
||||
</div>
|
||||
<div class="kt-ai-drawer-actions" style="margin-top:var(--kt-ai-space-8)">
|
||||
<button class="kt-ai-btn" data-variant="primary" id="d-confirm" type="button"><svg class="kt-icon" aria-hidden="true"><use href="#check"></use></svg>Подтвердить участие</button>
|
||||
<button class="kt-ai-btn" id="d-unlink" type="button">Убрать из списка</button>
|
||||
<button class="kt-ai-btn" data-variant="ghost" id="d-close" type="button">Закрыть</button>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<div class="kt-ai-toast-stack" id="toasts"></div>
|
||||
|
||||
<noscript>
|
||||
<div style="padding:24px;text-align:center;color:var(--kt-ai-fg-muted)">Для работы регистрации включите JavaScript в браузере.</div>
|
||||
</noscript>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
323
script.js
Normal file
323
script.js
Normal file
@ -0,0 +1,323 @@
|
||||
/* Тимбилдинг ДЦБ Астана — регистрация участников и список администратора.
|
||||
Данные живут в localStorage (общий хранилище страницы); при первом заходе
|
||||
список наполняется примерами участников для показа структуры. */
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
var LS_KEY = "teambuilding_dcb_participants";
|
||||
|
||||
var STATUS = {
|
||||
waiting: { label: "Ждёт подтверждения", tone: "warn" },
|
||||
confirmed: { label: "Подтверждён", tone: "ok" }
|
||||
};
|
||||
|
||||
var SEED = [
|
||||
{ id: 1, name: "Данияр", lastname: "Орынбаев", phone: "+7 705 214 88 30", status: "confirmed", date: "08.09.2026" },
|
||||
{ id: 2, name: "Айгерим", lastname: "Сатыбаева", phone: "+7 702 551 40 12", status: "confirmed", date: "09.09.2026" },
|
||||
{ id: 3, name: "Ержан", lastname: "Абдрахимов", phone: "+7 771 902 33 56", status: "waiting", date: "09.09.2026" },
|
||||
{ id: 4, name: "Гульнара", lastname: "Касымжанова", phone: "+7 747 635 21 08", status: "confirmed", date: "10.09.2026" },
|
||||
{ id: 5, name: "Мадина", lastname: "Турсынбаева", phone: "+7 708 442 17 65", status: "confirmed", date: "11.09.2026" },
|
||||
{ id: 6, name: "Асхат", lastname: "Жаманкулов", phone: "+7 700 118 92 44", status: "waiting", date: "12.09.2026" },
|
||||
{ id: 7, name: "Сейіл", lastname: "Кенжарик", phone: "+7 748 276 50 91", status: "confirmed", date: "13.09.2026" },
|
||||
{ id: 8, name: "Жанна", lastname: "Мырзахметова", phone: "+7 706 390 77 14", status: "confirmed", date: "15.09.2026" },
|
||||
{ id: 9, name: "Бахыт", lastname: "Айтимов", phone: "+7 775 813 26 40", status: "confirmed", date: "15.09.2026" },
|
||||
{ id: 10, name: "Алия", lastname: "Исмухамбетова", phone: "+7 707 564 38 29", status: "waiting", date: "16.09.2026" },
|
||||
{ id: 11, name: "Нурлан", lastname: "Сейткалиев", phone: "+7 702 837 15 73", status: "confirmed", date: "16.09.2026" },
|
||||
{ id: 12, name: "Карлыгаш", lastname: "Ералиева", phone: "+7 749 620 84 57", status: "waiting", date: "17.09.2026" }
|
||||
];
|
||||
|
||||
/* ── Хранилище ── */
|
||||
function load() {
|
||||
try {
|
||||
var raw = localStorage.getItem(LS_KEY);
|
||||
if (raw) {
|
||||
var arr = JSON.parse(raw);
|
||||
if (Array.isArray(arr)) return arr;
|
||||
}
|
||||
} catch (e) { /* повреждённые данные — пересеять */ }
|
||||
localStorage.setItem(LS_KEY, JSON.stringify(SEED));
|
||||
return SEED.slice();
|
||||
}
|
||||
function save(list) {
|
||||
try { localStorage.setItem(LS_KEY, JSON.stringify(list)); } catch (e) {}
|
||||
}
|
||||
|
||||
var participants = load();
|
||||
var nextId = 1;
|
||||
for (var i = 0; i < participants.length; i++) {
|
||||
if (participants[i].id >= nextId) nextId = participants[i].id + 1;
|
||||
}
|
||||
|
||||
/* ── Хелперы ── */
|
||||
function $(sel, root) { return (root || document).querySelector(sel); }
|
||||
function esc(s) {
|
||||
return String(s == null ? "" : s).replace(/[&<>"']/g, function (c) {
|
||||
return { "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }[c];
|
||||
});
|
||||
}
|
||||
function todayStr() {
|
||||
var d = new Date();
|
||||
var m = ("0" + (d.getMonth() + 1)).slice(-2);
|
||||
var day = ("0" + d.getDate()).slice(-2);
|
||||
return day + "." + m + "." + d.getFullYear();
|
||||
}
|
||||
|
||||
var toastsRoot = $("#toasts");
|
||||
function toast(text, tone) {
|
||||
var el = document.createElement("div");
|
||||
el.className = "kt-ai-toast";
|
||||
if (tone) el.setAttribute("data-tone", tone);
|
||||
el.textContent = text;
|
||||
toastsRoot.appendChild(el);
|
||||
setTimeout(function () {
|
||||
el.style.opacity = "0";
|
||||
el.style.transition = "opacity 200ms";
|
||||
setTimeout(function () { el.remove(); }, 220);
|
||||
}, 3200);
|
||||
}
|
||||
|
||||
/* ── Переключение видов ── */
|
||||
function showView(name) {
|
||||
var p = $("#view-participant");
|
||||
var a = $("#view-admin");
|
||||
if (name === "admin") {
|
||||
p.hidden = true;
|
||||
a.hidden = false;
|
||||
} else {
|
||||
a.hidden = true;
|
||||
p.hidden = false;
|
||||
}
|
||||
if (name === "admin") renderAdmin();
|
||||
try { location.hash = name; } catch (e) {}
|
||||
}
|
||||
|
||||
/* ── Форма регистрации ── */
|
||||
var form = $("#regform");
|
||||
var done = $("#regdone");
|
||||
var fName = $("#f-name");
|
||||
var fLast = $("#f-lastname");
|
||||
var fPhone = $("#f-phone");
|
||||
|
||||
form.addEventListener("submit", function (ev) {
|
||||
ev.preventDefault();
|
||||
var name = fName.value.trim();
|
||||
var lastname = fLast.value.trim();
|
||||
var phone = fPhone.value.trim();
|
||||
|
||||
if (!name) { flashField(fName, "Введите имя"); return; }
|
||||
if (!lastname) { flashField(fLast, "Введите фамилию"); return; }
|
||||
if (!/\+?[0-9](?:[0-9 ()\-]{6,14})/.test(phone)) {
|
||||
flashField(fPhone, "Укажите телефон в формате +7 XXX XXX XX XX");
|
||||
return;
|
||||
}
|
||||
var dup = null;
|
||||
for (var i = 0; i < participants.length; i++) {
|
||||
var p = participants[i];
|
||||
var key = (p.name + " " + p.lastname).trim().replace(/\s+/g, " ").toLowerCase();
|
||||
if (key === (name + " " + lastname).replace(/\s+/g, " ").toLowerCase() &&
|
||||
p.phone.replace(/\D/g, "") === phone.replace(/\D/g, "")) { dup = p; break; }
|
||||
}
|
||||
if (dup) {
|
||||
toast("Этот участник уже зарегистрирован", "warn");
|
||||
return;
|
||||
}
|
||||
|
||||
var rec = {
|
||||
id: nextId++,
|
||||
name: name,
|
||||
lastname: lastname,
|
||||
phone: phone,
|
||||
status: "waiting",
|
||||
date: todayStr()
|
||||
};
|
||||
participants.unshift(rec);
|
||||
save(participants);
|
||||
|
||||
form.hidden = true;
|
||||
done.hidden = false;
|
||||
$("#regdone-text").textContent =
|
||||
name + ", вы в списке участников. Администратор подтвердит участие до 18 сентября.";
|
||||
toast("Регистрация принята", "ok");
|
||||
});
|
||||
|
||||
function flashField(input, msg) {
|
||||
input.focus();
|
||||
toast(msg, "warn");
|
||||
}
|
||||
|
||||
$("#regdone-again").addEventListener("click", function () {
|
||||
form.reset();
|
||||
done.hidden = true;
|
||||
form.hidden = false;
|
||||
fName.focus();
|
||||
});
|
||||
|
||||
/* ── Админка ── */
|
||||
var statusFilter = "all";
|
||||
var searchQuery = "";
|
||||
var selectedId = null;
|
||||
|
||||
function filtered() {
|
||||
return participants.filter(function (p) {
|
||||
if (statusFilter !== "all" && p.status !== statusFilter) return false;
|
||||
if (searchQuery) {
|
||||
var hay = (p.name + " " + p.lastname + " " + p.phone).toLowerCase();
|
||||
if (hay.indexOf(searchQuery) === -1) return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
function statusPill(status) {
|
||||
var s = STATUS[status] || STATUS.waiting;
|
||||
return '<span class="kt-ai-status-pill" data-status="' + s.tone + '">' + esc(s.label) + "</span>";
|
||||
}
|
||||
|
||||
function renderAdmin() {
|
||||
/* навигация-счётчик */
|
||||
$("#nav-count").textContent = participants.length;
|
||||
|
||||
/* KPI */
|
||||
var total = participants.length;
|
||||
var confirmed = 0, waiting = 0;
|
||||
for (var i = 0; i < participants.length; i++) {
|
||||
if (participants[i].status === "confirmed") confirmed++; else waiting++;
|
||||
}
|
||||
$("#kpi-total").textContent = total;
|
||||
$("#kpi-confirmed").textContent = confirmed;
|
||||
$("#kpi-waiting").textContent = waiting;
|
||||
|
||||
/* фильтр-чипы */
|
||||
var chips = document.querySelectorAll(".kt-ai-filter-chip");
|
||||
for (var j = 0; j < chips.length; j++) {
|
||||
chips[j].setAttribute("data-active", chips[j].getAttribute("data-status") === statusFilter ? "true" : "false");
|
||||
}
|
||||
|
||||
/* таблица */
|
||||
var list = filtered();
|
||||
var tbody = $("#adm-tbody");
|
||||
var html = "";
|
||||
for (var k = 0; k < list.length; k++) {
|
||||
var p = list[k];
|
||||
html += "<tr data-id=\"" + p.id + '"' + (p.id === selectedId ? ' data-selected="true"' : "") + ">" +
|
||||
'<td data-label="Участник"><span class="cell-name">' + esc(p.lastname) + " " + esc(p.name) + "</span>" +
|
||||
'<span class="cell-sub">ДЦБ Астана</span></td>' +
|
||||
'<td data-label="Телефон" class="num">' + esc(p.phone) + "</td>" +
|
||||
'<td data-label="Зарегистрирован" class="num">' + esc(p.date) + "</td>" +
|
||||
'<td data-label="Статус">' + statusPill(p.status) + "</td>" +
|
||||
'<td><span class="row-act kt-ai-tooltip" data-tip="Подробнее" aria-hidden="true"><svg class="kt-icon"><use href="#chevronRight"></use></svg></span></td>' +
|
||||
"</tr>";
|
||||
}
|
||||
tbody.innerHTML = html;
|
||||
|
||||
/* пустое состояние */
|
||||
var empty = $("#adm-empty");
|
||||
if (list.length === 0) {
|
||||
empty.hidden = false;
|
||||
var hasFilter = statusFilter !== "all" || searchQuery !== "";
|
||||
$("#adm-empty-title").textContent = hasFilter ? "Никого не нашлось" : "Пока никто не зарегистрировался";
|
||||
$("#adm-empty-text").textContent = hasFilter
|
||||
? "Попробуйте изменить запрос или фильтр."
|
||||
: "Регистрации участников появятся здесь, как только коллеги отправят форму.";
|
||||
} else {
|
||||
empty.hidden = true;
|
||||
}
|
||||
}
|
||||
|
||||
/* клики по строке и по «открыть» */
|
||||
$("#adm-tbody").addEventListener("click", function (ev) {
|
||||
var tr = ev.target.closest("tr[data-id]");
|
||||
if (!tr) return;
|
||||
openDrawer(parseInt(tr.getAttribute("data-id"), 10));
|
||||
});
|
||||
|
||||
/* поиск + фильтры */
|
||||
$("#adm-search").addEventListener("input", function () {
|
||||
searchQuery = this.value.trim().toLowerCase();
|
||||
renderAdmin();
|
||||
});
|
||||
document.querySelectorAll(".kt-ai-filter-chip").forEach(function (chip) {
|
||||
chip.addEventListener("click", function () {
|
||||
statusFilter = this.getAttribute("data-status");
|
||||
renderAdmin();
|
||||
});
|
||||
});
|
||||
|
||||
/* ── Drawer ── */
|
||||
var drawer = $("#drawer");
|
||||
|
||||
function openDrawer(id) {
|
||||
var p = null;
|
||||
for (var i = 0; i < participants.length; i++) if (participants[i].id === id) { p = participants[i]; break; }
|
||||
if (!p) return;
|
||||
selectedId = id;
|
||||
|
||||
$("#d-name").textContent = p.lastname + " " + p.name;
|
||||
var phoneEl = $("#d-phone");
|
||||
phoneEl.textContent = p.phone;
|
||||
phoneEl.toggleAttribute("data-empty", !p.phone);
|
||||
$("#d-date").textContent = p.date;
|
||||
$("#d-status").innerHTML = statusPill(p.status);
|
||||
$("#d-confirm").hidden = p.status === "confirmed";
|
||||
$("#d-confirm").setAttribute("data-tone", p.status === "confirmed" ? "ok" : "");
|
||||
|
||||
drawer.hidden = false;
|
||||
document.documentElement.classList.add("kt-ai-scroll-locked");
|
||||
renderAdmin();
|
||||
}
|
||||
function closeDrawer() {
|
||||
drawer.hidden = true;
|
||||
selectedId = null;
|
||||
document.documentElement.classList.remove("kt-ai-scroll-locked");
|
||||
renderAdmin();
|
||||
}
|
||||
$(".drawer-close").addEventListener("click", closeDrawer);
|
||||
$("#d-close").addEventListener("click", closeDrawer);
|
||||
document.addEventListener("keydown", function (e) {
|
||||
if (e.key === "Escape" && !drawer.hidden) closeDrawer();
|
||||
});
|
||||
|
||||
$("#d-confirm").addEventListener("click", function () {
|
||||
var p = null;
|
||||
for (var i = 0; i < participants.length; i++) if (participants[i].id === selectedId) { p = participants[i]; break; }
|
||||
if (!p) return;
|
||||
p.status = "confirmed";
|
||||
save(participants);
|
||||
toast(p.lastname + " " + p.name + ": участие подтверждено", "ok");
|
||||
openDrawer(p.id);
|
||||
});
|
||||
|
||||
$("#d-unlink").addEventListener("click", function () {
|
||||
var p = null;
|
||||
for (var i = 0; i < participants.length; i++) if (participants[i].id === selectedId) { p = participants[i]; break; }
|
||||
if (!p) return;
|
||||
var idx = participants.indexOf(p);
|
||||
participants.splice(idx, 1);
|
||||
save(participants);
|
||||
closeDrawer();
|
||||
toast(p.lastname + " " + p.name + " убран из списка");
|
||||
});
|
||||
|
||||
/* ── Навигация ── */
|
||||
document.querySelectorAll("[data-nav]").forEach(function (el) {
|
||||
el.addEventListener("click", function (ev) {
|
||||
ev.preventDefault();
|
||||
showView("participant");
|
||||
var target = this.getAttribute("data-nav");
|
||||
if (target && target !== "reg") {
|
||||
setTimeout(function () {
|
||||
var sec = document.getElementById(target);
|
||||
if (sec) sec.scrollIntoView({ behavior: "smooth", block: "start" });
|
||||
}, 60);
|
||||
} else {
|
||||
window.scrollTo({ top: 0, behavior: "smooth" });
|
||||
}
|
||||
});
|
||||
});
|
||||
var adminLink = document.querySelector(".tb-admin-link-strong");
|
||||
if (adminLink) adminLink.addEventListener("click", function () { showView("admin"); });
|
||||
|
||||
/* стартовый вид по location.hash */
|
||||
if (location.hash === "#admin" || location.hash === "#view-admin") showView("admin");
|
||||
else showView("participant");
|
||||
})();
|
||||
114
style.css
Normal file
114
style.css
Normal file
@ -0,0 +1,114 @@
|
||||
/* Страница «Тимбилдинг» — только раскладка и мелочи; цвета и компоненты рисует ДС. */
|
||||
|
||||
/* ── Шапка участника: логотип + название рядом ── */
|
||||
.tb-brand {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: var(--kt-ai-space-4);
|
||||
min-width: 0;
|
||||
}
|
||||
.tb-logo { display: block; flex: 0 0 auto; }
|
||||
.tb-brand-name {
|
||||
font-size: var(--kt-ai-text-md);
|
||||
font-weight: var(--kt-ai-weight-medium);
|
||||
color: var(--kt-ai-fg);
|
||||
white-space: nowrap;
|
||||
}
|
||||
.tb-pagehead-in { gap: var(--kt-ai-space-5); }
|
||||
|
||||
/* ── Chip в hero: отбить от заголовка ── */
|
||||
.kt-ai-hero .kt-ai-chip { margin-top: calc(-1 * var(--kt-ai-space-9)); }
|
||||
|
||||
/* ── Иконка в плитке: спокойный контейнер ── */
|
||||
.kt-ai-tile-icon {
|
||||
display: inline-grid;
|
||||
place-items: center;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
margin-bottom: var(--kt-ai-space-4);
|
||||
border-radius: var(--vibe-card-radius, var(--kt-ai-radius-xl));
|
||||
background: var(--vibe-tint, var(--kt-ai-primary-subtle));
|
||||
color: var(--kt-ai-fg);
|
||||
}
|
||||
.kt-icon { width: 18px; height: 18px; }
|
||||
|
||||
/* ── Примечание под расписанием ── */
|
||||
.tb-note { padding: var(--kt-ai-space-5) var(--kt-ai-space-6); }
|
||||
|
||||
/* ── Форма регистрации ── */
|
||||
#regform .kt-ai-btn { width: 100%; margin-top: var(--kt-ai-space-2); }
|
||||
#regdone {
|
||||
border: 1px dashed var(--kt-ai-border-strong);
|
||||
margin-top: 0;
|
||||
}
|
||||
.kt-ai-empty-ic {
|
||||
display: inline-grid;
|
||||
place-items: center;
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
border-radius: var(--kt-ai-radius-full);
|
||||
background: var(--kt-ai-status-ok-bg, var(--kt-ai-primary-subtle));
|
||||
color: var(--kt-ai-status-ok-fg, var(--kt-ai-fg));
|
||||
}
|
||||
.kt-ai-empty-ic .kt-icon { width: 20px; height: 20px; }
|
||||
|
||||
/* ── Подвал ── */
|
||||
.tb-admin-link {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
font-size: var(--kt-ai-text-xs);
|
||||
color: var(--kt-ai-fg-muted);
|
||||
padding: var(--kt-ai-space-2) var(--kt-ai-space-3);
|
||||
border-radius: var(--kt-ai-radius-md);
|
||||
}
|
||||
.tb-admin-link:hover { background: var(--kt-ai-bg-hover); color: var(--kt-ai-fg); }
|
||||
|
||||
/* ── Сайдбар админки ── */
|
||||
.tb-brand-v { padding: 0 var(--kt-ai-space-5); }
|
||||
.kt-ai-spacer-tb { flex: 1; min-height: var(--kt-ai-space-8); }
|
||||
.tb-admin-card { margin: 0 var(--kt-ai-space-5); background: var(--kt-ai-bg) !important; border: 1px solid var(--kt-ai-border); padding: var(--kt-ai-space-4) var(--kt-ai-space-5); }
|
||||
.tb-admin-card p { margin: var(--kt-ai-space-2) 0 0; font-size: var(--kt-ai-text-2xs); line-height: var(--kt-ai-leading-normal); }
|
||||
|
||||
/* ── Topbar: хлебные крошки ── */
|
||||
.kt-ai-breadcrumb {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: var(--kt-ai-space-3);
|
||||
font-size: var(--kt-ai-text-sm);
|
||||
min-width: 0;
|
||||
}
|
||||
.kt-ai-breadcrumb .kt-icon { width: 14px; height: 14px; opacity: .6; }
|
||||
.kt-ai-breadcrumb strong { font-weight: var(--kt-ai-weight-semibold); }
|
||||
|
||||
/* KPI-полоса админки: плотнее дефолта */
|
||||
#adm-kpis { margin-top: var(--kt-ai-space-6); }
|
||||
|
||||
/* ── Tabula: строки кликабельны ── */
|
||||
#adm-tbody tr { cursor: pointer; }
|
||||
#adm-tbody .cell-name { font-weight: var(--kt-ai-weight-medium); }
|
||||
#adm-tbody .cell-sub { display: block; font-size: var(--kt-ai-text-2xs); color: var(--kt-ai-fg-faint); margin-top: 1px; }
|
||||
#adm-tbody td.num { white-space: nowrap; }
|
||||
|
||||
/* ── Drawer ── */
|
||||
.drawer-head { display: flex; align-items: center; gap: var(--kt-ai-space-4); margin-bottom: var(--kt-ai-space-6); }
|
||||
.drawer-head > .kt-ai-t-muted { font-size: var(--kt-ai-text-2xs); text-transform: uppercase; letter-spacing: .8px; white-space: nowrap; }
|
||||
.drawer-head strong { font-size: var(--kt-ai-text-xl); font-weight: var(--kt-ai-weight-semibold); flex: 1; min-width: 0; }
|
||||
.drawer-close {
|
||||
display: inline-grid; place-items: center;
|
||||
width: 32px; height: 32px; flex: 0 0 auto;
|
||||
border-radius: var(--kt-ai-radius-lg);
|
||||
color: var(--kt-ai-fg-muted); cursor: pointer;
|
||||
border: 1px solid var(--kt-ai-border);
|
||||
}
|
||||
.drawer-close:hover { background: var(--kt-ai-bg-hover); color: var(--kt-ai-fg); }
|
||||
.drawer-body .kt-ai-prop { border-bottom: 1px solid var(--kt-ai-divider); padding-bottom: var(--kt-ai-space-4); }
|
||||
.drawer-body .kt-ai-prop:last-child { border-bottom: none; padding-bottom: 0; }
|
||||
#d-phone { font-variant-numeric: tabular-nums; }
|
||||
|
||||
/* ── Мобильные ── */
|
||||
@media (max-width: 639.98px) {
|
||||
.tb-brand-name { font-size: var(--kt-ai-text-sm); }
|
||||
.kt-ai-kpi-strip { gap: var(--kt-ai-space-6); }
|
||||
.kt-ai-kpi .hint { max-width: 100%; }
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user