393 lines
16 KiB
HTML
393 lines
16 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">
|
||
<style>
|
||
.kt-ai-btn { text-decoration: none; }
|
||
.table-container { overflow-x: auto; }
|
||
.kt-ai-table { width: 100%; border-collapse: collapse; font-size: var(--kt-ai-text-sm); }
|
||
.kt-ai-table th { text-align: left; 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); white-space: nowrap; }
|
||
.kt-ai-table td { padding: var(--kt-ai-space-3) var(--kt-ai-space-4); border-bottom: 1px solid var(--kt-ai-border); color: var(--kt-ai-fg); }
|
||
.kt-ai-table tr:hover { background: var(--kt-ai-bg-soft); }
|
||
.kt-ai-table tr.clickable { cursor: pointer; }
|
||
.kt-ai-table .actions { opacity: 0; transition: opacity 0.2s; }
|
||
.kt-ai-table tr:hover .actions { opacity: 1; }
|
||
.kt-ai-chip { display: inline-flex; align-items: center; padding: 2px 10px; border-radius: var(--kt-ai-radius-full); font-size: var(--kt-ai-text-xs); font-weight: var(--kt-ai-weight-medium); }
|
||
.kt-ai-chip[data-region="Алматы"] { background: #dbeafe; color: #1e40af; }
|
||
.kt-ai-chip[data-region="Астана"] { background: #dcfce7; color: #166534; }
|
||
.kt-ai-chip[data-region="Шымкент"] { background: #fef3c7; color: #92400e; }
|
||
.kt-ai-chip[data-region="Регионы"] { background: #f3e8ff; color: #6b21a8; }
|
||
.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: 100; }
|
||
.kt-ai-modal-overlay.on { display: flex; }
|
||
.kt-ai-modal { background: var(--kt-ai-bg); border-radius: var(--kt-ai-radius-2xl); padding: var(--kt-ai-space-6); width: 100%; max-width: 480px; box-shadow: 0 20px 40px rgba(0,0,0,0.2); }
|
||
.kt-ai-kpi-strip { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: var(--kt-ai-space-4); margin-bottom: var(--kt-ai-space-6); }
|
||
.kt-ai-kpi-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-4); }
|
||
.kt-ai-kpi-card .value { font-size: var(--kt-ai-text-2xl); font-weight: var(--kt-ai-weight-bold); color: var(--kt-ai-fg); }
|
||
.kt-ai-kpi-card .label { font-size: var(--kt-ai-text-sm); color: var(--kt-ai-fg-muted); margin-top: 4px; }
|
||
.search-row { display: flex; gap: var(--kt-ai-space-3); align-items: center; flex-wrap: wrap; margin-bottom: var(--kt-ai-space-4); }
|
||
.search-input { flex: 1; min-width: 200px; padding: 8px 12px; border: 1px solid var(--kt-ai-border); border-radius: var(--kt-ai-radius-md); background: var(--kt-ai-bg); color: var(--kt-ai-fg); font-size: var(--kt-ai-text-sm); }
|
||
.filter-chips { display: flex; gap: var(--kt-ai-space-2); flex-wrap: wrap; }
|
||
.filter-chip { padding: 6px 12px; border: 1px solid var(--kt-ai-border); border-radius: var(--kt-ai-radius-full); background: var(--kt-ai-bg); color: var(--kt-ai-fg); font-size: var(--kt-ai-text-sm); cursor: pointer; transition: all 0.2s; }
|
||
.filter-chip:hover { background: var(--kt-ai-bg-soft); }
|
||
.filter-chip.active { background: var(--kt-ai-primary); color: white; border-color: var(--kt-ai-primary); }
|
||
.log-entry { font-size: var(--kt-ai-text-xs); color: var(--kt-ai-fg-muted); padding: var(--kt-ai-space-2) 0; border-bottom: 1px solid var(--kt-ai-border); }
|
||
.empty-state { text-align: center; padding: var(--kt-ai-space-12); color: var(--kt-ai-fg-muted); }
|
||
</style>
|
||
</head>
|
||
<body>
|
||
|
||
<header class="kt-ai-pagehead">
|
||
<div class="kt-ai-wrap kt-ai-pagehead-in">
|
||
<strong>Справочник тарифов КТ</strong>
|
||
<span class="kt-ai-spacer"></span>
|
||
<button class="kt-ai-btn" data-variant="ghost" onclick="exportCsv()">Выгрузить в Excel</button>
|
||
<button class="kt-ai-btn" data-variant="primary" onclick="openModal()">+ Добавить тариф</button>
|
||
</div>
|
||
</header>
|
||
|
||
<main class="kt-ai-wrap" style="padding-top: var(--kt-ai-space-8); padding-bottom: var(--kt-ai-space-12);">
|
||
|
||
<section class="kt-ai-kpi-strip" data-cards="true">
|
||
<div class="kt-ai-kpi-card">
|
||
<div class="value" id="kpi-total">0</div>
|
||
<div class="label">Всего тарифов</div>
|
||
</div>
|
||
<div class="kt-ai-kpi-card">
|
||
<div class="value" id="kpi-almaty">0</div>
|
||
<div class="label">Алматы</div>
|
||
</div>
|
||
<div class="kt-ai-kpi-card">
|
||
<div class="value" id="kpi-astana">0</div>
|
||
<div class="label">Астана</div>
|
||
</div>
|
||
<div class="kt-ai-kpi-card">
|
||
<div class="value" id="kpi-shymkent">0</div>
|
||
<div class="label">Шымкент</div>
|
||
</div>
|
||
<div class="kt-ai-kpi-card">
|
||
<div class="value" id="kpi-regions">0</div>
|
||
<div class="label">Регионы</div>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="kt-ai-section">
|
||
<div class="kt-ai-wrap">
|
||
<div class="search-row">
|
||
<input type="text" class="search-input" id="search-input" placeholder="Поиск по названию тарифа…">
|
||
<div class="filter-chips" id="filter-chips"></div>
|
||
</div>
|
||
|
||
<div class="table-container">
|
||
<table class="kt-ai-table">
|
||
<thead>
|
||
<tr>
|
||
<th>Название</th>
|
||
<th>Скорость</th>
|
||
<th>Цена</th>
|
||
<th>Регион</th>
|
||
<th style="text-align: right;">Действия</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="tariff-table-body"></tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<div id="empty-state" class="empty-state" style="display: none;">
|
||
<p>Тарифы не найдены</p>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="kt-ai-section" data-divider="true">
|
||
<div class="kt-ai-wrap">
|
||
<h2>Журнал изменений</h2>
|
||
<div id="log-container" style="margin-top: var(--kt-ai-space-4);"></div>
|
||
</div>
|
||
</section>
|
||
|
||
</main>
|
||
|
||
<div class="kt-ai-modal-overlay" id="modal-overlay">
|
||
<div class="kt-ai-modal">
|
||
<h2 id="modal-title" style="margin-bottom: var(--kt-ai-space-4);">Добавить тариф</h2>
|
||
<form id="tariff-form">
|
||
<input type="hidden" id="edit-id">
|
||
<div class="kt-ai-field" style="margin-bottom: var(--kt-ai-space-4);">
|
||
<label>Название тарифа</label>
|
||
<input class="kt-ai-input" id="form-name" required placeholder="Например: Fiber Optic Pro">
|
||
</div>
|
||
<div class="kt-ai-field" style="margin-bottom: var(--kt-ai-space-4);">
|
||
<label>Скорость</label>
|
||
<input class="kt-ai-input" id="form-speed" required placeholder="Например: 100 Мбит/с">
|
||
</div>
|
||
<div class="kt-ai-field" style="margin-bottom: var(--kt-ai-space-4);">
|
||
<label>Цена (тенге)</label>
|
||
<input class="kt-ai-input" id="form-price" type="number" required placeholder="Например: 5990">
|
||
</div>
|
||
<div class="kt-ai-field" style="margin-bottom: var(--kt-ai-space-6);">
|
||
<label>Регион</label>
|
||
<select class="kt-ai-select" id="form-region" required>
|
||
<option value="Алматы">Алматы</option>
|
||
<option value="Астана">Астана</option>
|
||
<option value="Шымкент">Шымкент</option>
|
||
<option value="Регионы">Регионы</option>
|
||
</select>
|
||
</div>
|
||
<div style="display: flex; gap: var(--kt-ai-space-3); justify-content: flex-end;">
|
||
<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 REGIONS = ['Все', 'Алматы', 'Астана', 'Шымкент', 'Регионы'];
|
||
const DEMO_DATA = [
|
||
{ id: 1, name: 'Tariff Home', speed: '100 Мбит/с', price: 3990, region: 'Алматы' },
|
||
{ id: 2, name: 'Fiber Optic', speed: '500 Мбит/с', price: 7990, region: 'Астана' },
|
||
{ id: 3, name: 'Mobile Pro', speed: '50 Мбит/с', price: 2990, region: 'Шымкент' },
|
||
{ id: 4, name: 'Business Start', speed: '200 Мбит/с', price: 9990, region: 'Алматы' },
|
||
{ id: 5, name: 'Premium Fiber', speed: '1 Гбит/с', price: 14990, region: 'Астана' },
|
||
{ id: 6, name: 'Economy', speed: '50 Мбит/с', price: 3000, region: 'Регионы' },
|
||
{ id: 7, name: 'Smart Home', speed: '150 Мбит/с', price: 5490, region: 'Алматы' },
|
||
{ id: 8, name: 'Gaming Pro', speed: '500 Мбит/с', price: 8990, region: 'Шымкент' },
|
||
{ id: 9, name: 'Office Basic', speed: '100 Мбит/с', price: 6990, region: 'Регионы' },
|
||
{ id: 10, name: 'Ultra Fast', speed: '1 Гбит/с', price: 13990, region: 'Алматы' },
|
||
{ id: 11, name: 'Family Pack', speed: '200 Мбит/с', price: 6490, region: 'Астана' },
|
||
{ id: 12, name: 'Student', speed: '50 Мбит/с', price: 2490, region: 'Регионы' },
|
||
{ id: 13, name: 'Corporate', speed: '500 Мбит/с', price: 11990, region: 'Алматы' },
|
||
{ id: 14, name: 'Light Speed', speed: '300 Мбит/с', price: 7490, region: 'Шымкент' },
|
||
{ id: 15, name: 'Regional Plus', speed: '100 Мбит/с', price: 4990, region: 'Регионы' }
|
||
];
|
||
|
||
let tariffs = [];
|
||
let log = [];
|
||
let currentFilter = 'Все';
|
||
let searchQuery = '';
|
||
|
||
function loadFromStorage() {
|
||
const stored = localStorage.getItem('tariffs');
|
||
const storedLog = localStorage.getItem('tariffsLog');
|
||
if (stored) {
|
||
tariffs = JSON.parse(stored);
|
||
} else {
|
||
tariffs = [...DEMO_DATA];
|
||
saveToStorage();
|
||
}
|
||
if (storedLog) {
|
||
log = JSON.parse(storedLog);
|
||
}
|
||
}
|
||
|
||
function saveToStorage() {
|
||
localStorage.setItem('tariffs', JSON.stringify(tariffs));
|
||
localStorage.setItem('tariffsLog', JSON.stringify(log));
|
||
}
|
||
|
||
function formatPrice(price) {
|
||
return price.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ' ') + ' ₸';
|
||
}
|
||
|
||
function formatDate(dateStr) {
|
||
const d = new Date(dateStr);
|
||
return d.toLocaleDateString('ru-RU', { day: '2-digit', month: '2-digit', year: 'numeric' });
|
||
}
|
||
|
||
function renderKPIs() {
|
||
const filtered = getFilteredData();
|
||
document.getElementById('kpi-total').textContent = filtered.length;
|
||
document.getElementById('kpi-almaty').textContent = filtered.filter(t => t.region === 'Алматы').length;
|
||
document.getElementById('kpi-astana').textContent = filtered.filter(t => t.region === 'Астана').length;
|
||
document.getElementById('kpi-shymkent').textContent = filtered.filter(t => t.region === 'Шымкент').length;
|
||
document.getElementById('kpi-regions').textContent = filtered.filter(t => t.region === 'Регионы').length;
|
||
}
|
||
|
||
function getFilteredData() {
|
||
return tariffs.filter(t => {
|
||
const matchesRegion = currentFilter === 'Все' || t.region === currentFilter;
|
||
const matchesSearch = searchQuery === '' || t.name.toLowerCase().includes(searchQuery.toLowerCase());
|
||
return matchesRegion && matchesSearch;
|
||
});
|
||
}
|
||
|
||
function renderTable() {
|
||
const tbody = document.getElementById('tariff-table-body');
|
||
const data = getFilteredData();
|
||
|
||
if (data.length === 0) {
|
||
document.getElementById('empty-state').style.display = 'block';
|
||
tbody.innerHTML = '';
|
||
return;
|
||
}
|
||
|
||
document.getElementById('empty-state').style.display = 'none';
|
||
tbody.innerHTML = data.map(t => `
|
||
<tr class="clickable" onclick="editTariff(${t.id})">
|
||
<td>${t.name}</td>
|
||
<td>${t.speed}</td>
|
||
<td>${formatPrice(t.price)}</td>
|
||
<td><span class="kt-ai-chip" data-region="${t.region}">${t.region}</span></td>
|
||
<td class="actions" style="text-align: right;">
|
||
<button class="kt-ai-btn" data-variant="danger" data-size="sm" onclick="event.stopPropagation(); deleteTariff(${t.id})">Удалить</button>
|
||
</td>
|
||
</tr>
|
||
`).join('');
|
||
}
|
||
|
||
function renderFilterChips() {
|
||
const container = document.getElementById('filter-chips');
|
||
container.innerHTML = REGIONS.map(r => `
|
||
<button class="filter-chip ${currentFilter === r ? 'active' : ''}" onclick="setFilter('${r}')">${r}</button>
|
||
`).join('');
|
||
}
|
||
|
||
function renderLog() {
|
||
const container = document.getElementById('log-container');
|
||
if (log.length === 0) {
|
||
container.innerHTML = '<p style="color: var(--kt-ai-fg-muted); font-size: var(--kt-ai-text-sm);">Журнал пуст</p>';
|
||
return;
|
||
}
|
||
container.innerHTML = log.slice().reverse().map(entry => `
|
||
<div class="log-entry">
|
||
<strong>${formatDate(entry.date)}</strong> — ${entry.action} (${entry.who})
|
||
</div>
|
||
`).join('');
|
||
}
|
||
|
||
function setFilter(region) {
|
||
currentFilter = region;
|
||
renderFilterChips();
|
||
renderTable();
|
||
renderKPIs();
|
||
}
|
||
|
||
function openModal() {
|
||
document.getElementById('modal-title').textContent = 'Добавить тариф';
|
||
document.getElementById('edit-id').value = '';
|
||
document.getElementById('form-name').value = '';
|
||
document.getElementById('form-speed').value = '';
|
||
document.getElementById('form-price').value = '';
|
||
document.getElementById('form-region').value = 'Алматы';
|
||
document.getElementById('modal-overlay').classList.add('on');
|
||
}
|
||
|
||
function closeModal() {
|
||
document.getElementById('modal-overlay').classList.remove('on');
|
||
}
|
||
|
||
function editTariff(id) {
|
||
const tariff = tariffs.find(t => t.id === id);
|
||
if (!tariff) return;
|
||
|
||
document.getElementById('modal-title').textContent = 'Редактировать тариф';
|
||
document.getElementById('edit-id').value = tariff.id;
|
||
document.getElementById('form-name').value = tariff.name;
|
||
document.getElementById('form-speed').value = tariff.speed;
|
||
document.getElementById('form-price').value = tariff.price;
|
||
document.getElementById('form-region').value = tariff.region;
|
||
document.getElementById('modal-overlay').classList.add('on');
|
||
}
|
||
|
||
function deleteTariff(id) {
|
||
if (!confirm('Вы уверены, что хотите удалить этот тариф?')) return;
|
||
|
||
const tariff = tariffs.find(t => t.id === id);
|
||
if (!tariff) return;
|
||
|
||
tariffs = tariffs.filter(t => t.id !== id);
|
||
log.push({
|
||
date: new Date().toISOString(),
|
||
action: `Удалён тариф "${tariff.name}"`,
|
||
who: 'Пользователь'
|
||
});
|
||
|
||
saveToStorage();
|
||
renderTable();
|
||
renderKPIs();
|
||
renderLog();
|
||
}
|
||
|
||
function exportCsv() {
|
||
const data = getFilteredData();
|
||
const headers = ['Название', 'Скорость', 'Цена', 'Регион'];
|
||
const rows = data.map(t => [
|
||
'"' + t.name.replace(/"/g, '""') + '"',
|
||
'"' + t.speed.replace(/"/g, '""') + '"',
|
||
t.price,
|
||
'"' + t.region + '"'
|
||
]);
|
||
|
||
const csv = '\uFEFF' + [headers.join(';'), ...rows.map(r => r.join(';'))].join('\n');
|
||
const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
|
||
const link = document.createElement('a');
|
||
link.href = URL.createObjectURL(blob);
|
||
link.download = 'spravochnik-tarifov.csv';
|
||
link.click();
|
||
}
|
||
|
||
document.getElementById('tariff-form').addEventListener('submit', function(e) {
|
||
e.preventDefault();
|
||
|
||
const id = document.getElementById('edit-id').value;
|
||
const name = document.getElementById('form-name').value.trim();
|
||
const speed = document.getElementById('form-speed').value.trim();
|
||
const price = parseInt(document.getElementById('form-price').value);
|
||
const region = document.getElementById('form-region').value;
|
||
|
||
if (id) {
|
||
const index = tariffs.findIndex(t => t.id == id);
|
||
if (index !== -1) {
|
||
const old = tariffs[index];
|
||
tariffs[index] = { ...old, name, speed, price, region };
|
||
log.push({
|
||
date: new Date().toISOString(),
|
||
action: `Изменён тариф "${name}"`,
|
||
who: 'Пользователь'
|
||
});
|
||
}
|
||
} else {
|
||
const newTariff = {
|
||
id: Date.now(),
|
||
name,
|
||
speed,
|
||
price,
|
||
region
|
||
};
|
||
tariffs.push(newTariff);
|
||
log.push({
|
||
date: new Date().toISOString(),
|
||
action: `Добавлен тариф "${name}"`,
|
||
who: 'Пользователь'
|
||
});
|
||
}
|
||
|
||
saveToStorage();
|
||
closeModal();
|
||
renderTable();
|
||
renderKPIs();
|
||
renderLog();
|
||
});
|
||
|
||
document.getElementById('search-input').addEventListener('input', function(e) {
|
||
searchQuery = e.target.value.trim();
|
||
renderTable();
|
||
renderKPIs();
|
||
});
|
||
|
||
loadFromStorage();
|
||
renderFilterChips();
|
||
renderTable();
|
||
renderKPIs();
|
||
renderLog();
|
||
</script>
|
||
|
||
</body>
|
||
</html>
|