Compare commits

..

No commits in common. "pages" and "main" have entirely different histories.
pages ... main

5 changed files with 25 additions and 1610 deletions

View File

@ -1,212 +0,0 @@
# Инструкция для IT-отдела: Настройка системы сбора данных ЕППК
## Цель
Автоматизация сбора данных Единого плана повышения квалификации (ЕППК) и отчётов по бюджету обучений с филиалов Казахтелеком.
## Требования
### Google Workspace
- Корпоративный аккаунт Google Workspace
- Доступ к созданию форм и таблиц
- Доступ к Google Apps Script
### Пользователи
- 21 филиал + Центральный аппарат
- Каждый филиал заполняет форму через корпоративный email
---
## Шаг 1: Создание Google Формы
### Структура формы
**Название:** Сбор данных ЕППК — Казахтелеком
**Поля формы:**
| Поле | Тип | Обязательное | Варианты |
|------|-----|--------------|----------|
| Филиал | Выпадающий список | Да | Список филиалов (см. ниже) |
| Тип отчёта | Выпадающий список | Да | Бюджет (ежемесячно) / ЕППК (годовой) |
| Период | Выпадающий список | Да | Январь, Февраль, Март, ..., Декабрь |
| Год | Выпадающий список | Да | 2025, 2026 |
| Бюджет обучений (тенге) | Число | Да | - |
| Бюджет командировок (тенге) | Число | Да | - |
| Запланировано обучений | Число | Нет | - |
| Прошло обучений | Число | Нет | - |
| Комментарий | Текст | Нет | - |
### Список филиалов для выпадающего списка
```
Астана
Алматы
Шымкент
Атырау
Актобе
Караганда
Павлодар
Петропавловск
Костанай
Туркестан
Усть-Каменогорск
Уральск
Семей
Кызылорда
Тараз
Жезказган
Экибастуз
Темиртау
Балхаш
Турксиб
ЦА (Центральный аппарат)
```
### Настройки формы
1. **Ограничение доступа:** Только сотрудники корпоративного аккаунта
2. **Сбор email:** Включить автоматический сбор email заполнителя
3. **Одна отправка на email:** Запретить повторные отправки (опционально)
4. **Показывать.progress bar:** Включить
---
## Шаг 2: Настройка Google Таблицы
### Автоматическое создание таблицы
При создании формы нажмите "Создать таблицу" — данные будут поступать автоматически.
### Дополнительные колонки (формулы)
Добавьте в таблицу вычисляемые колонки:
| Колонка | Формула | Описание |
|---------|---------|----------|
| Статус | `=IF(E2>0,"Сдано","Ожидает")` | Автоматический статус |
| % выполнения | `=IF(D2>0,E2/D2*100,0)` | Процент выполнения плана |
| Разница бюджета | `=F2-G2` | Разница между обучением и командировками |
---
## Шаг 3: Настройка автоматических напоминаний
### Google Apps Script для напоминаний
Создайте новый скрипт: **Расширения → Apps Script**
```javascript
// Конфигурация
const SHEET_NAME = "Ответы формы";
const EMAIL_COLUMN = 2; // Столбец с email (B)
const STATUS_COLUMN = 10; // Столбец со статусом (J)
const DEADLINE_DAY = 25; // День дедлайна (25-е число)
function sendReminders() {
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(SHEET_NAME);
const data = sheet.getDataRange().getValues();
const today = new Date();
const currentDay = today.getDate();
// Проверяем, не наступил ли дедлайн (25-е число)
if (currentDay < DEADLINE_DAY) {
Logger.log("Дедлайн ещё не наступил. Текущий день: " + currentDay);
return;
}
// Собираем email филиалов, которые ещё не сдали
const pendingEmails = [];
for (let i = 1; i < data.length; i++) {
const email = data[i][EMAIL_COLUMN - 1];
const status = data[i][STATUS_COLUMN - 1];
if (email && status !== "Сдано") {
pendingEmails.push(email);
}
}
if (pendingEmails.length === 0) {
Logger.log("Все филиалы сдали отчёт!");
return;
}
// Формируем письмо
const subject = "Напоминание: сдача отчёта по ЕППК";
const body = `
Уважаемые коллеги!
Напоминаем о необходимости сдачи ежемесячного отчёта по бюджету обучений и командировочных расходов.
Дедлайн: 25-е число текущего месяца.
Пожалуйста, заполните форму:
[ВСТАВЬТЕ ССЫЛКУ НА ФОРМУ]
С уважением,
Центральный аппарат Казахтелеком
`;
// Отправляем каждому филиалу
pendingEmails.forEach(email => {
GmailApp.sendEmail(email, subject, body);
Logger.log("Отправлено: " + email);
});
// Или отправляем одним письмом BCC
// GmailApp.sendEmail("", subject, body, {bcc: pendingEmails.join(",")});
}
// Автоматический запуск каждый месяц 25-го числа
function createMonthlyTrigger() {
ScriptApp.newTrigger("sendReminders")
.timeBased()
.onMonthDay(DEADLINE_DAY)
.atHour(10) // 10:00 утра
.create();
}
```
### Настройка триггера
1. Запустите функцию `createMonthlyTrigger()` один раз
2. Скрипт будет автоматически отправлять напоминания 25-го числа каждого месяца в 10:00
---
## Шаг 4: Настройка доступа
### Кто может заполнять форму
- Настройте форму: **Настройки → Ограничения → Только сотрудники [ваша_организация].com**
### Кто может просматривать таблицу
- Настройте общий доступ к Google Таблице
- Роль: "Просмотр" для филиалов, "Редактор" для вас и IT
### Кто может редактировать скрипт
- Только IT-отдел и вы
---
## Шаг 5: Тестирование
1. Создайте тестовую форму с ограничением доступа
2. Отправьте тестовые данные
3. Проверьте, что данные поступают в таблицу
4. Проверьте отправку напоминаний вручную
5. Настройте автоматический триггер
---
## Контакты
При вопросах обращайтесь к [ваше имя] — [ваш email]
---
## Дополнительные опции
### Если нет Google Workspace
- **Microsoft Forms + Power Automate** — аналогичная функциональность
- **Яндекс.Формы** — если используете Яндекс.Корпоративный портал
### Если нужна более сложная система
- **Airtable** — база данных с формами и автоматизациями
- **Notion** — база данных + формы + шаблоны отчётов

