(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 { "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }[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 += '";
}
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(
'
' +
'| ' + esc(r.id) + " | " +
"" + esc(r.equip) + " | " +
"" + esc(r.loc) + " | " +
'' + fmtDate(r.created) + " | " +
'' + s.label + " | " +
"
"
);
}
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-export").addEventListener("click", function () {
var rows = [];
for (var i = 0; i < requests.length; i++) {
if (!rowMatches(requests[i])) continue;
var r = requests[i];
rows.push({
"Заявка": r.id,
"Оборудование": r.equip,
"Расположение": r.loc,
"Описание": r.desc,
"Создана": fmtDate(r.created),
"Статус": STATUS[r.status] ? STATUS[r.status].label : r.status
});
}
if (!rows.length) {
toast("Нечего выгружать — по фильтрам нет записей");
return;
}
var wb = XLSX.utils.json_to_sheet(rows);
var widths = ["Заявка", "Оборудование", "Расположение", "Описание"].map(function (h) {
var w = h.length;
rows.forEach(function (row) {
var v = String(row[h] || "");
if (v.length > w) w = v.length;
});
return { wch: Math.min(w + 2, 60) };
});
widths.push({ wch: 12 }, { wch: 20 });
wb["!cols"] = widths;
var sheetName = state.status === "all" ? "Заявки" : STATUS[state.status].label;
var wbout = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(wbout, wb, sheetName.slice(0, 31));
var d = new Date();
var name = "Journal_" + String(d.getDate()).padStart(2, "0") + String(d.getMonth() + 1).padStart(2, "0") + d.getFullYear() + ".xlsx";
XLSX.writeFile(wbout, name);
toast("Выгружено записей: " + rows.length);
});
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();
})();