204 lines
9.0 KiB
JavaScript
204 lines
9.0 KiB
JavaScript
// Хакатон Казахтелеком — демо-логика (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 => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }[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);
|