v2 - dashboard for tracking submissions + reminders

This commit is contained in:
Dauren777 2026-06-19 05:38:32 +00:00
parent e035bd72ce
commit ac2246a784

325
dashboard.html Normal file
View File

@ -0,0 +1,325 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Дашборд сбора данных ЕППК</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700;800&display=swap" rel="stylesheet">
<style>
:root{--ink:#0F1218;--cyan:#00E5FF;--cyan-50:#E8FCFF;--white:#fff;--gray-500:#5B6573;--gray-100:#F2F4F7;--green:#10B981;--red:#EF4444;--yellow:#F59E0B}
*{box-sizing:border-box;margin:0;padding:0}
body{font-family:'Inter',sans-serif;font-size:15px;line-height:1.5;color:var(--ink);background:var(--gray-100)}
.header{background:var(--ink);color:var(--white);padding:24px 32px}
.header h1{font-size:24px;font-weight:800}
.header p{color:#9aa3b2;font-size:14px}
.container{max-width:1200px;margin:0 auto;padding:32px 24px}
.tabs{display:flex;gap:8px;margin-bottom:24px}
.tab{padding:10px 20px;border-radius:8px;font-weight:600;cursor:pointer;border:none;background:var(--white);color:var(--ink)}
.tab.active{background:var(--cyan);color:var(--ink)}
.stats{display:grid;grid-template-columns:repeat(4,1fr);gap:16px;margin-bottom:32px}
.stat-card{background:var(--white);border-radius:12px;padding:20px}
.stat-card .label{font-size:13px;color:var(--gray-500);margin-bottom:4px}
.stat-card .value{font-size:28px;font-weight:800}
.stat-card .value.green{color:var(--green)}
.stat-card .value.yellow{color:var(--yellow)}
.stat-card .value.red{color:var(--red)}
.card{background:var(--white);border-radius:12px;padding:24px;margin-bottom:16px}
.card h2{font-size:18px;font-weight:700;margin-bottom:16px}
table{width:100%;border-collapse:collapse}
th,td{text-align:left;padding:12px 16px;border-bottom:1px solid var(--gray-100)}
th{font-weight:600;color:var(--gray-500);font-size:13px;text-transform:uppercase}
tr:hover{background:var(--cyan-50)}
.status{display:inline-block;padding:4px 12px;border-radius:20px;font-size:12px;font-weight:600}
.status.done{background:#D1FAE5;color:#065F46}
.status.pending{background:#FEF3C7;color:#92400E}
.status.overdue{background:#FEE2E2;color:#991B1B}
.btn{display:inline-block;padding:8px 16px;border-radius:6px;font-weight:600;font-size:13px;text-decoration:none;cursor:pointer;border:none}
.btn-remind{background:var(--cyan);color:var(--ink)}
.btn-remind:hover{background:#1be5ff}
.btn-download{background:var(--gray-100);color:var(--ink)}
.filter-bar{display:flex;gap:12px;margin-bottom:16px;flex-wrap:wrap}
.filter-bar input,.filter-bar select{padding:8px 12px;border:2px solid var(--gray-100);border-radius:6px;font-size:14px}
.hidden{display:none}
@media(max-width:768px){
.stats{grid-template-columns:repeat(2,1fr)}
table{font-size:13px}
th,td{padding:8px 12px}
}
</style>
</head>
<body>
<div class="header">
<h1>Дашборд сбора данных ЕППК</h1>
<p>Отслеживание сдачи отчётов от филиалов Казахтелеком</p>
</div>
<div class="container">
<div class="tabs">
<button class="tab active" onclick="showTab('budget')">Бюджет (ежемесячно)</button>
<button class="tab" onclick="showTab('eppk')">ЕППК (годовой)</button>
</div>
<!-- Бюджет -->
<div id="tab-budget">
<div class="stats">
<div class="stat-card">
<div class="label">Всего филиалов</div>
<div class="value" id="total-branches">21</div>
</div>
<div class="stat-card">
<div class="label">Сдали отчёт</div>
<div class="value green" id="submitted-count">0</div>
</div>
<div class="stat-card">
<div class="label">Ожидают</div>
<div class="value yellow" id="pending-count">0</div>
</div>
<div class="stat-card">
<div class="label">Просрочено</div>
<div class="value red" id="overdue-count">0</div>
</div>
</div>
<div class="card">
<h2>Список филиалов — Бюджет обучений и командировок</h2>
<div class="filter-bar">
<input type="text" id="search-budget" placeholder="Поиск филиала..." oninput="filterTable('budget')">
<select id="status-filter-budget" onchange="filterTable('budget')">
<option value="all">Все статусы</option>
<option value="done">Сдали</option>
<option value="pending">Ожидают</option>
<option value="overdue">Просрочено</option>
</select>
<button class="btn btn-remind" onclick="remindAll('budget')">Напомнить всем</button>
<button class="btn btn-download" onclick="downloadTemplate()">Скачать шаблон Excel</button>
</div>
<table id="table-budget">
<thead>
<tr>
<th>Филиал</th>
<th>Статус</th>
<th>Дата сдачи</th>
<th>Период</th>
<th>Действия</th>
</tr>
</thead>
<tbody id="tbody-budget"></tbody>
</table>
</div>
</div>
<!-- ЕППК -->
<div id="tab-eppk" class="hidden">
<div class="stats">
<div class="stat-card">
<div class="label">Всего филиалов</div>
<div class="value">21</div>
</div>
<div class="stat-card">
<div class="label">Сдали ЕППК</div>
<div class="value green" id="eppk-submitted">0</div>
</div>
<div class="stat-card">
<div class="label">Ожидают</div>
<div class="value yellow" id="eppk-pending">0</div>
</div>
<div class="stat-card">
<div class="label">Просрочено</div>
<div class="value red" id="eppk-overdue">0</div>
</div>
</div>
<div class="card">
<h2>ЕППК — Единый план повышения квалификации</h2>
<div class="filter-bar">
<input type="text" id="search-eppk" placeholder="Поиск филиала..." oninput="filterTable('eppk')">
<select id="status-filter-eppk" onchange="filterTable('eppk')">
<option value="all">Все статусы</option>
<option value="done">Сдали</option>
<option value="pending">Ожидают</option>
<option value="overdue">Просрочено</option>
</select>
<button class="btn btn-remind" onclick="remindAll('eppk')">Напомнить всем</button>
</div>
<table id="table-eppk">
<thead>
<tr>
<th>Филиал</th>
<th>Статус</th>
<th>Дата сдачи</th>
<th>Год</th>
<th>Действия</th>
</tr>
</thead>
<tbody id="tbody-eppk"></tbody>
</table>
</div>
</div>
</div>
<script>
const branches = [
"Астана","Алматы","Шымкент","Атырау","Актобе","Караганда","Павлодар",
"Петропавловск","Костанай","Туркестан","Усть-Каменогорск","Уральск",
"Семей","Кызылорда","Тараз","Жезказган","Экибастуз","Темиртау",
"Балхаш","Турксиб","ЦА (Центральный аппарат)"
];
const emails = {
"Астана":"astana@kaztelecom.kz",
"Алматы":"almaty@kaztelecom.kz",
"Шымкент":"shymkent@kaztelecom.kz",
"Атырау":"atyrau@kaztelecom.kz",
"Актобе":"aktobe@kaztelecom.kz",
"Караганда":"karaganda@kaztelecom.kz",
"Павлодар":"pavlodar@kaztelecom.kz",
"Петропавловск":"petropavlovsk@kaztelecom.kz",
"Костанай":"kostanay@kaztelecom.kz",
"Туркестан":"turkestan@kaztelecom.kz",
"Усть-Каменогорск":"uskamenogorsk@kaztelecom.kz",
"Уральск":"uralsk@kaztelecom.kz",
"Семей":"semey@kaztelecom.kz",
"Кызылорда":"kyzylorda@kaztelecom.kz",
"Тараз":"taraz@kaztelecom.kz",
"Жезказган":"jezkazgan@kaztelecom.kz",
"Экибастуз":"ekibastuz@kaztelecom.kz",
"Темиртау":"temirtau@kaztelecom.kz",
"Балхаш":"balhash@kaztelecom.kz",
"Турксиб":"turksib@kaztelecom.kz",
"ЦА (Центральный аппарат)":"ca@kaztelecom.kz"
};
let data = JSON.parse(localStorage.getItem('eppk-data')) || {};
function saveData() {
localStorage.setItem('eppk-data', JSON.stringify(data));
}
function getStatus(branch, type) {
const key = `${branch}-${type}`;
if (data[key]) return data[key];
return { status: 'pending', date: '' };
}
function setStatus(branch, type, status, date) {
const key = `${branch}-${type}`;
data[key] = { status, date };
saveData();
}
function renderTable(type) {
const tbody = document.getElementById(`tbody-${type}`);
tbody.innerHTML = '';
branches.forEach(branch => {
const info = getStatus(branch, type);
const statusClass = info.status;
const statusText = info.status === 'done' ? 'Сдал' : info.status === 'overdue' ? 'Просрочено' : 'Ожидает';
const period = type === 'budget' ? 'Текущий месяц' : '2026';
const row = document.createElement('tr');
row.setAttribute('data-status', info.status);
row.setAttribute('data-branch', branch.toLowerCase());
row.innerHTML = `
<td><strong>${branch}</strong></td>
<td><span class="status ${statusClass}">${statusText}</span></td>
<td>${info.date || '—'}</td>
<td>${period}</td>
<td>
<button class="btn btn-remind" onclick="sendReminder('${branch}','${type}')" style="margin-right:6px">Напомнить</button>
<button class="btn btn-download" onclick="markDone('${branch}','${type}')">Сдал ✓</button>
</td>
`;
tbody.appendChild(row);
});
updateStats(type);
}
function updateStats(type) {
let done = 0, pending = 0, overdue = 0;
branches.forEach(branch => {
const info = getStatus(branch, type);
if (info.status === 'done') done++;
else if (info.status === 'overdue') overdue++;
else pending++;
});
if (type === 'budget') {
document.getElementById('submitted-count').textContent = done;
document.getElementById('pending-count').textContent = pending;
document.getElementById('overdue-count').textContent = overdue;
} else {
document.getElementById('eppk-submitted').textContent = done;
document.getElementById('eppk-pending').textContent = pending;
document.getElementById('eppk-overdue').textContent = overdue;
}
}
function filterTable(type) {
const search = document.getElementById(`search-${type}`).value.toLowerCase();
const statusFilter = document.getElementById(`status-filter-${type}`).value;
const rows = document.querySelectorAll(`#tbody-${type} tr`);
rows.forEach(row => {
const branch = row.getAttribute('data-branch');
const status = row.getAttribute('data-status');
const matchSearch = branch.includes(search);
const matchStatus = statusFilter === 'all' || status === statusFilter;
row.style.display = matchSearch && matchStatus ? '' : 'none';
});
}
function sendReminder(branch, type) {
const email = emails[branch] || '';
const subject = type === 'budget'
? 'Напоминание: сдача ежемесячного отчёта по бюджету обучений'
: 'Напоминание: сдача ЕППК — Единый план повышения квалификации';
const body = type === 'budget'
? `Уважаемые коллеги!\n\nНапоминаем о необходимости сдачи ежемесячного отчёта по бюджету обучений и командировочных расходов.\n\nПожалуйста, заполните форму: https://pages.git.vibe42.kz/1ospanovaroza/eppk-form/\n\nС уважением,\nЦентральный аппарат`
: `Уважаемые коллеги!\n\nНапоминаем о необходимости сдачи ЕППК — Единого плана повышения квалификации.\n\nПожалуйста, заполните форму: https://pages.git.vibe42.kz/1ospanovaroza/eppk-form/\n\nС уважением,\nЦентральный аппарат`;
window.open(`mailto:${email}?subject=${encodeURIComponent(subject)}&body=${encodeURIComponent(body)}`);
}
function remindAll(type) {
let emails_list = branches.map(b => emails[b]).filter(e => e).join(',');
const subject = type === 'budget'
? 'Напоминание: сдача ежемесячного отчёта по бюджету обучений'
: 'Напоминание: сдача ЕППК';
window.open(`mailto:?bcc=${emails_list}&subject=${encodeURIComponent(subject)}&body=${encodeURIComponent('Уважаемые коллеги!\n\nПросим сдать отчёт в срок.\n\nФорма: https://pages.git.vibe42.kz/1ospanovaroza/eppk-form/')}`);
}
function markDone(branch, type) {
const today = new Date().toLocaleDateString('ru-RU');
setStatus(branch, type, 'done', today);
renderTable(type);
}
function showTab(tab) {
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
event.target.classList.add('active');
document.getElementById('tab-budget').classList.toggle('hidden', tab !== 'budget');
document.getElementById('tab-eppk').classList.toggle('hidden', tab !== 'eppk');
}
function downloadTemplate() {
const headers = ['Филиал','Бюджет обучений (тенге)','Бюджет командировок (тенге)','Запланировано обучений','Прошло обучений','Комментарий'];
let csv = '\uFEFF' + headers.join(';') + '\n';
branches.forEach(b => {
csv += `${b};;;;;;;;;\n`;
});
const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
const link = document.createElement('a');
link.href = URL.createObjectURL(blob);
link.download = `shablon_eppk_${new Date().toISOString().slice(0,7)}.csv`;
link.click();
}
renderTable('budget');
renderTable('eppk');
</script>
</body>
</html>