PHP Classes

File: templates/polls.twig

Recommend this page to a friend!
  Packages of Matthew Asham   Binkterm PHP   templates/polls.twig   Download  
File: templates/polls.twig
Role: Auxiliary data
Content type: text/plain
Description: Auxiliary data
Class: Binkterm PHP
Bulletin board system based on the Web
Author: By
Last change:
Date: 5 days ago
Size: 7,867 bytes
 

Contents

Class file image Download
{% extends "base.twig" %} {% block title %}{{ t('ui.polls.title', {}, locale, ['common']) }} - {{ parent() }}{% endblock %} {% block content %} <div class="row justify-content-center"> <div class="col-xl-8 col-lg-9"> <div class="card"> <div class="card-header d-flex justify-content-between align-items-center"> <h5 class="mb-0"> <i class="fas fa-vote-yea me-2"></i> {{ t('ui.polls.title', {}, locale, ['common']) }} </h5> <a href="/polls/create" class="btn btn-sm btn-primary"> <i class="fas fa-file-circle-plus me-1"></i> {{ t('ui.polls.create', {}, locale, ['common']) }} </a> </div> <div class="card-body" id="votingBoothBody"> <div class="text-center"> <i class="fas fa-spinner fa-spin"></i> {{ t('ui.polls.loading', {}, locale, ['common']) }} </div> </div> <div class="card-footer d-flex justify-content-between align-items-center"> <div class="small text-muted">{{ t('ui.polls.description', {}, locale, ['common']) }}</div> <div class="btn-group btn-group-sm" role="group"> <button type="button" class="btn btn-outline-secondary btn-sm" id="pollPrevBtn" onclick="changeVotingPoll(-1)" title="{{ t('ui.polls.previous', {}, locale, ['common']) }}"> <i class="fas fa-chevron-left"></i> </button> <button type="button" class="btn btn-outline-secondary btn-sm" id="pollNextBtn" onclick="changeVotingPoll(1)" title="{{ t('ui.polls.next', {}, locale, ['common']) }}"> <i class="fas fa-chevron-right"></i> </button> </div> </div> </div> </div> </div> {% endblock %} {% block scripts %} <script> let votingPolls = []; let votingPollIndex = 0; function apiError(payload, fallback) { if (window.getApiErrorMessage) { return window.getApiErrorMessage(payload, fallback); } return payload && payload.error ? payload.error : fallback; } function uiT(key, fallback, params = {}) { if (window.t) { return window.t(key, params, fallback); } return fallback; } $(document).ready(function() { loadI18nNamespaces(['common', 'errors']).then(function() { loadVotingBooth(); }); }); function loadVotingBooth(keepPollId = null) { $.get('/api/polls/active') .done(function(data) { votingPolls = data.polls || []; if (votingPolls.length === 0) { $('#votingBoothBody').html(`<p class="text-muted mb-0">${uiT('ui.dashboard.polls.none_active', 'No active polls right now.')}</p>`); updateVotingPollNav(); return; } if (keepPollId) { const idx = votingPolls.findIndex(p => p.id === keepPollId); votingPollIndex = idx >= 0 ? idx : 0; } else { const unansweredIndex = votingPolls.findIndex(p => !p.has_voted); votingPollIndex = unansweredIndex >= 0 ? unansweredIndex : 0; } renderVotingBooth(); }) .fail(function() { $('#votingBoothBody').html(`<p class="text-danger mb-0">${uiT('ui.dashboard.polls.load_failed', 'Failed to load poll.')}</p>`); updateVotingPollNav(); }); } function renderVotingBooth() { if (votingPolls.length === 0) { $('#votingBoothBody').html(`<p class="text-muted mb-0">${uiT('ui.dashboard.polls.none_active', 'No active polls right now.')}</p>`); updateVotingPollNav(); return; } const poll = votingPolls[votingPollIndex]; const hasVoted = !!poll.has_voted; if (hasVoted) { const totalVotes = poll.total_votes || 0; let resultsHtml = `<h5 class="mb-3">${escapeHtml(poll.question)}</h5>`; resultsHtml += `<div class="small text-muted mb-3">${uiT('ui.dashboard.polls.results', 'Results')}</div>`; if (totalVotes === 0) { resultsHtml += `<p class="text-muted mb-0">${uiT('ui.dashboard.polls.no_votes', 'No votes yet.')}</p>`; } else { (poll.results || []).forEach(result => { const percent = totalVotes > 0 ? Math.round((result.votes / totalVotes) * 100) : 0; resultsHtml += ` <div class="mb-3"> <div class="d-flex justify-content-between small"> <span>${escapeHtml(result.option_text)}</span> <span>${result.votes} (${percent}%)</span> </div> <div class="progress" style="height: 8px;"> <div class="progress-bar" role="progressbar" style="width: ${percent}%"></div> </div> </div> `; }); } $('#votingBoothBody').html(resultsHtml); updateVotingPollNav(); return; } let optionsHtml = `<h5 class="mb-3">${escapeHtml(poll.question)}</h5>`; optionsHtml += `<div class="small text-muted mb-3">${uiT('ui.dashboard.polls.vote_to_see_results', 'Vote to see results')}</div>`; optionsHtml += '<form id="votingBoothForm">'; (poll.options || []).forEach(option => { optionsHtml += ` <div class="form-check mb-3"> <input class="form-check-input" type="radio" name="poll_option" id="pollOption${option.id}" value="${option.id}"> <label class="form-check-label" for="pollOption${option.id}"> ${escapeHtml(option.option_text)} </label> </div> `; }); optionsHtml += ` <button type="button" class="btn btn-primary" onclick="submitPollVote(${poll.id})"> ${uiT('ui.dashboard.polls.submit_vote', 'Submit Vote')} </button> `; optionsHtml += '</form>'; $('#votingBoothBody').html(optionsHtml); updateVotingPollNav(); } function changeVotingPoll(delta) { if (votingPolls.length <= 1) { return; } const nextIndex = votingPollIndex + delta; if (nextIndex < 0 || nextIndex >= votingPolls.length) { return; } votingPollIndex = nextIndex; renderVotingBooth(); } function updateVotingPollNav() { const hasMultiple = votingPolls.length > 1; const prevBtn = document.getElementById('pollPrevBtn'); const nextBtn = document.getElementById('pollNextBtn'); if (!prevBtn || !nextBtn) { return; } prevBtn.disabled = !hasMultiple || votingPollIndex === 0; nextBtn.disabled = !hasMultiple || votingPollIndex === (votingPolls.length - 1); } function submitPollVote(pollId) { const selected = document.querySelector('input[name="poll_option"]:checked'); if (!selected) { alert(uiT('errors.polls.option_required', 'Please select an option.')); return; } fetch(`/api/polls/${pollId}/vote`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ option_id: selected.value }) }) .then(response => response.json()) .then(data => { if (data.success) { loadVotingBooth(pollId); } else { alert(apiError(data, uiT('errors.polls.vote_failed', 'Failed to submit vote.'))); } }) .catch(() => alert(uiT('errors.polls.vote_failed', 'Failed to submit vote.'))); } function escapeHtml(text) { const map = { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#039;' }; return String(text).replace(/[&<>"']/g, function(m) { return map[m]; }); } </script> {% endblock %}