Compare commits

..

No commits in common. "b8639bf03f6371945544daf658ade2893e0b16ec" and "02273a773f623ce6357a65a5daf903c948b4657a" have entirely different histories.

2 changed files with 2 additions and 512 deletions

View File

@ -1,489 +0,0 @@
<!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:1400px;margin:0 auto;padding:32px 24px}
.stats{display:grid;grid-template-columns:repeat(5,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);font-size:14px}
th{font-weight:600;color:var(--gray-500);font-size:12px;text-transform:uppercase}
tr:hover{background:var(--cyan-50)}
.btn{display:inline-block;padding:8px 16px;border-radius:6px;font-weight:600;font-size:13px;text-decoration:none;cursor:pointer;border:none}
.btn-primary{background:var(--cyan);color:var(--ink)}
.btn-primary:hover{background:#1be5ff}
.btn-success{background:var(--green);color:var(--white)}
.btn-download{background:var(--gray-100);color:var(--ink)}
.filter-bar{display:flex;gap:12px;margin-bottom:16px;flex-wrap:wrap;align-items:center}
.filter-bar input,.filter-bar select{padding:8px 12px;border:2px solid var(--gray-100);border-radius:6px;font-size:14px}
.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)}
.progress-bar{height:8px;background:var(--gray-100);border-radius:4px;overflow:hidden;margin-top:8px}
.progress-fill{height:100%;border-radius:4px;transition:width 0.3s}
.progress-fill.good{background:var(--green)}
.progress-fill.warning{background:var(--yellow)}
.progress-fill.danger{background:var(--red)}
.text-right{text-align:right}
.text-center{text-align:center}
.total-row{background:var(--cyan-50);font-weight:700}
.hidden{display:none}
@media(max-width:1024px){
.stats{grid-template-columns:repeat(2,1fr)}
}
@media(max-width:640px){
.stats{grid-template-columns:1fr}
table{font-size:12px}
th,td{padding:8px}
}
</style>
</head>
<body>
<div class="header">
<h1>Дашборд исполнения бюджета</h1>
<p>Мониторинг бюджета по статьям — филиалы Казахтелеком</p>
</div>
<div class="container">
<div class="tabs">
<button class="tab active" onclick="showTab('summary')">Сводная таблица</button>
<button class="tab" onclick="showTab('by-branch')">По филиалам</button>
<button class="tab" onclick="showTab('by-article')">По статьям</button>
</div>
<!-- Сводная таблица -->
<div id="tab-summary">
<div class="stats">
<div class="stat-card">
<div class="label">Общий план</div>
<div class="value" id="total-plan">0</div>
</div>
<div class="stat-card">
<div class="label">Общий факт</div>
<div class="value" id="total-fact">0</div>
</div>
<div class="stat-card">
<div class="label">Выполнение</div>
<div class="value green" id="total-percent">0%</div>
</div>
<div class="stat-card">
<div class="label">Остаток</div>
<div class="value yellow" id="total-remain">0</div>
</div>
<div class="stat-card">
<div class="label">Филиалов сдано</div>
<div class="value green" id="submitted-branches">0</div>
</div>
</div>
<div class="card">
<div class="filter-bar">
<select id="month-filter" onchange="filterData()">
<option value="all">Все месяцы</option>
<option value="Январь">Январь</option>
<option value="Февраль">Февраль</option>
<option value="Март">Март</option>
<option value="Апрель">Апрель</option>
<option value="Май">Май</option>
<option value="Июнь">Июнь</option>
<option value="Июль">Июль</option>
<option value="Август">Август</option>
<option value="Сентябрь">Сентябрь</option>
<option value="Октябрь">Октябрь</option>
<option value="Ноябрь">Ноябрь</option>
<option value="Декабрь">Декабрь</option>
</select>
<select id="article-filter" onchange="filterData()">
<option value="all">Все статьи</option>
<option value="2.4.5">2.4.5 - Командировочные расходы</option>
<option value="2.4.6">2.4.6 - Подготовка кадров</option>
<option value="4.1.6">4.1.6 - Командировочные расходы</option>
<option value="4.1.7">4.1.7 - Подготовка кадров</option>
</select>
<button class="btn btn-download" onclick="exportToExcel()">Скачать Excel</button>
<button class="btn btn-primary" onclick="addTestData()">Добавить тестовые данные</button>
</div>
<table id="summary-table">
<thead>
<tr>
<th>Статья</th>
<th class="text-right">План (тенге)</th>
<th class="text-right">Факт (тенге)</th>
<th class="text-right">Нарастающий итог</th>
<th class="text-center">Выполнение</th>
<th>Прогресс</th>
</tr>
</thead>
<tbody id="summary-body"></tbody>
<tfoot>
<tr class="total-row">
<td><strong>ИТОГО</strong></td>
<td class="text-right" id="foot-plan">0</td>
<td class="text-right" id="foot-fact">0</td>
<td class="text-right" id="foot-cumulative">0</td>
<td class="text-center" id="foot-percent">0%</td>
<td></td>
</tr>
</tfoot>
</table>
</div>
</div>
<!-- По филиалам -->
<div id="tab-by-branch" class="hidden">
<div class="card">
<div class="filter-bar">
<input type="text" id="search-branch" placeholder="Поиск филиала..." oninput="filterBranchTable()">
<button class="btn btn-download" onclick="exportBranchesToExcel()">Скачать Excel</button>
</div>
<table id="branch-table">
<thead>
<tr>
<th>Филиал</th>
<th class="text-right">План (тенге)</th>
<th class="text-right">Факт (тенге)</th>
<th class="text-center">Выполнение</th>
<th>Прогресс</th>
<th>Статус</th>
</tr>
</thead>
<tbody id="branch-body"></tbody>
</table>
</div>
</div>
<!-- По статьям -->
<div id="tab-by-article" class="hidden">
<div class="card">
<div class="filter-bar">
<select id="article-detail-filter" onchange="filterArticleTable()">
<option value="all">Все статьи</option>
<option value="2.4.5">2.4.5 - Командировочные расходы</option>
<option value="2.4.6">2.4.6 - Подготовка кадров</option>
<option value="4.1.6">4.1.6 - Командировочные расходы</option>
<option value="4.1.7">4.1.7 - Подготовка кадров</option>
</select>
<button class="btn btn-download" onclick="exportArticlesToExcel()">Скачать Excel</button>
</div>
<table id="article-table">
<thead>
<tr>
<th>Статья</th>
<th>Филиал</th>
<th class="text-right">План (тенге)</th>
<th class="text-right">Факт (тенге)</th>
<th class="text-right">Нарастающий итог</th>
<th class="text-center">Выполнение</th>
</tr>
</thead>
<tbody id="article-body"></tbody>
</table>
</div>
</div>
</div>
<script>
const articles = [
{code: '2.4.5', name: '2.4.5 - Командировочные расходы по подготовке кадров и повышению квалификации кадров'},
{code: '2.4.6', name: '2.4.6 - Подготовка и повышение квалификации кадров'},
{code: '4.1.6', name: '4.1.6 - Командировочные расходы по подготовке кадров и повышению квалификации кадров'},
{code: '4.1.7', name: '4.1.7 - Подготовка и повышение квалификации кадров'}
];
const branches = [
"Астана","Алматы","Шымкент","Атырау","Актобе","Караганда","Павлодар",
"Петропавловск","Костанай","Туркестан","Усть-Каменогорск","Уральск",
"Семей","Кызылорда","Тараз","Жезказган","Экибастуз","Темиртау",
"Балхаш","Турксиб","ЦА (Центральный аппарат)"
];
let budgetData = JSON.parse(localStorage.getItem('budget-data')) || [];
function saveData() {
localStorage.setItem('budget-data', JSON.stringify(budgetData));
}
function addTestData() {
budgetData = [];
branches.forEach(branch => {
articles.forEach(article => {
const plan = Math.floor(Math.random() * 5000000) + 1000000;
const fact = Math.floor(plan * (0.5 + Math.random() * 0.6));
budgetData.push({
branch: branch,
month: 'Июнь',
year: '2026',
article: article.name,
articleCode: article.code,
plan: plan,
fact: fact,
cumulative: fact
});
});
});
saveData();
renderAll();
}
function renderAll() {
renderSummary();
renderBranches();
renderArticles();
updateStats();
}
function updateStats() {
let totalPlan = 0, totalFact = 0;
budgetData.forEach(item => {
totalPlan += item.plan;
totalFact += item.fact;
});
const percent = totalPlan > 0 ? Math.round((totalFact / totalPlan) * 100) : 0;
const remain = totalPlan - totalFact;
document.getElementById('total-plan').textContent = totalPlan.toLocaleString('ru-RU');
document.getElementById('total-fact').textContent = totalFact.toLocaleString('ru-RU');
document.getElementById('total-percent').textContent = percent + '%';
document.getElementById('total-remain').textContent = remain.toLocaleString('ru-RU');
const uniqueBranches = [...new Set(budgetData.map(d => d.branch))];
document.getElementById('submitted-branches').textContent = uniqueBranches.length;
}
function renderSummary() {
const monthFilter = document.getElementById('month-filter').value;
const articleFilter = document.getElementById('article-filter').value;
let filtered = budgetData;
if (monthFilter !== 'all') filtered = filtered.filter(d => d.month === monthFilter);
if (articleFilter !== 'all') filtered = filtered.filter(d => d.articleCode === articleFilter);
const summary = {};
filtered.forEach(item => {
if (!summary[item.articleCode]) {
summary[item.articleCode] = {article: item.article, plan: 0, fact: 0, cumulative: 0};
}
summary[item.articleCode].plan += item.plan;
summary[item.articleCode].fact += item.fact;
summary[item.articleCode].cumulative += item.cumulative;
});
const tbody = document.getElementById('summary-body');
tbody.innerHTML = '';
let totalPlan = 0, totalFact = 0, totalCum = 0;
Object.values(summary).forEach(item => {
const percent = item.plan > 0 ? Math.round((item.fact / item.plan) * 100) : 0;
const progressClass = percent >= 90 ? 'good' : percent >= 70 ? 'warning' : 'danger';
totalPlan += item.plan;
totalFact += item.fact;
totalCum += item.cumulative;
const row = document.createElement('tr');
row.innerHTML = `
<td>${item.article}</td>
<td class="text-right">${item.plan.toLocaleString('ru-RU')}</td>
<td class="text-right">${item.fact.toLocaleString('ru-RU')}</td>
<td class="text-right">${item.cumulative.toLocaleString('ru-RU')}</td>
<td class="text-center">${percent}%</td>
<td><div class="progress-bar"><div class="progress-fill ${progressClass}" style="width:${percent}%"></div></div></td>
`;
tbody.appendChild(row);
});
const totalPercent = totalPlan > 0 ? Math.round((totalFact / totalPlan) * 100) : 0;
document.getElementById('foot-plan').textContent = totalPlan.toLocaleString('ru-RU');
document.getElementById('foot-fact').textContent = totalFact.toLocaleString('ru-RU');
document.getElementById('foot-cumulative').textContent = totalCum.toLocaleString('ru-RU');
document.getElementById('foot-percent').textContent = totalPercent + '%';
}
function renderBranches() {
const summary = {};
budgetData.forEach(item => {
if (!summary[item.branch]) {
summary[item.branch] = {plan: 0, fact: 0};
}
summary[item.branch].plan += item.plan;
summary[item.branch].fact += item.fact;
});
const tbody = document.getElementById('branch-body');
tbody.innerHTML = '';
Object.entries(summary).forEach(([branch, data]) => {
const percent = data.plan > 0 ? Math.round((data.fact / data.plan) * 100) : 0;
const progressClass = percent >= 90 ? 'good' : percent >= 70 ? 'warning' : 'danger';
const status = percent >= 90 ? 'В норме' : percent >= 70 ? 'Внимание' : 'Критично';
const statusClass = percent >= 90 ? 'good' : percent >= 70 ? 'warning' : 'danger';
const row = document.createElement('tr');
row.innerHTML = `
<td><strong>${branch}</strong></td>
<td class="text-right">${data.plan.toLocaleString('ru-RU')}</td>
<td class="text-right">${data.fact.toLocaleString('ru-RU')}</td>
<td class="text-center">${percent}%</td>
<td><div class="progress-bar"><div class="progress-fill ${progressClass}" style="width:${percent}%"></div></div></td>
<td><span class="btn btn-${statusClass}" style="font-size:12px">${status}</span></td>
`;
tbody.appendChild(row);
});
}
function renderArticles() {
const filter = document.getElementById('article-detail-filter').value;
let filtered = budgetData;
if (filter !== 'all') filtered = filtered.filter(d => d.articleCode === filter);
const tbody = document.getElementById('article-body');
tbody.innerHTML = '';
filtered.forEach(item => {
const percent = item.plan > 0 ? Math.round((item.fact / item.plan) * 100) : 0;
const row = document.createElement('tr');
row.innerHTML = `
<td>${item.article}</td>
<td>${item.branch}</td>
<td class="text-right">${item.plan.toLocaleString('ru-RU')}</td>
<td class="text-right">${item.fact.toLocaleString('ru-RU')}</td>
<td class="text-right">${item.cumulative.toLocaleString('ru-RU')}</td>
<td class="text-center">${percent}%</td>
`;
tbody.appendChild(row);
});
}
function filterData() {
renderSummary();
}
function filterBranchTable() {
const search = document.getElementById('search-branch').value.toLowerCase();
document.querySelectorAll('#branch-body tr').forEach(row => {
const text = row.textContent.toLowerCase();
row.style.display = text.includes(search) ? '' : 'none';
});
}
function filterArticleTable() {
renderArticles();
}
function showTab(tab) {
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
event.target.classList.add('active');
document.getElementById('tab-summary').classList.toggle('hidden', tab !== 'summary');
document.getElementById('tab-by-branch').classList.toggle('hidden', tab !== 'by-branch');
document.getElementById('tab-by-article').classList.toggle('hidden', tab !== 'by-article');
}
function exportToExcel() {
let csv = '\uFEFF';
csv += 'Статья;План;Факт;Нарастающий итог;Выполнение (%)\n';
const monthFilter = document.getElementById('month-filter').value;
const articleFilter = document.getElementById('article-filter').value;
let filtered = budgetData;
if (monthFilter !== 'all') filtered = filtered.filter(d => d.month === monthFilter);
if (articleFilter !== 'all') filtered = filtered.filter(d => d.articleCode === articleFilter);
const summary = {};
filtered.forEach(item => {
if (!summary[item.articleCode]) {
summary[item.articleCode] = {article: item.article, plan: 0, fact: 0, cumulative: 0};
}
summary[item.articleCode].plan += item.plan;
summary[item.articleCode].fact += item.fact;
summary[item.articleCode].cumulative += item.cumulative;
});
let totalPlan = 0, totalFact = 0;
Object.values(summary).forEach(item => {
const percent = item.plan > 0 ? Math.round((item.fact / item.plan) * 100) : 0;
csv += `${item.article};${item.plan};${item.fact};${item.cumulative};${percent}\n`;
totalPlan += item.plan;
totalFact += item.fact;
});
const totalPercent = totalPlan > 0 ? Math.round((totalFact / totalPlan) * 100) : 0;
csv += `ИТОГО;${totalPlan};${totalFact};${totalFact};${totalPercent}\n`;
downloadCSV(csv, `budget_summary_${new Date().toISOString().slice(0,10)}.csv`);
}
function exportBranchesToExcel() {
let csv = '\uFEFF';
csv += 'Филиал;План;Факт;Выполнение (%);Статус\n';
const summary = {};
budgetData.forEach(item => {
if (!summary[item.branch]) {
summary[item.branch] = {plan: 0, fact: 0};
}
summary[item.branch].plan += item.plan;
summary[item.branch].fact += item.fact;
});
Object.entries(summary).forEach(([branch, data]) => {
const percent = data.plan > 0 ? Math.round((data.fact / data.plan) * 100) : 0;
const status = percent >= 90 ? 'В норме' : percent >= 70 ? 'Внимание' : 'Критично';
csv += `${branch};${data.plan};${data.fact};${percent};${status}\n`;
});
downloadCSV(csv, `budget_by_branch_${new Date().toISOString().slice(0,10)}.csv`);
}
function exportArticlesToExcel() {
let csv = '\uFEFF';
csv += 'Статья;Филиал;План;Факт;Нарастающий итог;Выполнение (%)\n';
const filter = document.getElementById('article-detail-filter').value;
let filtered = budgetData;
if (filter !== 'all') filtered = filtered.filter(d => d.articleCode === filter);
filtered.forEach(item => {
const percent = item.plan > 0 ? Math.round((item.fact / item.plan) * 100) : 0;
csv += `${item.article};${item.branch};${item.plan};${item.fact};${item.cumulative};${percent}\n`;
});
downloadCSV(csv, `budget_by_article_${new Date().toISOString().slice(0,10)}.csv`);
}
function downloadCSV(csv, filename) {
const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
const link = document.createElement('a');
link.href = URL.createObjectURL(blob);
link.download = filename;
link.click();
}
renderAll();
</script>
</body>
</html>

