476 lines
25 KiB
HTML
476 lines
25 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="ru" data-theme="light">
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||
<title>Система принятия решений | Казахтелеком</title>
|
||
<link rel="stylesheet" href="./design-system/kt-ai-fonts.css">
|
||
<link rel="stylesheet" href="./design-system/kt-ai-tokens.css">
|
||
<link rel="stylesheet" href="./design-system/kt-ai-components.css">
|
||
<link rel="stylesheet" href="./design-system/kt-ai-page.css">
|
||
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>📋</text></svg>">
|
||
<style>
|
||
.kt-ai-app { min-height: 100vh; background: var(--kt-ai-bg); color: var(--kt-ai-fg); font-family: var(--kt-ai-font-sans); }
|
||
.kt-ai-header-content { display: flex; align-items: center; gap: var(--kt-ai-space-4); }
|
||
.kt-ai-logo-wrap { display: flex; align-items: center; gap: var(--kt-ai-space-3); }
|
||
.kt-ai-main { padding: var(--kt-ai-space-6) 0; }
|
||
.kt-ai-kpi-strip { display: grid; grid-template-columns: repeat(4, 1fr); gap: var(--kt-ai-space-4); margin-bottom: var(--kt-ai-space-6); }
|
||
.kt-ai-kpi { background: var(--kt-ai-card-bg); border: 1px solid var(--kt-ai-card-border); border-radius: var(--kt-ai-radius-xl); padding: var(--kt-ai-space-5); }
|
||
.kt-ai-kpi .value { font-size: var(--kt-ai-text-3xl); font-weight: var(--kt-ai-weight-bold); color: var(--kt-ai-fg); }
|
||
.kt-ai-kpi .label { font-size: var(--kt-ai-text-sm); color: var(--kt-ai-fg-muted); margin-top: var(--kt-ai-space-1); }
|
||
.kt-ai-kpi .hint { font-size: var(--kt-ai-text-xs); color: var(--kt-ai-fg-faint); margin-top: var(--kt-ai-space-2); }
|
||
.kt-ai-filters { display: flex; gap: var(--kt-ai-space-3); flex-wrap: wrap; margin-bottom: var(--kt-ai-space-5); align-items: center; }
|
||
.kt-ai-filter-group { display: flex; flex-direction: column; gap: var(--kt-ai-space-1); }
|
||
.kt-ai-filter-label { font-size: var(--kt-ai-text-xs); color: var(--kt-ai-fg-muted); }
|
||
.kt-ai-table-wrap { overflow-x: auto; background: var(--kt-ai-card-bg); border: 1px solid var(--kt-ai-card-border); border-radius: var(--kt-ai-radius-xl); }
|
||
.kt-ai-table { width: 100%; border-collapse: collapse; font-size: var(--kt-ai-text-sm); }
|
||
.kt-ai-table th { text-align: left; padding: var(--kt-ai-space-3) var(--kt-ai-space-4); border-bottom: 1px solid var(--kt-ai-border); color: var(--kt-ai-fg-muted); font-weight: var(--kt-ai-weight-medium); }
|
||
.kt-ai-table td { padding: var(--kt-ai-space-3) var(--kt-ai-space-4); border-bottom: 1px solid var(--kt-ai-border-strong); vertical-align: middle; }
|
||
.kt-ai-table tr:last-child td { border-bottom: none; }
|
||
.kt-ai-table tr:hover td { background: var(--kt-ai-bg-soft); }
|
||
.kt-ai-table .num { text-align: right; font-variant-numeric: tabular-nums; }
|
||
.kt-ai-empty { text-align: center; padding: var(--kt-ai-space-10); color: var(--kt-ai-fg-muted); }
|
||
.kt-ai-modal-overlay { position: fixed; inset: 0; background: rgba(0,0,0,0.5); display: none; align-items: center; justify-content: center; z-index: 1000; }
|
||
.kt-ai-modal-overlay.in { display: flex; }
|
||
.kt-ai-modal { background: var(--kt-ai-card-bg); border-radius: var(--kt-ai-radius-2xl); padding: var(--kt-ai-space-6); max-width: 560px; width: 90%; max-height: 90vh; overflow-y: auto; }
|
||
.kt-ai-modal-head { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: var(--kt-ai-space-5); }
|
||
.kt-ai-modal-title { font-size: var(--kt-ai-text-xl); font-weight: var(--kt-ai-weight-semibold); }
|
||
.kt-ai-modal-close { background: none; border: none; font-size: var(--kt-ai-text-2xl); color: var(--kt-ai-fg-muted); cursor: pointer; padding: 0; line-height: 1; }
|
||
.kt-ai-modal-close:hover { color: var(--kt-ai-fg); }
|
||
.kt-ai-modal-body { display: flex; flex-direction: column; gap: var(--kt-ai-space-4); }
|
||
.kt-ai-info-row { display: flex; justify-content: space-between; padding: var(--kt-ai-space-2) 0; border-bottom: 1px solid var(--kt-ai-border-strong); }
|
||
.kt-ai-info-row:last-of-type { border-bottom: none; }
|
||
.kt-ai-info-label { color: var(--kt-ai-fg-muted); font-size: var(--kt-ai-text-sm); }
|
||
.kt-ai-info-value { font-size: var(--kt-ai-text-sm); font-weight: var(--kt-ai-weight-medium); }
|
||
.kt-ai-modal-actions { display: flex; gap: var(--kt-ai-space-3); margin-top: var(--kt-ai-space-5); padding-top: var(--kt-ai-space-5); border-top: 1px solid var(--kt-ai-border); }
|
||
.kt-ai-decision-note { font-size: var(--kt-ai-text-xs); color: var(--kt-ai-fg-faint); margin-top: var(--kt-ai-space-2); }
|
||
@media (max-width: 768px) {
|
||
.kt-ai-kpi-strip { grid-template-columns: repeat(2, 1fr); }
|
||
.kt-ai-filters { flex-direction: column; align-items: stretch; }
|
||
.kt-ai-filter-group { width: 100%; }
|
||
.kt-ai-select { width: 100%; }
|
||
}
|
||
</style>
|
||
</head>
|
||
<body class="kt-ai-app" data-kt-data-mode="demo">
|
||
|
||
<header class="kt-ai-pagehead">
|
||
<div class="kt-ai-wrap kt-ai-pagehead-in">
|
||
<div class="kt-ai-header-content">
|
||
<img src="https://code.vibe42.kz/tore/aitu/kt-logo.svg" alt="Казахтелеком" height="28">
|
||
<strong>Система принятия решений</strong>
|
||
</div>
|
||
<span class="kt-ai-spacer"></span>
|
||
<span class="kt-ai-chip" data-tone="orange">Демо-данные</span>
|
||
</div>
|
||
</header>
|
||
|
||
<main class="kt-ai-wrap kt-ai-main">
|
||
|
||
<section class="kt-ai-kpi-strip">
|
||
<div class="kt-ai-kpi">
|
||
<div class="value" id="kpi-total">0</div>
|
||
<div class="label">Всего заявок</div>
|
||
<div class="hint">за выбранный период</div>
|
||
</div>
|
||
<div class="kt-ai-kpi">
|
||
<div class="value" id="kpi-review">0</div>
|
||
<div class="label">На рассмотрении</div>
|
||
<div class="hint">требуют решения</div>
|
||
</div>
|
||
<div class="kt-ai-kpi">
|
||
<div class="value" id="kpi-approved" style="color: var(--kt-ai-status-ok-fg)">0</div>
|
||
<div class="label">Принято</div>
|
||
<div class="hint">за выбранный период</div>
|
||
</div>
|
||
<div class="kt-ai-kpi">
|
||
<div class="value" id="kpi-rejected" style="color: var(--kt-ai-status-risk-fg)">0</div>
|
||
<div class="label">Отклонено</div>
|
||
<div class="hint">за выбранный период</div>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="kt-ai-filters">
|
||
<div class="kt-ai-filter-group">
|
||
<label class="kt-ai-filter-label">Период</label>
|
||
<select class="kt-ai-select" id="filter-period" style="min-width: 140px">
|
||
<option value="7d">7 дней</option>
|
||
<option value="30d">30 дней</option>
|
||
<option value="quarter">Квартал</option>
|
||
<option value="year">Год</option>
|
||
</select>
|
||
</div>
|
||
<div class="kt-ai-filter-group">
|
||
<label class="kt-ai-filter-label">Статус</label>
|
||
<select class="kt-ai-select" id="filter-status" style="min-width: 160px">
|
||
<option value="all">Все статусы</option>
|
||
<option value="new">Новый</option>
|
||
<option value="review">На рассмотрении</option>
|
||
<option value="decided">Решение принято</option>
|
||
<option value="rejected">Отклонено</option>
|
||
</select>
|
||
</div>
|
||
<div class="kt-ai-filter-group">
|
||
<label class="kt-ai-filter-label">Тип заявки</label>
|
||
<select class="kt-ai-select" id="filter-type" style="min-width: 160px">
|
||
<option value="all">Все типы</option>
|
||
<option value="access">Доступ к данным</option>
|
||
<option value="integration">Интеграция</option>
|
||
<option value="hardware">Оборудование</option>
|
||
<option value="other">Другое</option>
|
||
</select>
|
||
</div>
|
||
<div class="kt-ai-filter-group">
|
||
<label class="kt-ai-filter-label">Приоритет</label>
|
||
<select class="kt-ai-select" id="filter-priority" style="min-width: 140px">
|
||
<option value="all">Все приоритеты</option>
|
||
<option value="high">Высокий</option>
|
||
<option value="medium">Средний</option>
|
||
<option value="low">Низкий</option>
|
||
</select>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="kt-ai-table-wrap">
|
||
<table class="kt-ai-table">
|
||
<thead>
|
||
<tr>
|
||
<th>Дата</th>
|
||
<th>Заявитель</th>
|
||
<th>Тип</th>
|
||
<th>Приоритет</th>
|
||
<th>Статус</th>
|
||
<th>Решение</th>
|
||
<th></th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="requests-table-body">
|
||
</tbody>
|
||
</table>
|
||
<div id="empty-state" class="kt-ai-empty" style="display: none">
|
||
<p>Нет данных за выбранный период</p>
|
||
<p style="font-size: var(--kt-ai-text-xs); margin-top: var(--kt-ai-space-2)">Измените параметры фильтра</p>
|
||
</div>
|
||
</section>
|
||
|
||
</main>
|
||
|
||
<div class="kt-ai-modal-overlay" id="modal-overlay">
|
||
<div class="kt-ai-modal">
|
||
<div class="kt-ai-modal-head">
|
||
<h2 class="kt-ai-modal-title">Рассмотрение заявки</h2>
|
||
<button class="kt-ai-modal-close" id="modal-close">×</button>
|
||
</div>
|
||
<div class="kt-ai-modal-body">
|
||
<div id="request-details"></div>
|
||
<div class="kt-ai-field">
|
||
<label for="decision-comment">Комментарий к решению</label>
|
||
<textarea id="decision-comment" class="kt-ai-textarea" rows="3" placeholder="Обоснование решения..."></textarea>
|
||
</div>
|
||
<div class="kt-ai-modal-actions">
|
||
<button class="kt-ai-btn" data-variant="primary" id="btn-approve">Принять</button>
|
||
<button class="kt-ai-btn" data-variant="danger" id="btn-reject">Отклонить</button>
|
||
<button class="kt-ai-btn" data-variant="ghost" id="btn-rework">На доработку</button>
|
||
</div>
|
||
<p class="kt-ai-decision-note">Решение будет сохранено в localStorage</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
// Реестр фильтров
|
||
const FILTERS = [
|
||
{ id: 'period', label: 'Период', affects: ['table', 'kpi', 'empty'] },
|
||
{ id: 'status', label: 'Статус', affects: ['table', 'kpi', 'empty'] },
|
||
{ id: 'type', label: 'Тип', affects: ['table', 'kpi', 'empty'] },
|
||
{ id: 'priority', label: 'Приоритет', affects: ['table', 'kpi', 'empty'] }
|
||
];
|
||
|
||
// Демо-данные
|
||
const DEMO_DATA = {
|
||
'7d': [
|
||
{ id: 1, date: '2026-09-10', applicant: 'Иванов А.С.', department: 'Департамент ИТ', type: 'access', typeName: 'Доступ к данным', priority: 'high', priorityName: 'Высокий', status: 'new', statusName: 'Новый', decision: null, comment: null },
|
||
{ id: 2, date: '2026-09-11', applicant: 'Петрова М.К.', department: 'Отдел аналитики', type: 'integration', typeName: 'Интеграция', priority: 'medium', priorityName: 'Средний', status: 'review', statusName: 'На рассмотрении', decision: null, comment: null },
|
||
{ id: 3, date: '2026-09-12', applicant: 'Сидоров Е.В.', department: 'Служба безопасности', type: 'hardware', typeName: 'Оборудование', priority: 'low', priorityName: 'Низкий', status: 'decided', statusName: 'Решение принято', decision: 'approved', decisionName: 'Принято', comment: 'Согласовано в рамках бюджета' },
|
||
{ id: 4, date: '2026-09-13', applicant: 'Алиев Н.Р.', department: 'Департамент ИТ', type: 'access', typeName: 'Доступ к данным', priority: 'high', priorityName: 'Высокий', status: 'new', statusName: 'Новый', decision: null, comment: null },
|
||
{ id: 5, date: '2026-09-14', applicant: 'Козлова Д.А.', department: 'Бухгалтерия', type: 'other', typeName: 'Другое', priority: 'medium', priorityName: 'Средний', status: 'review', statusName: 'На рассмотрении', decision: null, comment: null }
|
||
],
|
||
'30d': [
|
||
{ id: 6, date: '2026-09-01', applicant: 'Нурсултанов Б.Т.', department: 'Отдел маркетинга', type: 'integration', typeName: 'Интеграция', priority: 'medium', priorityName: 'Средний', status: 'decided', statusName: 'Решение принято', decision: 'approved', decisionName: 'Принято', comment: 'Интеграция одобрена' },
|
||
{ id: 7, date: '2026-09-03', applicant: 'Омарова Г.С.', department: 'Департамент ИТ', type: 'access', typeName: 'Доступ к данным', priority: 'high', priorityName: 'Высокий', status: 'decided', statusName: 'Решение принято', decision: 'approved', decisionName: 'Принято', comment: 'Доступ предоставлен' },
|
||
{ id: 8, date: '2026-09-05', applicant: 'Васильев П.И.', department: 'Служба поддержки', type: 'hardware', typeName: 'Оборудование', priority: 'low', priorityName: 'Низкий', status: 'new', statusName: 'Новый', decision: null, comment: null },
|
||
{ id: 9, date: '2026-09-07', applicant: 'Жанарбеков А.К.', department: 'Отдел продаж', type: 'other', typeName: 'Другое', priority: 'medium', priorityName: 'Средний', status: 'review', statusName: 'На рассмотрении', decision: null, comment: null },
|
||
{ id: 10, date: '2026-09-09', applicant: 'Смирнова О.Л.', department: 'Департамент ИТ', type: 'access', typeName: 'Доступ к данным', priority: 'high', priorityName: 'Высокий', status: 'new', statusName: 'Новый', decision: null, comment: null },
|
||
{ id: 11, date: '2026-09-10', applicant: 'Иванов А.С.', department: 'Департамент ИТ', type: 'access', typeName: 'Доступ к данным', priority: 'high', priorityName: 'Высокий', status: 'new', statusName: 'Новый', decision: null, comment: null },
|
||
{ id: 12, date: '2026-09-11', applicant: 'Петрова М.К.', department: 'Отдел аналитики', type: 'integration', typeName: 'Интеграция', priority: 'medium', priorityName: 'Средний', status: 'review', statusName: 'На рассмотрении', decision: null, comment: null },
|
||
{ id: 13, date: '2026-09-12', applicant: 'Сидоров Е.В.', department: 'Служба безопасности', type: 'hardware', typeName: 'Оборудование', priority: 'low', priorityName: 'Низкий', status: 'decided', statusName: 'Решение принято', decision: 'approved', decisionName: 'Принято', comment: 'Согласовано в рамках бюджета' }
|
||
],
|
||
'quarter': [
|
||
{ id: 14, date: '2026-07-15', applicant: 'Ахметов Р.Б.', department: 'Департамент ИТ', type: 'integration', typeName: 'Интеграция', priority: 'high', priorityName: 'Высокий', status: 'decided', statusName: 'Решение принято', decision: 'approved', decisionName: 'Принято', comment: 'Интеграция завершена' },
|
||
{ id: 15, date: '2026-08-01', applicant: 'Кузнецова А.В.', department: 'Отдел кадров', type: 'access', typeName: 'Доступ к данным', priority: 'medium', priorityName: 'Средний', status: 'decided', statusName: 'Решение принято', decision: 'approved', decisionName: 'Принято', comment: 'Доступ настроен' },
|
||
{ id: 16, date: '2026-08-10', applicant: 'Токтаев К.С.', department: 'Служба безопасности', type: 'hardware', typeName: 'Оборудование', priority: 'high', priorityName: 'Высокий', status: 'decided', statusName: 'Решение принято', decision: 'approved', decisionName: 'Принято', comment: 'Оборудование закуплено' },
|
||
{ id: 17, date: '2026-08-20', applicant: 'Лебедева М.А.', department: 'Бухгалтерия', type: 'other', typeName: 'Другое', priority: 'low', priorityName: 'Низкий', status: 'new', statusName: 'Новый', decision: null, comment: null },
|
||
{ id: 18, date: '2026-09-01', applicant: 'Нурсултанов Б.Т.', department: 'Отдел маркетинга', type: 'integration', typeName: 'Интеграция', priority: 'medium', priorityName: 'Средний', status: 'decided', statusName: 'Решение принято', decision: 'approved', decisionName: 'Принято', comment: 'Интеграция одобрена' },
|
||
{ id: 19, date: '2026-09-05', applicant: 'Васильев П.И.', department: 'Служба поддержки', type: 'hardware', typeName: 'Оборудование', priority: 'low', priorityName: 'Низкий', status: 'new', statusName: 'Новый', decision: null, comment: null }
|
||
],
|
||
'year': [] // Пустой срез для демонстрации пустого состояния
|
||
};
|
||
|
||
// Загрузка сохранённых решений из localStorage
|
||
function loadSavedDecisions() {
|
||
const saved = localStorage.getItem('requestDecisions');
|
||
return saved ? JSON.parse(saved) : {};
|
||
}
|
||
|
||
// Сохранение решения в localStorage
|
||
function saveDecision(requestId, decision, comment) {
|
||
const decisions = loadSavedDecisions();
|
||
decisions[requestId] = { decision, comment, date: new Date().toISOString() };
|
||
localStorage.setItem('requestDecisions', JSON.stringify(decisions));
|
||
}
|
||
|
||
// Применение сохранённых решений к демо-данным
|
||
function applySavedDecisions(data) {
|
||
const decisions = loadSavedDecisions();
|
||
return data.map(item => {
|
||
if (decisions[item.id]) {
|
||
const saved = decisions[item.id];
|
||
return {
|
||
...item,
|
||
decision: saved.decision,
|
||
decisionName: saved.decision === 'approved' ? 'Принято' : saved.decision === 'rejected' ? 'Отклонено' : 'На доработку',
|
||
status: 'decided',
|
||
statusName: 'Решение принято',
|
||
comment: saved.comment
|
||
};
|
||
}
|
||
return item;
|
||
});
|
||
}
|
||
|
||
// Получение данных по фильтру периода
|
||
function getDataForPeriod(period) {
|
||
const baseData = DEMO_DATA[period] || [];
|
||
return applySavedDecisions(baseData);
|
||
}
|
||
|
||
// Фильтрация данных
|
||
function filterData(data, filters) {
|
||
let filtered = [...data];
|
||
|
||
if (filters.status !== 'all') {
|
||
if (filters.status === 'new') {
|
||
filtered = filtered.filter(item => item.status === 'new');
|
||
} else if (filters.status === 'review') {
|
||
filtered = filtered.filter(item => item.status === 'review');
|
||
} else if (filters.status === 'decided') {
|
||
filtered = filtered.filter(item => item.status === 'decided' && item.decision === 'approved');
|
||
} else if (filters.status === 'rejected') {
|
||
filtered = filtered.filter(item => item.status === 'decided' && item.decision === 'rejected');
|
||
}
|
||
}
|
||
|
||
if (filters.type !== 'all') {
|
||
filtered = filtered.filter(item => item.type === filters.type);
|
||
}
|
||
|
||
if (filters.priority !== 'all') {
|
||
filtered = filtered.filter(item => item.priority === filters.priority);
|
||
}
|
||
|
||
return filtered;
|
||
}
|
||
|
||
// Расчёт KPI
|
||
function calculateKPI(data) {
|
||
const total = data.length;
|
||
const review = data.filter(item => item.status === 'review').length;
|
||
const approved = data.filter(item => item.decision === 'approved').length;
|
||
const rejected = data.filter(item => item.decision === 'rejected').length;
|
||
return { total, review, approved, rejected };
|
||
}
|
||
|
||
// Отрисовка KPI
|
||
function renderKPI(kpi) {
|
||
document.getElementById('kpi-total').textContent = kpi.total;
|
||
document.getElementById('kpi-review').textContent = kpi.review;
|
||
document.getElementById('kpi-approved').textContent = kpi.approved;
|
||
document.getElementById('kpi-rejected').textContent = kpi.rejected;
|
||
}
|
||
|
||
// Цвет статуса
|
||
function getStatusChip(status, decision) {
|
||
const chips = {
|
||
'new': '<span class="kt-ai-chip" data-tone="blue">Новый</span>',
|
||
'review': '<span class="kt-ai-chip" data-tone="orange">На рассмотрении</span>',
|
||
'decided': decision === 'approved'
|
||
? '<span class="kt-ai-chip" data-tone="green">Принято</span>'
|
||
: decision === 'rejected'
|
||
? '<span class="kt-ai-chip" data-tone="red">Отклонено</span>'
|
||
: '<span class="kt-ai-chip" data-tone="orange">На доработку</span>'
|
||
};
|
||
return chips[status] || chips['new'];
|
||
}
|
||
|
||
// Цвет приоритета
|
||
function getPriorityChip(priority) {
|
||
const chips = {
|
||
'high': '<span class="kt-ai-chip" data-tone="red">Высокий</span>',
|
||
'medium': '<span class="kt-ai-chip" data-tone="orange">Средний</span>',
|
||
'low': '<span class="kt-ai-chip" data-tone="blue">Низкий</span>'
|
||
};
|
||
return chips[priority] || chips['medium'];
|
||
}
|
||
|
||
// Отрисовка таблицы
|
||
function renderTable(data) {
|
||
const tbody = document.getElementById('requests-table-body');
|
||
const emptyState = document.getElementById('empty-state');
|
||
|
||
if (data.length === 0) {
|
||
tbody.innerHTML = '';
|
||
emptyState.style.display = 'block';
|
||
return;
|
||
}
|
||
|
||
emptyState.style.display = 'none';
|
||
tbody.innerHTML = data.map(item => `
|
||
<tr>
|
||
<td>${item.date}</td>
|
||
<td>
|
||
<div style="font-weight: var(--kt-ai-weight-medium)">${item.applicant}</div>
|
||
<div style="font-size: var(--kt-ai-text-xs); color: var(--kt-ai-fg-muted)">${item.department}</div>
|
||
</td>
|
||
<td>${item.typeName}</td>
|
||
<td>${getPriorityChip(item.priority)}</td>
|
||
<td>${getStatusChip(item.status, item.decision)}</td>
|
||
<td>${item.decision ? (item.decision === 'approved' ? '<span style="color: var(--kt-ai-status-ok-fg)">✓</span>' : '<span style="color: var(--kt-ai-status-risk-fg)">✗</span>') : '—'}</td>
|
||
<td class="num">
|
||
<button class="kt-ai-btn" data-variant="ghost" data-size="sm" onclick="openModal(${item.id})">Рассмотреть</button>
|
||
</td>
|
||
</tr>
|
||
`).join('');
|
||
}
|
||
|
||
// Текущие фильтры
|
||
let currentFilters = {
|
||
period: '7d',
|
||
status: 'all',
|
||
type: 'all',
|
||
priority: 'all'
|
||
};
|
||
|
||
let currentRequestId = null;
|
||
|
||
// Обновление отображения
|
||
function updateDisplay() {
|
||
const data = getDataForPeriod(currentFilters.period);
|
||
const filtered = filterData(data, currentFilters);
|
||
const kpi = calculateKPI(filtered);
|
||
|
||
renderKPI(kpi);
|
||
renderTable(filtered);
|
||
}
|
||
|
||
// Открытие модального окна
|
||
function openModal(requestId) {
|
||
const data = getDataForPeriod(currentFilters.period);
|
||
const request = data.find(item => item.id === requestId);
|
||
|
||
if (!request) return;
|
||
|
||
currentRequestId = requestId;
|
||
|
||
const details = document.getElementById('request-details');
|
||
details.innerHTML = `
|
||
<div class="kt-ai-info-row">
|
||
<span class="kt-ai-info-label">Дата</span>
|
||
<span class="kt-ai-info-value">${request.date}</span>
|
||
</div>
|
||
<div class="kt-ai-info-row">
|
||
<span class="kt-ai-info-label">Заявитель</span>
|
||
<span class="kt-ai-info-value">${request.applicant}</span>
|
||
</div>
|
||
<div class="kt-ai-info-row">
|
||
<span class="kt-ai-info-label">Отдел</span>
|
||
<span class="kt-ai-info-value">${request.department}</span>
|
||
</div>
|
||
<div class="kt-ai-info-row">
|
||
<span class="kt-ai-info-label">Тип заявки</span>
|
||
<span class="kt-ai-info-value">${request.typeName}</span>
|
||
</div>
|
||
<div class="kt-ai-info-row">
|
||
<span class="kt-ai-info-label">Приоритет</span>
|
||
<span class="kt-ai-info-value">${getPriorityChip(request.priority)}</span>
|
||
</div>
|
||
<div class="kt-ai-info-row">
|
||
<span class="kt-ai-info-label">Статус</span>
|
||
<span class="kt-ai-info-value">${getStatusChip(request.status, request.decision)}</span>
|
||
</div>
|
||
`;
|
||
|
||
document.getElementById('decision-comment').value = request.comment || '';
|
||
document.getElementById('modal-overlay').classList.add('in');
|
||
}
|
||
|
||
// Закрытие модального окна
|
||
function closeModal() {
|
||
document.getElementById('modal-overlay').classList.remove('in');
|
||
currentRequestId = null;
|
||
}
|
||
|
||
// Сохранение решения
|
||
function makeDecision(decision) {
|
||
if (!currentRequestId) return;
|
||
|
||
const comment = document.getElementById('decision-comment').value.trim();
|
||
saveDecision(currentRequestId, decision, comment);
|
||
|
||
closeModal();
|
||
updateDisplay();
|
||
|
||
// Показать уведомление
|
||
alert('Решение сохранено!');
|
||
}
|
||
|
||
// Обработчики фильтров
|
||
document.getElementById('filter-period').addEventListener('change', (e) => {
|
||
currentFilters.period = e.target.value;
|
||
updateDisplay();
|
||
});
|
||
|
||
document.getElementById('filter-status').addEventListener('change', (e) => {
|
||
currentFilters.status = e.target.value;
|
||
updateDisplay();
|
||
});
|
||
|
||
document.getElementById('filter-type').addEventListener('change', (e) => {
|
||
currentFilters.type = e.target.value;
|
||
updateDisplay();
|
||
});
|
||
|
||
document.getElementById('filter-priority').addEventListener('change', (e) => {
|
||
currentFilters.priority = e.target.value;
|
||
updateDisplay();
|
||
});
|
||
|
||
// Обработчики модального окна
|
||
document.getElementById('modal-close').addEventListener('click', closeModal);
|
||
document.getElementById('modal-overlay').addEventListener('click', (e) => {
|
||
if (e.target === e.currentTarget) closeModal();
|
||
});
|
||
|
||
document.getElementById('btn-approve').addEventListener('click', () => makeDecision('approved'));
|
||
document.getElementById('btn-reject').addEventListener('click', () => makeDecision('rejected'));
|
||
document.getElementById('btn-rework').addEventListener('click', () => makeDecision('rework'));
|
||
|
||
// Инициализация
|
||
updateDisplay();
|
||
|
||
// Сделать функцию доступной глобально
|
||
window.openModal = openModal;
|
||
</script>
|
||
|
||
</body>
|
||
</html>
|