diff --git a/discount-helper/app.js b/discount-helper/app.js
new file mode 100644
index 0000000..bcac84c
--- /dev/null
+++ b/discount-helper/app.js
@@ -0,0 +1,1097 @@
+// === ПРИЛОЖЕНИЕ "ПОМОЩНИК ПО СКИДКАМ" ===
+
+// Состояние приложения
+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 = `
+
+
По запросу "${escapeHtml(query)}" ничего не найдено.
+
+ Попробуйте:
+ • Задать вопрос в AI-помощнике
+ • Посмотреть раздел "Ошибки" или "FAQ"
+
+
+ `;
+ } else {
+ container.innerHTML = results.map(r => `
+
+
${escapeHtml(r.title)}
+
${escapeHtml(r.preview)}
+
+ `).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 = `
+
+
+
🔍
+
+
Анализ завершён
+
+ Уверенность распознавания: ${result.confidence}%
+
+
+
+
+
+
📋 Я вижу ошибку:
+
${result.detected}
+
+
+
+
📖 Что это значит
+
${result.solution.meaning}
+
+
+
+
⚠️ Возможные причины
+
+
+ ${result.solution.causes.map(c => `- ${c}
`).join('')}
+
+
+
+
+
+
✅ Что сделать
+
+
+ ${result.solution.solution.map(s => `- ${s}
`).join('')}
+
+
+
+
+ ${result.solution.contact ? `
+
+
📞 Если не помогло
+
${result.solution.contact}
+
+ ` : ''}
+
+
+
+
+
+ `;
+ }, 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 = `
+
+
📋 Алгоритм действий
+
+
+ ${solution.steps.map(s => `- ${s}
`).join('')}
+
+
+
+ ${solution.contact ? `
+
+
📞 Контакты
+
${solution.contact}
+
+ ` : ''}
+ `;
+
+ modal.style.display = 'flex';
+}
+
+// === Рендер уроков ===
+function renderLessons() {
+ const container = document.getElementById('lessonsList');
+ container.innerHTML = KB.lessons.map(lesson => `
+
+
+
+ `).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 = `
+
+ ${lesson.content}
+
+
+
+
+ `;
+
+ modal.style.display = 'flex';
+}
+
+// === Рендер ошибок ===
+function renderErrors() {
+ renderErrorsGrid(KB.errors);
+}
+
+function renderErrorsGrid(errors) {
+ const container = document.getElementById('errorsGrid');
+ if (errors.length === 0) {
+ container.innerHTML = 'Ошибки не найдены
';
+ return;
+ }
+
+ container.innerHTML = errors.map(err => `
+
+
Код: ${err.code}
+
${err.title}
+
${err.meaning}
+
+ `).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 = `
+
+
📖 Что это значит
+
${err.meaning}
+
+
+
+
⚠️ Почему возникает
+
+
+ ${err.causes.map(c => `- ${c}
`).join('')}
+
+
+
+
+
+
✅ Что сделать
+
+
+ ${err.solution.map(s => `- ${s}
`).join('')}
+
+
+
+
+ ${err.contact ? `
+
+
📞 Если не помогло
+
${err.contact}
+
+ ` : ''}
+ `;
+
+ 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 => `
+
+
+ ${cat}
+
+
+ ${KB.instructions.filter(i => i.category === cat).map(inst => `
+
+
+
+ `).join('')}
+
+
+ `).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 = `
+
+
Категория: ${inst.category}
+
+
+
Пошаговая инструкция
+
+ ${inst.steps.map((step, i) => `
+ -
+ ${step}
+
+ `).join('')}
+
+
+
+
+
+ `;
+
+ modal.style.display = 'flex';
+}
+
+// === Рендер FAQ ===
+function renderFAQ() {
+ const container = document.getElementById('faqList');
+ container.innerHTML = KB.faq.map((f, i) => `
+
+
+ ${f.question}
+ +
+
+
+ ${f.answer}
+
+
+ `).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 =>
+ ``
+ ).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 = `
+ Результат расчета
+
+ Тариф:
+ ${tariff.name}
+
+
+ Абонентская плата:
+ ${tariff.price.toLocaleString('ru-RU')} ₸
+
+
+ Тип скидки:
+ ${type === 'percent' ? `${amount}%` : `${amount.toLocaleString('ru-RU')} ₸`}
+
+
+ Срок действия:
+ ${period} мес.
+
+
+ Итоговая плата в месяц:
+ ${finalPrice.toLocaleString('ru-RU', { minimumFractionDigits: 2, maximumFractionDigits: 2 })} ₸
+
+
+ Экономия за период:
+ ${totalSavings.toLocaleString('ru-RU', { minimumFractionDigits: 2, maximumFractionDigits: 2 })} ₸
+
+ `;
+}
+
+// === 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 += `
+
+
+
${escapeHtml(message)}
+
+
+ `;
+
+ // Имитация ответа AI (в реальности здесь будет API)
+ const response = generateAIResponse(message);
+
+ setTimeout(() => {
+ messagesContainer.innerHTML += `
+
+ `;
+ 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 `
+ Это ошибка: ${err.code} — ${err.title}
+ Что это значит: ${err.meaning}
+ Причины:
+ ${err.causes.map(c => `- ${c}
`).join('')}
+ Что сделать:
+ ${err.solution.map(s => `- ${s}
`).join('')}
+ ${err.contact ? `Контакты: ${err.contact}
` : ''}
+ `;
+ } else if (bestMatch.type === 'faq') {
+ const f = bestMatch.data;
+ return `${f.question}
${f.answer}
`;
+ }
+ }
+
+ // Ответ по умолчанию
+ return `
+ Я нашёл несколько материалов, которые могут помочь:
+
+ - Раздел "Ошибки" — справочник кодов ошибок и решений
+ - Раздел "Обучение" — пошаговые уроки по работе со скидками
+ - Раздел "FAQ" — частые вопросы и ответы
+
+ Вы можете уточнить вопрос или открыть один из этих разделов.
+ `;
+}
+
+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 = `${text}`;
+ setTimeout(() => {
+ feedbackBlock.style.display = 'none';
+ feedbackBlock.innerHTML = `
+ Ответ помог?
+
+
+ `;
+ 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) => `
+ ${a}
+ `).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 = `
+
+ ${KB.errors.map(err => `
+
+
+ ${err.code} — ${err.title}
+
+
+
+
+
+
+ `).join('')}
+ `;
+ }
+
+ if (type === 'faq') {
+ container.innerHTML = `
+
+ ${KB.faq.map(f => `
+
+
+ ${f.question}
+
+
+
+
+
+
+ `).join('')}
+ `;
+ }
+
+ if (type === 'lessons') {
+ container.innerHTML = `
+
+ ${KB.lessons.map(l => `
+
+
+
${l.icon} ${l.title}
+
${l.description}
+
+
+
+
+
+
+ `).join('')}
+ `;
+ }
+
+ if (type === 'instructions') {
+ container.innerHTML = `
+
+ ${KB.instructions.map(i => `
+
+
+
${i.icon} ${i.title}
+
${i.category}
+
+
+
+
+
+
+ `).join('')}
+ `;
+ }
+
+ if (type === 'tests') {
+ container.innerHTML = `
+
+ ${KB.tests.map(t => `
+
+
+
${t.title}
+
${t.questions.length} вопросов
+
+
+
+
+
+
+ `).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();
diff --git a/discount-helper/data.js b/discount-helper/data.js
new file mode 100644
index 0000000..a9ae6cf
--- /dev/null
+++ b/discount-helper/data.js
@@ -0,0 +1,798 @@
+// === БАЗА ЗНАНИЙ ===
+// Центральный источник данных для всего приложения
+
+const KB = {
+ // Ошибки
+ errors: [
+ {
+ id: 'err_001',
+ code: '403',
+ title: 'Нет прав на создание скидки',
+ meaning: 'У пользователя недостаточно прав для выполнения операции',
+ causes: [
+ 'Отсутствует роль "Менеджер по скидкам"',
+ 'Истек срок действия полномочий',
+ 'Попытка создать скидку выше лимита'
+ ],
+ solution: [
+ 'Проверьте свою роль в системе (Раздел "Профиль")',
+ 'Убедитесь, что размер скидки входит в ваш лимит',
+ 'При необходимости запросите согласование у руководителя',
+ 'Обратитесь к администратору для расширения прав'
+ ],
+ contact: 'helpdesk@kazakhtelecom.kz, внутренний номер: 1111'
+ },
+ {
+ id: 'err_002',
+ code: 'TARIFF_NOT_FOUND',
+ title: 'Тариф не найден',
+ meaning: 'Выбранный тариф недоступен для применения скидки',
+ causes: [
+ 'Тариф архивный или выведен из эксплуатации',
+ 'На тариф действуют ограничения по скидкам',
+ 'Неверно выбран код тарифа в системе'
+ ],
+ solution: [
+ 'Проверьте актуальность тарифа в справочнике',
+ 'Убедитесь, что тариф поддерживает скидки',
+ 'Сверьте код тарифа с официальным каталогом',
+ 'Выберите альтернативный тариф из доступных'
+ ],
+ contact: 'Отдел продуктового маркетинга, номер: 2222'
+ },
+ {
+ id: 'err_003',
+ code: 'DISCOUNT_LIMIT',
+ title: 'Превышен лимит скидки',
+ meaning: 'Запрошенный размер скидки превышает максимально допустимый',
+ causes: [
+ 'Размер скидки выше полномочий менеджера',
+ 'Превышен совокупный лимит на клиента',
+ 'Для тарифа установлен максимальный порог'
+ ],
+ solution: [
+ 'Проверьте максимально допустимый размер скидки для тарифа',
+ 'Сверьте свои полномочия (см. таблицу лимитов)',
+ 'Запросите согласование у руководителя при необходимости',
+ 'Предложите клиенту альтернативный вариант скидки'
+ ],
+ contact: 'Руководитель отдела продаж'
+ },
+ {
+ id: 'err_004',
+ code: 'INVALID_PERIOD',
+ title: 'Недопустимый период',
+ meaning: 'Указанный срок действия скидки не соответствует правилам',
+ causes: [
+ 'Срок меньше минимального (3 месяца)',
+ 'Срок превышает максимальный (24 месяца)',
+ 'Дата окончания раньше даты начала'
+ ],
+ solution: [
+ 'Минимальный срок действия скидки — 3 месяца',
+ 'Максимальный срок — 24 месяца',
+ 'Проверьте, что дата окончания позже даты начала',
+ 'Используйте стандартные периоды: 3, 6, 12, 24 месяца'
+ ],
+ contact: null
+ },
+ {
+ id: 'err_005',
+ code: 'CLIENT_NOT_FOUND',
+ title: 'Клиент не найден',
+ meaning: 'Не удалось найти клиента в базе данных',
+ causes: [
+ 'Неверно введен ИИН/БИН',
+ 'Клиент отсутствует в базе',
+ 'Техническая ошибка поиска'
+ ],
+ solution: [
+ 'Проверьте правильность ввода ИИН/БИН (12/9 цифр)',
+ 'Попробуйте найти клиента по номеру договора',
+ 'Убедитесь, что клиент активен в системе',
+ 'При технической ошибке повторите попытку через 5 минут'
+ ],
+ contact: 'Техподдержка: 1111'
+ },
+ {
+ id: 'err_006',
+ code: 'DUPLICATE_DISCOUNT',
+ title: 'Дублирующая скидка',
+ meaning: 'На данный тариф уже оформлена активная скидка',
+ causes: [
+ 'Скидка уже действует на этом тарифе',
+ 'Есть_pending заявка на скидку',
+ 'Попытка продления без закрытия предыдущей'
+ ],
+ solution: [
+ 'Проверьте активные скидки клиента',
+ 'Убедитесь, что текущая скидка завершена',
+ 'При необходимости оформите продление',
+ 'Закройте pending-заявку перед созданием новой'
+ ],
+ contact: null
+ },
+ {
+ id: 'err_007',
+ code: 'MISSING_FIELD',
+ title: 'Не заполнено обязательное поле',
+ meaning: 'Отсутствуют данные в обязательных полях формы',
+ causes: [
+ 'Не выбрано основание для скидки',
+ 'Не указан размер скидки',
+ 'Не выбран тариф или срок действия'
+ ],
+ solution: [
+ 'Проверьте все поля, отмеченные звёздочкой (*)',
+ 'Обязательно выберите основание скидки',
+ 'Укажите размер скидки в процентах или тенге',
+ 'Выберите тариф и срок действия'
+ ],
+ contact: null
+ },
+ {
+ id: 'err_008',
+ code: 'SYSTEM_ERROR',
+ title: 'Системная ошибка',
+ meaning: 'Внутренняя ошибка системы обработки данных',
+ causes: [
+ 'Временный сбой сервера',
+ 'Превышено время ожидания ответа',
+ 'Ошибка базы данных'
+ ],
+ solution: [
+ 'Повторите операцию через 1-2 минуты',
+ 'Очистите кэш браузера (Ctrl+Shift+Delete)',
+ 'Попробуйте в другом браузере',
+ 'При повторении ошибки обратитесь в техподдержку'
+ ],
+ contact: 'helpdesk@kazakhtelecom.kz, 1111'
+ }
+ ],
+
+ // Уроки
+ lessons: [
+ {
+ id: 'lesson_01',
+ icon: '💡',
+ title: 'Что такое скидка',
+ description: 'Основы работы со скидками: виды, условия, ограничения',
+ duration: '10 мин',
+ content: `
+ Что такое скидка?
+ Скидка — это уменьшение абонентской платы или стоимости услуги для клиента на определённый период.
+
+ Зачем используются скидки?
+
+ - Удержание существующих клиентов
+ - Привлечение новых абонентов
+ - Поощрение лояльности
+ - Компенсация при жалобах
+ - Продвижение новых тарифов
+
+
+ Когда можно применять скидку?
+
+
✅ Можно:
+
+ - Клиент хочет уйти к конкуренту
+ - Клиент длительное время пользуется услугами
+ - Проводится акция по тарифу
+ - Клиент пожаловался на качество
+ - Клиент подключает несколько услуг
+
+
+
+
+
❌ Нельзя:
+
+ - Без согласования превышать лимит
+ - Применять к архивным тарифам
+ - Создавать скидку без основания
+ - Обещать клиенту невыполнимые условия
+
+
+
+ Виды скидок
+
+
+ | Тип |
+ Описание |
+ Пример |
+
+
+ | Процентная |
+ Снижение на X% |
+ 30% от абонентской платы |
+
+
+ | Фиксированная |
+ Снижение на X тенге |
+ 5 000 ₸ ежемесячно |
+
+
+ | Разовая |
+ Единовременное начисление |
+ Бонус за подключение |
+
+
+
+ Кто имеет право создавать скидку?
+
+ - Менеджер Call-центра: до 20%
+ - Старший менеджер: до 40%
+ - Руководитель отдела: до 60%
+ - Директор: без ограничений
+
+
+
+
📝 Мини-тест
+
Вопрос: Какой максимальный размер скидки может предложить менеджер Call-центра?
+
Ответ: 20% (смотрите таблицу полномочий выше)
+
+ `
+ },
+ {
+ id: 'lesson_02',
+ icon: '📝',
+ title: 'Как создать скидку',
+ description: 'Пошаговая инструкция по оформлению скидки',
+ duration: '15 мин',
+ content: `
+ Пошаговая инструкция: Создание скидки
+
+
+
1
+
Найдите клиента в системе
+
Введите ИИН/БИН или номер договора в поиске.
+
💡 Убедитесь, что клиент активен и не имеет блокировок
+
+
+
+
2
+
Выберите тариф для скидки
+
Перейдите в раздел "Тарифы клиента" и выберите активный тариф.
+
💡 Скидка применяется только к действующим тарифам
+
+
+
+
3
+
Нажмите "Добавить скидку"
+
Откроется форма создания скидки.
+
💡 Кнопка находится в карточке тарифа
+
+
+
+
4
+
Заполните поля формы
+
+ - Тип скидки: процентная или фиксированная
+ - Размер: числовое значение
+ - Срок действия: от 3 до 24 месяцев
+ - Основание: выберите из списка
+
+
💡 Все поля обязательны для заполнения
+
+
+
+
5
+
Проверьте данные
+
Перед сохранением убедитесь:
+
+ - Размер скидки в пределах вашего лимита
+ - Тариф поддерживает скидки
+ - Срок действия корректен
+ - Основание выбрано
+
+
+
+
+
6
+
Сохраните скидку
+
Нажмите "Создать скидку". Система проверит данные и применит скидку.
+
💡 После сохранения скидка отображается в карточке клиента
+
+
+ Типичные ошибки при создании
+
+ - ❌ Не выбрано основание — система не сохранит
+ - ❌ Превышен лимит — требуется согласование
+ - ❌ Неверный срок — используйте стандартные периоды
+ - ❌ Дублирование — проверьте активные скидки
+
+ `
+ },
+ {
+ id: 'lesson_03',
+ icon: '⚠️',
+ title: 'Типичные ошибки',
+ description: 'Разбор частых ошибок и способы их устранения',
+ duration: '12 мин',
+ content: `
+ Типичные ошибки и их решения
+
+
+
Ошибка 1: "Нет прав на создание скидки"
+
Причина: Вы пытаетесь создать скидку выше своего лимита.
+
Решение:
+
+ - Проверьте свой лимит полномочий
+ - Запросите согласование у руководителя
+ - Или уменьшите размер скидки
+
+
+
+
+
Ошибка 2: "Тариф не найден"
+
Причина: Тариф архивный или недоступен для скидок.
+
Решение:
+
+ - Проверьте статус тарифа в справочнике
+ - Предложите клиенту актуальный тариф
+ - Переведите клиента на новый тариф, затем примените скидку
+
+
+
+
+
Ошибка 3: "Дублирующая скидка"
+
Причина: На тарифе уже есть активная скидка.
+
Решение:
+
+ - Проверьте раздел "Активные скидки"
+ - Дождитесь окончания текущей скидки
+ - Или оформите продление вместо новой
+
+
+
+
+
Ошибка 4: "Недопустимый период"
+
Причина: Срок вне допустимого диапазона.
+
Решение:
+
+ - Минимальный срок: 3 месяца
+ - Максимальный срок: 24 месяца
+ - Используйте стандартные значения: 3, 6, 12, 24
+
+
+
+ Таблица: Коды ошибок
+
+
+ | Код |
+ Описание |
+ Действие |
+
+
+ | 403 |
+ Нет прав |
+ Согласование |
+
+
+ | TARIFF_NOT_FOUND |
+ Тариф не найден |
+ Проверить тариф |
+
+
+ | DISCOUNT_LIMIT |
+ Превышен лимит |
+ Уменьшить / согласовать |
+
+
+ | INVALID_PERIOD |
+ Неверный период |
+ Исправить срок |
+
+
+ `
+ },
+ {
+ id: 'lesson_04',
+ icon: '🎯',
+ title: 'Основания для скидки',
+ description: 'Какие основания указывать и когда',
+ duration: '8 мин',
+ content: `
+ Основания для предоставления скидки
+
+ Правильный выбор основания важен для отчётности и анализа эффективности скидок.
+
+ 1. Лояльность клиента
+ Когда использовать:
+
+ - Клиент пользуется услугами более 1 года
+ - Нет жалоб и просрочек
+ - Клиент подключает дополнительные услуги
+
+ Максимальный размер: до 25%
+
+ 2. Акция / Продвижение
+ Когда использовать:
+
+ - Официальная маркетинговая акция
+ - Запуск нового тарифа
+ - Сезонное предложение
+
+ Максимальный размер: до 40% (по условиям акции)
+
+ 3. Жалоба клиента
+ Когда использовать:
+
+ - Клиент пожаловался на качество
+ - Были технические сбои
+ - Недовольство обслуживанием
+
+ Максимальный размер: до 50% (компенсация)
+ 💡 Требуется указание номера жалобы
+
+ 4. Удержание клиента
+ Когда использовать:
+
+ - Клиент заявил об уходе
+ - Получено заявление на расторжение
+ - Клиент сравнивает с конкурентами
+
+ Максимальный размер: до 60%
+ 💡 Требуется согласование руководителя
+
+ 5. Мультиуслуга
+ Когда использовать:
+
+ - Подключение 2+ услуг одновременно
+ - Переход на конвергентный тариф
+
+ Максимальный размер: до 30%
+
+
+
📝 Проверка знаний
+
Ситуация: Клиент хочет уйти к конкуренту из-за высокой цены. Какое основание выбрать?
+
Ответ: "Удержание клиента" — это максимальная скидка до 60%
+
+ `
+ }
+ ],
+
+ // Инструкции
+ instructions: [
+ {
+ id: 'inst_01',
+ icon: '📋',
+ title: 'Создание процентной скидки',
+ category: 'Базовые операции',
+ steps: [
+ 'Откройте карточку клиента',
+ 'Перейдите на вкладку "Тарифы"',
+ 'Выберите активный тариф',
+ 'Нажмите кнопку "Добавить скидку"',
+ 'Выберите тип "Процентная"',
+ 'Укажите размер в процентах (например, 30)',
+ 'Выберите срок действия (3/6/12/24 месяца)',
+ 'Выберите основание из списка',
+ 'Нажмите "Проверить" — система покажет итоговую сумму',
+ 'Нажмите "Создать скидку"'
+ ]
+ },
+ {
+ id: 'inst_02',
+ icon: '💰',
+ title: 'Создание фиксированной скидки',
+ category: 'Базовые операции',
+ steps: [
+ 'Откройте карточку клиента',
+ 'Перейдите на вкладку "Тарифы"',
+ 'Выберите активный тариф',
+ 'Нажмите кнопку "Добавить скидку"',
+ 'Выберите тип "Фиксированная"',
+ 'Укажите сумму в тенге (например, 5000)',
+ 'Выберите срок действия',
+ 'Выберите основание',
+ 'Проверьте, что сумма не превышает абонентскую плату',
+ 'Нажмите "Создать скидку"'
+ ]
+ },
+ {
+ id: 'inst_03',
+ icon: '🔄',
+ title: 'Продление существующей скидки',
+ category: 'Работа с активными скидками',
+ steps: [
+ 'Найдите клиента в системе',
+ 'Откройте раздел "Активные скидки"',
+ 'Найдите скидку для продления',
+ 'Нажмите кнопку "Продлить"',
+ 'Выберите новый срок действия',
+ 'Проверьте, что основание актуально',
+ 'Нажмите "Подтвердить продление"',
+ 'Система создаст новую скидку со старой даты окончания'
+ ]
+ },
+ {
+ id: 'inst_04',
+ icon: '❌',
+ title: 'Отмена / удаление скидки',
+ category: 'Работа с активными скидками',
+ steps: [
+ 'Найдите клиента',
+ 'Откройте "Активные скидки"',
+ 'Выберите скидку для отмены',
+ 'Нажмите "Отменить скидку"',
+ 'Укажите причину отмены',
+ 'Подтвердите операцию',
+ 'Скидка будет отменена с текущей даты',
+ 'Перерасчёт выполнится автоматически'
+ ]
+ },
+ {
+ id: 'inst_05',
+ icon: '📊',
+ title: 'Проверка лимитов скидки',
+ category: 'Контроль',
+ steps: [
+ 'Откройте свой профиль пользователя',
+ 'Перейдите в раздел "Полномочия"',
+ 'Найдите таблицу "Лимиты на скидки"',
+ 'Определите свой максимальный процент',
+ 'При создании скидки система автоматически проверит лимит',
+ 'При превышении потребуется согласование'
+ ]
+ },
+ {
+ id: 'inst_06',
+ icon: '📄',
+ title: 'Запрос согласования скидки',
+ category: 'Согласования',
+ steps: [
+ 'При создании скидки с превышением лимита система предложит "Запросить согласование"',
+ 'Нажмите эту кнопку',
+ 'Заполните обоснование (почему нужна такая скидка)',
+ 'Прикрепите документы при необходимости',
+ 'Выберите руководителя для согласования',
+ 'Отправьте запрос',
+ 'Отслеживайте статус в разделе "Мои согласования"',
+ 'После согласования скидка будет доступна для создания'
+ ]
+ }
+ ],
+
+ // FAQ
+ faq: [
+ {
+ id: 'faq_01',
+ question: 'Как создать скидку?',
+ answer: 'Откройте карточку клиента → Выберите тариф → Нажмите "Добавить скидку" → Заполните форму (тип, размер, срок, основание) → Проверьте данные → Нажмите "Создать". Подробная инструкция в разделе "Обучение" → "Как создать скидку".'
+ },
+ {
+ id: 'faq_02',
+ question: 'Почему скидка не применяется?',
+ answer: 'Возможные причины: 1) Превышен ваш лимит — запросите согласование; 2) Тариф не поддерживает скидки — выберите другой; 3) Уже есть активная скидка — дождитесь окончания или продлите; 4) Не заполнены обязательные поля — проверьте форму.'
+ },
+ {
+ id: 'faq_03',
+ question: 'Почему не отображается тариф?',
+ answer: 'Тариф может быть: 1) Архивным (выведен из эксплуатации); 2) Неактивным (истёк срок); 3) Недоступным для вашего региона. Проверьте статус тарифа в справочнике или предложите клиенту актуальные тарифы.'
+ },
+ {
+ id: 'faq_04',
+ question: 'Как изменить размер скидки?',
+ answer: 'Изменить активную скидку нельзя. Нужно: 1) Отменить текущую скидку; 2) Создать новую с нужным размером. Убедитесь, что новый размер в пределах вашего лимита.'
+ },
+ {
+ id: 'faq_05',
+ question: 'Какие поля обязательны?',
+ answer: 'Все поля формы обязательны: тип скидки, размер, срок действия, основание. Без заполнения любого из них система не сохранит скидку.'
+ },
+ {
+ id: 'faq_06',
+ question: 'Что делать, если появилась ошибка?',
+ answer: '1) Запишите код ошибки; 2) Откройте раздел "Ошибки" и найдите код; 3) Следуйте пошаговому решению; 4) Если не помогло — обратитесь в техподдержку (контакты указаны в карточке ошибки).'
+ },
+ {
+ id: 'faq_07',
+ question: 'Куда обращаться при технической ошибке?',
+ answer: 'При системных ошибках (код SYSTEM_ERROR, таймауты, сбои): 1) Повторите через 5 минут; 2) Очистите кэш браузера; 3) Если не помогло — helpdesk@kazakhtelecom.kz, внутренний номер 1111.'
+ },
+ {
+ id: 'faq_08',
+ question: 'Какой максимальный размер скидки я могу предложить?',
+ answer: 'Зависит от вашей роли: Менеджер Call-центра — до 20%; Старший менеджер — до 40%; Руководитель отдела — до 60%; Директор — без ограничений. Проверьте свои полномочия в профиле.'
+ },
+ {
+ id: 'faq_09',
+ question: 'Можно ли применить скидку к архивному тарифу?',
+ answer: 'Нет, к архивным тарифам скидки не применяются. Предложите клиенту перейти на актуальный тариф, затем примените скидку.'
+ },
+ {
+ id: 'faq_10',
+ question: 'Как проверить, какие скидки активны у клиента?',
+ answer: 'В карточке клиента откройте вкладку "Активные скидки". Там отображаются все действующие скидки с датами начала и окончания, размером и основанием.'
+ }
+ ],
+
+ // Тесты
+ tests: [
+ {
+ id: 'test_main',
+ title: 'Готов к работе со скидками',
+ description: 'Итоговый тест по работе со скидками',
+ questions: [
+ {
+ question: 'Какое действие необходимо выполнить первым при создании скидки?',
+ answers: [
+ 'Выбрать размер скидки',
+ 'Найти клиента в системе',
+ 'Сохранить заявку',
+ 'Отправить клиенту'
+ ],
+ correct: 1,
+ explanation: 'Первым шагом нужно найти клиента в системе по ИИН/БИН или номеру договора.'
+ },
+ {
+ question: 'Максимальный размер скидки для менеджера Call-центра:',
+ answers: ['10%', '20%', '30%', '40%'],
+ correct: 1,
+ explanation: 'Менеджер Call-центра может самостоятельно предлагать скидку до 20%.'
+ },
+ {
+ question: 'Минимальный срок действия скидки:',
+ answers: ['1 месяц', '3 месяца', '6 месяцев', '12 месяцев'],
+ correct: 1,
+ explanation: 'Минимальный срок действия скидки — 3 месяца.'
+ },
+ {
+ question: 'Что делать, если система выдаёт ошибку "Нет прав на создание скидки"?',
+ answers: [
+ 'Повторить попытку',
+ 'Запросить согласование у руководителя',
+ 'Обратиться в техподдержку',
+ 'Уменьшить размер скидки или запросить согласование'
+ ],
+ correct: 3,
+ explanation: 'Нужно либо уменьшить размер скидки до своего лимита, либо запросить согласование.'
+ },
+ {
+ question: 'Какое основание выбрать, если клиент хочет уйти к конкуренту?',
+ answers: ['Лояльность', 'Акция', 'Удержание клиента', 'Жалоба'],
+ correct: 2,
+ explanation: '"Удержание клиента" — специальное основание для таких ситуаций, максимальная скидка до 60%.'
+ },
+ {
+ question: 'Можно ли изменить активную скидку?',
+ answers: ['Да, в любой момент', 'Нет, нужно отменить и создать новую', 'Только размер', 'Только срок'],
+ correct: 1,
+ explanation: 'Изменить активную скидку нельзя. Нужно отменить текущую и создать новую.'
+ },
+ {
+ question: 'Что означает ошибка TARIFF_NOT_FOUND?',
+ answers: [
+ 'Неверный пароль',
+ 'Тариф архивный или недоступен',
+ 'Клиент не найден',
+ 'Системный сбой'
+ ],
+ correct: 1,
+ explanation: 'Эта ошибка означает, что выбранный тариф архивный или недоступен для скидок.'
+ },
+ {
+ question: 'Какое поле НЕ является обязательным при создании скидки?',
+ answers: ['Тип скидки', 'Размер', 'Основание', 'Все поля обязательны'],
+ correct: 3,
+ explanation: 'Все поля формы обязательны для заполнения.'
+ },
+ {
+ question: 'Максимальный срок действия скидки:',
+ answers: ['6 месяцев', '12 месяцев', '18 месяцев', '24 месяца'],
+ correct: 3,
+ explanation: 'Максимальный срок действия скидки — 24 месяца.'
+ },
+ {
+ question: 'Что делать при ошибке "Дублирующая скидка"?',
+ answers: [
+ 'Создать новую скидку',
+ 'Проверить активные скидки и дождаться окончания',
+ 'Обратиться в техподдержку',
+ 'Удалить клиента'
+ ],
+ correct: 1,
+ explanation: 'Нужно проверить активные скидки клиента и дождаться окончания текущей или оформить продление.'
+ },
+ {
+ question: 'Какое основание даёт максимальный размер скидки?',
+ answers: ['Лояльность', 'Акция', 'Удержание клиента', 'Жалоба'],
+ correct: 2,
+ explanation: '"Удержание клиента" позволяет предложить скидку до 60%.'
+ },
+ {
+ question: 'Куда обращаться при системной ошибке SYSTEM_ERROR?',
+ answers: [
+ 'Руководителю',
+ 'В техподдержку (helpdesk@kazakhtelecom.kz, 1111)',
+ 'Клиенту',
+ 'В отдел кадров'
+ ],
+ correct: 1,
+ explanation: 'При системных ошибках нужно обратиться в техподдержку.'
+ }
+ ]
+ }
+ ],
+
+ // Тарифы для калькулятора
+ tariffs: [
+ { id: 't1', name: 'Интернет ЕШДИ 100', price: 5990 },
+ { id: 't2', name: 'Интернет ЕШДИ 200', price: 7990 },
+ { id: 't3', name: 'Интернет ЕШДИ 500', price: 10990 },
+ { id: 't4', name: 'Домашний телефон + Интернет', price: 4990 },
+ { id: 't5', name: 'Триколор (ТВ + Интернет + Телефон)', price: 8990 }
+ ],
+
+ // Быстрые решения
+ quickSolutions: {
+ 'not-created': {
+ title: 'Не создается скидка',
+ steps: [
+ 'Проверьте, все ли обязательные поля заполнены (тип, размер, срок, основание)',
+ 'Убедитесь, что размер скидки не превышает ваш лимит',
+ 'Проверьте, что тариф активен и поддерживает скидки',
+ 'Убедитесь, что нет активной скидки на этом тарифе',
+ 'Проверьте срок действия (от 3 до 24 месяцев)'
+ ],
+ contact: 'Если проблема не решена — раздел "Ошибки" или техподдержка 1111'
+ },
+ 'error': {
+ title: 'Появилась ошибка',
+ steps: [
+ 'Запишите код ошибки (например, 403, TARIFF_NOT_FOUND)',
+ 'Откройте раздел "Ошибки" и найдите код',
+ 'Следуйте пошаговому решению из карточки ошибки',
+ 'Если ошибки нет в справочнике — используйте AI-помощника',
+ 'При технической ошибке повторите через 5 минут'
+ ],
+ contact: 'helpdesk@kazakhtelecom.kz'
+ },
+ 'no-tariff': {
+ title: 'Не вижу нужный тариф',
+ steps: [
+ 'Проверьте, активен ли тариф у клиента',
+ 'Убедитесь, что тариф не архивный',
+ 'Сверьте код тарифа с официальным каталогом',
+ 'Проверьте доступность тарифа в вашем регионе',
+ 'Предложите клиенту актуальные тарифы из каталога'
+ ],
+ contact: 'Отдел продуктового маркетинга: 2222'
+ },
+ 'cant-change': {
+ title: 'Не могу изменить скидку',
+ steps: [
+ 'Активную скидку изменить нельзя',
+ 'Отмените текущую скидку (раздел "Активные скидки")',
+ 'Создайте новую скидку с нужными параметрами',
+ 'Убедитесь, что новый размер в пределах лимита',
+ 'Перерасчёт выполнится автоматически'
+ ],
+ contact: null
+ },
+ 'which-choose': {
+ title: 'Какую скидку выбрать',
+ steps: [
+ 'Определите цель: удержание, лояльность, жалоба, акция',
+ 'Проверьте свой лимит полномочий',
+ 'Для удержания — до 60% (требует согласования)',
+ 'Для лояльности — до 25%',
+ 'При жалобе — до 50% (компенсация)',
+ 'По акции — до 40% (по условиям акции)'
+ ],
+ contact: null
+ },
+ 'check-conditions': {
+ title: 'Проверить условия',
+ steps: [
+ 'Откройте карточку клиента',
+ 'Проверьте активные тарифы',
+ 'Откройте "Активные скидки"',
+ 'Убедитесь, что нет дублирующих скидок',
+ 'Проверьте срок действия текущих скидок',
+ 'Сверьте лимиты в своём профиле'
+ ],
+ contact: null
+ }
+ }
+};
diff --git a/discount-helper/index.html b/discount-helper/index.html
new file mode 100644
index 0000000..85c0726
--- /dev/null
+++ b/discount-helper/index.html
@@ -0,0 +1,355 @@
+
+
+
+
+
+ Помощник по скидкам
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Чем могу помочь?
+
+
+
+
+
+ Популярные вопросы:
+
+
+
+
+
+
+
+
+
+
📎
+
Загрузить скриншот ошибки
+
Перетащите изображение сюда или нажмите для загрузки
+
+
+
+
+
+
+
Быстрые решения
+
+
+
🔴
+
Не создается скидка
+
+
+
+
+
+
🟣
+
Какую скидку выбрать
+
+
+
🟢
+
Проверить условия
+
+
+
+
+
+
+
📚
+
Обучение
+
Пошаговые уроки по работе со скидками
+
+
+
⚠️
+
Ошибки
+
Справочник ошибок и решений
+
+
+
📖
+
Инструкции
+
Пошаговые руководства
+
+
+
🤖
+
AI-помощник
+
Ответы на любые вопросы
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Расчет скидки
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 🤖 Помощник по скидкам
+
+
+
+
+
Здравствуйте! Я ваш помощник по скидкам.
+
Задайте мне любой вопрос, например:
+
+ - Как создать скидку на тариф?
+ - Почему не проходит скидка 30%?
+ - Что означает ошибка 403?
+
+
+
+
+
+
+
+
+
+ Ответ помог?
+
+
+
+
+
+
+
+
+
+
+
+ Проверка знаний
+
+
Пройдите тест, чтобы проверить свои знания по работе со скидками.
+
+
+
+
+
+
+
+
+
+
+
Результат
+
+
+
+
+
+
+
+
+ Админ-панель
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Анализирую изображение…
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/discount-helper/style.css b/discount-helper/style.css
new file mode 100644
index 0000000..4388761
--- /dev/null
+++ b/discount-helper/style.css
@@ -0,0 +1,1183 @@
+* {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+:root {
+ --primary: #0066cc;
+ --primary-dark: #0052a3;
+ --primary-light: #e6f0ff;
+ --secondary: #64748b;
+ --success: #10b981;
+ --warning: #f59e0b;
+ --danger: #ef4444;
+ --bg: #f8fafc;
+ --card-bg: #ffffff;
+ --text: #1e293b;
+ --text-muted: #64748b;
+ --border: #e2e8f0;
+ --shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
+ --shadow-md: 0 4px 12px rgba(0, 0, 0, 0.1);
+ --shadow-lg: 0 10px 40px rgba(0, 0, 0, 0.12);
+ --radius: 12px;
+ --radius-sm: 8px;
+}
+
+body {
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
+ background: var(--bg);
+ color: var(--text);
+ line-height: 1.6;
+ min-height: 100vh;
+}
+
+.app {
+ display: flex;
+ min-height: 100vh;
+}
+
+/* Sidebar */
+.sidebar {
+ width: 280px;
+ background: var(--card-bg);
+ border-right: 1px solid var(--border);
+ padding: 24px 16px;
+ display: flex;
+ flex-direction: column;
+ position: fixed;
+ height: 100vh;
+ overflow-y: auto;
+ z-index: 100;
+ transition: transform 0.3s;
+}
+
+.sidebar-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 32px;
+ padding-bottom: 16px;
+ border-bottom: 1px solid var(--border);
+}
+
+.logo {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+}
+
+.logo-icon {
+ font-size: 28px;
+}
+
+.logo-text {
+ font-size: 16px;
+ font-weight: 600;
+ color: var(--text);
+}
+
+.close-sidebar {
+ display: none;
+}
+
+.nav {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+}
+
+.nav-item {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ padding: 12px 16px;
+ border-radius: var(--radius-sm);
+ color: var(--text-muted);
+ text-decoration: none;
+ transition: all 0.2s;
+ cursor: pointer;
+}
+
+.nav-item:hover {
+ background: var(--bg);
+ color: var(--text);
+}
+
+.nav-item.active {
+ background: var(--primary-light);
+ color: var(--primary);
+ font-weight: 500;
+}
+
+.nav-icon {
+ font-size: 20px;
+ width: 24px;
+ text-align: center;
+}
+
+.sidebar-footer {
+ margin-top: auto;
+ padding-top: 16px;
+ border-top: 1px solid var(--border);
+}
+
+/* Main Content */
+.main-content {
+ flex: 1;
+ margin-left: 280px;
+ padding: 32px;
+ min-height: 100vh;
+}
+
+.mobile-header {
+ display: none;
+ align-items: center;
+ gap: 16px;
+ padding: 16px 24px;
+ background: var(--card-bg);
+ border-bottom: 1px solid var(--border);
+ position: sticky;
+ top: 0;
+ z-index: 50;
+}
+
+.mobile-title {
+ font-size: 18px;
+ font-weight: 600;
+}
+
+/* Pages */
+.page {
+ display: none;
+ animation: fadeIn 0.3s;
+}
+
+.page.active {
+ display: block;
+}
+
+@keyframes fadeIn {
+ from { opacity: 0; transform: translateY(10px); }
+ to { opacity: 1; transform: translateY(0); }
+}
+
+.page-title {
+ font-size: 28px;
+ font-weight: 600;
+ margin-bottom: 32px;
+ color: var(--text);
+}
+
+/* Hero Section */
+.hero {
+ text-align: center;
+ padding: 40px 0;
+}
+
+.hero-title {
+ font-size: 36px;
+ font-weight: 700;
+ margin-bottom: 32px;
+ color: var(--text);
+}
+
+.search-box {
+ max-width: 600px;
+ margin: 0 auto 24px;
+ display: flex;
+ gap: 12px;
+}
+
+.search-input {
+ flex: 1;
+ padding: 14px 20px;
+ font-size: 16px;
+ border: 2px solid var(--border);
+ border-radius: var(--radius);
+ background: var(--card-bg);
+ transition: border-color 0.2s, box-shadow 0.2s;
+}
+
+.search-input:focus {
+ outline: none;
+ border-color: var(--primary);
+ box-shadow: 0 0 0 4px rgba(0, 102, 204, 0.1);
+}
+
+.search-input.large {
+ max-width: 600px;
+ margin: 0 auto;
+}
+
+.quick-questions {
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: center;
+ gap: 8px;
+ align-items: center;
+}
+
+.qq-label {
+ color: var(--text-muted);
+ font-size: 14px;
+}
+
+.qq-btn {
+ padding: 8px 16px;
+ background: var(--card-bg);
+ border: 1px solid var(--border);
+ border-radius: 20px;
+ font-size: 14px;
+ color: var(--text-muted);
+ cursor: pointer;
+ transition: all 0.2s;
+}
+
+.qq-btn:hover {
+ border-color: var(--primary);
+ color: var(--primary);
+ background: var(--primary-light);
+}
+
+/* Upload Section */
+.upload-section {
+ max-width: 600px;
+ margin: 40px auto;
+}
+
+.upload-card {
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+ border-radius: var(--radius);
+ padding: 40px;
+ text-align: center;
+ color: white;
+ cursor: pointer;
+ transition: transform 0.2s, box-shadow 0.2s;
+ border: 2px dashed rgba(255, 255, 255, 0.3);
+}
+
+.upload-card:hover {
+ transform: translateY(-4px);
+ box-shadow: var(--shadow-lg);
+}
+
+.upload-card.dragover {
+ border-color: white;
+ background: rgba(255, 255, 255, 0.1);
+}
+
+.upload-icon {
+ font-size: 48px;
+ margin-bottom: 16px;
+}
+
+.upload-card h3 {
+ font-size: 20px;
+ margin-bottom: 8px;
+}
+
+.upload-card p {
+ opacity: 0.9;
+ margin-bottom: 20px;
+ font-size: 14px;
+}
+
+/* Quick Solutions */
+.quick-solutions {
+ margin-top: 60px;
+}
+
+.section-title {
+ font-size: 22px;
+ font-weight: 600;
+ margin-bottom: 24px;
+ text-align: center;
+}
+
+.solutions-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
+ gap: 16px;
+ max-width: 900px;
+ margin: 0 auto;
+}
+
+.solution-card {
+ background: var(--card-bg);
+ border-radius: var(--radius);
+ padding: 24px 16px;
+ text-align: center;
+ cursor: pointer;
+ transition: all 0.2s;
+ box-shadow: var(--shadow);
+}
+
+.solution-card:hover {
+ transform: translateY(-4px);
+ box-shadow: var(--shadow-md);
+}
+
+.solution-icon {
+ font-size: 32px;
+ margin-bottom: 12px;
+}
+
+.solution-card h3 {
+ font-size: 14px;
+ font-weight: 500;
+ color: var(--text);
+}
+
+/* Features Grid */
+.features-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
+ gap: 24px;
+ margin-top: 60px;
+ max-width: 1000px;
+ margin-left: auto;
+ margin-right: auto;
+}
+
+.feature-card {
+ background: var(--card-bg);
+ border-radius: var(--radius);
+ padding: 32px 24px;
+ text-align: center;
+ cursor: pointer;
+ transition: all 0.2s;
+ box-shadow: var(--shadow);
+}
+
+.feature-card:hover {
+ transform: translateY(-4px);
+ box-shadow: var(--shadow-md);
+}
+
+.feature-icon {
+ font-size: 40px;
+ margin-bottom: 16px;
+}
+
+.feature-card h3 {
+ font-size: 18px;
+ margin-bottom: 8px;
+}
+
+.feature-card p {
+ color: var(--text-muted);
+ font-size: 14px;
+}
+
+/* Buttons */
+.btn {
+ padding: 12px 24px;
+ font-size: 15px;
+ font-weight: 500;
+ border: none;
+ border-radius: var(--radius-sm);
+ cursor: pointer;
+ transition: all 0.2s;
+ display: inline-flex;
+ align-items: center;
+ gap: 8px;
+ justify-content: center;
+}
+
+.btn:active {
+ transform: scale(0.98);
+}
+
+.btn-primary {
+ background: var(--primary);
+ color: white;
+}
+
+.btn-primary:hover {
+ background: var(--primary-dark);
+}
+
+.btn-secondary {
+ background: var(--card-bg);
+ color: var(--text);
+ border: 1px solid var(--border);
+}
+
+.btn-secondary:hover {
+ background: var(--bg);
+}
+
+.btn-large {
+ padding: 14px 32px;
+ font-size: 16px;
+ width: 100%;
+}
+
+.btn-icon {
+ width: 36px;
+ height: 36px;
+ border-radius: 50%;
+ border: none;
+ background: transparent;
+ cursor: pointer;
+ font-size: 20px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ transition: background 0.2s;
+ color: var(--text-muted);
+}
+
+.btn-icon:hover {
+ background: var(--bg);
+}
+
+.btn-close {
+ width: 32px;
+ height: 32px;
+ border-radius: 6px;
+ border: none;
+ background: transparent;
+ cursor: pointer;
+ font-size: 24px;
+ color: var(--text-muted);
+ transition: background 0.2s;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.btn-close:hover {
+ background: var(--bg);
+}
+
+/* Cards */
+.card {
+ background: var(--card-bg);
+ border-radius: var(--radius);
+ padding: 24px;
+ box-shadow: var(--shadow);
+ margin-bottom: 24px;
+}
+
+/* Search Section */
+.search-section {
+ max-width: 700px;
+ margin: 0 auto 40px;
+ text-align: center;
+}
+
+.search-section .search-input {
+ width: 100%;
+ margin-bottom: 16px;
+}
+
+/* Lessons List */
+.lessons-list,
+.instructions-list,
+.faq-list {
+ max-width: 800px;
+ margin: 0 auto;
+ display: flex;
+ flex-direction: column;
+ gap: 16px;
+}
+
+.lesson-card,
+.instruction-card,
+.faq-card {
+ background: var(--card-bg);
+ border-radius: var(--radius);
+ padding: 24px;
+ box-shadow: var(--shadow);
+ cursor: pointer;
+ transition: all 0.2s;
+}
+
+.lesson-card:hover,
+.instruction-card:hover,
+.faq-card:hover {
+ box-shadow: var(--shadow-md);
+ transform: translateY(-2px);
+}
+
+.lesson-header,
+.instruction-header {
+ display: flex;
+ align-items: center;
+ gap: 16px;
+ margin-bottom: 12px;
+}
+
+.lesson-icon,
+.instruction-icon {
+ font-size: 32px;
+ width: 56px;
+ height: 56px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background: var(--primary-light);
+ border-radius: var(--radius-sm);
+}
+
+.lesson-title,
+.instruction-title {
+ font-size: 18px;
+ font-weight: 600;
+}
+
+.lesson-desc,
+.instruction-desc {
+ color: var(--text-muted);
+ font-size: 14px;
+}
+
+.faq-card {
+ padding: 20px 24px;
+}
+
+.faq-question {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ cursor: pointer;
+ font-weight: 500;
+}
+
+.faq-icon {
+ font-size: 20px;
+ transition: transform 0.3s;
+}
+
+.faq-card.active .faq-icon {
+ transform: rotate(45deg);
+}
+
+.faq-answer {
+ max-height: 0;
+ overflow: hidden;
+ transition: max-height 0.3s;
+ padding-top: 0;
+ color: var(--text-muted);
+ line-height: 1.7;
+}
+
+.faq-card.active .faq-answer {
+ max-height: 500px;
+ padding-top: 16px;
+}
+
+/* Errors Grid */
+.errors-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
+ gap: 20px;
+ max-width: 1200px;
+ margin: 0 auto;
+}
+
+.error-card {
+ background: var(--card-bg);
+ border-radius: var(--radius);
+ padding: 20px;
+ box-shadow: var(--shadow);
+ cursor: pointer;
+ transition: all 0.2s;
+ border-left: 4px solid var(--danger);
+}
+
+.error-card:hover {
+ box-shadow: var(--shadow-md);
+ transform: translateY(-2px);
+}
+
+.error-code {
+ font-size: 14px;
+ color: var(--danger);
+ font-weight: 600;
+ margin-bottom: 8px;
+}
+
+.error-title {
+ font-size: 16px;
+ font-weight: 600;
+ margin-bottom: 8px;
+}
+
+.error-preview {
+ color: var(--text-muted);
+ font-size: 14px;
+ line-height: 1.5;
+}
+
+/* Calculator */
+.calculator-card {
+ max-width: 500px;
+ margin: 0 auto;
+ background: var(--card-bg);
+ border-radius: var(--radius);
+ padding: 32px;
+ box-shadow: var(--shadow-md);
+}
+
+.form-group {
+ margin-bottom: 20px;
+}
+
+.form-group label {
+ display: block;
+ font-size: 14px;
+ font-weight: 500;
+ color: var(--text-muted);
+ margin-bottom: 8px;
+}
+
+.input-select {
+ width: 100%;
+ padding: 12px 16px;
+ font-size: 15px;
+ border: 1px solid var(--border);
+ border-radius: var(--radius-sm);
+ background: var(--card-bg);
+ cursor: pointer;
+}
+
+.input-select:focus {
+ outline: none;
+ border-color: var(--primary);
+}
+
+.calc-result {
+ margin-top: 24px;
+ padding: 20px;
+ background: var(--primary-light);
+ border-radius: var(--radius-sm);
+ border-left: 4px solid var(--primary);
+}
+
+.calc-result h3 {
+ font-size: 16px;
+ margin-bottom: 12px;
+ color: var(--primary);
+}
+
+.result-row {
+ display: flex;
+ justify-content: space-between;
+ padding: 8px 0;
+ border-bottom: 1px solid rgba(0, 102, 204, 0.1);
+}
+
+.result-row:last-child {
+ border-bottom: none;
+ font-weight: 600;
+ font-size: 18px;
+ color: var(--primary);
+}
+
+/* Assistant */
+.assistant-container {
+ max-width: 800px;
+ margin: 0 auto;
+ background: var(--card-bg);
+ border-radius: var(--radius);
+ box-shadow: var(--shadow-md);
+ display: flex;
+ flex-direction: column;
+ height: calc(100vh - 200px);
+}
+
+.chat-messages {
+ flex: 1;
+ overflow-y: auto;
+ padding: 24px;
+ display: flex;
+ flex-direction: column;
+ gap: 16px;
+}
+
+.message {
+ display: flex;
+ gap: 12px;
+ max-width: 85%;
+}
+
+.message.user {
+ align-self: flex-end;
+ flex-direction: row-reverse;
+}
+
+.message-avatar {
+ width: 36px;
+ height: 36px;
+ border-radius: 50%;
+ background: var(--primary-light);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 18px;
+ flex-shrink: 0;
+}
+
+.message.user .message-avatar {
+ background: var(--bg);
+}
+
+.message-content {
+ background: var(--bg);
+ padding: 14px 18px;
+ border-radius: var(--radius);
+ line-height: 1.6;
+}
+
+.message.user .message-content {
+ background: var(--primary);
+ color: white;
+}
+
+.message-content p {
+ margin-bottom: 12px;
+}
+
+.message-content p:last-child {
+ margin-bottom: 0;
+}
+
+.message-content ul {
+ margin: 12px 0;
+ padding-left: 20px;
+}
+
+.message-content li {
+ margin-bottom: 6px;
+}
+
+.chat-input {
+ display: flex;
+ gap: 12px;
+ padding: 16px 24px;
+ border-top: 1px solid var(--border);
+}
+
+.chat-input .search-input {
+ flex: 1;
+}
+
+.feedback-block {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ padding: 16px 24px;
+ background: var(--bg);
+ border-top: 1px solid var(--border);
+ justify-content: center;
+}
+
+.feedback-btn {
+ width: 40px;
+ height: 40px;
+ font-size: 22px;
+ border-radius: 50%;
+ background: var(--card-bg);
+ border: 1px solid var(--border);
+}
+
+.feedback-btn:hover {
+ background: var(--primary-light);
+ border-color: var(--primary);
+}
+
+/* Tests */
+.tests-intro {
+ max-width: 600px;
+ margin: 0 auto;
+ text-align: center;
+ padding: 60px 24px;
+}
+
+.tests-intro p {
+ font-size: 18px;
+ color: var(--text-muted);
+ margin-bottom: 32px;
+}
+
+.test-container {
+ max-width: 700px;
+ margin: 0 auto;
+ background: var(--card-bg);
+ border-radius: var(--radius);
+ padding: 32px;
+ box-shadow: var(--shadow-md);
+}
+
+.test-progress {
+ display: flex;
+ align-items: center;
+ gap: 16px;
+ margin-bottom: 24px;
+}
+
+.progress-bar {
+ flex: 1;
+ height: 8px;
+ background: var(--bg);
+ border-radius: 4px;
+ overflow: hidden;
+}
+
+.progress-fill {
+ height: 100%;
+ background: var(--primary);
+ transition: width 0.3s;
+ width: 0%;
+}
+
+.test-counter {
+ font-size: 14px;
+ color: var(--text-muted);
+ min-width: 50px;
+ text-align: right;
+}
+
+.test-question {
+ font-size: 18px;
+ font-weight: 600;
+ margin-bottom: 24px;
+ line-height: 1.5;
+}
+
+.test-answers {
+ display: flex;
+ flex-direction: column;
+ gap: 12px;
+}
+
+.test-answer {
+ padding: 16px 20px;
+ background: var(--bg);
+ border: 2px solid var(--border);
+ border-radius: var(--radius-sm);
+ cursor: pointer;
+ transition: all 0.2s;
+ font-size: 15px;
+}
+
+.test-answer:hover {
+ border-color: var(--primary);
+ background: var(--primary-light);
+}
+
+.test-answer.selected {
+ border-color: var(--primary);
+ background: var(--primary-light);
+}
+
+.test-answer.correct {
+ border-color: var(--success);
+ background: #dcfce7;
+}
+
+.test-answer.incorrect {
+ border-color: var(--danger);
+ background: #fee2e2;
+}
+
+.test-feedback {
+ margin-top: 20px;
+ padding: 16px;
+ border-radius: var(--radius-sm);
+ background: var(--success);
+ color: white;
+ display: none;
+}
+
+.test-feedback.show {
+ display: block;
+}
+
+.test-feedback.incorrect {
+ background: var(--danger);
+}
+
+.test-result {
+ max-width: 500px;
+ margin: 0 auto;
+ text-align: center;
+ padding: 60px 24px;
+ background: var(--card-bg);
+ border-radius: var(--radius);
+ box-shadow: var(--shadow-md);
+}
+
+.result-score {
+ font-size: 64px;
+ font-weight: 700;
+ color: var(--primary);
+ margin: 24px 0;
+}
+
+.result-message {
+ font-size: 18px;
+ color: var(--text-muted);
+ margin-bottom: 32px;
+}
+
+/* Admin */
+.admin-tabs {
+ display: flex;
+ gap: 8px;
+ margin-bottom: 24px;
+ flex-wrap: wrap;
+}
+
+.admin-tab {
+ padding: 10px 20px;
+ background: var(--card-bg);
+ border: 1px solid var(--border);
+ border-radius: var(--radius-sm);
+ cursor: pointer;
+ font-weight: 500;
+ transition: all 0.2s;
+}
+
+.admin-tab:hover {
+ background: var(--bg);
+}
+
+.admin-tab.active {
+ background: var(--primary);
+ color: white;
+ border-color: var(--primary);
+}
+
+.admin-content {
+ background: var(--card-bg);
+ border-radius: var(--radius);
+ padding: 24px;
+ box-shadow: var(--shadow);
+}
+
+.admin-panel {
+ display: none;
+}
+
+.admin-panel.active {
+ display: block;
+}
+
+.admin-item {
+ padding: 16px;
+ border: 1px solid var(--border);
+ border-radius: var(--radius-sm);
+ margin-bottom: 12px;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.admin-item-actions {
+ display: flex;
+ gap: 8px;
+}
+
+.admin-btn {
+ padding: 6px 12px;
+ font-size: 13px;
+ border: none;
+ border-radius: 6px;
+ cursor: pointer;
+}
+
+.admin-btn-edit {
+ background: var(--primary-light);
+ color: var(--primary);
+}
+
+.admin-btn-delete {
+ background: #fee2e2;
+ color: var(--danger);
+}
+
+.admin-add-btn {
+ margin-bottom: 20px;
+}
+
+/* Modal */
+.modal-overlay {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: rgba(0, 0, 0, 0.5);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ z-index: 1000;
+ padding: 24px;
+}
+
+.modal {
+ background: var(--card-bg);
+ border-radius: var(--radius);
+ width: 100%;
+ max-width: 600px;
+ max-height: 90vh;
+ overflow-y: auto;
+ box-shadow: var(--shadow-lg);
+}
+
+.modal-large {
+ max-width: 800px;
+}
+
+.modal-xlarge {
+ max-width: 900px;
+}
+
+.modal-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 20px 24px;
+ border-bottom: 1px solid var(--border);
+}
+
+.modal-header h2 {
+ font-size: 20px;
+}
+
+.modal-body {
+ padding: 24px;
+}
+
+.upload-loading {
+ text-align: center;
+ padding: 40px;
+}
+
+.spinner {
+ width: 48px;
+ height: 48px;
+ border: 4px solid var(--border);
+ border-top-color: var(--primary);
+ border-radius: 50%;
+ animation: spin 1s linear infinite;
+ margin: 0 auto 24px;
+}
+
+@keyframes spin {
+ to { transform: rotate(360deg); }
+}
+
+/* Error Modal Content */
+.error-detail-section {
+ margin-bottom: 24px;
+}
+
+.error-detail-section h3 {
+ font-size: 16px;
+ color: var(--text);
+ margin-bottom: 12px;
+ display: flex;
+ align-items: center;
+ gap: 8px;
+}
+
+.error-detail-section h3::before {
+ content: '';
+ width: 4px;
+ height: 20px;
+ background: var(--primary);
+ border-radius: 2px;
+}
+
+.error-detail-content {
+ background: var(--bg);
+ padding: 16px;
+ border-radius: var(--radius-sm);
+ line-height: 1.7;
+}
+
+.error-steps {
+ list-style: none;
+ counter-reset: step-counter;
+}
+
+.error-steps li {
+ position: relative;
+ padding-left: 36px;
+ margin-bottom: 12px;
+ counter-increment: step-counter;
+}
+
+.error-steps li::before {
+ content: counter(step-counter);
+ position: absolute;
+ left: 0;
+ top: 2px;
+ width: 24px;
+ height: 24px;
+ background: var(--primary);
+ color: white;
+ border-radius: 50%;
+ font-size: 12px;
+ font-weight: 600;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+/* Responsive */
+@media (max-width: 1024px) {
+ .main-content {
+ margin-left: 0;
+ padding: 24px 16px;
+ }
+
+ .sidebar {
+ transform: translateX(-100%);
+ }
+
+ .sidebar.open {
+ transform: translateX(0);
+ }
+
+ .close-sidebar {
+ display: flex;
+ }
+
+ .mobile-header {
+ display: flex;
+ }
+}
+
+@media (max-width: 600px) {
+ .hero-title {
+ font-size: 28px;
+ }
+
+ .search-box {
+ flex-direction: column;
+ }
+
+ .solutions-grid {
+ grid-template-columns: repeat(2, 1fr);
+ }
+
+ .features-grid {
+ grid-template-columns: 1fr;
+ }
+
+ .errors-grid {
+ grid-template-columns: 1fr;
+ }
+
+ .modal {
+ margin: 16px;
+ max-height: calc(100vh - 32px);
+ }
+}