/* global $, window, document, showAlert, showConfirm, showQRCodeModal */ $(document).ready(function() { const adminLink = window.location.pathname.split('/').pop(); let questionnaireData = null; // Функция для получения базового пути 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 страницы до /admin/[adminLink] const basePath = pathname.split('/admin')[0]; // Путь до API приложения return basePath + '/api'; } }; // Загрузка данных опроса const loadQuestionnaire = () => { $.ajax({ url: `${getApiPath()}/questionnaires/admin/${adminLink}`, method: 'GET', success: function(result) { if (result.success) { questionnaireData = result.data; renderQuestionnaire(); } else { $('#loading').text(`Ошибка: ${result.error}`); } }, error: function(error) { console.error('Error loading questionnaire:', error); $('#loading').text('Не удалось загрузить опрос. Пожалуйста, попробуйте позже.'); } }); }; // Отображение данных опроса const renderQuestionnaire = () => { // Заполняем основные данные $('#questionnaire-title').text(questionnaireData.title); $('#questionnaire-description').text(questionnaireData.description || 'Нет описания'); // Формируем ссылки const baseUrl = window.location.origin; const isMsPath = window.location.pathname.includes('/ms/questioneer'); let baseQuestionnairePath; if (isMsPath) { // Для продакшна: используем /ms/questioneer baseQuestionnairePath = '/ms/questioneer'; } else { // Для локальной разработки: используем текущий путь baseQuestionnairePath = window.location.pathname.split('/admin')[0]; } const publicUrl = `${baseUrl}${baseQuestionnairePath}/poll/${questionnaireData.publicLink}`; const adminUrl = `${baseUrl}${baseQuestionnairePath}/admin/${questionnaireData.adminLink}`; $('#public-link').val(publicUrl); $('#admin-link').val(adminUrl); // Отображаем статистику renderStats(questionnaireData.questions); // Показываем контейнер с данными $('#loading').hide(); $('#questionnaire-container').show(); }; // Отображение статистики опроса const renderStats = (questions) => { const $statsContainer = $('#stats-container'); $statsContainer.empty(); // Проверяем, есть ли ответы let hasAnyResponses = false; // Проверяем наличие ответов для каждого типа вопросов for (const question of questions) { // Согласовываем типы вопросов между бэкендом и фронтендом const questionType = normalizeQuestionType(question.type); if (questionType === 'single' || questionType === 'multiple') { if (question.options && question.options.some(option => (option.votes > 0 || option.count > 0))) { hasAnyResponses = true; break; } } else if (questionType === 'tagcloud') { if (question.tags && question.tags.some(tag => tag.count > 0)) { hasAnyResponses = true; break; } } else if (questionType === 'scale' || questionType === 'rating') { // Проверяем оба возможных поля для данных шкалы const hasScaleValues = question.scaleValues && question.scaleValues.length > 0; const hasResponses = question.responses && question.responses.length > 0; if (hasScaleValues || hasResponses) { hasAnyResponses = true; break; } } else if (questionType === 'text') { // Проверяем оба возможных поля для текстовых ответов const hasTextAnswers = question.textAnswers && question.textAnswers.length > 0; const hasAnswers = question.answers && question.answers.length > 0; if (hasTextAnswers || hasAnswers) { hasAnyResponses = true; break; } } } if (!hasAnyResponses) { $statsContainer.html('
Пока нет ответов на опрос
'); return; } // Для каждого вопроса создаем блок статистики questions.forEach((question, index) => { const $questionStats = $('
', { class: 'question-stats' }); const $questionTitle = $('

', { text: `${index + 1}. ${question.text}` }); $questionStats.append($questionTitle); // Согласовываем типы вопросов между бэкендом и фронтендом const questionType = normalizeQuestionType(question.type); // В зависимости от типа вопроса отображаем разную статистику if (questionType === 'single' || questionType === 'multiple') { // Для вопросов с выбором вариантов renderChoiceStats(question, $questionStats); } else if (questionType === 'tagcloud') { // Для облака тегов renderTagCloudStats(question, $questionStats); } else if (questionType === 'scale' || questionType === 'rating') { // Для шкалы и рейтинга renderScaleStats(question, $questionStats); } else if (questionType === 'text') { // Для текстовых ответов renderTextStats(question, $questionStats); } $statsContainer.append($questionStats); }); }; // Приводит тип вопроса к стандартному формату const normalizeQuestionType = (type) => { const typeMap = { 'single_choice': 'single', 'multiple_choice': 'multiple', 'tag_cloud': 'tagcloud', 'single': 'single', 'multiple': 'multiple', 'tagcloud': 'tagcloud', 'scale': 'scale', 'rating': 'rating', 'text': 'text' }; return typeMap[type] || type; }; // Отображение статистики для вопросов с выбором const renderChoiceStats = (question, $container) => { // Преобразуем опции к единому формату const options = question.options.map(option => ({ text: option.text, votes: option.votes || option.count || 0 })); const totalVotes = options.reduce((sum, option) => sum + option.votes, 0); if (totalVotes === 0) { $container.append($('
', { class: 'no-votes', text: 'Нет голосов' })); return; } const $table = $('', { class: 'stats-table' }); const $thead = $('').append( $('').append( $(''); options.forEach(option => { const votes = option.votes; const percent = totalVotes > 0 ? Math.round((votes / totalVotes) * 100) : 0; const $tr = $('').append( $('
', { text: 'Вариант' }), $('', { text: 'Голоса' }), $('', { text: '%' }), $('', { text: 'Визуализация' }) ) ); const $tbody = $('
', { text: option.text }), $('', { text: votes }), $('', { text: `${percent}%` }), $('').append( $('
', { class: 'bar-container' }).append( $('
', { class: 'bar', css: { width: `${percent}%` } }) ) ) ); $tbody.append($tr); }); $table.append($thead, $tbody); $container.append($table); $container.append($('
', { class: 'total-votes', text: `Всего голосов: ${totalVotes}` })); }; // Отображение статистики для облака тегов const renderTagCloudStats = (question, $container) => { if (!question.tags || question.tags.length === 0 || !question.tags.some(tag => tag.count > 0)) { $container.append($('
', { class: 'no-votes', text: 'Нет выбранных тегов' })); return; } const $tagCloud = $('
', { class: 'tag-cloud-stats' }); // Находим максимальное количество для масштабирования const maxCount = Math.max(...question.tags.map(tag => tag.count || 0)); // Сортируем теги по популярности const sortedTags = [...question.tags].sort((a, b) => (b.count || 0) - (a.count || 0)); sortedTags.forEach(tag => { if (tag.count && tag.count > 0) { const fontSize = maxCount > 0 ? 1 + (tag.count / maxCount) * 1.5 : 1; // от 1em до 2.5em $tagCloud.append( $('', { class: 'tag-item', text: `${tag.text} (${tag.count})`, css: { fontSize: `${fontSize}em` } }) ); } }); $container.append($tagCloud); }; // Отображение статистики для шкалы и рейтинга const renderScaleStats = (question, $container) => { // Используем scaleValues или responses, в зависимости от того, что доступно const values = question.responses && question.responses.length > 0 ? question.responses : (question.scaleValues || []); if (values.length === 0) { $container.append($('
', { class: 'no-votes', text: 'Нет оценок' })); return; } const sum = values.reduce((a, b) => a + b, 0); const avg = sum / values.length; const min = Math.min(...values); const max = Math.max(...values); // Создаем контейнер для статистики const $scaleStats = $('
', { class: 'scale-stats' }); // Добавляем сводную статистику $scaleStats.append( $('
', { class: 'stat-summary' }).append( $('
', { class: 'stat-item' }).append( $('', { class: 'stat-label', text: 'Среднее значение:' }), $('', { class: 'stat-value', text: avg.toFixed(1) }) ), $('
', { class: 'stat-item' }).append( $('', { class: 'stat-label', text: 'Минимум:' }), $('', { class: 'stat-value', text: min }) ), $('
', { class: 'stat-item' }).append( $('', { class: 'stat-label', text: 'Максимум:' }), $('', { class: 'stat-value', text: max }) ), $('
', { class: 'stat-item' }).append( $('', { class: 'stat-label', text: 'Количество оценок:' }), $('', { class: 'stat-value', text: values.length }) ) ) ); // Создаем таблицу для визуализации распределения голосов const $table = $('', { class: 'stats-table' }); const $thead = $('').append( $('').append( $(''); // Определяем минимальное и максимальное значение шкалы из самих данных // либо используем значения из настроек вопроса, если они есть const scaleMin = question.scaleMin !== undefined ? question.scaleMin : min; const scaleMax = question.scaleMax !== undefined ? question.scaleMax : max; // Создаем счетчик для каждого возможного значения шкалы const countByValue = {}; for (let i = scaleMin; i <= scaleMax; i++) { countByValue[i] = 0; } // Подсчитываем количество голосов для каждого значения values.forEach(value => { if (countByValue[value] !== undefined) { countByValue[value]++; } }); // Создаем строки таблицы для каждого значения шкалы for (let value = scaleMin; value <= scaleMax; value++) { const count = countByValue[value] || 0; const percent = values.length > 0 ? Math.round((count / values.length) * 100) : 0; const $tr = $('').append( $('
', { text: 'Значение' }), $('', { text: 'Голоса' }), $('', { text: '%' }), $('', { text: 'Визуализация' }) ) ); const $tbody = $('
', { text: value }), $('', { text: count }), $('', { text: `${percent}%` }), $('').append( $('
', { class: 'bar-container' }).append( $('
', { class: 'bar', css: { width: `${percent}%` } }) ) ) ); $tbody.append($tr); } $table.append($thead, $tbody); $scaleStats.append($table); $container.append($scaleStats); }; // Отображение статистики для текстовых ответов const renderTextStats = (question, $container) => { // Используем textAnswers или answers, в зависимости от того, что доступно const answers = question.textAnswers && question.textAnswers.length > 0 ? question.textAnswers : (question.answers || []); if (answers.length === 0) { $container.append($('
', { class: 'no-votes', text: 'Нет текстовых ответов' })); return; } const $textAnswers = $('
', { class: 'text-answers-list' }); answers.forEach((answer, i) => { $textAnswers.append( $('
', { class: 'text-answer-item' }).append( $('
', { class: 'answer-number', text: `#${i + 1}` }), $('
', { class: 'answer-text', text: answer }) ) ); }); $container.append($textAnswers); }; // Копирование ссылок $('#copy-public-link').on('click', function() { $('#public-link').select(); document.execCommand('copy'); showAlert('Ссылка для голосования скопирована в буфер обмена', 'Копирование', null, true); }); $('#copy-admin-link').on('click', function() { $('#admin-link').select(); document.execCommand('copy'); showAlert('Административная ссылка скопирована в буфер обмена', 'Копирование', null, true); }); // Отображение QR-кода $('#show-qr-code').on('click', function() { const publicUrl = $('#public-link').val(); showQRCodeModal(publicUrl, 'QR-код для голосования'); }); // Редактирование опроса $('#edit-questionnaire').on('click', function() { // Перенаправляем на страницу редактирования const isMsPath = window.location.pathname.includes('/ms/questioneer'); let basePath; if (isMsPath) { // Для продакшна: используем /ms/questioneer basePath = '/ms/questioneer'; } else { // Для локальной разработки: используем текущий путь basePath = window.location.pathname.split('/admin')[0]; } window.location.href = `${basePath}/edit/${adminLink}`; }); // Удаление опроса $('#delete-questionnaire').on('click', function() { showConfirm('Вы уверены, что хотите удалить опрос? Все ответы будут удалены безвозвратно.', function(confirmed) { if (confirmed) { deleteQuestionnaire(); } }, 'Удаление опроса'); }); // Функция удаления опроса const deleteQuestionnaire = () => { $.ajax({ url: `${getApiPath()}/questionnaires/${adminLink}`, method: 'DELETE', success: function(result) { if (result.success) { showAlert('Опрос успешно удален', 'Удаление опроса', function() { window.location.href = window.location.pathname.split('/admin')[0]; }, true); } else { showAlert(`Ошибка при удалении опроса: ${result.error}`, 'Ошибка'); } }, error: function(error) { console.error('Error deleting questionnaire:', error); showAlert('Не удалось удалить опрос. Пожалуйста, попробуйте позже.', 'Ошибка'); } }); }; // Инициализация loadQuestionnaire(); // Обновление данных каждые 10 секунд setInterval(loadQuestionnaire, 10000); });