prilozhenie-sluzhbe-podderzh/index.html

497 lines
28 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!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>
a.kt-ai-btn { text-decoration: none; }
body.kt-ai-app { min-height: 100vh; display: flex; flex-direction: column; }
.app-content { flex: 1; padding-bottom: var(--kt-ai-space-8); }
.kpi-strip { display: flex; gap: var(--kt-ai-space-4); flex-wrap: wrap; margin-bottom: var(--kt-ai-space-6); }
.kpi { flex: 1; min-width: 160px; 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); }
.kpi .value { font-size: 28px; font-weight: var(--kt-ai-weight-bold); color: var(--kt-ai-fg); line-height: 1.2; }
.kpi .label { font-size: 13px; color: var(--kt-ai-fg-muted); margin-top: var(--kt-ai-space-1); }
.kpi .hint { font-size: 12px; color: var(--kt-ai-fg-faint); margin-top: var(--kt-ai-space-2); }
.chart-wrap { 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); margin-bottom: var(--kt-ai-space-6); }
.chart { display: flex; align-items: flex-end; gap: var(--kt-ai-space-3); height: 200px; padding-top: var(--kt-ai-space-4); }
.chart-bar { flex: 1; background: var(--kt-ai-chart-blue); border-radius: var(--kt-ai-radius-md); min-width: 24px; position: relative; transition: opacity 0.2s; cursor: pointer; }
.chart-bar:hover { opacity: 0.8; }
.chart-bar[data-kt-tip]::after { content: attr(data-kt-tip); position: absolute; bottom: 100%; left: 50%; transform: translateX(-50%); background: var(--kt-ai-bg); border: 1px solid var(--kt-ai-border); border-radius: var(--kt-ai-radius-md); padding: 4px 8px; font-size: 12px; white-space: nowrap; opacity: 0; pointer-events: none; transition: opacity 0.2s; margin-bottom: 6px; }
.chart-bar:hover::after { opacity: 1; }
.chart-labels { display: flex; gap: var(--kt-ai-space-3); margin-top: var(--kt-ai-space-3); }
.chart-label { flex: 1; min-width: 24px; font-size: 11px; color: var(--kt-ai-fg-muted); text-align: center; }
.filters { display: flex; gap: var(--kt-ai-space-3); flex-wrap: wrap; align-items: center; margin-bottom: var(--kt-ai-space-5); }
.filter-chip { padding: 6px 12px; border-radius: var(--kt-ai-radius-full); font-size: 13px; cursor: pointer; border: 1px solid var(--kt-ai-border); background: var(--kt-ai-card-bg); color: var(--kt-ai-fg-muted); transition: all 0.2s; }
.filter-chip:hover { border-color: var(--kt-ai-primary); }
.filter-chip.active { background: var(--kt-ai-primary); color: var(--kt-ai-fg); border-color: var(--kt-ai-primary); }
.table-wrap { background: var(--kt-ai-card-bg); border: 1px solid var(--kt-ai-card-border); border-radius: var(--kt-ai-radius-xl); overflow: hidden; }
.table { width: 100%; border-collapse: collapse; }
.table th { text-align: left; font-size: 12px; font-weight: var(--kt-ai-weight-medium); color: var(--kt-ai-fg-muted); padding: var(--kt-ai-space-3) var(--kt-ai-space-4); border-bottom: 1px solid var(--kt-ai-border); background: var(--kt-ai-bg-soft); }
.table td { padding: var(--kt-ai-space-4); border-bottom: 1px solid var(--kt-ai-border); font-size: 14px; color: var(--kt-ai-fg); }
.table tbody tr { cursor: pointer; transition: background 0.2s; }
.table tbody tr:hover { background: var(--kt-ai-bg-soft); }
.table tbody tr:last-child td { border-bottom: none; }
.table .num { text-align: right; font-variant-numeric: tabular-nums; }
.status { display: inline-flex; align-items: center; gap: 6px; padding: 4px 10px; border-radius: var(--kt-ai-radius-full); font-size: 12px; font-weight: var(--kt-ai-weight-medium); }
.status.new { background: var(--kt-ai-chart-blue); color: var(--kt-ai-fg); }
.status.progress { background: var(--kt-ai-chart-orange); color: var(--kt-ai-fg); }
.status.done { background: var(--kt-ai-chart-green); color: var(--kt-ai-fg); }
.status.overdue { background: var(--kt-ai-chart-red); color: var(--kt-ai-fg); }
.priority { display: inline-flex; gap: 3px; }
.priority-dot { width: 6px; height: 6px; border-radius: 50%; background: var(--kt-ai-fg-faint); }
.priority-dot.active.low { background: var(--kt-ai-chart-green); }
.priority-dot.active.medium { background: var(--kt-ai-chart-orange); }
.priority-dot.active.high { background: var(--kt-ai-chart-red); }
.drawer-overlay { position: fixed; inset: 0; background: rgba(0,0,0,0.4); opacity: 0; pointer-events: none; transition: opacity 0.3s; z-index: 100; }
.drawer-overlay.open { opacity: 1; pointer-events: auto; }
.drawer { position: fixed; right: 0; top: 0; bottom: 0; width: 480px; max-width: 100vw; background: var(--kt-ai-bg); transform: translateX(100%); transition: transform 0.3s; z-index: 101; display: flex; flex-direction: column; }
.drawer.open { transform: translateX(0); }
.drawer-header { padding: var(--kt-ai-space-5); border-bottom: 1px solid var(--kt-ai-border); display: flex; align-items: center; justify-content: space-between; }
.drawer-header h2 { font-size: 18px; font-weight: var(--kt-ai-weight-semibold); margin: 0; }
.drawer-close { background: none; border: none; font-size: 24px; color: var(--kt-ai-fg-muted); cursor: pointer; padding: 4px; }
.drawer-body { flex: 1; overflow-y: auto; padding: var(--kt-ai-space-5); }
.detail-row { display: flex; justify-content: space-between; padding: var(--kt-ai-space-3) 0; border-bottom: 1px solid var(--kt-ai-border); }
.detail-row:last-child { border-bottom: none; }
.detail-label { color: var(--kt-ai-fg-muted); font-size: 13px; }
.detail-value { font-size: 14px; color: var(--kt-ai-fg); text-align: right; }
.empty { text-align: center; padding: var(--kt-ai-space-10); color: var(--kt-ai-fg-muted); }
.empty-icon { font-size: 48px; margin-bottom: var(--kt-ai-space-4); opacity: 0.5; }
.btn-icon { background: none; border: none; font-size: 20px; cursor: pointer; color: var(--kt-ai-fg-muted); padding: 8px; border-radius: var(--kt-ai-radius-md); }
.btn-icon:hover { background: var(--kt-ai-bg-soft); color: var(--kt-ai-fg); }
.header-title { display: flex; align-items: center; gap: var(--kt-ai-space-3); }
.modal-overlay { position: fixed; inset: 0; background: rgba(0,0,0,0.4); display: flex; align-items: center; justify-content: center; z-index: 200; opacity: 0; pointer-events: none; transition: opacity 0.3s; }
.modal-overlay.open { opacity: 1; pointer-events: auto; }
.modal { background: var(--kt-ai-bg); border-radius: var(--kt-ai-radius-2xl); width: 90%; max-width: 520px; max-height: 90vh; overflow-y: auto; }
.modal-header { padding: var(--kt-ai-space-5); border-bottom: 1px solid var(--kt-ai-border); display: flex; justify-content: space-between; align-items: center; }
.modal-body { padding: var(--kt-ai-space-5); }
.modal-footer { padding: var(--kt-ai-space-4) var(--kt-ai-space-5); border-top: 1px solid var(--kt-ai-border); display: flex; gap: var(--kt-ai-space-3); justify-content: flex-end; }
@media (max-width: 768px) {
.kpi-strip { flex-direction: column; }
.filters { flex-direction: column; align-items: stretch; }
.table th, .table td { padding: var(--kt-ai-space-2) var(--kt-ai-space-3); font-size: 13px; }
.drawer { 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="header-title">
<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>
<button class="kt-ai-btn" data-variant="primary" onclick="openNewTicketModal()">+ Заявка</button>
</div>
</header>
<main class="kt-ai-wrap app-content" style="padding-top: var(--kt-ai-space-6)">
<div class="kpi-strip">
<div class="kpi">
<div class="value" id="kpi-total">0</div>
<div class="label">Всего заявок</div>
<div class="hint">за выбранный период</div>
</div>
<div class="kpi">
<div class="value" id="kpi-progress">0</div>
<div class="label">В работе</div>
<div class="hint">требуют внимания</div>
</div>
<div class="kpi">
<div class="value" id="kpi-done">0</div>
<div class="label">Решено</div>
<div class="hint">успешно закрыто</div>
</div>
<div class="kpi">
<div class="value" id="kpi-overdue" style="color: var(--kt-ai-chart-red)">0</div>
<div class="label">Просрочено</div>
<div class="hint">требуют срочного решения</div>
</div>
</div>
<div class="chart-wrap">
<h3 style="margin: 0 0 var(--kt-ai-space-4) 0; font-size: 16px;">Заявки по дням</h3>
<div class="chart" id="chart"></div>
<div class="chart-labels" id="chart-labels"></div>
</div>
<div class="filters">
<span style="font-size: 13px; color: var(--kt-ai-fg-muted); font-weight: var(--kt-ai-weight-medium)">Фильтры:</span>
<button class="filter-chip active" data-filter="status" data-value="all">Все статусы</button>
<button class="filter-chip" data-filter="status" data-value="new">Новые</button>
<button class="filter-chip" data-filter="status" data-value="progress">В работе</button>
<button class="filter-chip" data-filter="status" data-value="done">Решено</button>
<button class="filter-chip" data-filter="status" data-value="overdue">Просрочено</button>
<span class="kt-ai-spacer" style="flex: 1"></span>
<select class="kt-ai-select" id="priority-filter" style="min-width: 140px">
<option value="all">Любой приоритет</option>
<option value="high">Высокий</option>
<option value="medium">Средний</option>
<option value="low">Низкий</option>
</select>
</div>
<div class="table-wrap">
<table class="table">
<thead>
<tr>
<th style="width: 80px">№</th>
<th>Тема</th>
<th style="width: 120px">Статус</th>
<th style="width: 100px">Приоритет</th>
<th style="width: 120px">Дата</th>
<th style="width: 140px">Исполнитель</th>
</tr>
</thead>
<tbody id="tickets-body"></tbody>
</table>
</div>
<div class="empty" id="empty-state" style="display: none">
<div class="empty-icon">📭</div>
<div style="font-size: 16px; font-weight: var(--kt-ai-weight-medium)">Нет заявок</div>
<div style="font-size: 14px; margin-top: var(--kt-ai-space-2)">По выбранным фильтрам ничего не найдено</div>
</div>
</main>
<div class="drawer-overlay" id="drawer-overlay" onclick="closeDrawer()"></div>
<div class="drawer" id="drawer">
<div class="drawer-header">
<h2 id="drawer-title">Заявка №000</h2>
<button class="drawer-close" onclick="closeDrawer()">×</button>
</div>
<div class="drawer-body" id="drawer-body"></div>
</div>
<div class="modal-overlay" id="new-ticket-modal">
<div class="modal">
<div class="modal-header">
<h2 style="margin: 0; font-size: 18px">Новая заявка</h2>
<button class="btn-icon" onclick="closeNewTicketModal()">×</button>
</div>
<div class="modal-body">
<div class="kt-ai-field">
<label>Тема *</label>
<input class="kt-ai-input" id="new-subject" placeholder="Краткое описание проблемы" style="width: 100%">
</div>
<div class="kt-ai-field">
<label>Описание *</label>
<textarea class="kt-ai-textarea" id="new-description" rows="4" placeholder="Подробное описание..." style="width: 100%"></textarea>
</div>
<div class="kt-ai-field">
<label>Категория</label>
<select class="kt-ai-select" id="new-category" style="width: 100%">
<option value="technics">Технические вопросы</option>
<option value="billing">Биллинг и оплата</option>
<option value="connection">Подключение услуг</option>
<option value="other">Другое</option>
</select>
</div>
<div class="kt-ai-field">
<label>Приоритет</label>
<select class="kt-ai-select" id="new-priority" style="width: 100%">
<option value="low">Низкий</option>
<option value="medium" selected>Средний</option>
<option value="high">Высокий</option>
</select>
</div>
<div class="kt-ai-field">
<label>Контактный телефон</label>
<input class="kt-ai-input" id="new-phone" placeholder="+7 (___) ___-__-__" style="width: 100%">
</div>
</div>
<div class="modal-footer">
<button class="kt-ai-btn" data-variant="ghost" onclick="closeNewTicketModal()">Отмена</button>
<button class="kt-ai-btn" data-variant="primary" onclick="createTicket()">Создать заявку</button>
</div>
</div>
</div>
<script>
const DEMO_TICKETS = [
{ id: 1001, subject: "Не работает интернет", category: "technics", status: "progress", priority: "high", date: "2026-09-14", executor: "Алиев М.", description: "Полное отсутствие связи с утра. Роутер перезагружал." },
{ id: 1002, subject: "Ошибка в счёте", category: "billing", status: "new", priority: "medium", date: "2026-09-14", executor: "—", description: "В счёте за август указана неверная сумма." },
{ id: 1003, subject: "Подключить ТВ", category: "connection", status: "done", priority: "low", date: "2026-09-13", executor: "Иванов А.", description: "Заявка на подключение цифрового ТВ." },
{ id: 1004, subject: "Низкая скорость", category: "technics", status: "progress", priority: "medium", date: "2026-09-13", executor: "Алиев М.", description: "Скорость не превышает 5 Мбит/с." },
{ id: 1005, subject: "Смена тарифа", category: "connection", status: "done", priority: "low", date: "2026-09-12", executor: "Петров К.", description: "Переход на тариф «Домашний»." },
{ id: 1006, subject: "Нет связи", category: "technics", status: "overdue", priority: "high", date: "2026-09-11", executor: "—", description: "Нет телефонной связи и интернета." },
{ id: 1007, subject: "Перерасчёт", category: "billing", status: "new", priority: "medium", date: "2026-09-12", executor: "—", description: "Запрошен перерасчёт за период отпуска." },
{ id: 1008, subject: "Настройка роутера", category: "technics", status: "done", priority: "low", date: "2026-09-10", executor: "Сидоров В.", description: "Помощь в настройке Wi-Fi роутера." },
{ id: 1009, subject: "Отключение услуги", category: "connection", status: "progress", priority: "medium", date: "2026-09-11", executor: "Иванов А.", description: "Отключить услугу «Антивирус»." },
{ id: 1010, subject: "Дублирование платежа", category: "billing", status: "done", priority: "high", date: "2026-09-09", executor: "Петров К.", description: "Платёж проведён дважды." },
{ id: 1011, subject: "Помехи на линии", category: "technics", status: "new", priority: "medium", date: "2026-09-14", executor: "—", description: "Постоянные помехи при разговоре." },
{ id: 1012, subject: "Запрос акта", category: "billing", status: "done", priority: "low", date: "2026-09-08", executor: "Иванов А.", description: "Нужен акт выполненных работ." },
{ id: 1013, subject: "Перенос срока оплаты", category: "billing", status: "progress", priority: "medium", date: "2026-09-10", executor: "Алиев М.", description: "Запрос на отсрочку платежа." },
{ id: 1014, subject: "Обрыв кабеля", category: "technics", status: "overdue", priority: "high", date: "2026-09-09", executor: "—", description: "Видно повреждение кабеля в подъезде." },
{ id: 1015, subject: "Подключение SIP", category: "connection", status: "new", priority: "low", date: "2026-09-13", executor: "—", description: "Интересует SIP-телефония для офиса." },
{ id: 1016, subject: "Не приходит SMS", category: "technics", status: "done", priority: "medium", date: "2026-09-07", executor: "Сидоров В.", description: "Не приходят SMS с кодами." },
{ id: 1017, subject: "Возврат средств", category: "billing", status: "progress", priority: "high", date: "2026-09-11", executor: "Петров К.", description: "Запрос на возврат переплаты." },
{ id: 1018, subject: "Частые обрывы", category: "technics", status: "new", priority: "high", date: "2026-09-14", executor: "—", description: "Связь пропадает несколько раз в день." },
{ id: 1019, subject: "Смена собственника", category: "billing", status: "done", priority: "medium", date: "2026-09-06", executor: "Иванов А.", description: "Обновление данных собственника." },
{ id: 1020, subject: "Тест скорости", category: "technics", status: "done", priority: "low", date: "2026-09-05", executor: "Алиев М.", description: "Проверка соответствия скорости тарифу." },
{ id: 1021, subject: "Отключить телефон", category: "connection", status: "progress", priority: "low", date: "2026-09-12", executor: "Сидоров В.", description: "Отключение домашнего телефона." },
{ id: 1022, subject: "Ошибка авторизации", category: "technics", status: "new", priority: "medium", date: "2026-09-13", executor: "—", description: "Не входит в личный кабинет." },
{ id: 1023, subject: "Подключение статического IP", category: "connection", status: "done", priority: "medium", date: "2026-09-04", executor: "Петров К.", description: "Заказ услуги статического IP." },
{ id: 1024, subject: "Некорректный счёт", category: "billing", status: "overdue", priority: "high", date: "2026-09-08", executor: "—", description: "В счёте лишние услуги." },
{ id: 1025, subject: "Консультация по тарифам", category: "other", status: "done", priority: "low", date: "2026-09-03", executor: "Иванов А.", description: "Нужна консультация по новым тарифам." }
];
let tickets = [];
let currentFilters = { status: 'all', priority: 'all' };
function init() {
loadTickets();
render();
setupFilters();
}
function loadTickets() {
const stored = localStorage.getItem('support-tickets');
if (stored) {
tickets = JSON.parse(stored);
} else {
tickets = [...DEMO_TICKETS];
saveTickets();
}
}
function saveTickets() {
localStorage.setItem('support-tickets', JSON.stringify(tickets));
}
function setupFilters() {
document.querySelectorAll('[data-filter="status"]').forEach(btn => {
btn.addEventListener('click', () => {
document.querySelectorAll('[data-filter="status"]').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
currentFilters.status = btn.dataset.value;
render();
});
});
document.getElementById('priority-filter').addEventListener('change', (e) => {
currentFilters.priority = e.target.value;
render();
});
}
function render() {
const filtered = getFilteredTickets();
renderKPI(filtered);
renderChart(filtered);
renderTable(filtered);
document.getElementById('empty-state').style.display = filtered.length === 0 ? 'block' : 'none';
}
function getFilteredTickets() {
return tickets.filter(t => {
if (currentFilters.status !== 'all' && t.status !== currentFilters.status) return false;
if (currentFilters.priority !== 'all' && t.priority !== currentFilters.priority) return false;
return true;
});
}
function renderKPI(data) {
const total = data.length;
const progress = data.filter(t => t.status === 'progress' || t.status === 'new').length;
const done = data.filter(t => t.status === 'done').length;
const overdue = data.filter(t => t.status === 'overdue').length;
document.getElementById('kpi-total').textContent = total;
document.getElementById('kpi-progress').textContent = progress;
document.getElementById('kpi-done').textContent = done;
document.getElementById('kpi-overdue').textContent = overdue;
}
function renderChart(data) {
const days = ['Пн', 'Вт', 'Ср', 'Чт', 'Пт', 'Сб', 'Вс'];
const counts = [0, 0, 0, 0, 0, 0, 0];
data.forEach(t => {
const date = new Date(t.date);
const dayIndex = (date.getDay() + 6) % 7;
counts[dayIndex]++;
});
const max = Math.max(...counts, 1);
const chartEl = document.getElementById('chart');
const labelsEl = document.getElementById('chart-labels');
chartEl.innerHTML = '';
labelsEl.innerHTML = '';
counts.forEach((count, i) => {
const height = (count / max) * 160;
const bar = document.createElement('div');
bar.className = 'chart-bar';
bar.style.height = height + 'px';
bar.setAttribute('data-kt-tip', count + ' заявок');
chartEl.appendChild(bar);
const label = document.createElement('div');
label.className = 'chart-label';
label.textContent = days[i];
labelsEl.appendChild(label);
});
}
function renderTable(data) {
const tbody = document.getElementById('tickets-body');
tbody.innerHTML = '';
data.forEach(t => {
const tr = document.createElement('tr');
tr.onclick = () => openDrawer(t.id);
const statusText = { new: 'Новый', progress: 'В работе', done: 'Решено', overdue: 'Просрочено' }[t.status];
const priorityHtml = `
<div class="priority">
<div class="priority-dot ${t.priority === 'high' ? 'active high' : ''}"></div>
<div class="priority-dot ${t.priority === 'medium' ? 'active medium' : ''}"></div>
<div class="priority-dot ${t.priority === 'low' ? 'active low' : ''}"></div>
</div>
`;
tr.innerHTML = `
<td style="font-weight: var(--kt-ai-weight-medium)">#${t.id}</td>
<td>${t.subject}</td>
<td><span class="status ${t.status}">${statusText}</span></td>
<td>${priorityHtml}</td>
<td style="color: var(--kt-ai-fg-muted)">${formatDate(t.date)}</td>
<td style="color: var(--kt-ai-fg-muted)">${t.executor}</td>
`;
tbody.appendChild(tr);
});
}
function formatDate(dateStr) {
const date = new Date(dateStr);
const today = new Date();
const diff = Math.floor((today - date) / (1000 * 60 * 60 * 24));
if (diff === 0) return 'Сегодня';
if (diff === 1) return 'Вчера';
if (diff < 7) return date.toLocaleDateString('ru-RU', { weekday: 'short' });
return date.toLocaleDateString('ru-RU', { day: 'numeric', month: 'short' });
}
function openDrawer(id) {
const ticket = tickets.find(t => t.id === id);
if (!ticket) return;
document.getElementById('drawer-title').textContent = `Заявка №${ticket.id}`;
const statusText = { new: 'Новый', progress: 'В работе', done: 'Решено', overdue: 'Просрочено' }[ticket.status];
const categoryText = { technics: 'Технические вопросы', billing: 'Биллинг и оплата', connection: 'Подключение услуг', other: 'Другое' }[ticket.category];
const priorityText = { low: 'Низкий', medium: 'Средний', high: 'Высокий' }[ticket.priority];
document.getElementById('drawer-body').innerHTML = `
<div class="detail-row">
<span class="detail-label">Тема</span>
<span class="detail-value">${ticket.subject}</span>
</div>
<div class="detail-row">
<span class="detail-label">Статус</span>
<span class="detail-value"><span class="status ${ticket.status}">${statusText}</span></span>
</div>
<div class="detail-row">
<span class="detail-label">Категория</span>
<span class="detail-value">${categoryText}</span>
</div>
<div class="detail-row">
<span class="detail-label">Приоритет</span>
<span class="detail-value">${priorityText}</span>
</div>
<div class="detail-row">
<span class="detail-label">Дата создания</span>
<span class="detail-value">${ticket.date}</span>
</div>
<div class="detail-row">
<span class="detail-label">Исполнитель</span>
<span class="detail-value">${ticket.executor}</span>
</div>
<div style="margin-top: var(--kt-ai-space-5); padding-top: var(--kt-ai-space-4); border-top: 1px solid var(--kt-ai-border)">
<div style="font-size: 13px; font-weight: var(--kt-ai-weight-medium); color: var(--kt-ai-fg-muted); margin-bottom: var(--kt-ai-space-2)">Описание</div>
<div style="font-size: 14px; line-height: 1.6">${ticket.description}</div>
</div>
<div style="margin-top: var(--kt-ai-space-6); display: flex; gap: var(--kt-ai-space-3)">
<button class="kt-ai-btn" data-variant="primary" style="flex: 1" onclick="changeStatus(${ticket.id}, 'done')">✓ Решено</button>
<button class="kt-ai-btn" data-variant="ghost" style="flex: 1" onclick="changeStatus(${ticket.id}, 'progress')">⟳ В работу</button>
</div>
`;
document.getElementById('drawer').classList.add('open');
document.getElementById('drawer-overlay').classList.add('open');
}
function closeDrawer() {
document.getElementById('drawer').classList.remove('open');
document.getElementById('drawer-overlay').classList.remove('open');
}
function changeStatus(id, newStatus) {
const ticket = tickets.find(t => t.id === id);
if (ticket) {
ticket.status = newStatus;
saveTickets();
render();
closeDrawer();
}
}
function openNewTicketModal() {
document.getElementById('new-subject').value = '';
document.getElementById('new-description').value = '';
document.getElementById('new-category').value = 'technics';
document.getElementById('new-priority').value = 'medium';
document.getElementById('new-phone').value = '';
document.getElementById('new-ticket-modal').classList.add('open');
}
function closeNewTicketModal() {
document.getElementById('new-ticket-modal').classList.remove('open');
}
function createTicket() {
const subject = document.getElementById('new-subject').value.trim();
const description = document.getElementById('new-description').value.trim();
if (!subject || !description) {
alert('Заполните тему и описание');
return;
}
const newTicket = {
id: Math.max(...tickets.map(t => t.id)) + 1,
subject: subject,
category: document.getElementById('new-category').value,
status: 'new',
priority: document.getElementById('new-priority').value,
date: new Date().toISOString().split('T')[0],
executor: '—',
description: description
};
tickets.unshift(newTicket);
saveTickets();
render();
closeNewTicketModal();
openDrawer(newTicket.id);
}
init();
</script>
</body>
</html>