hr_system/script.js

170 lines
8.4 KiB
JavaScript
Raw 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.

function api(url, opts) {
return fetch(url, opts).then(function (r) {
if (!r.ok) return r.json().then(function (j) { throw new Error(j.error || "Ошибка сервера"); });
return r.json();
});
}
/* ==== Обратный отсчёт до 20.07.2027 19:00 ==== */
var TARGET = new Date("2027-07-20T19:00:00").getTime();
function tick() {
var diff = Math.max(0, TARGET - Date.now());
document.getElementById("cd-d").textContent = Math.floor(diff / 86400000);
document.getElementById("cd-h").textContent = Math.floor(diff / 3600000) % 24;
document.getElementById("cd-m").textContent = Math.floor(diff / 60000) % 60;
document.getElementById("cd-s").textContent = Math.floor(diff / 1000) % 60;
}
setInterval(tick, 1000);
tick();
/* ==== Мок-данные для Предпросмотра (когда сервер не запущен) ==== */
var MOCK = [
{ id: 101, title: "Книга «Властелин колец» (подарочное издание)", emoji: "📚", reservedBy: "" },
{ id: 102, title: "Шахматы в фэнтези-стиле (драконы против эльфов)", emoji: "♟️", reservedBy: "" },
{ id: 103, title: "Настольная игра «Война кольца»", emoji: "🎲", reservedBy: "Айгуль" }
];
/* ==== Регистрация ==== */
var regForm = document.getElementById("regForm");
regForm.addEventListener("submit", function (e) {
e.preventDefault();
var msg = document.getElementById("regMsg");
var data = {
name: document.getElementById("r-name").value.trim(),
phone: document.getElementById("r-phone").value.trim(),
guests: document.getElementById("r-guests").value,
note: document.getElementById("r-note").value.trim()
};
msg.className = "msg"; msg.textContent = "Отправляем клятву в королевство...";
api("/api/regs", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(data) })
.then(function (j) {
msg.className = "msg ok";
msg.textContent = "🎉 Ты в списке героев! Врата откроются 20.07.2027 в 19:00 — до встречи!";
var c = document.getElementById("confirmedCount");
c.hidden = false; c.querySelector("b").textContent = j.total;
regForm.reset();
})
.catch(function (err) {
if (err.message === "Failed to fetch" || /fetch/i.test(err.message)) {
msg.className = "msg ok";
msg.textContent = "🎉 Ты в списке героев! (демо-режим: сервер не запущен, на живой ссылке заявка сохранится)";
} else {
msg.className = "msg err";
msg.textContent = "Не получилось: " + err.message;
}
});
});
/* ==== Вишлист ==== */
function esc(s) {
return String(s == null ? "" : s).replace(/[&<>"']/g, function (c) {
return { "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" }[c];
});
}
function renderWishes(wishes) {
var grid = document.getElementById("wishGrid");
var note = document.getElementById("wishNote");
if (!wishes.length) {
grid.innerHTML = "";
note.textContent = "Сокровищница пока пуста — скоро наполним ✨";
return;
}
grid.innerHTML = wishes.map(function (w) {
var taken = !!w.reservedBy;
return '<div class="gift' + (taken ? " taken" : "") + '">' +
'<div class="g-emoji">' + esc(w.emoji) + '</div>' +
'<div class="g-title">' + esc(w.title) + '</div>' +
'<div class="g-status' + (taken ? " taken" : "") + '">' + (taken ? "✦ Забрал(а): " + esc(w.reservedBy) : "Ещё свободен") + '</div>' +
(taken
? '<button class="g-btn done" disabled>Забронировано</button>'
: '<button class="g-btn" data-id="' + w.id + '">Забираю этот дар</button>') +
'</div>';
}).join("");
note.textContent = wishes.filter(function (w) { return w.reservedBy; }).length + " из " + wishes.length + " даров уже обрели хозяина";
grid.querySelectorAll(".g-btn:not(.done)").forEach(function (btn) {
btn.addEventListener("click", function () {
var guest = prompt("Напиши своё имя, чтобы мы знали — кто несёт этот дар:");
if (!guest) return;
api("/api/wish/reserve", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ id: Number(btn.dataset.id), guest: guest.trim() }) })
.then(function (j) { renderWishes(j.data); })
.catch(function (err) { alert(err.message); });
});
});
}
function loadWishes() {
api("/api/wish").then(function (j) { renderWishes(j.data); })
.catch(function () { renderWishes(MOCK); });
}
loadWishes();
/* ==== Панель хозяина ==== */
var pin = sessionStorage.getItem("bday_pin") || "";
var aLogin = document.getElementById("a-login");
var aPanel = document.getElementById("a-panel");
var aMsg = document.getElementById("a-msg");
function renderAdminWishes(wishes) {
document.getElementById("a-wishes").innerHTML = wishes.map(function (w) {
return '<div class="a-row"><span class="g-emoji" style="font-size:20px">' + esc(w.emoji) + '</span>' +
'<span class="grow">' + esc(w.title) + (w.reservedBy ? ' <span class="gold">· ' + esc(w.reservedBy) + '</span>' : "") + '</span>' +
'<button class="a-del" data-id="' + w.id + '">Удалить</button></div>';
}).join("");
document.querySelectorAll("#a-wishes .a-del").forEach(function (b) {
b.addEventListener("click", function () {
api("/api/admin/wish/del", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ pin: pin, id: Number(b.dataset.id) }) })
.then(function (j) { renderAdminWishes(j.data); renderWishes(j.data); })
.catch(function (err) { aMsg.className = "msg err"; aMsg.textContent = err.message; });
});
});
}
function loadAdmin() {
api("/api/admin/regs", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ pin: pin }) })
.then(function (j) {
aMsg.className = "msg ok"; aMsg.textContent = "Добро пожаловать, хозяин замка 🏰";
aPanel.hidden = false;
var regs = j.data;
document.getElementById("a-regs").innerHTML = regs.length
? regs.map(function (r) {
return '<div class="a-row"><span class="grow"><span class="guest-name">' + esc(r.name) + '</span><div class="guest-info">' + esc(r.phone) + " · гостей: " + r.guests + (r.note ? " · «" + esc(r.note) + "»" : "") + " · " + r.date + '</div></span></div>';
}).join("")
: '<div class="muted">Пока никто не подтвердил участие.</div>';
document.getElementById("confirmedCount").hidden = false;
document.getElementById("confirmedCount").querySelector("b").textContent = j.total;
return api("/api/wish");
})
.then(function (j) { renderAdminWishes(j.data); })
.catch(function (err) {
aMsg.className = "msg err"; aMsg.textContent = "Неверный код или ошибка: " + err.message;
});
}
aLogin.addEventListener("click", function () {
pin = document.getElementById("a-pin").value.trim();
if (!pin) return;
sessionStorage.setItem("bday_pin", pin);
loadAdmin();
});
document.getElementById("a-pin").addEventListener("keydown", function (e) { if (e.key === "Enter") aLogin.click(); });
document.getElementById("a-wadd").addEventListener("click", function () {
var title = document.getElementById("a-wtitle").value.trim();
var emoji = document.getElementById("a-wemoji").value.trim() || "🎁";
if (!title) return;
api("/api/admin/wish", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ pin: pin, title: title, emoji: emoji }) })
.then(function (j) {
renderAdminWishes(j.data); renderWishes(j.data);
document.getElementById("a-wtitle").value = "";
document.getElementById("a-wemoji").value = "";
})
.catch(function (err) { aMsg.className = "msg err"; aMsg.textContent = err.message; });
});
if (pin) {
document.getElementById("a-pin").value = pin;
loadAdmin();
}
/* ==== Плавные появления ==== */
var io = new IntersectionObserver(function (entries) {
entries.forEach(function (e) { if (e.isIntersecting) e.target.classList.add("in"); });
}, { threshold: .15 });
document.querySelectorAll(".reveal").forEach(function (el) { io.observe(el); });