(function () { "use strict"; /* ================= Демо-данные: отдел, 11 человек ================= */ var PERIODS = { "7d": { label: "период: 7 дней", metricKey: "tasks", metricName: "Задач закрыто", metricUnit: "зад.", kpis: [ { label: "Закрыто задач", value: "128", hint: "из 141 по плану", tone: "ok", delta: "+6%" }, { label: "Эффективность", value: "81%", hint: "цель 80%", tone: "ok", delta: "+2 п.п." }, { label: "Средняя оценка", value: "4,4", hint: "из 5,0" }, { label: "Переработка в среднем", value: "1,8 ч", hint: "на день", tone: "risk", delta: "+0,4 ч" } ], trend: [ { c: "Вт", a: 70, b: 66, s: 78 }, { c: "Ср", a: 74, b: 69, s: 81 }, { c: "Чт", a: 68, b: 72, s: 79 }, { c: "Пт", a: 71, b: 70, s: 82 }, { c: "Сб", a: 65, b: 63, s: 77 }, { c: "Вс", a: 62, b: 66, s: 76 }, { c: "Пн", a: 73, b: 74, s: 84 } ], services: [ { l: "Поддержка", v: 88, tone: "green" }, { l: "Аналитика", v: 79 }, { l: "Монтаж", v: 71 }, { l: "Коммерция", v: 64 } ], top: [ { n: "Айгерим Нурлан", d: "Поддержка", v: 93 }, { n: "Тимур Серик", d: "Монтаж", v: 88 }, { n: "Ержан Касым", d: "Поддержка", v: 84 }, { n: "Динар Жума", d: "Коммерция", v: 81 }, { n: "Мария Ильина", d: "Аналитика", v: 79 } ], table: [ { name: "Айгерим Нурлан", dept: "Поддержка", metric: 24, mins: 34, plan: 96, status: "ok", sLabel: "Выше плана" }, { name: "Ержан Касым", dept: "Поддержка", metric: 16, mins: 29, plan: 94, status: "ok", sLabel: "Выше плана" }, { name: "Тимур Серик", dept: "Монтаж", metric: 18, mins: 51, plan: 90, status: "ok", sLabel: "Выше плана" }, { name: "Мария Ильина", dept: "Аналитика", metric: 21, mins: 47, plan: 87, status: "ok", sLabel: "В плане" }, { name: "Динар Жума", dept: "Коммерция", metric: 12, mins: 44, plan: 83, status: "ok", sLabel: "В плане" }, { name: "Салтанат Аман", dept: "Аналитика", metric: 19, mins: 58, plan: 80, status: "warn", sLabel: "Рядом с планом" }, { name: "Нурлан Баек", dept: "Монтаж", metric: 14, mins: 56, plan: 77, status: "warn", sLabel: "Рядом с планом" }, { name: "Жанар Сапар", dept: "Поддержка", metric: 13, mins: 38, plan: 74, status: "warn", sLabel: "Рядом с планом" }, { name: "Берик Тлеу", dept: "Коммерция", metric: 11, mins: 61, plan: 71, status: "warn", sLabel: "Ниже плана" }, { name: "Айдын Тлеу", dept: "Монтаж", metric: 10, mins: 72, plan: 66, status: "risk", sLabel: "Ниже плана" }, { name: "Алия Мырзахм", dept: "Аналитика", metric: 9, mins: 66, plan: 62, status: "risk", sLabel: "Ниже плана" } ], note: "К концу недели показатели выше начала — пик загрузки выпал на последние дни.", serviceNote: "Поддержка стабильно выше остальных", conclusion: "За неделю закрыто 128 задач при плане 141. Лучший показатель — у Айгерим Нурлан (24 задачи, 96% плана). Двое сотрудников ниже плана: Айдын Тлеу и Алия Мырзахм — стоит сверить распределение задач." }, "30d": { label: "период: 30 дней", metricKey: "tasks", metricName: "Задач закрыто", metricUnit: "зад.", kpis: [ { label: "Закрыто задач", value: "512", hint: "из 540 по плану", tone: "warn", delta: "−5%" }, { label: "Эффективность", value: "76%", hint: "цель 80%", tone: "warn", delta: "−1,4 п.п." }, { label: "Средняя оценка", value: "4,2", hint: "из 5,0" }, { label: "Переработка в среднем", value: "2,1 ч", hint: "на день", tone: "risk", delta: "+0,3 ч" } ], trend: [ { c: "Нед. 1", a: 118, b: 124, s: 121 }, { c: "Нед. 2", a: 126, b: 121, s: 124 }, { c: "Нед. 3", a: 132, b: 138, s: 135 }, { c: "Нед. 4", a: 108, b: 120, s: 102 } ], services: [ { l: "Поддержка", v: 82 }, { l: "Аналитика", v: 74 }, { l: "Монтаж", v: 68, tone: "orange" }, { l: "Коммерция", v: 61, tone: "orange" } ], top: [ { n: "Айгерим Нурлан", d: "Поддержка", v: 92 }, { n: "Мария Ильина", d: "Аналитика", v: 90 }, { n: "Тимур Серик", d: "Монтаж", v: 85 }, { n: "Ержан Касым", d: "Поддержка", v: 82 }, { n: "Салтанат Аман", d: "Аналитика", v: 80 } ], table: [ { name: "Айгерим Нурлан", dept: "Поддержка", metric: 96, mins: 35, plan: 95, status: "ok", sLabel: "Выше плана" }, { name: "Мария Ильина", dept: "Аналитика", metric: 104, mins: 48, plan: 92, status: "ok", sLabel: "Выше плана" }, { name: "Ержан Касым", dept: "Поддержка", metric: 88, mins: 31, plan: 90, status: "ok", sLabel: "В плане" }, { name: "Тимур Серик", dept: "Монтаж", metric: 86, mins: 52, plan: 88, status: "ok", sLabel: "В плане" }, { name: "Салтанат Аман", dept: "Аналитика", metric: 92, mins: 60, plan: 84, status: "ok", sLabel: "В плане" }, { name: "Динар Жума", dept: "Коммерция", metric: 74, mins: 45, plan: 79, status: "warn", sLabel: "Рядом с планом" }, { name: "Жанар Сапар", dept: "Поддержка", metric: 84, mins: 39, plan: 76, status: "warn", sLabel: "Рядом с планом" }, { name: "Нурлан Баек", dept: "Монтаж", metric: 70, mins: 58, plan: 73, status: "warn", sLabel: "Ниже плана" }, { name: "Берик Тлеу", dept: "Коммерция", metric: 62, mins: 63, plan: 68, status: "warn", sLabel: "Ниже плана" }, { name: "Айдын Тлеу", dept: "Монтаж", metric: 55, mins: 74, plan: 62, status: "risk", sLabel: "Ниже плана" }, { name: "Алия Мырзахм", dept: "Аналитика", metric: 58, mins: 67, plan: 58, status: "risk", sLabel: "Ниже плана" } ], note: "Четвёртая неделя — заметно ниже первых трёх: план не выполнен на 17%.", serviceNote: "У монтажа и коммерции — наихудшая динамика", conclusion: "За месяц закрыто 512 задач из плана 540. Четвёртая неделя дала на 17% меньше, чем средняя по первым трём — к концу месяца план не закрыт. Эффективность 76% при цели 80%; отстают монтаж и коммерция." }, "quarter": { label: "период: квартал", metricKey: "tasks", metricName: "Задач закрыто", metricUnit: "зад.", kpis: [ { label: "Закрыто задач", value: "1 486", hint: "из 1 520 по плану", tone: "warn", delta: "−2%" }, { label: "Эффективность", value: "79%", hint: "цель 80%", tone: "warn", delta: "−0,6 п.п." }, { label: "Средняя оценка", value: "4,1", hint: "из 5,0" }, { label: "Переработка в среднем", value: "2,3 ч", hint: "на день", tone: "risk", delta: "+0,5 ч" } ], trend: [ { c: "Июнь", a: 448, b: 470, s: 459 }, { c: "Июль", a: 462, b: 488, s: 475 }, { c: "Август", a: 489, b: 511, s: 500 } ], services: [ { l: "Поддержка", v: 84, tone: "green" }, { l: "Аналитика", v: 77 }, { l: "Монтаж", v: 70 }, { l: "Коммерция", v: 66 } ], top: [ { n: "Айгерим Нурлан", d: "Поддержка", v: 94 }, { name: "Мария Ильина", d: "Аналитика", v: 89 }, { n: "Тимур Серик", d: "Монтаж", v: 86 }, { n: "Салтанат Аман", d: "Аналитика", v: 83 }, { n: "Ержан Касым", d: "Поддержка", v: 81 } ], table: [ { name: "Айгерим Нурлан", dept: "Поддержка", metric: 268, mins: 36, plan: 94, status: "ok", sLabel: "Выше плана" }, { name: "Мария Ильина", dept: "Аналитика", metric: 294, mins: 49, plan: 89, status: "ok", sLabel: "Выше плана" }, { name: "Тимур Серик", dept: "Монтаж", metric: 252, mins: 53, plan: 86, status: "ok", sLabel: "В плане" }, { name: "Ержан Касым", dept: "Поддержка", metric: 246, mins: 32, plan: 85, status: "ok", sLabel: "В плане" }, { name: "Салтанат Аман", dept: "Аналитика", metric: 271, mins: 61, plan: 83, status: "ok", sLabel: "В плане" }, { name: "Динар Жума", dept: "Коммерция", metric: 208, mins: 46, plan: 79, status: "ok", sLabel: "В плане" }, { name: "Жанар Сапар", dept: "Поддержка", metric: 239, mins: 40, plan: 76, status: "warn", sLabel: "Рядом с планом" }, { name: "Нурлан Баек", dept: "Монтаж", metric: 217, mins: 59, plan: 73, status: "warn", sLabel: "Рядом с планом" }, { name: "Берик Тлеу", dept: "Коммерция", metric: 198, mins: 64, plan: 68, status: "warn", sLabel: "Ниже плана" }, { name: "Алидин Тлеу", dept: "Монтаж", metric: 185, mins: 76, plan: 61, status: "risk", sLabel: "Ниже плана" }, { name: "Алия Мырзахм", dept: "Аналитика", metric: 195, mins: 68, plan: 57, status: "risk", sLabel: "Ниже плана" } ], note: "Тренд положительный: за квартал — прирост 10,6% закрытых задач к июню.", serviceNote: "Поддержка держит план по всем месяцам", conclusion: "За квартал — 1 486 задач из плана 1 520. Общий тренд положительный (+10,6% к июню), но план за месяц не закрыт. Эффективность 79% при цели 80%, переработка выросла до 2,3 ч в день — нагрузка выше проектной." } }; /* ================= Состояние и localStorage ================= */ var LS_KEY = "dash-effect-v1"; function defaultState() { return { period: "30d", search: "", dept: "all", sortKey: "metric", sortDir: "desc" }; } function loadState() { try { var raw = localStorage.getItem(LS_KEY); if (!raw) return defaultState(); var parsed = JSON.parse(raw); var st = defaultState(); if (parsed && typeof parsed === "object") { if (PERIODS[parsed.period]) st.period = parsed.period; if (typeof parsed.search === "string") st.search = parsed.search; if (typeof parsed.dept === "string") st.dept = parsed.dept; if (parsed.sortKey === "metric" || parsed.sortKey === "time") st.sortKey = parsed.sortKey; if (parsed.sortDir === "asc" || parsed.sortDir === "desc") st.sortDir = parsed.sortDir; } return st; } catch (e) { return defaultState(); } } function saveState() { try { localStorage.setItem(LS_KEY, JSON.stringify(state)); } catch (e) {} } var state = loadState(); /* ================= Утилиты ================= */ function el(id) { return document.getElementById(id); } function esc(s) { return String(s).replace(/&/g, "&").replace(//g, ">").replace(/"/g, """); } function fmt(mins) { var h = Math.floor(mins / 60); var m = Math.round(mins % 60); if (!h) return m + " мин"; return h + " ч " + m + " мин"; } function todayStr() { var d = new Date(); var p = function (n) { return String(n).length < 2 ? "0" + String(n) : String(n); }; return p(d.getDate()) + "." + p(d.getMonth() + 1) + "." + d.getFullYear(); } function toast(msg, tone) { var stack = document.querySelector(".kt-ai-toast-stack"); if (!stack) return; var t = document.createElement("div"); t.className = "kt-ai-toast"; if (tone) t.setAttribute("data-tone", tone); t.textContent = msg; stack.appendChild(t); setTimeout(function () { if (t.parentNode) t.parentNode.removeChild(t); }, 3000); } function deltaHtml(item) { if (!item.delta) return ""; var tone = item.tone === "risk" ? "risk" : "ok"; return '' + esc(item.delta) + " к прошлому периоду"; } /* ================= Данные периода ================= */ function data(period) { var d = PERIODS[period] || PERIODS["30d"]; var rows = (d.table || []).map(function (r) { return { name: r.name, dept: r.dept, metric: Number(r.metric) || 0, mins: Number(r.mins) || 0, plan: Number(r.plan) || 0, status: r.status, sLabel: r.sLabel }; }); /* статус строится из плана — согласуется даже при расхождениях данных */ rows = rows.map(function (r) { var s = r.plan >= 88 ? "ok" : r.plan >= 75 ? "warn" : "risk"; var l = r.plan >= 88 ? "Выше плана" : r.plan >= 75 ? "Рядом с планом" : "Ниже плана"; return { name: r.name, dept: r.dept, metric: r.metric, mins: r.mins, plan: r.plan, status: s, sLabel: l }; }); var depts = []; var seen = {}; rows.forEach(function (r) { if (!seen[r.dept]) { seen[r.dept] = true; depts.push(r.dept); } }); return { raw: d, rows: rows, depts: depts }; } /* ================= KPI ================= */ function renderKpis(period) { var d = data(period); var wrap = el("kpi-strip"); if (!wrap) return; wrap.innerHTML = d.raw.kpis.map(function (k) { return '