navaybkod-deshbord-ispolneni/index.html

454 lines
34 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="ru" data-theme="light">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>OKR Дашборд</title>
<script data-kt-ai-base-resolver>
(function () {
var корень = document.documentElement;
function норм(значение) {
значение = (значение || "").trim();
if (!значение || значение === "/") return "";
if (значение.charAt(0) !== "/") значение = "/" + значение;
return значение.replace(/\/+$/, "");
}
var база = норм(корень.getAttribute("data-kt-ai-base"));
var папка = норм(корень.getAttribute("data-kt-ai-static")) || "/design-system";
window.ktAiBase = база;
window.ktAiAsset = function (имя) {
return /^([a-z][a-z0-9+.-]*:|\/\/)/i.test(имя) ? имя : база + папка + "/" + имя;
};
})();
</script>
<script data-kt-ai-boot="css">
["kt-ai-tokens.css", "kt-ai-components.css"].forEach(function (файл) {
var ссылка = document.createElement("link");
ссылка.rel = "stylesheet";
ссылка.href = window.ktAiAsset(файл);
document.head.appendChild(ссылка);
});
</script>
<style>
@layer product {
*, *::before, *::after { box-sizing: border-box; }
body { margin: 0; }
.shell-topbar-burger { display: none; }
@media (max-width: 831.98px) { .shell-topbar-burger { display: inline-flex; } }
.sb-top { display: flex; align-items: center; gap: var(--kt-ai-space-3); padding: 2px 4px 12px; }
.sb-top img { width: 28px; height: 28px; border-radius: var(--kt-ai-radius-xl); flex-shrink: 0; }
.sb-top .nm { font-weight: var(--kt-ai-weight-semibold); font-size: var(--kt-ai-text-sm); min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: 1; }
.sb-bottom { margin-top: auto; display: flex; flex-direction: column; gap: 2px; }
.sb-bottom .badge { margin-left: auto; font-family: var(--kt-ai-font-mono); font-size: var(--kt-ai-text-2xs); background: var(--kt-ai-primary-subtle); color: var(--kt-ai-primary); border-radius: var(--kt-ai-radius-full); padding: 0 6px; }
.sb-bottom { margin-top: auto; display: flex; flex-direction: column; gap: 2px; padding-top: var(--kt-ai-space-4); }
.sb-bottom button { background: none; border: none; font: inherit; color: inherit; text-align: left; cursor: pointer; width: 100%; }
.kt-ai-shell[data-rail="true"] .sb-tour { justify-content: center; padding: var(--kt-ai-space-3); }
.kt-ai-shell[data-rail="true"] .sb-tour .tx { display: none; }
.topbar-ai {
display: inline-flex; align-items: center; gap: var(--kt-ai-space-2); flex-shrink: 0;
height: 32px; padding: 0 var(--kt-ai-space-4); border-radius: var(--kt-ai-radius-full);
border: 1px solid var(--kt-ai-border-strong); background: var(--kt-ai-bg-elevated); color: var(--kt-ai-fg);
cursor: pointer; font-size: var(--kt-ai-text-sm); font-weight: var(--kt-ai-weight-medium);
transition: background var(--kt-ai-dur-fast);
}
.topbar-ai:hover { background: var(--kt-ai-bg-hover); }
@media (max-width: 639.98px) { .topbar-ai { padding: 0; width: 32px; justify-content: center; } .topbar-ai .ai-label { display: none; } }
.topbar-title { display: flex; align-items: baseline; gap: var(--kt-ai-space-4); min-width: 0; }
.topbar-title .t { font-size: var(--kt-ai-text-lg); font-weight: var(--kt-ai-weight-semibold); letter-spacing: -.01em; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.topbar-title .sub { font-size: var(--kt-ai-text-xs); color: var(--kt-ai-fg-muted); white-space: nowrap; }
@media (max-width: 639.98px) { .topbar-title .sub { display: none; } }
.kt-ai-topbar .kt-ai-search-field:not([data-compact="true"]) { max-width: 240px; }
@media (max-width: 767.98px) { .kt-ai-topbar .kt-ai-search-field:not([data-compact="true"]) { max-width: 176px; } }
#view { width: 100%; max-width: var(--kt-ai-content-table); margin-inline: auto; padding: var(--kt-ai-stack-region) var(--kt-ai-stack-region) var(--kt-ai-space-11); }
@media (max-width: 639.98px) { #view { padding: var(--kt-ai-stack-group) var(--kt-ai-space-6) var(--kt-ai-space-10); } }
.page-hero { display: flex; align-items: flex-start; gap: var(--kt-ai-space-6); margin-bottom: var(--kt-ai-stack-group); }
.page-hero .ph-text { flex: 1; min-width: 0; }
.page-hero h1 { margin: 0; font-size: var(--kt-ai-text-2xl); font-weight: var(--kt-ai-weight-semibold); letter-spacing: -.01em; line-height: 1.15; }
.page-hero .sub { margin-top: var(--kt-ai-space-3); font-size: var(--kt-ai-text-md); color: var(--kt-ai-fg-muted); }
.page-hero .ph-action { flex-shrink: 0; }
@media (max-width: 639.98px) { .page-hero { flex-direction: column; gap: var(--kt-ai-space-5); } .page-hero h1 { font-size: var(--kt-ai-text-2xl); } }
.shell-panel-toggle { display: inline-flex; }
@media (max-width: 831.98px) { .shell-panel-toggle { display: none; } }
.sb-new { margin: 0 4px var(--kt-ai-space-5); }
.sb-new .kt-ai-btn { width: 100%; justify-content: center; }
.kt-ai-shell[data-rail="true"] .sb-new .kt-ai-btn { padding: 0; }
.sb-bottom .kt-ai-nav-item > .kt-icon { flex: 0 0 20px; }
.sb-bottom .kt-ai-avatar { width: 20px; height: 20px; font-size: 8px; flex: 0 0 20px; }
.sb-bottom > div:last-child { padding: var(--kt-ai-space-3) var(--kt-ai-space-4) 0; }
.case-steps { margin-bottom: var(--kt-ai-space-6); overflow-x: auto; padding-bottom: 2px; }
.case-file { margin-top: var(--kt-ai-space-4); }
.menu-user { padding: var(--kt-ai-space-3) var(--kt-ai-space-4); }
.menu-user .nm { font-size: var(--kt-ai-text-sm); font-weight: var(--kt-ai-weight-medium); color: var(--kt-ai-fg); }
.menu-user .role { font-size: var(--kt-ai-text-2xs); color: var(--kt-ai-fg-muted); }
.notif-menu { min-width: 300px; max-width: 340px; padding: 0; }
.notif-menu .head { padding: var(--kt-ai-space-4) var(--kt-ai-space-5); font-size: var(--kt-ai-text-xs); font-weight: var(--kt-ai-weight-semibold); border-bottom: 1px solid var(--kt-ai-divider); }
.toolbar-row { display: flex; align-items: center; gap: var(--kt-ai-space-4); flex-wrap: wrap; padding: 0 0 var(--kt-ai-stack-block); }
.toolbar-row .spacer { flex: 1; }
.drawer-head { display: flex; align-items: flex-start; gap: var(--kt-ai-space-4); margin-bottom: var(--kt-ai-space-6); }
.drawer-head .t { flex: 1; }
.drawer-flags { display: flex; flex-direction: column; gap: var(--kt-ai-space-3); margin: var(--kt-ai-space-6) 0; }
.drawer-actions { display: flex; flex-direction: column; gap: var(--kt-ai-space-4); margin-top: var(--kt-ai-space-8); }
.kt-ai-card .drawer-actions { flex-direction: row-reverse; justify-content: flex-start; }
.kt-ai-card .drawer-actions .kt-ai-btn { width: auto; }
@media (max-width: 639.98px) { .kt-ai-card .drawer-actions { flex-direction: column; } }
.section-stub { margin-top: var(--kt-ai-space-10); }
.proto-label { margin-left: auto; }
td .kt-ai-checkbox input, th .kt-ai-checkbox input { margin: 0; }
.topbar-dot { position: absolute; top: 4px; right: 4px; width: 7px; height: 7px; border-radius: 50%; background: var(--kt-ai-primary); }
.kt-ai-topbar .kt-ai-menu { right: 0; left: auto; top: calc(100% + 6px); }
.progress-cell { display: flex; align-items: center; gap: var(--kt-ai-space-3); }
.progress-bar { flex: 1; height: 8px; border-radius: var(--kt-ai-radius-full); background: var(--kt-ai-bg-sunken); overflow: hidden; }
.progress-bar > span { display: block; height: 100%; border-radius: var(--kt-ai-radius-full); background: var(--kt-ai-chart-blue); }
.progress-bar.ok > span { background: var(--kt-ai-chart-green); }
.progress-bar.warn > span { background: var(--kt-ai-chart-orange); }
.progress-bar.risk > span { background: var(--kt-ai-chart-red); }
.progress-pct { font-family: var(--kt-ai-font-mono); font-size: var(--kt-ai-text-xs); color: var(--kt-ai-fg-muted); min-width: 42px; text-align: right; }
.chart-container { height: 220px; margin-top: var(--kt-ai-space-6); }
.chart-bars { display: flex; align-items: flex-end; gap: var(--kt-ai-space-4); height: 180px; padding: var(--kt-ai-space-4) 0; }
.chart-bar { flex: 1; display: flex; flex-direction: column; align-items: center; gap: var(--kt-ai-space-2); }
.chart-bar .bar { width: 100%; max-width: 48px; border-radius: var(--kt-ai-radius-md) var(--kt-ai-radius-md) 0 0; background: var(--kt-ai-chart-blue); transition: height 0.3s ease; }
.chart-bar .bar.ok { background: var(--kt-ai-chart-green); }
.chart-bar .bar.warn { background: var(--kt-ai-chart-orange); }
.chart-bar .bar.risk { background: var(--kt-ai-chart-red); }
.chart-bar .label { font-size: var(--kt-ai-text-2xs); color: var(--kt-ai-fg-muted); text-align: center; }
.chart-bar .value { font-family: var(--kt-ai-font-mono); font-size: var(--kt-ai-text-xs); color: var(--kt-ai-fg); }
.owner-avatar { width: 24px; height: 24px; border-radius: 50%; background: var(--kt-ai-primary-subtle); color: var(--kt-ai-primary); display: inline-flex; align-items: center; justify-content: center; font-size: var(--kt-ai-text-2xs); font-weight: var(--kt-ai-weight-semibold); margin-right: var(--kt-ai-space-2); }
.kt-ai-kpi-strip { margin-bottom: var(--kt-ai-stack-block); }
}
</style>
</head>
<body class="kt-ai-app">
<script id="kt-app-config" type="application/json">
{
"product": { "name": "OKR Дашборд", "purpose": "Исполнение OKR компании", "entity": "ok" },
"user": { "name": "Төре", "role": "Руководитель" },
"nav": [
{ "label": "Дашборд", "icon": "dashboard" },
{ "label": "Цели", "icon": "trophy" },
{ "label": "Команда", "icon": "users" },
{ "label": "Отчёты", "icon": "chart" }
],
"kpis": [
{ "value": "24", "label": "всего OKR", "hint": "Q3 2026" },
{ "value": "9", "label": "выполнено", "hint": "37.5%" },
{ "value": "12", "label": "в работе", "hint": "on-track" },
{ "value": "3", "label": "риски", "hint": "требуют внимания" }
],
"statuses": {
"on-track": { "label": "в графике", "tone": "ok" },
"at-risk": { "label": "риск", "tone": "warn" },
"off-track": { "label": "отставание", "tone": "risk" },
"completed": { "label": "выполнено", "tone": "ok" }
},
"table": {
"columns": [
{ "key": "objective", "label": "Цель", "type": "text" },
{ "key": "owner", "label": "Владелец", "type": "text" },
{ "key": "progress", "label": "Прогресс", "type": "num" },
{ "key": "quarter", "label": "Квартал", "type": "text" },
{ "key": "status", "label": "Статус", "type": "status" }
],
"rows": [
{ "objective": "Увеличить доход на 30%", "owner": "Алия Т.", "progress": 85, "quarter": "Q3", "status": "on-track", "_kr": "KR1: Новые клиенты +25%, KR2: LTV +15%" },
{ "objective": "Запустить мобильное приложение", "owner": "Бауыржан К.", "progress": 100, "quarter": "Q3", "status": "completed", "_kr": "KR1: App Store, KR2: Google Play, KR3: 10K загрузок" },
{ "objective": "Снизить отток клиентов до 5%", "owner": "Светлана И.", "progress": 60, "quarter": "Q3", "status": "on-track", "_kr": "KR1: Churn <5%, KR2: NPS >40" },
{ "objective": "Автоматизировать 80% рутины", "owner": "Данияр М.", "progress": 45, "quarter": "Q3", "status": "at-risk", "_kr": "KR1: 20 процессов, KR2: 500 часов/мес" },
{ "objective": "Выйти на рынок Узбекистана", "owner": "Ержан А.", "progress": 30, "quarter": "Q3", "status": "at-risk", "_kr": "KR1: Юрлицо, KR2: 5 сотрудников" },
{ "objective": "Повысить удовлетворённость сотрудников", "owner": "Гульнара С.", "progress": 100, "quarter": "Q2", "status": "completed", "_kr": "KR1: eNPS >30, KR2: Текучесть <10%" },
{ "objective": "Запустить 3 новых продукта", "owner": "Алия Т.", "progress": 67, "quarter": "Q3", "status": "on-track", "_kr": "KR1: Продукт A, KR2: Продукт B, KR3: Продукт C" },
{ "objective": "Сократить издержки на 15%", "owner": "Бауыржан К.", "progress": 100, "quarter": "Q2", "status": "completed", "_kr": "KR1: Оптимизация закупок, KR2: Энергоэффективность" },
{ "objective": "Обучить 100% команды", "owner": "Гульнара С.", "progress": 78, "quarter": "Q3", "status": "on-track", "_kr": "KR1: 40 часов/сотрудник, KR2: 10 сертификаций" },
{ "objective": "Увеличить долю рынка до 25%", "owner": "Светлана И.", "progress": 52, "quarter": "Q3", "status": "on-track", "_kr": "KR1: Маркетинг +30%, KR2: Партнёрства" },
{ "objective": "Внедрить CRM-систему", "owner": "Данияр М.", "progress": 90, "quarter": "Q3", "status": "on-track", "_kr": "KR1: Миграция данных, KR2: Обучение" },
{ "objective": "Запустить программу лояльности", "owner": "Ержан А.", "progress": 20, "quarter": "Q3", "status": "off-track", "_kr": "KR1: 10K участников, KR2: Retention +10%" },
{ "objective": "Оптимизировать логистику", "owner": "Алия Т.", "progress": 100, "quarter": "Q2", "status": "completed", "_kr": "KR1: Время доставки -20%, KR2: Стоимость -15%" },
{ "objective": "Развить партнёрскую сеть", "owner": "Бауыржан К.", "progress": 65, "quarter": "Q3", "status": "on-track", "_kr": "KR1: 50 партнёров, KR2: 20% продаж" },
{ "objective": "Улучшить качество поддержки", "owner": "Светлана И.", "progress": 88, "quarter": "Q3", "status": "on-track", "_kr": "KR1: CSAT >4.5, KR2: Время ответа <5 мин" },
{ "objective": "Запустить онлайн-академию", "owner": "Гульнара С.", "progress": 40, "quarter": "Q3", "status": "at-risk", "_kr": "KR1: 20 курсов, KR2: 1000 студентов" },
{ "objective": "Модернизировать инфраструктуру", "owner": "Данияр М.", "progress": 100, "quarter": "Q2", "status": "completed", "_kr": "KR1: Миграция в облако, KR2: Uptime 99.9%" },
{ "objective": "Расширить ассортимент на 40%", "owner": "Ержан А.", "progress": 72, "quarter": "Q3", "status": "on-track", "_kr": "KR1: 200 SKU, KR2: 30% от продаж" },
{ "objective": "Снизить время разработки на 25%", "owner": "Алия Т.", "progress": 55, "quarter": "Q3", "status": "on-track", "_kr": "KR1: CI/CD, KR2: Автоматизация тестов" },
{ "objective": "Повысить узнаваемость бренда", "owner": "Бауыржан К.", "progress": 48, "quarter": "Q3", "status": "on-track", "_kr": "KR1: Охват 1M, KR2: Упоминания +50%" },
{ "objective": "Запустить реферальную программу", "owner": "Светлана И.", "progress": 100, "quarter": "Q2", "status": "completed", "_kr": "KR1: 5K рефералов, KR2: 15% от продаж" },
{ "objective": "Оптимизировать налоговую нагрузку", "owner": "Гульнара С.", "progress": 100, "quarter": "Q2", "status": "completed", "_kr": "KR1: Легальная оптимизация, KR2: Экономия 10%" },
{ "objective": "Внедрить эко-инициативы", "owner": "Данияр М.", "progress": 35, "quarter": "Q3", "status": "off-track", "_kr": "KR1: Углеродный след -20%, KR2: Переработка" },
{ "objective": "Развить корпоративную культуру", "owner": "Ержан А.", "progress": 82, "quarter": "Q3", "status": "on-track", "_kr": "KR1: 12 мероприятий, KR2: Участие 80%" }
]
},
"drawer": {
"titleKey": "objective",
"fields": [
{ "key": "owner", "label": "Владелец" },
{ "key": "quarter", "label": "Квартал" },
{ "key": "progress", "label": "Прогресс" }
],
"flagsLabel": "Ключевые результаты"
}
}
</script>
<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" style="position:absolute;width:0;height:0;overflow:hidden">
<symbol id="dashboard" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><rect width="7" height="9" x="3" y="3" rx="1"/><rect width="7" height="5" x="14" y="3" rx="1"/><rect width="7" height="9" x="14" y="12" rx="1"/><rect width="7" height="5" x="3" y="16" rx="1"/></symbol>
<symbol id="trophy" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="M10 14.66v1.626a2 2 0 0 1-.976 1.696A5 5 0 0 0 7 21.978"/><path d="M14 14.66v1.626a2 2 0 0 0 .976 1.696A5 5 0 0 1 17 21.978"/><path d="M18 9h1.5a1 1 0 0 0 0-5H18"/><path d="M4 22h16"/><path d="M6 9a6 6 0 0 0 12 0V3a1 1 0 0 0-1-1H7a1 1 0 0 0-1 1z"/><path d="M6 9H4.5a1 1 0 0 1 0-5H6"/></symbol>
<symbol id="users" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="M19 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></symbol>
<symbol id="chart" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="M3 3v16a2 2 0 0 0 2 2h16"/><path d="M7 16h8"/><path d="M7 11h12"/><path d="M7 6h3"/></symbol>
<symbol id="search" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="m21 21-4.34-4.34"/><circle cx="11" cy="11" r="8"/></symbol>
<symbol id="filter" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="M10 20a1 1 0 0 0 .553.895l2 1A1 1 0 0 0 14 21v-7a2 2 0 0 1 .517-1.341L21.74 4.67A1 1 0 0 0 21 3H3a1 1 0 0 0-.742 1.67l7.225 7.989A2 2 0 0 1 10 14z"/></symbol>
<symbol id="settings" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="M10 5H3"/><path d="M12 19H3"/><path d="M14 3v4"/><path d="M16 17v4"/><path d="M21 12h-9"/><path d="M21 19h-5"/><path d="M21 5h-7"/><path d="M8 10v4"/></symbol>
<symbol id="help" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"/><path d="M12 17h.01"/></symbol>
<symbol id="bell" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="M10.268 21a2 2 0 0 0 3.464 0"/><path d="M3.262 15.326A1 1 0 0 0 4 17h16a1 1 0 0 0 .74-1.673C19.41 13.956 18 12.499 18 8A6 6 0 0 0 6 8c0 4.499-1.411 5.956-2.738 7.326"/></symbol>
<symbol id="sun" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="4"/><path d="M12 2v2"/><path d="M12 20v2"/><path d="m4.93 4.93 1.41 1.41"/><path d="m17.66 17.66 1.41 1.41"/><path d="M2 12h2"/><path d="M20 12h2"/><path d="m6.34 17.66-1.41 1.41"/><path d="m19.07 4.93-1.41 1.41"/></symbol>
<symbol id="moon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="M20.985 12.486a9 9 0 1 1-9.473-9.472c.405-.022.617.46.402.803a6 6 0 0 0 8.268 8.268c.344-.215.825-.004.803.401"/></symbol>
<symbol id="panelLeftClose" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><rect width="18" height="18" x="3" y="3" rx="2"/><path d="M9 3v18"/><path d="m16 15-3-3 3-3"/></symbol>
<symbol id="panelLeft" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><rect width="18" height="18" x="3" y="3" rx="2"/><path d="M9 3v18"/></symbol>
<symbol id="check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="M20 6 9 17l-5-5"/></symbol>
<symbol id="x" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></symbol>
<symbol id="chevronRight" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="m9 18 6-6-6-6"/></symbol>
<symbol id="more" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="1"/><circle cx="19" cy="12" r="1"/><circle cx="5" cy="12" r="1"/></symbol>
<symbol id="plus" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="M5 12h14M12 5v14"/></symbol>
</svg>
<div id="shell-root"></div>
<script src="design-system/kt-ai-composer.js"></script>
<script src="design-system/kt-ai-feedback.js"></script>
<script src="design-system/kt-ai-chart-tip.js"></script>
<script>
(function () {
var CFG = JSON.parse(document.getElementById('kt-app-config').textContent);
var owners = [];
CFG.table.rows.forEach(function (r) { if (owners.indexOf(r.owner) === -1) owners.push(r.owner); });
var saved = null;
try { saved = localStorage.getItem('okr-dashboard-state'); } catch (_) {}
var state = saved ? JSON.parse(saved) : { filterStatus: 'all', filterOwner: 'all', sortBy: null, sortDir: 'asc' };
function saveState() { try { localStorage.setItem('okr-dashboard-state', JSON.stringify(state)); } catch (_) {} }
function getStatusTone(status) {
var s = CFG.statuses[status];
if (!s) return '';
if (s.tone === 'ok') return 'ok';
if (s.tone === 'warn') return 'warn';
if (s.tone === 'risk') return 'risk';
return '';
}
function getProgressTone(pct) {
if (pct >= 80) return 'ok';
if (pct >= 50) return 'warn';
return 'risk';
}
function initials(name) {
var parts = (name || '').split(' ');
if (parts.length === 1) return parts[0].slice(0, 2).toUpperCase();
return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase();
}
function render() {
var rows = CFG.table.rows.slice();
if (state.filterStatus !== 'all') {
rows = rows.filter(function (r) { return r.status === state.filterStatus; });
}
if (state.filterOwner !== 'all') {
rows = rows.filter(function (r) { return r.owner === state.filterOwner; });
}
if (state.sortBy) {
rows.sort(function (a, b) {
var va = a[state.sortBy], vb = b[state.sortBy];
if (typeof va === 'number' && typeof vb === 'number') {
return state.sortDir === 'asc' ? va - vb : vb - va;
}
va = String(va).toLowerCase(); vb = String(vb).toLowerCase();
if (va < vb) return state.sortDir === 'asc' ? -1 : 1;
if (va > vb) return state.sortDir === 'asc' ? 1 : -1;
return 0;
});
}
var statusCounts = { all: CFG.table.rows.length, on-track: 0, at-risk: 0, off-track: 0, completed: 0 };
CFG.table.rows.forEach(function (r) { if (statusCounts[r.status] !== undefined) statusCounts[r.status]++; });
var quarterData = {};
CFG.table.rows.forEach(function (r) {
var q = r.quarter;
if (!quarterData[q]) quarterData[q] = { total: 0, sum: 0 };
quarterData[q].total++;
quarterData[q].sum += r.progress;
});
var quarters = Object.keys(quarterData).sort();
var html = '';
html += '<div class="kt-ai-shell" data-rail="false">';
html += '<aside class="kt-ai-sidebar">';
html += '<div class="sb-top"><img src="data:image/svg+xml,%3Csvg xmlns=\'http://www.w3.org/2000/svg\' viewBox=\'0 0 100 100\'%3E%3Ctext y=\'.9em\' font-size=\'90\'%3E🎯%3C/text%3E%3C/svg%3E" alt=""><span class="nm">OKR Дашборд</span></div>';
html += '<nav class="kt-ai-nav">';
CFG.nav.forEach(function (item, i) {
html += '<a class="kt-ai-nav-item' + (i === 0 ? ' active' : '') + '" href="#"><svg class="kt-icon"><use href="#' + (item.icon || 'dashboard') + '"></use></svg><span>' + item.label + '</span></a>';
});
html += '</nav>';
html += '<div class="sb-bottom">';
html += '<button class="kt-ai-nav-item" id="themeToggle"><svg class="kt-icon"><use href="#sun"></use></svg><span>Тема</span></button>';
html += '<button class="kt-ai-nav-item"><svg class="kt-icon"><use href="#help"></use></svg><span>Помощь</span></button>';
html += '<div class="kt-ai-nav-item"><div class="kt-ai-avatar">' + initials(CFG.user.name) + '</div><span>' + CFG.user.name + '</span><span class="badge">' + CFG.user.role + '</span></div>';
html += '</div>';
html += '</aside>';
html += '<div class="kt-ai-canvas">';
html += '<header class="kt-ai-topbar">';
html += '<div class="kt-ai-topbar-start"><button class="kt-ai-icon-button shell-topbar-burger" id="burgerBtn"><svg class="kt-icon"><use href="#panelLeft"></use></svg></button>';
html += '<div class="topbar-title"><span class="t">Исполнение OKR</span><span class="sub">Q3 2026</span></div></div>';
html += '<div class="kt-ai-topbar-end">';
html += '<div class="kt-ai-search-field"><input class="kt-ai-search" placeholder="Поиск OKR…" id="searchInput"><svg class="kt-icon"><use href="#search"></use></svg></div>';
html += '</div></header>';
html += '<main id="view">';
html += '<div class="page-hero"><div class="ph-text"><h1>Дашборд исполнения OKR</h1><p class="sub">Отслеживайте прогресс по целям и ключевым результатам компании</p></div></div>';
html += '<div class="kt-ai-kpi-strip" data-cards="true">';
CFG.kpis.forEach(function (kpi) {
html += '<div class="kt-ai-kpi"><span class="value">' + kpi.value + '</span><span class="label">' + kpi.label + '</span>' + (kpi.hint ? '<span class="hint">' + kpi.hint + '</span>' : '') + '</div>';
});
html += '</div>';
html += '<div class="toolbar-row">';
html += '<div class="kt-ai-chip-group">';
var statusLabels = { all: 'Все', on-track: 'В графике', at-risk: 'Риск', off-track: 'Отставание', completed: 'Выполнено' };
['all', 'on-track', 'at-risk', 'off-track', 'completed'].forEach(function (s) {
html += '<button class="kt-ai-chip' + (state.filterStatus === s ? ' active' : '') + '" data-status="' + s + '">' + statusLabels[s] + ' <span class="proto-label">' + statusCounts[s] + '</span></button>';
});
html += '</div>';
html += '<div class="kt-ai-select-wrap"><select class="kt-ai-select" id="ownerFilter"><option value="all">Все владельцы</option>';
owners.forEach(function (o) { html += '<option value="' + o + '"' + (state.filterOwner === o ? ' selected' : '') + '>' + o + '</option>'; });
html += '</select></div>';
html += '<div class="spacer"></div>';
html += '<button class="kt-ai-icon-button" title="Обновить"><svg class="kt-icon"><use href="#check"></use></svg></button>';
html += '<button class="kt-ai-icon-button" id="columnsBtn" title="Колонки"><svg class="kt-icon"><use href="#more"></use></svg></button>';
html += '</div>';
html += '<div class="kt-ai-table-wrap"><table class="kt-ai-table"><thead><tr>';
CFG.table.columns.forEach(function (col) {
html += '<th>' + col.label + '</th>';
});
html += '<th></th></tr></thead><tbody>';
rows.forEach(function (r) {
var statusDef = CFG.statuses[r.status] || {};
var tone = getStatusTone(r.status);
var progressTone = getProgressTone(r.progress);
html += '<tr data-obj="' + r.objective.replace(/"/g, '&quot;') + '">';
html += '<td><div><div class="cell-title">' + r.objective + '</div>' + (r._kr ? '<div class="cell-sub">' + r._kr + '</div>' : '') + '</div></td>';
html += '<td><span class="owner-avatar">' + initials(r.owner) + '</span>' + r.owner + '</td>';
html += '<td><div class="progress-cell"><div class="progress-bar ' + progressTone + '"><span style="width:' + r.progress + '%"></span></div><span class="progress-pct">' + r.progress + '%</span></div></td>';
html += '<td>' + r.quarter + '</td>';
html += '<td><span class="kt-ai-chip' + (tone ? ' data-tone="' + tone + '"' : '') + '>' + statusDef.label + '</span></td>';
html += '<td class="row-act"><button class="kt-ai-icon-button row-kebab"><svg class="kt-icon"><use href="#more"></use></svg></button></td>';
html += '</tr>';
});
html += '</tbody></table></div>';
html += '<div class="chart-container">';
html += '<h3 style="font-size:var(--kt-ai-text-lg);font-weight:var(--kt-ai-weight-semibold);margin-bottom:var(--kt-ai-space-4)">Прогресс по кварталам</h3>';
html += '<div class="chart-bars">';
quarters.forEach(function (q) {
var avg = Math.round(quarterData[q].sum / quarterData[q].total);
var barTone = getProgressTone(avg);
html += '<div class="chart-bar" data-kt-tip=\'{"title":"' + q + ' 2026","rows":[{"label":"Средний прогресс","value":"' + avg + '%"},{"label":"Целей","value":"' + quarterData[q].total + '"}]}\' tabindex="0">';
html += '<div class="bar ' + barTone + '" style="height:' + (avg * 1.8) + 'px"></div>';
html += '<span class="label">' + q + '</span>';
html += '<span class="value">' + avg + '%</span>';
html += '</div>';
});
html += '</div></div>';
html += '</main></div></div>';
document.getElementById('shell-root').innerHTML = html;
document.querySelectorAll('.kt-ai-chip[data-status]').forEach(function (chip) {
chip.addEventListener('click', function () {
state.filterStatus = this.getAttribute('data-status');
saveState();
render();
});
});
document.getElementById('ownerFilter').addEventListener('change', function () {
state.filterOwner = this.value;
saveState();
render();
});
document.getElementById('themeToggle').addEventListener('click', function () {
var html = document.documentElement;
var cur = html.getAttribute('data-theme');
var next = cur === 'light' ? 'dark' : 'light';
html.setAttribute('data-theme', next);
var icon = this.querySelector('use');
if (icon) icon.setAttribute('href', next === 'light' ? '#sun' : '#moon');
});
document.getElementById('burgerBtn').addEventListener('click', function () {
var shell = document.querySelector('.kt-ai-shell');
var cur = shell.getAttribute('data-rail');
shell.setAttribute('data-rail', cur === 'true' ? 'false' : 'true');
var icon = this.querySelector('use');
if (icon) icon.setAttribute('href', cur === 'true' ? '#panelLeft' : '#panelLeftClose');
});
document.querySelectorAll('tbody tr').forEach(function (tr) {
tr.addEventListener('click', function () {
var obj = this.getAttribute('data-obj');
var row = CFG.table.rows.find(function (r) { return r.objective === obj; });
if (!row) return;
openDrawer(row);
});
});
}
function openDrawer(row) {
var statusDef = CFG.statuses[row.status] || {};
var overlay = document.createElement('div');
overlay.className = 'kt-ai-overlay';
overlay.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,.4);z-index:1000';
var drawer = document.createElement('aside');
drawer.className = 'kt-ai-drawer kt-ai-card';
drawer.style.cssText = 'position:fixed;top:0;right:0;bottom:0;width:480px;max-width:100vw;z-index:1001;padding:var(--kt-ai-space-8);overflow-y:auto';
var krHtml = '';
if (row._kr) {
row._kr.split(',').forEach(function (kr) {
krHtml += '<span class="kt-ai-chip">' + kr.trim() + '</span>';
});
}
drawer.innerHTML = '<div class="drawer-head"><div class="t"><div style="font-size:var(--kt-ai-text-xl);font-weight:var(--kt-ai-weight-semibold);margin-bottom:var(--kt-ai-space-2)">' + row.objective + '</div><span class="kt-ai-chip' + (getStatusTone(row.status) ? ' data-tone="' + getStatusTone(row.status) + '"' : '') + '">' + statusDef.label + '</span></div><button class="kt-ai-icon-button" id="closeDrawer"><svg class="kt-icon"><use href="#x"></use></svg></button></div>';
drawer.innerHTML += '<div class="drawer-flags"><strong style="font-size:var(--kt-ai-text-sm);color:var(--kt-ai-fg-muted)">Ключевые результаты</strong>' + krHtml + '</div>';
drawer.innerHTML += '<div class="drawer-sec"><div style="display:grid;gap:var(--kt-ai-space-4)">' +
'<div><span style="font-size:var(--kt-ai-text-2xs);color:var(--kt-ai-fg-muted)">Владелец</span><div style="font-size:var(--kt-ai-text-sm);font-weight:var(--kt-ai-weight-medium)">' + row.owner + '</div></div>' +
'<div><span style="font-size:var(--kt-ai-text-2xs);color:var(--kt-ai-fg-muted)">Квартал</span><div style="font-size:var(--kt-ai-text-sm);font-weight:var(--kt-ai-weight-medium)">' + row.quarter + '</div></div>' +
'<div><span style="font-size:var(--kt-ai-text-2xs);color:var(--kt-ai-fg-muted)">Прогресс</span><div class="progress-cell" style="margin-top:var(--kt-ai-space-2)"><div class="progress-bar ' + getProgressTone(row.progress) + '" style="flex:1"><span style="width:' + row.progress + '%"></span></div><span class="progress-pct">' + row.progress + '%</span></div></div>' +
'</div></div>';
drawer.innerHTML += '<div class="drawer-actions"><button class="kt-ai-btn" data-variant="primary">Редактировать</button><button class="kt-ai-btn">Закрыть</button></div>';
document.body.appendChild(overlay);
document.body.appendChild(drawer);
function close() { overlay.remove(); drawer.remove(); }
document.getElementById('closeDrawer').addEventListener('click', close);
overlay.addEventListener('click', close);
drawer.querySelector('.kt-ai-btn:last-child').addEventListener('click', close);
}
render();
})();
</script>
</body>
</html>