Обновить index.html
This commit is contained in:
parent
1244c52e41
commit
89011a6e28
741
index.html
741
index.html
@ -1,180 +1,603 @@
|
|||||||
<!doctype html>
|
<!DOCTYPE html>
|
||||||
<html lang="ru" data-theme="dark">
|
<html lang="ru">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>Дашборд функций рекрутинга — telecom.quest</title>
|
<title>Дашборд Текучести Персонала 2025</title>
|
||||||
<link rel="stylesheet" href="./styles.css">
|
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
|
||||||
<script src="https://cdn.sheetjs.com/xlsx-0.20.3/package/dist/xlsx.full.min.js"></script>
|
<style>
|
||||||
<script src="https://cdn.plot.ly/plotly-2.35.2.min.js"></script>
|
:root {
|
||||||
|
--navy: #1B2A4A;
|
||||||
|
--navy2: #243358;
|
||||||
|
--red: #C0392B;
|
||||||
|
--white: #FFFFFF;
|
||||||
|
--bg: #F0F4F9;
|
||||||
|
--card: #FFFFFF;
|
||||||
|
--text: #1E293B;
|
||||||
|
--muted: #64748B;
|
||||||
|
--border: #E2E8F0;
|
||||||
|
--teal: #0D9488;
|
||||||
|
--purple: #7C3AED;
|
||||||
|
--orange: #D97706;
|
||||||
|
--green: #059669;
|
||||||
|
}
|
||||||
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||||
|
body {
|
||||||
|
font-family: 'Segoe UI', Arial, sans-serif;
|
||||||
|
background: var(--bg);
|
||||||
|
color: var(--text);
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Header ── */
|
||||||
|
header {
|
||||||
|
background: var(--navy);
|
||||||
|
padding: 0 32px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
height: 64px;
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 100;
|
||||||
|
border-bottom: 4px solid var(--red);
|
||||||
|
box-shadow: 0 2px 16px rgba(0,0,0,.25);
|
||||||
|
}
|
||||||
|
header h1 {
|
||||||
|
color: #fff;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: .5px;
|
||||||
|
}
|
||||||
|
header .period {
|
||||||
|
color: #94A8CC;
|
||||||
|
font-size: 13px;
|
||||||
|
background: rgba(255,255,255,.07);
|
||||||
|
border: 1px solid rgba(255,255,255,.12);
|
||||||
|
padding: 5px 14px;
|
||||||
|
border-radius: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Layout ── */
|
||||||
|
main { padding: 28px 32px 40px; max-width: 1440px; margin: 0 auto; }
|
||||||
|
|
||||||
|
/* ── KPI Strip ── */
|
||||||
|
.kpi-row {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(5, 1fr);
|
||||||
|
gap: 16px;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
.kpi {
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 20px 18px 16px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: 0 2px 8px rgba(0,0,0,.10);
|
||||||
|
transition: transform .15s, box-shadow .15s;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
.kpi:hover { transform: translateY(-3px); box-shadow: 0 6px 20px rgba(0,0,0,.15); }
|
||||||
|
.kpi::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0; left: 0; right: 0;
|
||||||
|
height: 4px;
|
||||||
|
background: rgba(255,255,255,.35);
|
||||||
|
border-radius: 12px 12px 0 0;
|
||||||
|
}
|
||||||
|
.kpi-label { font-size: 10px; font-weight: 700; letter-spacing: 1.2px; text-transform: uppercase; opacity: .75; color: #fff; }
|
||||||
|
.kpi-value { font-size: 36px; font-weight: 800; color: #fff; line-height: 1.1; }
|
||||||
|
.kpi-sub { font-size: 12px; color: rgba(255,255,255,.65); margin-top: 2px; }
|
||||||
|
.kpi-icon { position: absolute; right: 14px; top: 14px; font-size: 28px; opacity: .2; }
|
||||||
|
|
||||||
|
.kpi-total { background: linear-gradient(135deg, #1B2A4A, #2D4070); }
|
||||||
|
.kpi-vol { background: linear-gradient(135deg, #B83232, #E74C3C); }
|
||||||
|
.kpi-agr { background: linear-gradient(135deg, #243358, #1A5276); }
|
||||||
|
.kpi-dept { background: linear-gradient(135deg, #065F46, #059669); }
|
||||||
|
.kpi-peak { background: linear-gradient(135deg, #6C3483, #8E44AD); }
|
||||||
|
|
||||||
|
/* ── Section ── */
|
||||||
|
.section-title {
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: 1.4px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: var(--muted);
|
||||||
|
margin: 28px 0 14px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
.section-title::after {
|
||||||
|
content: '';
|
||||||
|
flex: 1;
|
||||||
|
height: 1px;
|
||||||
|
background: var(--border);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Grid ── */
|
||||||
|
.grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; }
|
||||||
|
.grid-3 { display: grid; grid-template-columns: 2fr 1fr 1fr; gap: 20px; }
|
||||||
|
.grid-4 { display: grid; grid-template-columns: repeat(4,1fr); gap: 20px; }
|
||||||
|
.col-span2{ grid-column: span 2; }
|
||||||
|
|
||||||
|
/* ── Card ── */
|
||||||
|
.card {
|
||||||
|
background: var(--card);
|
||||||
|
border-radius: 14px;
|
||||||
|
padding: 22px 24px 18px;
|
||||||
|
box-shadow: 0 1px 6px rgba(0,0,0,.07);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
transition: box-shadow .15s;
|
||||||
|
}
|
||||||
|
.card:hover { box-shadow: 0 4px 18px rgba(0,0,0,.10); }
|
||||||
|
.card-title {
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--navy);
|
||||||
|
margin-bottom: 16px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
.card-title .dot {
|
||||||
|
width: 8px; height: 8px;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
.chart-wrap { position: relative; }
|
||||||
|
.chart-wrap canvas { max-height: 280px; }
|
||||||
|
|
||||||
|
/* ── Table ── */
|
||||||
|
table { width: 100%; border-collapse: collapse; font-size: 13px; }
|
||||||
|
thead th {
|
||||||
|
background: var(--navy);
|
||||||
|
color: #fff;
|
||||||
|
padding: 9px 12px;
|
||||||
|
text-align: left;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 600;
|
||||||
|
letter-spacing: .5px;
|
||||||
|
}
|
||||||
|
thead th:first-child { border-radius: 8px 0 0 0; }
|
||||||
|
thead th:last-child { border-radius: 0 8px 0 0; }
|
||||||
|
tbody tr { border-bottom: 1px solid var(--border); transition: background .1s; }
|
||||||
|
tbody tr:hover { background: #F8FAFF; }
|
||||||
|
tbody td { padding: 9px 12px; color: var(--text); }
|
||||||
|
tbody tr:last-child td:first-child { border-radius: 0 0 0 8px; }
|
||||||
|
tbody tr:last-child td:last-child { border-radius: 0 0 8px 0; }
|
||||||
|
|
||||||
|
/* Progress bar in table */
|
||||||
|
.bar-wrap { display: flex; align-items: center; gap: 8px; }
|
||||||
|
.bar-track { flex: 1; height: 7px; background: var(--border); border-radius: 4px; min-width: 60px; }
|
||||||
|
.bar-fill { height: 7px; border-radius: 4px; }
|
||||||
|
.bar-pct { font-size: 11px; color: var(--muted); width: 38px; text-align: right; }
|
||||||
|
|
||||||
|
/* Badge */
|
||||||
|
.badge {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 2px 9px;
|
||||||
|
border-radius: 20px;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
.badge-blue { background: #EEF2FF; color: #3730A3; }
|
||||||
|
.badge-red { background: #FEF2F2; color: #B91C1C; }
|
||||||
|
.badge-green { background: #F0FDF4; color: #15803D; }
|
||||||
|
.badge-grey { background: #F1F5F9; color: #475569; }
|
||||||
|
|
||||||
|
/* ── Gender donut label ── */
|
||||||
|
.gender-wrap { display: flex; flex-direction: column; gap: 12px; }
|
||||||
|
.gender-legend { display: flex; flex-direction: column; gap: 8px; margin-top: 8px; }
|
||||||
|
.gl-item { display: flex; align-items: center; gap: 10px; font-size: 13px; }
|
||||||
|
.gl-dot { width: 12px; height: 12px; border-radius: 50%; flex-shrink: 0; }
|
||||||
|
.gl-label { color: var(--text); flex: 1; }
|
||||||
|
.gl-val { font-weight: 700; color: var(--navy); }
|
||||||
|
.gl-pct { color: var(--muted); font-size: 12px; }
|
||||||
|
|
||||||
|
/* ── Footer ── */
|
||||||
|
footer {
|
||||||
|
text-align: center;
|
||||||
|
padding: 20px;
|
||||||
|
color: var(--muted);
|
||||||
|
font-size: 12px;
|
||||||
|
border-top: 1px solid var(--border);
|
||||||
|
margin-top: 36px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Responsive ── */
|
||||||
|
@media (max-width: 1100px) {
|
||||||
|
.kpi-row { grid-template-columns: repeat(3,1fr); }
|
||||||
|
.grid-3 { grid-template-columns: 1fr 1fr; }
|
||||||
|
.grid-4 { grid-template-columns: repeat(2,1fr); }
|
||||||
|
.col-span2{ grid-column: span 1; }
|
||||||
|
}
|
||||||
|
@media (max-width: 700px) {
|
||||||
|
main { padding: 16px; }
|
||||||
|
.kpi-row { grid-template-columns: repeat(2,1fr); }
|
||||||
|
.grid-2, .grid-3, .grid-4 { grid-template-columns: 1fr; }
|
||||||
|
header h1 { font-size: 14px; }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<header>
|
|
||||||
<img src="./assets/logo.svg" alt="telecom.quest" class="logo">
|
|
||||||
<h1>Дашборд функций рекрутинга</h1>
|
|
||||||
<button id="theme-toggle" aria-label="Тема">🌙</button>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<section class="toolbar">
|
<header>
|
||||||
<label class="btn-primary">
|
<h1>📊 Дашборд Текучести Персонала</h1>
|
||||||
📁 Загрузить .xlsx
|
<span class="period">Январь — Ноябрь 2025 · 11 месяцев</span>
|
||||||
<input type="file" id="file-input" accept=".xlsx" hidden>
|
</header>
|
||||||
</label>
|
|
||||||
<select id="group-filter" multiple size="3" hidden></select>
|
|
||||||
<input type="search" id="search-input" placeholder="🔍 Поиск функции..." hidden>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="hero">
|
<main>
|
||||||
<div class="hero-text">
|
|
||||||
<h2>Аналитика рекрутинга</h2>
|
|
||||||
<p>Время функций → KPI, нагрузка, узкие места, симулятор «что если». Без сервера: данные парсятся прямо в браузере.</p>
|
|
||||||
</div>
|
|
||||||
<img src="./assets/hero.svg" alt="" class="hero-img" loading="lazy">
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<main id="dashboard" hidden>
|
<!-- KPI -->
|
||||||
<section class="block">
|
<div class="kpi-row">
|
||||||
<h2>Базовые показатели</h2>
|
<div class="kpi kpi-total">
|
||||||
<div id="kpi" class="kpi-grid"></div>
|
<span class="kpi-icon">👥</span>
|
||||||
</section>
|
<span class="kpi-label">Всего уволено</span>
|
||||||
|
<span class="kpi-value">705</span>
|
||||||
<section class="block">
|
<span class="kpi-sub">за 11 месяцев 2025</span>
|
||||||
<h2>📊 Стратегические KPI</h2>
|
|
||||||
<div class="inputs">
|
|
||||||
<label>Вакансий/мес <input type="number" id="inp-vacancies" value="15" min="0"></label>
|
|
||||||
<label>Ставка ₸/мес <input type="number" id="inp-salary" value="500000" min="0" step="10000"></label>
|
|
||||||
<label title="Время функций, помеченных 🏢, умножается на это число">
|
|
||||||
Структурных подразделений (СП)
|
|
||||||
<input type="number" id="inp-units" value="1" min="1" step="1">
|
|
||||||
</label>
|
|
||||||
</div>
|
</div>
|
||||||
<div id="strategic-kpi" class="kpi-grid"></div>
|
<div class="kpi kpi-vol">
|
||||||
</section>
|
<span class="kpi-icon">🚪</span>
|
||||||
|
<span class="kpi-label">По собств. желанию</span>
|
||||||
<section class="block with-mascot">
|
<span class="kpi-value">348</span>
|
||||||
<img src="./assets/recruiter.svg" alt="" class="mascot" loading="lazy">
|
<span class="kpi-sub">49% от общего</span>
|
||||||
<div class="block-body">
|
|
||||||
<h2>👤 Нагрузка на сотрудника (дни и недели)</h2>
|
|
||||||
<div id="workload-kpi" class="kpi-grid"></div>
|
|
||||||
</div>
|
</div>
|
||||||
</section>
|
<div class="kpi kpi-agr">
|
||||||
|
<span class="kpi-icon">🤝</span>
|
||||||
<section class="block with-mascot mascot-right">
|
<span class="kpi-label">По соглашению сторон</span>
|
||||||
<img src="./assets/candidate.svg" alt="" class="mascot" loading="lazy">
|
<span class="kpi-value">302</span>
|
||||||
<div class="block-body">
|
<span class="kpi-sub">43% от общего</span>
|
||||||
<h2>✏️ Ручные правки времени (без изменения xlsx)</h2>
|
|
||||||
<p class="hint">Заменить минуты конкретной функции по №. Влияет на все KPI и графики. Пусто = убрать правку.</p>
|
|
||||||
<div class="ovr-form">
|
|
||||||
<label>№ функции <input type="number" id="ovr-num" min="1" placeholder="например 39"></label>
|
|
||||||
<label>Время, мин <input type="number" id="ovr-min" min="0" placeholder="например 60"></label>
|
|
||||||
<button type="button" id="ovr-apply">Применить</button>
|
|
||||||
<button type="button" id="ovr-clear" class="secondary">Очистить все</button>
|
|
||||||
</div>
|
</div>
|
||||||
<div id="ovr-list" class="ovr-list"></div>
|
<div class="kpi kpi-dept">
|
||||||
|
<span class="kpi-icon">🏢</span>
|
||||||
|
<span class="kpi-label">Топ направление</span>
|
||||||
|
<span class="kpi-value">ПРОДАЖИ</span>
|
||||||
|
<span class="kpi-sub">343 чел · 49%</span>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
<div class="kpi kpi-peak">
|
||||||
|
<span class="kpi-icon">📈</span>
|
||||||
<section class="block with-mascot">
|
<span class="kpi-label">Пиковый месяц</span>
|
||||||
<img src="./assets/thinker.svg" alt="" class="mascot" loading="lazy">
|
<span class="kpi-value">Сен</span>
|
||||||
<div class="block-body">
|
<span class="kpi-sub">86 увольнений</span>
|
||||||
<h2>🧪 Симулятор «что если»</h2>
|
|
||||||
<p class="hint">Не меняет исходные данные. Покажет, как изменятся KPI при заданных условиях.</p>
|
|
||||||
<label class="sim-toggle"><input type="checkbox" id="sim-toggle"> Включить симулятор</label>
|
|
||||||
|
|
||||||
<div id="sim-controls" class="sim-controls" hidden>
|
|
||||||
<div class="sim-row">
|
|
||||||
<label>Оптимизация всех функций: <span id="sim-optimize-val">0%</span>
|
|
||||||
<input type="range" id="sim-optimize" min="0" max="80" step="5" value="0">
|
|
||||||
</label>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="sim-row">
|
|
||||||
<label>Исключить топ-N длинных функций: <span id="sim-exclude-top-val">0</span>
|
|
||||||
<input type="range" id="sim-exclude-top" min="0" max="10" step="1" value="0">
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div class="sim-row">
|
|
||||||
<label>Исключить этап (авто-классификатор):
|
|
||||||
<select id="sim-stage"></select>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div class="sim-row">
|
|
||||||
<label>Рекрутеров (FTE): <span id="sim-recruiters-val">1</span>
|
|
||||||
<input type="range" id="sim-recruiters" min="1" max="10" step="1" value="1">
|
|
||||||
</label>
|
|
||||||
<label>Вакансий/мес: <span id="sim-vacancies-val">15</span>
|
|
||||||
<input type="range" id="sim-vacancies" min="0" max="50" step="1" value="15">
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<button id="sim-reset" type="button">↺ Сбросить</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="sim-output" hidden></div>
|
<!-- Row 1: Monthly dynamics -->
|
||||||
|
<div class="section-title">Динамика по месяцам</div>
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-title"><span class="dot" style="background:var(--navy)"></span>Количество увольнений по месяцам · 2025</div>
|
||||||
|
<div class="chart-wrap"><canvas id="chartMonthly"></canvas></div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="block">
|
<!-- Row 2: Reasons + Departments -->
|
||||||
<h2>📈 Сценарий: FTE % vs число вакансий</h2>
|
<div class="section-title">Причины увольнений и направления</div>
|
||||||
<p class="hint">Зелёный — норма (≤100%), жёлтый — перегруз ≤150%, красный — критично. Пунктир — 100% FTE.</p>
|
<div class="grid-2">
|
||||||
<div id="chart-fte-scenario"></div>
|
<div class="card">
|
||||||
</section>
|
<div class="card-title"><span class="dot" style="background:var(--red)"></span>Причины увольнений</div>
|
||||||
|
<table>
|
||||||
<section class="block">
|
<thead><tr><th>#</th><th>Причина</th><th>Кол-во</th><th>Доля</th></tr></thead>
|
||||||
<h2>Топ-10 самых долгих функций</h2>
|
<tbody id="tbReasons"></tbody>
|
||||||
<div id="chart-top10"></div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="block">
|
|
||||||
<h2>📉 Pareto: накопительный % времени</h2>
|
|
||||||
<p class="hint">Какие функции дают 80% всей нагрузки. Жёлтая линия — порог 80%.</p>
|
|
||||||
<div id="chart-pareto"></div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="charts-row">
|
|
||||||
<div class="block">
|
|
||||||
<h2>📊 Гистограмма длительности</h2>
|
|
||||||
<div id="chart-histogram"></div>
|
|
||||||
</div>
|
|
||||||
<div class="block">
|
|
||||||
<h2>⏱ Категории функций</h2>
|
|
||||||
<div id="chart-duration"></div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="block">
|
|
||||||
<h2>🧭 Авто-этапы (по ключевым словам в названии)</h2>
|
|
||||||
<p class="hint">Эвристика — не правит данные. Привлечение / Скрининг / Интервью / Оффер / Адаптация / Документы-СПП / Коммуникация.</p>
|
|
||||||
<div id="chart-stages"></div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="block">
|
|
||||||
<h2>🗺 Treemap: группа → функция (площадь = время)</h2>
|
|
||||||
<div id="chart-treemap"></div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="block with-mascot mascot-right mascot-wide">
|
|
||||||
<img src="./assets/team.svg" alt="" class="mascot" loading="lazy">
|
|
||||||
<div class="block-body">
|
|
||||||
<h2>Все функции <span class="hint">(клик по заголовку — сортировка)</span></h2>
|
|
||||||
<table id="table">
|
|
||||||
<thead><tr>
|
|
||||||
<th data-col="num">№</th>
|
|
||||||
<th data-col="name">Функция</th>
|
|
||||||
<th data-col="rawTime">Исх. время</th>
|
|
||||||
<th data-col="minutes">Мин</th>
|
|
||||||
<th data-col="stage">Этап</th>
|
|
||||||
<th data-col="group">Группа</th>
|
|
||||||
</tr></thead>
|
|
||||||
<tbody></tbody>
|
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
<div class="card">
|
||||||
</main>
|
<div class="card-title"><span class="dot" style="background:var(--teal)"></span>По направлениям</div>
|
||||||
|
<div class="chart-wrap"><canvas id="chartDept"></canvas></div>
|
||||||
<div id="placeholder">
|
</div>
|
||||||
<img src="./assets/empty.svg" alt="" loading="lazy">
|
|
||||||
<p>Загрузка данных… Если дашборд не появился — нажмите «📁 Загрузить .xlsx».</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="./app.js?v=11"></script>
|
<!-- Row 3: Region + Gender + Grade -->
|
||||||
|
<div class="section-title">Регионы · Гендер · Грейды</div>
|
||||||
|
<div class="grid-3">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-title"><span class="dot" style="background:var(--orange)"></span>По регионам</div>
|
||||||
|
<div class="chart-wrap"><canvas id="chartRegion"></canvas></div>
|
||||||
|
</div>
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-title"><span class="dot" style="background:var(--purple)"></span>Гендерный состав</div>
|
||||||
|
<div class="gender-wrap">
|
||||||
|
<div class="chart-wrap"><canvas id="chartGender" style="max-height:180px"></canvas></div>
|
||||||
|
<div class="gender-legend">
|
||||||
|
<div class="gl-item">
|
||||||
|
<span class="gl-dot" style="background:#1B2A4A"></span>
|
||||||
|
<span class="gl-label">Женщины</span>
|
||||||
|
<span class="gl-val">363</span>
|
||||||
|
<span class="gl-pct">51.5%</span>
|
||||||
|
</div>
|
||||||
|
<div class="gl-item">
|
||||||
|
<span class="gl-dot" style="background:#C0392B"></span>
|
||||||
|
<span class="gl-label">Мужчины</span>
|
||||||
|
<span class="gl-val">341</span>
|
||||||
|
<span class="gl-pct">48.4%</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-title"><span class="dot" style="background:var(--green)"></span>По грейдам (топ-8)</div>
|
||||||
|
<div class="chart-wrap"><canvas id="chartGrade"></canvas></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Row 4: Top positions -->
|
||||||
|
<div class="section-title">Топ должностей по увольнениям</div>
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-title"><span class="dot" style="background:var(--purple)"></span>Топ-10 должностей</div>
|
||||||
|
<table>
|
||||||
|
<thead><tr><th>#</th><th>Должность</th><th>Кол-во</th><th>Доля от общего</th></tr></thead>
|
||||||
|
<tbody id="tbPositions"></tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<footer>Данные: Текучесть персонала за 11 месяцев 2025 | Всего записей: 705 | 01.01.2025 – 29.11.2025</footer>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// ── Data ────────────────────────────────────────────────────────────────────
|
||||||
|
const DATA = {
|
||||||
|
monthly: {
|
||||||
|
labels: ['Янв','Фев','Мар','Апр','Май','Июн','Июл','Авг','Сен','Окт','Ноя'],
|
||||||
|
values: [55, 57, 46, 71, 65, 72, 77, 75, 86, 67, 33]
|
||||||
|
},
|
||||||
|
reasons: [
|
||||||
|
{ label: 'По собственному желанию', count: 348 },
|
||||||
|
{ label: 'По соглашению сторон', count: 302 },
|
||||||
|
{ label: 'Уход на пенсию', count: 27 },
|
||||||
|
{ label: 'Преждевременный уход на пенсию', count: 12 },
|
||||||
|
{ label: 'Истечение срока договора', count: 8 },
|
||||||
|
{ label: 'Смерть сотрудника', count: 3 },
|
||||||
|
{ label: 'Нарушение труд. дисципл.', count: 2 },
|
||||||
|
{ label: 'Перевод в филиал / Сокращение', count: 2 },
|
||||||
|
],
|
||||||
|
depts: {
|
||||||
|
labels: ['ПРОДАЖИ','РиПС','Customer\nservice','ФИНАНСЫ','МАРКЕТИНГ','ЕХ','РУКОВОДСТВО'],
|
||||||
|
values: [343, 169, 138, 40, 12, 2, 1]
|
||||||
|
},
|
||||||
|
regions: {
|
||||||
|
labels: ['АЛМАТЫ','ЮГ','ЦЕНТР','ЗАПАД','ВОСТОК','СЕВЕР'],
|
||||||
|
values: [535, 60, 57, 23, 21, 9]
|
||||||
|
},
|
||||||
|
grades: {
|
||||||
|
labels: ['B1','A10','A9','B2','B3','B4','B5','B6'],
|
||||||
|
values: [207, 191, 119, 108, 28, 16, 13, 10]
|
||||||
|
},
|
||||||
|
gender: { labels: ['Женщины','Мужчины'], values: [363, 341] },
|
||||||
|
positions: [
|
||||||
|
'Специалист по обслуживанию',
|
||||||
|
'Хантер',
|
||||||
|
'Инсталлятор',
|
||||||
|
'Техник по работе с абонентами',
|
||||||
|
'Специалист',
|
||||||
|
'Вед. специалист по обслуживанию',
|
||||||
|
'Электромонтер Р4',
|
||||||
|
'Электромонтер Р7',
|
||||||
|
'Инженер',
|
||||||
|
'Главный специалист',
|
||||||
|
],
|
||||||
|
positionCounts: [193, 102, 77, 58, 49, 28, 21, 18, 14, 11]
|
||||||
|
};
|
||||||
|
|
||||||
|
const TOTAL = 705;
|
||||||
|
const NAVY = '#1B2A4A';
|
||||||
|
const RED = '#C0392B';
|
||||||
|
const TEAL = '#0D9488';
|
||||||
|
const ORANGE = '#D97706';
|
||||||
|
const PURPLE = '#7C3AED';
|
||||||
|
const GREEN = '#059669';
|
||||||
|
const BLUE = '#2563EB';
|
||||||
|
|
||||||
|
Chart.defaults.font.family = "'Segoe UI', Arial, sans-serif";
|
||||||
|
Chart.defaults.color = '#475569';
|
||||||
|
|
||||||
|
// ── Helpers ─────────────────────────────────────────────────────────────────
|
||||||
|
function pct(n) { return (n / TOTAL * 100).toFixed(1) + '%'; }
|
||||||
|
|
||||||
|
function makeBar(el, labels, values, color, opts = {}) {
|
||||||
|
return new Chart(el, {
|
||||||
|
type: 'bar',
|
||||||
|
data: {
|
||||||
|
labels,
|
||||||
|
datasets: [{ data: values, backgroundColor: color,
|
||||||
|
borderRadius: opts.horizontal ? 4 : 6,
|
||||||
|
borderSkipped: false }]
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
indexAxis: opts.horizontal ? 'y' : 'x',
|
||||||
|
responsive: true, maintainAspectRatio: true,
|
||||||
|
plugins: {
|
||||||
|
legend: { display: false },
|
||||||
|
tooltip: { callbacks: {
|
||||||
|
label: ctx => ` ${ctx.parsed[opts.horizontal ? 'x' : 'y']} чел. (${pct(ctx.parsed[opts.horizontal ? 'x' : 'y'])})`
|
||||||
|
}}
|
||||||
|
},
|
||||||
|
scales: {
|
||||||
|
x: { grid: { color: '#E2E8F0' }, ticks: { font: { size: 11 } } },
|
||||||
|
y: { grid: { color: opts.horizontal ? '#E2E8F0' : 'transparent' },
|
||||||
|
ticks: { font: { size: 11 } } }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Monthly chart ────────────────────────────────────────────────────────────
|
||||||
|
new Chart(document.getElementById('chartMonthly'), {
|
||||||
|
type: 'bar',
|
||||||
|
data: {
|
||||||
|
labels: DATA.monthly.labels,
|
||||||
|
datasets: [{
|
||||||
|
label: 'Уволено',
|
||||||
|
data: DATA.monthly.values,
|
||||||
|
backgroundColor: DATA.monthly.values.map((v, i) =>
|
||||||
|
v === Math.max(...DATA.monthly.values) ? RED : NAVY),
|
||||||
|
borderRadius: 6,
|
||||||
|
borderSkipped: false,
|
||||||
|
}, {
|
||||||
|
type: 'line',
|
||||||
|
label: 'Тренд',
|
||||||
|
data: DATA.monthly.values,
|
||||||
|
borderColor: ORANGE,
|
||||||
|
backgroundColor: 'transparent',
|
||||||
|
pointBackgroundColor: ORANGE,
|
||||||
|
pointRadius: 4,
|
||||||
|
tension: 0.35,
|
||||||
|
borderWidth: 2,
|
||||||
|
borderDash: [6, 4],
|
||||||
|
pointHoverRadius: 6,
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
responsive: true, maintainAspectRatio: false,
|
||||||
|
plugins: {
|
||||||
|
legend: { position: 'top', labels: { boxWidth: 14, font: { size: 12 } } },
|
||||||
|
tooltip: { callbacks: {
|
||||||
|
label: ctx => ` ${ctx.parsed.y} чел.`
|
||||||
|
}}
|
||||||
|
},
|
||||||
|
scales: {
|
||||||
|
x: { grid: { display: false } },
|
||||||
|
y: { grid: { color: '#E2E8F0' },
|
||||||
|
ticks: { font: { size: 11 } } }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
document.querySelector('#chartMonthly').parentElement.style.height = '260px';
|
||||||
|
|
||||||
|
// ── Dept chart ────────────────────────────────────────────────────────────
|
||||||
|
makeBar(
|
||||||
|
document.getElementById('chartDept'),
|
||||||
|
DATA.depts.labels,
|
||||||
|
DATA.depts.values,
|
||||||
|
[NAVY, TEAL, BLUE, ORANGE, PURPLE, RED, GREEN],
|
||||||
|
{ horizontal: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
// ── Region chart ────────────────────────────────────────────────────────────
|
||||||
|
new Chart(document.getElementById('chartRegion'), {
|
||||||
|
type: 'bar',
|
||||||
|
data: {
|
||||||
|
labels: DATA.regions.labels,
|
||||||
|
datasets: [{
|
||||||
|
data: DATA.regions.values,
|
||||||
|
backgroundColor: [NAVY, TEAL, BLUE, ORANGE, PURPLE, RED],
|
||||||
|
borderRadius: 6,
|
||||||
|
borderSkipped: false,
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
responsive: true, maintainAspectRatio: true,
|
||||||
|
plugins: {
|
||||||
|
legend: { display: false },
|
||||||
|
tooltip: { callbacks: {
|
||||||
|
label: ctx => ` ${ctx.parsed.y} чел. (${pct(ctx.parsed.y)})`
|
||||||
|
}}
|
||||||
|
},
|
||||||
|
scales: {
|
||||||
|
x: { grid: { display: false }, ticks: { font: { size: 11 } } },
|
||||||
|
y: { grid: { color: '#E2E8F0' }, ticks: { font: { size: 11 } } }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// ── Gender donut ─────────────────────────────────────────────────────────────
|
||||||
|
new Chart(document.getElementById('chartGender'), {
|
||||||
|
type: 'doughnut',
|
||||||
|
data: {
|
||||||
|
labels: DATA.gender.labels,
|
||||||
|
datasets: [{
|
||||||
|
data: DATA.gender.values,
|
||||||
|
backgroundColor: [NAVY, RED],
|
||||||
|
borderWidth: 3,
|
||||||
|
borderColor: '#fff',
|
||||||
|
hoverOffset: 8
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
responsive: true, maintainAspectRatio: true,
|
||||||
|
cutout: '60%',
|
||||||
|
plugins: {
|
||||||
|
legend: { display: false },
|
||||||
|
tooltip: { callbacks: {
|
||||||
|
label: ctx => ` ${ctx.parsed} чел. (${pct(ctx.parsed)})`
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// ── Grade chart ────────────────────────────────────────────────────────────
|
||||||
|
new Chart(document.getElementById('chartGrade'), {
|
||||||
|
type: 'bar',
|
||||||
|
data: {
|
||||||
|
labels: DATA.grades.labels,
|
||||||
|
datasets: [{
|
||||||
|
data: DATA.grades.values,
|
||||||
|
backgroundColor: [NAVY,RED,TEAL,BLUE,ORANGE,PURPLE,GREEN,'#94A3B8'],
|
||||||
|
borderRadius: 5,
|
||||||
|
borderSkipped: false,
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
indexAxis: 'y',
|
||||||
|
responsive: true, maintainAspectRatio: true,
|
||||||
|
plugins: {
|
||||||
|
legend: { display: false },
|
||||||
|
tooltip: { callbacks: {
|
||||||
|
label: ctx => ` ${ctx.parsed.x} чел. (${pct(ctx.parsed.x)})`
|
||||||
|
}}
|
||||||
|
},
|
||||||
|
scales: {
|
||||||
|
x: { grid: { color: '#E2E8F0' }, ticks: { font: { size: 11 } } },
|
||||||
|
y: { grid: { display: false }, ticks: { font: { size: 12 } } }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// ── Reasons table ────────────────────────────────────────────────────────────
|
||||||
|
const reasonColors = [RED, NAVY, TEAL, ORANGE, BLUE, '#6B7280', '#6B7280', '#6B7280'];
|
||||||
|
const tbody = document.getElementById('tbReasons');
|
||||||
|
DATA.reasons.forEach((r, i) => {
|
||||||
|
const p = (r.count / TOTAL * 100).toFixed(1);
|
||||||
|
const tr = document.createElement('tr');
|
||||||
|
tr.innerHTML = `
|
||||||
|
<td style="color:var(--muted);font-weight:700;width:28px">${i+1}</td>
|
||||||
|
<td>${r.label}</td>
|
||||||
|
<td style="font-weight:700;color:${reasonColors[i]};white-space:nowrap">${r.count}</td>
|
||||||
|
<td>
|
||||||
|
<div class="bar-wrap">
|
||||||
|
<div class="bar-track">
|
||||||
|
<div class="bar-fill" style="width:${p}%;background:${reasonColors[i]}"></div>
|
||||||
|
</div>
|
||||||
|
<span class="bar-pct">${p}%</span>
|
||||||
|
</div>
|
||||||
|
</td>`;
|
||||||
|
tbody.appendChild(tr);
|
||||||
|
});
|
||||||
|
|
||||||
|
// ── Positions table ───────────────────────────────────────────────────────────
|
||||||
|
const posColors = [RED, NAVY, TEAL, ORANGE, BLUE, PURPLE, GREEN, '#0891B2', '#B45309', '#6B7280'];
|
||||||
|
const tbody2 = document.getElementById('tbPositions');
|
||||||
|
DATA.positions.forEach((pos, i) => {
|
||||||
|
const cnt = DATA.positionCounts[i];
|
||||||
|
const p = (cnt / TOTAL * 100).toFixed(1);
|
||||||
|
const tr = document.createElement('tr');
|
||||||
|
tr.innerHTML = `
|
||||||
|
<td style="color:var(--muted);font-weight:700;width:28px">${i+1}</td>
|
||||||
|
<td>${pos}</td>
|
||||||
|
<td style="font-weight:700;color:${posColors[i]};white-space:nowrap">${cnt}</td>
|
||||||
|
<td>
|
||||||
|
<div class="bar-wrap">
|
||||||
|
<div class="bar-track">
|
||||||
|
<div class="bar-fill" style="width:${(p/27.4*100)}%;background:${posColors[i]}"></div>
|
||||||
|
</div>
|
||||||
|
<span class="bar-pct">${p}%</span>
|
||||||
|
</div>
|
||||||
|
</td>`;
|
||||||
|
tbody2.appendChild(tr);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
Reference in New Issue
Block a user