294 lines
12 KiB
JavaScript
294 lines
12 KiB
JavaScript
|
/* global $, window, document, showAlert, showConfirm, showQRCodeModal */
|
|||
|
$(document).ready(function() {
|
|||
|
const adminLink = window.location.pathname.split('/').pop();
|
|||
|
let questionnaireData = null;
|
|||
|
|
|||
|
// Получаем базовый путь API (для работы и с /questioneer, и с /ms/questioneer)
|
|||
|
const getApiPath = () => {
|
|||
|
const pathParts = window.location.pathname.split('/');
|
|||
|
// Убираем последние две части пути (admin/:adminLink)
|
|||
|
pathParts.pop();
|
|||
|
pathParts.pop();
|
|||
|
return pathParts.join('/') + '/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 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) {
|
|||
|
if (question.type === 'single' || question.type === 'multiple') {
|
|||
|
if (question.options && question.options.some(option => option.votes && option.votes > 0)) {
|
|||
|
hasAnyResponses = true;
|
|||
|
break;
|
|||
|
}
|
|||
|
} else if (question.type === 'tagcloud') {
|
|||
|
if (question.tags && question.tags.some(tag => tag.count && tag.count > 0)) {
|
|||
|
hasAnyResponses = true;
|
|||
|
break;
|
|||
|
}
|
|||
|
} else if (question.type === 'scale' || question.type === 'rating') {
|
|||
|
if (question.responses && question.responses.length > 0) {
|
|||
|
hasAnyResponses = true;
|
|||
|
break;
|
|||
|
}
|
|||
|
} else if (question.type === 'text') {
|
|||
|
if (question.textAnswers && question.textAnswers.length > 0) {
|
|||
|
hasAnyResponses = true;
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if (!hasAnyResponses) {
|
|||
|
$statsContainer.html('<div class="no-stats">Пока нет ответов на опрос</div>');
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
// Для каждого вопроса создаем блок статистики
|
|||
|
questions.forEach((question, index) => {
|
|||
|
const $questionStats = $('<div>', { class: 'question-stats' });
|
|||
|
const $questionTitle = $('<h3>', { text: `${index + 1}. ${question.text}` });
|
|||
|
$questionStats.append($questionTitle);
|
|||
|
|
|||
|
// В зависимости от типа вопроса отображаем разную статистику
|
|||
|
if (question.type === 'single' || question.type === 'multiple') {
|
|||
|
// Для вопросов с выбором вариантов
|
|||
|
const totalVotes = question.options.reduce((sum, option) => sum + (option.votes || 0), 0);
|
|||
|
|
|||
|
if (totalVotes === 0) {
|
|||
|
$questionStats.append($('<div>', { class: 'no-votes', text: 'Нет голосов' }));
|
|||
|
} else {
|
|||
|
const $table = $('<table>', { class: 'stats-table' });
|
|||
|
const $thead = $('<thead>').append(
|
|||
|
$('<tr>').append(
|
|||
|
$('<th>', { text: 'Вариант' }),
|
|||
|
$('<th>', { text: 'Голоса' }),
|
|||
|
$('<th>', { text: '%' }),
|
|||
|
$('<th>', { text: 'Визуализация' })
|
|||
|
)
|
|||
|
);
|
|||
|
|
|||
|
const $tbody = $('<tbody>');
|
|||
|
|
|||
|
question.options.forEach(option => {
|
|||
|
const votes = option.votes || 0;
|
|||
|
const percent = totalVotes > 0 ? Math.round((votes / totalVotes) * 100) : 0;
|
|||
|
|
|||
|
const $tr = $('<tr>').append(
|
|||
|
$('<td>', { text: option.text }),
|
|||
|
$('<td>', { text: votes }),
|
|||
|
$('<td>', { text: `${percent}%` }),
|
|||
|
$('<td>').append(
|
|||
|
$('<div>', { class: 'bar-container' }).append(
|
|||
|
$('<div>', {
|
|||
|
class: 'bar',
|
|||
|
css: { width: `${percent}%` }
|
|||
|
})
|
|||
|
)
|
|||
|
)
|
|||
|
);
|
|||
|
|
|||
|
$tbody.append($tr);
|
|||
|
});
|
|||
|
|
|||
|
$table.append($thead, $tbody);
|
|||
|
$questionStats.append($table);
|
|||
|
$questionStats.append($('<div>', { class: 'total-votes', text: `Всего голосов: ${totalVotes}` }));
|
|||
|
}
|
|||
|
} else if (question.type === 'tagcloud') {
|
|||
|
// Для облака тегов
|
|||
|
if (!question.tags || question.tags.length === 0 || !question.tags.some(tag => tag.count > 0)) {
|
|||
|
$questionStats.append($('<div>', { class: 'no-votes', text: 'Нет выбранных тегов' }));
|
|||
|
} else {
|
|||
|
const $tagCloud = $('<div>', { 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(
|
|||
|
$('<span>', {
|
|||
|
class: 'tag-item',
|
|||
|
text: `${tag.text} (${tag.count})`,
|
|||
|
css: { fontSize: `${fontSize}em` }
|
|||
|
})
|
|||
|
);
|
|||
|
}
|
|||
|
});
|
|||
|
|
|||
|
$questionStats.append($tagCloud);
|
|||
|
}
|
|||
|
} else if (question.type === 'scale' || question.type === 'rating') {
|
|||
|
// Для шкалы и рейтинга
|
|||
|
if (!question.responses || question.responses.length === 0) {
|
|||
|
$questionStats.append($('<div>', { class: 'no-votes', text: 'Нет оценок' }));
|
|||
|
} else {
|
|||
|
const values = question.responses;
|
|||
|
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 = $('<div>', { class: 'scale-stats' });
|
|||
|
|
|||
|
$scaleStats.append(
|
|||
|
$('<div>', { class: 'stat-item' }).append(
|
|||
|
$('<span>', { class: 'stat-label', text: 'Среднее значение:' }),
|
|||
|
$('<span>', { class: 'stat-value', text: avg.toFixed(1) })
|
|||
|
),
|
|||
|
$('<div>', { class: 'stat-item' }).append(
|
|||
|
$('<span>', { class: 'stat-label', text: 'Минимум:' }),
|
|||
|
$('<span>', { class: 'stat-value', text: min })
|
|||
|
),
|
|||
|
$('<div>', { class: 'stat-item' }).append(
|
|||
|
$('<span>', { class: 'stat-label', text: 'Максимум:' }),
|
|||
|
$('<span>', { class: 'stat-value', text: max })
|
|||
|
),
|
|||
|
$('<div>', { class: 'stat-item' }).append(
|
|||
|
$('<span>', { class: 'stat-label', text: 'Количество оценок:' }),
|
|||
|
$('<span>', { class: 'stat-value', text: values.length })
|
|||
|
)
|
|||
|
);
|
|||
|
|
|||
|
$questionStats.append($scaleStats);
|
|||
|
}
|
|||
|
} else if (question.type === 'text') {
|
|||
|
// Для текстовых ответов
|
|||
|
if (!question.textAnswers || question.textAnswers.length === 0) {
|
|||
|
$questionStats.append($('<div>', { class: 'no-votes', text: 'Нет текстовых ответов' }));
|
|||
|
} else {
|
|||
|
const $textAnswers = $('<div>', { class: 'text-answers-list' });
|
|||
|
|
|||
|
question.textAnswers.forEach((answer, i) => {
|
|||
|
$textAnswers.append(
|
|||
|
$('<div>', { class: 'text-answer-item' }).append(
|
|||
|
$('<div>', { class: 'answer-number', text: `#${i + 1}` }),
|
|||
|
$('<div>', { class: 'answer-text', text: answer })
|
|||
|
)
|
|||
|
);
|
|||
|
});
|
|||
|
|
|||
|
$questionStats.append($textAnswers);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
$statsContainer.append($questionStats);
|
|||
|
});
|
|||
|
};
|
|||
|
|
|||
|
// Копирование ссылок
|
|||
|
$('#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 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/admin/${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);
|
|||
|
});
|