1098 lines
35 KiB
JavaScript
1098 lines
35 KiB
JavaScript
// === ПРИЛОЖЕНИЕ "ПОМОЩНИК ПО СКИДКАМ" ===
|
||
|
||
// Состояние приложения
|
||
let state = {
|
||
currentPage: 'home',
|
||
chatHistory: [],
|
||
testCurrent: null,
|
||
testScore: 0,
|
||
testQuestionIndex: 0,
|
||
testAnswers: []
|
||
};
|
||
|
||
// === Инициализация ===
|
||
function init() {
|
||
setupNavigation();
|
||
setupSearch();
|
||
setupUpload();
|
||
setupQuickSolutions();
|
||
renderLessons();
|
||
renderErrors();
|
||
renderInstructions();
|
||
renderFAQ();
|
||
renderTariffs();
|
||
setupCalculator();
|
||
setupAssistant();
|
||
setupTests();
|
||
setupAdmin();
|
||
loadState();
|
||
}
|
||
|
||
// === Навигация ===
|
||
function setupNavigation() {
|
||
// Клик по меню
|
||
document.querySelectorAll('.nav-item[data-page]').forEach(item => {
|
||
item.addEventListener('click', (e) => {
|
||
e.preventDefault();
|
||
const page = item.dataset.page;
|
||
navigateTo(page);
|
||
|
||
// Мобильное меню
|
||
if (window.innerWidth <= 1024) {
|
||
document.getElementById('sidebar').classList.remove('open');
|
||
}
|
||
});
|
||
});
|
||
|
||
// Клик по фичам на главной
|
||
document.querySelectorAll('.feature-card').forEach(card => {
|
||
card.addEventListener('click', () => {
|
||
const page = card.dataset.page;
|
||
if (page) navigateTo(page);
|
||
});
|
||
});
|
||
|
||
// Мобильное меню
|
||
document.getElementById('openSidebar').addEventListener('click', () => {
|
||
document.getElementById('sidebar').classList.add('open');
|
||
});
|
||
|
||
document.getElementById('closeSidebar').addEventListener('click', () => {
|
||
document.getElementById('sidebar').classList.remove('open');
|
||
});
|
||
}
|
||
|
||
function navigateTo(page) {
|
||
// Скрыть все страницы
|
||
document.querySelectorAll('.page').forEach(p => p.classList.remove('active'));
|
||
document.querySelectorAll('.nav-item').forEach(n => n.classList.remove('active'));
|
||
|
||
// Показать нужную
|
||
document.getElementById(`page-${page}`).classList.add('active');
|
||
document.querySelector(`.nav-item[data-page="${page}"]`)?.classList.add('active');
|
||
|
||
state.currentPage = page;
|
||
saveState();
|
||
}
|
||
|
||
// === Поиск ===
|
||
function setupSearch() {
|
||
const searchHandler = () => {
|
||
const query = document.getElementById('searchInput').value.trim().toLowerCase();
|
||
if (!query) return;
|
||
performSearch(query);
|
||
};
|
||
|
||
document.getElementById('searchBtn').addEventListener('click', searchHandler);
|
||
document.getElementById('searchInput').addEventListener('keypress', (e) => {
|
||
if (e.key === 'Enter') searchHandler();
|
||
});
|
||
|
||
// Поиск с главной
|
||
document.getElementById('homeSearchBtn').addEventListener('click', () => {
|
||
const query = document.getElementById('homeSearchInput').value.trim();
|
||
if (query) {
|
||
navigateTo('search');
|
||
document.getElementById('searchInput').value = query;
|
||
performSearch(query.toLowerCase());
|
||
}
|
||
});
|
||
|
||
document.getElementById('homeSearchInput').addEventListener('keypress', (e) => {
|
||
if (e.key === 'Enter') {
|
||
const query = e.target.value.trim();
|
||
if (query) {
|
||
navigateTo('search');
|
||
document.getElementById('searchInput').value = query;
|
||
performSearch(query.toLowerCase());
|
||
}
|
||
}
|
||
});
|
||
|
||
// Быстрые вопросы
|
||
document.querySelectorAll('.qq-btn').forEach(btn => {
|
||
btn.addEventListener('click', () => {
|
||
const q = btn.dataset.q;
|
||
navigateTo('assistant');
|
||
setTimeout(() => {
|
||
document.getElementById('chatInput').value = q;
|
||
sendChatMessage(q);
|
||
}, 300);
|
||
});
|
||
});
|
||
|
||
// Поиск ошибок
|
||
document.getElementById('errorSearchInput').addEventListener('input', (e) => {
|
||
const query = e.target.value.toLowerCase();
|
||
filterErrors(query);
|
||
});
|
||
}
|
||
|
||
function performSearch(query) {
|
||
const results = [];
|
||
|
||
// Поиск в ошибках
|
||
KB.errors.forEach(err => {
|
||
if (
|
||
err.code.toLowerCase().includes(query) ||
|
||
err.title.toLowerCase().includes(query) ||
|
||
err.meaning.toLowerCase().includes(query)
|
||
) {
|
||
results.push({
|
||
type: 'error',
|
||
title: `Ошибка ${err.code}: ${err.title}`,
|
||
preview: err.meaning,
|
||
data: err
|
||
});
|
||
}
|
||
});
|
||
|
||
// Поиск в уроках
|
||
KB.lessons.forEach(lesson => {
|
||
if (
|
||
lesson.title.toLowerCase().includes(query) ||
|
||
lesson.description.toLowerCase().includes(query)
|
||
) {
|
||
results.push({
|
||
type: 'lesson',
|
||
title: `Урок: ${lesson.title}`,
|
||
preview: lesson.description,
|
||
data: lesson
|
||
});
|
||
}
|
||
});
|
||
|
||
// Поиск в инструкциях
|
||
KB.instructions.forEach(inst => {
|
||
if (
|
||
inst.title.toLowerCase().includes(query) ||
|
||
inst.category.toLowerCase().includes(query)
|
||
) {
|
||
results.push({
|
||
type: 'instruction',
|
||
title: `Инструкция: ${inst.title}`,
|
||
preview: inst.steps.slice(0, 3).join(' → ') + '...',
|
||
data: inst
|
||
});
|
||
}
|
||
});
|
||
|
||
// Поиск в FAQ
|
||
KB.faq.forEach(f => {
|
||
if (
|
||
f.question.toLowerCase().includes(query) ||
|
||
f.answer.toLowerCase().includes(query)
|
||
) {
|
||
results.push({
|
||
type: 'faq',
|
||
title: `FAQ: ${f.question}`,
|
||
preview: f.answer.slice(0, 150) + '...',
|
||
data: f
|
||
});
|
||
}
|
||
});
|
||
|
||
// Отображение результатов
|
||
const container = document.getElementById('searchResults');
|
||
if (results.length === 0) {
|
||
container.innerHTML = `
|
||
<div class="card">
|
||
<p>По запросу "${escapeHtml(query)}" ничего не найдено.</p>
|
||
<p style="margin-top: 12px; color: var(--text-muted);">
|
||
Попробуйте:<br>
|
||
• Задать вопрос в AI-помощнике<br>
|
||
• Посмотреть раздел "Ошибки" или "FAQ"
|
||
</p>
|
||
</div>
|
||
`;
|
||
} else {
|
||
container.innerHTML = results.map(r => `
|
||
<div class="card search-result-item" data-type="${r.type}" data-id="${r.data.id}">
|
||
<h3>${escapeHtml(r.title)}</h3>
|
||
<p style="color: var(--text-muted); margin-top: 8px;">${escapeHtml(r.preview)}</p>
|
||
</div>
|
||
`).join('');
|
||
|
||
// Клик по результату
|
||
container.querySelectorAll('.search-result-item').forEach(item => {
|
||
item.addEventListener('click', () => {
|
||
const type = item.dataset.type;
|
||
const id = item.dataset.id;
|
||
if (type === 'error') showErrorModal(KB.errors.find(e => e.id === id));
|
||
if (type === 'lesson') showLessonModal(KB.lessons.find(l => l.id === id));
|
||
if (type === 'instruction') navigateTo('instructions');
|
||
if (type === 'faq') navigateTo('faq');
|
||
});
|
||
});
|
||
}
|
||
}
|
||
|
||
// === Загрузка скриншотов ===
|
||
function setupUpload() {
|
||
const dropzone = document.getElementById('uploadDropzone');
|
||
const input = document.getElementById('uploadInput');
|
||
const btn = document.getElementById('uploadBtn');
|
||
|
||
btn.addEventListener('click', () => input.click());
|
||
input.addEventListener('change', (e) => {
|
||
if (e.target.files.length > 0) handleUpload(e.target.files[0]);
|
||
});
|
||
|
||
// Drag & drop
|
||
dropzone.addEventListener('dragover', (e) => {
|
||
e.preventDefault();
|
||
dropzone.classList.add('dragover');
|
||
});
|
||
|
||
dropzone.addEventListener('dragleave', () => {
|
||
dropzone.classList.remove('dragover');
|
||
});
|
||
|
||
dropzone.addEventListener('drop', (e) => {
|
||
e.preventDefault();
|
||
dropzone.classList.remove('dragover');
|
||
if (e.dataTransfer.files.length > 0) {
|
||
handleUpload(e.dataTransfer.files[0]);
|
||
}
|
||
});
|
||
|
||
// Клик по всей карточке
|
||
dropzone.addEventListener('click', (e) => {
|
||
if (e.target !== btn && e.target !== input) input.click();
|
||
});
|
||
}
|
||
|
||
function handleUpload(file) {
|
||
if (!file.type.startsWith('image/')) {
|
||
alert('Пожалуйста, загрузите изображение (PNG, JPG, JPEG)');
|
||
return;
|
||
}
|
||
|
||
const modal = document.getElementById('uploadModal');
|
||
const body = document.getElementById('uploadModalBody');
|
||
modal.style.display = 'flex';
|
||
|
||
// Имитация анализа (в реальности здесь будет OCR API)
|
||
setTimeout(() => {
|
||
// Демонстрационный ответ
|
||
const demoErrors = [
|
||
{
|
||
detected: 'Ошибка 403: Нет прав на создание скидки',
|
||
confidence: 85,
|
||
solution: KB.errors[0]
|
||
},
|
||
{
|
||
detected: 'Ошибка TARIFF_NOT_FOUND',
|
||
confidence: 70,
|
||
solution: KB.errors[1]
|
||
}
|
||
];
|
||
|
||
const result = demoErrors[Math.floor(Math.random() * demoErrors.length)];
|
||
|
||
body.innerHTML = `
|
||
<div style="margin-bottom: 24px;">
|
||
<div style="display: flex; align-items: center; gap: 12px; margin-bottom: 16px;">
|
||
<span style="font-size: 32px;">🔍</span>
|
||
<div>
|
||
<h3 style="margin-bottom: 4px;">Анализ завершён</h3>
|
||
<p style="color: var(--text-muted); font-size: 14px;">
|
||
Уверенность распознавания: ${result.confidence}%
|
||
</p>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="card" style="background: var(--primary-light); border-left: 4px solid var(--primary);">
|
||
<h4 style="color: var(--primary); margin-bottom: 8px;">📋 Я вижу ошибку:</h4>
|
||
<p style="font-size: 16px; font-weight: 500;">${result.detected}</p>
|
||
</div>
|
||
|
||
<div class="error-detail-section">
|
||
<h3>📖 Что это значит</h3>
|
||
<div class="error-detail-content">${result.solution.meaning}</div>
|
||
</div>
|
||
|
||
<div class="error-detail-section">
|
||
<h3>⚠️ Возможные причины</h3>
|
||
<div class="error-detail-content">
|
||
<ol style="margin: 0; padding-left: 20px;">
|
||
${result.solution.causes.map(c => `<li>${c}</li>`).join('')}
|
||
</ol>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="error-detail-section">
|
||
<h3>✅ Что сделать</h3>
|
||
<div class="error-detail-content">
|
||
<ol class="error-steps">
|
||
${result.solution.solution.map(s => `<li>${s}</li>`).join('')}
|
||
</ol>
|
||
</div>
|
||
</div>
|
||
|
||
${result.solution.contact ? `
|
||
<div class="card" style="background: #fef3c7; border-left: 4px solid var(--warning);">
|
||
<h4 style="margin-bottom: 8px;">📞 Если не помогло</h4>
|
||
<p>${result.solution.contact}</p>
|
||
</div>
|
||
` : ''}
|
||
|
||
<div style="margin-top: 24px; text-align: center;">
|
||
<button class="btn btn-primary" onclick="document.getElementById('uploadModal').style.display='none'">
|
||
Понятно
|
||
</button>
|
||
</div>
|
||
</div>
|
||
`;
|
||
}, 2000);
|
||
}
|
||
|
||
// === Быстрые решения ===
|
||
function setupQuickSolutions() {
|
||
document.querySelectorAll('.solution-card').forEach(card => {
|
||
card.addEventListener('click', () => {
|
||
const key = card.dataset.solution;
|
||
const solution = KB.quickSolutions[key];
|
||
if (solution) showQuickSolution(solution);
|
||
});
|
||
});
|
||
}
|
||
|
||
function showQuickSolution(solution) {
|
||
const modal = document.getElementById('errorModal');
|
||
const title = document.getElementById('errorModalTitle');
|
||
const body = document.getElementById('errorModalBody');
|
||
|
||
title.textContent = solution.title;
|
||
body.innerHTML = `
|
||
<div class="error-detail-section">
|
||
<h3>📋 Алгоритм действий</h3>
|
||
<div class="error-detail-content">
|
||
<ol class="error-steps">
|
||
${solution.steps.map(s => `<li>${s}</li>`).join('')}
|
||
</ol>
|
||
</div>
|
||
</div>
|
||
${solution.contact ? `
|
||
<div class="card" style="background: #fef3c7; border-left: 4px solid var(--warning);">
|
||
<h4 style="margin-bottom: 8px;">📞 Контакты</h4>
|
||
<p>${solution.contact}</p>
|
||
</div>
|
||
` : ''}
|
||
`;
|
||
|
||
modal.style.display = 'flex';
|
||
}
|
||
|
||
// === Рендер уроков ===
|
||
function renderLessons() {
|
||
const container = document.getElementById('lessonsList');
|
||
container.innerHTML = KB.lessons.map(lesson => `
|
||
<div class="lesson-card" data-id="${lesson.id}">
|
||
<div class="lesson-header">
|
||
<div class="lesson-icon">${lesson.icon}</div>
|
||
<div>
|
||
<div class="lesson-title">${lesson.title}</div>
|
||
<div class="lesson-desc">${lesson.description}</div>
|
||
<div style="font-size: 13px; color: var(--text-muted); margin-top: 4px;">
|
||
⏱ ${lesson.duration}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`).join('');
|
||
|
||
container.querySelectorAll('.lesson-card').forEach(card => {
|
||
card.addEventListener('click', () => {
|
||
const lesson = KB.lessons.find(l => l.id === card.dataset.id);
|
||
showLessonModal(lesson);
|
||
});
|
||
});
|
||
}
|
||
|
||
function showLessonModal(lesson) {
|
||
const modal = document.getElementById('lessonModal');
|
||
const title = document.getElementById('lessonModalTitle');
|
||
const body = document.getElementById('lessonModalBody');
|
||
|
||
title.textContent = lesson.title;
|
||
body.innerHTML = `
|
||
<div style="line-height: 1.8;">
|
||
${lesson.content}
|
||
</div>
|
||
<div style="margin-top: 32px; text-align: center;">
|
||
<button class="btn btn-primary" onclick="document.getElementById('lessonModal').style.display='none'">
|
||
Завершить урок
|
||
</button>
|
||
</div>
|
||
`;
|
||
|
||
modal.style.display = 'flex';
|
||
}
|
||
|
||
// === Рендер ошибок ===
|
||
function renderErrors() {
|
||
renderErrorsGrid(KB.errors);
|
||
}
|
||
|
||
function renderErrorsGrid(errors) {
|
||
const container = document.getElementById('errorsGrid');
|
||
if (errors.length === 0) {
|
||
container.innerHTML = '<p style="text-align: center; color: var(--text-muted); grid-column: 1/-1;">Ошибки не найдены</p>';
|
||
return;
|
||
}
|
||
|
||
container.innerHTML = errors.map(err => `
|
||
<div class="error-card" data-id="${err.id}">
|
||
<div class="error-code">Код: ${err.code}</div>
|
||
<div class="error-title">${err.title}</div>
|
||
<div class="error-preview">${err.meaning}</div>
|
||
</div>
|
||
`).join('');
|
||
|
||
container.querySelectorAll('.error-card').forEach(card => {
|
||
card.addEventListener('click', () => {
|
||
const err = KB.errors.find(e => e.id === card.dataset.id);
|
||
showErrorModal(err);
|
||
});
|
||
});
|
||
}
|
||
|
||
function filterErrors(query) {
|
||
const filtered = KB.errors.filter(err =>
|
||
err.code.toLowerCase().includes(query) ||
|
||
err.title.toLowerCase().includes(query) ||
|
||
err.meaning.toLowerCase().includes(query)
|
||
);
|
||
renderErrorsGrid(filtered);
|
||
}
|
||
|
||
function showErrorModal(err) {
|
||
const modal = document.getElementById('errorModal');
|
||
const title = document.getElementById('errorModalTitle');
|
||
const body = document.getElementById('errorModalBody');
|
||
|
||
title.textContent = `Ошибка ${err.code}`;
|
||
body.innerHTML = `
|
||
<div class="error-detail-section">
|
||
<h3>📖 Что это значит</h3>
|
||
<div class="error-detail-content">${err.meaning}</div>
|
||
</div>
|
||
|
||
<div class="error-detail-section">
|
||
<h3>⚠️ Почему возникает</h3>
|
||
<div class="error-detail-content">
|
||
<ol style="margin: 0; padding-left: 20px;">
|
||
${err.causes.map(c => `<li>${c}</li>`).join('')}
|
||
</ol>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="error-detail-section">
|
||
<h3>✅ Что сделать</h3>
|
||
<div class="error-detail-content">
|
||
<ol class="error-steps">
|
||
${err.solution.map(s => `<li>${s}</li>`).join('')}
|
||
</ol>
|
||
</div>
|
||
</div>
|
||
|
||
${err.contact ? `
|
||
<div class="card" style="background: #fef3c7; border-left: 4px solid var(--warning);">
|
||
<h4 style="margin-bottom: 8px;">📞 Если не помогло</h4>
|
||
<p>${err.contact}</p>
|
||
</div>
|
||
` : ''}
|
||
`;
|
||
|
||
modal.style.display = 'flex';
|
||
}
|
||
|
||
// === Рендер инструкций ===
|
||
function renderInstructions() {
|
||
const container = document.getElementById('instructionsList');
|
||
const categories = [...new Set(KB.instructions.map(i => i.category))];
|
||
|
||
container.innerHTML = categories.map(cat => `
|
||
<div style="margin-bottom: 32px;">
|
||
<h3 style="margin-bottom: 16px; color: var(--text-muted); font-size: 14px; text-transform: uppercase;">
|
||
${cat}
|
||
</h3>
|
||
<div style="display: flex; flex-direction: column; gap: 12px;">
|
||
${KB.instructions.filter(i => i.category === cat).map(inst => `
|
||
<div class="instruction-card" data-id="${inst.id}">
|
||
<div class="instruction-header">
|
||
<div class="instruction-icon">${inst.icon}</div>
|
||
<div>
|
||
<div class="instruction-title">${inst.title}</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
`).join('')}
|
||
</div>
|
||
</div>
|
||
`).join('');
|
||
|
||
container.querySelectorAll('.instruction-card').forEach(card => {
|
||
card.addEventListener('click', () => {
|
||
const inst = KB.instructions.find(i => i.id === card.dataset.id);
|
||
showInstructionModal(inst);
|
||
});
|
||
});
|
||
}
|
||
|
||
function showInstructionModal(inst) {
|
||
const modal = document.getElementById('lessonModal');
|
||
const title = document.getElementById('lessonModalTitle');
|
||
const body = document.getElementById('lessonModalBody');
|
||
|
||
title.textContent = inst.title;
|
||
body.innerHTML = `
|
||
<div style="margin-bottom: 24px;">
|
||
<p style="color: var(--text-muted);">Категория: ${inst.category}</p>
|
||
</div>
|
||
<div>
|
||
<h3 style="margin-bottom: 20px;">Пошаговая инструкция</h3>
|
||
<ol class="error-steps" style="counter-reset: step-counter;">
|
||
${inst.steps.map((step, i) => `
|
||
<li style="margin-bottom: 16px; padding-bottom: 16px; border-bottom: 1px solid var(--border);">
|
||
${step}
|
||
</li>
|
||
`).join('')}
|
||
</ol>
|
||
</div>
|
||
<div style="margin-top: 32px; text-align: center;">
|
||
<button class="btn btn-primary" onclick="document.getElementById('lessonModal').style.display='none'">
|
||
Закрыть
|
||
</button>
|
||
</div>
|
||
`;
|
||
|
||
modal.style.display = 'flex';
|
||
}
|
||
|
||
// === Рендер FAQ ===
|
||
function renderFAQ() {
|
||
const container = document.getElementById('faqList');
|
||
container.innerHTML = KB.faq.map((f, i) => `
|
||
<div class="faq-card" data-index="${i}">
|
||
<div class="faq-question">
|
||
<span>${f.question}</span>
|
||
<span class="faq-icon">+</span>
|
||
</div>
|
||
<div class="faq-answer">
|
||
${f.answer}
|
||
</div>
|
||
</div>
|
||
`).join('');
|
||
|
||
container.querySelectorAll('.faq-card').forEach(card => {
|
||
card.addEventListener('click', () => {
|
||
card.classList.toggle('active');
|
||
});
|
||
});
|
||
}
|
||
|
||
// === Калькулятор ===
|
||
function renderTariffs() {
|
||
const select = document.getElementById('calcTariff');
|
||
select.innerHTML = KB.tariffs.map(t =>
|
||
`<option value="${t.id}">${t.name} — ${t.price.toLocaleString('ru-RU')} ₸</option>`
|
||
).join('');
|
||
}
|
||
|
||
function setupCalculator() {
|
||
document.getElementById('calcBtn').addEventListener('click', calculateDiscount);
|
||
}
|
||
|
||
function calculateDiscount() {
|
||
const tariffId = document.getElementById('calcTariff').value;
|
||
const type = document.getElementById('calcType').value;
|
||
const amount = parseFloat(document.getElementById('calcAmount').value);
|
||
const period = parseInt(document.getElementById('calcPeriod').value);
|
||
const reason = document.getElementById('calcReason').value;
|
||
|
||
// Валидация
|
||
if (!amount || amount <= 0) {
|
||
alert('Введите корректный размер скидки');
|
||
return;
|
||
}
|
||
|
||
if (!reason) {
|
||
alert('Выберите основание для скидки');
|
||
return;
|
||
}
|
||
|
||
const tariff = KB.tariffs.find(t => t.id === tariffId);
|
||
let discountAmount, finalPrice, totalSavings;
|
||
|
||
if (type === 'percent') {
|
||
if (amount > 100) {
|
||
alert('Процентная скидка не может быть больше 100%');
|
||
return;
|
||
}
|
||
discountAmount = (tariff.price * amount) / 100;
|
||
finalPrice = tariff.price - discountAmount;
|
||
totalSavings = discountAmount * period;
|
||
} else {
|
||
if (amount >= tariff.price) {
|
||
alert('Фиксированная скидка не может превышать абонентскую плату');
|
||
return;
|
||
}
|
||
discountAmount = amount;
|
||
finalPrice = tariff.price - discountAmount;
|
||
totalSavings = discountAmount * period;
|
||
}
|
||
|
||
const container = document.getElementById('calcResult');
|
||
container.style.display = 'block';
|
||
container.innerHTML = `
|
||
<h3>Результат расчета</h3>
|
||
<div class="result-row">
|
||
<span>Тариф:</span>
|
||
<span>${tariff.name}</span>
|
||
</div>
|
||
<div class="result-row">
|
||
<span>Абонентская плата:</span>
|
||
<span>${tariff.price.toLocaleString('ru-RU')} ₸</span>
|
||
</div>
|
||
<div class="result-row">
|
||
<span>Тип скидки:</span>
|
||
<span>${type === 'percent' ? `${amount}%` : `${amount.toLocaleString('ru-RU')} ₸`}</span>
|
||
</div>
|
||
<div class="result-row">
|
||
<span>Срок действия:</span>
|
||
<span>${period} мес.</span>
|
||
</div>
|
||
<div class="result-row">
|
||
<span>Итоговая плата в месяц:</span>
|
||
<span>${finalPrice.toLocaleString('ru-RU', { minimumFractionDigits: 2, maximumFractionDigits: 2 })} ₸</span>
|
||
</div>
|
||
<div class="result-row">
|
||
<span>Экономия за период:</span>
|
||
<span>${totalSavings.toLocaleString('ru-RU', { minimumFractionDigits: 2, maximumFractionDigits: 2 })} ₸</span>
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
// === AI-помощник ===
|
||
function setupAssistant() {
|
||
const input = document.getElementById('chatInput');
|
||
const btn = document.getElementById('chatSendBtn');
|
||
|
||
btn.addEventListener('click', () => {
|
||
const message = input.value.trim();
|
||
if (message) {
|
||
sendChatMessage(message);
|
||
input.value = '';
|
||
}
|
||
});
|
||
|
||
input.addEventListener('keypress', (e) => {
|
||
if (e.key === 'Enter' && input.value.trim()) {
|
||
sendChatMessage(input.value.trim());
|
||
input.value = '';
|
||
}
|
||
});
|
||
|
||
// Обратная связь
|
||
document.getElementById('feedbackYes').addEventListener('click', () => {
|
||
showFeedbackResponse('Спасибо за оценку! Рад, что помог.');
|
||
});
|
||
|
||
document.getElementById('feedbackNo').addEventListener('click', () => {
|
||
showFeedbackResponse('Что можно улучшить? Вы можете задать вопрос подробнее или обратиться к руководителю.');
|
||
});
|
||
}
|
||
|
||
function sendChatMessage(message) {
|
||
const messagesContainer = document.getElementById('chatMessages');
|
||
const feedbackBlock = document.getElementById('feedbackBlock');
|
||
|
||
// Сообщение пользователя
|
||
messagesContainer.innerHTML += `
|
||
<div class="message user">
|
||
<div class="message-content">
|
||
<p>${escapeHtml(message)}</p>
|
||
</div>
|
||
</div>
|
||
`;
|
||
|
||
// Имитация ответа AI (в реальности здесь будет API)
|
||
const response = generateAIResponse(message);
|
||
|
||
setTimeout(() => {
|
||
messagesContainer.innerHTML += `
|
||
<div class="message bot">
|
||
<div class="message-avatar">🤖</div>
|
||
<div class="message-content">
|
||
${response}
|
||
</div>
|
||
</div>
|
||
`;
|
||
messagesContainer.scrollTop = messagesContainer.scrollHeight;
|
||
feedbackBlock.style.display = 'flex';
|
||
}, 800);
|
||
}
|
||
|
||
function generateAIResponse(message) {
|
||
const lowerMsg = message.toLowerCase();
|
||
|
||
// Поиск по базе знаний
|
||
let bestMatch = null;
|
||
let bestScore = 0;
|
||
|
||
// Проверка ошибок
|
||
KB.errors.forEach(err => {
|
||
const score = calculateSimilarity(lowerMsg, err.code.toLowerCase() + ' ' + err.title.toLowerCase());
|
||
if (score > bestScore) {
|
||
bestScore = score;
|
||
bestMatch = { type: 'error', data: err };
|
||
}
|
||
});
|
||
|
||
// Проверка FAQ
|
||
KB.faq.forEach(f => {
|
||
const score = calculateSimilarity(lowerMsg, f.question.toLowerCase());
|
||
if (score > bestScore) {
|
||
bestScore = score;
|
||
bestMatch = { type: 'faq', data: f };
|
||
}
|
||
});
|
||
|
||
if (bestMatch && bestScore > 0.3) {
|
||
if (bestMatch.type === 'error') {
|
||
const err = bestMatch.data;
|
||
return `
|
||
<p><strong>Это ошибка: ${err.code} — ${err.title}</strong></p>
|
||
<p><strong>Что это значит:</strong> ${err.meaning}</p>
|
||
<p><strong>Причины:</strong></p>
|
||
<ul>${err.causes.map(c => `<li>${c}</li>`).join('')}</ul>
|
||
<p><strong>Что сделать:</strong></p>
|
||
<ol>${err.solution.map(s => `<li>${s}</li>`).join('')}</ol>
|
||
${err.contact ? `<p><strong>Контакты:</strong> ${err.contact}</p>` : ''}
|
||
`;
|
||
} else if (bestMatch.type === 'faq') {
|
||
const f = bestMatch.data;
|
||
return `<p><strong>${f.question}</strong></p><p>${f.answer}</p>`;
|
||
}
|
||
}
|
||
|
||
// Ответ по умолчанию
|
||
return `
|
||
<p>Я нашёл несколько материалов, которые могут помочь:</p>
|
||
<ul>
|
||
<li>Раздел <strong>"Ошибки"</strong> — справочник кодов ошибок и решений</li>
|
||
<li>Раздел <strong>"Обучение"</strong> — пошаговые уроки по работе со скидками</li>
|
||
<li>Раздел <strong>"FAQ"</strong> — частые вопросы и ответы</li>
|
||
</ul>
|
||
<p>Вы можете уточнить вопрос или открыть один из этих разделов.</p>
|
||
`;
|
||
}
|
||
|
||
function calculateSimilarity(s1, s2) {
|
||
const words1 = s1.split(/\s+/);
|
||
const words2 = s2.split(/\s+/);
|
||
const matches = words1.filter(w => words2.some(w2 => w2.includes(w) || w.includes(w2)));
|
||
return matches.length / Math.max(words1.length, words2.length);
|
||
}
|
||
|
||
function showFeedbackResponse(text) {
|
||
const feedbackBlock = document.getElementById('feedbackBlock');
|
||
feedbackBlock.innerHTML = `<span style="color: var(--text-muted);">${text}</span>`;
|
||
setTimeout(() => {
|
||
feedbackBlock.style.display = 'none';
|
||
feedbackBlock.innerHTML = `
|
||
<span>Ответ помог?</span>
|
||
<button class="btn-icon feedback-btn" id="feedbackYes">👍</button>
|
||
<button class="btn-icon feedback-btn" id="feedbackNo">👎</button>
|
||
`;
|
||
setupAssistant();
|
||
}, 3000);
|
||
}
|
||
|
||
// === Тесты ===
|
||
function setupTests() {
|
||
document.getElementById('startTestBtn').addEventListener('click', startTest);
|
||
document.getElementById('nextQuestionBtn').addEventListener('click', nextQuestion);
|
||
document.getElementById('restartTestBtn').addEventListener('click', () => {
|
||
document.getElementById('testResult').style.display = 'none';
|
||
document.getElementById('testsIntro').style.display = 'block';
|
||
});
|
||
}
|
||
|
||
function startTest() {
|
||
const test = KB.tests[0];
|
||
state.testCurrent = test;
|
||
state.testScore = 0;
|
||
state.testQuestionIndex = 0;
|
||
state.testAnswers = [];
|
||
|
||
document.getElementById('testsIntro').style.display = 'none';
|
||
document.getElementById('testContainer').style.display = 'block';
|
||
|
||
showQuestion();
|
||
}
|
||
|
||
function showQuestion() {
|
||
const test = state.testCurrent;
|
||
const q = test.questions[state.testQuestionIndex];
|
||
|
||
document.getElementById('testCounter').textContent =
|
||
`${state.testQuestionIndex + 1}/${test.questions.length}`;
|
||
|
||
document.getElementById('testProgress').style.width =
|
||
`${((state.testQuestionIndex) / test.questions.length) * 100}%`;
|
||
|
||
document.getElementById('testQuestion').textContent = q.question;
|
||
document.getElementById('testAnswers').innerHTML = q.answers.map((a, i) => `
|
||
<div class="test-answer" data-index="${i}">${a}</div>
|
||
`).join('');
|
||
|
||
document.getElementById('testFeedback').className = 'test-feedback';
|
||
document.getElementById('testFeedback').style.display = 'none';
|
||
document.getElementById('nextQuestionBtn').style.display = 'none';
|
||
|
||
// Выбор ответа
|
||
document.querySelectorAll('.test-answer').forEach(ans => {
|
||
ans.addEventListener('click', () => {
|
||
document.querySelectorAll('.test-answer').forEach(a => a.classList.remove('selected'));
|
||
ans.classList.add('selected');
|
||
|
||
// Проверка
|
||
const selectedIndex = parseInt(ans.dataset.index);
|
||
checkAnswer(selectedIndex);
|
||
});
|
||
});
|
||
}
|
||
|
||
function checkAnswer(selectedIndex) {
|
||
const test = state.testCurrent;
|
||
const q = test.questions[state.testQuestionIndex];
|
||
const isCorrect = selectedIndex === q.correct;
|
||
|
||
if (isCorrect) {
|
||
state.testScore++;
|
||
}
|
||
|
||
state.testAnswers.push({
|
||
question: q.question,
|
||
selected: selectedIndex,
|
||
correct: q.correct,
|
||
isCorrect
|
||
});
|
||
|
||
// Показать результат
|
||
const feedback = document.getElementById('testFeedback');
|
||
feedback.textContent = isCorrect ? '✅ Правильно! ' + q.explanation : '❌ Неправильно. ' + q.explanation;
|
||
feedback.className = 'test-feedback ' + (isCorrect ? '' : 'incorrect');
|
||
feedback.style.display = 'block';
|
||
|
||
// Блокировка ответов
|
||
document.querySelectorAll('.test-answer').forEach((a, i) => {
|
||
if (i === q.correct) a.classList.add('correct');
|
||
if (i === selectedIndex && !isCorrect) a.classList.add('incorrect');
|
||
});
|
||
|
||
document.getElementById('nextQuestionBtn').style.display = 'inline-block';
|
||
}
|
||
|
||
function nextQuestion() {
|
||
state.testQuestionIndex++;
|
||
|
||
const test = state.testCurrent;
|
||
if (state.testQuestionIndex >= test.questions.length) {
|
||
showTestResult();
|
||
} else {
|
||
showQuestion();
|
||
}
|
||
}
|
||
|
||
function showTestResult() {
|
||
document.getElementById('testContainer').style.display = 'none';
|
||
document.getElementById('testResult').style.display = 'block';
|
||
|
||
const score = state.testScore;
|
||
const total = state.testCurrent.questions.length;
|
||
const percentage = (score / total) * 100;
|
||
|
||
document.getElementById('resultScore').textContent = `${score}/${total}`;
|
||
|
||
let message = '';
|
||
if (percentage >= 90) {
|
||
message = '🎉 Отлично! Вы полностью готовы к работе со скидками.';
|
||
} else if (percentage >= 70) {
|
||
message = '👍 Хорошо! Есть небольшие пробелы, но вы готовы к работе.';
|
||
} else if (percentage >= 50) {
|
||
message = '📚 Неплохо, но стоит повторить материал и пройти тест ещё раз.';
|
||
} else {
|
||
message = '⚠️ Рекомендуем пройти обучение ещё раз и внимательно изучить материалы.';
|
||
}
|
||
|
||
document.getElementById('resultMessage').textContent = message;
|
||
}
|
||
|
||
// === Админ-панель ===
|
||
function setupAdmin() {
|
||
document.querySelectorAll('.admin-tab').forEach(tab => {
|
||
tab.addEventListener('click', () => {
|
||
document.querySelectorAll('.admin-tab').forEach(t => t.classList.remove('active'));
|
||
document.querySelectorAll('.admin-panel').forEach(p => p.classList.remove('active'));
|
||
|
||
tab.classList.add('active');
|
||
document.getElementById(`admin-${tab.dataset.tab}`).classList.add('active');
|
||
});
|
||
});
|
||
|
||
renderAdminPanel('errors');
|
||
}
|
||
|
||
function renderAdminPanel(type) {
|
||
const container = document.getElementById(`admin-${type}`);
|
||
|
||
if (type === 'errors') {
|
||
container.innerHTML = `
|
||
<button class="btn btn-primary admin-add-btn">+ Добавить ошибку</button>
|
||
${KB.errors.map(err => `
|
||
<div class="admin-item">
|
||
<div>
|
||
<strong>${err.code}</strong> — ${err.title}
|
||
</div>
|
||
<div class="admin-item-actions">
|
||
<button class="admin-btn admin-btn-edit">Изменить</button>
|
||
<button class="admin-btn admin-btn-delete">Удалить</button>
|
||
</div>
|
||
</div>
|
||
`).join('')}
|
||
`;
|
||
}
|
||
|
||
if (type === 'faq') {
|
||
container.innerHTML = `
|
||
<button class="btn btn-primary admin-add-btn">+ Добавить вопрос</button>
|
||
${KB.faq.map(f => `
|
||
<div class="admin-item">
|
||
<div style="flex: 1;">
|
||
<strong>${f.question}</strong>
|
||
</div>
|
||
<div class="admin-item-actions">
|
||
<button class="admin-btn admin-btn-edit">Изменить</button>
|
||
<button class="admin-btn admin-btn-delete">Удалить</button>
|
||
</div>
|
||
</div>
|
||
`).join('')}
|
||
`;
|
||
}
|
||
|
||
if (type === 'lessons') {
|
||
container.innerHTML = `
|
||
<button class="btn btn-primary admin-add-btn">+ Добавить урок</button>
|
||
${KB.lessons.map(l => `
|
||
<div class="admin-item">
|
||
<div>
|
||
<strong>${l.icon} ${l.title}</strong>
|
||
<div style="font-size: 13px; color: var(--text-muted);">${l.description}</div>
|
||
</div>
|
||
<div class="admin-item-actions">
|
||
<button class="admin-btn admin-btn-edit">Изменить</button>
|
||
<button class="admin-btn admin-btn-delete">Удалить</button>
|
||
</div>
|
||
</div>
|
||
`).join('')}
|
||
`;
|
||
}
|
||
|
||
if (type === 'instructions') {
|
||
container.innerHTML = `
|
||
<button class="btn btn-primary admin-add-btn">+ Добавить инструкцию</button>
|
||
${KB.instructions.map(i => `
|
||
<div class="admin-item">
|
||
<div>
|
||
<strong>${i.icon} ${i.title}</strong>
|
||
<div style="font-size: 13px; color: var(--text-muted);">${i.category}</div>
|
||
</div>
|
||
<div class="admin-item-actions">
|
||
<button class="admin-btn admin-btn-edit">Изменить</button>
|
||
<button class="admin-btn admin-btn-delete">Удалить</button>
|
||
</div>
|
||
</div>
|
||
`).join('')}
|
||
`;
|
||
}
|
||
|
||
if (type === 'tests') {
|
||
container.innerHTML = `
|
||
<button class="btn btn-primary admin-add-btn">+ Добавить тест</button>
|
||
${KB.tests.map(t => `
|
||
<div class="admin-item">
|
||
<div>
|
||
<strong>${t.title}</strong>
|
||
<div style="font-size: 13px; color: var(--text-muted);">${t.questions.length} вопросов</div>
|
||
</div>
|
||
<div class="admin-item-actions">
|
||
<button class="admin-btn admin-btn-edit">Изменить</button>
|
||
<button class="admin-btn admin-btn-delete">Удалить</button>
|
||
</div>
|
||
</div>
|
||
`).join('')}
|
||
`;
|
||
}
|
||
}
|
||
|
||
// === Сохранение состояния ===
|
||
function saveState() {
|
||
localStorage.setItem('discountHelper_state', JSON.stringify({
|
||
currentPage: state.currentPage
|
||
}));
|
||
}
|
||
|
||
function loadState() {
|
||
const saved = localStorage.getItem('discountHelper_state');
|
||
if (saved) {
|
||
try {
|
||
const data = JSON.parse(saved);
|
||
state.currentPage = data.currentPage || 'home';
|
||
} catch (e) {}
|
||
}
|
||
navigateTo(state.currentPage);
|
||
}
|
||
|
||
// === Утилиты ===
|
||
function escapeHtml(text) {
|
||
const div = document.createElement('div');
|
||
div.textContent = text;
|
||
return div.innerHTML;
|
||
}
|
||
|
||
// === Закрытие модальных окон ===
|
||
document.getElementById('closeErrorModal').addEventListener('click', () => {
|
||
document.getElementById('errorModal').style.display = 'none';
|
||
});
|
||
|
||
document.getElementById('closeUploadModal').addEventListener('click', () => {
|
||
document.getElementById('uploadModal').style.display = 'none';
|
||
});
|
||
|
||
document.getElementById('closeLessonModal').addEventListener('click', () => {
|
||
document.getElementById('lessonModal').style.display = 'none';
|
||
});
|
||
|
||
// Клик по overlay
|
||
document.querySelectorAll('.modal-overlay').forEach(overlay => {
|
||
overlay.addEventListener('click', (e) => {
|
||
if (e.target === overlay) {
|
||
overlay.style.display = 'none';
|
||
}
|
||
});
|
||
});
|
||
|
||
// Клавиша Escape
|
||
document.addEventListener('keydown', (e) => {
|
||
if (e.key === 'Escape') {
|
||
document.querySelectorAll('.modal-overlay').forEach(m => m.style.display = 'none');
|
||
}
|
||
});
|
||
|
||
// === Старт ===
|
||
init();
|