multy-stub/server/routers/questioneer/public/static/js/admin.js
Primakov Alexandr Alexandrovich 1fcc5ed70d init Questionnaire
2025-03-11 23:50:50 +03:00

294 lines
12 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* 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);
});