registratsiya-na-hakaton-kaz/script.js

204 lines
9.0 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Хакатон Казахтелеком — демо-логика (localStorage, без сервера)
const SEED_PARTICIPANTS = [
{ id: "p1", name: "Айгерим Нурлан", email: "aigerim.nur@example.kz", phone: "+7 777 145 20 18", city: "Астана", direction: "Frontend", team: "solo" },
{ id: "p2", name: "Дмитрий Козлов", email: "d.kozlov@example.com", phone: "+7 701 233 90 41", city: "Алматы", direction: "Backend", team: "team" },
{ id: "p3", name: "Жанара Сәтбаева", email: "janara.sat@example.kz", phone: "+7 747 882 15 07", city: "Караганда", direction: "Data Science", team: "solo" },
{ id: "p4", name: "Ержан Алиев", email: "yerzhan.aliev@example.kz", phone: "+7 705 641 33 82", city: "Астана", direction: "Frontend", team: "team" },
{ id: "p5", name: "Марат Суюнбеков", email: "m.suyunbekov@example.kz", phone: "+7 708 402 77 51", city: "Алматы", direction: "Backend", team: "solo" },
{ id: "p6", name: "Айлин Керимова", email: "ailin.kerimova@example.kz", phone: "+7 745 918 26 44", city: "Астана", direction: "Data Science", team: "team" },
{ id: "p7", name: "Тимур Оспанов", email: "t.ospanov@example.kz", phone: "+7 702 550 19 73", city: "Шымкент", direction: "Frontend", team: "solo" }
];
function loadParticipants() {
try {
const raw = localStorage.getItem("kt_hackathon_v1");
const arr = raw ? JSON.parse(raw) : null;
if (Array.isArray(arr) && arr.length) return arr;
} catch (e) { /* повреждённые данные — пересеваем */ }
localStorage.setItem("kt_hackathon_v1", JSON.stringify(SEED_PARTICIPANTS));
return SEED_PARTICIPANTS.slice();
}
function saveParticipants(list) {
localStorage.setItem("kt_hackathon_v1", JSON.stringify(list));
}
function esc(s) {
return String(s).replace(/[&<>"']/g, c => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" }[c]));
}
const DIR_LABELS = { "Frontend": "Frontend", "Backend": "Backend", "Data Science": "Data Science" };
const DIR_CLASS = { "Frontend": "frontend", "Backend": "backend", "Data Science": "data" };
/* ================= Главный экран: статистика ================= */
function renderStats() {
const el = document.getElementById("stats");
if (!el) return;
const total = loadParticipants().length;
const items = [
{ icon: "calendar", value: "28.09.2026", label: "Дата проведения", hint: "суббота" },
{ icon: "clock", value: "10:00", label: "Начало", hint: "длительность 5 часов" },
{ icon: "building", value: "ул. Сауран, 12", label: "Место проведения", hint: "Казахстан" },
{ icon: "users", value: String(total), label: "Участников", hint: "регистрация открыта" }
];
el.innerHTML = items.map(k =>
'<div class="kt-ai-kpi">' +
'<span class="value">' + esc(k.value) + '</span>' +
'<span class="label">' + esc(k.label) + '</span>' +
'<span class="hint">' + esc(k.hint) + '</span>' +
'</div>'
).join("");
}
/* ================= Форма регистрации ================= */
function initRegister() {
const form = document.getElementById("reg-form");
if (!form) return;
function setError(key, msg) {
const el = form.querySelector('[data-err="' + key + '"]');
if (el) el.textContent = msg || "";
}
form.addEventListener("submit", function (ev) {
ev.preventDefault();
const data = {
name: document.getElementById("f-name").value.trim(),
email: document.getElementById("f-email").value.trim(),
phone: document.getElementById("f-phone").value.trim(),
city: document.getElementById("f-city").value.trim() || "—",
direction: document.getElementById("f-dir").value,
team: document.getElementById("f-team").value,
consent: document.getElementById("f-consent").checked
};
const errors = {};
if (data.name.length < 5) errors.name = "Укажите фамилию и имя полностью";
if (!/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(data.email)) errors.email = "Укажите корректный email";
if (!/^[+\d][\d\s()-]{9,}$/.test(data.phone)) errors.phone = "Укажите телефон, например +7 700 555 12 34";
if (!data.direction) errors.dir = "Выберите направление";
if (!data.team) errors.team = "Выберите формат участия";
if (!data.consent) errors.consent = "Без согласия мы не можем отправить подтверждение";
Object.keys({ name: 1, email: 1, phone: 1, dir: 1, team: 1, consent: 1 }).forEach(k => setError(k, errors[k]));
if (Object.keys(errors).length) return;
const list = loadParticipants();
if (!list.some(p => p.email.toLowerCase() === data.email.toLowerCase())) {
list.unshift({
id: "r" + Date.now(),
name: data.name,
email: data.email,
phone: data.phone,
city: data.city,
direction: data.direction,
team: data.team
});
saveParticipants(list);
}
const succ = document.getElementById("reg-succ");
document.getElementById("reg-succ-text").textContent =
"Подтверждение отправим на " + data.email + " накануне мероприятия.";
form.hidden = true;
succ.hidden = false;
succ.scrollIntoView({ behavior: "smooth", block: "center" });
});
}
/* ================= Список участников ================= */
let partState = { q: "", dir: "all" };
function initParticipants() {
const tbody = document.getElementById("part-tbody");
if (!tbody) return;
const searchField = document.getElementById("p-search");
const chips = document.getElementById("dir-chips");
const empty = document.getElementById("part-empty");
const strip = document.getElementById("kpi-strip");
function kpiHtml(v, l, h) {
return '<div class="kt-ai-kpi"><span class="value">' + esc(v) + '</span><span class="label">' + esc(l) + '</span><span class="hint">' + esc(h) + '</span></div>';
}
function render() {
const all = loadParticipants();
// KPI: считаются по текущему фильтру направления
const base = partState.dir === "all" ? all : all.filter(p => p.direction === partState.dir);
const total = base.length;
const solo = base.filter(p => p.team === "solo").length;
const cities = new Set(base.map(p => p.city)).size;
strip.innerHTML =
kpiHtml(String(total), "Участников", partState.dir === "all" ? "всего" : "по направлению") +
kpiHtml(String(solo), "Индивидуально", "остальные — с командами") +
kpiHtml(String(cities), "Городов", "представлены");
// чипы направлений
const dirs = ["all", "Frontend", "Backend", "Data Science"];
chips.innerHTML = dirs.map(d => {
const active = partState.dir === d;
const n = d === "all" ? all.length : all.filter(p => p.direction === d).length;
const label = d === "all" ? "Все" : DIR_LABELS[d];
return '<button type="button" class="kt-ai-chip ds-dir-chip" data-dir="' + d + '"' +
(active ? ' data-active="true"' : '') + '>' + esc(label) + ' · ' + n + '</button>';
}).join("");
// строки
const q = partState.q.toLowerCase();
const rows = all.filter(p =>
(partState.dir === "all" || p.direction === partState.dir) &&
(!q || p.name.toLowerCase().includes(q) || p.city.toLowerCase().includes(q) || p.email.toLowerCase().includes(q))
);
tbody.innerHTML = rows.map(p =>
"<tr>" +
"<td><span class=\"ds-p-name\">" + esc(p.name) + '</span><span class="ds-p-email">' + esc(p.email) + "</span></td>" +
"<td>" + esc(p.city) + "</td>" +
'<td><span class="ds-chip ' + (DIR_CLASS[p.direction] || "") + '">' + esc(DIR_LABELS[p.direction] || p.direction) + "</span></td>" +
'<td class="ds-p-count">' + (p.team === "team" ? "команда" : "соло") + "</td>" +
"</tr>"
).join("");
empty.hidden = rows.length > 0;
}
chips.addEventListener("click", ev => {
const btn = ev.target.closest("[data-dir]");
if (!btn) return;
partState.dir = btn.getAttribute("data-dir");
render();
});
searchField.addEventListener("input", () => {
partState.q = searchField.value.trim();
render();
});
render();
}
/* ================= Старт ================= */
function init() {
if (document.getElementById("reg-form")) {
if (new URLSearchParams(location.search).has("registered")) {
const succ = document.getElementById("reg-succ");
const form = document.getElementById("reg-form");
document.getElementById("reg-succ-text").textContent = "Подтверждение отправим на почту накануне мероприятия.";
form.hidden = true;
succ.hidden = false;
}
}
renderStats();
initRegister();
initParticipants();
}
document.addEventListener("DOMContentLoaded", init);