tam-pravka-ekrana-sravneniya/index.html

433 lines
19 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>
a.kt-ai-btn { text-decoration: none; }
.comparison-grid { display: grid; grid-template-columns: 1fr 1fr; gap: var(--kt-ai-space-6); }
.comparison-panel { display: flex; flex-direction: column; gap: var(--kt-ai-space-4); }
.screenshot-placeholder {
background: var(--kt-ai-bg-soft);
border: 2px dashed var(--kt-ai-border);
border-radius: var(--kt-ai-radius-3xl);
aspect-ratio: 16/9;
display: flex;
align-items: center;
justify-content: center;
color: var(--kt-ai-fg-muted);
font-size: var(--kt-ai-text-sm);
}
.change-item {
display: flex;
align-items: flex-start;
gap: var(--kt-ai-space-3);
padding: var(--kt-ai-space-3);
border-radius: var(--kt-ai-radius-xl);
background: var(--kt-ai-card-bg);
border: 1px solid var(--kt-ai-card-border);
}
.change-item.accepted { border-color: var(--kt-ai-status-ok-fg); background: var(--kt-ai-status-ok-bg); }
.change-item.rejected { border-color: var(--kt-ai-status-risk-fg); background: var(--kt-ai-status-risk-bg); }
.change-icon {
width: 24px; height: 24px;
border-radius: var(--kt-ai-radius-md);
display: flex; align-items: center; justify-content: center;
flex-shrink: 0;
}
.change-icon.add { background: var(--kt-ai-status-ok-fg); color: white; }
.change-icon.remove { background: var(--kt-ai-status-risk-fg); color: white; }
.change-icon.modify { background: var(--kt-ai-chart-blue); color: white; }
.change-content { flex: 1; min-width: 0; }
.change-title { font-weight: var(--kt-ai-weight-medium); color: var(--kt-ai-fg); margin-bottom: 2px; }
.change-desc { font-size: var(--kt-ai-text-sm); color: var(--kt-ai-fg-muted); }
.filter-bar { display: flex; gap: var(--kt-ai-space-3); align-items: center; flex-wrap: wrap; }
.stats-row { display: grid; grid-template-columns: repeat(4, 1fr); gap: var(--kt-ai-space-4); }
@media (max-width: 900px) {
.comparison-grid { grid-template-columns: 1fr; }
.stats-row { grid-template-columns: 1fr 1fr; }
}
.comment-section { margin-top: var(--kt-ai-space-6); }
.comment-list { display: flex; flex-direction: column; gap: var(--kt-ai-space-3); margin-top: var(--kt-ai-space-4); }
.comment-item { padding: var(--kt-ai-space-3); background: var(--kt-ai-bg-soft); border-radius: var(--kt-ai-radius-xl); }
.comment-meta { font-size: var(--kt-ai-text-xs); color: var(--kt-ai-fg-faint); margin-bottom: var(--kt-ai-space-2); }
.empty-state { text-align: center; padding: var(--kt-ai-space-12); color: var(--kt-ai-fg-muted); }
</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 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>
<span class="kt-ai-chip" data-tone="orange">Демо-данные</span>
<button class="kt-ai-btn" data-variant="ghost" id="themeToggle">🌓 Тема</button>
<button class="kt-ai-btn" data-variant="primary" id="approveAllBtn">Принять все</button>
</div>
</header>
<main class="kt-ai-wrap" style="padding: var(--kt-ai-space-8) 0">
<!-- KPI полоса -->
<div class="kt-ai-kpi-strip" style="margin-bottom: var(--kt-ai-space-8)">
<div class="kt-ai-kpi">
<div class="value" id="kpi-total">0</div>
<div class="label">Всего правок</div>
</div>
<div class="kt-ai-kpi">
<div class="value" id="kpi-accepted" style="color: var(--kt-ai-status-ok-fg)">0</div>
<div class="label">Принято</div>
</div>
<div class="kt-ai-kpi">
<div class="value" id="kpi-pending" style="color: var(--kt-ai-chart-orange)">0</div>
<div class="label">На проверке</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>
</div>
<!-- Фильтры -->
<div class="kt-ai-card" style="margin-bottom: var(--kt-ai-space-6)">
<div class="filter-bar">
<strong style="color: var(--kt-ai-fg-muted)">Фильтры:</strong>
<select class="kt-ai-select" id="filter-type">
<option value="all">Все типы</option>
<option value="add">Добавлено</option>
<option value="remove">Удалено</option>
<option value="modify">Изменено</option>
</select>
<select class="kt-ai-select" id="filter-status">
<option value="all">Все статусы</option>
<option value="pending">На проверке</option>
<option value="accepted">Принято</option>
<option value="rejected">Отклонено</option>
</select>
<select class="kt-ai-select" id="filter-period">
<option value="7d">7 дней</option>
<option value="14d">14 дней</option>
<option value="30d">30 дней</option>
</select>
<span class="kt-ai-spacer"></span>
<button class="kt-ai-btn" data-variant="ghost" id="resetFilters">Сбросить</button>
</div>
</div>
<!-- Основная сетка сравнения -->
<div class="comparison-grid">
<!-- Левая панель: было -->
<div class="comparison-panel">
<div class="kt-ai-card">
<h3 style="margin-bottom: var(--kt-ai-space-4)">Версия 1.2.4 (было)</h3>
<div class="screenshot-placeholder">
<div style="text-align:center">
<div style="font-size:48px;margin-bottom:var(--kt-ai-space-2)">📸</div>
<div>Скриншот старой версии</div>
<div style="font-size:var(--kt-ai-text-xs);margin-top:var(--kt-ai-space-1)">800×450</div>
</div>
</div>
<div style="margin-top: var(--kt-ai-space-4); font-size: var(--kt-ai-text-sm); color: var(--kt-ai-fg-muted)">
<div>Дата: <strong>01.09.2026</strong></div>
<div>Автор: <strong>Иванов А.</strong></div>
</div>
</div>
<!-- Список изменений -->
<div class="kt-ai-card">
<h3 style="margin-bottom: var(--kt-ai-space-4)">Изменения</h3>
<div id="changes-list" style="display:flex;flex-direction:column;gap:var(--kt-ai-space-3)">
<!-- Заполняется JS -->
</div>
</div>
</div>
<!-- Правая панель: стало -->
<div class="comparison-panel">
<div class="kt-ai-card">
<h3 style="margin-bottom: var(--kt-ai-space-4)">Версия 1.2.5 (стало)</h3>
<div class="screenshot-placeholder">
<div style="text-align:center">
<div style="font-size:48px;margin-bottom:var(--kt-ai-space-2)">📸</div>
<div>Скриншот новой версии</div>
<div style="font-size:var(--kt-ai-text-xs);margin-top:var(--kt-ai-space-1)">800×450</div>
</div>
</div>
<div style="margin-top: var(--kt-ai-space-4); font-size: var(--kt-ai-text-sm); color: var(--kt-ai-fg-muted)">
<div>Дата: <strong>08.09.2026</strong></div>
<div>Автор: <strong>Серик К.</strong></div>
</div>
</div>
<!-- Детали выбранного изменения -->
<div class="kt-ai-card" id="detail-panel">
<h3 style="margin-bottom: var(--kt-ai-space-4)">Детали</h3>
<div id="detail-content" class="empty-state">
Выберите изменение из списка
</div>
</div>
</div>
</div>
<!-- Комментарии -->
<div class="comment-section kt-ai-card">
<h3 style="margin-bottom: var(--kt-ai-space-4)">Комментарии к сравнению</h3>
<div style="display:flex;gap:var(--kt-ai-space-3);margin-bottom:var(--kt-ai-space-4)">
<input type="text" class="kt-ai-input" id="comment-input" placeholder="Добавить комментарий..." style="flex:1">
<button class="kt-ai-btn" data-variant="primary" id="addCommentBtn">Добавить</button>
</div>
<div class="comment-list" id="comment-list">
<!-- Комментарии JS -->
</div>
</div>
</main>
<footer class="kt-ai-wrap kt-ai-pagefoot" style="margin-top: var(--kt-ai-space-12)">
<span>© 2026 Казахтелеком AI</span>
<span class="kt-ai-spacer"></span>
<span style="color: var(--kt-ai-fg-muted)">Экран сравнения версий v1.0</span>
</footer>
<script>
// === Демо-данные ===
const DEMO_CHANGES = [
{ id: 1, type: 'add', title: 'Кнопка "Экспорт в Excel"', desc: 'Добавлена возможность экспорта таблицы в Excel', status: 'pending', date: '2026-09-08' },
{ id: 2, type: 'modify', title: 'Цвет статусов', desc: 'Изменена палитра статусов на более контрастную', status: 'pending', date: '2026-09-08' },
{ id: 3, type: 'remove', title: 'Убрана анимация загрузки', desc: 'Удалена анимация для ускорения работы', status: 'accepted', date: '2026-09-07' },
{ id: 4, type: 'add', title: 'Фильтр по периодам', desc: 'Добавлен новый фильтр "Период"', status: 'pending', date: '2026-09-08' },
{ id: 5, type: 'modify', title: 'Размер шрифта в таблице', desc: 'Увеличен с 12px до 14px для читаемости', status: 'rejected', date: '2026-09-06' },
{ id: 6, type: 'add', title: 'Иконки в меню', desc: 'Добавлены иконки к пунктам навигации', status: 'pending', date: '2026-09-08' },
{ id: 7, type: 'remove', title: 'Лишние разделители', desc: 'Убраны визуальные разделители в карточках', status: 'pending', date: '2026-09-07' },
{ id: 8, type: 'modify', title: 'Отступы в форме', desc: 'Увеличены отступы между полями', status: 'accepted', date: '2026-09-05' }
];
const DEMO_COMMENTS = [
{ id: 1, author: 'Серик К.', text: 'Просьба принять все правки до конца недели', date: '2026-09-08 10:30' },
{ id: 2, author: 'Иванов А.', text: 'По пункту 5 — не согласен, шрифт должен быть крупнее', date: '2026-09-07 14:15' },
{ id: 3, author: 'Ольга М.', text: 'Визуально стало лучше, поддерживаю изменения', date: '2026-09-06 09:00' }
];
// === Состояние ===
let changes = JSON.parse(localStorage.getItem('comparison_changes')) || DEMO_CHANGES;
let comments = JSON.parse(localStorage.getItem('comparison_comments')) || DEMO_COMMENTS;
let selectedChangeId = null;
// === Реестр фильтров ===
const FILTERS = [
{ id: 'type', label: 'Тип', affects: ['table', 'stats'] },
{ id: 'status', label: 'Статус', affects: ['table', 'stats'] },
{ id: 'period', label: 'Период', affects: ['table'], demoOnly: true }
];
// === Рендеринг ===
function renderChanges() {
const container = document.getElementById('changes-list');
const typeFilter = document.getElementById('filter-type').value;
const statusFilter = document.getElementById('filter-status').value;
const filtered = changes.filter(c => {
if (typeFilter !== 'all' && c.type !== typeFilter) return false;
if (statusFilter !== 'all' && c.status !== statusFilter) return false;
return true;
});
if (filtered.length === 0) {
container.innerHTML = '<div class="empty-state">Нет изменений по выбранным фильтрам</div>';
return;
}
container.innerHTML = filtered.map(c => {
const iconClass = c.type === 'add' ? 'add' : c.type === 'remove' ? 'remove' : 'modify';
const iconText = c.type === 'add' ? '+' : c.type === 'remove' ? '−' : '≈';
const statusClass = c.status === 'accepted' ? 'accepted' : c.status === 'rejected' ? 'rejected' : '';
return `
<div class="change-item ${statusClass}" data-id="${c.id}" style="cursor:pointer">
<div class="change-icon ${iconClass}">${iconText}</div>
<div class="change-content">
<div class="change-title">${c.title}</div>
<div class="change-desc">${c.desc}</div>
</div>
<button class="kt-ai-btn ${c.status === 'pending' ? '' : 'disabled'}"
data-variant="${c.status === 'rejected' ? 'ghost' : 'primary'}"
data-size="sm"
onclick="event.stopPropagation(); toggleStatus(${c.id})">
${c.status === 'accepted' ? '✓' : c.status === 'rejected' ? '✗' : 'Принять'}
</button>
</div>
`;
}).join('');
// Клик для выбора
container.querySelectorAll('.change-item').forEach(el => {
el.addEventListener('click', () => {
const id = parseInt(el.dataset.id);
selectedChangeId = id;
renderDetail(id);
});
});
renderStats();
}
function renderDetail(id) {
const change = changes.find(c => c.id === id);
if (!change) return;
const container = document.getElementById('detail-content');
const typeLabels = { add: 'Добавлено', remove: 'Удалено', modify: 'Изменено' };
const statusLabels = { pending: 'На проверке', accepted: 'Принято', rejected: 'Отклонено' };
container.innerHTML = `
<div style="margin-bottom: var(--kt-ai-space-4)">
<span class="kt-ai-chip" data-tone="blue">${typeLabels[change.type]}</span>
<span class="kt-ai-chip" style="margin-left: var(--kt-ai-space-2)"
data-tone="${change.status === 'accepted' ? 'green' : change.status === 'rejected' ? 'red' : 'orange'}">
${statusLabels[change.status]}
</span>
</div>
<h4 style="margin-bottom: var(--kt-ai-space-2)">${change.title}</h4>
<p style="color: var(--kt-ai-fg-muted); margin-bottom: var(--kt-ai-space-4)">${change.desc}</p>
<div style="font-size: var(--kt-ai-text-sm); color: var(--kt-ai-fg-faint)">
<div>Дата изменения: <strong>${change.date}</strong></div>
<div>Автор: <strong>Серик К.</strong></div>
</div>
${change.status === 'pending' ? `
<div style="margin-top: var(--kt-ai-space-4); display: flex; gap: var(--kt-ai-space-3)">
<button class="kt-ai-btn" data-variant="primary" onclick="toggleStatus(${change.id})">Принять</button>
<button class="kt-ai-btn" data-variant="ghost" onclick="toggleStatus(${change.id}, true)">Отклонить</button>
</div>
` : ''}
`;
}
function renderStats() {
const total = changes.length;
const accepted = changes.filter(c => c.status === 'accepted').length;
const rejected = changes.filter(c => c.status === 'rejected').length;
const pending = changes.filter(c => c.status === 'pending').length;
document.getElementById('kpi-total').textContent = total;
document.getElementById('kpi-accepted').textContent = accepted;
document.getElementById('kpi-pending').textContent = pending;
document.getElementById('kpi-rejected').textContent = rejected;
}
function renderComments() {
const container = document.getElementById('comment-list');
if (comments.length === 0) {
container.innerHTML = '<div class="empty-state">Пока нет комментариев</div>';
return;
}
container.innerHTML = comments.map(c => `
<div class="comment-item">
<div class="comment-meta">
<strong>${c.author}</strong> · ${c.date}
</div>
<div style="color: var(--kt-ai-fg)">${c.text}</div>
</div>
`).join('');
}
// === Действия ===
function toggleStatus(id, reject = false) {
const change = changes.find(c => c.id === id);
if (!change || change.status !== 'pending') return;
change.status = reject ? 'rejected' : 'accepted';
localStorage.setItem('comparison_changes', JSON.stringify(changes));
renderChanges();
if (selectedChangeId === id) renderDetail(id);
}
function approveAll() {
let count = 0;
changes.forEach(c => {
if (c.status === 'pending') {
c.status = 'accepted';
count++;
}
});
localStorage.setItem('comparison_changes', JSON.stringify(changes));
renderChanges();
renderDetail(selectedChangeId);
alert(`Принято ${count} изменений`);
}
function addComment() {
const input = document.getElementById('comment-input');
const text = input.value.trim();
if (!text) return;
const newComment = {
id: Date.now(),
author: 'Вы',
text: text,
date: new Date().toLocaleString('ru-RU', {
day: '2-digit', month: '2-digit', year: 'numeric',
hour: '2-digit', minute: '2-digit'
})
};
comments.unshift(newComment);
localStorage.setItem('comparison_comments', JSON.stringify(comments));
input.value = '';
renderComments();
}
function resetFilters() {
document.getElementById('filter-type').value = 'all';
document.getElementById('filter-status').value = 'all';
document.getElementById('filter-period').value = '7d';
renderChanges();
}
// === Переключение темы ===
function toggleTheme() {
const html = document.documentElement;
const current = html.getAttribute('data-theme');
const next = current === 'light' ? 'dark' : 'light';
html.setAttribute('data-theme', next);
localStorage.setItem('comparison_theme', next);
}
// === Инициализация ===
document.addEventListener('DOMContentLoaded', () => {
// Тема
const savedTheme = localStorage.getItem('comparison_theme') || 'light';
document.documentElement.setAttribute('data-theme', savedTheme);
document.getElementById('themeToggle').addEventListener('click', toggleTheme);
// Кнопки
document.getElementById('approveAllBtn').addEventListener('click', approveAll);
document.getElementById('addCommentBtn').addEventListener('click', addComment);
document.getElementById('resetFilters').addEventListener('click', resetFilters);
// Фильтры
['filter-type', 'filter-status', 'filter-period'].forEach(id => {
document.getElementById(id).addEventListener('change', renderChanges);
});
// Enter в комментарии
document.getElementById('comment-input').addEventListener('keypress', (e) => {
if (e.key === 'Enter') addComment();
});
renderChanges();
renderComments();
});
</script>
</body>
</html>