eppk-form/dashboard.html

326 lines
14 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">
<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>