455 lines
24 KiB
HTML
455 lines
24 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">
|
||
<title>Business Metrics — Контроль качества</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>
|
||
.kt-ai-app { background: var(--kt-ai-bg); color: var(--kt-ai-fg); font-family: var(--kt-ai-font-sans); min-height: 100vh; }
|
||
.metrics-header { display: flex; align-items: center; gap: var(--kt-ai-space-4); padding: var(--kt-ai-space-4) 0; border-bottom: 1px solid var(--kt-ai-border); background: var(--kt-ai-bg); position: sticky; top: 0; z-index: 10; }
|
||
.metrics-title { font-size: var(--kt-ai-text-xl); font-weight: var(--kt-ai-weight-semibold); }
|
||
.demo-badge { background: var(--kt-ai-chart-orange); color: var(--kt-ai-fg); font-size: var(--kt-ai-text-xs); font-weight: var(--kt-ai-weight-bold); padding: 4px 10px; border-radius: var(--kt-ai-radius-full); text-transform: uppercase; letter-spacing: 0.5px; }
|
||
.filters-row { display: flex; gap: var(--kt-ai-space-3); align-items: center; flex-wrap: wrap; margin-bottom: var(--kt-ai-space-6); }
|
||
.kpi-strip { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: var(--kt-ai-space-4); margin-bottom: var(--kt-ai-space-6); }
|
||
.kpi-card { background: var(--kt-ai-card-bg); border: 1px solid var(--kt-ai-card-border); border-radius: var(--kt-ai-radius-xl); padding: var(--kt-ai-space-5); }
|
||
.kpi-value { font-size: var(--kt-ai-text-3xl); font-weight: var(--kt-ai-weight-bold); font-family: var(--kt-ai-font-mono); color: var(--kt-ai-fg); }
|
||
.kpi-label { font-size: var(--kt-ai-text-sm); color: var(--kt-ai-fg-muted); margin-top: var(--kt-ai-space-1); }
|
||
.kpi-hint { font-size: var(--kt-ai-text-xs); color: var(--kt-ai-fg-faint); margin-top: var(--kt-ai-space-2); }
|
||
.hint-positive { color: var(--kt-ai-chart-green); }
|
||
.hint-negative { color: var(--kt-ai-chart-red); }
|
||
.hint-neutral { color: var(--kt-ai-fg-faint); }
|
||
.chart-wrap { background: var(--kt-ai-card-bg); border: 1px solid var(--kt-ai-card-border); border-radius: var(--kt-ai-radius-xl); padding: var(--kt-ai-space-5); margin-bottom: var(--kt-ai-space-6); }
|
||
.chart-title { font-size: var(--kt-ai-text-lg); font-weight: var(--kt-ai-weight-semibold); margin-bottom: var(--kt-ai-space-4); }
|
||
.chart-container { height: 200px; display: flex; align-items: flex-end; gap: 8px; padding: var(--kt-ai-space-4) 0; }
|
||
.chart-bar { flex: 1; background: var(--kt-ai-chart-blue); border-radius: var(--kt-ai-radius-md); min-width: 24px; position: relative; cursor: pointer; transition: opacity 0.2s; }
|
||
.chart-bar:hover { opacity: 0.8; }
|
||
.chart-bar[data-kt-tip]::after { content: attr(data-kt-tip); position: absolute; bottom: 100%; left: 50%; transform: translateX(-50%); background: var(--kt-ai-fg); color: var(--kt-ai-bg); padding: 4px 8px; border-radius: var(--kt-ai-radius-md); font-size: var(--kt-ai-text-xs); white-space: nowrap; opacity: 0; pointer-events: none; transition: opacity 0.2s; margin-bottom: 4px; }
|
||
.chart-bar:hover::after { opacity: 1; }
|
||
.table-wrap { background: var(--kt-ai-card-bg); border: 1px solid var(--kt-ai-card-border); border-radius: var(--kt-ai-radius-xl); overflow: hidden; margin-bottom: var(--kt-ai-space-6); }
|
||
.kt-ai-table { width: 100%; border-collapse: collapse; }
|
||
.kt-ai-table th { text-align: left; padding: var(--kt-ai-space-3) var(--kt-ai-space-4); font-size: var(--kt-ai-text-sm); font-weight: var(--kt-ai-weight-medium); color: var(--kt-ai-fg-muted); border-bottom: 1px solid var(--kt-ai-border); background: var(--kt-ai-bg-soft); }
|
||
.kt-ai-table td { padding: var(--kt-ai-space-3) var(--kt-ai-space-4); font-size: var(--kt-ai-text-sm); border-bottom: 1px solid var(--kt-ai-border); }
|
||
.kt-ai-table tr:last-child td { border-bottom: none; }
|
||
.kt-ai-table td.num { text-align: right; font-family: var(--kt-ai-font-mono); tabular-nums: 1; }
|
||
.insights-wrap { background: var(--kt-ai-card-bg); border: 1px solid var(--kt-ai-card-border); border-radius: var(--kt-ai-radius-xl); padding: var(--kt-ai-space-5); margin-bottom: var(--kt-ai-space-6); }
|
||
.insights-title { font-size: var(--kt-ai-text-lg); font-weight: var(--kt-ai-weight-semibold); margin-bottom: var(--kt-ai-space-4); }
|
||
.insight-card { background: var(--kt-ai-bg-soft); border-radius: var(--kt-ai-radius-lg); padding: var(--kt-ai-space-4); margin-bottom: var(--kt-ai-space-3); }
|
||
.insight-card:last-child { margin-bottom: 0; }
|
||
.insight-label { font-size: var(--kt-ai-text-xs); font-weight: var(--kt-ai-weight-medium); color: var(--kt-ai-fg-muted); text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: var(--kt-ai-space-2); }
|
||
.insight-text { font-size: var(--kt-ai-text-sm); line-height: 1.5; }
|
||
.recommendation-list { list-style: none; padding: 0; margin: 0; }
|
||
.recommendation-list li { position: relative; padding-left: var(--kt-ai-space-5); margin-bottom: var(--kt-ai-space-2); font-size: var(--kt-ai-text-sm); line-height: 1.5; }
|
||
.recommendation-list li::before { content: "→"; position: absolute; left: 0; color: var(--kt-ai-primary); font-weight: var(--kt-ai-weight-bold); }
|
||
.no-data { text-align: center; padding: var(--kt-ai-space-10); color: var(--kt-ai-fg-muted); font-size: var(--kt-ai-text-lg); }
|
||
</style>
|
||
</head>
|
||
<body class="kt-ai-app">
|
||
|
||
<div class="kt-ai-wrap">
|
||
<header class="metrics-header">
|
||
<img src="https://code.vibe42.kz/tore/aitu/kt-logo.svg" alt="Казахтелеком" height="28">
|
||
<span class="metrics-title">Business Metrics</span>
|
||
<span class="demo-badge">Демонстрационные данные</span>
|
||
<span class="kt-ai-spacer"></span>
|
||
<button class="kt-ai-btn" data-variant="ghost" id="resetBtn">Сбросить фильтры</button>
|
||
</header>
|
||
|
||
<main style="padding: var(--kt-ai-space-6) 0;">
|
||
<div class="filters-row">
|
||
<div class="kt-ai-field">
|
||
<label style="font-size: var(--kt-ai-text-xs); color: var(--kt-ai-fg-muted); display: block; margin-bottom: var(--kt-ai-space-1);">Сегмент</label>
|
||
<select class="kt-ai-select" id="segmentFilter" style="min-width: 140px;">
|
||
<option value="all">Все</option>
|
||
<option value="B2B">B2B</option>
|
||
<option value="B2C">B2C</option>
|
||
</select>
|
||
</div>
|
||
<div class="kt-ai-field">
|
||
<label style="font-size: var(--kt-ai-text-xs); color: var(--kt-ai-fg-muted); display: block; margin-bottom: var(--kt-ai-space-1);">Канал</label>
|
||
<select class="kt-ai-select" id="channelFilter" style="min-width: 160px;">
|
||
<option value="all">Все каналы</option>
|
||
<option value="call-center">Call-центр</option>
|
||
<option value="online">Онлайн-кабинет</option>
|
||
<option value="mobile-app">Мобильное приложение</option>
|
||
<option value="office">Офисы</option>
|
||
</select>
|
||
</div>
|
||
<div class="kt-ai-field">
|
||
<label style="font-size: var(--kt-ai-text-xs); color: var(--kt-ai-fg-muted); display: block; margin-bottom: var(--kt-ai-space-1);">Период</label>
|
||
<select class="kt-ai-select" id="periodFilter" style="min-width: 140px;">
|
||
<option value="7d">7 дней</option>
|
||
<option value="30d">30 дней</option>
|
||
<option value="90d">90 дней</option>
|
||
</select>
|
||
</div>
|
||
<div class="kt-ai-field">
|
||
<label style="font-size: var(--kt-ai-text-xs); color: var(--kt-ai-fg-muted); display: block; margin-bottom: var(--kt-ai-space-1);">Режим</label>
|
||
<select class="kt-ai-select" id="dataModeFilter" style="min-width: 160px;">
|
||
<option value="data">С данными</option>
|
||
<option value="nodata">Нет данных</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="kpi-strip" id="kpiStrip">
|
||
<div class="kpi-card">
|
||
<div class="kpi-value" id="kpiNPS">42</div>
|
||
<div class="kpi-label">NPS</div>
|
||
<div class="kpi-hint hint-positive">+3 за период</div>
|
||
</div>
|
||
<div class="kpi-card">
|
||
<div class="kpi-value" id="kpiCSAT">87%</div>
|
||
<div class="kpi-label">CSAT</div>
|
||
<div class="kpi-hint hint-neutral">Без изменений</div>
|
||
</div>
|
||
<div class="kpi-card">
|
||
<div class="kpi-value" id="kpiFCR">76%</div>
|
||
<div class="kpi-label">FCR</div>
|
||
<div class="kpi-hint hint-negative">−2 за период</div>
|
||
</div>
|
||
<div class="kpi-card">
|
||
<div class="kpi-value" id="kpiAHT">4.2 мин</div>
|
||
<div class="kpi-label">AHT</div>
|
||
<div class="kpi-hint hint-positive">−0.3 мин</div>
|
||
</div>
|
||
<div class="kpi-card">
|
||
<div class="kpi-value" id="kpiResolution">94%</div>
|
||
<div class="kpi-label">Решено с первого раза</div>
|
||
<div class="kpi-hint hint-neutral">Без изменений</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="chart-wrap">
|
||
<div class="chart-title">Динамика NPS по периодам</div>
|
||
<div class="chart-container" id="chartContainer"></div>
|
||
</div>
|
||
|
||
<div class="table-wrap">
|
||
<table class="kt-ai-table">
|
||
<thead>
|
||
<tr>
|
||
<th>Канал</th>
|
||
<th>Сегмент</th>
|
||
<th class="num">NPS</th>
|
||
<th class="num">CSAT</th>
|
||
<th class="num">FCR</th>
|
||
<th class="num">AHT</th>
|
||
<th class="num">Заявок</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="metricsTable"></tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<div class="insights-wrap">
|
||
<div class="insights-title">Анализ отклонений</div>
|
||
<div id="insightsContent">
|
||
<div class="insight-card">
|
||
<div class="insight-label">Гипотеза причин отклонений</div>
|
||
<div class="insight-text" id="explanationText">Загрузка...</div>
|
||
</div>
|
||
<div class="insight-card">
|
||
<div class="insight-label">Рекомендации</div>
|
||
<ul class="recommendation-list" id="recommendationsList"></ul>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="noDataBlock" class="no-data" style="display: none;">
|
||
Недостаточно данных для выбранных фильтров
|
||
</div>
|
||
</main>
|
||
</div>
|
||
|
||
<script src="./design-system/kt-ai-chart-tip.js"></script>
|
||
<script>
|
||
const DEMO_DATA = {
|
||
'7d': {
|
||
metrics: [
|
||
{ channel: 'call-center', segment: 'B2B', nps: 38, csat: 84, fcr: 72, aht: 5.2, requests: 320 },
|
||
{ channel: 'call-center', segment: 'B2C', nps: 45, csat: 89, fcr: 78, aht: 3.8, requests: 890 },
|
||
{ channel: 'online', segment: 'B2B', nps: 52, csat: 92, fcr: 85, aht: 2.1, requests: 240 },
|
||
{ channel: 'online', segment: 'B2C', nps: 48, csat: 88, fcr: 80, aht: 2.5, requests: 560 },
|
||
{ channel: 'mobile-app', segment: 'B2B', nps: 44, csat: 86, fcr: 75, aht: 3.2, requests: 150 },
|
||
{ channel: 'mobile-app', segment: 'B2C', nps: 50, csat: 91, fcr: 82, aht: 2.8, requests: 480 },
|
||
{ channel: 'office', segment: 'B2B', nps: 35, csat: 80, fcr: 68, aht: 6.5, requests: 85 },
|
||
{ channel: 'office', segment: 'B2C', nps: 40, csat: 85, fcr: 74, aht: 4.5, requests: 260 }
|
||
],
|
||
history: [
|
||
{ period: 'Пн', nps: 40 }, { period: 'Вт', nps: 42 }, { period: 'Ср', nps: 39 },
|
||
{ period: 'Чт', nps: 44 }, { period: 'Пт', nps: 43 }, { period: 'Сб', nps: 41 }, { period: 'Вс', nps: 42 }
|
||
],
|
||
insights: {
|
||
B2B: {
|
||
explanation: 'Возможно, снижение FCR в B2B связано с увеличением сложных технических запросов на этой неделе. AHT выше среднего из-за длительной обработки в офисах.',
|
||
recommendations: [
|
||
'Усилить техническую подготовку операторов B2B-направления',
|
||
'Внедрить шаблоны ответов для типовых сложных запросов'
|
||
]
|
||
},
|
||
B2C: {
|
||
explanation: 'Возможно, небольшое снижение NPS в выходные связано с ростом нагрузки в выходные дни.',
|
||
recommendations: [
|
||
'Поддерживать текущий уровень сервиса в онлайн-каналах',
|
||
'Провести работу над ошибками в call-центре для роста FCR'
|
||
]
|
||
},
|
||
all: {
|
||
explanation: 'Общие показатели в пределах нормы. Наблюдается стабильная динамика NPS в течение недели.',
|
||
recommendations: [
|
||
'Продолжить развитие онлайн-каналов обслуживания',
|
||
'Провести обучение операторов call-центра'
|
||
]
|
||
}
|
||
}
|
||
},
|
||
'30d': {
|
||
metrics: [
|
||
{ channel: 'call-center', segment: 'B2B', nps: 40, csat: 85, fcr: 74, aht: 5.0, requests: 1240 },
|
||
{ channel: 'call-center', segment: 'B2C', nps: 47, csat: 90, fcr: 79, aht: 3.6, requests: 3420 },
|
||
{ channel: 'online', segment: 'B2B', nps: 54, csat: 93, fcr: 86, aht: 2.0, requests: 890 },
|
||
{ channel: 'online', segment: 'B2C', nps: 50, csat: 89, fcr: 81, aht: 2.4, requests: 2150 },
|
||
{ channel: 'mobile-app', segment: 'B2B', nps: 46, csat: 87, fcr: 76, aht: 3.0, requests: 560 },
|
||
{ channel: 'mobile-app', segment: 'B2C', nps: 52, csat: 92, fcr: 83, aht: 2.6, requests: 1890 },
|
||
{ channel: 'office', segment: 'B2B', nps: 37, csat: 81, fcr: 70, aht: 6.2, requests: 320 },
|
||
{ channel: 'office', segment: 'B2C', nps: 42, csat: 86, fcr: 75, aht: 4.3, requests: 980 }
|
||
],
|
||
history: [
|
||
{ period: 'Нед 1', nps: 39 }, { period: 'Нед 2', nps: 42 }, { period: 'Нед 3', nps: 44 },
|
||
{ period: 'Нед 4', nps: 43 }
|
||
],
|
||
insights: {
|
||
B2B: {
|
||
explanation: 'Возможно, снижение FCR в B2B-сегменте связано с увеличением сложных технических запросов в этом месяце.',
|
||
recommendations: [
|
||
'Усилить техническую подготовку операторов B2B-направления',
|
||
'Внедрить шаблоны ответов для типовых сложных запросов',
|
||
'Сократить время обработки в офисах через цифровизацию'
|
||
]
|
||
},
|
||
B2C: {
|
||
explanation: 'B2C-сегмент показывает стабильные результаты. Возможно, небольшое снижение NPS связано с сезонным ростом нагрузки.',
|
||
recommendations: [
|
||
'Поддерживать текущий уровень сервиса в онлайн-каналах',
|
||
'Провести работу над ошибками в call-центре'
|
||
]
|
||
},
|
||
all: {
|
||
explanation: 'Общие показатели в пределах нормы. Наблюдается положительная динамика NPS за месяц.',
|
||
recommendations: [
|
||
'Продолжить развитие онлайн-каналов обслуживания',
|
||
'Провести обучение операторов call-центра',
|
||
'Внедрить единую базу знаний для всех каналов'
|
||
]
|
||
}
|
||
}
|
||
},
|
||
'90d': {
|
||
metrics: [
|
||
{ channel: 'call-center', segment: 'B2B', nps: 41, csat: 86, fcr: 75, aht: 4.9, requests: 3800 },
|
||
{ channel: 'call-center', segment: 'B2C', nps: 48, csat: 91, fcr: 80, aht: 3.5, requests: 10200 },
|
||
{ channel: 'online', segment: 'B2B', nps: 55, csat: 94, fcr: 87, aht: 1.9, requests: 2700 },
|
||
{ channel: 'online', segment: 'B2C', nps: 51, csat: 90, fcr: 82, aht: 2.3, requests: 6400 },
|
||
{ channel: 'mobile-app', segment: 'B2B', nps: 47, csat: 88, fcr: 77, aht: 2.9, requests: 1700 },
|
||
{ channel: 'mobile-app', segment: 'B2C', nps: 53, csat: 93, fcr: 84, aht: 2.5, requests: 5600 },
|
||
{ channel: 'office', segment: 'B2B', nps: 38, csat: 82, fcr: 71, aht: 6.0, requests: 950 },
|
||
{ channel: 'office', segment: 'B2C', nps: 43, csat: 87, fcr: 76, aht: 4.2, requests: 2900 }
|
||
],
|
||
history: [
|
||
{ period: 'Июл', nps: 44 }, { period: 'Авг', nps: 46 }, { period: 'Сен', nps: 42 }
|
||
],
|
||
insights: {
|
||
B2B: {
|
||
explanation: 'Возможно, снижение FCR в B2B-сегменте за квартал связано с увеличением объёма сложных технических запросов.',
|
||
recommendations: [
|
||
'Усилить техническую подготовку операторов B2B-направления',
|
||
'Внедрить шаблоны ответов для типовых сложных запросов',
|
||
'Сократить время обработки в офисах через цифровизацию процессов'
|
||
]
|
||
},
|
||
B2C: {
|
||
explanation: 'B2C-сегмент показывает стабильные результаты за квартал. Возможно, снижение NPS в сентябре связано с сезонным фактором.',
|
||
recommendations: [
|
||
'Поддерживать текущий уровень сервиса в онлайн-каналах',
|
||
'Провести работу над ошибками в call-центре для роста FCR'
|
||
]
|
||
},
|
||
all: {
|
||
explanation: 'Общие показатели в пределах нормы. Наблюдается положительная динамика NPS за последние 3 месяца.',
|
||
recommendations: [
|
||
'Продолжить развитие онлайн-каналов обслуживания',
|
||
'Провести обучение операторов call-центра',
|
||
'Внедрить единую базу знаний для всех каналов'
|
||
]
|
||
}
|
||
}
|
||
}
|
||
};
|
||
|
||
let currentFilters = { segment: 'all', channel: 'all', period: '7d', dataMode: 'data' };
|
||
|
||
function loadFilters() {
|
||
const saved = localStorage.getItem('bm_filters');
|
||
if (saved) {
|
||
currentFilters = JSON.parse(saved);
|
||
document.getElementById('segmentFilter').value = currentFilters.segment;
|
||
document.getElementById('channelFilter').value = currentFilters.channel;
|
||
document.getElementById('periodFilter').value = currentFilters.period;
|
||
document.getElementById('dataModeFilter').value = currentFilters.dataMode;
|
||
}
|
||
}
|
||
|
||
function saveFilters() {
|
||
localStorage.setItem('bm_filters', JSON.stringify(currentFilters));
|
||
}
|
||
|
||
function getFilteredData() {
|
||
const periodData = DEMO_DATA[currentFilters.period] || DEMO_DATA['7d'];
|
||
let data = periodData.metrics;
|
||
if (currentFilters.segment !== 'all') {
|
||
data = data.filter(m => m.segment === currentFilters.segment);
|
||
}
|
||
if (currentFilters.channel !== 'all') {
|
||
data = data.filter(m => m.channel === currentFilters.channel);
|
||
}
|
||
return data;
|
||
}
|
||
|
||
function getHistory() {
|
||
const periodData = DEMO_DATA[currentFilters.period] || DEMO_DATA['7d'];
|
||
return periodData.history;
|
||
}
|
||
|
||
function getInsights(segment) {
|
||
const periodData = DEMO_DATA[currentFilters.period] || DEMO_DATA['7d'];
|
||
return periodData.insights[segment] || periodData.insights.all;
|
||
}
|
||
|
||
function calculateAverages(data) {
|
||
if (data.length === 0) return null;
|
||
const sum = data.reduce((acc, m) => ({
|
||
nps: acc.nps + m.nps,
|
||
csat: acc.csat + m.csat,
|
||
fcr: acc.fcr + m.fcr,
|
||
aht: acc.aht + m.aht,
|
||
requests: acc.requests + m.requests
|
||
}), { nps: 0, csat: 0, fcr: 0, aht: 0, requests: 0 });
|
||
return {
|
||
nps: Math.round(sum.nps / data.length),
|
||
csat: Math.round(sum.csat / data.length),
|
||
fcr: Math.round(sum.fcr / data.length),
|
||
aht: (sum.aht / data.length).toFixed(1),
|
||
requests: sum.requests
|
||
};
|
||
}
|
||
|
||
function renderKPIs(avgs) {
|
||
if (!avgs) {
|
||
document.getElementById('kpiStrip').style.display = 'none';
|
||
return;
|
||
}
|
||
document.getElementById('kpiStrip').style.display = 'grid';
|
||
document.getElementById('kpiNPS').textContent = avgs.nps;
|
||
document.getElementById('kpiCSAT').textContent = avgs.csat + '%';
|
||
document.getElementById('kpiFCR').textContent = avgs.fcr + '%';
|
||
document.getElementById('kpiAHT').textContent = avgs.aht + ' мин';
|
||
document.getElementById('kpiResolution').textContent = avgs.fcr + '%';
|
||
}
|
||
|
||
function renderChart() {
|
||
const container = document.getElementById('chartContainer');
|
||
const history = getHistory();
|
||
if (!history || history.length === 0) {
|
||
container.innerHTML = '<div class="no-data" style="padding: var(--kt-ai-space-10);">Недостаточно данных</div>';
|
||
return;
|
||
}
|
||
const maxNPS = Math.max(...history.map(h => h.nps));
|
||
const colors = ['var(--kt-ai-chart-blue)', 'var(--kt-ai-chart-green)', 'var(--kt-ai-chart-orange)', 'var(--kt-ai-chart-pink)', 'var(--kt-ai-chart-red)', 'var(--kt-ai-chart-salmon)'];
|
||
container.innerHTML = history.map((h, i) => {
|
||
const height = (h.nps / maxNPS) * 160;
|
||
return `<div class="chart-bar" style="height: ${height}px; background: ${colors[i % colors.length]}" data-kt-tip="${h.period}: ${h.nps}"></div>`;
|
||
}).join('');
|
||
}
|
||
|
||
function renderTable(data) {
|
||
const tbody = document.getElementById('metricsTable');
|
||
if (data.length === 0) {
|
||
tbody.innerHTML = '<tr><td colspan="7" class="num" style="text-align: center; padding: var(--kt-ai-space-6);">Нет данных</td></tr>';
|
||
return;
|
||
}
|
||
tbody.innerHTML = data.map(m => `
|
||
<tr>
|
||
<td>${m.channel === 'call-center' ? 'Call-центр' : m.channel === 'online' ? 'Онлайн-кабинет' : m.channel === 'mobile-app' ? 'Мобильное приложение' : 'Офисы'}</td>
|
||
<td>${m.segment}</td>
|
||
<td class="num">${m.nps}</td>
|
||
<td class="num">${m.csat}%</td>
|
||
<td class="num">${m.fcr}%</td>
|
||
<td class="num">${m.aht}</td>
|
||
<td class="num">${m.requests}</td>
|
||
</tr>
|
||
`).join('');
|
||
}
|
||
|
||
function renderInsights(segment) {
|
||
if (currentFilters.dataMode === 'nodata') {
|
||
document.getElementById('insightsContent').style.display = 'none';
|
||
return;
|
||
}
|
||
document.getElementById('insightsContent').style.display = 'block';
|
||
const insight = getInsights(segment);
|
||
document.getElementById('explanationText').textContent = insight.explanation;
|
||
document.getElementById('recommendationsList').innerHTML = insight.recommendations.map(r => `<li>${r}</li>`).join('');
|
||
}
|
||
|
||
function render() {
|
||
const noData = currentFilters.dataMode === 'nodata';
|
||
document.getElementById('kpiStrip').style.display = noData ? 'none' : 'grid';
|
||
document.getElementById('noDataBlock').style.display = noData ? 'block' : 'none';
|
||
document.getElementById('metricsTable').closest('.table-wrap').style.display = noData ? 'none' : 'block';
|
||
if (noData) {
|
||
renderChart();
|
||
renderInsights(null);
|
||
return;
|
||
}
|
||
const data = getFilteredData();
|
||
const avgs = calculateAverages(data);
|
||
renderKPIs(avgs);
|
||
renderChart();
|
||
renderTable(data);
|
||
renderInsights(currentFilters.segment);
|
||
}
|
||
|
||
function onFilterChange() {
|
||
currentFilters = {
|
||
segment: document.getElementById('segmentFilter').value,
|
||
channel: document.getElementById('channelFilter').value,
|
||
period: document.getElementById('periodFilter').value,
|
||
dataMode: document.getElementById('dataModeFilter').value
|
||
};
|
||
saveFilters();
|
||
render();
|
||
}
|
||
|
||
document.getElementById('segmentFilter').addEventListener('change', onFilterChange);
|
||
document.getElementById('channelFilter').addEventListener('change', onFilterChange);
|
||
document.getElementById('periodFilter').addEventListener('change', onFilterChange);
|
||
document.getElementById('resetBtn').addEventListener('click', () => {
|
||
currentFilters = { segment: 'all', channel: 'all', period: '7d', dataMode: 'data' };
|
||
document.getElementById('segmentFilter').value = 'all';
|
||
document.getElementById('channelFilter').value = 'all';
|
||
document.getElementById('periodFilter').value = '7d';
|
||
document.getElementById('dataModeFilter').value = 'data';
|
||
saveFilters();
|
||
render();
|
||
});
|
||
|
||
loadFilters();
|
||
render();
|
||
</script>
|
||
</body>
|
||
</html>
|