View File

@ -143,32 +143,11 @@ footer{background:var(--ink);color:var(--gray-500);text-align:center;padding:40p
</thead> </thead>
<tbody id="articles-body"> <tbody id="articles-body">
<tr> <tr>
<td><input type="text" name="article_name[]" value="2.4.5 - Командировочные расходы по подготовке кадров и повышению квалификации кадров" required></td> <td><input type="text" name="article_name[]" placeholder="Название статьи" required></td>
<td><input type="number" name="article_plan[]" placeholder="0" required></td> <td><input type="number" name="article_plan[]" placeholder="0" required></td>
<td><input type="number" name="article_fact[]" placeholder="0" required></td> <td><input type="number" name="article_fact[]" placeholder="0" required></td>
<td><input type="number" name="article_cumulative[]" placeholder="0" required></td> <td><input type="number" name="article_cumulative[]" placeholder="0" required></td>
<td></td> <td><button type="button" class="btn-danger" onclick="removeArticle(this)">Удалить</button></td>
</tr>
<tr>
<td><input type="text" name="article_name[]" value="2.4.6 - Подготовка и повышение квалификации кадров" required></td>
<td><input type="number" name="article_plan[]" placeholder="0" required></td>
<td><input type="number" name="article_fact[]" placeholder="0" required></td>
<td><input type="number" name="article_cumulative[]" placeholder="0" required></td>
<td></td>
</tr>
<tr>
<td><input type="text" name="article_name[]" value="4.1.6 - Командировочные расходы по подготовке кадров и повышению квалификации кадров" required></td>
<td><input type="number" name="article_plan[]" placeholder="0" required></td>
<td><input type="number" name="article_fact[]" placeholder="0" required></td>
<td><input type="number" name="article_cumulative[]" placeholder="0" required></td>
<td></td>
</tr>
<tr>
<td><input type="text" name="article_name[]" value="4.1.7 - Подготовка и повышение квалификации кадров" required></td>
<td><input type="number" name="article_plan[]" placeholder="0" required></td>
<td><input type="number" name="article_fact[]" placeholder="0" required></td>
<td><input type="number" name="article_cumulative[]" placeholder="0" required></td>
<td></td>
</tr> </tr>
</tbody> </tbody>
<tfoot> <tfoot>