uchet-zayavok-na-propusk/script.js

270 lines
11 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.

/* Учёт заявок на пропуск — демо: хранение в localStorage.
Демо-набор отдельно (key "kt_pass_demo_seed") от добавленных пользователем записей
(key "kt_pass_requests") — показываем как есть, без смешивания и подмены. */
var STATUS = {
new: { label: "Новая", pill: "info" },
processing: { label: "В обработке", pill: "warn" },
approved: { label: "Одобрена", pill: "ok" },
rejected: { label: "Отклонена", pill: "risk" }
};
var STATUS_ORDER = ["new", "processing", "approved", "rejected"];
var DEMO = [
{ id: 1042, name: "Ахметов Ерлан Сергеевич", purpose: "Въезд на технологическую территорию ЦОД Алматы", date: "2026-09-18" },
{ id: 1041, name: "Бекеева Алия Нурлановна", purpose: "Обучение: работа с ОДЗ, учебный класс B", date: "2026-09-17" },
{ id: 1040, name: "Сериков Данияр Олегович", purpose: "Работа подрядчика, зона монтажа №3", date: "2026-09-16" },
{ id: 1039, name: "Махамбетова Гульнара", purpose: "Ревизия склада СИЗ", date: "2026-09-15" },
{ id: 1038, name: "Токтаров Нурлан", purpose: "Въезд на территорию офиса №2", date: "2026-09-15" },
{ id: 1037, name: "Абдрахманова Семина", purpose: "Курсы ИБ, 2 день", date: "2026-09-12" },
{ id: 1036, name: "Куанышбаев Асхат", purpose: "Сопровождение инспекции", date: "2026-09-12" },
{ id: 1035, name: "Омарова Жания", purpose: "Подача документов в отдел кадров", date: "2026-09-10" },
{ id: 1034, name: "Нурланов Серик", purpose: "Доставка оборудования, грузовой въезд", date: "2026-09-05" }
];
var DEMO_STATUS = { 1042: "new", 1041: "new", 1040: "processing", 1039: "processing", 1038: "approved", 1037: "approved", 1036: "rejected", 1035: "rejected", 1034: "approved" };
/** Демо-набор: статусы приложены и зафиксированы, перезаписывать нельзя. */
function demoItems() {
return DEMO.map(function (d) {
return { id: d.id, name: d.name, purpose: d.purpose, date: d.date, status: DEMO_STATUS[d.id], demo: true };
});
}
/** Записи, добавленные пользователем в этой сессии браузера. */
function loadUser() {
try {
var raw = localStorage.getItem("kt_pass_requests");
var arr = raw ? JSON.parse(raw) : [];
return Array.isArray(arr) ? arr.filter(function (r) { return isReq(r); }) : [];
} catch (e) {
return [];
}
}
function saveUser(arr) {
try {
localStorage.setItem("kt_pass_requests", JSON.stringify(arr));
} catch (e) {
/* localStorage недоступен — работаем только в памяти */
}
}
function isReq(r) {
return r && r.name && r.purpose && r.date && STATUS[r.status] != null && r.id > 0;
}
function nextId(user) {
var m = 0;
user.forEach(function (r) { if (r.id > m) m = r.id; });
return 10000 + user.length + 1 + (m >= 10000 ? m - 9999 : 0);
}
var state = { filter: "all", user: loadUser() };
var $form = document.getElementById("request-form");
var $kpiStrip = document.getElementById("kpi-strip");
var $tableWrap = document.getElementById("table-wrap");
var $tbody = document.getElementById("list-body");
var $empty = document.getElementById("list-empty");
var $emptyHint = document.getElementById("list-empty-hint");
var $total = document.getElementById("list-total");
var $toastStack = document.getElementById("toast-stack");
function esc(s) {
return String(s).replace(/[&<>"']/g, function (c) {
return { "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" }[c];
});
}
function pad(n) { return String(n).length < 2 ? "0" + n : String(n); }
function fmtDate(iso) {
if (!iso) return "—";
var parts = iso.split("-");
if (parts.length !== 3) return iso;
return parts[2] + "." + parts[1] + "." + parts[0];
}
function statusInfo(s) {
return STATUS[s] || STATUS.new;
}
function allItems() {
var merged = state.user.map(function (r) {
return { id: r.id, name: r.name, purpose: r.purpose, date: r.date, status: r.status, demo: false };
}).concat(demoItems());
merged.sort(function (a, b) { return b.id - a.id; });
return merged;
}
function visible() {
var all = allItems();
if (state.filter === "all") return all;
return all.filter(function (r) { return r.status === state.filter; });
}
function renderKPI() {
var all = allItems();
var counts = { all: all.length };
STATUS_ORDER.forEach(function (k) { counts[k] = 0; });
all.forEach(function (r) { counts[r.status] = (counts[r.status] || 0) + 1; });
function kpi(id, value, label) {
var active = state.filter === id ? "true" : "false";
return '<div class="kt-ai-kpi" data-tab="true" data-active="' + active + '" data-status="' + id + '">' +
'<span class="value">' + value + "</span>" +
'<span class="label">' + label + "</span>" +
"</div>";
}
var KPIS = [
["all", "Всего"],
["new", "Новые"],
["processing", "В обработке"],
["approved", "Одобрено"],
["rejected", "Отклонено"]
];
var parts = KPIS.map(function (k) { return kpi(k[0], counts[k[0]], k[1]); });
$kpiStrip.innerHTML = parts.join("");
}
function plural(n, one, few, many) {
var n10 = n % 10, n100 = n % 100;
if (n10 === 1 && n100 !== 11) return one;
if (n10 >= 2 && n10 <= 4 && (n100 < 12 || n100 > 14)) return few;
return many;
}
function renderList() {
var list = visible();
var total = allItems().length;
$total.textContent = total + " " + plural(total, "заявка", "заявки", "заявок");
if (list.length === 0) {
$tbody.innerHTML = "";
$tableWrap.hidden = true;
$empty.hidden = false;
$emptyHint.textContent = state.filter === "all"
? "Заявок пока нет — создайте первую в форме выше."
: "Под выбранный статус заявки не нашлись.";
return;
}
$tableWrap.hidden = false;
$empty.hidden = true;
var rows = list.map(function (r) {
var st = statusInfo(r.status);
var actions = "";
if (!r.demo) {
if (r.status === "new") {
actions += btnAction(r.id, "processing", "check", "В обработку");
} else if (r.status === "approved") {
actions += btnAction(r.id, "new", "refresh", "Сменить статус");
}
actions += btnAction(r.id, "delete", "trash", "Удалить", "ghost");
}
return "<tr>" +
'<td class="num">' + r.id + "</td>" +
"<td>" + esc(r.name) + "</td>" +
'<td class="kt-ai-truncate" title="' + esc(r.purpose) + '">' + esc(r.purpose) + "</td>" +
'<td class="num">' + fmtDate(r.date) + "</td>" +
'<td class="t-status"><span class="kt-ai-chip" data-tone="' + st.pill + '">' + st.label + "</span></td>" +
'<td class="t-col-actions"><span class="t-actions">' + (actions || "—") + "</span></td>" +
"</tr>";
});
$tbody.innerHTML = rows.join("");
}
function btnAction(id, target, icon, label, variant) {
return '<button class="kt-ai-btn" type="button" data-action="' + target + '" data-id="' + id + '"' +
(variant ? ' data-variant="' + variant + '"' : "") + ' title="' + label + '">' +
'<svg class="kt-icon kt-icon-sm" aria-hidden="true"><use href="#' + icon + '"></use></svg>' +
(label !== "Удалить" ? "<span>" + label + "</span>" : "") +
"</button>";
}
function render() {
renderKPI();
renderList();
}
function toast(text, tone) {
var el = document.createElement("div");
el.className = "kt-ai-toast";
if (tone) el.setAttribute("data-tone", tone);
el.textContent = text;
$toastStack.appendChild(el);
setTimeout(function () { el.remove(); }, 4000);
}
function setInvalid(input, bad) {
input.setAttribute("data-invalid", bad ? "true" : "false");
var field = input.closest(".kt-ai-field");
if (field) {
var err = field.querySelector(".error");
if (err) err.hidden = !bad;
field.setAttribute("data-invalid", bad ? "true" : "false");
}
}
function setFilter(s) {
state.filter = s;
document.querySelectorAll(".kt-ai-filter-chip[data-status]").forEach(function (c) {
c.setAttribute("data-active", c.getAttribute("data-status") === s ? "true" : "false");
});
renderKPI();
renderList();
}
document.addEventListener("click", function (e) {
var chip = e.target.closest(".kt-ai-filter-chip[data-status]");
if (chip) { setFilter(chip.getAttribute("data-status")); return; }
var kpi = e.target.closest(".kt-ai-kpi[data-status]");
if (kpi) { setFilter(kpi.getAttribute("data-status")); return; }
var act = e.target.closest(".kt-ai-btn[data-action]");
if (act) {
var id = Number(act.getAttribute("data-id"));
var target = act.getAttribute("data-action");
state.user = state.user.filter(function (r) {
if (r.id !== id) return true;
if (target === "delete") { toast("Заявка №" + id + " удалена"); return false; }
r.status = target;
toast(
target === "processing" ? "Заявка №" + id + " перенесена в обработку" :
target === "approved" ? "Заявка №" + id + " одобрена" :
"Статус заявки №" + id + ": " + statusInfo(target).label
);
return true;
});
saveUser(state.user);
render();
}
});
document.getElementById("btn-reset").addEventListener("click", function () { setFilter("all"); });
["f-name", "f-purpose", "f-date"].forEach(function (i) {
document.getElementById(i).addEventListener("input", function () { setInvalid(this, false); });
});
$form.addEventListener("submit", function (e) {
e.preventDefault();
var f = $form;
var name = f.elements["f-name"].value.trim();
var purpose = f.elements["f-purpose"].value.trim();
var date = f.elements["f-date"].value;
var ok = true;
if (!name) { setInvalid(f.elements["f-name"], true); ok = false; }
if (!purpose) { setInvalid(f.elements["f-purpose"], true); ok = false; }
if (!date) { setInvalid(f.elements["f-date"], true); ok = false; }
if (!ok) { toast("Проверьте поля формы", "warn"); return; }
var item = { id: nextId(state.user), name: name, purpose: purpose, date: date, status: "new" };
state.user.push(item);
saveUser(state.user);
f.reset();
render();
toast("Заявка №" + item.id + " создана, статус: Новая");
$form.scrollIntoView({ behavior: "smooth", block: "nearest" });
});
(function init() {
var today = new Date();
document.getElementById("f-date").valueAsDate = today;
render();
})();