diff --git a/app/assets/stylesheets/layout.scss b/app/assets/stylesheets/layout.scss index 56a1fe187..6a22e92b9 100644 --- a/app/assets/stylesheets/layout.scss +++ b/app/assets/stylesheets/layout.scss @@ -214,6 +214,8 @@ a { margin-bottom: $line-height / 2; li { + font-size: $base-font-size; + margin-bottom: 0; margin-right: $line-height / 2; @include breakpoint(medium) { @@ -316,13 +318,16 @@ a { } .tabs { - border: { - left: 0; - right: 0; - top: 0; - }; + border-left: 0; + border-right: 0; + border-top: 0; margin-bottom: $line-height; + .tabs-title { + font-size: $base-font-size; + margin-bottom: 0; + } + .tabs-title > a { color: $text-medium; margin-bottom: rem-calc(-1); @@ -376,6 +381,10 @@ a { box-shadow: none; } +.uppercase { + text-transform: uppercase; +} + // 02. Header // ---------- diff --git a/app/assets/stylesheets/participation.scss b/app/assets/stylesheets/participation.scss index 55645309f..81757d78c 100644 --- a/app/assets/stylesheets/participation.scss +++ b/app/assets/stylesheets/participation.scss @@ -8,6 +8,7 @@ // 06. Budget // 07. Proposals successful // 08. Polls +// 09. Polls results and stats // // 01. Votes and supports @@ -1799,3 +1800,59 @@ } } } + +// 09. Polls results and stats +// --------------------------- + +.polls-results-stats { + + .sidebar { + border-bottom: 1px solid $border; + margin-bottom: $line-height; + + @include breakpoint(medium) { + border-bottom: 0; + border-right: 1px solid $border; + } + + .menu { + padding: 0; + + li a { + color: $link; + line-height: $line-height; + } + } + } + + table { + table-layout: fixed; + + caption { + padding: $line-height / 2 0; + text-align: left; + } + + th { + text-align: left; + + &.win { + background: #009fde; + } + } + + td { + + &.win { + background: #ccedf8; + font-weight: bold; + } + } + } + + .number { + font-size: rem-calc(60); + font-weight: bold; + line-height: rem-calc(60); + } +} diff --git a/app/controllers/admin/poll/polls_controller.rb b/app/controllers/admin/poll/polls_controller.rb index 8ba6e934e..90a31192c 100644 --- a/app/controllers/admin/poll/polls_controller.rb +++ b/app/controllers/admin/poll/polls_controller.rb @@ -58,9 +58,10 @@ class Admin::Poll::PollsController < Admin::Poll::BaseController end def poll_params - params.require(:poll).permit(:name, :starts_at, :ends_at, :geozone_restricted, :summary, :description, - geozone_ids: [], - image_attributes: [:id, :title, :attachment, :cached_attachment, :user_id, :_destroy]) + params.require(:poll).permit(:name, :starts_at, :ends_at, :geozone_restricted, + :summary, :description, :results_enabled, :stats_enabled, + geozone_ids: [], + image_attributes: [:id, :title, :attachment, :cached_attachment, :user_id, :_destroy]) end def search_params diff --git a/app/controllers/admin/poll/shifts_controller.rb b/app/controllers/admin/poll/shifts_controller.rb index 89bb68999..1261f2951 100644 --- a/app/controllers/admin/poll/shifts_controller.rb +++ b/app/controllers/admin/poll/shifts_controller.rb @@ -6,6 +6,8 @@ class Admin::Poll::ShiftsController < Admin::Poll::BaseController def new load_shifts @shift = ::Poll::Shift.new + @voting_polls = @booth.polls.current_or_incoming + @recount_polls = @booth.polls.current_or_recounting_or_incoming end def create diff --git a/app/controllers/polls/questions_controller.rb b/app/controllers/polls/questions_controller.rb index 407e6d984..e1fc73805 100644 --- a/app/controllers/polls/questions_controller.rb +++ b/app/controllers/polls/questions_controller.rb @@ -13,6 +13,9 @@ class Polls::QuestionsController < ApplicationController answer.touch if answer.persisted? answer.save! answer.record_voter_participation(token) + @question.question_answers.where(question_id: @question).each do |answer| + answer.set_most_voted + end @answers_by_question_id = { @question.id => params[:answer] } end diff --git a/app/controllers/polls_controller.rb b/app/controllers/polls_controller.rb index a13bfd33d..708f68abb 100644 --- a/app/controllers/polls_controller.rb +++ b/app/controllers/polls_controller.rb @@ -25,8 +25,13 @@ class PollsController < ApplicationController @commentable = @poll @comment_tree = CommentTree.new(@commentable, params[:page], @current_order) - + end + + def stats @stats = Poll::Stats.new(@poll).generate end + def results + end + end diff --git a/app/helpers/shifts_helper.rb b/app/helpers/shifts_helper.rb index 3a55feb4f..7ea17baf2 100644 --- a/app/helpers/shifts_helper.rb +++ b/app/helpers/shifts_helper.rb @@ -1,10 +1,12 @@ module ShiftsHelper def shift_vote_collection_dates(booth, polls) + return [] if polls.blank? date_options((start_date(polls)..end_date(polls)), Poll::Shift.tasks[:vote_collection], booth) end def shift_recount_scrutiny_dates(booth, polls) + return [] if polls.blank? dates = polls.map(&:ends_at).map(&:to_date).sort.inject([]) do |total, date| initial_date = date < Date.current ? Date.current : date total << (initial_date..date + Poll::RECOUNT_DURATION).to_a diff --git a/app/models/abilities/administrator.rb b/app/models/abilities/administrator.rb index 704f9be24..1274229b5 100644 --- a/app/models/abilities/administrator.rb +++ b/app/models/abilities/administrator.rb @@ -56,7 +56,7 @@ module Abilities can [:index, :create, :edit, :update, :destroy], Geozone - can [:read, :create, :update, :destroy, :add_question, :search_booths, :search_officers, :booth_assignments], Poll + can [:read, :create, :update, :destroy, :add_question, :search_booths, :search_officers, :booth_assignments, :results, :stats], Poll can [:read, :create, :update, :destroy, :available], Poll::Booth can [:search, :create, :index, :destroy], ::Poll::Officer can [:create, :destroy, :manage], ::Poll::BoothAssignment diff --git a/app/models/abilities/everyone.rb b/app/models/abilities/everyone.rb index 73f3220ab..dd692c269 100644 --- a/app/models/abilities/everyone.rb +++ b/app/models/abilities/everyone.rb @@ -7,6 +7,12 @@ module Abilities can [:read, :map, :summary, :share], Proposal can :read, Comment can :read, Poll + can :results, Poll do |poll| + poll.expired? && poll.results_enabled? + end + can :stats, Poll do |poll| + poll.expired? && poll.stats_enabled? + end can :read, Poll::Question can [:read, :welcome], Budget can :read, SpendingProposal @@ -23,7 +29,6 @@ module Abilities can [:read], Legislation::Question can [:create], Legislation::Answer can [:search, :comments, :read, :create, :new_comment], Legislation::Annotation - can :read_stats, Poll end end end diff --git a/app/models/poll/booth.rb b/app/models/poll/booth.rb index 07e7d2456..b9cba45b1 100644 --- a/app/models/poll/booth.rb +++ b/app/models/poll/booth.rb @@ -12,7 +12,7 @@ class Poll end def self.available - where(polls: { id: Poll.current_or_incoming }).includes(:polls) + where(polls: { id: Poll.current_or_recounting_or_incoming }).includes(:polls) end def assignment_on_poll(poll) diff --git a/app/models/poll/question.rb b/app/models/poll/question.rb index 066d223d9..6be729757 100644 --- a/app/models/poll/question.rb +++ b/app/models/poll/question.rb @@ -55,4 +55,8 @@ class Poll::Question < ActiveRecord::Base where(poll_id: Poll.answerable_by(user).pluck(:id)) end + def answers_total_votes + question_answers.map { |a| Poll::Answer.where(question_id: self, answer: a.title).count }.sum + end + end diff --git a/app/models/poll/question/answer.rb b/app/models/poll/question/answer.rb index ccbcf1abd..dbf2f2139 100644 --- a/app/models/poll/question/answer.rb +++ b/app/models/poll/question/answer.rb @@ -31,4 +31,24 @@ class Poll::Question::Answer < ActiveRecord::Base def self.last_position(question_id) where(question_id: question_id).maximum('given_order') || 0 end + + def total_votes + Poll::Answer.where(question_id: question, answer: title).count + end + + def most_voted? + self.most_voted + end + + def total_votes_percentage + question.answers_total_votes == 0 ? 0 : (total_votes * 100) / question.answers_total_votes + end + + def set_most_voted + answers = question.question_answers + .map { |a| Poll::Answer.where(question_id: a.question, answer: a.title).count } + is_most_voted = !answers.any?{ |a| a > self.total_votes } + + self.update(most_voted: is_most_voted) + end end diff --git a/app/models/poll/stats.rb b/app/models/poll/stats.rb index 1df281434..ac45a2aae 100644 --- a/app/models/poll/stats.rb +++ b/app/models/poll/stats.rb @@ -16,110 +16,128 @@ class Poll end private - + def total_participants - total_participants_web + total_participants_booth + stats_cache('total_participants') { total_participants_web + total_participants_booth } end def total_participants_web - total_web_valid + total_web_white + total_web_null + stats_cache('total_participants_web') { total_web_valid + total_web_white + total_web_null } end - + def total_participants_web_percentage - (total_participants) == 0 ? 0 : total_participants_web * 100 / total_participants + stats_cache('total_participants_web_percentage') { + (total_participants) == 0 ? 0 : total_participants_web * 100 / total_participants + } end def total_participants_booth - voters.where(origin: 'booth').count + stats_cache('total_participants_booth') { voters.where(origin: 'booth').count } end - + def total_participants_booth_percentage - (total_participants) == 0 ? 0 : total_participants_booth * 100 / total_participants.to_f + stats_cache('total_participants_booth_percentage') { + (total_participants) == 0 ? 0 : total_participants_booth * 100 / total_participants.to_f + } end - + def total_web_valid - voters.where(origin: 'web').count + stats_cache('total_web_valid') { voters.where(origin: 'web').count } end - + def valid_percentage_web - (total_valid_votes) == 0 ? 0 : total_web_valid * 100 / total_valid_votes.to_f + stats_cache('valid_percentage_web') { + (total_valid_votes) == 0 ? 0 : total_web_valid * 100 / total_valid_votes.to_f + } end - + def total_web_white - 0 + stats_cache('total_web_white') { 0 } end - + def white_percentage_web - 0 + stats_cache('white_percentage_web') { 0 } end - + def total_web_null - 0 + stats_cache('total_web_null') { 0 } end - + def null_percentage_web - 0 + stats_cache('null_percentage_web') { 0 } end - + def total_booth_valid - recounts.sum(:total_amount) + stats_cache('total_booth_valid') { recounts.sum(:total_amount) } end - + def valid_percentage_booth - (total_valid_votes) == 0 ? 0 : total_booth_valid * 100 / total_valid_votes.to_f + stats_cache('valid_percentage_booth') { + (total_valid_votes) == 0 ? 0 : total_booth_valid * 100 / total_valid_votes.to_f + } end - - def total_booth_white - recounts.sum(:white_amount) + + def total_booth_white + stats_cache('total_booth_white') { recounts.sum(:white_amount) } end - + def white_percentage_booth - (total_white_votes) == 0 ? 0 : total_booth_white * 100 / total_white_votes.to_f + stats_cache('white_percentage_booth') { + (total_white_votes) == 0 ? 0 : total_booth_white * 100 / total_white_votes.to_f + } end - + def total_booth_null - recounts.sum(:null_amount) + stats_cache('total_booth_null') { recounts.sum(:null_amount) } end - + def null_percentage_booth - (total_null_votes == 0) ? 0 : total_booth_null * 100 / total_null_votes.to_f + stats_cache('null_percentage_booth') { + (total_null_votes == 0) ? 0 : total_booth_null * 100 / total_null_votes.to_f + } end - + def total_valid_votes - total_web_valid + total_booth_valid + stats_cache('total_valid_votes') { total_web_valid + total_booth_valid } end - + def total_valid_percentage - (total_participants) == 0 ? 0 : total_valid_votes * 100 / total_participants.to_f + stats_cache('total_valid_percentage'){ + (total_participants) == 0 ? 0 : total_valid_votes * 100 / total_participants.to_f + } end - + def total_white_votes - total_web_white + total_booth_white + stats_cache('total_white_votes') { total_web_white + total_booth_white } end - + def total_white_percentage - (total_participants) == 0 ? 0 : total_white_votes * 100 / total_participants.to_f + stats_cache('total_white_percentage') { + (total_participants) == 0 ? 0 : total_white_votes * 100 / total_participants.to_f + } end - + def total_null_votes - total_web_null + total_booth_null + stats_cache('total_null_votes') { total_web_null + total_booth_null } end - + def total_null_percentage - (total_participants) == 0 ? 0 : total_null_votes * 100 / total_participants.to_f + stats_cache('total_null_percentage') { + (total_participants) == 0 ? 0 : total_null_votes * 100 / total_participants.to_f + } end def voters - @poll.voters + stats_cache('voters') { @poll.voters } end - + def recounts - @poll.recounts + stats_cache('recounts') { @poll.recounts } end def stats_cache(key, &block) - Rails.cache.fetch("polls_stats/#{@poll.id}/#{key}/v7", &block) + Rails.cache.fetch("polls_stats/#{@poll.id}/#{key}", &block) end end -end \ No newline at end of file +end diff --git a/app/views/admin/poll/polls/_form.html.erb b/app/views/admin/poll/polls/_form.html.erb index aff19d9d7..c0439af7b 100644 --- a/app/views/admin/poll/polls/_form.html.erb +++ b/app/views/admin/poll/polls/_form.html.erb @@ -53,6 +53,17 @@ + <% if controller_name == "polls" && action_name == "edit" %> +