"use strict";
/* Журнал заявок на ремонт оборудования
Архетип: операционная очередь (queue). Данные — localStorage. */
var STORAGE_KEY = "remont-zayavok-v1";
var STATUSES = {
new: { label: "Новая", tone: "info" },
inwork: { label: "В работе", tone: "warn" },
waiting: { label: "Ожидает запчасти", tone: "muted" },
done: { label: "Выполнено", tone: "ok" },
cancelled: { label: "Отменена", tone: "risk" }
};
var PRIORITIES = {
low: { label: "Низкий" },
normal: { label: "Средний", tone: "muted" },
high: { label: "Высокий", tone: "risk" }
};
var PRIORITY_OPTIONS = [
{ key: "", label: "Все" },
{ key: "high", label: "Высокий" },
{ key: "normal", label: "Средний" },
{ key: "low", label: "Низкий" }
];
function todayIso(offsetDays) {
var d = new Date();
d.setDate(d.getDate() + (offsetDays || 0));
var m = String(d.getMonth() + 1);
var day = String(d.getDate());
if (m.length < 2) m = "0" + m;
if (day.length < 2) day = "0" + day;
return d.getFullYear() + "-" + m + "-" + day;
}
function seedRequests() {
return [
{ id: "ОБ-2026-0027", equipment: "Коммутатор C803", address: "Машинный зал, стеллаж 4, стойка 02", reporter: "Айбек С.", created: todayIso(-1), status: "new", priority: "high", reason: "Оборудование не отвечает, индикатор питания не горит" },
{ id: "ОБ-2026-0026", equipment: "UPS 3 кВА", address: "Машинный зал, стеллаж 3", reporter: "Айбек С.", created: todayIso(-1), status: "inwork", priority: "normal", reason: "Звуковое оповещение о разряде батареи не отключается" },
{ id: "ОБ-2026-0025", equipment: "Кабельная линейка UTP, 6-я линия", address: "Служебный модуль, линия 06", reporter: "Данияр К.", created: todayIso(-2), status: "waiting", priority: "normal", reason: "Постоянные обрывы линии, обрыв фиксируется каждые 10–15 минут" },
{ id: "ОБ-2026-0024", equipment: "Маршрутизатор", address: "Уровень 4, шкаф связи", reporter: "Сейсем М.", created: todayIso(-4), status: "inwork", priority: "high", reason: "Нагрузка на устройства уровня 4 нестабильна, скорость падает" },
{ id: "ОБ-2026-0023", equipment: "Коммутатор ЦОД", address: "Машинный зал, стеллаж 3, стойка 01", reporter: "Айбек С.", created: todayIso(-5), status: "done", priority: "normal", reason: "Появились ошибки в портах, пакетная потеря" },
{ id: "ОБ-2026-0022", equipment: "Серверный шкаф, дверь", address: "Служебный модуль, линия 14", reporter: "Данияр К.", created: todayIso(-6), status: "done", priority: "low", reason: "Блокиратор двери не фиксирует, закрывает с задержкой" },
{ id: "ОБ-2026-0021", equipment: "Телевизор, зал ожидания", address: "Этаж 2, зона ожидания", reporter: "Сейсем М.", created: todayIso(-7), status: "done", priority: "low", reason: "Нет изображения, звук работает" },
{ id: "ОБ-2026-0020", equipment: "Телевизор, зона ожидания", address: "Этаж 2, зона ожидания", reporter: "Эльмира Б.", created: todayIso(-9), status: "done", priority: "low", reason: "Изображение мерцает" },
{ id: "ОБ-2026-0019", equipment: "Устройство резервного питания", address: "Служебный модуль, линия 06", reporter: "Данияр К.", created: todayIso(-10), status: "waiting", priority: "normal", reason: "Батарея держит заряд 3 минуты, требует замены" },
{ id: "ОБ-2026-0018", equipment: "Кабельная линейка, 3-я линия", address: "Служебный модуль, линия 03", reporter: "Айбек С.", created: todayIso(-12), status: "cancelled", priority: "normal", reason: "Обрыв линии — по заявке, дубликат" }
];
}
function nextId() {
var n = (window.REMOUNT_NEXT || 0);
n += 1;
window.REMOUNT_NEXT = n;
var s = String(n);
while (s.length < 4) s = "0" + s;
return "ОБ-2026-" + s;
}
/* ---------- Состояние приложения ---------- */
var state = {
requests: [],
query: "",
statusFilter: "", // "" = все
priorityFilter: "", // "" = все
openId: null, // id записи в drawer
drawerMode: "" // "" | "view" | "new" | "edit"
};
function loadRequests() {
var raw = "";
try {
raw = localStorage.getItem(STORAGE_KEY);
} catch (e) {
return null;
}
if (!raw) return null;
var parsed = null;
try {
parsed = JSON.parse(raw);
} catch (e) {
return null;
}
if (!parsed || !parsed.requests) return null;
if (typeof parsed.nextSeq === "number") window.REMOUNT_NEXT = parsed.nextSeq;
return parsed.requests;
}
function saveRequests() {
var data = { requests: state.requests, nextSeq: window.REMOUNT_NEXT || 0 };
try {
localStorage.setItem(STORAGE_KEY, JSON.stringify(data));
} catch (e) {
/* localStorage может быть недоступен — просто не сохраняем */
}
}
function initRequests() {
var loaded = loadRequests();
if (loaded && loaded.length > 0) {
state.requests = loaded;
} else {
state.requests = seedRequests();
var maxSeq = 27;
for (var i = 0; i < state.requests.length; i++) {
var m = /(\d+)$/.exec(state.requests[i].id);
if (m) {
var num = parseInt(m[1], 10);
if (num > maxSeq) maxSeq = num;
}
}
window.REMOUNT_NEXT = maxSeq;
saveRequests();
}
}
/* ---------- Утилиты ---------- */
function $(id) {
return document.getElementById(id);
}
function esc(s) {
return String(s == null ? "" : s).replace(/[&<>"']/g, function (c) {
return { "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }[c];
});
}
function fmtDate(iso) {
if (!iso) return "—";
var p = iso.split("-");
if (p.length !== 3) return iso;
return p[2] + "." + p[1] + "." + p[0];
}
function findRequest(id) {
for (var i = 0; i < state.requests.length; i++) {
if (state.requests[i].id === id) return state.requests[i];
}
return null;
}
function statusPill(status) {
var st = STATUSES[status] || { label: status, tone: "muted" };
return '' + esc(st.label) + "";
}
function prioChip(p) {
var pr = PRIORITIES[p] || PRIORITIES.normal;
return '' + esc(pr.label) + "";
}
function filteredRequests(q) {
q = (q == null ? "" : q).trim().toLowerCase();
var out = [];
for (var i = 0; i < state.requests.length; i++) {
var r = state.requests[i];
if (state.statusFilter && r.status !== state.statusFilter) continue;
if (state.priorityFilter && r.priority !== state.priorityFilter) continue;
if (q) {
if (String(r.equipment).toLowerCase().indexOf(q) === -1) continue;
}
out.push(r);
}
return out;
}
function statusCounts(q) {
var c = { "": 0, new: 0, inwork: 0, waiting: 0, done: 0, cancelled: 0 };
for (var i = 0; i < state.requests.length; i++) {
var r = state.requests[i];
if (state.priorityFilter && r.priority !== state.priorityFilter) continue;
if (q) {
if (String(r.equipment).toLowerCase().indexOf(q) === -1) continue;
}
c[""] += 1;
c[r.status] = (c[r.status] || 0) + 1;
}
return c;
}
/* ---------- Отрисовка ---------- */
function renderKpis() {
var c = statusCounts(state.query);
var items = [
{ value: c[""], label: "Заявки всего" },
{ value: c.inwork + (c.waiting || 0), label: "В работе" },
{ value: c.new, label: "Новые" },
{ value: c.done, label: "Выполнено" }
];
var h = "";
for (var i = 0; i < items.length; i++) {
h += '
' + items[i].value +
'' + esc(items[i].label) + "
";
}
var strip = $("kpiStrip");
if (strip) strip.innerHTML = h;
var nav = $("navCount");
if (nav) nav.textContent = c[""];
}
var SEG_OPTIONS = [
{ key: "", label: "Все" },
{ key: "new", label: "Новые" },
{ key: "inwork", label: "В работе" },
{ key: "waiting", label: "Ожидание" },
{ key: "done", label: "Выполнено" }
];
function renderStatusSeg() {
var h = "";
for (var i = 0; i < SEG_OPTIONS.length; i++) {
var o = SEG_OPTIONS[i];
var active = state.statusFilter === o.key;
h += '";
}
var seg = $("statusSeg");
if (seg) seg.innerHTML = h;
}
function renderTable() {
var wrap = $("tableWrap");
if (!wrap) return;
var vis = filteredRequests(state.query);
if (vis.length === 0) {
var zeroData = state.requests.length === 0;
var icon = zeroData
? ''
: '';
wrap.innerHTML = '
" +
"" + (zeroData ? "Пока нет заявок" : "Ничего не найдено") + "" +
"" + (zeroData
? "Создайте первую заявку кнопкой «Новая заявка»"
: "Измените поиск или фильтр статуса") + "
";
return;
}
var h = '
' +
"
№
Оборудование / адрес
Статус
Приоритет
Создана
Кто
" +
"
";
for (var i = 0; i < vis.length; i++) {
var r = vis[i];
h += '
";
wrap.innerHTML = h;
var el = $("visCount");
if (el) el.textContent = vis.length + " из " + state.requests.length;
}
function renderPrioSelect() {
var sel = $("prioSelect");
if (!sel) return;
var h = "";
for (var i = 0; i < PRIORITY_OPTIONS.length; i++) {
var o = PRIORITY_OPTIONS[i];
var active = state.priorityFilter === o.key ? "1" : "0";
h += '";
}
sel.innerHTML = h;
}
function renderAll() {
renderKpis();
renderStatusSeg();
renderPrioSelect();
renderTable();
}
/* ---------- Drawer ---------- */
function openDrawer() {
var dr = $("drawer");
if (dr) {
dr.style.display = "block";
dr.onkeydown = function (e) {
if (e.key === "Escape") closeDrawer();
};
dr.focus();
}
}
function closeDrawer() {
var dr = $("drawer");
if (dr) dr.style.display = "none";
state.openId = null;
state.drawerMode = "";
}
function fieldRow(k, v, multiline) {
return '
' + esc(k) + '' + esc(v) + "
";
}
function renderView(id) {
var r = findRequest(id);
if (!r) return "";
var prio = PRIORITIES[r.priority] || { label: "—" };
var actions = "";
if (r.status === "new") {
actions += '';
}
if (r.status === "inwork") {
actions += '';
actions += '';
}
if (r.status === "waiting") {
actions += '';
}
if (r.status !== "done" && r.status !== "cancelled") {
actions += '';
}
actions += '';
return '
" : "");
}
function optionList(opts, selected) {
var h = "";
for (var i = 0; i < opts.length; i++) {
var o = opts[i];
h += "";
}
return h;
}
function renderNewForm() {
return '
' +
'
Новая заявка
' +
"
" +
'
' +
'";
}
function bindForm() {
var form = $("reqForm");
if (!form) return;
form.onsubmit = function (e) {
e.preventDefault();
var equip = ($("fEquip").value || "").trim();
var addr = ($("fAddr").value || "").trim();
var rep = ($("fReporter").value || "").trim();
var reason = ($("fReason").value || "").trim();
var prio = $("fPrio").value || "normal";
var bad = 0;
setErr("fEquip", equip);
setErr("fAddr", addr);
setErr("fReporter", rep);
setErr("fReason", reason);
function setErr(fid, v) {
if (!v) bad++;
var el = $(fid);
var f = el ? el.closest(".kt-ai-field") : null;
if (f) f.setAttribute("data-error", v ? "false" : "true");
}
if (bad > 0) return;
var r = {
id: nextId(),
equipment: equip,
address: addr,
reporter: rep,
created: todayIso(0),
status: "new",
priority: prio,
reason: reason
};
state.requests.unshift(r);
saveRequests();
renderAll();
S.openView(r.id);
toast("Заявка " + r.id + " создана", "ok");
};
}
function renderEditForm(id) {
var r = findRequest(id);
if (!r) return "";
var can = {};
can.new = r.status === "new" || r.status === "inwork" || r.status === "waiting";
var h = '
' +
'
' + esc(r.id) + "
" +
'
' + esc(r.equipment) + "
" +
"
" +
'
' +
'";
return h;
}
function bindEditForm(id) {
var form = $("reqForm");
if (!form) return;
form.onsubmit = function (e) {
e.preventDefault();
var r = findRequest(id);
if (!r) return;
var equip = ($("fEquip").value || "").trim();
var addr = ($("fAddr").value || "").trim();
var rep = ($("fReporter").value || "").trim();
var reason = ($("fReason").value || "").trim();
var bad = 0;
function setErr(fid, v) {
if (!v) bad++;
var el = $(fid);
var f = el ? el.closest(".kt-ai-field") : null;
if (f) f.setAttribute("data-error", v ? "false" : "true");
}
setErr("fEquip", equip);
setErr("fAddr", addr);
setErr("fReporter", rep);
setErr("fReason", reason);
if (bad > 0) return;
r.equipment = equip;
r.address = addr;
r.reporter = rep;
r.reason = reason;
r.priority = $("fPrio").value || r.priority;
var fStat = $("fStat");
if (fStat) r.status = fStat.value;
saveRequests();
renderAll();
S.openView(id);
toast("Заявка " + id + " обновлена", "ok");
};
}
/* ---------- Toast ---------- */
function toast(text, tone) {
var stack = $("toasts");
if (!stack) return;
var el = document.createElement("div");
el.className = "kt-ai-toast";
el.setAttribute("data-tone", tone || "");
el.textContent = text;
stack.appendChild(el);
setTimeout(function () {
if (el.parentNode) el.parentNode.removeChild(el);
}, 3600);
}
/* ---------- Публичный API ---------- */
var S = {};
S.goList = function () {
renderAll();
};
S.openView = function (id) {
state.openId = id;
state.drawerMode = "view";
var dr = $("drawer");
if (dr) {
dr.innerHTML = renderView(id);
openDrawer();
}
};
S.openNew = function () {
state.openId = null;
state.drawerMode = "new";
var dr = $("drawer");
if (dr) {
dr.innerHTML = renderNewForm();
openDrawer();
bindForm();
}
};
S.openEdit = function (id) {
state.openId = id;
state.drawerMode = "edit";
var dr = $("drawer");
if (dr) {
dr.innerHTML = renderEditForm(id);
openDrawer();
bindEditForm(id);
}
};
S.closeDrawer = closeDrawer;
S.setStatus = function (id, st) {
var r = findRequest(id);
if (!r) return;
if (!STATUSES[st]) return;
r.status = st;
saveRequests();
renderAll();
S.openView(id);
toast("Статус: " + (STATUSES[st] ? STATUSES[st].label : st), "ok");
};
S.cancel = function (id) {
var r = findRequest(id);
if (!r) return;
r.status = "cancelled";
saveRequests();
renderAll();
S.openView(id);
toast("Заявка " + id + " отменена", "warn");
};
S.setSeg = function (key) {
state.statusFilter = key;
renderKpis();
renderStatusSeg();
renderTable();
};
S.onSearch = function (v) {
state.query = v;
renderAll();
};
S.setPrio = function (v) {
state.priorityFilter = v;
renderAll();
};
S.toggleSidebar = function () {
var sb = $("sidebar");
if (sb) sb.setAttribute("data-open", sb.getAttribute("data-open") === "true" ? "false" : "true");
};
/* ---------- Старт ---------- */
document.addEventListener("DOMContentLoaded", function () {
initRequests();
renderAll();
});