/* ===== Сверка документов при назначении руководителей =====
Статичный прототип: данные демонстрационные, состояние в localStorage. */
"use strict";
var LS = "svd-nazn-v1";
/* ---------- Демо-данные (нейтральные примеры) ---------- */
var VIOL = [
{ id: 1, type: "norm", doc: "Анкета кандидата", item: "Раздел 2 «Опыт работы»", desc: "Общий стаж — 7 лет 4 месяца, требуется минимум 8 лет", norm: "Закон об АО, ст. 45", normId: 1 },
{ id: 2, type: "norm", doc: "Должностная инструкция", item: "Пункт 4.1", desc: "Требование «высшее образование» не соответствует профилю позиции", norm: "Положение о кадровом делопроизводстве, п. 12.4", normId: 2 },
{ id: 3, type: "norm", doc: "Анкета кандидата", item: "Раздел 3 «Образование»", desc: "Удостоверение об образовании не приложено", norm: "Положение о кадровом делопроизводстве, п. 7.2", normId: 3 },
{ id: 4, type: "norm", doc: "Пояснительная записка", item: "Пункт 3", desc: "Период работы по предыдущему месту не подтверждён справкой", norm: "ТК РК, ст. 68", normId: 4 },
{ id: 5, type: "contra", doc: "Пояснительная записка / Анкета", item: "Пункт 2 / Раздел 3", desc: "Указаны разные даты окончания предыдущей должности", norm: "—", normId: 5 },
{ id: 6, type: "norm", doc: "Анкета кандидата", item: "Раздел 1 «Личные сведения»", desc: "Отсутствие сведений о судимости (заполнено «нет данных»)", norm: "ТК РК, ст. 68, п. 2", normId: 6 },
{ id: 7, type: "norm", doc: "Должностная инструкция", item: "Приложение 1", desc: "Квалификационный разряд ниже требований к должности", norm: "Должностной регламент фонда, п. 5.1", normId: 7 },
{ id: 8, type: "norm", doc: "Пояснительная записка", item: "Пункт 4", desc: "Указанный руководитель не подтверждён штатным расписанием", norm: "Должностной регламент фонда, п. 9.3", normId: 8 },
{ id: 9, type: "contra", doc: "Анкета кандидата / ПЗ", item: "Раздел 2 / Пункт 3", desc: "Различаются периоды работы в одной организации", norm: "—", normId: 9 },
{ id: 10, type: "norm", doc: "Анкета кандидата", item: "Раздел 2 «Опыт работы»", desc: "Не подтверждён стаж работы по смежной специальности", norm: "Закон об АО, ст. 45", normId: 10 },
{ id: 11, type: "contra", doc: "Пояснительная записка / ДИ", item: "Пункт 2 / Пункт 4.2", desc: "Совокупная нагрузка по должности указана расхождением", norm: "—", normId: 11 },
{ id: 12, type: "norm", doc: "Пояснительная записка", item: "Пункт 5", desc: "Отсутствует ссылка на приказ о назначении", norm: "Должностной регламент фонда, п. 3.7", normId: 12 }
];
var NORMATIVES = [
{ id: 1, r: "Закон об АО", p: "ст. 45" },
{ id: 2, r: "Положение о кадровом делопроизводстве", p: "п. 12.4" },
{ id: 3, r: "Положение о кадровом делопроизводстве", p: "п. 7.2" },
{ id: 4, r: "ТК РК", p: "ст. 68" },
{ id: 5, r: "Закон об АО", p: "ст. 46" },
{ id: 6, r: "Должностной регламент фонда", p: "п. 5.1" },
{ id: 7, r: "Должностной регламент фонда", p: "п. 9.3" },
{ id: 8, r: "Должностной регламент фонда", p: "п. 3.7" }
];
var FILES_INIT = [
{ name: "Должностная инструкция.pdf", type: "ДИ", status: "done" },
{ name: "Пояснительная записка.docx", type: "ПЗ", status: "done" },
{ name: "Анкета кандидата.pdf", type: "Анкета", status: "pending"}
];
var CANDS = [
{ id: 1, name: "Ахметов Ерлан Сергеевич", post: "Руководитель отдела", stage: "approved", sent: null },
{ id: 2, name: "Бекмембетова Динара Талгат", post: "Начальник управления", stage: "approved", sent: null },
{ id: 3, name: "Сапаров Ибрагим Нурлан", post: "Заместитель директора", stage: "sent", sent: "21.09.2026, 13:05" },
{ id: 4, name: "Мырзагалиев Айдос Керек", post: "Руководитель направления", stage: "draft", sent: null },
{ id: 5, name: "Оралова Гульнара Бакыт", post: "Начальник сектора", stage: "ready", sent: null },
{ id: 6, name: "Токтаров Марат Алиши", post: "Директор подразделения", stage: "uploaded", sent: null },
{ id: 7, name: "Утебаева Алия Дильдари", post: "Заместитель руководителя", stage: "uploaded", sent: null },
{ id: 8, name: "Жумабаев Нурлан Серик", post: "Руководитель службы", stage: "uploaded", sent: null }
];
var STEPS = [
{ label: "Загрузка пакета" },
{ label: "Анализ ИИ" },
{ label: "Заключение" },
{ label: "Согласование" }
];
var SCREENS = {
load: { title: "Загрузка пакета документов", sub: "Пакет кандидата · экспертиза заключения" },
analysis: { title: "Анализ ИИ", sub: "Сверка с нормативами · поиск противоречий" },
concl: { title: "Заключение", sub: "Проект заключения эксперта" },
approve: { title: "Согласование", sub: "Утверждение заключения руководителем" },
about: { title: "О решении", sub: "Паспорт ИИ-решения" }
};
var STAGE_CHIP = {
uploaded: "Загружен",
ready: "Готов к анализу",
analyzed: "Анализ завершён",
draft: "Черновик",
sent: "На согласовании",
approved: "Согласовано"
};
/* ---------- Состояние ---------- */
var S = null;
function defaultState() {
var checks = {};
var i;
for (i = 0; i < VIOL.length; i++) {
checks[VIOL[i].id] =
(i < 5) ? "confirmed" :
(i === 5) ? "rejected" : "pending";
}
return {
screen: "load",
role: "expert",
candId: 5,
candidate: { name: "Оралова Гульнара Бакыт", post: "Начальник сектора", submitted: "19.09.2026" },
files: FILES_INIT.map(function (f) { return Object.assign({}, f); }),
analysisStatus: "done",
checks: checks,
text: "ЗАКЛЮЧЕНИЕ о соответствии кандидата требованиям должности\n\n" +
"Кандидат: Оралова Гульнара Бакыт\n" +
"Должность: Начальник сектора\n" +
"Дата пакета: 19.09.2026\n\n" +
"Проверены документы: должностная инструкция, пояснительная записка, анкета кандидата.\n" +
"Сверка выполнена с квалификационными требованиями и внутренние нормативы фонда.\n\n" +
"По результатам сверки зафиксированы несоответствия и расхождения, подробно изложенные ниже. При назначении кандидата на должность рекомендуется устранить замечания в порядке, установленном правилами фонда.\n\nСоставил: эксперт кадровой службы",
sentAt: null,
approveComment: null
};
}
function save() {
try { localStorage.setItem(LS, JSON.stringify(S)); } catch (e) { /* приватный режим */ }
}
function load() {
try {
var raw = localStorage.getItem(LS);
if (!raw) return null;
var st = JSON.parse(raw);
if (!st || !st.screen || !st.checks) return null;
return st;
} catch (e) { return null; }
}
/* ---------- Утилиты ---------- */
var $ = function (id) { return document.getElementById(id); };
function esc(s) {
return String(s == null ? "" : s).replace(/[&<>"']/g, function (c) {
return { "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }[c];
});
}
function cand() { return CANDS.find(function (c) { return c.id === S.candId; }); }
function initials(s) {
var p = String(s).split(" ");
return (p[0] ? p[0].charAt(0) : "") + (p[1] ? p[1].charAt(0) : "");
}
function icon(name) {
return ' ';
}
function kpi(value, label, hint) {
return '
' + esc(value) + ' ' + esc(label) + " " +
(hint ? '' + esc(hint) + " " : "") + "
";
}
function pill(label, tone) {
return '' + esc(label) + " ";
}
function toast(msg, tone) {
var stack = $("toasts");
if (!stack) return;
var el = document.createElement("div");
el.className = "kt-ai-toast";
if (tone) el.setAttribute("data-tone", tone);
el.textContent = msg;
stack.appendChild(el);
setTimeout(function () {
el.style.transition = "opacity .3s";
el.style.opacity = "0";
setTimeout(function () { if (el.parentNode) el.parentNode.removeChild(el); }, 320);
}, 2600);
}
function openDrawer(html) {
var d = $("drawer");
d.innerHTML = html;
d.style.display = "block";
}
function closeDrawer() {
var d = $("drawer");
d.style.display = "none";
d.innerHTML = "";
}
/* ---------- Навигация и оболочка ---------- */
function curStage() {
var st = cand().stage;
if (S.screen === "load") return 1;
if (S.screen === "analysis") return 2;
if (S.screen === "concl") return 3;
return 4;
}
function navHtml() {
var items = [
{ id: "load", label: "Загрузка пакета", ic: "upload" },
{ id: "analysis", label: "Анализ ИИ", ic: "sparkles" },
{ id: "concl", label: "Заключение", ic: "clipboard" },
{ id: "approve", label: "Согласование", ic: "check-circle" },
{ id: "about", label: "О решении", ic: "info" }
];
var cur = curStage();
var html = "";
items.forEach(function (it) {
var st = "";
if (it.id === "load") st = (cur === 1) ? "current" : "done";
else if (it.id === "analysis") st = (cur === 2) ? "current" : (cur > 2 ? "done" : "");
else if (it.id === "concl") st = (cur === 3) ? "current" : (cur > 3 ? "done" : "");
else if (it.id === "approve") st = (cur === 4) ? "current" : "";
var active = S.screen === it.id ? "true" : "false";
html += '' +
icon(it.ic) + '' + esc(it.label) + " " +
(st ? ' • ' : "") +
" ";
});
return html;
}
S = load() || defaultState();
$("pageTitle").textContent = SCREENS[S.screen].title;
$("pageSub").style.display = "none";
$("sbAvatar").textContent = initials(S.candidate.name);
$("sbName").textContent = S.candidate.name;
$("crumbRole").textContent = S.role === "leader" ? "Руководитель" : "Эксперт";
$("snav").innerHTML = navHtml();
/* ---------- Шапка-карточка кандидата (экраны 1–3) ---------- */
function candHead(sub, extra) {
var c = S.candidate;
var done = cand()._doneCount || 0;
return '' +
'
" +
'
' + STEPS.map(function (s, i) {
var st = (i + 1 < curStage()) ? "done" : (i + 1 === curStage()) ? "current" : "";
return '
" + (i + 1) + " " +
'' + esc(s.label) + "
" + (i < STEPS.length - 1 ? '
' : "");
}).join("") + "
" +
(extra || "") +
"
";
}
/* ============================================================
ЭКРАН 1 — ЗАГРУЗКА ПАКЕТА ДОКУМЕНТОВ
============================================================ */
function renderLoad() {
var c = cand();
var files = S.files;
var zoneHtml = ["ДИ", "ПЗ", "Анкета"].map(function (t) {
var f = null;
for (var i = 0; i < files.length; i++) if (files[i].type === t) { f = files[i]; break; }
var has = !!f;
var ic = has ? "file" : "upload";
var fname = has ? f.name : "Файл ещё не загружен";
var sub = has ? (f.status === "done" ? "Обработан" : "Ожидание обработки") : "Перетащите или выберите файл";
return '' +
'' + icon(ic) + " " +
'' + t + " " +
'' + esc(fname) + " " +
'' + esc(sub) + " " +
'' + (has ? "Заменить файл" : "Загрузить файл") + " " +
"
";
}).join("");
var rows = files.map(function (f, i) {
var st = f.status === "done" ? "ok" : "warn";
var stLabel = f.status === "done" ? "Обработан" : "В обработке";
return '' +
'
' + icon("file") + " " +
'
' + esc(f.name) + " " +
'Тип: ' + esc(f.type) + "
" +
'
' + stLabel + " " +
'
' + icon("x") + " ";
}).join("");
var btns = '' + icon("play") + " Запустить анализ ИИ " +
'Отменить ';
return '' + candHead() +
'
' +
kpi(String(files.length) + " / 3", "Документов загружено", "пакет кандидата") +
kpi("150 мин", "Труда без автоматизации", "на одно назначение") +
kpi("3 р.д", "Срок подготовки заключения", "по регламенту") +
"
" +
'
' +
'
' +
'
Новая сверка ' +
'
' +
"Заполните сведения о кандидате и загрузите комплект документов — система сверит их с нормативами и найдёт расхождения.
" +
'
" +
'
' +
'
Загрузка документов ' +
'
' +
'
3 обязательных файла ' +
'
' + zoneHtml + "
" +
'
' +
'
Загруженные файлы ' +
'
' +
'
Ожидание загрузки / обработки ' +
'
' + rows + "
" +
'
' + btns + "
" +
"
" +
"
" +
"
";
}
/* ============================================================
ЭКРАН 2 — АНАЛИЗ ИИ И ПРОВЕРКА ВЫВОДОВ
============================================================ */
function violCard(v) {
var ch = S.checks[v.id];
var t = v.type === "contra" ? "contra" : "norm";
var tLabel = v.type === "contra" ? "Расхождение" : "Несоответствие нормативу";
var normLine = (v.type === "norm")
? ('Норматив: ' + esc(v.norm) + ' ')
: 'Расхождение в данных документов ';
var body =
'' + icon(t === "contra" ? "compare" : "scale") + "
" +
'' +
'
' + tLabel + " " +
'
' + esc(v.desc) + "
" +
'
Документ: ' + esc(v.doc) + " · Пункт: " + esc(v.item) + "
" +
normLine +
"
";
var actions =
ch === "pending"
? '' + icon("check") + " Подтвердить " +
'Отклонить '
: ch === "confirmed"
? 'Вернуть на проверку " + pill("Подтверждено", "ok")
: pill("Отклонено", "muted") + 'Вернуть ';
return '' + body +
'
' + actions + "
";
}
function renderAnalysis() {
var done = 0, confirmed = 0, rejected = 0, pending = 0;
VIOL.forEach(function (v) {
var ch = S.checks[v.id];
if (ch === "pending") pending++;
else if (ch === "confirmed") confirmed++;
else rejected++;
if (ch !== "pending") done++;
});
var tone = S.analysisStatus === "done" ? "ok" : "risk";
var banner = S.analysisStatus === "done"
? 'Сверка завершена: найдено ' + VIOL.length + " несоответствий и " + VIOL.filter(function(v){return v.type==="contra"}).length + " расхождений " +
'22.04 с
'
: 'Анализ не завершён: один документ не распознан ' +
'' + icon("refresh") + " Повторить
";
return '' + candHead() + banner +
'
' +
kpi(String(VIOL.length), "Найдено несоответствий", "сверка с нормативами") +
kpi(String(confirmed), "Подтверждено", "экспертом") +
kpi(String(rejected), "Отклонено", "ложные срабатывания") +
kpi(String(pending), "Осталось проверить", "к ручному осмотру") +
"
" +
'
' +
'
' +
'
" +
'
' +
'
' +
'' + icon("arrowRight") + " Перейти к составлению заключения " +
'В заключение войдут ' + confirmed + " подтверждённых" + (confirmed === 1 ? " пункт" : confirmed >= 2 && confirmed <= 4 ? " пункта" : " пунктов") + " " +
"
" +
"
" +
"
" +
"
";
}
/* ============================================================
ЭКРАН 3 — СОСТАВЛЕНИЕ ЗАКЛЮЧЕНИЯ
============================================================ */
function confirmedViolHtml(n) {
var c = 0;
var html = "";
VIOL.forEach(function (v) {
if (S.checks[v.id] === "confirmed") {
c++;
var toneDot = v.type === "contra" ? "var(--kt-ai-status-warn-fg)" : "var(--kt-ai-status-info-fg)";
var normTxt = v.type === "norm" ? esc(v.norm) : "Расхождение данных";
html += '' +
"
" + c + ". " + esc(v.desc) + "
" +
'
' + esc(v.doc) + " · " + esc(v.item) + " · " + normTxt + "
";
}
});
if (c === 0) html = 'Пока ни одного подтверждённого Вернитесь на «Анализ ИИ» и подтвердите несоответствия — они попадут в заключение.
';
return html;
}
function normativeRefsHtml() {
var seen = {};
var out = [];
VIOL.forEach(function (v) {
if (S.checks[v.id] === "confirmed" && v.type === "norm") {
var ref = NORMATIVES.find(function (nr) { return nr.id === v.normId; });
if (ref && !seen[ref.id]) {
seen[ref.id] = true;
out.push("" + esc(ref.r) + " " + esc(ref.p) + " ");
}
}
});
if (!out.length) return 'Ссылки появятся после подтверждения нормативных несоответствий.
';
return out.map(function (h) {
return '' + icon("link") + h + "
";
}).join("");
}
function renderConcl() {
var c = cand();
var tone = c.stage === "sent" ? "warn" : "gray";
return '' + candHead() +
'
' +
'
' +
'
' +
'
' +
'
Текст заключения ' +
'
' +
pill(c.stage === "sent" ? "На согласовании" : "Черновик", tone) +
"
" +
'
' +
'
' +
'' + icon("check") + " Сохранить черновик " +
'' + icon("send") + " Отправить на согласование " +
"
" +
'
Руководитель получит копию на согласование, кадровая служба — копию.
' +
"
" +
'
' +
'
' +
'
' +
'
Подтверждённые несоответствия (' + (function(){var n=0;VIOL.forEach(function(v){if(S.checks[v.id]==="confirmed")n++;});return n;})() + ") " +
'
' +
'
из «Анализ ИИ» ' +
"
" +
'
' + confirmedViolHtml() + "
" +
"
" +
'
' +
'
Пункты нормативов
' +
normativeRefsHtml() +
"
" +
"
" +
"
" +
"
" +
"
";
}
/* ============================================================
ЭКРАН 4 — СОГЛАСОВАНИЕ РУКОВОДИТЕЛЕМ
============================================================ */
function renderApprove() {
var c = cand();
var chip = c.stage === "approved"
? pill("Согласовано", "ok")
: c.stage === "sent"
? pill("На согласовании", "warn")
: pill("Вернуто на доработку", "risk");
var c2 = c;
var actions =
c.stage === "approved"
? 'Заключение согласовано ' + esc(c.approvedAt || "сегодня") + ". Кадровая служба получит уведомление.
"
: c.stage === "sent"
? '' +
'' +
'
' +
'' + icon("check") + " Согласовать " +
'' + icon("arrowLeft") + " Вернуть на доработку " +
"
"
: 'Черновик возвращён эксперту на доработку. Дождитесь новой отправки.
';
var sentLine = c.sent ? "Отправлено: " + esc(c.sent) : "Отправлено: —";
var approvedLine = c.stage === "approved" ? "Согласовано руководителем: " + esc(c.approvedAt || "—") : "Согласование: ожидается";
return '' + candHead() +
'
' +
'
' +
kpi(esc(c.name.split(" ")[0] + " " + c.name.split(" ")[1]), "Кандидат", "на согласовании") +
kpi(sentLine.split(":")[1] || "—", "Отправлено", "экспертом") +
kpi(c.sent ? "1 день" : "—", "В согласовании", "из 3 р.д по регламенту") +
"
" +
'
' +
'
' +
'
' +
'
Заключение эксперта
' +
'
' + sentLine + " · " + approvedLine + "
" +
"
" +
chip +
"
" +
'
' +
'
Подтверждённые несоответствия
' +
confirmedViolHtml() +
actions +
"
" +
"
" +
"
";
}
/* ============================================================
ЭКРАН 5 — О РЕШЕНИИ
============================================================ */
function renderAbout() {
var proc = [
{ t: "Эксперт получает пакет документов", who: "от кадровой службы" },
{ t: "Эксперт читает пояснительную записку" },
{ t: "ИИ сверяет документы с квалификационными требованиями", ai: true },
{ t: "ИИ ищет расхождения в документах", ai: true },
{ t: "Эксперт проверяет выводы ИИ" },
{ t: "Эксперт готовит заключение с несоответствиями и ссылками" },
{ t: "Руководитель согласовывает заключение" }
];
return '' +
'
' +
'
Паспорт ИИ-решения
' +
'
Ускоряет подготовку заключения о соответствии кандидата за счёт автоматизации сверки и поиска расхождений.
' +
'
' +
'
' +
kpi("~50 ч/мес", "Экономия", "20 случаев × 150 мин") +
kpi("3 р.д → 1 день", "Срок подготовки", "заключения") +
kpi("20 случаев", "Объём в месяц", "оценка владельца процесса") +
kpi("—", "Дата внедрения", "после согласования макета") +
"
" +
"
" +
'
' +
'
' +
'
Поэтапный эффект
' +
'
' +
'
Сокращение чтения вручную эксперт осматривает уже только выводы ИИ
' +
'
Запасание расхождений при сверке нормативы и требования — в одном месте
' +
'
Стабильный срок заключения руководитель получает в тот же день
' +
"
" +
"
" +
'
' +
'
Контроль и риски
' +
'
' +
'
Ошибочное срабатывание ИИ в случаях расхождений данных
' +
'
Митигация — ручная проверка каждый вывод подтверждает или отклоняет эксперт
' +
"
" +
"
" +
"
" +
'
' +
'
Данные на входе
' +
'
' +
'
' + icon("file") + ' Комплект документов кандидата должностная инструкция, пояснительная записка, анкета
' +
'
' + icon("book") + ' Внутренние нормативы папка на общем диске
' +
"
" +
"
" +
'
' +
'
Процесс
' +
'
' + proc.map(function (p, i) {
return '
' + (i + 1) + ' ' +
"
" + esc(p.t) + '
' + (p.ai ? "ИИ" : (p.who ? esc(p.who) : "эксперт / руководитель")) + " ";
}).join("") + "
" +
"
" +
'
' +
'
' +
'
Роль пользователя
' +
'
' +
'
Эксперт загружает документы, проверяет выводы ИИ, готовит и отправляет заключение
' +
'
Руководитель просматривает итог и согласовывает
' +
'
Кадровая служба передаст пакет документов и получает копию заключения
' +
"
" +
"
" +
'
' +
'
Что выполняет ИИ
' +
'
' +
'
' + icon("scale") + ' Сверка с квалификационными требованиями и нормативами
' +
'
' + icon("compare") + ' Поиск расхождений в документах
' +
'
Человек решает: проверка выводов, написание текста, согласование.
' +
"
" +
"
" +
"
" +
'
' +
kpi("Владелец процесса", "Исполнитель", "—") +
kpi("Ветеринация на Vibe AI", "Формат", "макет → рабочее решение") +
kpi("ИИ-агент Alem", "Опция", "работа с документами") +
"
" +
"
" +
"";
}
/* ============================================================
РЕНДЕР
============================================================ */
function render() {
var v = $("view");
if (S.screen === "load") v.innerHTML = renderLoad();
else if (S.screen === "analysis") v.innerHTML = renderAnalysis();
else if (S.screen === "concl") v.innerHTML = renderConcl();
else if (S.screen === "approve") v.innerHTML = renderApprove();
else v.innerHTML = renderAbout();
var sc = SCREENS[S.screen];
$("pageTitle").textContent = sc.title;
$("snav").innerHTML = navHtml();
/* После вставки — заполнение значений в поле/подсветка текущего и навешивание действий через делегирование */
if (S.screen === "analysis") {
applyViolFilters();
}
if (S.screen === "concl") {
$("conclText").value = S.text;
}
if (S.screen === "approve") {
$("approveText").textContent = S.text;
}
}
function setScreen(name) {
S.screen = name;
save();
closeDrawer();
render();
}
/* ---------- Фильтры экрана 2 ---------- */
function applyViolFilters() {
var list = $("violList");
if (!list) return;
var typeEl = $("vType"), stateEl = $("vState");
var ty = typeEl ? typeEl.value : "all";
var st = stateEl ? stateEl.value : "all";
var html = "";
VIOL.forEach(function (v) {
if (ty !== "all" && v.type !== ty) return;
if (st !== "all" && S.checks[v.id] !== st) return;
html += violCard(v);
});
list.innerHTML = html;
}
/* ---------- Делегированные обработчики ---------- */
document.addEventListener("click", function (e) {
var navBtn = e.target.closest("[data-nav]");
if (navBtn) { setScreen(navBtn.getAttribute("data-nav")); return; }
var zoneBtn = e.target.closest(".up-grid button, .up-zones button.kt-ai-btn");
if (zoneBtn) {
var t = zoneBtn.getAttribute("data-type");
var f = S.files.find(function (x) { return x.type === t; });
if (f) {
f.status = "pending";
save();
toast("Файл загружен — ожидание обработки", "info");
render();
}
return;
}
var xBtn = e.target.closest(".up-list button.kt-ai-icon-button");
if (xBtn) {
var t2 = xBtn.getAttribute("data-type");
var f2 = S.files.find(function (x) { return x.type === t2; });
if (f2) { f2.status = "pending"; save(); render(); }
return;
}
if (e.target.id === "btnRunAnalysis") {
S.analysisStatus = "done";
save();
closeDrawer();
S.screen = "analysis";
render();
toast("Анализ завершён", "ok");
return;
}
if (e.target.id === "btnCancelLoad") {
S.files = FILES_INIT.map(function (f) { return Object.assign({}, f); });
save();
toast("Загрузка сброшена", "info");
render();
return;
}
var actBtn = e.target.closest("[data-act]");
if (actBtn) {
var id = parseInt(actBtn.getAttribute("data-id"), 10);
var act = actBtn.getAttribute("data-act");
if (act === "confirm") S.checks[id] = "confirmed";
else if (act === "reject") S.checks[id] = "rejected";
else if (act === "reopen") S.checks[id] = "pending";
save();
if (act === "confirm") toast("Несоответствие подтверждено", "ok");
if (act === "reject") toast("Отклонено как ложное срабатывание", "info");
if (act === "reopen") toast("Возвращено на проверку", "info");
applyViolFilters();
refreshKpis();
return;
}
if (e.target.id === "btnRetryAnalysis") {
S.analysisStatus = "done";
save();
render();
toast("Анализ завершён", "ok");
return;
}
if (e.target.id === "btnToConcl") {
S.screen = "concl"; save(); render();
return;
}
if (e.target.id === "btnSaveConcl") {
var ta = $("conclText");
if (ta) { S.text = ta.value; save(); }
var cc = cand();
if (cc.stage === "uploaded" || cc.stage === "ready" || cc.stage === "analyzed") cc.stage = "draft";
save();
toast("Черновик сохранён", "ok");
render();
return;
}
if (e.target.id === "btnSendConcl") {
var ta2 = $("conclText");
if (ta2) S.text = ta2.value;
var cc2 = cand();
cc2.stage = "sent";
cc2.sent = "21.09.2026, " + new Date().toTimeString().slice(0, 5);
S.text = S.text + "\n\nОтправлено на согласование: " + cc2.sent;
save();
toast("Отправлено на согласование", "ok");
S.screen = "approve";
render();
return;
}
if (e.target.id === "btnApprove") {
var cc3 = cand();
cc3.stage = "approved";
var rc = $("rejectComment");
cc3.approvedAt = rc && rc.value ? "С комментарием: " + rc.value : "без замечаний";
save();
toast("Заключение согласовано", "ok");
render();
return;
}
if (e.target.id === "btnReturnConcl") {
var cc4 = cand();
var rc2 = $("rejectComment");
cc4.stage = "draft";
cc4.returned = rc2 && rc2.value ? rc2.value : "Возвращено в экспертизу";
S.role = "expert";
S.screen = "concl";
save();
$("crumbRole").textContent = S.role === "leader" ? "Руководитель" : "Эксперт";
toast("Возвращено на доработку", "warn");
render();
return;
}
if (e.target.closest("#sbUser")) {
S.role = S.role === "expert" ? "leader" : "expert";
$("crumbRole").textContent = S.role === "leader" ? "Руководитель" : "Эксперт";
save();
toast("Переключено на роль: " + ($("crumbRole").textContent), "info");
}
});
/* Перерисовка KPI вверху экрана 2 после подтверждения/отклонения */
function refreshKpis() {
var strip = document.querySelector(".kt-ai-kpi-strip");
if (!strip) return;
var confirmed = 0, rejected = 0, pending = 0;
VIOL.forEach(function (v) {
var ch = S.checks[v.id];
if (ch === "confirmed") confirmed++;
else if (ch === "rejected") rejected++;
else pending++;
});
var vals = [
[String(pending), "Осталось проверить", "к ручному осмотру"],
[String(confirmed), "Подтверждено", "экспертом"],
[String(rejected), "Отклонено", "ложные срабатывания"]
];
var cards = strip.querySelectorAll(".kt-ai-kpi");
if (cards.length >= 4) {
cards[3].querySelector(".value").textContent = pending;
cards[1].querySelector(".value").textContent = confirmed;
cards[2].querySelector(".value").textContent = rejected;
}
}
/* Селекты на экране 2 */
document.addEventListener("change", function (e) {
if (e.target.id === "vType" || e.target.id === "vState") {
applyViolFilters();
}
});
/* Burger */
$("burger").addEventListener("click", function () {
var sb = $("sidebar");
var st = getComputedStyle(sb);
if (sb.dataset.open === "true") { sb.dataset.open = "false"; sb.style.position = "sticky"; }
else { sb.dataset.open = "true"; sb.style.position = "fixed"; sb.style.zIndex = 50; sb.style.top = "0"; sb.style.left = "0"; sb.style.right = "0"; sb.style.bottom = "0"; }
});
/* Первый рендер */
render();
if (S.screen === "concl") { var t = $("conclText"); if (t) t.value = S.text; }