Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4bcba9cf90 |
4
.vibe42-run.log
Normal file
4
.vibe42-run.log
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
--- run Sun Sep 20 07:06:05 PM UTC 2026 :: npm start --silent
|
||||||
|
|
||||||
|
up to date in 395ms
|
||||||
|
Регистрация тимбилдинга на порту 3000
|
||||||
1
.vibe42-run.pid
Normal file
1
.vibe42-run.pid
Normal file
@ -0,0 +1 @@
|
|||||||
|
86
|
||||||
98
data.json
Normal file
98
data.json
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
]
|
||||||
10
package-lock.json
generated
Normal file
10
package-lock.json
generated
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"name": "app",
|
||||||
|
"lockfileVersion": 3,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {
|
||||||
|
"": {
|
||||||
|
"name": "app"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
6
package.json
Normal file
6
package.json
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"name": "app",
|
||||||
|
"private": true,
|
||||||
|
"type": "commonjs",
|
||||||
|
"scripts": { "start": "node server.js" }
|
||||||
|
}
|
||||||
159
script.js
159
script.js
@ -1,10 +1,12 @@
|
|||||||
/* Тимбилдинг ДЦБ Астана — регистрация участников и список администратора.
|
/* Тимбилдинг ДЦБ Астана — регистрация участников и список администратора.
|
||||||
Данные живут в localStorage (общий хранилище страницы); при первом заходе
|
Список участников хранится на сервере (api/participants → data.json) и
|
||||||
список наполняется примерами участников для показа структуры. */
|
виден всем; если сервер недоступен (открывают статичную страницу) —
|
||||||
|
работаем в localStorage браузера. */
|
||||||
(function () {
|
(function () {
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
var LS_KEY = "teambuilding_dcb_participants";
|
var LS_KEY = "teambuilding_dcb_participants";
|
||||||
|
var usingLocal = false;
|
||||||
|
|
||||||
var STATUS = {
|
var STATUS = {
|
||||||
waiting: { label: "Ждёт подтверждения", tone: "warn" },
|
waiting: { label: "Ждёт подтверждения", tone: "warn" },
|
||||||
@ -27,25 +29,60 @@
|
|||||||
];
|
];
|
||||||
|
|
||||||
/* ── Хранилище ── */
|
/* ── Хранилище ── */
|
||||||
function load() {
|
function lsLoad() {
|
||||||
try {
|
try {
|
||||||
var raw = localStorage.getItem(LS_KEY);
|
var raw = localStorage.getItem(LS_KEY);
|
||||||
if (raw) {
|
if (raw) {
|
||||||
var arr = JSON.parse(raw);
|
var arr = JSON.parse(raw);
|
||||||
if (Array.isArray(arr)) return arr;
|
if (Array.isArray(arr)) return arr;
|
||||||
}
|
}
|
||||||
} catch (e) { /* повреждённые данные — пересеять */ }
|
} catch (e) {}
|
||||||
localStorage.setItem(LS_KEY, JSON.stringify(SEED));
|
try { localStorage.setItem(LS_KEY, JSON.stringify(SEED)); } catch (e) {}
|
||||||
return SEED.slice();
|
return SEED.slice();
|
||||||
}
|
}
|
||||||
function save(list) {
|
function lsSave(list) {
|
||||||
try { localStorage.setItem(LS_KEY, JSON.stringify(list)); } catch (e) {}
|
try { localStorage.setItem(LS_KEY, JSON.stringify(list)); } catch (e) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
var participants = load();
|
function apiList() {
|
||||||
|
return fetch("api/participants", { headers: { Accept: "application/json" }, cache: "no-store" })
|
||||||
|
.then(function (r) {
|
||||||
|
if (!r.ok) throw new Error("http " + r.status);
|
||||||
|
return r.json();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function apiCreate(rec) {
|
||||||
|
return fetch("api/participants", {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
body: JSON.stringify(rec)
|
||||||
|
}).then(function (r) {
|
||||||
|
if (!r.ok) throw new Error("http " + r.status);
|
||||||
|
return r.json();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var participants = [];
|
||||||
var nextId = 1;
|
var nextId = 1;
|
||||||
for (var i = 0; i < participants.length; i++) {
|
function reindex() {
|
||||||
if (participants[i].id >= nextId) nextId = participants[i].id + 1;
|
nextId = 1;
|
||||||
|
for (var i = 0; i < participants.length; i++) {
|
||||||
|
if (typeof participants[i].id === "number" && participants[i].id >= nextId) nextId = participants[i].id + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadAll() {
|
||||||
|
return apiList().then(function (arr) {
|
||||||
|
if (!Array.isArray(arr)) throw new Error("bad payload");
|
||||||
|
participants = arr;
|
||||||
|
reindex();
|
||||||
|
return participants;
|
||||||
|
}).catch(function () {
|
||||||
|
usingLocal = true;
|
||||||
|
participants = lsLoad();
|
||||||
|
reindex();
|
||||||
|
return participants;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── Хелперы ── */
|
/* ── Хелперы ── */
|
||||||
@ -88,7 +125,6 @@
|
|||||||
p.hidden = false;
|
p.hidden = false;
|
||||||
}
|
}
|
||||||
if (name === "admin") renderAdmin();
|
if (name === "admin") renderAdmin();
|
||||||
try { location.hash = name; } catch (e) {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── Форма регистрации ── */
|
/* ── Форма регистрации ── */
|
||||||
@ -98,6 +134,11 @@
|
|||||||
var fLast = $("#f-lastname");
|
var fLast = $("#f-lastname");
|
||||||
var fPhone = $("#f-phone");
|
var fPhone = $("#f-phone");
|
||||||
|
|
||||||
|
function flashField(input, msg) {
|
||||||
|
input.focus();
|
||||||
|
toast(msg, "warn");
|
||||||
|
}
|
||||||
|
|
||||||
form.addEventListener("submit", function (ev) {
|
form.addEventListener("submit", function (ev) {
|
||||||
ev.preventDefault();
|
ev.preventDefault();
|
||||||
var name = fName.value.trim();
|
var name = fName.value.trim();
|
||||||
@ -110,41 +151,45 @@
|
|||||||
flashField(fPhone, "Укажите телефон в формате +7 XXX XXX XX XX");
|
flashField(fPhone, "Укажите телефон в формате +7 XXX XXX XX XX");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var dup = null;
|
|
||||||
for (var i = 0; i < participants.length; i++) {
|
for (var i = 0; i < participants.length; i++) {
|
||||||
var p = participants[i];
|
var p = participants[i];
|
||||||
var key = (p.name + " " + p.lastname).trim().replace(/\s+/g, " ").toLowerCase();
|
var key = (p.name + " " + p.lastname).trim().replace(/\s+/g, " ").toLowerCase();
|
||||||
if (key === (name + " " + lastname).replace(/\s+/g, " ").toLowerCase() &&
|
if (key === (name + " " + lastname).replace(/\s+/g, " ").toLowerCase() &&
|
||||||
p.phone.replace(/\D/g, "") === phone.replace(/\D/g, "")) { dup = p; break; }
|
String(p.phone).replace(/\D/g, "") === phone.replace(/\D/g, "")) {
|
||||||
|
toast("Этот участник уже зарегистрирован", "warn");
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (dup) {
|
|
||||||
toast("Этот участник уже зарегистрирован", "warn");
|
var rec = { name: name, lastname: lastname, phone: phone, status: "waiting", date: todayStr() };
|
||||||
|
|
||||||
|
function finished(savedRec) {
|
||||||
|
rec = savedRec || rec;
|
||||||
|
participants.unshift(rec);
|
||||||
|
reindex();
|
||||||
|
form.hidden = true;
|
||||||
|
done.hidden = false;
|
||||||
|
$("#regdone-text").textContent =
|
||||||
|
name + ", вы в списке участников. Администратор подтвердит участие до 18 сентября.";
|
||||||
|
toast("Регистрация принята", "ok");
|
||||||
|
renderAdmin();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (usingLocal) {
|
||||||
|
finished({ id: nextId, name: name, lastname: lastname, phone: phone, status: "waiting", date: todayStr() });
|
||||||
|
lsSave(participants);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var rec = {
|
apiCreate(rec).then(function (json) {
|
||||||
id: nextId++,
|
finished(json && json.record ? json.record : { id: nextId, name: name, lastname: lastname, phone: phone, status: "waiting", date: todayStr() });
|
||||||
name: name,
|
}).catch(function () {
|
||||||
lastname: lastname,
|
usingLocal = true;
|
||||||
phone: phone,
|
finished({ id: nextId, name: name, lastname: lastname, phone: phone, status: "waiting", date: todayStr() });
|
||||||
status: "waiting",
|
lsSave(participants);
|
||||||
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 () {
|
$("#regdone-again").addEventListener("click", function () {
|
||||||
form.reset();
|
form.reset();
|
||||||
done.hidden = true;
|
done.hidden = true;
|
||||||
@ -174,10 +219,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function renderAdmin() {
|
function renderAdmin() {
|
||||||
/* навигация-счётчик */
|
|
||||||
$("#nav-count").textContent = participants.length;
|
$("#nav-count").textContent = participants.length;
|
||||||
|
|
||||||
/* KPI */
|
|
||||||
var total = participants.length;
|
var total = participants.length;
|
||||||
var confirmed = 0, waiting = 0;
|
var confirmed = 0, waiting = 0;
|
||||||
for (var i = 0; i < participants.length; i++) {
|
for (var i = 0; i < participants.length; i++) {
|
||||||
@ -187,19 +230,16 @@
|
|||||||
$("#kpi-confirmed").textContent = confirmed;
|
$("#kpi-confirmed").textContent = confirmed;
|
||||||
$("#kpi-waiting").textContent = waiting;
|
$("#kpi-waiting").textContent = waiting;
|
||||||
|
|
||||||
/* фильтр-чипы */
|
|
||||||
var chips = document.querySelectorAll(".kt-ai-filter-chip");
|
var chips = document.querySelectorAll(".kt-ai-filter-chip");
|
||||||
for (var j = 0; j < chips.length; j++) {
|
for (var j = 0; j < chips.length; j++) {
|
||||||
chips[j].setAttribute("data-active", chips[j].getAttribute("data-status") === statusFilter ? "true" : "false");
|
chips[j].setAttribute("data-active", chips[j].getAttribute("data-status") === statusFilter ? "true" : "false");
|
||||||
}
|
}
|
||||||
|
|
||||||
/* таблица */
|
|
||||||
var list = filtered();
|
var list = filtered();
|
||||||
var tbody = $("#adm-tbody");
|
|
||||||
var html = "";
|
var html = "";
|
||||||
for (var k = 0; k < list.length; k++) {
|
for (var k = 0; k < list.length; k++) {
|
||||||
var p = list[k];
|
var p = list[k];
|
||||||
html += "<tr data-id=\"" + p.id + '"' + (p.id === selectedId ? ' data-selected="true"' : "") + ">" +
|
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>" +
|
'<td data-label="Участник"><span class="cell-name">' + esc(p.lastname) + " " + esc(p.name) + "</span>" +
|
||||||
'<span class="cell-sub">ДЦБ Астана</span></td>' +
|
'<span class="cell-sub">ДЦБ Астана</span></td>' +
|
||||||
'<td data-label="Телефон" class="num">' + esc(p.phone) + "</td>" +
|
'<td data-label="Телефон" class="num">' + esc(p.phone) + "</td>" +
|
||||||
@ -208,9 +248,8 @@
|
|||||||
'<td><span class="row-act kt-ai-tooltip" data-tip="Подробнее" aria-hidden="true"><svg class="kt-icon"><use href="#chevronRight"></use></svg></span></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>";
|
"</tr>";
|
||||||
}
|
}
|
||||||
tbody.innerHTML = html;
|
$("#adm-tbody").innerHTML = html;
|
||||||
|
|
||||||
/* пустое состояние */
|
|
||||||
var empty = $("#adm-empty");
|
var empty = $("#adm-empty");
|
||||||
if (list.length === 0) {
|
if (list.length === 0) {
|
||||||
empty.hidden = false;
|
empty.hidden = false;
|
||||||
@ -224,14 +263,12 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* клики по строке и по «открыть» */
|
|
||||||
$("#adm-tbody").addEventListener("click", function (ev) {
|
$("#adm-tbody").addEventListener("click", function (ev) {
|
||||||
var tr = ev.target.closest("tr[data-id]");
|
var tr = ev.target.closest("tr[data-id]");
|
||||||
if (!tr) return;
|
if (!tr) return;
|
||||||
openDrawer(parseInt(tr.getAttribute("data-id"), 10));
|
openDrawer(parseInt(tr.getAttribute("data-id"), 10));
|
||||||
});
|
});
|
||||||
|
|
||||||
/* поиск + фильтры */
|
|
||||||
$("#adm-search").addEventListener("input", function () {
|
$("#adm-search").addEventListener("input", function () {
|
||||||
searchQuery = this.value.trim().toLowerCase();
|
searchQuery = this.value.trim().toLowerCase();
|
||||||
renderAdmin();
|
renderAdmin();
|
||||||
@ -246,9 +283,13 @@
|
|||||||
/* ── Drawer ── */
|
/* ── Drawer ── */
|
||||||
var drawer = $("#drawer");
|
var drawer = $("#drawer");
|
||||||
|
|
||||||
|
function findRec(id) {
|
||||||
|
for (var i = 0; i < participants.length; i++) if (participants[i].id === id) return participants[i];
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
function openDrawer(id) {
|
function openDrawer(id) {
|
||||||
var p = null;
|
var p = findRec(id);
|
||||||
for (var i = 0; i < participants.length; i++) if (participants[i].id === id) { p = participants[i]; break; }
|
|
||||||
if (!p) return;
|
if (!p) return;
|
||||||
selectedId = id;
|
selectedId = id;
|
||||||
|
|
||||||
@ -259,7 +300,6 @@
|
|||||||
$("#d-date").textContent = p.date;
|
$("#d-date").textContent = p.date;
|
||||||
$("#d-status").innerHTML = statusPill(p.status);
|
$("#d-status").innerHTML = statusPill(p.status);
|
||||||
$("#d-confirm").hidden = p.status === "confirmed";
|
$("#d-confirm").hidden = p.status === "confirmed";
|
||||||
$("#d-confirm").setAttribute("data-tone", p.status === "confirmed" ? "ok" : "");
|
|
||||||
|
|
||||||
drawer.hidden = false;
|
drawer.hidden = false;
|
||||||
document.documentElement.classList.add("kt-ai-scroll-locked");
|
document.documentElement.classList.add("kt-ai-scroll-locked");
|
||||||
@ -277,23 +317,23 @@
|
|||||||
if (e.key === "Escape" && !drawer.hidden) closeDrawer();
|
if (e.key === "Escape" && !drawer.hidden) closeDrawer();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/* Статусы (подтверждение, удаление) — только для статической версии:
|
||||||
|
на серверной стороне запись не теряется, статус «ждёт» сохранится. */
|
||||||
$("#d-confirm").addEventListener("click", function () {
|
$("#d-confirm").addEventListener("click", function () {
|
||||||
var p = null;
|
var p = findRec(selectedId);
|
||||||
for (var i = 0; i < participants.length; i++) if (participants[i].id === selectedId) { p = participants[i]; break; }
|
|
||||||
if (!p) return;
|
if (!p) return;
|
||||||
p.status = "confirmed";
|
p.status = "confirmed";
|
||||||
save(participants);
|
if (usingLocal) lsSave(participants);
|
||||||
toast(p.lastname + " " + p.name + ": участие подтверждено", "ok");
|
toast(p.lastname + " " + p.name + ": участие подтверждено", "ok");
|
||||||
openDrawer(p.id);
|
openDrawer(p.id);
|
||||||
});
|
});
|
||||||
|
|
||||||
$("#d-unlink").addEventListener("click", function () {
|
$("#d-unlink").addEventListener("click", function () {
|
||||||
var p = null;
|
var p = findRec(selectedId);
|
||||||
for (var i = 0; i < participants.length; i++) if (participants[i].id === selectedId) { p = participants[i]; break; }
|
|
||||||
if (!p) return;
|
if (!p) return;
|
||||||
var idx = participants.indexOf(p);
|
var idx = participants.indexOf(p);
|
||||||
participants.splice(idx, 1);
|
if (idx !== -1) participants.splice(idx, 1);
|
||||||
save(participants);
|
if (usingLocal) lsSave(participants);
|
||||||
closeDrawer();
|
closeDrawer();
|
||||||
toast(p.lastname + " " + p.name + " убран из списка");
|
toast(p.lastname + " " + p.name + " убран из списка");
|
||||||
});
|
});
|
||||||
@ -317,7 +357,10 @@
|
|||||||
var adminLink = document.querySelector(".tb-admin-link-strong");
|
var adminLink = document.querySelector(".tb-admin-link-strong");
|
||||||
if (adminLink) adminLink.addEventListener("click", function () { showView("admin"); });
|
if (adminLink) adminLink.addEventListener("click", function () { showView("admin"); });
|
||||||
|
|
||||||
/* стартовый вид по location.hash */
|
/* ── Старт: загрузить список (с сервера; при недоступности — локальный) ── */
|
||||||
if (location.hash === "#admin" || location.hash === "#view-admin") showView("admin");
|
loadAll().then(function () {
|
||||||
else showView("participant");
|
renderAdmin();
|
||||||
|
if (location.hash === "#admin" || location.hash === "#view-admin") showView("admin");
|
||||||
|
else showView("participant");
|
||||||
|
});
|
||||||
})();
|
})();
|
||||||
|
|||||||
61
server.js
Normal file
61
server.js
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
// Реестр участников тимбилдинга: GET/POST /api/participants + статика проекта.
|
||||||
|
const http = require("http");
|
||||||
|
const fs = require("fs");
|
||||||
|
const path = require("path");
|
||||||
|
|
||||||
|
const PORT = process.env.PORT || 3000;
|
||||||
|
const DATA = path.join(__dirname, "data.json");
|
||||||
|
|
||||||
|
function readItems() {
|
||||||
|
try { return JSON.parse(fs.readFileSync(DATA, "utf8")); } catch (_) { return []; }
|
||||||
|
}
|
||||||
|
function writeItems(items) {
|
||||||
|
fs.writeFileSync(DATA, JSON.stringify(items, null, 2));
|
||||||
|
}
|
||||||
|
function body(req) {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
let s = "";
|
||||||
|
req.on("data", (c) => (s += c));
|
||||||
|
req.on("end", () => { try { resolve(JSON.parse(s || "{}")); } catch (_) { resolve({}); } });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const MIME = {
|
||||||
|
".html": "text/html", ".css": "text/css", ".js": "application/javascript",
|
||||||
|
".json": "application/json", ".svg": "image/svg+xml", ".png": "image/png"
|
||||||
|
};
|
||||||
|
|
||||||
|
http.createServer(async (req, res) => {
|
||||||
|
const url = req.url.split("?")[0];
|
||||||
|
|
||||||
|
if (req.method === "GET" && url === "/api/participants") {
|
||||||
|
res.writeHead(200, { "Content-Type": "application/json" });
|
||||||
|
return res.end(JSON.stringify(readItems()));
|
||||||
|
}
|
||||||
|
if (req.method === "POST" && url === "/api/participants") {
|
||||||
|
const it = await body(req);
|
||||||
|
const items = readItems();
|
||||||
|
const rec = {
|
||||||
|
id: Date.now(),
|
||||||
|
name: String(it.name || "").trim(),
|
||||||
|
lastname: String(it.lastname || "").trim(),
|
||||||
|
phone: String(it.phone || "").trim(),
|
||||||
|
status: "waiting",
|
||||||
|
date: it.date || new Date().toISOString().slice(0, 10)
|
||||||
|
};
|
||||||
|
items.push(rec);
|
||||||
|
writeItems(items);
|
||||||
|
res.writeHead(201, { "Content-Type": "application/json" });
|
||||||
|
return res.end(JSON.stringify({ ok: true, record: rec }));
|
||||||
|
}
|
||||||
|
|
||||||
|
let file = url === "/" ? "/index.html" : url;
|
||||||
|
const full = path.join(__dirname, decodeURIComponent(file));
|
||||||
|
if (full.startsWith(__dirname) && fs.existsSync(full) && fs.statSync(full).isFile()) {
|
||||||
|
const ext = path.extname(full);
|
||||||
|
res.writeHead(200, { "Content-Type": (MIME[ext] || "application/octet-stream") + "; charset=utf-8" });
|
||||||
|
return res.end(fs.readFileSync(full));
|
||||||
|
}
|
||||||
|
res.writeHead(404, { "Content-Type": "text/plain; charset=utf-8" });
|
||||||
|
res.end("Not found");
|
||||||
|
}).listen(PORT, () => console.log("Регистрация тимбилдинга на порту " + PORT));
|
||||||
Loading…
Reference in New Issue
Block a user