484 lines
25 KiB
HTML
484 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 { background: var(--kt-ai-bg); color: var(--kt-ai-fg); font-family: var(--kt-ai-font-sans); min-height: 100vh; }
|
||
.kt-ai-app-layout { display: grid; grid-template-columns: 240px 1fr; min-height: 100vh; }
|
||
.kt-ai-sidebar { background: var(--kt-ai-bg-soft); border-right: 1px solid var(--kt-ai-border); padding: var(--kt-ai-space-5); }
|
||
.kt-ai-main { padding: var(--kt-ai-space-6); overflow: auto; }
|
||
.kt-ai-sidebar-title { font-size: var(--kt-ai-text-sm); font-weight: var(--kt-ai-weight-semibold); color: var(--kt-ai-fg-muted); text-transform: uppercase; letter-spacing: 0.04em; margin-bottom: var(--kt-ai-space-4); }
|
||
.kt-ai-nav-item { display: flex; align-items: center; gap: var(--kt-ai-space-3); padding: var(--kt-ai-space-3) var(--kt-ai-space-3); border-radius: var(--kt-ai-radius-md); color: var(--kt-ai-fg); text-decoration: none; margin-bottom: var(--kt-ai-space-1); }
|
||
.kt-ai-nav-item:hover { background: var(--kt-ai-card-bg); }
|
||
.kt-ai-nav-item.active { background: var(--kt-ai-primary); color: #fff; }
|
||
.kt-ai-nav-icon { width: 20px; height: 20px; flex-shrink: 0; }
|
||
.kt-ai-topbar { display: flex; align-items: center; justify-content: space-between; margin-bottom: var(--kt-ai-space-6); padding-bottom: var(--kt-ai-space-4); border-bottom: 1px solid var(--kt-ai-border); }
|
||
.kt-ai-topbar-left { display: flex; align-items: center; gap: var(--kt-ai-space-4); }
|
||
.kt-ai-page-title { font-size: var(--kt-ai-text-2xl); font-weight: var(--kt-ai-weight-semibold); }
|
||
.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); line-height: 1.2; }
|
||
.kt-ai-kpi .label { font-size: var(--kt-ai-text-sm); color: var(--kt-ai-fg-muted); margin-top: var(--kt-ai-space-2); }
|
||
.kt-ai-kpi .hint { font-size: var(--kt-ai-text-xs); color: var(--kt-ai-fg-faint); margin-top: var(--kt-ai-space-1); }
|
||
.kt-ai-kpi.trend-up .hint { color: var(--kt-ai-status-ok-fg); }
|
||
.kt-ai-kpi.trend-down .hint { color: var(--kt-ai-status-risk-fg); }
|
||
.kt-ai-cards-row { display: grid; grid-template-columns: 2fr 1fr; gap: var(--kt-ai-space-4); margin-bottom: var(--kt-ai-space-6); }
|
||
.kt-ai-card { 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-card-title { font-size: var(--kt-ai-text-lg); font-weight: var(--kt-ai-weight-semibold); margin-bottom: var(--kt-ai-space-4); }
|
||
.kt-ai-table-wrap { overflow-x: auto; }
|
||
.kt-ai-table { width: 100%; border-collapse: collapse; }
|
||
.kt-ai-table th { text-align: left; font-size: var(--kt-ai-text-xs); color: var(--kt-ai-fg-muted); text-transform: uppercase; letter-spacing: 0.04em; padding: var(--kt-ai-space-3) var(--kt-ai-space-3); border-bottom: 1px solid var(--kt-ai-border); }
|
||
.kt-ai-table td { padding: var(--kt-ai-space-3); border-bottom: 1px solid var(--kt-ai-border); font-size: var(--kt-ai-text-sm); }
|
||
.kt-ai-table tr:hover { background: var(--kt-ai-bg-soft); }
|
||
.kt-ai-table-num { text-align: right; font-variant-numeric: tabular-nums; }
|
||
.kt-ai-filters { display: flex; gap: var(--kt-ai-space-3); margin-bottom: var(--kt-ai-space-4); flex-wrap: wrap; align-items: center; }
|
||
.kt-ai-filter-group { display: flex; align-items: center; gap: var(--kt-ai-space-2); }
|
||
.kt-ai-filter-label { font-size: var(--kt-ai-text-sm); color: var(--kt-ai-fg-muted); }
|
||
.kt-ai-select { padding: var(--kt-ai-space-2) var(--kt-ai-space-3); border-radius: var(--kt-ai-radius-md); border: 1px solid var(--kt-ai-border); background: var(--kt-ai-bg); color: var(--kt-ai-fg); font-size: var(--kt-ai-text-sm); }
|
||
.kt-ai-operator-avatar { width: 32px; height: 32px; border-radius: var(--kt-ai-radius-full); background: var(--kt-ai-primary); color: #fff; display: flex; align-items: center; justify-content: center; font-size: var(--kt-ai-text-sm); font-weight: var(--kt-ai-weight-semibold); }
|
||
.kt-ai-chart-container { height: 200px; display: flex; align-items: flex-end; gap: 8px; padding-top: var(--kt-ai-space-4); }
|
||
.kt-ai-chart-bar { flex: 1; background: var(--kt-ai-chart-blue); border-radius: var(--kt-ai-radius-md) var(--kt-ai-radius-md) 0 0; min-width: 24px; position: relative; cursor: pointer; transition: opacity 0.2s; }
|
||
.kt-ai-chart-bar:hover { opacity: 0.8; }
|
||
.kt-ai-chart-bar[data-kt-tip]::after { content: attr(data-kt-tip); position: absolute; bottom: 100%; left: 50%; transform: translateX(-50%); background: var(--kt-ai-fg); color: var(--kt-ai-bg); padding: 4px 8px; border-radius: var(--kt-ai-radius-md); font-size: var(--kt-ai-text-xs); white-space: nowrap; opacity: 0; pointer-events: none; transition: opacity 0.2s; margin-bottom: 4px; }
|
||
.kt-ai-chart-bar:hover::after { opacity: 1; }
|
||
.kt-ai-chart-labels { display: flex; gap: 8px; margin-top: var(--kt-ai-space-2); }
|
||
.kt-ai-chart-label { flex: 1; text-align: center; font-size: var(--kt-ai-text-xs); color: var(--kt-ai-fg-muted); min-width: 24px; }
|
||
.kt-ai-empty { text-align: center; padding: var(--kt-ai-space-10); color: var(--kt-ai-fg-muted); }
|
||
.kt-ai-empty-icon { width: 64px; height: 64px; margin-bottom: var(--kt-ai-space-4); opacity: 0.5; }
|
||
.kt-ai-btn-sm { padding: var(--kt-ai-space-1) var(--kt-ai-space-3); font-size: var(--kt-ai-text-xs); }
|
||
.kt-ai-modal-overlay { position: fixed; inset: 0; background: rgba(0,0,0,0.5); display: flex; align-items: center; justify-content: center; z-index: 1000; }
|
||
.kt-ai-modal { background: var(--kt-ai-card-bg); border-radius: var(--kt-ai-radius-2xl); padding: var(--kt-ai-space-6); width: 100%; max-width: 480px; border: 1px solid var(--kt-ai-card-border); }
|
||
.kt-ai-modal-title { font-size: var(--kt-ai-text-xl); font-weight: var(--kt-ai-weight-semibold); margin-bottom: var(--kt-ai-space-4); }
|
||
.kt-ai-field { margin-bottom: var(--kt-ai-space-4); }
|
||
.kt-ai-field label { display: block; font-size: var(--kt-ai-text-sm); color: var(--kt-ai-fg-muted); margin-bottom: var(--kt-ai-space-2); }
|
||
.kt-ai-input, .kt-ai-textarea { width: 100%; padding: var(--kt-ai-space-2) var(--kt-ai-space-3); border-radius: var(--kt-ai-radius-md); border: 1px solid var(--kt-ai-border); background: var(--kt-ai-bg); color: var(--kt-ai-fg); font-size: var(--kt-ai-text-sm); font-family: inherit; }
|
||
.kt-ai-textarea { min-height: 100px; resize: vertical; }
|
||
.kt-ai-modal-actions { display: flex; gap: var(--kt-ai-space-3); justify-content: flex-end; margin-top: var(--kt-ai-space-6); }
|
||
.kt-ai-hidden { display: none !important; }
|
||
</style>
|
||
</head>
|
||
<body class="kt-ai-app" data-kt-data-mode="demo">
|
||
|
||
<div class="kt-ai-app-layout">
|
||
<aside class="kt-ai-sidebar">
|
||
<div class="kt-ai-sidebar-title">Навигация</div>
|
||
<nav>
|
||
<a href="#" class="kt-ai-nav-item active" data-page="dashboard">
|
||
<svg class="kt-ai-nav-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="7" height="7" rx="1"/><rect x="14" y="3" width="7" height="7" rx="1"/><rect x="3" y="14" width="7" height="7" rx="1"/><rect x="14" y="14" width="7" height="7" rx="1"/></svg>
|
||
Дашборд
|
||
</a>
|
||
<a href="#" class="kt-ai-nav-item" data-page="tickets">
|
||
<svg class="kt-ai-nav-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/></svg>
|
||
Заявки
|
||
</a>
|
||
</nav>
|
||
</aside>
|
||
|
||
<main class="kt-ai-main">
|
||
<div class="kt-ai-topbar">
|
||
<div class="kt-ai-topbar-left">
|
||
<img src="https://code.vibe42.kz/tore/aitu/kt-logo.svg" alt="Казахтелеком" height="28">
|
||
<h1 class="kt-ai-page-title">Поддержка клиентов</h1>
|
||
<span class="kt-ai-chip" data-tone="orange">Демо-данные</span>
|
||
</div>
|
||
<div style="display:flex;align-items:center;gap:var(--kt-ai-space-3)">
|
||
<span class="kt-ai-chip" data-tone="green">Онлайн</span>
|
||
<div class="kt-ai-operator-avatar">А</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- DASHBOARD PAGE -->
|
||
<div id="page-dashboard">
|
||
<div class="kt-ai-kpi-strip">
|
||
<div class="kt-ai-kpi trend-up">
|
||
<div class="value" id="kpi-total">0</div>
|
||
<div class="label">Всего обращений</div>
|
||
<div class="hint">↑ 12% за неделю</div>
|
||
</div>
|
||
<div class="kt-ai-kpi trend-up">
|
||
<div class="value" id="kpi-avg-time">0</div>
|
||
<div class="label">Среднее время ответа</div>
|
||
<div class="hint">↓ 8 мин быстрее</div>
|
||
</div>
|
||
<div class="kt-ai-kpi">
|
||
<div class="value" id="kpi-satisfaction">0%</div>
|
||
<div class="label">Удовлетворённость</div>
|
||
<div class="hint">За последний месяц</div>
|
||
</div>
|
||
<div class="kt-ai-kpi">
|
||
<div class="value" id="kpi-active">0</div>
|
||
<div class="label">В работе</div>
|
||
<div class="hint">Текущая нагрузка</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="kt-ai-cards-row">
|
||
<div class="kt-ai-card">
|
||
<h2 class="kt-ai-card-title">Обращения по дням</h2>
|
||
<div class="kt-ai-chart-container" id="chart-bars"></div>
|
||
<div class="kt-ai-chart-labels" id="chart-labels"></div>
|
||
</div>
|
||
<div class="kt-ai-card">
|
||
<h2 class="kt-ai-card-title">По статусам</h2>
|
||
<div style="display:grid;gap:var(--kt-ai-space-3)">
|
||
<div style="display:flex;justify-content:space-between;align-items:center">
|
||
<span style="display:flex;align-items:center;gap:8px"><span class="kt-ai-chip" data-tone="blue">Новые</span></span>
|
||
<strong id="status-new">0</strong>
|
||
</div>
|
||
<div style="display:flex;justify-content:space-between;align-items:center">
|
||
<span style="display:flex;align-items:center;gap:8px"><span class="kt-ai-chip" data-tone="orange">В работе</span></span>
|
||
<strong id="status-progress">0</strong>
|
||
</div>
|
||
<div style="display:flex;justify-content:space-between;align-items:center">
|
||
<span style="display:flex;align-items:center;gap:8px"><span class="kt-ai-chip" data-tone="green">Завершённые</span></span>
|
||
<strong id="status-done">0</strong>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- TICKETS PAGE -->
|
||
<div id="page-tickets" class="kt-ai-hidden">
|
||
<div class="kt-ai-card">
|
||
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:var(--kt-ai-space-4)">
|
||
<h2 class="kt-ai-card-title" style="margin:0">Все заявки</h2>
|
||
<button class="kt-ai-btn" data-variant="primary" id="btn-add-ticket">+ Новая заявка</button>
|
||
</div>
|
||
|
||
<div class="kt-ai-filters">
|
||
<div class="kt-ai-filter-group">
|
||
<span class="kt-ai-filter-label">Статус:</span>
|
||
<select class="kt-ai-select" id="filter-status">
|
||
<option value="all">Все</option>
|
||
<option value="new">Новые</option>
|
||
<option value="progress">В работе</option>
|
||
<option value="done">Завершённые</option>
|
||
</select>
|
||
</div>
|
||
<div class="kt-ai-filter-group">
|
||
<span class="kt-ai-filter-label">Оператор:</span>
|
||
<select class="kt-ai-select" id="filter-operator">
|
||
<option value="all">Все</option>
|
||
</select>
|
||
</div>
|
||
<div class="kt-ai-filter-group">
|
||
<span class="kt-ai-filter-label">Поиск:</span>
|
||
<input type="text" class="kt-ai-input" id="filter-search" placeholder="Клиент или тема" style="width:200px">
|
||
</div>
|
||
</div>
|
||
|
||
<div class="kt-ai-table-wrap">
|
||
<table class="kt-ai-table">
|
||
<thead>
|
||
<tr>
|
||
<th>ID</th>
|
||
<th>Клиент</th>
|
||
<th>Тема</th>
|
||
<th>Оператор</th>
|
||
<th>Статус</th>
|
||
<th>Дата</th>
|
||
<th>Действия</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="tickets-body"></tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<div id="empty-state" class="kt-ai-empty kt-ai-hidden">
|
||
<svg class="kt-ai-empty-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/></svg>
|
||
<p>Заявок не найдено</p>
|
||
<p style="font-size:var(--kt-ai-text-sm);margin-top:var(--kt-ai-space-1)">Измените фильтры или создайте новую заявку</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</main>
|
||
</div>
|
||
|
||
<!-- ADD TICKET MODAL -->
|
||
<div id="modal-add" class="kt-ai-modal-overlay kt-ai-hidden">
|
||
<div class="kt-ai-modal">
|
||
<h3 class="kt-ai-modal-title">Новая заявка</h3>
|
||
<form id="form-add-ticket">
|
||
<div class="kt-ai-field">
|
||
<label>Имя клиента</label>
|
||
<input type="text" class="kt-ai-input" name="client" required>
|
||
</div>
|
||
<div class="kt-ai-field">
|
||
<label>Тема обращения</label>
|
||
<input type="text" class="kt-ai-input" name="subject" required>
|
||
</div>
|
||
<div class="kt-ai-field">
|
||
<label>Описание</label>
|
||
<textarea class="kt-ai-textarea" name="description" required></textarea>
|
||
</div>
|
||
<div class="kt-ai-field">
|
||
<label>Оператор</label>
|
||
<select class="kt-ai-select" name="operator" required></select>
|
||
</div>
|
||
<div class="kt-ai-modal-actions">
|
||
<button type="button" class="kt-ai-btn" data-variant="ghost" onclick="closeModal()">Отмена</button>
|
||
<button type="submit" class="kt-ai-btn" data-variant="primary">Создать</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
// Демо-данные и состояние
|
||
const OPERATORS = [
|
||
{ id: 'a', name: 'Анна К.', avatar: 'А' },
|
||
{ id: 'b', name: 'Бауыржан М.', avatar: 'Б' },
|
||
{ id: 'c', name: 'Светлана П.', avatar: 'С' },
|
||
{ id: 'd', name: 'Данияр Т.', avatar: 'Д' }
|
||
];
|
||
|
||
const DEMO_TICKETS = [
|
||
{ id: 1001, client: 'Иванов Петр', subject: 'Не работает интернет', description: 'Пропал сигнал с утра', operator: 'a', status: 'new', date: '2026-09-15', dayIndex: 0 },
|
||
{ id: 1002, client: 'Смирнова Елена', subject: 'Подключение ТВ', description: 'Хочу подключить цифровое ТВ', operator: 'b', status: 'progress', date: '2026-09-14', dayIndex: 1 },
|
||
{ id: 1003, client: 'Алиев Нурлан', subject: 'Смена тарифа', description: 'Перейти на безлимитный', operator: 'a', status: 'done', date: '2026-09-14', dayIndex: 1 },
|
||
{ id: 1004, client: 'Козлова Мария', subject: 'Ошибка в счёте', description: 'Неверная сумма в квитанции', operator: 'c', status: 'progress', date: '2026-09-13', dayIndex: 2 },
|
||
{ id: 1005, client: 'Омаров Асет', subject: 'Переезд', description: 'Перенос номера на новый адрес', operator: 'd', status: 'done', date: '2026-09-13', dayIndex: 2 },
|
||
{ id: 1006, client: 'Волкова Ольга', subject: 'Не приходит SMS', description: 'Коды подтверждения не доходят', operator: 'b', status: 'new', date: '2026-09-12', dayIndex: 3 },
|
||
{ id: 1007, client: 'Ибрагимов Рустам', subject: 'Роуминг', description: 'Настроить роуминг для Казахстана', operator: 'c', status: 'done', date: '2026-09-12', dayIndex: 3 },
|
||
{ id: 1008, client: 'Пак Юлия', subject: 'Блокировка', description: 'Разблокировать номер', operator: 'a', status: 'done', date: '2026-09-11', dayIndex: 4 },
|
||
{ id: 1009, client: 'Дюсенбаев Ержан', subject: 'Оплата', description: 'Не проходит оплата с карты', operator: 'd', status: 'progress', date: '2026-09-11', dayIndex: 4 },
|
||
{ id: 1010, client: 'Кузнецов Андрей', subject: 'Новый номер', description: 'Подобрать красивый номер', operator: 'b', status: 'new', date: '2026-09-10', dayIndex: 5 },
|
||
{ id: 1011, client: 'Абдрасулова Айгуль', subject: 'Домашний телефон', description: 'Подключить домашний телефон', operator: 'c', status: 'done', date: '2026-09-10', dayIndex: 5 },
|
||
{ id: 1012, client: 'Ли Дмитрий', subject: 'Wi-Fi роутер', description: 'Настройка роутера', operator: 'a', status: 'done', date: '2026-09-09', dayIndex: 6 }
|
||
];
|
||
|
||
const DAY_NAMES = ['Пн', 'Вт', 'Ср', 'Чт', 'Пт', 'Сб', 'Вс'];
|
||
const CHART_COLORS = ['var(--kt-ai-chart-blue)', 'var(--kt-ai-chart-green)', 'var(--kt-ai-chart-orange)', 'var(--kt-ai-chart-pink)', 'var(--kt-ai-chart-red)', 'var(--kt-ai-chart-salmon)', 'var(--kt-ai-chart-blue)'];
|
||
|
||
let tickets = [];
|
||
|
||
// Инициализация
|
||
function init() {
|
||
loadTickets();
|
||
renderOperatorFilter();
|
||
renderOperatorSelect();
|
||
updateDashboard();
|
||
renderTickets();
|
||
setupEventListeners();
|
||
}
|
||
|
||
function loadTickets() {
|
||
const stored = localStorage.getItem('kt_tickets');
|
||
if (stored) {
|
||
tickets = JSON.parse(stored);
|
||
} else {
|
||
tickets = [...DEMO_TICKETS];
|
||
saveTickets();
|
||
}
|
||
}
|
||
|
||
function saveTickets() {
|
||
localStorage.setItem('kt_tickets', JSON.stringify(tickets));
|
||
}
|
||
|
||
function renderOperatorFilter() {
|
||
const select = document.getElementById('filter-operator');
|
||
OPERATORS.forEach(op => {
|
||
const opt = document.createElement('option');
|
||
opt.value = op.id;
|
||
opt.textContent = op.name;
|
||
select.appendChild(opt);
|
||
});
|
||
}
|
||
|
||
function renderOperatorSelect() {
|
||
const selects = document.querySelectorAll('select[name="operator"]');
|
||
selects.forEach(select => {
|
||
select.innerHTML = '';
|
||
OPERATORS.forEach(op => {
|
||
const opt = document.createElement('option');
|
||
opt.value = op.id;
|
||
opt.textContent = op.name;
|
||
select.appendChild(opt);
|
||
});
|
||
});
|
||
}
|
||
|
||
function updateDashboard() {
|
||
const total = tickets.length;
|
||
const active = tickets.filter(t => t.status === 'new' || t.status === 'progress').length;
|
||
const done = tickets.filter(t => t.status === 'done').length;
|
||
const newCount = tickets.filter(t => t.status === 'new').length;
|
||
const progressCount = tickets.filter(t => t.status === 'progress').length;
|
||
|
||
document.getElementById('kpi-total').textContent = total;
|
||
document.getElementById('kpi-active').textContent = active;
|
||
document.getElementById('kpi-satisfaction').textContent = '94%';
|
||
document.getElementById('kpi-avg-time').textContent = '24 мин';
|
||
|
||
document.getElementById('status-new').textContent = newCount;
|
||
document.getElementById('status-progress').textContent = progressCount;
|
||
document.getElementById('status-done').textContent = done;
|
||
|
||
renderChart();
|
||
}
|
||
|
||
function renderChart() {
|
||
const byDay = [0, 0, 0, 0, 0, 0, 0];
|
||
tickets.forEach(t => {
|
||
if (t.dayIndex >= 0 && t.dayIndex < 7) byDay[t.dayIndex]++;
|
||
});
|
||
|
||
const max = Math.max(...byDay, 1);
|
||
const barsContainer = document.getElementById('chart-bars');
|
||
const labelsContainer = document.getElementById('chart-labels');
|
||
barsContainer.innerHTML = '';
|
||
labelsContainer.innerHTML = '';
|
||
|
||
byDay.forEach((count, i) => {
|
||
const height = (count / max) * 160 + 20;
|
||
const bar = document.createElement('div');
|
||
bar.className = 'kt-ai-chart-bar';
|
||
bar.style.height = height + 'px';
|
||
bar.style.background = CHART_COLORS[i];
|
||
bar.setAttribute('data-kt-tip', count + ' обращений');
|
||
barsContainer.appendChild(bar);
|
||
|
||
const label = document.createElement('div');
|
||
label.className = 'kt-ai-chart-label';
|
||
label.textContent = DAY_NAMES[i];
|
||
labelsContainer.appendChild(label);
|
||
});
|
||
}
|
||
|
||
function renderTickets() {
|
||
const statusFilter = document.getElementById('filter-status').value;
|
||
const operatorFilter = document.getElementById('filter-operator').value;
|
||
const searchFilter = document.getElementById('filter-search').value.toLowerCase();
|
||
|
||
let filtered = tickets.filter(t => {
|
||
if (statusFilter !== 'all' && t.status !== statusFilter) return false;
|
||
if (operatorFilter !== 'all' && t.operator !== operatorFilter) return false;
|
||
if (searchFilter && !t.client.toLowerCase().includes(searchFilter) && !t.subject.toLowerCase().includes(searchFilter)) return false;
|
||
return true;
|
||
});
|
||
|
||
const tbody = document.getElementById('tickets-body');
|
||
const emptyState = document.getElementById('empty-state');
|
||
|
||
if (filtered.length === 0) {
|
||
tbody.innerHTML = '';
|
||
emptyState.classList.remove('kt-ai-hidden');
|
||
} else {
|
||
emptyState.classList.add('kt-ai-hidden');
|
||
tbody.innerHTML = filtered.map(t => {
|
||
const op = OPERATORS.find(o => o.id === t.operator);
|
||
const statusChip = getStatusChip(t.status);
|
||
return `<tr>
|
||
<td>#${t.id}</td>
|
||
<td>${t.client}</td>
|
||
<td>${t.subject}</td>
|
||
<td><div class="kt-ai-operator-avatar" style="width:24px;height:24px;font-size:12px;display:inline-flex;margin-right:6px">${op.avatar}</div>${op.name}</td>
|
||
<td>${statusChip}</td>
|
||
<td class="kt-ai-table-num">${formatDate(t.date)}</td>
|
||
<td>
|
||
${t.status !== 'done' ? `<button class="kt-ai-btn kt-ai-btn-sm" data-variant="ghost" onclick="completeTicket(${t.id})">Завершить</button>` : ''}
|
||
<button class="kt-ai-btn kt-ai-btn-sm" data-variant="ghost" onclick="deleteTicket(${t.id})">Удалить</button>
|
||
</td>
|
||
</tr>`;
|
||
}).join('');
|
||
}
|
||
}
|
||
|
||
function getStatusChip(status) {
|
||
const map = {
|
||
new: '<span class="kt-ai-chip" data-tone="blue">Новая</span>',
|
||
progress: '<span class="kt-ai-chip" data-tone="orange">В работе</span>',
|
||
done: '<span class="kt-ai-chip" data-tone="green">Завершена</span>'
|
||
};
|
||
return map[status] || '';
|
||
}
|
||
|
||
function formatDate(dateStr) {
|
||
const d = new Date(dateStr);
|
||
return d.toLocaleDateString('ru-RU', { day: 'numeric', month: 'short' });
|
||
}
|
||
|
||
function setupEventListeners() {
|
||
document.getElementById('filter-status').addEventListener('change', renderTickets);
|
||
document.getElementById('filter-operator').addEventListener('change', renderTickets);
|
||
document.getElementById('filter-search').addEventListener('input', renderTickets);
|
||
|
||
document.getElementById('btn-add-ticket').addEventListener('click', () => {
|
||
document.getElementById('modal-add').classList.remove('kt-ai-hidden');
|
||
});
|
||
|
||
document.getElementById('form-add-ticket').addEventListener('submit', (e) => {
|
||
e.preventDefault();
|
||
const form = e.target;
|
||
const newTicket = {
|
||
id: Math.max(...tickets.map(t => t.id), 1000) + 1,
|
||
client: form.client.value,
|
||
subject: form.subject.value,
|
||
description: form.description.value,
|
||
operator: form.operator.value,
|
||
status: 'new',
|
||
date: new Date().toISOString().split('T')[0],
|
||
dayIndex: new Date().getDay() === 0 ? 6 : new Date().getDay() - 1
|
||
};
|
||
tickets.unshift(newTicket);
|
||
saveTickets();
|
||
updateDashboard();
|
||
renderTickets();
|
||
closeModal();
|
||
form.reset();
|
||
});
|
||
|
||
document.querySelectorAll('.kt-ai-nav-item').forEach(item => {
|
||
item.addEventListener('click', (e) => {
|
||
e.preventDefault();
|
||
const page = item.dataset.page;
|
||
document.querySelectorAll('.kt-ai-nav-item').forEach(i => i.classList.remove('active'));
|
||
item.classList.add('active');
|
||
document.getElementById('page-dashboard').classList.add('kt-ai-hidden');
|
||
document.getElementById('page-tickets').classList.add('kt-ai-hidden');
|
||
document.getElementById('page-' + page).classList.remove('kt-ai-hidden');
|
||
});
|
||
});
|
||
}
|
||
|
||
function closeModal() {
|
||
document.getElementById('modal-add').classList.add('kt-ai-hidden');
|
||
}
|
||
|
||
function completeTicket(id) {
|
||
const ticket = tickets.find(t => t.id === id);
|
||
if (ticket) {
|
||
ticket.status = 'done';
|
||
saveTickets();
|
||
updateDashboard();
|
||
renderTickets();
|
||
}
|
||
}
|
||
|
||
function deleteTicket(id) {
|
||
if (confirm('Удалить заявку #' + id + '?')) {
|
||
tickets = tickets.filter(t => t.id !== id);
|
||
saveTickets();
|
||
updateDashboard();
|
||
renderTickets();
|
||
}
|
||
}
|
||
|
||
window.completeTicket = completeTicket;
|
||
window.deleteTicket = deleteTicket;
|
||
window.closeModal = closeModal;
|
||
|
||
init();
|
||
</script>
|
||
</body>
|
||
</html>
|