/* 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; // Объявляем переменную для хранения накопленных ответов let accumulatedAnswers = []; // Проверка доступности 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 = () => { // Проверяем, содержит ли путь /ms/ (продакшн на dev.bro-js.ru) const pathname = window.location.pathname; const isMsPath = pathname.includes('/ms/questioneer'); if (isMsPath) { // Для продакшна: если в пути есть /ms/, то API доступно по /ms/questioneer/api return '/ms/questioneer/api'; } else { // Для локальной разработки: формируем путь к API без учета текущей страницы // Извлекаем базовый путь из URL страницы до /poll const basePath = pathname.split('/poll')[0]; // Путь до API приложения return basePath + '/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 = $('
', { id: 'welcome-animation', class: 'welcome-animation', css: { opacity: 0, transform: 'translateY(20px)' } }); const $welcomeIcon = $('
', { class: 'welcome-icon', html: '' }); const $welcomeTitle = $('

', { text: `Добро пожаловать в опрос "${questionnaireData.title}"`, class: 'welcome-title' }); const $welcomeDescription = $('

', { text: questionnaireData.description || 'Пожалуйста, ответьте на следующие вопросы:', class: 'welcome-description' }); const $startButton = $(' Вопрос 1 из ${questionnaireData.questions.length}

`); // Обработчики для навигации $('#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; } // Сохраняем ответ на текущий вопрос saveCurrentAnswer(currentQuestion, currentQuestionIndex); // Если есть еще вопросы, переходим к следующему 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 saveCurrentAnswer = (question, questionIndex) => { // Нормализуем тип вопроса для согласованной обработки const questionType = normalizeQuestionType(question.type); const $questionEl = $(`.question-item[data-index="${questionIndex}"]`); // Удаляем предыдущий ответ на этот вопрос, если он был accumulatedAnswers = accumulatedAnswers.filter(answer => answer.questionIndex !== questionIndex); let optionIndices; let selectedOption; let selectedOptions; let selectedTags; let textAnswer; let scaleValue; switch (questionType) { case 'single': selectedOption = $questionEl.find(`input[name="question_${questionIndex}"]:checked`); if (selectedOption.length) { accumulatedAnswers.push({ questionIndex, optionIndices: [parseInt(selectedOption.val())] }); } break; case 'multiple': 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]); }); accumulatedAnswers.push({ questionIndex, optionIndices }); } break; case 'text': textAnswer = $questionEl.find('textarea').val().trim(); if (textAnswer) { accumulatedAnswers.push({ questionIndex, textAnswer }); } break; case 'scale': case 'rating': // Обратная совместимость selectedOption = $questionEl.find(`input[name="question_${questionIndex}"]:checked`); if (selectedOption.length) { const scaleValue = parseInt(selectedOption.val()); accumulatedAnswers.push({ questionIndex, scaleValue, optionIndices: [scaleValue] }); } break; case 'tagcloud': selectedTags = $questionEl.find('.tag-item.selected'); if (selectedTags.length) { const tagTexts = $.map(selectedTags, function(tag) { return $(tag).text().replace('×', '').trim(); }); accumulatedAnswers.push({ questionIndex, tagTexts }); } break; } }; // Отправка формы const submitForm = function() { // Отключаем атрибут required у невидимых полей перед отправкой $('input[required]:not(:visible), textarea[required]:not(:visible)').prop('required', false); // Отправляем накопленные ответы на сервер $.ajax({ url: `${getApiPath()}/vote/${publicLink}`, method: 'POST', contentType: 'application/json', data: JSON.stringify({ answers: accumulatedAnswers }), success: function(result) { if (result.success) { // Сохраняем информацию о прохождении опроса markAsCompleted(); // Показываем анимацию благодарности вместо сразу скрытия формы showThankYouAnimation(); } else { showAlert(`Ошибка: ${result.error}`, 'Ошибка'); } }, error: function(error) { console.error('Error submitting poll:', error); showAlert('Не удалось отправить ответы. Пожалуйста, попробуйте позже.', 'Ошибка'); } }); }; // Проверка наличия ответа на вопрос 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 = $('
', { class: 'question-item', 'data-index': questionIndex }); const $questionTitle = $('

', { text: question.text, class: 'question-title' }); if (question.required) { const $requiredMark = $('', { 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 = $('
', { class: 'radio-options-container' }); $.each(question.options, function(optionIndex, option) { const $optionContainer = $('
', { class: 'radio-option' }); const $optionInput = $('', { type: 'radio', id: `question_${questionIndex}_option_${optionIndex}`, name: `question_${questionIndex}`, value: optionIndex, required: question.required }); const $optionLabel = $('