View File

@ -1,675 +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:#1a365d;--blue:#2563eb;--blue-light:#dbeafe;--white:#fff;--gray-500:#64748b;--gray-100:#f1f5f9;--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(--blue-light)}
.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(--blue);color:var(--white)}
.btn-primary:hover{background:#1d4ed8}
.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(--blue);color:var(--white)}
.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(--blue-light);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>
<button class="tab" onclick="showTab('plan')">Ввод плана</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 id="tab-plan" class="hidden">
<div class="card">
<h2>Ввод плановых показателей по месяцам</h2>
<p style="color:var(--gray-500);margin-bottom:24px">Введите плановые показатели для каждой статьи по месяцам. Данные сохраняются автоматически.</p>
<div class="filter-bar">
<select id="plan-month" onchange="renderPlanTable()">
<option value="Январь">Январь</option>
<option value="Февраль">Февраль</option>
<option value="Март">Март</option>
<option value="Апрель">Апрель</option>
<option value="Май">Май</option>
<option value="Июнь" selected>Июнь</option>
<option value="Июль">Июль</option>
<option value="Август">Август</option>
<option value="Сентябрь">Сентябрь</option>
<option value="Октябрь">Октябрь</option>
<option value="Ноябрь">Ноябрь</option>
<option value="Декабрь">Декабрь</option>
</select>
<select id="plan-branch" onchange="renderPlanTable()">
<option value="all">Все филиалы (сводный)</option>
${branches.map(b => `<option value="${b}">${b}</option>`).join('')}
</select>
<button class="btn btn-success" onclick="savePlanData()">Сохранить</button>
<button class="btn btn-download" onclick="exportPlanToExcel()">Скачать план Excel</button>
</div>
<table id="plan-table">
<thead>
<tr>
<th>Статья бюджета</th>
<th class="text-right">План на месяц (тенге)</th>
<th class="text-right">План на год (тенге)</th>
</tr>
</thead>
<tbody id="plan-body"></tbody>
<tfoot>
<tr class="total-row">
<td><strong>ИТОГО</strong></td>
<td class="text-right" id="plan-total-month">0</td>
<td class="text-right" id="plan-total-year">0</td>
</tr>
</tfoot>
</table>
</div>
<div class="card">
<h2>Помесячный план (свод)</h2>
<div style="overflow-x:auto">
<table id="monthly-plan-table">
<thead>
<tr>
<th>Статья</th>
<th class="text-right">Янв</th>
<th class="text-right">Фев</th>
<th class="text-right">Мар</th>
<th class="text-right">Апр</th>
<th class="text-right">Май</th>
<th class="text-right">Июн</th>
<th class="text-right">Июл</th>
<th class="text-right">Авг</th>
<th class="text-right">Сен</th>
<th class="text-right">Окт</th>
<th class="text-right">Ноя</th>
<th class="text-right">Дек</th>
<th class="text-right">Итого</th>
</tr>
</thead>
<tbody id="monthly-plan-body"></tbody>
</table>
</div>
</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');
document.getElementById('tab-plan').classList.toggle('hidden', tab !== 'plan');
if (tab === 'plan') {
renderPlanTable();
renderMonthlyPlanTable();
}
}
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();
}
// Plan functions
let planData = JSON.parse(localStorage.getItem('plan-data')) || {};
function savePlanData() {
const month = document.getElementById('plan-month').value;
const branch = document.getElementById('plan-branch').value;
const key = `${branch}-${month}`;
const rows = document.querySelectorAll('#plan-body tr');
planData[key] = {};
rows.forEach(row => {
const articleCode = row.getAttribute('data-code');
const monthPlan = parseInt(row.querySelector('.month-plan').value) || 0;
const yearPlan = parseInt(row.querySelector('.year-plan').value) || 0;
planData[key][articleCode] = {monthPlan, yearPlan};
});
localStorage.setItem('plan-data', JSON.stringify(planData));
alert('План сохранён!');
renderMonthlyPlanTable();
}
function renderPlanTable() {
const month = document.getElementById('plan-month').value;
const branch = document.getElementById('plan-branch').value;
const key = `${branch}-${month}`;
const data = planData[key] || {};
const tbody = document.getElementById('plan-body');
tbody.innerHTML = '';
let totalMonth = 0, totalYear = 0;
articles.forEach(article => {
const item = data[article.code] || {monthPlan: 0, yearPlan: 0};
totalMonth += item.monthPlan;
totalYear += item.yearPlan;
const row = document.createElement('tr');
row.setAttribute('data-code', article.code);
row.innerHTML = `
<td>${article.name}</td>
<td class="text-right"><input type="number" class="month-plan" value="${item.monthPlan}" style="width:150px;text-align:right;padding:8px;border:2px solid var(--gray-100);border-radius:6px"></td>
<td class="text-right"><input type="number" class="year-plan" value="${item.yearPlan}" style="width:150px;text-align:right;padding:8px;border:2px solid var(--gray-100);border-radius:6px"></td>
`;
tbody.appendChild(row);
});
document.getElementById('plan-total-month').textContent = totalMonth.toLocaleString('ru-RU');
document.getElementById('plan-total-year').textContent = totalYear.toLocaleString('ru-RU');
}
function renderMonthlyPlanTable() {
const months = ['Январь','Февраль','Март','Апрель','Май','Июнь','Июль','Август','Сентябрь','Октябрь','Ноябрь','Декабрь'];
const branch = document.getElementById('plan-branch').value;
const tbody = document.getElementById('monthly-plan-body');
tbody.innerHTML = '';
articles.forEach(article => {
const row = document.createElement('tr');
let total = 0;
let cells = `<td>${article.name}</td>`;
months.forEach(month => {
const key = `${branch}-${month}`;
const data = planData[key] || {};
const item = data[article.code] || {monthPlan: 0};
total += item.monthPlan;
cells += `<td class="text-right">${item.monthPlan.toLocaleString('ru-RU')}</td>`;
});
cells += `<td class="text-right"><strong>${total.toLocaleString('ru-RU')}</strong></td>`;
row.innerHTML = cells;
tbody.appendChild(row);
});
}
function exportPlanToExcel() {
const months = ['Январь','Февраль','Март','Апрель','Май','Июнь','Июль','Август','Сентябрь','Октябрь','Ноябрь','Декабрь'];
const branch = document.getElementById('plan-branch').value;
let csv = '\uFEFF';
csv += 'Статья;' + months.join(';') + ';Итого\n';
articles.forEach(article => {
let row = article.name;
let total = 0;
months.forEach(month => {
const key = `${branch}-${month}`;
const data = planData[key] || {};
const item = data[article.code] || {monthPlan: 0};
total += item.monthPlan;
row += ';' + item.monthPlan;
});
row += ';' + total;
csv += row + '\n';
});
downloadCSV(csv, `budget_plan_${branch}_${new Date().toISOString().slice(0,10)}.csv`);
}
renderAll();
</script>
</body>
</html>

