/* global $, window, document, showAlert */ $(document).ready(function() { const publicLink = window.location.pathname.split('/').pop(); let questionnaireData = null; // Элементы DOM const loadingEl = $('#loading'); const containerEl = $('#questionnaire-container'); const titleEl = $('#questionnaire-title'); const descriptionEl = $('#questionnaire-description'); const questionsContainerEl = $('#questions-container'); const formEl = $('#poll-form'); const resultsContainerEl = $('#results-container'); const pollResultsContainerEl = $('#poll-results-container'); // Элементы навигации для пошаговых опросов const navigationControlsEl = $('#navigation-controls'); const prevButtonEl = $('#prev-question'); const nextButtonEl = $('#next-question'); const questionCounterEl = $('#question-counter'); const submitButtonEl = $('#submit-button'); // Для пошаговых опросов let currentQuestionIndex = 0; // Проверка доступности localStorage const isLocalStorageAvailable = () => { try { const testKey = 'test'; window.localStorage.setItem(testKey, testKey); window.localStorage.removeItem(testKey); return true; } catch (e) { return false; } }; // Ключ для localStorage const getLocalStorageKey = () => `questionnaire_${publicLink}_completed`; // Проверка на повторное прохождение опроса const checkIfAlreadyCompleted = () => { if (!isLocalStorageAvailable()) return false; return window.localStorage.getItem(getLocalStorageKey()) === 'true'; }; // Сохранение информации о прохождении опроса const markAsCompleted = () => { if (!isLocalStorageAvailable()) return; window.localStorage.setItem(getLocalStorageKey(), 'true'); }; // Получаем базовый путь API const getApiPath = () => { const pathParts = window.location.pathname.split('/'); // Убираем последние две части пути (poll/:publicLink) pathParts.pop(); pathParts.pop(); return pathParts.join('/') + '/api'; }; // Загрузка данных опроса const loadQuestionnaire = () => { $.ajax({ url: `${getApiPath()}/questionnaires/public/${publicLink}`, method: 'GET', success: function(result) { if (result.success) { questionnaireData = result.data; // Проверяем, проходил ли пользователь уже этот опрос if (checkIfAlreadyCompleted()) { showAlreadyCompletedMessage(); } else { showWelcomeAnimation(); } } else { loadingEl.text(`Ошибка: ${result.error}`); } }, error: function(error) { console.error('Error loading questionnaire:', error); loadingEl.text('Не удалось загрузить опрос. Пожалуйста, попробуйте позже.'); } }); }; // Показываем анимацию приветствия const showWelcomeAnimation = () => { // Скрываем индикатор загрузки loadingEl.hide(); // Создаем элемент приветствия const $welcomeEl = $('<div>', { id: 'welcome-animation', class: 'welcome-animation', css: { opacity: 0, transform: 'translateY(20px)' } }); const $welcomeIcon = $('<div>', { class: 'welcome-icon', html: '<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" fill="currentColor" viewBox="0 0 16 16"><path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z"/><path d="M4.285 9.567a.5.5 0 0 1 .683.183A3.498 3.498 0 0 0 8 11.5a3.498 3.498 0 0 0 3.032-1.75.5.5 0 1 1 .866.5A4.498 4.498 0 0 1 8 12.5a4.498 4.498 0 0 1-3.898-2.25.5.5 0 0 1 .183-.683zM7 6.5C7 7.328 6.552 8 6 8s-1-.672-1-1.5S5.448 5 6 5s1 .672 1 1.5zm4 0c0 .828-.448 1.5-1 1.5s-1-.672-1-1.5S9.448 5 10 5s1 .672 1 1.5z"/></svg>' }); const $welcomeTitle = $('<h2>', { text: `Добро пожаловать в опрос "${questionnaireData.title}"`, class: 'welcome-title' }); const $welcomeDescription = $('<p>', { text: questionnaireData.description || 'Пожалуйста, ответьте на следующие вопросы:', class: 'welcome-description' }); const $startButton = $('<button>', { type: 'button', text: 'Начать опрос', class: 'btn btn-primary welcome-start-btn' }); $startButton.on('click', function() { $welcomeEl.css({ opacity: 0, transform: 'translateY(-20px)' }); setTimeout(() => { $welcomeEl.remove(); renderQuestionnaire(); }, 500); }); $welcomeEl.append($welcomeIcon, $welcomeTitle, $welcomeDescription, $startButton); // Добавляем приветствие перед контейнером опроса containerEl.before($welcomeEl); // Анимируем появление setTimeout(() => { $welcomeEl.css({ opacity: 1, transform: 'translateY(0)' }); }, 100); }; // Показываем сообщение о том, что опрос уже пройден const showAlreadyCompletedMessage = () => { loadingEl.hide(); const $completedEl = $('<div>', { class: 'already-completed', css: { opacity: 0, transform: 'translateY(20px)' } }); const $completedIcon = $('<div>', { class: 'completed-icon', html: '<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" fill="currentColor" viewBox="0 0 16 16"><path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z"/><path d="M10.97 4.97a.235.235 0 0 0-.02.022L7.477 9.417 5.384 7.323a.75.75 0 0 0-1.06 1.06L6.97 11.03a.75.75 0 0 0 1.079-.02l3.992-4.99a.75.75 0 0 0-1.071-1.05z"/></svg>' }); const $completedTitle = $('<h2>', { text: 'Вы уже прошли этот опрос', class: 'completed-title' }); const $completedDescription = $('<p>', { text: 'Спасибо за ваше участие. Вы уже отправили свои ответы на этот опрос.', class: 'completed-description' }); const $viewResultsButton = $('<button>', { type: 'button', text: 'Посмотреть результаты', class: 'btn btn-primary view-results-btn' }); const $startAgainButton = $('<button>', { type: 'button', text: 'Пройти снова', class: 'btn btn-secondary start-again-btn' }); $viewResultsButton.on('click', function() { $completedEl.remove(); containerEl.show(); formEl.hide(); resultsContainerEl.show(); loadPollResults(); }); $startAgainButton.on('click', function() { if (isLocalStorageAvailable()) { window.localStorage.removeItem(getLocalStorageKey()); } $completedEl.remove(); showWelcomeAnimation(); }); $completedEl.append($completedIcon, $completedTitle, $completedDescription, $viewResultsButton, $startAgainButton); containerEl.before($completedEl); setTimeout(() => { $completedEl.css({ opacity: 1, transform: 'translateY(0)' }); }, 100); }; // Отображение опроса const renderQuestionnaire = () => { titleEl.text(questionnaireData.title); descriptionEl.text(questionnaireData.description || ''); // Удаляем кнопку "Отправить ответы", так как есть кнопка "Отправить" в навигации formEl.find('.form-actions').remove(); // Добавляем навигацию для пошагового опроса formEl.append(` <div class="step-navigation"> <button type="button" id="prev-question" class="btn btn-secondary nav-btn"> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16"> <path fill-rule="evenodd" d="M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z"/> </svg> Назад </button> <span id="question-counter">Вопрос 1 из ${questionnaireData.questions.length}</span> <button type="button" id="next-question" class="btn nav-btn"> Далее <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" viewBox="0 0 16 16"> <path fill-rule="evenodd" d="M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z"/> </svg> </button> </div> `); // Обработчики для навигации $('#prev-question').on('click', prevQuestion); $('#next-question').on('click', nextQuestion); // Показываем только первый вопрос currentQuestionIndex = 0; renderCurrentQuestion(); // Показываем контейнер с данными containerEl.show(); }; // Отображение текущего вопроса в пошаговом режиме const renderCurrentQuestion = () => { // Очищаем контейнер вопросов questionsContainerEl.empty(); // Получаем текущий вопрос const currentQuestion = questionnaireData.questions[currentQuestionIndex]; // Отображаем вопрос renderQuestion(questionsContainerEl, currentQuestion, currentQuestionIndex); // Добавляем классы для анимации const $currentQuestionEl = questionsContainerEl.find(`.question-item[data-index="${currentQuestionIndex}"]`); $currentQuestionEl.addClass('enter'); // Запускаем анимацию появления после небольшой задержки setTimeout(() => { $currentQuestionEl.removeClass('enter').addClass('active'); }, 50); // Обновляем счетчик вопросов questionCounterEl.addClass('update'); questionCounterEl.text(`Вопрос ${currentQuestionIndex + 1} из ${questionnaireData.questions.length}`); // Снимаем класс анимации счетчика setTimeout(() => { questionCounterEl.removeClass('update'); }, 500); // Управляем состоянием кнопок навигации prevButtonEl.prop('disabled', currentQuestionIndex === 0); nextButtonEl.text( currentQuestionIndex === questionnaireData.questions.length - 1 ? 'Отправить' : 'Далее' ); }; // Переход к предыдущему вопросу const prevQuestion = function() { if (currentQuestionIndex > 0) { currentQuestionIndex--; renderCurrentQuestion(); } }; // Переход к следующему вопросу или завершение опроса const nextQuestion = function() { const currentQuestion = questionnaireData.questions[currentQuestionIndex]; // Проверяем, что на текущий вопрос есть ответ, если он обязательный if (currentQuestion.required && !checkQuestionAnswer(currentQuestion, currentQuestionIndex)) { // Добавляем класс ошибки к текущему вопросу const $currentQuestionEl = questionsContainerEl.find(`.question-item[data-index="${currentQuestionIndex}"]`); $currentQuestionEl.addClass('error shake'); // Удаляем класс ошибки после окончания анимации setTimeout(() => { $currentQuestionEl.removeClass('shake'); }, 500); return; } // Если есть еще вопросы, переходим к следующему if (currentQuestionIndex < questionnaireData.questions.length - 1) { // Анимируем текущий вопрос перед переходом к следующему questionsContainerEl.find(`.question-item[data-index="${currentQuestionIndex}"]`).addClass('exit'); // Увеличиваем индекс текущего вопроса currentQuestionIndex++; // Рендерим новый вопрос после небольшой задержки setTimeout(() => { renderCurrentQuestion(); }, 200); } else { // Анимируем контейнер перед отправкой формы questionsContainerEl.find('.question-item').addClass('exit'); // Последний вопрос, отправляем форму setTimeout(() => { // Отключаем атрибут required у невидимых полей перед отправкой $('input[required]:not(:visible), textarea[required]:not(:visible)').prop('required', false); submitForm(); }, 300); } }; // Проверка наличия ответа на вопрос const checkQuestionAnswer = (question, questionIndex) => { switch (question.type) { case 'single_choice': return $(`.question-item[data-index="${questionIndex}"] input[name="question_${questionIndex}"]:checked`).length > 0; case 'multiple_choice': return $(`.question-item[data-index="${questionIndex}"] input[type="checkbox"]:checked:not(.multiple-choice-validation)`).length > 0; case 'text': return $(`.question-item[data-index="${questionIndex}"] textarea`).val().trim() !== ''; case 'scale': return $(`.question-item[data-index="${questionIndex}"] input[name="question_${questionIndex}"]:checked`).length > 0; case 'tag_cloud': return $(`.question-item[data-index="${questionIndex}"] .tag-item.selected`).length > 0; default: return true; } }; // Отображение всех вопросов (стандартный режим) const renderQuestions = () => { questionsContainerEl.empty(); $.each(questionnaireData.questions, function(questionIndex, question) { renderQuestion(questionsContainerEl, question, questionIndex); }); }; // Отображение одного вопроса const renderQuestion = (container, question, questionIndex) => { const $questionEl = $('<div>', { class: 'question-item', 'data-index': questionIndex }); const $questionTitle = $('<h3>', { text: question.text, class: 'question-title' }); if (question.required) { const $requiredMark = $('<span>', { class: 'required-mark', text: ' *' }); $questionTitle.append($requiredMark); } $questionEl.append($questionTitle); // Отображение вариантов ответа в зависимости от типа вопроса switch (question.type) { case 'single_choice': renderSingleChoice($questionEl, question, questionIndex); break; case 'multiple_choice': renderMultipleChoice($questionEl, question, questionIndex); break; case 'text': renderTextAnswer($questionEl, question, questionIndex); break; case 'scale': renderScale($questionEl, question, questionIndex); break; case 'rating': // Обратная совместимость для рейтинга renderScale($questionEl, { ...question, scaleMax: 5 }, questionIndex); break; case 'tag_cloud': renderTagCloud($questionEl, question, questionIndex); break; } container.append($questionEl); }; // Отображение вопроса с одиночным выбором const renderSingleChoice = ($container, question, questionIndex) => { const $optionsContainer = $('<div>', { class: 'radio-options-container' }); $.each(question.options, function(optionIndex, option) { const $optionContainer = $('<div>', { class: 'radio-option' }); const $optionInput = $('<input>', { type: 'radio', id: `question_${questionIndex}_option_${optionIndex}`, name: `question_${questionIndex}`, value: optionIndex, required: question.required }); const $optionLabel = $('<label>', { for: `question_${questionIndex}_option_${optionIndex}`, text: option.text }); $optionContainer.append($optionInput, $optionLabel); $optionsContainer.append($optionContainer); }); $container.append($optionsContainer); }; // Отображение вопроса с множественным выбором const renderMultipleChoice = ($container, question, questionIndex) => { const $optionsContainer = $('<div>', { class: 'checkbox-options-container' }); // Скрытое поле для проверки заполнения обязательного вопроса с чекбоксами if (question.required) { const $validationCheckbox = $('<input>', { type: 'checkbox', class: 'multiple-choice-validation', required: true, name: `question_${questionIndex}_validation`, style: 'display: none;', id: `question_${questionIndex}_validation` }); $optionsContainer.append($validationCheckbox); } $.each(question.options, function(optionIndex, option) { const $optionContainer = $('<div>', { class: 'checkbox-option' }); const $optionInput = $('<input>', { type: 'checkbox', id: `question_${questionIndex}_option_${optionIndex}`, name: `question_${questionIndex}_option_${optionIndex}`, value: 'true' }); const $optionLabel = $('<label>', { for: `question_${questionIndex}_option_${optionIndex}`, text: option.text }); // Обработчик изменения значения чекбокса if (question.required) { $optionInput.on('change', function() { const anyChecked = $optionsContainer.find('input[type="checkbox"]:checked:not(.multiple-choice-validation)').length > 0; $(`#question_${questionIndex}_validation`).prop('checked', anyChecked); }); } $optionContainer.append($optionInput, $optionLabel); $optionsContainer.append($optionContainer); }); $container.append($optionsContainer); }; // Отображение текстового вопроса const renderTextAnswer = ($container, question, questionIndex) => { const $textareaContainer = $('<div>', { class: 'textarea-container' }); const $textarea = $('<textarea>', { id: `question_${questionIndex}_text`, name: `question_${questionIndex}_text`, rows: 4, placeholder: 'Введите ваш ответ здесь...', required: question.required }); $textareaContainer.append($textarea); $container.append($textareaContainer); }; // Отображение вопроса с шкалой оценки const renderScale = ($container, question, questionIndex) => { const minValue = question.scaleMin || 0; const maxValue = question.scaleMax || 10; const minLabel = question.scaleMinLabel || 'Минимум'; const maxLabel = question.scaleMaxLabel || 'Максимум'; const $scaleContainer = $('<div>', { class: 'scale-container' }); const $scaleLabels = $('<div>', { class: 'scale-labels' }); $scaleLabels.append( $('<span>', { class: 'scale-label-min', text: minLabel }), $('<span>', { class: 'scale-label-max', text: maxLabel }) ); const $scaleValues = $('<div>', { class: 'scale-values' }); // Создаем кнопки для шкалы от min до max for (let i = minValue; i <= maxValue; i++) { const $scaleItem = $('<div>', { class: 'scale-item' }); const $scaleInput = $('<input>', { type: 'radio', id: `question_${questionIndex}_scale_${i}`, name: `question_${questionIndex}`, value: i, required: question.required }); const $scaleLabel = $('<label>', { for: `question_${questionIndex}_scale_${i}`, text: i }); $scaleItem.append($scaleInput, $scaleLabel); $scaleValues.append($scaleItem); } $scaleContainer.append($scaleLabels, $scaleValues); $container.append($scaleContainer); }; // Отображение вопроса с облаком тегов const renderTagCloud = ($container, question, questionIndex) => { const $tagCloudContainer = $('<div>', { class: 'tag-cloud-container' }); if (question.options && question.options.length > 0) { // Предопределенное облако тегов $.each(question.options, function(optionIndex, option) { if (!option || !option.text) return; // Пропускаем пустые или невалидные опции const $tagItem = $('<div>', { class: 'tag-item', text: option.text, 'data-index': optionIndex, click: function() { $(this).toggleClass('selected'); updateTagValidation(); } }); $tagCloudContainer.append($tagItem); }); } else { // Пользовательское облако тегов const $tagInputContainer = $('<div>', { class: 'tag-input-container' }); const $tagInput = $('<input>', { type: 'text', placeholder: 'Введите теги, разделенные пробелом, и нажмите Enter', class: 'tag-input' }); const $tagItems = $('<div>', { class: 'tag-items' }); // Обработчик ввода тегов $tagInput.on('keypress', function(e) { if (e.which === 13) { // Enter e.preventDefault(); const inputText = $(this).val().trim(); if (inputText) { // Разбиваем ввод на отдельные теги const tags = inputText.split(/\s+/); // Добавляем каждый тег как отдельный элемент tags.forEach(tagText => { if (tagText) { const $tagItem = $('<div>', { class: 'tag-item selected', text: tagText }); const $tagRemove = $('<span>', { class: 'tag-remove', html: '×', click: function(e) { e.stopPropagation(); $(this).parent().remove(); updateTagValidation(); } }); $tagItem.append($tagRemove); $tagItems.append($tagItem); } }); $(this).val(''); updateTagValidation(); } } }); $tagInputContainer.append($tagInput, $tagItems); $tagCloudContainer.append($tagInputContainer); } // Скрытое поле для валидации if (question.required) { const $validationInput = $('<input>', { type: 'text', required: true, style: 'display: none;', id: `question_${questionIndex}_tag_validation` }); $tagCloudContainer.append($validationInput); } $container.append($tagCloudContainer); // Функция для обновления валидации тегов function updateTagValidation() { if (question.required) { const hasSelectedTags = $tagCloudContainer.find('.tag-item.selected').length > 0; $(`#question_${questionIndex}_tag_validation`).val(hasSelectedTags ? 'valid' : ''); } } }; // Показываем анимацию благодарности const showThankYouAnimation = () => { // Скрываем форму formEl.hide(); // Создаем анимацию благодарности const $thankYouEl = $('<div>', { class: 'thank-you-animation', css: { opacity: 0, transform: 'scale(0.9)' } }); const $thankYouIcon = $('<div>', { class: 'thank-you-icon', html: '<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" fill="currentColor" viewBox="0 0 16 16"><path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z"/><path d="M10.97 4.97a.235.235 0 0 0-.02.022L7.477 9.417 5.384 7.323a.75.75 0 0 0-1.06 1.06L6.97 11.03a.75.75 0 0 0 1.079-.02l3.992-4.99a.75.75 0 0 0-1.071-1.05z"/></svg>' }); const $thankYouTitle = $('<h2>', { text: 'Спасибо за участие!', class: 'thank-you-title' }); const $thankYouDescription = $('<p>', { text: 'Ваши ответы были успешно записаны. Мы ценим ваше мнение!', class: 'thank-you-description' }); $thankYouEl.append($thankYouIcon, $thankYouTitle, $thankYouDescription); // Добавляем анимацию перед результатами resultsContainerEl.prepend($thankYouEl); // Показываем контейнер с результатами resultsContainerEl.show(); // Анимируем появление setTimeout(() => { $thankYouEl.css({ opacity: 1, transform: 'scale(1)' }); // Загружаем результаты через некоторое время setTimeout(() => { loadPollResults(); }, 1000); }, 100); }; // Отправка формы const submitForm = function() { // Отключаем атрибут required у невидимых полей перед отправкой $('input[required]:not(:visible), textarea[required]:not(:visible)').prop('required', false); // Собираем ответы const answers = []; $.each(questionnaireData.questions, function(questionIndex, question) { const $questionEl = $(`.question-item[data-index="${questionIndex}"]`); let optionIndices; let selectedOption; let selectedOptions; let selectedTags; let textAnswer; let scaleValue; switch (question.type) { case 'single_choice': selectedOption = $questionEl.find(`input[name="question_${questionIndex}"]:checked`); if (selectedOption.length) { answers.push({ questionIndex, optionIndices: [parseInt(selectedOption.val())] }); } break; case 'multiple_choice': selectedOptions = $questionEl.find('input[type="checkbox"]:checked:not(.multiple-choice-validation)'); if (selectedOptions.length) { optionIndices = $.map(selectedOptions, function(option) { const nameParts = $(option).attr('name').split('_'); return parseInt(nameParts[nameParts.length - 1]); }); answers.push({ questionIndex, optionIndices }); } break; case 'text': textAnswer = $questionEl.find('textarea').val().trim(); if (textAnswer) { answers.push({ questionIndex, textAnswer }); } break; case 'scale': case 'rating': // Обратная совместимость selectedOption = $questionEl.find(`input[name="question_${questionIndex}"]:checked`); if (selectedOption.length) { answers.push({ questionIndex, scaleValue: parseInt(selectedOption.val()) }); } break; case 'tag_cloud': selectedTags = $questionEl.find('.tag-item.selected'); if (selectedTags.length) { const tagTexts = $.map(selectedTags, function(tag) { return $(tag).text().replace('×', '').trim(); }); answers.push({ questionIndex, tagTexts }); } break; } }); // Отправляем ответы на сервер $.ajax({ url: `${getApiPath()}/vote/${publicLink}`, method: 'POST', contentType: 'application/json', data: JSON.stringify({ answers }), success: function(result) { if (result.success) { // Сохраняем информацию о прохождении опроса markAsCompleted(); // Показываем анимацию благодарности вместо сразу скрытия формы showThankYouAnimation(); } else { showAlert(`Ошибка: ${result.error}`, 'Ошибка'); } }, error: function(error) { console.error('Error submitting poll:', error); showAlert('Не удалось отправить ответы. Пожалуйста, попробуйте позже.', 'Ошибка'); } }); }; // Загрузка результатов опроса const loadPollResults = () => { $.ajax({ url: `${getApiPath()}/results/${publicLink}`, method: 'GET', success: function(result) { if (result.success) { renderPollResults(result.data); } else { pollResultsContainerEl.html(`<p class="error">Ошибка: ${result.error}</p>`); } }, error: function(error) { console.error('Error loading poll results:', error); pollResultsContainerEl.html('<p class="error">Не удалось загрузить результаты. Пожалуйста, попробуйте позже.</p>'); } }); }; // Отображение результатов опроса const renderPollResults = (data) => { pollResultsContainerEl.empty(); // Добавляем заголовок с анимацией const $resultsTitle = $('<h2>', { text: 'Результаты опроса', class: 'results-title' }); pollResultsContainerEl.append($resultsTitle); // Отображаем результаты с отложенной анимацией data.questions.forEach((question, index) => { setTimeout(() => { const $questionResult = $('<div>', { class: 'question-result', css: { opacity: 0, transform: 'translateY(20px)' } }); const $questionTitle = $('<h3>', { text: question.text, class: 'question-title' }); $questionResult.append($questionTitle); // Отображаем результаты в зависимости от типа вопроса switch (question.type) { case 'single_choice': case 'multiple_choice': if (question.options && question.options.length > 0) { // Находим общее количество голосов const totalVotes = question.options.reduce((sum, option) => sum + (option.count || 0), 0); if (totalVotes > 0) { question.options.forEach((option) => { const percent = totalVotes > 0 ? Math.round((option.count || 0) * 100 / totalVotes) : 0; const $optionResult = $('<div>', { class: 'result-bar-container' }); const $optionLabel = $('<div>', { class: 'result-label', text: option.text }); const $resultBar = $('<div>', { class: 'result-bar' }); const $resultBarFill = $('<div>', { class: 'result-bar-fill', css: { width: '0%' } }); const $resultPercent = $('<div>', { class: 'result-percent', text: `${percent}% (${option.count || 0} голосов)` }); $resultBar.append($resultBarFill); $optionResult.append($optionLabel, $resultBar, $resultPercent); $questionResult.append($optionResult); // Анимируем заполнение полосы после добавления в DOM setTimeout(() => { $resultBarFill.css('width', `${percent}%`); }, 100); }); } else { $questionResult.append($('<p>', { text: 'Пока нет голосов для этого вопроса', class: 'no-results' })); } } break; case 'tag_cloud': if (question.tags && question.tags.length > 0) { const $tagCloud = $('<div>', { class: 'results-tag-cloud' }); // Находим максимальное количество для масштабирования const maxCount = Math.max(...question.tags.map(tag => tag.count || 1)); question.tags.forEach((tag, tagIndex) => { // Масштабируем размер тега от 1 до 3 в зависимости от частоты const scale = maxCount > 1 ? 1 + (tag.count / maxCount) * 2 : 1; const $tag = $('<div>', { class: 'result-tag', text: tag.text, css: { opacity: 0, transform: 'scale(0.5)', fontSize: `${scale}em` } }); $tagCloud.append($tag); // Анимируем появление тега с задержкой setTimeout(() => { $tag.css({ opacity: 1, transform: 'scale(1)' }); }, tagIndex * 100); }); $questionResult.append($tagCloud); } else { $questionResult.append($('<p>', { text: 'Пока нет тегов для этого вопроса', class: 'no-results' })); } break; case 'scale': case 'rating': if (question.scaleValues && question.scaleValues.length > 0) { const average = question.scaleAverage || (question.scaleValues.reduce((sum, val) => sum + val, 0) / question.scaleValues.length); const min = Math.min(...question.scaleValues); const max = Math.max(...question.scaleValues); const $scaleAverage = $('<div>', { class: 'scale-average', html: `Средняя оценка: <span class="highlight">${average.toFixed(1)}</span> (мин: ${min}, макс: ${max})` }); $questionResult.append($scaleAverage); } else { $questionResult.append($('<p>', { text: 'Пока нет оценок для этого вопроса', class: 'no-results' })); } break; case 'text': if (question.answers && question.answers.length > 0) { const $answersContainer = $('<div>', { class: 'text-answers-container' }); question.answers.forEach((answer, answerIndex) => { const $textAnswer = $('<div>', { class: 'text-answer', text: answer, css: { opacity: 0, transform: 'translateX(20px)' } }); $answersContainer.append($textAnswer); // Анимируем появление ответа с задержкой setTimeout(() => { $textAnswer.css({ opacity: 1, transform: 'translateX(0)' }); }, answerIndex * 200); }); $questionResult.append($answersContainer); } else { $questionResult.append($('<p>', { text: 'Пока нет текстовых ответов для этого вопроса', class: 'no-results' })); } break; } pollResultsContainerEl.append($questionResult); // Анимируем появление блока результатов setTimeout(() => { $questionResult.css({ opacity: 1, transform: 'translateY(0)' }); }, 100); }, index * 300); // Задержка между вопросами }); // Если нет данных для отображения if (!data.questions || data.questions.length === 0) { pollResultsContainerEl.append($('<p>', { text: 'Нет данных для отображения', class: 'no-results' })); } }; // Обработка отправки формы formEl.on('submit', function(e) { e.preventDefault(); // Всегда используем навигацию через кнопки в пошаговом режиме return false; }); // Инициализация loadQuestionnaire(); });