";
}
function typeCounts() {
var c = { all: METRICS.length, turnover: 0, vacancy: 0, reserve: 0, performance: 0 };
METRICS.forEach(function (m) { c[m.type]++; });
return c;
}
function viewSummary() {
var t = filteredRows();
var coOpts = '' + COMPANIES.map(function (c) {
return '";
}).join("");
var cc = typeCounts();
var seg = '
' +
'" +
'" +
'" +
'" +
'" +
"
";
var toolbar = '
' + seg +
'" +
'" +
'
';
return pageHead("Сводка показателей", "Показатели дочерних компаний после загрузки, " + Q,
'") +
'
' +
'Что показывает сводка' +
'Цифры по 4 типам HR-показателей сопоставимы между компаниями. Текучесть остаётся в рабочих пределах 9–15%, назначение из резерва — сильная сторона группы, производительность варьируется по профилю бизнеса.' +
'Значение «СетьРитейл» по производительности исключено из сводки до исправления формулы — эксперт подтверждает источник после ручной проверки выгрузки.';
}
/* экран 5: о решении */
function viewAbout() {
var steps = [
"Аналитик собирает данные с дочерних компаний (ИИ)",
"Аналитик сводит данные в Excel (ИИ)",
"Аналитик проводит анализ показателей (ИИ)",
"Аналитик пишет текстовое заключение (ИИ)",
"Эксперт проверяет итоговое заключение"
];
var aiWorks = [
"Сбор и валидация данных из Excel — экран «Загрузка данных»",
"Сводка данных и проверка формул — экран «Сводка показателей»",
"Проведение анализа и генерация текстового заключения — экран «Анализ и заключение»"
];
var humans = [
"Аналитик: загружает исходные данные, проверяет сводку, редактирует заключение",
"Эффект: ускоряет процесс, повышает качество аналитики",
"Аналитик: время на один цикл — 2 недели при частоте раз в квартал. Оценка — около 20 часов в месяц"
];
var risks = [
"Ошибка ИИ при неправильном заполнении данных работниками",
"Ошибка ИИ при неверно выставленных формулах в Excel",
"Сроки и исключения по процессу определяет эксперт"
];
var measures = [
"Проверка результата человеком перед использованием",
"Журнал действий ИИ и возможность вернуть ручной режим"
];
var stepsItems = steps.map(function (s, i) {
return "
" +
'' + (i + 1) + "" +
"" + esc(s) + "
";
}).join("");
function ul(list) {
return "
" + list.map(function (l) { return "
" + esc(l) + "
"; }).join("") + "
";
}
return pageHead("О решении", "Автоматизировать сбор, сводку и анализ HR-показателей для подготовки итогового заключения") +
'
' +
'
Паспорт
' +
'
Наименование
Сбор и анализ HR-показателей
' +
"
Исполнитель
владелец процесса – сборка на платформе Vibe AI
" +
"
Пользователь
Аналитик, Руководитель
" +
"
Дата внедрения
—
" +
"
Предпосылки
" +
'
Необходимо собирать HR-аналитику в читабельном, доступном и точном формате' +
"
Отсутствует понимание, как и в какой программе собирать данные
" +
"
Входные данные и источники
" +
'
Данные в формате Excel, заполняются полностью вручную
' +
"
Примерно 30 HR-показателей (текучесть, срок закрытия вакансий, доля назначений из резерва, производительность труда)
" +
"" +
"
Шаги процесса
" +
'' + stepsItems + "" +
"
Результат
" +
'
Таблица с цифрами, текстовый отчет с выводами и дашборд
' +
"
Итоговые заключения используются руководителями для принятия решений по управлению персоналом
" +
'
Ожидаемый эффект
' +
'
Ускорение процесса
Повышение качества аналитики
' +
"" +
"
Что делает ИИ
" +
ul(aiWorks) +
'
Что проверяет и решает человек
' +
ul(humans) +
"" +
'
Контроль и риски
' +
ul(risks) +
'
Меры по снижению
' +
ul(measures) +
'
Формат реализации
' +
'
Веб-интерфейс на Vibe AI: сначала статичный макет всего процесса, после согласования — рабочее решение.
' +
"
Экраны макета: Загрузка данных; Сводка показателей; Анализ и заключение; Проверка экспертом.
" +
"" +
"
";
}
/* ---------- навигация и рендер ---------- */
var SCREENS = [
{ id: "upload", title: "Загрузка данных", sub: "исходные данные от дочерних компаний", icon: "upload", view: viewUpload },
{ id: "summary", title: "Сводка показателей", sub: "таблица, фильтры, проверки", icon: "table", view: viewSummary },
{ id: "analysis", title: "Анализ и заключение", sub: "результаты анализа и отчёт", icon: "sparkles", view: viewAnalysis },
{ id: "expert", title: "Проверка экспертом", sub: "валидация и утверждение", icon: "checkCircle", view: viewExpert },
{ id: "about", title: "О решении", sub: "паспорт и контекст процесса", icon: "info", view: viewAbout }
];
function currentTitle() {
for (var i = 0; i < SCREENS.length; i++) if (SCREENS[i].id === S.screen) return SCREENS[i].title;
return SCREENS[0].title;
}
function renderSidebar() {
var nav = $("sbNav");
if (!nav) return;
var html = [];
SCREENS.forEach(function (s, i) {
html.push('' +
icon(s.icon) +
'' + esc(s.title) + "" + esc(s.sub) + "" +
'' + (i + 1) + "");
});
nav.innerHTML = html.join("");
}
function renderTopbar() {
var t = $("topTitle");
if (!t) return;
t.innerHTML = '' + esc(currentTitle()) + "";
}
function render() {
renderSidebar();
renderTopbar();
var view = $("view");
if (!view) return;
for (var i = 0; i < SCREENS.length; i++) {
if (SCREENS[i].id === S.screen) {
view.innerHTML = SCREENS[i].view();
break;
}
}
syncSummaryControls();
syncSelects();
save();
window.scrollTo(0, 0);
}
function navigate(id) {
if (id === S.screen) return;
S.screen = id;
EDITING = null;
render();
}
function syncSummaryControls() {
var fCompany = $("fCompany");
if (fCompany) fCompany.value = CHK.co;
var fOnly = $("fOnly");
if (fOnly) fOnly.value = CHK.only;
var note = $("fNote");
if (S.screen === "summary" && note) {
var errN = METRICS.filter(function (m) { return checkStatus(m) === "error"; }).length;
note.textContent = errN ? ("Ошибок формул: " + errN) : "";
}
}
function syncSelects() {
var rc = $("srcCompany");
if (rc) rc.selectedIndex = 0;
}
/* --- события (делегирование) --- */
function onSegType(e) {
var b = e.target.closest(".seg");
if (!b) return;
CHK.type = b.getAttribute("data-v");
render();
}
function onFileListClick(e) {
var row = e.target.closest("[data-file]");
if (!row) return;
var idx = parseInt(row.getAttribute("data-file"), 10);
if (isNaN(idx)) return;
var f = S.files[idx];
if (!f) return;
if (f.status === "risk") {
toast("Файл содержит ошибку формулы: " + f.note, "risk");
} else if (f.status === "warn") {
toast("Загрузка с замечаниями: " + f.note, "warn");
} else {
toast("Файл загружен без ошибок", "ok");
}
addLog("Пользователь", "Просмотр файла", "ok", company(f.co) + ": файл открыт для просмотра");
save();
render();
}
function onTableClick(e) {
var row = e.target.closest("tr[data-row]");
if (!row) return;
var btn = e.target.closest("[data-act='error']");
if (btn) return;
var key = row.getAttribute("data-row");
var parts = key ? key.split("|") : [];
var id = parts[0], co = parts[1];
if (EDITING && EDITING.id === id && EDITING.co === co) return;
EDITING = { id: id, co: co };
render();
}
function onKeydown(e) {
if (e.key !== "Enter") return;
var inp = e.target;
if (!inp || !inp.getAttribute || !inp.getAttribute("data-edit")) return;
var key = inp.getAttribute("data-edit");
var parts = key.split("|");
var id = parts[0], co = parts[1];
var val = inp.value.trim();
var key2 = id + "_" + co;
S.overrides[key2] = { value: val, status: "ok" };
S.manual[key2] = true;
EDITING = null;
addLog("Пользователь", "Правка значения", "ok", "Значение исправлено вручную");
toast("Значение сохранено", "ok");
save();
render();
}
function onInput(e) {
if (e.target && e.target.id === "reportText") S.reportText = e.target.value;
}
function onDropzoneDragOver(e) {
e.preventDefault();
e.dataTransfer && e.dataTransfer.dropEffect && (e.dataTransfer.dropEffect = "copy");
}
function onFileInputChange(e) {
var input = e.target;
if (!input || !input.files || !input.files.length) return;
var file = input.files[0];
var srcSel = $("srcCompany");
var srcCo = srcSel ? srcSel.value : "A";
var ext = (file.name || "").toLowerCase();
var isExcel = ext.indexOf(".xlsx") !== -1 || ext.indexOf(".xls") !== -1;
if (!isExcel) {
toast("Нужен Excel-файл (.xlsx или .xls)", "risk");
return;
}
var fname = "HR_Q3-2026_" + srcCo + ".xlsx";
var existing = null;
for (var i = 0; i < S.files.length; i++) {
if (S.files[i].co === srcCo) { existing = S.files[i]; break; }
}
if (existing) {
existing.rows = 5 + Math.floor(Math.random() * 5);
existing.time = nowTime();
existing.status = "ok";
existing.note = "";
} else {
S.files.push({
name: fname, co: srcCo, rows: 5 + Math.floor(Math.random() * 5),
status: "ok", time: nowTime(), note: ""
});
}
var name = company(srcCo);
addLog("ИИ", "Загрузка", "ok", name + ": файл получен, показатели добавлены в сводку");
toast("Файл загружен: " + name, "ok");
save();
render();
}
function manualAction() {
addLog("Пользователь", "Ручной режим", "ok", "Сбор и анализ возвращены в ручной режим");
toast("Ручной режим включён", "warn");
save();
render();
}
function openLogAction() {
addLog("Пользователь", "Просмотр журнала", "ok", "Журнал действий открыт");
S.screen = "upload";
save();
render();
}
function runAnalysisAction() {
addLog("ИИ", "Анализ", "ok", "Анализ заведомо выполнен: сформирован черновик заключения");
toast("Анализ запущен, черновик заключения сформирован", "ok");
S.screen = "analysis";
save();
render();
}
function sendReviewAction() {
if (S.reportStatus !== "draft") return;
S.reportStatus = "review";
S.history.push({ time: nowTime(), text: "Отправлено на проверку эксперту", by: "Аналитик" });
addLog("Пользователь", "Отправка на проверку", "ok", "Заключение отправлено эксперту");
toast("Заключение отправлено на проверку", "ok");
save();
render();
}
function downloadAction() {
addLog("Пользователь", "Скачивание отчёта", "ok", "Отчёт сохранён в PDF");
toast("Отчёт сохранён в PDF", "ok");
save();
render();
}
function approveAction() {
if (S.reportStatus === "approved") return;
var cm = $("expertComment");
if (cm && cm.value && cm.value !== S.expertComment) {
S.expertComment = cm.value;
S.history.push({ time: nowTime(), text: "Эксперт оставил комментарий", by: "Эксперт" });
}
S.reportStatus = "approved";
S.history.push({ time: nowTime(), text: "Заключение утверждено", by: "Эксперт" });
addLog("Эксперт", "Утверждение", "ok", "Итоговое заключение утверждено");
toast("Заключение утверждено", "ok");
save();
render();
}
function rejectAction() {
if (S.reportStatus === "approved") return;
var cm = $("expertComment");
var c = cm && cm.value ? cm.value.trim() : "";
if (!c) {
toast("Введите комментарий к отклонению", "warn");
return;
}
S.expertComment = c;
S.reportStatus = "rejected";
S.history.push({ time: nowTime(), text: "Заключение отклонено с комментарием", by: "Эксперт" });
addLog("Эксперт", "Отклонение", "risk", "Заключение возвращено аналитику");
toast("Заключение отклонено, возвращено аналитику", "warn");
save();
render();
}
function toAnalysisAction() { S.screen = "analysis"; save(); render(); }
function toExpertAction() { S.screen = "expert"; save(); render(); }
function resetDemo() {
try { localStorage.removeItem(LS); } catch (e) {}
S = loadState();
toast("Данные сброшены", "ok");
render();
}
function onNavScreen(e) {
var a = e.target.closest("[data-screen]");
if (!a) return;
e.preventDefault();
navigate(a.getAttribute("data-screen"));
}
function onActClick(e) {
var b = e.target.closest("[data-act]");
if (!b) return;
var act = b.getAttribute("data-act");
var saveKey = b.getAttribute("data-save");
var input = b.closest("tr[data-row]");
if (act === "save-edit" && saveKey && input) {
var val = (input.querySelector("[data-edit]") || {}).value || "";
var parts = saveKey.split("|");
var key2 = parts[0] + "_" + parts[1];
S.overrides[key2] = { value: val.trim(), status: "ok" };
S.manual[key2] = true;
EDITING = null;
addLog("Пользователь", "Правка значения", "ok", "Значение исправлено вручную");
save();
render();
return;
}
if (act === "upload-open") {
var fi = $("fileInput");
if (fi) fi.click();
return;
}
if (act === "upload-file") {
toast("Выберите Excel-файл для загрузки", "info");
return;
}
if (act === "manual-mode") { manualAction(); return; }
if (act === "open-log") { openLogAction(); return; }
if (act === "run-analysis") { runAnalysisAction(); return; }
if (act === "send-review") { sendReviewAction(); return; }
if (act === "download") { downloadAction(); return; }
if (act === "approve") { approveAction(); return; }
if (act === "reject") { rejectAction(); return; }
if (act === "to-analysis") { toAnalysisAction(); return; }
if (act === "to-expert") { toExpertAction(); return; }
}
function setupEvents() {
document.addEventListener("click", function (e) {
if (e.target.closest(".kt-ai-segmented [data-v]")) { onSegType(e); return; }
if (e.target.closest("#fileList [data-file]")) { onFileListClick(e); return; }
if (e.target.closest("#sumTableWrap tr[data-row]")) { onTableClick(e); return; }
if (e.target.closest("[data-act]")) { onActClick(e); return; }
if (e.target.closest("[data-screen]")) { onNavScreen(e); return; }
});
document.addEventListener("keydown", onKeydown);
document.addEventListener("input", onInput);
var dz = $("dropzone");
function bindDropzone() {
var el = $("dropzone");
if (!el || el._dzBound) return;
el._dzBound = true;
el.addEventListener("dragover", onDropzoneDragOver);
el.addEventListener("drop", function (e) {
e.preventDefault();
toast("Выберите Excel-файл для загрузки", "info");
});
}
setInterval(bindDropzone, 200);
document.addEventListener("change", function (e) {
var t = e.target;
if (!t) return;
if (t.id === "fileInput") { onFileInputChange(e); return; }
if (t.id === "fCompany") { CHK.co = t.value; render(); return; }
if (t.id === "fOnly") { CHK.only = t.value; render(); return; }
if (t.id === "expertComment") { S.expertComment = t.value; save(); return; }
});
/* --- мобильное меню --- */
var burger = $("burger"), sidebar = $("sidebar"), scrim = $("scrim"), drawer = $("drawer");
function openDrawer(open) {
if (scrim) scrim.classList.toggle("on", open);
if (sidebar) sidebar.style.display = open ? "block" : "";
}
if (burger) burger.addEventListener("click", function () { openDrawer(true); });
if (scrim) scrim.addEventListener("click", function () { openDrawer(false); });
if (drawer) drawer.addEventListener("click", function (e) {
var a = e.target.closest("[data-screen]");
if (!a) return;
e.preventDefault();
openDrawer(false);
navigate(a.getAttribute("data-screen"));
});
/* --- тема --- */
var tg = $("themeToggle"), ti = $("themeIcon");
if (tg) tg.addEventListener("click", function () {
var dark = S.theme === "light";
S.theme = dark ? "dark" : "light";
document.documentElement.setAttribute("data-theme", S.theme);
if (ti) ti.setAttribute("href", dark ? "#sun" : "#moon");
save();
});
document.documentElement.setAttribute("data-theme", S.theme);
}
function init() {
var burger = $("burger");
if (burger) {
function toggleBurger() {
burger.hidden = window.innerWidth > 831;
}
window.addEventListener("resize", toggleBurger);
toggleBurger();
}
render();
setupEvents();
}
document.addEventListener("DOMContentLoaded", init);
})();