View File

@ -1,354 +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:#1a365d;--blue:#2563eb;--blue-light:#dbeafe;--white:#fff;--gray-500:#64748b;--gray-100:#f1f5f9}
*{box-sizing:border-box;margin:0;padding:0}
body{font-family:'Inter',sans-serif;font-size:17px;line-height:1.6;color:var(--ink);background:var(--white)}
.container{max-width:1140px;margin:0 auto;padding:80px 24px}
.hero{background:var(--ink);color:var(--white);text-align:center}
.hero h1{font-size:56px;font-weight:800;line-height:1.05;margin-bottom:24px}
.hero p{font-size:20px;color:#9aa3b2;max-width:700px;margin:0 auto 32px}
.btn{display:inline-block;background:var(--blue);color:var(--white);padding:14px 28px;border-radius:8px;font-weight:700;text-decoration:none;border:none;cursor:pointer;font-size:17px}
.btn:hover{background:#1d4ed8}
.btn-secondary{background:transparent;color:var(--ink);border:2px solid var(--ink);padding:12px 26px;border-radius:8px}
.btn-danger{background:#EF4444;color:var(--white);padding:8px 16px;border-radius:6px;font-size:14px}
.section{padding:80px 24px}
.section h2{font-size:36px;font-weight:700;margin-bottom:24px;text-align:center}
.form-card{background:var(--gray-100);border-radius:16px;padding:40px;max-width:900px;margin:0 auto}
.form-group{margin-bottom:24px}
.form-group label{display:block;font-weight:600;margin-bottom:8px}
.form-group select,.form-group input,.form-group textarea{width:100%;padding:14px;border:2px solid var(--gray-100);border-radius:8px;font-size:17px;font-family:inherit}
.form-group select:focus,.form-group input:focus,.form-group textarea:focus{outline:none;border-color:var(--blue)}
.form-group textarea{min-height:100px;resize:vertical}
.form-row{display:grid;grid-template-columns:1fr 1fr;gap:24px}
.form-section{margin-bottom:32px;padding-bottom:24px;border-bottom:2px solid var(--gray-100)}
.form-section h3{font-size:20px;font-weight:700;margin-bottom:16px;color:var(--ink)}
.success-message{display:none;background:#10B981;color:var(--white);padding:20px;border-radius:8px;text-align:center;font-weight:600}
footer{background:var(--ink);color:var(--gray-500);text-align:center;padding:40px 24px;font-size:14px}
.articles-table{width:100%;border-collapse:collapse;margin-bottom:16px}
.articles-table th,.articles-table td{padding:12px;text-align:left;border-bottom:1px solid var(--gray-100)}
.articles-table th{background:var(--white);font-weight:600;font-size:14px}
.articles-table input{width:100%;padding:10px;border:2px solid var(--gray-100);border-radius:6px;font-size:15px}
.articles-table input:focus{outline:none;border-color:var(--cyan)}
.articles-table .col-name{width:30%}
.articles-table .col-plan{width:20%}
.articles-table .col-fact{width:20%}
.articles-table .col-cumulative{width:20%}
.articles-table .col-action{width:10%}
.add-article-btn{display:inline-flex;align-items:center;gap:8px;background:var(--white);color:var(--ink);border:2px dashed var(--gray-100);padding:12px 20px;border-radius:8px;font-weight:600;cursor:pointer;margin-bottom:24px}
.add-article-btn:hover{border-color:var(--cyan);background:var(--cyan-50)}
.total-row{background:var(--cyan-50);font-weight:700}
@media(max-width:640px){
.hero h1{font-size:36px}
.section h2{font-size:28px}
.form-row{grid-template-columns:1fr}
.container{padding:48px 20px}
.articles-table{font-size:14px}
.articles-table th,.articles-table td{padding:8px}
}
</style>
</head>
<body>
<section class="hero">
<div class="container">
<h1>Исполнение бюджета</h1>
<p>Ежемесячный отчёт по статьям бюджета — филиалы Казахтелеком</p>
</div>
</section>
<section class="section">
<div class="container">
<h2>Отправка данных</h2>
<div class="form-card">
<form id="budget-form" action="https://formspree.io/f/mbldnbvb" method="POST">
<div class="form-section">
<h3>Основная информация</h3>
<div class="form-row">
<div class="form-group">
<label for="branch">Филиал</label>
<select id="branch" name="branch" required>
<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>
<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>
<option value="СФ">СФ</option>
<option value="КУ">КУ</option>
<option value="ДТК">ДТК</option>
<option value="ДУП">ДУП</option>
<option value="ДПБ">ДПБ</option>
<option value="ДЦБ">ДЦБ</option>
<option value="ЦА (Центральный аппарат)">ЦА (Центральный аппарат)</option>
</select>
</div>
<div class="form-group">
<label for="month">Месяц</label>
<select id="month" name="month" required>
<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>
<option value="Декабрь">Декабрь</option>
</select>
</div>
</div>
<div class="form-group">
<label for="year">Год</label>
<select id="year" name="year" required>
<option value="2026">2026</option>
<option value="2025">2025</option>
</select>
</div>
</div>
<div class="form-section">
<h3>Статьи бюджета</h3>
<p style="color:var(--gray-500);margin-bottom:16px;font-size:15px">Добавьте статьи расходов и укажите план, факт и нарастающий итог по каждой</p>
<table class="articles-table" id="articles-table">
<thead>
<tr>
<th class="col-name">Статья расходов</th>
<th class="col-plan">План на год (тенге)</th>
<th class="col-fact">Факт за месяц (тенге)</th>
<th class="col-cumulative">Нарастающий итог (тенге)</th>
<th class="col-action"></th>
</tr>
</thead>
<tbody id="articles-body">
<tr>
<td><input type="text" name="article_name[]" value="2.4.5 - Командировочные расходы по подготовке кадров и повышению квалификации кадров" 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="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>
</tbody>
<tfoot>
<tr class="total-row">
<td><strong>ИТОГО</strong></td>
<td id="total-plan">0</td>
<td id="total-fact">0</td>
<td id="total-cumulative">0</td>
<td></td>
</tr>
</tfoot>
</table>
<button type="button" class="add-article-btn" onclick="addArticle()">
<span style="font-size:20px">+</span> Добавить статью
</button>
</div>
<div class="form-section">
<h3>Статистика обучений</h3>
<div class="form-row">
<div class="form-group">
<label for="planned_count">Запланировано обучений</label>
<input type="number" id="planned_count" name="planned_count" placeholder="0">
</div>
<div class="form-group">
<label for="completed_count">Прошло обучений</label>
<input type="number" id="completed_count" name="completed_count" placeholder="0">
</div>
</div>
<div class="form-row">
<div class="form-group">
<label for="planned_employees">Запланировано сотрудников</label>
<input type="number" id="planned_employees" name="planned_employees" placeholder="0">
</div>
<div class="form-group">
<label for="completed_employees">Обучено сотрудников</label>
<input type="number" id="completed_employees" name="completed_employees" placeholder="0">
</div>
</div>
</div>
<div class="form-group">
<label for="notes">Комментарии</label>
<textarea id="notes" name="notes" placeholder="Основания перерасхода/экономии, особенности периода..."></textarea>
</div>
<input type="hidden" name="_subject" value="Исполнение бюджета - ежемесячный отчёт">
<input type="hidden" name="_template" value="table">
<input type="text" name="_gotcha" style="display:none">
<button type="submit" class="btn" style="width:100%">Отправить данные</button>
</form>
<div id="success-message" class="success-message">
✓ Данные успешно отправлены! Спасибо.
</div>
</div>
</div>
</section>
<footer>
<p>© 2026 Казахтелеком — Исполнение бюджета</p>
</footer>
<script>
let articleCount = 1;
function addArticle() {
const tbody = document.getElementById('articles-body');
const row = document.createElement('tr');
row.innerHTML = `
<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_fact[]" placeholder="0" required></td>
<td><input type="number" name="article_cumulative[]" placeholder="0" required></td>
<td><button type="button" class="btn-danger" onclick="removeArticle(this)">Удалить</button></td>
`;
tbody.appendChild(row);
articleCount++;
updateTotals();
}
function removeArticle(btn) {
const tbody = document.getElementById('articles-body');
if (tbody.rows.length > 1) {
btn.closest('tr').remove();
updateTotals();
} else {
alert('Должна остаться хотя бы одна статья');
}
}
function updateTotals() {
let totalPlan = 0, totalFact = 0, totalCumulative = 0;
document.querySelectorAll('#articles-body tr').forEach(row => {
const plan = parseInt(row.querySelector('[name="article_plan[]"]').value) || 0;
const fact = parseInt(row.querySelector('[name="article_fact[]"]').value) || 0;
const cumulative = parseInt(row.querySelector('[name="article_cumulative[]"]').value) || 0;
totalPlan += plan;
totalFact += fact;
totalCumulative += cumulative;
});
document.getElementById('total-plan').textContent = totalPlan.toLocaleString('ru-RU');
document.getElementById('total-fact').textContent = totalFact.toLocaleString('ru-RU');
document.getElementById('total-cumulative').textContent = totalCumulative.toLocaleString('ru-RU');
}
document.getElementById('articles-body').addEventListener('input', updateTotals);
document.getElementById('budget-form').addEventListener('submit', async function(e) {
e.preventDefault();
const form = e.target;
const submitBtn = form.querySelector('button[type="submit"]');
submitBtn.textContent = 'Отправка...';
submitBtn.disabled = true;
// Save to localStorage for dashboard
const branch = document.getElementById('branch').value;
const month = document.getElementById('month').value;
const year = document.getElementById('year').value;
let budgetData = JSON.parse(localStorage.getItem('budget-data')) || [];
document.querySelectorAll('#articles-body tr').forEach(row => {
const articleName = row.querySelector('[name="article_name[]"]').value;
const articleCode = articleName.split(' - ')[0];
const plan = parseInt(row.querySelector('[name="article_plan[]"]').value) || 0;
const fact = parseInt(row.querySelector('[name="article_fact[]"]').value) || 0;
const cumulative = parseInt(row.querySelector('[name="article_cumulative[]"]').value) || 0;
budgetData.push({
branch: branch,
month: month,
year: year,
article: articleName,
articleCode: articleCode,
plan: plan,
fact: fact,
cumulative: cumulative
});
});
localStorage.setItem('budget-data', JSON.stringify(budgetData));
try {
const response = await fetch(form.action, {
method: 'POST',
body: new FormData(form),
headers: { 'Accept': 'application/json' }
});
if (response.ok) {
form.style.display = 'none';
document.getElementById('success-message').style.display = 'block';
} else {
throw new Error('Ошибка сервера');
}
} catch (error) {
alert('Произошла ошибка при отправке. Попробуйте ещё раз.');
submitBtn.textContent = 'Отправить данные';
submitBtn.disabled = false;
}
});
updateTotals();
</script>
</body>
</html>

View File

@ -1,335 +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:#1a365d;--blue:#2563eb;--blue-light:#dbeafe;--white:#fff;--gray-500:#64748b;--gray-100:#f1f5f9;--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(--blue);color:var(--white)}
.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(--blue-light)}
.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(--blue);color:var(--white)}
.btn-remind:hover{background:#1d4ed8}
.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">30</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">30</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",
"ДРБ":"drb@kaztelecom.kz",
"ДКБ":"dkb@kaztelecom.kz",
"ОДС":"ods@kaztelecom.kz",
"СФ":"sf@kaztelecom.kz",
"КУ":"ku@kaztelecom.kz",
"ДТК":"dtk@kaztelecom.kz",
"ДУП":"dup@kaztelecom.kz",
"ДПБ":"dpb@kaztelecom.kz",
"ДЦБ":"dcb@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>

View File

@ -7,22 +7,22 @@
<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:#1a365d;--blue:#2563eb;--blue-light:#dbeafe;--white:#fff;--gray-500:#64748b;--gray-100:#f1f5f9}
:root{--ink:#0F1218;--cyan:#00E5FF;--cyan-50:#E8FCFF;--white:#fff;--gray-500:#5B6573;--gray-100:#F2F4F7}
*{box-sizing:border-box;margin:0;padding:0}
body{font-family:'Inter',sans-serif;font-size:17px;line-height:1.6;color:var(--ink);background:var(--white)}
.container{max-width:1140px;margin:0 auto;padding:80px 24px}
.hero{background:var(--ink);color:var(--white);text-align:center}
.hero h1{font-size:56px;font-weight:800;line-height:1.05;margin-bottom:24px}
.hero p{font-size:20px;color:#9aa3b2;max-width:700px;margin:0 auto 32px}
.btn{display:inline-block;background:var(--blue);color:var(--white);padding:14px 28px;border-radius:8px;font-weight:700;text-decoration:none;border:none;cursor:pointer;font-size:17px}
.btn:hover{background:#1d4ed8}
.btn{display:inline-block;background:var(--cyan);color:var(--ink);padding:14px 28px;border-radius:8px;font-weight:700;text-decoration:none;border:none;cursor:pointer;font-size:17px}
.btn:hover{background:#1be5ff}
.section{padding:80px 24px}
.section h2{font-size:36px;font-weight:700;margin-bottom:24px;text-align:center}
.form-card{background:var(--gray-100);border-radius:16px;padding:40px;max-width:700px;margin:0 auto}
.form-group{margin-bottom:24px}
.form-group label{display:block;font-weight:600;margin-bottom:8px}
.form-group select,.form-group input,.form-group textarea{width:100%;padding:14px;border:2px solid var(--gray-100);border-radius:8px;font-size:17px;font-family:inherit}
.form-group select:focus,.form-group input:focus,.form-group textarea:focus{outline:none;border-color:var(--blue)}
.form-group select:focus,.form-group input:focus,.form-group textarea:focus{outline:none;border-color:var(--cyan)}
.form-group textarea{min-height:100px;resize:vertical}
.form-row{display:grid;grid-template-columns:1fr 1fr;gap:24px}
.success-message{display:none;background:#10B981;color:var(--white);padding:20px;border-radius:8px;text-align:center;font-weight:600}
@ -53,36 +53,27 @@ footer{background:var(--ink);color:var(--gray-500);text-align:center;padding:40p
<label for="branch">Филиал</label>
<select id="branch" name="branch" required>
<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>
<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>
<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>
<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>
<option value="Темиртау">Темиртау</option>
<option value="Балхаш">Балхаш</option>
<option value="Турксиб">Турксиб</option>
<option value="ЦА (Центральный аппарат)">ЦА (Центральный аппарат)</option>
</select>
</div>