zhurnal-zayavok-na-remont-ob-4/script.js

282 lines
12 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 () {
"use strict";
var LS_KEY = "zt-remont-ob-v1";
var STATUS = {
new: { label: "Новая", pill: "info" },
work: { label: "В работе", pill: "warn" },
wait: { label: "Ожидает запчасти", pill: "muted" },
done: { label: "Выполнено", pill: "ok" },
cancel: { label: "Отменена", pill: "muted" }
};
var SEED = [
{ id: "Z-117", equip: "Маршрутизатор Cisco ISR 4451", loc: "ЦОД Астана, стойка 12", desc: "Утечка трафика на одном из модулей, требуется замена SFP.", created: "2026-09-21T09:14:00", updated: "2026-09-21T09:14:00", status: "done" },
{ id: "Z-116", equip: "Коммутатор HPE Aruba 2930F", loc: "Филиал Алматы, серверная", desc: "Не определяется один порт агрегации, перезагрузка не помогла.", created: "2026-09-19T15:02:00", updated: "2026-09-19T15:02:00", status: "work" },
{ id: "Z-115", equip: "ИБ-межсетевой экран FortiGate 200F", loc: "ЦОД Астана, зона DMZ", desc: "Падает производительность при сканировании, нужен апгрейд подписок.", created: "2026-09-18T11:26:00", updated: "2026-09-18T11:26:00", status: "wait" },
{ id: "Z-114", equip: "UPS APC Smart-UPS SRT 3000", loc: "Филиал Караганда", desc: "Сигнализация замены батарей, резерв держит меньше 5 минут.", created: "2026-09-17T13:40:00", updated: "2026-09-17T13:40:00", status: "new" },
{ id: "Z-113", equip: "Сервер Dell PowerEdge R750", loc: "ЦОД Астана, стойка 07", desc: "Неисправность контроллера RAID, деградирует массив.", created: "2026-09-15T10:05:00", updated: "2026-09-16T08:31:00", status: "work" },
{ id: "Z-112", equip: "Кабельная муфта ВОЛС 12c", loc: "Магистраль Астана — Костанай", desc: "Повышенная оптическая потеря после работ подрядчика, требуется перепайка.", created: "2026-09-12T16:48:00", updated: "2026-09-14T10:12:00", status: "done" },
{ id: "Z-111", equip: "Терминал доступа Juniper EX 3400", loc: "Филиал Шымкент", desc: "Перегрев, выключается через 20 минут под нагрузкой.", created: "2026-09-10T09:30:00", updated: "2026-09-10T09:30:00", status: "new" },
{ id: "Z-110", equip: "Коммутатор Cisco Catalyst 9300", loc: "ЦОД Астана, стойка 21", desc: "Зависает процессор управления, требуется замена PSU.", created: "2026-09-08T14:22:00", updated: "2026-09-08T14:22:00", status: "cancel" },
{ id: "Z-109", equip: "Антенна БС 19", loc: "Филиал Атырау", desc: "Слабый сигнал на секторах 2 и 3, требуется настройка азимута.", created: "2026-09-05T12:00:00", updated: "2026-09-07T09:45:00", status: "done" },
{ id: "Z-108", equip: "Климатическая шкафа-система", loc: "ЦОД Астана, зал B", desc: "Не держит температуру выше 26°C в жаркий период.", created: "2026-09-03T08:55:00", updated: "2026-09-04T17:20:00", status: "work" }
];
var state = { status: "all", query: "", editMode: "new", editId: null };
var requests = load();
function load() {
try {
var raw = localStorage.getItem(LS_KEY);
if (raw) {
var arr = JSON.parse(raw);
if (arr && arr.length) return arr;
}
} catch (e) { /* повреждённые данные — пересеваем */ }
localStorage.setItem(LS_KEY, JSON.stringify(SEED));
return SEED.slice();
}
function save() {
localStorage.setItem(LS_KEY, JSON.stringify(requests));
}
function nextId() {
var max = 100;
for (var i = 0; i < requests.length; i++) {
var m = /Z-(\d+)/.exec(requests[i].id);
if (m && parseInt(m[1], 10) > max) max = parseInt(m[1], 10);
}
return "Z-" + (max + 1);
}
function fmtDate(iso) {
var d = new Date(iso);
if (isNaN(d.getTime())) return "—";
var dd = String(d.getDate()).padStart(2, "0");
var mm = String(d.getMonth() + 1).padStart(2, "0");
return dd + "." + mm + "." + d.getFullYear();
}
function esc(s) {
return String(s).replace(/[&<>"']/g, function (c) {
return { "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" }[c];
});
}
function rowMatches(r, st) {
if (state.status !== "all" && r.status !== state.status) return false;
if (state.query) {
var q = state.query.toLowerCase();
var hay = (r.id + " " + r.equip + " " + r.loc + " " + r.desc).toLowerCase();
if (hay.indexOf(q) === -1) return false;
}
return true;
}
function renderFilter() {
var bar = document.getElementById("filterbar");
var items = [{ key: "all", label: "Все" }];
for (var key in STATUS) items.push({ key: key, label: STATUS[key].label });
var html = "";
for (var i = 0; i < items.length; i++) {
html += '<button type="button" class="kt-ai-filter-chip" data-filter="' + items[i].key + '"' +
(state.status === items[i].key ? ' data-active="true"' : "") + ">" + items[i].label + "</button>";
}
bar.innerHTML = html;
}
function renderTable() {
var rows = document.getElementById("rows");
var empty = document.getElementById("empty");
var count = document.getElementById("row-count");
var out = [];
var shown = 0;
for (var i = 0; i < requests.length; i++) {
var r = requests[i];
if (!rowMatches(r)) continue;
shown++;
var s = STATUS[r.status] || STATUS.new;
out.push(
'<tr data-row="' + esc(r.id) + '" tabindex="0" style="cursor:pointer">' +
'<td style="font-weight:500">' + esc(r.id) + "</td>" +
"<td>" + esc(r.equip) + "</td>" +
"<td style=\"color:var(--kt-ai-fg-muted)\">" + esc(r.loc) + "</td>" +
'<td style="color:var(--kt-ai-fg-muted);font-variant-numeric:tabular-nums">' + fmtDate(r.created) + "</td>" +
'<td><span class="kt-ai-status-pill" data-status="' + s.pill + '">' + s.label + "</span></td>" +
"</tr>"
);
}
rows.innerHTML = out.join("");
var total = requests.length;
count.textContent = state.status === "all" && !state.query
? total + " заявок"
: shown + " из " + total;
var visible = total > 0;
rows.parentElement.querySelector("table").style.display = visible ? "" : "none";
empty.hidden = visible;
}
function renderKpi() {
var all = requests.length, work = 0, wait = 0, done = 0;
for (var i = 0; i < requests.length; i++) {
var st = requests[i].status;
if (st === "work") work++;
if (st === "wait") wait++;
if (st === "done") done++;
}
document.getElementById("kpi-all").textContent = all;
document.getElementById("kpi-work").textContent = work;
document.getElementById("kpi-wait").textContent = wait;
document.getElementById("kpi-done").textContent = done;
}
function render() {
renderKpi();
renderFilter();
renderTable();
}
/* ---------- drawer ---------- */
function openDrawer(mode, id) {
state.editMode = mode;
state.editId = id || null;
var drawer = document.getElementById("drawer");
var form = document.getElementById("form");
var detail = document.getElementById("detail");
var title = document.getElementById("drawer-title");
var sub = document.getElementById("drawer-sub");
drawer.hidden = false;
document.body.style.overflow = "hidden";
form.hidden = false;
detail.hidden = true;
title.textContent = "Новая заявка";
sub.textContent = "Заполните поля, чтобы добавить запись в журнал";
document.getElementById("f-equip").value = "";
document.getElementById("f-loc").value = "";
document.getElementById("f-desc").value = "";
document.getElementById("f-status").value = "new";
document.getElementById("f-equip").focus();
}
function openDetail(id) {
var r = null;
for (var i = 0; i < requests.length; i++) {
if (requests[i].id === id) { r = requests[i]; break; }
}
if (!r) return;
state.editMode = "detail";
state.editId = id;
var drawer = document.getElementById("drawer");
var form = document.getElementById("form");
var detail = document.getElementById("detail");
drawer.hidden = false;
document.body.style.overflow = "hidden";
form.hidden = true;
detail.hidden = false;
document.getElementById("drawer-title").textContent = r.id + " · " + r.equip;
document.getElementById("drawer-sub").textContent =
(STATUS[r.status] ? STATUS[r.status].label : r.status) + " · создана " + fmtDate(r.created);
document.getElementById("d-desc").textContent = r.desc;
document.getElementById("d-equip").textContent = r.equip;
document.getElementById("d-loc").textContent = r.loc;
document.getElementById("d-date").textContent = fmtDate(r.created);
document.getElementById("d-upd").textContent = fmtDate(r.updated);
document.getElementById("d-status").value = r.status;
document.getElementById("drawer-close").focus();
}
function closeDrawer() {
document.getElementById("drawer").hidden = true;
document.body.style.overflow = "";
}
function toast(text) {
var stack = document.getElementById("toasts");
var el = document.createElement("div");
el.className = "kt-ai-toast";
el.textContent = text;
stack.appendChild(el);
setTimeout(function () {
if (el.parentElement) el.parentElement.removeChild(el);
}, 2600);
}
/* ---------- события ---------- */
document.getElementById("filterbar").addEventListener("click", function (e) {
var chip = e.target.closest("[data-filter]");
if (!chip) return;
state.status = chip.getAttribute("data-filter");
render();
});
document.getElementById("search").addEventListener("input", function () {
state.query = this.value.trim();
renderTable();
});
document.getElementById("btn-new").addEventListener("click", function () {
openDrawer("new");
});
document.getElementById("rows").addEventListener("click", function (e) {
var tr = e.target.closest("tr[data-row]");
if (!tr) return;
openDetail(tr.getAttribute("data-row"));
});
document.getElementById("rows").addEventListener("keydown", function (e) {
if (e.key !== "Enter") return;
var tr = e.target.closest("tr[data-row]");
if (tr) openDetail(tr.getAttribute("data-row"));
});
document.getElementById("drawer-close").addEventListener("click", closeDrawer);
document.getElementById("btn-cancel").addEventListener("click", closeDrawer);
document.addEventListener("keydown", function (e) {
if (e.key === "Escape" && !document.getElementById("drawer").hidden) closeDrawer();
});
document.getElementById("form").addEventListener("submit", function (e) {
e.preventDefault();
var rec = {
id: nextId(),
equip: document.getElementById("f-equip").value.trim(),
loc: document.getElementById("f-loc").value.trim(),
desc: document.getElementById("f-desc").value.trim(),
status: document.getElementById("f-status").value,
created: new Date().toISOString(),
updated: new Date().toISOString()
};
if (!rec.equip || !rec.loc || !rec.desc) return;
requests.unshift(rec);
save();
state.status = "all";
state.query = "";
document.getElementById("search").value = "";
closeDrawer();
render();
toast("Заявка " + rec.id + " добавлена");
});
document.getElementById("btn-apply").addEventListener("click", function () {
if (!state.editId) return;
for (var i = 0; i < requests.length; i++) {
if (requests[i].id === state.editId) {
requests[i].status = document.getElementById("d-status").value;
requests[i].updated = new Date().toISOString();
break;
}
}
save();
closeDrawer();
render();
toast("Статус обновлён");
});
render();
})();