fix путей
This commit is contained in:
@@ -3,13 +3,27 @@ $(document).ready(function() {
|
||||
const adminLink = window.location.pathname.split('/').pop();
|
||||
let questionnaireData = null;
|
||||
|
||||
// Получаем базовый путь API (для работы и с /questioneer, и с /ms/questioneer)
|
||||
// Функция для получения базового пути API
|
||||
const getApiPath = () => {
|
||||
const pathParts = window.location.pathname.split('/');
|
||||
// Убираем последние две части пути (admin/:adminLink)
|
||||
pathParts.pop();
|
||||
pathParts.pop();
|
||||
return pathParts.join('/') + '/api';
|
||||
// Проверяем, содержит ли путь /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 {
|
||||
// Для локальной разработки: используем обычный путь
|
||||
// Извлекаем базовый путь из URL страницы
|
||||
const pathParts = pathname.split('/');
|
||||
// Если последний сегмент пустой (из-за /) - удаляем его
|
||||
if (pathParts[pathParts.length - 1] === '') {
|
||||
pathParts.pop();
|
||||
}
|
||||
|
||||
// Путь до корня приложения
|
||||
return pathParts.join('/') + '/api';
|
||||
}
|
||||
};
|
||||
|
||||
// Загрузка данных опроса
|
||||
@@ -40,7 +54,17 @@ $(document).ready(function() {
|
||||
|
||||
// Формируем ссылки
|
||||
const baseUrl = window.location.origin;
|
||||
const baseQuestionnairePath = window.location.pathname.split('/admin')[0];
|
||||
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}`;
|
||||
|
||||
@@ -65,23 +89,32 @@ $(document).ready(function() {
|
||||
|
||||
// Проверяем наличие ответов для каждого типа вопросов
|
||||
for (const question of questions) {
|
||||
if (question.type === 'single' || question.type === 'multiple') {
|
||||
if (question.options && question.options.some(option => option.votes && option.votes > 0)) {
|
||||
// Согласовываем типы вопросов между бэкендом и фронтендом
|
||||
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 (question.type === 'tagcloud') {
|
||||
if (question.tags && question.tags.some(tag => tag.count && tag.count > 0)) {
|
||||
} else if (questionType === 'tagcloud') {
|
||||
if (question.tags && question.tags.some(tag => tag.count > 0)) {
|
||||
hasAnyResponses = true;
|
||||
break;
|
||||
}
|
||||
} else if (question.type === 'scale' || question.type === 'rating') {
|
||||
if (question.responses && question.responses.length > 0) {
|
||||
} 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 (question.type === 'text') {
|
||||
if (question.textAnswers && question.textAnswers.length > 0) {
|
||||
} 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;
|
||||
}
|
||||
@@ -99,138 +132,256 @@ $(document).ready(function() {
|
||||
const $questionTitle = $('<h3>', { text: `${index + 1}. ${question.text}` });
|
||||
$questionStats.append($questionTitle);
|
||||
|
||||
// Согласовываем типы вопросов между бэкендом и фронтендом
|
||||
const questionType = normalizeQuestionType(question.type);
|
||||
|
||||
// В зависимости от типа вопроса отображаем разную статистику
|
||||
if (question.type === 'single' || question.type === 'multiple') {
|
||||
if (questionType === 'single' || questionType === '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') {
|
||||
renderChoiceStats(question, $questionStats);
|
||||
} else if (questionType === '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') {
|
||||
renderTagCloudStats(question, $questionStats);
|
||||
} else if (questionType === 'scale' || questionType === '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') {
|
||||
renderScaleStats(question, $questionStats);
|
||||
} else if (questionType === '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);
|
||||
}
|
||||
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($('<div>', { class: 'no-votes', text: 'Нет голосов' }));
|
||||
return;
|
||||
}
|
||||
|
||||
const $table = $('<table>', { class: 'stats-table' });
|
||||
const $thead = $('<thead>').append(
|
||||
$('<tr>').append(
|
||||
$('<th>', { text: 'Вариант' }),
|
||||
$('<th>', { text: 'Голоса' }),
|
||||
$('<th>', { text: '%' }),
|
||||
$('<th>', { text: 'Визуализация' })
|
||||
)
|
||||
);
|
||||
|
||||
const $tbody = $('<tbody>');
|
||||
|
||||
options.forEach(option => {
|
||||
const votes = option.votes;
|
||||
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);
|
||||
$container.append($table);
|
||||
$container.append($('<div>', { 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($('<div>', { class: 'no-votes', text: 'Нет выбранных тегов' }));
|
||||
return;
|
||||
}
|
||||
|
||||
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` }
|
||||
})
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
$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($('<div>', { 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 = $('<div>', { class: 'scale-stats' });
|
||||
|
||||
// Добавляем сводную статистику
|
||||
$scaleStats.append(
|
||||
$('<div>', { class: 'stat-summary' }).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 })
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
// Создаем таблицу для визуализации распределения голосов
|
||||
const $table = $('<table>', { class: 'stats-table' });
|
||||
const $thead = $('<thead>').append(
|
||||
$('<tr>').append(
|
||||
$('<th>', { text: 'Значение' }),
|
||||
$('<th>', { text: 'Голоса' }),
|
||||
$('<th>', { text: '%' }),
|
||||
$('<th>', { text: 'Визуализация' })
|
||||
)
|
||||
);
|
||||
|
||||
const $tbody = $('<tbody>');
|
||||
|
||||
// Определяем минимальное и максимальное значение шкалы из самих данных
|
||||
// либо используем значения из настроек вопроса, если они есть
|
||||
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 = $('<tr>').append(
|
||||
$('<td>', { text: value }),
|
||||
$('<td>', { text: count }),
|
||||
$('<td>', { text: `${percent}%` }),
|
||||
$('<td>').append(
|
||||
$('<div>', { class: 'bar-container' }).append(
|
||||
$('<div>', {
|
||||
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($('<div>', { class: 'no-votes', text: 'Нет текстовых ответов' }));
|
||||
return;
|
||||
}
|
||||
|
||||
const $textAnswers = $('<div>', { class: 'text-answers-list' });
|
||||
|
||||
answers.forEach((answer, i) => {
|
||||
$textAnswers.append(
|
||||
$('<div>', { class: 'text-answer-item' }).append(
|
||||
$('<div>', { class: 'answer-number', text: `#${i + 1}` }),
|
||||
$('<div>', { class: 'answer-text', text: answer })
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
$container.append($textAnswers);
|
||||
};
|
||||
|
||||
// Копирование ссылок
|
||||
$('#copy-public-link').on('click', function() {
|
||||
$('#public-link').select();
|
||||
@@ -252,7 +403,18 @@ $(document).ready(function() {
|
||||
|
||||
// Редактирование опроса
|
||||
$('#edit-questionnaire').on('click', function() {
|
||||
const basePath = window.location.pathname.split('/admin')[0];
|
||||
// Перенаправляем на страницу редактирования
|
||||
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}`;
|
||||
});
|
||||
|
||||
@@ -268,7 +430,7 @@ $(document).ready(function() {
|
||||
// Функция удаления опроса
|
||||
const deleteQuestionnaire = () => {
|
||||
$.ajax({
|
||||
url: `${getApiPath()}/questionnaires/admin/${adminLink}`,
|
||||
url: `${getApiPath()}/questionnaires/${adminLink}`,
|
||||
method: 'DELETE',
|
||||
success: function(result) {
|
||||
if (result.success) {
|
||||
|
||||
Reference in New Issue
Block a user