345 lines
23 KiB
HTML
345 lines
23 KiB
HTML
<!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>
|
||
<link rel="stylesheet" href="./design-system/kt-ai-fonts.css">
|
||
<link rel="stylesheet" href="./design-system/kt-ai-tokens.css">
|
||
<link rel="stylesheet" href="./design-system/kt-ai-components.css">
|
||
<link rel="stylesheet" href="./design-system/kt-ai-page.css">
|
||
<style>
|
||
.page-hero { margin-bottom: var(--kt-ai-stack-group); }
|
||
.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; }
|
||
.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); }
|
||
</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>
|
||
|
||
<div id="shell-root"></div>
|
||
|
||
<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="kt-ai-sidebar-header"><div class="kt-ai-sidebar-brand"><svg class="kt-icon" style="width:22px;height:22px"><use href="#trophy"></use></svg><span class="kt-ai-sidebar-brand-name">OKR Дашборд</span></div></div>';
|
||
html += '<nav class="kt-ai-sidebar-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="kt-ai-sidebar-footer">';
|
||
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></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 kt-ai-topbar-burger" id="burgerBtn"><svg class="kt-icon"><use href="#panelLeft"></use></svg></button>';
|
||
html += '<div class="kt-ai-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="kt-ai-page-head"><h1>Дашборд исполнения OKR</h1><p class="kt-ai-page-subtitle">Отслеживайте прогресс по целям и ключевым результатам компании</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="kt-ai-chip-count">' + 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, '"') + '">';
|
||
html += '<td><div><div class="kt-ai-table-cell-title">' + r.objective + '</div>' + (r._kr ? '<div class="kt-ai-table-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="kt-ai-table-cell-actions"><button class="kt-ai-icon-button"><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 htmlEl = document.documentElement;
|
||
var cur = htmlEl.getAttribute('data-theme');
|
||
var next = cur === 'light' ? 'dark' : 'light';
|
||
htmlEl.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="kt-ai-drawer-header"><div class="kt-ai-drawer-title"><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="kt-ai-drawer-section"><strong class="kt-ai-drawer-section-label">Ключевые результаты</strong>' + krHtml + '</div>';
|
||
drawer.innerHTML += '<div class="kt-ai-drawer-section"><div style="display:grid;gap:var(--kt-ai-space-4)">' +
|
||
'<div><span class="kt-ai-form-label">Владелец</span><div style="font-size:var(--kt-ai-text-sm);font-weight:var(--kt-ai-weight-medium)">' + row.owner + '</div></div>' +
|
||
'<div><span class="kt-ai-form-label">Квартал</span><div style="font-size:var(--kt-ai-text-sm);font-weight:var(--kt-ai-weight-medium)">' + row.quarter + '</div></div>' +
|
||
'<div><span class="kt-ai-form-label">Прогресс</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="kt-ai-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>
|