zhurnal-zayavok-remont-orgte/index.html

318 lines
15 KiB
HTML
Raw Permalink 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>
.kt-ai-app { min-height: 100vh; display: flex; flex-direction: column; }
.main-content { flex: 1; padding: var(--kt-ai-space-6) 0; }
.kpi-row { display: flex; gap: var(--kt-ai-space-4); flex-wrap: wrap; margin-bottom: var(--kt-ai-space-6); }
.toolbar { display: flex; gap: var(--kt-ai-space-3); flex-wrap: wrap; align-items: center; margin-bottom: var(--kt-ai-space-4); }
.toolbar input { flex: 1; min-width: 200px; }
.table-wrap { overflow-x: auto; border: 1px solid var(--kt-ai-card-border); border-radius: var(--kt-ai-radius-xl); }
table { width: 100%; border-collapse: collapse; font-size: var(--kt-ai-text-sm); }
th, td { padding: var(--kt-ai-space-3) var(--kt-ai-space-4); text-align: left; border-bottom: 1px solid var(--kt-ai-border); }
th { background: var(--kt-ai-bg-soft); font-weight: var(--kt-ai-weight-semibold); color: var(--kt-ai-fg-muted); }
tr:hover { background: var(--kt-ai-bg-soft); }
td.num { text-align: right; font-variant-numeric: tabular-nums; }
.actions { display: flex; gap: var(--kt-ai-space-2); }
.drawer-overlay { position: fixed; inset: 0; background: rgba(0,0,0,0.4); display: none; z-index: 100; }
.drawer-overlay.on { display: flex; justify-content: flex-end; }
.drawer { width: 420px; max-width: 100%; background: var(--kt-ai-card-bg); padding: var(--kt-ai-space-6); overflow-y: auto; }
.drawer h2 { margin-bottom: var(--kt-ai-space-4); }
.form-group { margin-bottom: var(--kt-ai-space-4); }
.form-group label { display: block; margin-bottom: var(--kt-ai-space-2); font-weight: var(--kt-ai-weight-medium); }
.form-group input, .form-group select, .form-group textarea { width: 100%; }
.form-group textarea { min-height: 80px; resize: vertical; }
.status-chip { display: inline-block; padding: 2px 10px; border-radius: var(--kt-ai-radius-full); font-size: var(--kt-ai-text-xs); font-weight: var(--kt-ai-weight-semibold); }
.status-new { background: #dbeafe; color: #1d4ed8; }
.status-in-work { background: #fef3c7; color: #b45309; }
.status-done { background: #d1fae5; color: #047857; }
.status-closed { background: #e5e7eb; color: #374151; }
</style>
</head>
<body class="kt-ai-app">
<header class="kt-ai-pagehead">
<div class="kt-ai-wrap kt-ai-pagehead-in">
<div style="display:flex;align-items:center;gap:var(--kt-ai-space-3)">
<img src="https://code.vibe42.kz/tore/aitu/kt-logo.svg" alt="Казахтелеком" height="28">
<strong>Журнал заявок — Ремонт оргтехники</strong>
</div>
<span class="kt-ai-spacer"></span>
<button class="kt-ai-btn" data-variant="ghost" onclick="toggleTheme()">🌓 Тема</button>
<button class="kt-ai-btn" data-variant="primary" onclick="openDrawer()">+ Новая заявка</button>
</div>
</header>
<main class="main-content">
<div class="kt-ai-wrap">
<div class="kt-ai-kpi-strip" style="margin-bottom:var(--kt-ai-space-6)">
<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-new" style="color:var(--kt-ai-chart-blue)">0</div>
<div class="label">Новые</div>
<div class="hint">ожидают</div>
</div>
<div class="kt-ai-kpi">
<div class="value" id="kpi-in-work" style="color:var(--kt-ai-chart-orange)">0</div>
<div class="label">В работе</div>
<div class="hint">в ремонте</div>
</div>
<div class="kt-ai-kpi">
<div class="value" id="kpi-done" style="color:var(--kt-ai-chart-green)">0</div>
<div class="label">Готово</div>
<div class="hint">выполнено</div>
</div>
</div>
<div class="toolbar">
<input type="text" class="kt-ai-input" id="search-input" placeholder="Поиск по заявке, заявителю, технике..." oninput="renderTable()">
<select class="kt-ai-select" id="status-filter" onchange="renderTable()">
<option value="">Все статусы</option>
<option value="new">Новые</option>
<option value="in-work">В работе</option>
<option value="done">Готово</option>
<option value="closed">Закрыто</option>
</select>
</div>
<div class="table-wrap">
<table>
<thead>
<tr>
<th style="width:60px">№</th>
<th>Дата</th>
<th>Заявитель</th>
<th>Техника</th>
<th>Неисправность</th>
<th>Срочность</th>
<th>Статус</th>
<th>Исполнитель</th>
<th style="width:100px">Действия</th>
</tr>
</thead>
<tbody id="table-body"></tbody>
</table>
</div>
</div>
</main>
<div class="drawer-overlay" id="drawer-overlay" onclick="if(event.target===this)closeDrawer()">
<div class="drawer">
<h2 id="drawer-title">Новая заявка</h2>
<form id="ticket-form" onsubmit="saveTicket(event)">
<input type="hidden" id="ticket-id">
<div class="form-group">
<label>Заявитель *</label>
<input type="text" class="kt-ai-input" id="ticket-requester" required placeholder="ФИО или отдел">
</div>
<div class="form-group">
<label>Тип техники *</label>
<select class="kt-ai-select" id="ticket-type" required>
<option value="">Выберите...</option>
<option>Принтер</option>
<option>МФУ</option>
<option>Компьютер</option>
<option>Ноутбук</option>
<option>Ксерокс</option>
<option>Сканер</option>
<option>Факс</option>
<option>Другое</option>
</select>
</div>
<div class="form-group">
<label>Описание неисправности *</label>
<textarea class="kt-ai-textarea" id="ticket-description" required placeholder="Что случилось?"></textarea>
</div>
<div class="form-group">
<label>Срочность</label>
<select class="kt-ai-select" id="ticket-urgency">
<option value="normal">Обычная</option>
<option value="high">Высокая</option>
<option value="critical">Критическая</option>
</select>
</div>
<div class="form-group">
<label>Исполнитель</label>
<input type="text" class="kt-ai-input" id="ticket-assignee" placeholder="Кто в работе">
</div>
<div class="form-group">
<label>Статус</label>
<select class="kt-ai-select" id="ticket-status">
<option value="new">Новая</option>
<option value="in-work">В работе</option>
<option value="done">Готово</option>
<option value="closed">Закрыто</option>
</select>
</div>
<div style="display:flex;gap:var(--kt-ai-space-3);margin-top:var(--kt-ai-space-6)">
<button type="button" class="kt-ai-btn" onclick="closeDrawer()">Отмена</button>
<button type="submit" class="kt-ai-btn" data-variant="primary">Сохранить</button>
</div>
</form>
</div>
</div>
<script>
const DEMO_DATA = [
{ id: 1709884800000, date: '08.03.2026', requester: 'Иванов А.С. (Бухгалтерия)', type: 'Принтер', description: 'Замятие бумаги, печатает с полосами', urgency: 'high', status: 'in-work', assignee: 'Петров К.' },
{ id: 1709971200000, date: '09.03.2026', requester: 'Смирнова Е. (Отдел кадров)', type: 'МФУ', description: 'Не сканирует, ошибка драйвера', urgency: 'normal', status: 'new', assignee: '' },
{ id: 1710057600000, date: '10.03.2026', requester: 'Алиев Н. (Приёмная)', type: 'Ксерокс', description: 'Не включается, нет индикации', urgency: 'critical', status: 'in-work', assignee: 'Серикбаев М.' },
{ id: 1710144000000, date: '11.03.2026', requester: 'Оралов Д. (IT-отдел)', type: 'Компьютер', description: 'Шумит системный блок', urgency: 'normal', status: 'done', assignee: 'Петров К.' },
{ id: 1710230400000, date: '12.03.2026', requester: 'Ким Ю. (Маркетинг)', type: 'Ноутбук', description: 'Не работает клавиатура, залит кофе', urgency: 'high', status: 'new', assignee: '' },
{ id: 1710316800000, date: '13.03.2026', requester: 'Нурсултанов А. (Юр.отдел)', type: 'Принтер', description: 'Закончился тонер, нужна заправка', urgency: 'normal', status: 'closed', assignee: 'Петров К.' },
{ id: 1710403200000, date: '14.03.2026', requester: 'Ахметова Г. (Приёмная)', type: 'Факс', description: 'Не принимает входящие', urgency: 'critical', status: 'in-work', assignee: 'Серикбаев М.' },
{ id: 1710489600000, date: '15.03.2026', requester: 'Тимофеев Р. (Склад)', type: 'Сканер', description: 'Полосы при сканировании', urgency: 'normal', status: 'new', assignee: '' }
];
function loadTickets() {
const stored = localStorage.getItem('tickets');
return stored ? JSON.parse(stored) : DEMO_DATA;
}
function saveTickets(tickets) {
localStorage.setItem('tickets', JSON.stringify(tickets));
}
function renderKPI() {
const tickets = loadTickets();
document.getElementById('kpi-total').textContent = tickets.length;
document.getElementById('kpi-new').textContent = tickets.filter(t => t.status === 'new').length;
document.getElementById('kpi-in-work').textContent = tickets.filter(t => t.status === 'in-work').length;
document.getElementById('kpi-done').textContent = tickets.filter(t => t.status === 'done' || t.status === 'closed').length;
}
function getStatusChip(status) {
const labels = { 'new': 'Новая', 'in-work': 'В работе', 'done': 'Готово', 'closed': 'Закрыто' };
return '<span class="status-chip status-' + status + '">' + labels[status] + '</span>';
}
function getUrgencyLabel(urgency) {
const labels = { 'normal': 'Обычная', 'high': 'Высокая', 'critical': 'Критическая' };
return labels[urgency] || urgency;
}
function renderTable() {
const tickets = loadTickets();
const search = document.getElementById('search-input').value.toLowerCase();
const statusFilter = document.getElementById('status-filter').value;
const filtered = tickets.filter(t => {
const matchesSearch = !search ||
t.requester.toLowerCase().includes(search) ||
t.type.toLowerCase().includes(search) ||
t.description.toLowerCase().includes(search) ||
(t.assignee && t.assignee.toLowerCase().includes(search));
const matchesStatus = !statusFilter || t.status === statusFilter;
return matchesSearch && matchesStatus;
});
document.getElementById('table-body').innerHTML = filtered.map(t =>
'<tr>' +
'<td class="num">' + t.id.toString().slice(-4) + '</td>' +
'<td>' + t.date + '</td>' +
'<td>' + t.requester + '</td>' +
'<td>' + t.type + '</td>' +
'<td>' + t.description + '</td>' +
'<td>' + getUrgencyLabel(t.urgency) + '</td>' +
'<td>' + getStatusChip(t.status) + '</td>' +
'<td>' + (t.assignee || '—') + '</td>' +
'<td class="actions">' +
'<button class="kt-ai-btn" data-size="sm" onclick="editTicket(' + t.id + ')">✏️</button>' +
'<button class="kt-ai-btn" data-size="sm" data-variant="danger" onclick="deleteTicket(' + t.id + ')">🗑️</button>' +
'</td>' +
'</tr>'
).join('');
}
function openDrawer() {
document.getElementById('ticket-form').reset();
document.getElementById('ticket-id').value = '';
document.getElementById('drawer-title').textContent = 'Новая заявка';
document.getElementById('drawer-overlay').classList.add('on');
}
function closeDrawer() {
document.getElementById('drawer-overlay').classList.remove('on');
}
function editTicket(id) {
const tickets = loadTickets();
const ticket = tickets.find(t => t.id === id);
if (!ticket) return;
document.getElementById('ticket-id').value = ticket.id;
document.getElementById('ticket-requester').value = ticket.requester;
document.getElementById('ticket-type').value = ticket.type;
document.getElementById('ticket-description').value = ticket.description;
document.getElementById('ticket-urgency').value = ticket.urgency;
document.getElementById('ticket-assignee').value = ticket.assignee || '';
document.getElementById('ticket-status').value = ticket.status;
document.getElementById('drawer-title').textContent = 'Заявка №' + ticket.id.toString().slice(-4);
document.getElementById('drawer-overlay').classList.add('on');
}
function saveTicket(e) {
e.preventDefault();
const id = document.getElementById('ticket-id').value;
const tickets = loadTickets();
const ticket = {
id: id ? parseInt(id) : Date.now(),
date: id ? tickets.find(t => t.id == id).date : new Date().toLocaleDateString('ru-RU'),
requester: document.getElementById('ticket-requester').value,
type: document.getElementById('ticket-type').value,
description: document.getElementById('ticket-description').value,
urgency: document.getElementById('ticket-urgency').value,
assignee: document.getElementById('ticket-assignee').value,
status: document.getElementById('ticket-status').value
};
if (id) {
const idx = tickets.findIndex(t => t.id == id);
tickets[idx] = ticket;
} else {
tickets.unshift(ticket);
}
saveTickets(tickets);
closeDrawer();
renderKPI();
renderTable();
}
function deleteTicket(id) {
if (!confirm('Удалить заявку №' + id.toString().slice(-4) + '?')) return;
const tickets = loadTickets().filter(t => t.id !== id);
saveTickets(tickets);
renderKPI();
renderTable();
}
function toggleTheme() {
const html = document.documentElement;
const current = html.getAttribute('data-theme');
html.setAttribute('data-theme', current === 'light' ? 'dark' : 'light');
}
// Init
renderKPI();
renderTable();
</script>
</body>
</html>