Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c43b643e0a | |||
| 6b3affc35e | |||
| 7e6f43db04 | |||
| df85618b03 | |||
| b8639bf03f | |||
| 4cb48873b4 | |||
| 02273a773f | |||
| f33f1435c4 | |||
| 55905e16ed | |||
| ac2246a784 |
212
IT-INSTRUCTION.md
Normal file
212
IT-INSTRUCTION.md
Normal file
@ -0,0 +1,212 @@
|
|||||||
|
# Инструкция для 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** — база данных + формы + шаблоны отчётов
|
||||||
675
budget-dashboard.html
Normal file
675
budget-dashboard.html
Normal file
@ -0,0 +1,675 @@
|
|||||||
|
<!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>
|
||||||
354
budget-form.html
Normal file
354
budget-form.html
Normal file
@ -0,0 +1,354 @@
|
|||||||
|
<!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>
|
||||||
335
dashboard.html
Normal file
335
dashboard.html
Normal file
@ -0,0 +1,335 @@
|
|||||||
|
<!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>
|
||||||
17
index.html
17
index.html
@ -7,22 +7,22 @@
|
|||||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
<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">
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700;800&display=swap" rel="stylesheet">
|
||||||
<style>
|
<style>
|
||||||
:root{--ink:#0F1218;--cyan:#00E5FF;--cyan-50:#E8FCFF;--white:#fff;--gray-500:#5B6573;--gray-100:#F2F4F7}
|
:root{--ink:#1a365d;--blue:#2563eb;--blue-light:#dbeafe;--white:#fff;--gray-500:#64748b;--gray-100:#f1f5f9}
|
||||||
*{box-sizing:border-box;margin:0;padding:0}
|
*{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)}
|
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}
|
.container{max-width:1140px;margin:0 auto;padding:80px 24px}
|
||||||
.hero{background:var(--ink);color:var(--white);text-align:center}
|
.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 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}
|
.hero p{font-size:20px;color:#9aa3b2;max-width:700px;margin:0 auto 32px}
|
||||||
.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{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:#1be5ff}
|
.btn:hover{background:#1d4ed8}
|
||||||
.section{padding:80px 24px}
|
.section{padding:80px 24px}
|
||||||
.section h2{font-size:36px;font-weight:700;margin-bottom:24px;text-align:center}
|
.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-card{background:var(--gray-100);border-radius:16px;padding:40px;max-width:700px;margin:0 auto}
|
||||||
.form-group{margin-bottom:24px}
|
.form-group{margin-bottom:24px}
|
||||||
.form-group label{display:block;font-weight:600;margin-bottom:8px}
|
.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,.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(--cyan)}
|
.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-group textarea{min-height:100px;resize:vertical}
|
||||||
.form-row{display:grid;grid-template-columns:1fr 1fr;gap:24px}
|
.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}
|
.success-message{display:none;background:#10B981;color:var(--white);padding:20px;border-radius:8px;text-align:center;font-weight:600}
|
||||||
@ -73,6 +73,15 @@ footer{background:var(--ink);color:var(--gray-500);text-align:center;padding:40p
|
|||||||
<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>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user