diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/JS_Template.iml b/.idea/JS_Template.iml new file mode 100644 index 0000000..ead1d18 --- /dev/null +++ b/.idea/JS_Template.iml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/jsLibraryMappings.xml b/.idea/jsLibraryMappings.xml new file mode 100644 index 0000000..3565144 --- /dev/null +++ b/.idea/jsLibraryMappings.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..6f29fee --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..8811d7b --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/index.html b/index.html index 09c6dc0..12ee7ec 100644 --- a/index.html +++ b/index.html @@ -8,6 +8,71 @@ +
+
+ Create Poll +
+ + + +
+
+
+ +
+ + + \ No newline at end of file diff --git a/report.html b/report.html new file mode 100644 index 0000000..7acaa79 --- /dev/null +++ b/report.html @@ -0,0 +1,24 @@ + + + + + + Polls Report + + + +
+
+

Звіт Опитування

+

Візуалізація даних ваших опитувань за допомогою WebDataRocks.

+ Повернутися до опитувань +
+ +
+ + + + + + + \ No newline at end of file diff --git a/src/main.js b/src/main.js index e69de29..3d4ea11 100644 --- a/src/main.js +++ b/src/main.js @@ -0,0 +1,346 @@ +const pollsData = { + "polls": [ + { + "id": 1, + "question": "What is your favorite programming language?", + "category": "Technology", + "options": [ + {"name": "JavaScript", "votes": 68, "percentage": 45}, + {"name": "Python", "votes": 68, "percentage": 45}, + {"name": "Java", "votes": 53, "percentage": 35}, + {"name": "C++", "votes": 5, "percentage": 3} + ], + "totalVotes": 150 + }, + { + "id": 2, + "question": "Which social media platform do you use the most?", + "category": "Social Media", + "options": [ + {"name": "Facebook", "votes": 63, "percentage": 35}, + {"name": "Twitter", "votes": 45, "percentage": 25}, + {"name": "Instagram", "votes": 54, "percentage": 30}, + {"name": "LinkedIn", "votes": 18, "percentage": 10} + ], + "totalVotes": 180 + }, + { + "id": 3, + "question": "What's your preferred way to learn new skills?", + "category": "Education", + "options": [ + {"name": "Online courses", "votes": 85, "percentage": 42}, + {"name": "Books", "votes": 65, "percentage": 32}, + {"name": "YouTube videos", "votes": 40, "percentage": 20}, + {"name": "Workshops", "votes": 12, "percentage": 6} + ], + "totalVotes": 202 + }, + { + "id": 4, + "question": "Which streaming service do you use most?", + "category": "Entertainment", + "options": [ + {"name": "Netflix", "votes": 120, "percentage": 48}, + {"name": "Disney+", "votes": 65, "percentage": 26}, + {"name": "Amazon Prime", "votes": 40, "percentage": 16}, + {"name": "HBO Max", "votes": 25, "percentage": 10} + ], + "totalVotes": 250 + }, + { + "id": 5, + "question": "What's your favorite type of exercise?", + "category": "Sports", + "options": [ + {"name": "Running", "votes": 45, "percentage": 30}, + {"name": "Weight training", "votes": 60, "percentage": 40}, + {"name": "Yoga", "votes": 30, "percentage": 20}, + {"name": "Swimming", "votes": 15, "percentage": 10} + ], + "totalVotes": 150 + }, + { + "id": 6, + "question": "Which work style do you prefer?", + "category": "Business", + "options": [ + {"name": "Remote work", "votes": 140, "percentage": 56}, + {"name": "Office work", "votes": 60, "percentage": 24}, + {"name": "Hybrid", "votes": 50, "percentage": 20} + ], + "totalVotes": 250 + }, + { + "id": 7, + "question": "What's your favorite type of food?", + "category": "Lifestyle", + "options": [ + {"name": "Italian", "votes": 80, "percentage": 32}, + {"name": "Asian", "votes": 90, "percentage": 36}, + {"name": "Mexican", "votes": 50, "percentage": 20}, + {"name": "American", "votes": 30, "percentage": 12} + ], + "totalVotes": 250 + }, + { + "id": 8, + "question": "Which mobile operating system do you prefer?", + "category": "Technology", + "options": [ + {"name": "iOS", "votes": 95, "percentage": 48}, + {"name": "Android", "votes": 100, "percentage": 50}, + {"name": "Other", "votes": 5, "percentage": 2} + ], + "totalVotes": 200 + } + ] +}; + +// Функція для оновлення відсотків для конкретного опитування +function updatePollPercentages(poll) { + if (poll.totalVotes === 0) { + poll.options.forEach(option => { + option.percentage = 0; + }); + return; + } + poll.options.forEach(option => { + option.percentage = Math.round((option.votes / poll.totalVotes) * 100); + }); + + // Важливо: перерозподілити відсотки, якщо сума не дорівнює 100 через округлення + let currentTotalPercentage = poll.options.reduce((sum, option) => sum + option.percentage, 0); + if (currentTotalPercentage !== 100) { + const diff = 100 - currentTotalPercentage; + // Розподіляємо різницю серед опцій з найбільшими дробовими частинами + // Для спрощення, просто додамо/віднімемо від першої опції, або можна більш складно розподіляти + if (poll.options.length > 0) { + poll.options[0].percentage += diff; + } + } +} + + +// Function to render polls +function renderPolls(polls = pollsData.polls) { + const pollsGrid = document.getElementById('pollsGrid'); + const dynamicPopups = document.getElementById('dynamicPopups'); + + pollsGrid.innerHTML = ''; + dynamicPopups.innerHTML = ''; + + polls.forEach(poll => { + // Create poll card + const pollCard = document.createElement('a'); + pollCard.href = `#votePopup${poll.id}`; + pollCard.className = 'poll-card'; + + let optionsHTML = ''; + poll.options.forEach(option => { + optionsHTML += ` +
+ ${option.name} +
+
+
+ ${option.percentage}% +
+ `; + }); + + pollCard.innerHTML = ` +
${poll.question}
+ ${optionsHTML} +
+
${poll.category}
+
${poll.totalVotes} голосів
+
+ `; + + pollsGrid.appendChild(pollCard); + + // Create vote popup + const popup = document.createElement('div'); + popup.id = `votePopup${poll.id}`; + popup.className = 'popup-overlay'; + + let radioOptionsHTML = ''; + poll.options.forEach((option, index) => { + const optionId = `option${poll.id}_${index}`; + radioOptionsHTML += ` +
+ + +
+ `; + }); + + popup.innerHTML = ` + + `; + + dynamicPopups.appendChild(popup); + + // Add form submit handler for voting + const form = popup.querySelector('form'); + form.addEventListener('submit', function(e) { + e.preventDefault(); + const formData = new FormData(form); + const selectedOptionName = formData.get(`poll${poll.id}`); + + if (selectedOptionName) { + // Знаходимо опитування в pollsData за його ID + const currentPoll = pollsData.polls.find(p => p.id === poll.id); + + if (currentPoll) { + // Збільшуємо загальну кількість голосів для цього опитування + currentPoll.totalVotes++; + + // Знаходимо обраний варіант і збільшуємо його голоси + const selectedOption = currentPoll.options.find(opt => opt.name === selectedOptionName); + if (selectedOption) { + selectedOption.votes++; + } + + // Перераховуємо відсотки для всіх варіантів цього опитування + updatePollPercentages(currentPoll); + + // Після оновлення даних, перемальовуємо ВСІ опитування + // Це оновить і картку, і попап з голосуванням + renderPolls(); + + alert(`Дякуємо за ваш голос за: ${selectedOptionName}!`); + window.location.hash = '#'; + } + } else { + alert('Будь ласка, оберіть варіант, щоб проголосувати.'); + } + }); + }); +} + +// Отримуємо форму створення опитування та контейнер для опцій +const createPollForm = document.querySelector('#createPopup form'); +const optionsContainer = document.querySelector('.options-container'); +const addOptionBtn = document.querySelector('.add-option'); + +// Функція для додавання нового поля для опції +function addOptionInput() { + const newOptionInput = document.createElement('input'); + newOptionInput.type = 'text'; + newOptionInput.className = 'option-input'; + newOptionInput.name = `option${optionsContainer.children.length + 1}`; // Генеруємо унікальне ім'я + newOptionInput.placeholder = `Варіант ${optionsContainer.children.length + 1}`; + optionsContainer.appendChild(newOptionInput); +} + +// Обробник події для кнопки "Add option" +if (addOptionBtn) { + addOptionBtn.addEventListener('click', function(e) { + e.preventDefault(); + addOptionInput(); + }); +} + + +// Обробник події для відправки форми створення опитування +if (createPollForm) { + createPollForm.addEventListener('submit', function(e) { + e.preventDefault(); // Запобігаємо стандартній відправці форми + + const question = document.getElementById('question').value; + const category = document.getElementById('category').value; + const optionInputs = document.querySelectorAll('.option-input'); + + const options = []; + optionInputs.forEach(input => { + const optionName = input.value.trim(); + if (optionName) { // Додаємо лише непусті опції + options.push({ + name: optionName, + votes: 0, + percentage: 0 + }); + } + }); + + // Проста валідація: щонайменше 2 опції + if (options.length < 2) { + alert('Будь ласка, додайте щонайменше дві опції.'); + return; + } + + // Генеруємо новий ID для опитування + const newId = pollsData.polls.length > 0 ? Math.max(...pollsData.polls.map(p => p.id)) + 1 : 1; + + const newPoll = { + id: newId, + question: question, + category: category, + options: options, + totalVotes: 0 + }; + + // Додаємо нове опитування до нашого масиву даних + pollsData.polls.push(newPoll); + + // Перемальовуємо всі опитування, щоб показати нове + renderPolls(); + + // Очищаємо форму після створення + createPollForm.reset(); + // Прибираємо додаткові поля опцій, залишаючи перші два + while (optionsContainer.children.length > 2) { + optionsContainer.removeChild(optionsContainer.lastChild); + } + + // Закриваємо попап створення опитування + window.location.hash = '#'; + + alert('Опитування успішно створено!'); + }); +} + +// Search functionality +function setupSearch() { + const searchInput = document.querySelector('.search-input'); + const categoryDropdown = document.querySelector('.category-dropdown'); + + function filterPolls() { + const searchTerm = searchInput.value.toLowerCase(); + const selectedCategory = categoryDropdown.value.toLowerCase(); + + const filteredPolls = pollsData.polls.filter(poll => { + const matchesSearch = poll.question.toLowerCase().includes(searchTerm) || + poll.options.some(option => option.name.toLowerCase().includes(searchTerm)); + const matchesCategory = !selectedCategory || poll.category.toLowerCase() === selectedCategory; + + return matchesSearch && matchesCategory; + }); + + renderPolls(filteredPolls); + } + + searchInput.addEventListener('input', filterPolls); + categoryDropdown.addEventListener('change', filterPolls); +} + +// Initialize the page +document.addEventListener('DOMContentLoaded', function() { + renderPolls(); + setupSearch(); +}); \ No newline at end of file diff --git a/src/report.js b/src/report.js new file mode 100644 index 0000000..91d238b --- /dev/null +++ b/src/report.js @@ -0,0 +1,53 @@ +document.addEventListener('DOMContentLoaded', function() { + + const storedPolls = JSON.parse(localStorage.getItem('pollsData')) || []; + + const transformedData = []; + storedPolls.forEach(poll => { + poll.options.forEach(option => { + transformedData.push({ + "Питання": poll.question, + "Категорія": poll.category, + "Варіант": option.name, + "Голоси за варіант": option.votes, + "Відсоток за варіант": option.percentage, + "Всього голосів опитування": poll.totalVotes + }); + }); + }); + + // Ініціалізація WebDataRocks + if (WebDataRocks) { // Перевіряємо, чи бібліотека завантажилась + const pivot = new WebDataRocks({ + container: "#wdr-component", + toolbar: true, // Дозволити панель інструментів + report: { + dataSource: transformedData, + slice: { + rows: [ + { uniqueName: "Категорія" }, + { uniqueName: "Питання" }, + { uniqueName: "Варіант" } + ], + columns: [ + { uniqueName: "[Measures]" } + ], + measures: [ + { uniqueName: "Голоси за варіант", aggregation: "sum" }, + { uniqueName: "Відсоток за варіант", aggregation: "average" }, // або sum, якщо відсотки вже в сумі + { uniqueName: "Всього голосів опитування", aggregation: "sum" } + ] + }, + options: { + // Додаткові опції звіту, наприклад: + grid: { + showGrandTotals: "on", // Показати загальні підсумки + showColumnGrandTotals: false // Можна вимкнути для колонок, якщо не потрібно + } + } + } + }); + } else { + console.error("WebDataRocks не завантажено."); + } +}); \ No newline at end of file diff --git a/style/style.css b/style/style.css index e69de29..06abd6a 100644 --- a/style/style.css +++ b/style/style.css @@ -0,0 +1,493 @@ +:root { + --blue--: #4378d8; + --blue--hover: #275cbe; + --border_color_white--: #e0e0e0; +} + +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; + background-color: #f5f5f5; + padding: 20px; + line-height: 1.6; +} + +.container { + margin: 0 auto; +} + +.create-poll-header { + background: var(--blue--); + color: white; + padding: 20px; + border-radius: 12px; + margin-bottom: 20px; + display: flex; + justify-content: space-between; + align-items: center; + box-shadow: 0 4px 12px rgba(0,0,0,0.25); +} + +.create-poll-btn { + background: var(--blue--); + border: var(--blue--); + color: white; + padding: 12px 24px; + border-radius: 8px; + cursor: pointer; + font-size: 30px; + font-weight: 500; + transition: all 0.3s ease; + text-decoration: none; +} + +.create-poll-btn:hover { + background: var(--blue--hover); + border: var(--blue--hover); + transform: translateY(-2px); +} + +.search-bar { + background: white; + border-radius: 12px; + border: 2px solid var(--border_color_white--); + padding: 4px; + margin-bottom: 20px; + box-shadow: 0 2px 8px rgba(0,0,0,0.1); + display: flex; + gap: 15px; +} + +.search-input { + flex: 1; + padding: 12px 20px; + border: none; + border-radius: 8px 0 0 8px; + font-size: 25px; + outline: none; + width: 70%; + background: transparent; +} + +.search-bar:focus-within { + border-color: var(--blue--); +} + +.category-dropdown { + padding: 12px; + border: none; + border-radius: 0 8px 8px 0; + font-size: 25px; + background: white; + cursor: pointer; + min-width: 150px; + width: 15%; + outline: none; +} + +.polls-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(400px, 1fr)); + gap: 20px; + margin-top: 20px; +} + +@media (max-width: 768px) { + .polls-grid { + grid-template-columns: 1fr; + gap: 15px; + } + + .container { + max-width: 100%; + padding: 0 10px; + } +} + +@media (min-width: 1400px) { + .polls-grid { + grid-template-columns: repeat(3, 1fr); + } +} + +.poll-card { + background: white; + border-radius: 12px; + border: 2px solid var(--border_color_white--); + padding: 24px; + margin-bottom: 20px; + box-shadow: 0 2px 8px rgba(0,0,0,0.1); + transition: transform 0.3s ease, box-shadow 0.3s ease; + text-decoration: none; + cursor: pointer; + display: flex; + flex-direction: column; +} + +.poll-card:hover { + transform: translateY(-2px); + box-shadow: 0 4px 16px rgba(0,0,0,0.15); +} + +.poll-title { + font-size: 25px; + font-weight: 600; + color: #333; + margin-bottom: 20px; +} + +.poll-option { + display: flex; + align-items: center; + margin-bottom: 12px; + cursor: pointer; + padding: 8px; + border-radius: 8px; + transition: background-color 0.3s ease; +} + +.option-label { + font-size: 20px; + font-weight: 500; + color: #333; + width: 120px; + flex-shrink: 0; + text-align: left; +} + +.progress-container { + flex: 1; + margin: 0 20px; + background-color: white; + border-radius: 20px; + height: 8px; + position: relative; + overflow: hidden; +} + +.progress-bar { + height: 100%; + background: linear-gradient(90deg, #4285f4, #5b9bd5); + border-radius: 20px; + transition: width 0.6s ease; + position: relative; +} + +.percentage { + font-size: 20px; + font-weight: 600; + color: black; + min-width: 50px; + text-align: right; +} + +.info { + display: flex; + justify-content: space-between; + align-items: center; + margin-top: auto; + padding-top: 15px; +} + +.category { + color: gray; + font-size: 20px; + margin-top: 15px; + font-weight: 500; +} + +.vote-count { + color: gray; + font-size: 20px; + margin-top: 15px; + font-weight: 500; +} + +/* Popup styles using CSS :target */ +.popup-overlay { + display: none; + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.5); + z-index: 1000; + justify-content: center; + align-items: center; +} + +.popup-overlay:target { + display: flex; + animation: fadeIn 0.3s ease; +} + +.popup-content { + background: white; + border-radius: 16px; + padding: 40px; + max-width: 500px; + width: 90%; + max-height: 90vh; + overflow-y: auto; + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2); + animation: slideIn 0.3s ease; + position: relative; +} + +.popup-close { + position: absolute; + top: 15px; + right: 20px; + background: none; + border: none; + font-size: 24px; + cursor: pointer; + color: #666; + padding: 5px; + text-decoration: none; + line-height: 1; +} + +.popup-close:hover { + color: #333; +} + +@keyframes fadeIn { + from { opacity: 0; } + to { opacity: 1; } +} + +@keyframes slideIn { + from { + opacity: 0; + transform: translateY(-50px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +/* Vote popup styles */ +.vote-popup h1 { + text-align: center; + margin-bottom: 40px; + font-size: 2.5rem; +} + +.vote-popup .question { + font-size: 1.5rem; + text-align: center; + margin-bottom: 40px; +} + +.vote-popup .options { + display: flex; + flex-direction: column; + gap: 20px; + margin-bottom: 40px; +} + +.vote-popup .option { + position: relative; +} + +.vote-popup .option input[type="radio"] { + position: absolute; + opacity: 0; + cursor: pointer; +} + +.vote-popup .option label { + display: flex; + align-items: center; + font-size: 1.25rem; + cursor: pointer; + padding: 15px 20px; + border-radius: 12px; + transition: all 0.3s ease; + border: 2px solid transparent; +} + +.vote-popup .option label:hover { + background-color: #f8f9fa; + border-color: #e9ecef; +} + +.vote-popup .option input[type="radio"]:checked + label { + background-color: #e3f2fd; + border-color: var(--blue--hover); + color: var(--blue--); +} + +.vote-popup .radio-button { + width: 24px; + height: 24px; + border: 2px solid #ccc; + border-radius: 50%; + margin-right: 15px; + position: relative; + transition: all 0.3s ease; +} + +.vote-popup .option input[type="radio"]:checked + label .radio-button { + border-color: var(--blue--hover); + background-color: var(--blue--hover); +} + +.vote-popup .option input[type="radio"]:checked + label .radio-button::after { + content: ''; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 8px; + height: 8px; + background-color: white; + border-radius: 50%; +} + +.vote-popup .vote-button { + width: 100%; + background-color: var(--blue--); + color: white; + border: none; + border-radius: 12px; + padding: 18px; + font-size: 20px; + font-weight: 500; + cursor: pointer; + transition: all 0.3s ease; +} + +.vote-popup .vote-button:hover { + background-color: var(--blue--hover); + transform: translateY(-2px); + box-shadow: 0 6px 20px rgba(33, 150, 243, 0.3); +} + +/* Create poll popup styles */ +.create-popup h1 { + text-align: center; + margin-bottom: 40px; + font-size: 2.5rem; +} + +.create-popup .form-group { + margin-bottom: 30px; +} + +.create-popup label { + display: block; + font-size: 25px; + color: #333; + margin-bottom: 12px; + font-weight: 500; +} + +.create-popup input[type="text"] { + width: 100%; + padding: 16px; + border: 2px solid var(--border_color_white--); + border-radius: 8px; + font-size: 20px; + transition: all 0.3s ease; +} + +.create-popup input[type="text"]:focus { + outline: none; + border-color: #4285f4; + background-color: white; +} + +.create-popup .options-container { + display: flex; + flex-direction: column; + gap: 12px; +} + +.create-popup .option-input { + width: 100%; + padding: 16px; + border: 2px solid #e0e0e0; + border-radius: 8px; + font-size: 20px; + background-color: #fafafa; + transition: all 0.3s ease; +} + +.create-popup .add-option { + color: var(--blue--); + font-size: 20px; + text-decoration: none; + margin-top: 8px; + display: inline-block; + font-weight: 500; +} + +.create-popup .add-option:hover { + text-decoration: underline; +} + +.create-popup .create-btn { + width: 100%; + padding: 16px; + background-color: var(--blue--); + color: white; + border: none; + border-radius: 8px; + font-size: 25px; + font-weight: 500; + cursor: pointer; + margin-top: 10px; + transition: background-color 0.3s ease; +} + +.create-popup .create-btn:hover { + background-color: var(--blue--hover); +} + +.create-popup .category-select { + width: 100%; + padding: 16px; + border: 2px solid var(--border_color_white--); + border-radius: 8px; + font-size: 20px; + background-color: white; + transition: all 0.3s ease; + background-position: right 16px center; + background-size: 16px; +} + +.create-popup .category-select:focus { + outline: none; + border-color: var(--blue--); + background-color: white; + box-shadow: 0 0 0 3px rgba(67, 120, 216, 0.1); +} + +.create-popup .category-select:hover { + border-color: var(--blue--hover); +} + +.create-popup .category-select option { + padding: 12px 16px; + font-size: 18px; + color: #333; + background-color: white; +} + +.create-popup .category-select option:hover { + background-color: #f8f9fa; +} + +.create-popup .category-select option:checked { + background-color: var(--blue--); + color: white; +} \ No newline at end of file