Merge branch 'master' into feature/1929#add_shift_task_usage

This commit is contained in:
Bertocq
2017-10-03 12:01:21 +02:00
92 changed files with 1683 additions and 317 deletions

Binary file not shown.

View File

@@ -62,4 +62,5 @@
<glyph glyph-name="expand" unicode="&#48;" d="M26 168l-26-158c0-2 1-5 3-7 0 0 0 0 0 0 2-2 5-3 7-3l158 27c3 0 6 3 7 6 1 3 0 7-3 9l-30 31 82 82c4 4 4 9 0 13l-57 57c-3 3-9 3-12 0l-83-83-31 31c-2 2-5 3-9 2-3-1-5-4-6-7z m460 176l26 158c0 2-1 5-3 7 0 0 0 0 0 0-2 2-5 3-7 3l-158-27c-3 0-6-3-7-6-1-3 0-7 3-9l30-31-82-82c-4-4-4-9 0-13l57-57c3-3 9-3 12 0l83 83 31-31c2-2 5-3 9-2 3 1 5 4 6 7z"/>
<glyph glyph-name="telegram" unicode="&#49;" d="M504 509c6-5 9-11 8-18l-73-439c-1-6-4-10-10-13-2-2-5-2-8-2-3 0-5 0-7 1l-130 53-69-84c-3-5-8-7-14-7-2 0-4 0-6 1-4 1-7 4-9 7-2 3-3 6-3 10l0 100 247 303-306-265-113 47c-7 2-10 7-11 15 0 8 3 14 9 17l476 274c2 2 5 3 9 3 4 0 7-1 10-3z"/>
<glyph glyph-name="instagram" unicode="&#50;" d="M426 105l0 185-39 0c4-12 6-25 6-38 0-24-6-46-18-66-13-20-29-36-50-48-21-12-44-18-69-18-37 0-69 13-96 39-27 26-40 57-40 93 0 13 2 26 6 38l-41 0 0-185c0-5 2-10 5-13 4-3 8-5 13-5l305 0c5 0 9 2 13 5 3 3 5 8 5 13z m-81 152c0 23-9 44-26 60-18 17-38 25-63 25-24 0-45-8-62-25-17-16-26-37-26-60 0-24 9-44 26-61 17-16 38-25 62-25 25 0 45 9 63 25 17 17 26 37 26 61z m81 103l0 47c0 5-2 10-6 14-4 4-8 6-14 6l-50 0c-5 0-10-2-14-6-4-4-5-9-5-14l0-47c0-6 1-10 5-14 4-4 9-6 14-6l50 0c6 0 10 2 14 6 4 4 6 8 6 14z m49 59l0-326c0-16-5-29-16-40-11-11-24-16-40-16l-326 0c-16 0-29 5-40 16-11 11-16 24-16 40l0 326c0 16 5 29 16 40 11 11 24 16 40 16l326 0c16 0 29-5 40-16 11-11 16-24 16-40z"/>
<glyph glyph-name="image" unicode="&#51;" d="M165 347c0-15-6-28-16-38-11-11-24-16-39-16-16 0-28 5-39 16-11 10-16 23-16 38 0 16 5 29 16 39 11 11 23 16 39 16 15 0 28-5 39-16 10-10 16-23 16-39z m292-109l0-128-402 0 0 55 91 91 46-46 146 147z m28 201l-458 0c-2 0-4-1-6-3-2-2-3-4-3-6l0-348c0-2 1-4 3-6 2-2 4-3 6-3l458 0c2 0 4 1 6 3 2 2 3 4 3 6l0 348c0 2-1 4-3 6-2 2-4 3-6 3z m45-9l0-348c0-12-4-23-13-32-9-9-20-13-32-13l-458 0c-12 0-23 4-32 13-9 9-13 20-13 32l0 348c0 12 4 23 13 32 9 9 20 13 32 13l458 0c12 0 23-4 32-13 9-9 13-20 13-32z"/>
</font></defs></svg>

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Binary file not shown.

View File

@@ -411,7 +411,7 @@ $maincontent-shadow: 0 0 10px rgba($black, 0.5);
$orbit-bullet-background: $medium-gray;
$orbit-bullet-background-active: $dark-gray;
$orbit-bullet-diameter: 1.2rem;
$orbit-bullet-diameter: 0.8rem;
$orbit-bullet-margin: 0.1rem;
$orbit-bullet-margin-top: 0.8rem;
$orbit-bullet-margin-bottom: 0.8rem;

View File

@@ -97,10 +97,6 @@
content: '\72';
}
.icon-documents::before {
content: '\68';
}
.icon-proposals::before {
content: '\68';
}
@@ -197,10 +193,6 @@
content: '\53';
}
.icon-image::before {
content: '\68';
}
.icon-notification::before {
content: '\6e';
}
@@ -264,3 +256,7 @@
.icon-instagram::before {
content: '\32';
}
.icon-image::before {
content: '\33';
}

View File

@@ -18,7 +18,8 @@
// 16. Flags
// 17. Activity
// 18. Banners
// 19. Documents
// 19. Recommended Section Home
// 20. Documents
//
// 01. Global styles
@@ -341,10 +342,30 @@ a {
background: $brand;
}
.truncate-horizontal-text {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
-o-text-overflow: ellipsis;
-ms-text-overflow: ellipsis;
}
.align-top {
vertical-align: top;
}
.aling-middle {
vertical-align: middle;
}
.table {
display: table;
}
.table-cell {
display: table-cell;
}
// 02. Header
// ----------
@@ -612,7 +633,7 @@ header {
text-align: left;
@include breakpoint(medium) {
margin-right: $line-height * 1.5;
margin-right: rem-calc(24);
}
&:hover {
@@ -2161,6 +2182,142 @@ table {
}
}
// 19. Recommended Section Home
// -----------
.home-page {
.push {
display: none;
}
}
.section-recommended {
padding: $line-height * 2 0;
h2 {
margin-bottom: $line-height * 2;
}
.debates,
.proposals,
.budget-investments {
@include breakpoint(medium) {
margin-bottom: 0;
}
@include breakpoint(small) {
margin-bottom: $line-height;
}
.button.hollow {
margin-top: rem-calc(15);
}
}
.card {
.card-section {
padding: $line-height 0;
max-width: 300px;
margin: 0 auto;
p {
font-size: rem-calc(15);
text-align: left;
}
}
.orbit {
height: 300px;
.orbit-wrapper {
max-height: 250px;
overflow: hidden;
position: relative;
}
.orbit-bullets {
@include orbit-bullets;
width: 100%;
}
}
}
.card .orbit .orbit-wrapper .truncate {
background: image-url('truncate.png');
background-repeat: repeat-x;
bottom: 0;
height: 20px;
position: absolute;
width: 100%;
}
.debates-inner {
border-top: 4px solid $debates;
}
.proposals-inner {
border-top: 4px solid $proposals;
}
.budget-investments-inner {
border-top: 4px solid $budget;
}
.debates-inner,
.proposals-inner,
.budget-investments-inner {
background: #fff;
max-height: 350px;
@include breakpoint(small) {
max-height: 400px;
}
h4 {
margin-top: $line-height;
margin-bottom: 0;
font-size: rem-calc(18);
min-height: 50px;
}
h5 {
font-size: rem-calc(14);
text-align: left;
}
}
.carousel-image {
.card .orbit {
height: 480px;
.orbit-wrapper {
max-height: 450px;
}
}
.debates-inner,
.proposals-inner,
.budget-investments-inner {
max-height: 500px;
@include breakpoint(small) {
max-height: 600px;
}
}
}
.carousel-image .orbit-wrapper img {
display: block;
@include breakpoint(small) {
margin: 0 auto;
}
}
}
// 19. Documents
.documents-list {
@@ -2218,6 +2375,5 @@ table {
}
}
}
}

View File

@@ -1,6 +1,7 @@
// Table of Contents
//
// 01. Logo
// 02. Orbit bullets
//
// 01. Logo
@@ -31,12 +32,40 @@
}
}
// 02. Orbit bullet
// ----------------
@mixin orbit-bullets {
@include disable-mouse-outline;
position: relative;
margin-top: $orbit-bullet-margin-top;
margin-bottom: $orbit-bullet-margin-bottom;
text-align: center;
button {
width: $orbit-bullet-diameter;
height: $orbit-bullet-diameter;
margin: $orbit-bullet-margin;
border-radius: 50%;
background-color: $orbit-bullet-background;
&:hover {
background-color: $orbit-bullet-background-active;
}
&.is-active {
background-color: $orbit-bullet-background-active;
}
}
}
// 02. Direct uploads
// ------------------
@mixin direct-uploads {
.cached-image {
max-width: 150px;
max-height: 150px;
max-width: rem-calc(150);
max-height: rem-calc(150);
}
.progress-bar-placeholder {
@@ -49,15 +78,23 @@
.document-attachment,
.image-attachment {
padding-left:0;
padding-left: 0;
p{
p {
margin-bottom: 0;
}
}
input.js-document-attachment,
input.js-image-attachment{
display: none;
.attachment-errors {
> .js-image-attachment,
> .js-document-attachment {
display: none;
~ .error {
display: inline-block;
}
}
}
}
@@ -96,4 +133,5 @@
.loading-bar.no-transition {
transition: none;
}
}

View File

@@ -255,7 +255,6 @@
.icon-debates,
.icon-proposals,
.icon-budget,
.icon-documents,
.icon-image {
font-size: rem-calc(50);
line-height: $line-height;
@@ -267,7 +266,6 @@
}
.icon-proposals,
.icon-documents,
.icon-image {
color: $proposals;
}
@@ -312,12 +310,10 @@
.budget-investment-new,
.proposal-form,
.proposal-edit,
.new_poll_question,
.edit_poll_question {
.poll-question-form {
@include direct-uploads;
}
// 03. Show participation
// ----------------------
@@ -358,8 +354,7 @@
width: rem-calc(48);
}
.edit-debate,
.edit-proposal {
.edit-debate {
margin-bottom: 0;
}
@@ -661,20 +656,17 @@
.proposals-list .proposal {
@include breakpoint(small) {
.no-image {
width: 100%;
max-width: 300px;
max-width: rem-calc(300);
margin: 0 auto;
}
.no-image::before {
content: '';
display: block;
padding-top: 100%;
}
h3 {
font-size: 1.3rem;
&::before {
content: '';
display: block;
padding-top: 100%;
}
}
.column:first-child {
@@ -683,19 +675,17 @@
}
@include breakpoint(medium) {
.panel {
padding: 0 0.75rem 0 0;
padding: 0 $line-height / 2 0 0;
.no-image {
height: 245px;
width: 140px;
height: 100%;
min-height: rem-calc(245);
width: rem-calc(140);
}
}
h3 {
font-size: 1.4rem;
}
.column:first-child {
overflow: hidden;
}
@@ -705,7 +695,7 @@
}
.column:last-child:not(:first-child) {
padding-top: 0.75rem;
padding-top: $line-height / 2;
}
img {
@@ -1701,9 +1691,13 @@
}
.section-title-divider {
border-bottom: 2px solid $brand;
color: $brand;
margin-bottom: $line-height;
border-bottom: 1px solid #eee;
color: #000;
margin: $line-height 0;
span {
border-bottom: 1px solid #000;
}
}
.poll-question {

View File

@@ -15,7 +15,7 @@ class Admin::Poll::BoothAssignmentsController < Admin::Poll::BaseController
end
def show
@booth_assignment = @poll.booth_assignments.includes(:total_results, :voters,
@booth_assignment = @poll.booth_assignments.includes(:recounts, :voters,
officer_assignments: [officer: [:user]]).find(params[:id])
@voters_by_date = @booth_assignment.voters.group_by {|v| v.created_at.to_date}
end

View File

@@ -18,7 +18,7 @@ class Admin::Poll::OfficerAssignmentsController < Admin::Poll::BaseController
@officer = ::Poll::Officer.includes(:user).find(officer_assignment_params[:officer_id])
@officer_assignments = ::Poll::OfficerAssignment.
joins(:booth_assignment).
includes(:total_results, booth_assignment: :booth).
includes(:recounts, booth_assignment: :booth).
where("officer_id = ? AND poll_booth_assignments.poll_id = ?", @officer.id, @poll.id).
order(:date)
end

View File

@@ -3,7 +3,7 @@ class Admin::Poll::RecountsController < Admin::Poll::BaseController
def index
@booth_assignments = @poll.booth_assignments.
includes(:booth, :total_results, :voters).
includes(:booth, :recounts, :voters).
order("poll_booths.name").
page(params[:page]).per(50)
end

View File

@@ -4,17 +4,22 @@ module CommentableActions
include Search
def index
@resources = @search_terms.present? ? resource_model.search(@search_terms) : resource_model.all
@resources = @advanced_search_terms.present? ? @resources.filter(@advanced_search_terms) : @resources
@resources = resource_model.all
@resources = @current_order == "recommendations" && current_user.present? ? @resources.recommendations(current_user) : @resources.for_render
@resources = @resources.search(@search_terms) if @search_terms.present?
@resources = @advanced_search_terms.present? ? @resources.filter(@advanced_search_terms) : @resources
@resources = @resources.tagged_with(@tag_filter) if @tag_filter
@resources = @resources.page(params[:page]).for_render.send("sort_by_#{@current_order}")
@resources = @resources.page(params[:page]).send("sort_by_#{@current_order}")
index_customization if index_customization.present?
@tag_cloud = tag_cloud
@banners = Banner.with_active
set_resource_votes(@resources)
set_resources_instance
end

View File

@@ -10,7 +10,7 @@ class DebatesController < ApplicationController
invisible_captcha only: [:create, :update], honeypot: :subtitle
has_orders %w{hot_score confidence_score created_at relevance}, only: :index
has_orders ->(c) { Debate.debates_orders(c.current_user) }, only: :index
has_orders %w{most_voted newest oldest}, only: :show
load_and_authorize_resource

View File

@@ -11,7 +11,7 @@ class NotificationsController < ApplicationController
def show
@notification = current_user.notifications.find(params[:id])
redirect_to url_for(@notification.linkable_resource)
redirect_to linkable_resource_path(@notification)
end
def mark_all_as_read
@@ -25,4 +25,13 @@ class NotificationsController < ApplicationController
@notification.mark_as_read
end
def linkable_resource_path(notification)
case notification.linkable_resource.class.name
when "Budget::Investment"
budget_investment_path @notification.linkable_resource.budget, @notification.linkable_resource
else
url_for @notification.linkable_resource
end
end
end

View File

@@ -26,9 +26,7 @@ class Officing::ResultsController < Officing::BaseController
@partial_results = ::Poll::PartialResult.includes(:question).
where(booth_assignment_id: index_params[:booth_assignment_id]).
where(date: index_params[:date])
@whites = ::Poll::WhiteResult.where(booth_assignment_id: @booth_assignment.id, date: index_params[:date]).sum(:amount)
@nulls = ::Poll::NullResult.where(booth_assignment_id: @booth_assignment.id, date: index_params[:date]).sum(:amount)
@total = ::Poll::TotalResult.where(booth_assignment_id: @booth_assignment.id, date: index_params[:date]).sum(:amount)
@recounts = ::Poll::Recount.where(booth_assignment_id: @booth_assignment.id, date: index_params[:date])
end
end
@@ -52,14 +50,14 @@ class Officing::ResultsController < Officing::BaseController
go_back_to_new if question.blank?
results.each_pair do |answer_index, count|
next unless count.present?
next if count.blank?
answer = question.valid_answers[answer_index.to_i]
go_back_to_new if question.blank?
partial_result = ::Poll::PartialResult.find_or_initialize_by(booth_assignment_id: @officer_assignment.booth_assignment_id,
date: results_params[:date],
question_id: question_id,
answer: answer)
date: results_params[:date],
question_id: question_id,
answer: answer)
partial_result.officer_assignment_id = @officer_assignment.id
partial_result.amount = count.to_i
partial_result.author = current_user
@@ -68,45 +66,21 @@ class Officing::ResultsController < Officing::BaseController
end
end
build_white_results
build_null_results
build_total_results
build_recounts
end
def build_white_results
if results_params[:whites].present?
white_result = ::Poll::WhiteResult.find_or_initialize_by(booth_assignment_id: @officer_assignment.booth_assignment_id,
date: results_params[:date])
white_result.officer_assignment_id = @officer_assignment.id
white_result.amount = results_params[:whites].to_i
white_result.author = current_user
white_result.origin = 'booth'
@results << white_result
end
end
def build_null_results
if results_params[:nulls].present?
null_result = ::Poll::NullResult.find_or_initialize_by(booth_assignment_id: @officer_assignment.booth_assignment_id,
date: results_params[:date])
null_result.officer_assignment_id = @officer_assignment.id
null_result.amount = results_params[:nulls].to_i
null_result.author = current_user
null_result.origin = 'booth'
@results << null_result
end
end
def build_total_results
if results_params[:total].present?
total_result = ::Poll::TotalResult.find_or_initialize_by(booth_assignment_id: @officer_assignment.booth_assignment_id,
date: results_params[:date])
total_result.officer_assignment_id = @officer_assignment.id
total_result.amount = results_params[:total].to_i
total_result.author = current_user
total_result.origin = 'booth'
@results << total_result
def build_recounts
recount = ::Poll::Recount.find_or_initialize_by(booth_assignment_id: @officer_assignment.booth_assignment_id,
date: results_params[:date])
recount.officer_assignment_id = @officer_assignment.id
recount.author = current_user
recount.origin = 'booth'
[:whites, :nulls, :total].each do |recount_type|
if results_params[recount_type].present?
recount["#{recount_type.to_s.singularize}_amount"] = results_params[recount_type].to_i
end
end
@results << recount
end
def go_back_to_new(alert = nil)

View File

@@ -12,7 +12,8 @@ class Officing::VotersController < Officing::BaseController
@voter = Poll::Voter.new(document_type: @user.document_type,
document_number: @user.document_number,
user: @user,
poll: @poll)
poll: @poll,
origin: "booth")
@voter.save!
end

View File

@@ -9,7 +9,7 @@ class ProposalsController < ApplicationController
invisible_captcha only: [:create, :update], honeypot: :subtitle
has_orders %w{hot_score confidence_score created_at relevance archival_date}, only: :index
has_orders ->(c) { Proposal.proposals_orders(c.current_user) }, only: :index
has_orders %w{most_voted newest oldest}, only: :show
load_and_authorize_resource
@@ -113,7 +113,7 @@ class ProposalsController < ApplicationController
end
def load_featured
return unless !@advanced_search_terms && @search_terms.blank? && @tag_filter.blank? && params[:retired].blank?
return unless !@advanced_search_terms && @search_terms.blank? && @tag_filter.blank? && params[:retired].blank? && @current_order != "recommendations"
@featured_proposals = Proposal.not_archived.sort_by_confidence_score.limit(3)
if @featured_proposals.present?
set_featured_proposal_votes(@featured_proposals)

View File

@@ -1,12 +1,10 @@
class WelcomeController < ApplicationController
skip_authorization_check
before_action :set_user_recommendations, only: :index, if: :current_user
layout "devise", only: [:welcome, :verification]
def index
if current_user
redirect_to :proposals
end
end
def welcome
@@ -16,4 +14,11 @@ class WelcomeController < ApplicationController
redirect_to verification_path if signed_in?
end
private
def set_user_recommendations
@recommended_debates = Debate.recommendations(current_user).sort_by_recommendations.limit(3)
@recommended_proposals = Proposal.recommendations(current_user).sort_by_recommendations.limit(3)
end
end

View File

@@ -4,4 +4,12 @@ module DebatesHelper
Debate.all.featured.count > 0
end
end
def empty_recommended_debates_message_text(user)
if user.interests.any?
t('debates.index.recommendations.without_results')
else
t('debates.index.recommendations.without_interests')
end
end
end

View File

@@ -1,7 +1,7 @@
module PollRecountsHelper
def total_recounts_by_booth(booth_assignment)
booth_assignment.total_results.any? ? booth_assignment.total_results.to_a.sum(&:amount) : nil
booth_assignment.recounts.any? ? booth_assignment.recounts.to_a.sum(&:total_amount) : nil
end
end

View File

@@ -32,6 +32,14 @@ module ProposalsHelper
Proposal::RETIRE_OPTIONS.collect { |option| [ t("proposals.retire_options.#{option}"), option ] }
end
def empty_recommended_proposals_message_text(user)
if user.interests.any?
t('proposals.index.recommendations.without_results')
else
t('proposals.index.recommendations.without_interests')
end
end
def author_of_proposal?(proposal)
author_of?(proposal, current_user)
end
@@ -40,4 +48,4 @@ module ProposalsHelper
current_user && proposal.editable_by?(current_user)
end
end
end

View File

@@ -0,0 +1,62 @@
module WelcomeHelper
def active_class(index)
"is-active is-in" if index == 0
end
def slide_display(index)
"display: none;" if index > 0
end
def recommended_path(recommended)
case recommended.class.name
when "Debate"
debate_path(recommended)
when "Proposal"
proposal_path(recommended)
else
'#'
end
end
def render_recommendation_image(recommended, image_default)
image_path = calculate_image_path(recommended, image_default)
image_tag(image_path) if image_path.present?
end
def calculate_image_path(recommended, image_default)
if recommended.try(:image) && recommended.image.present? && recommended.image.attachment.exists?
recommended.image.attachment.send("url", :medium)
elsif image_default.present?
image_default
end
end
def calculate_carousel_size(debates, proposals, apply_offset)
offset = calculate_offset(debates, proposals, apply_offset)
centered = calculate_centered(debates, proposals)
"#{offset if offset} #{centered if centered}"
end
def calculate_centered(debates, proposals)
if (debates.blank? && proposals.any?) ||
(debates.any? && proposals.blank?)
centered = "medium-centered large-centered"
end
end
def calculate_offset(debates, proposals, apply_offset)
if (debates.any? && proposals.any?)
if apply_offset
offset = "medium-offset-2 large-offset-2"
else
offset = "end"
end
end
end
def highlight_background
(feature?("user.recommendations") && current_user) ? "highlight" : ""
end
end

View File

@@ -45,7 +45,7 @@ module Abilities
can [:read, :update, :valuate, :destroy, :summary], SpendingProposal
can [:index, :read, :new, :create, :update, :destroy, :calculate_winners], Budget
can [:index, :read, :new, :create, :update, :destroy, :calculate_winners, :read_results], Budget
can [:read, :create, :update, :destroy], Budget::Group
can [:read, :create, :update, :destroy], Budget::Heading
can [:hide, :update, :toggle_selection], Budget::Investment

View File

@@ -4,6 +4,10 @@ module Followable
included do
has_many :follows, as: :followable, dependent: :destroy
has_many :followers, through: :follows, source: :user
scope :followed_by_user, -> (user){
joins(:follows).where("follows.user_id = ?", user.id)
}
end
def followed_by?(user)

View File

@@ -37,14 +37,21 @@ class Debate < ActiveRecord::Base
scope :sort_by_random, -> { reorder("RANDOM()") }
scope :sort_by_relevance, -> { all }
scope :sort_by_flags, -> { order(flags_count: :desc, updated_at: :desc) }
scope :sort_by_recommendations, -> { order(cached_votes_total: :desc) }
scope :last_week, -> { where("created_at >= ?", 7.days.ago)}
scope :featured, -> { where("featured_at is not null")}
scope :public_for_api, -> { all }
# Ahoy setup
visitable # Ahoy will automatically assign visit_id on create
attr_accessor :link_required
def self.recommendations(user)
tagged_with(user.interests, any: true).
where("author_id != ?", user.id)
end
def searchable_values
{ title => 'A',
author.username => 'B',
@@ -135,4 +142,9 @@ class Debate < ActiveRecord::Base
featured_at.present?
end
def self.debates_orders(user)
orders = %w{hot_score confidence_score created_at relevance}
orders << "recommendations" if user.present?
orders
end
end

View File

@@ -2,9 +2,7 @@ class Poll < ActiveRecord::Base
has_many :booth_assignments, class_name: "Poll::BoothAssignment"
has_many :booths, through: :booth_assignments
has_many :partial_results, through: :booth_assignments
has_many :white_results, through: :booth_assignments
has_many :null_results, through: :booth_assignments
has_many :total_results, through: :booth_assignments
has_many :recounts, through: :booth_assignments
has_many :voters
has_many :officer_assignments, through: :booth_assignments
has_many :officers, through: :officer_assignments
@@ -61,6 +59,10 @@ class Poll < ActiveRecord::Base
voters.where(document_number: document_number, document_type: document_type).exists?
end
def voted_in_booth?(user)
Poll::Voter.where(poll: self, user: user, origin: "booth").exists?
end
def date_range
unless starts_at.present? && ends_at.present? && starts_at <= ends_at
errors.add(:starts_at, I18n.t('errors.messages.invalid_date_range'))

View File

@@ -8,12 +8,13 @@ class Poll::Answer < ActiveRecord::Base
validates :question, presence: true
validates :author, presence: true
validates :answer, presence: true
validates :answer, inclusion: {in: ->(a) { a.question.valid_answers }}
validates :answer, inclusion: { in: ->(a) { a.question.valid_answers }},
unless: ->(a) { a.question.blank? }
scope :by_author, ->(author_id) { where(author_id: author_id) }
scope :by_question, ->(question_id) { where(question_id: question_id) }
def record_voter_participation
Poll::Voter.create!(user: author, poll: poll)
Poll::Voter.find_or_create_by!(user: author, poll: poll, origin: "web")
end
end

View File

@@ -7,8 +7,6 @@ class Poll
has_many :officers, through: :officer_assignments
has_many :voters
has_many :partial_results
has_many :white_results
has_many :null_results
has_many :total_results
has_many :recounts
end
end

View File

@@ -3,9 +3,7 @@ class Poll
belongs_to :officer
belongs_to :booth_assignment
has_many :partial_results
has_many :white_results
has_many :null_results
has_many :total_results
has_many :recounts
has_many :voters
validates :officer_id, presence: true

View File

@@ -0,0 +1,36 @@
class Poll::Recount < ActiveRecord::Base
VALID_ORIGINS = %w{web booth letter}.freeze
belongs_to :author, -> { with_hidden }, class_name: 'User', foreign_key: 'author_id'
belongs_to :booth_assignment
belongs_to :officer_assignment
validates :author, presence: true
validates :origin, inclusion: {in: VALID_ORIGINS}
scope :web, -> { where(origin: 'web') }
scope :booth, -> { where(origin: 'booth') }
scope :letter, -> { where(origin: 'letter') }
scope :by_author, ->(author_id) { where(author_id: author_id) }
before_save :update_logs
def update_logs
amounts_changed = false
[:white, :null, :total].each do |amount|
next unless send("#{amount}_amount_changed?") && send("#{amount}_amount_was").present?
self["#{amount}_amount_log"] += ":#{send("#{amount}_amount_was")}"
amounts_changed = true
end
update_officer_author if amounts_changed
end
def update_officer_author
self.officer_assignment_id_log += ":#{officer_assignment_id_was}"
self.author_id_log += ":#{author_id_was}"
end
end

View File

@@ -1,5 +1,8 @@
class Poll
class Voter < ActiveRecord::Base
VALID_ORIGINS = %w{ web booth }
belongs_to :poll
belongs_to :user
belongs_to :geozone
@@ -10,9 +13,13 @@ class Poll
validates :user_id, presence: true
validates :document_number, presence: true, uniqueness: { scope: [:poll_id, :document_type], message: :has_voted }
validates :origin, inclusion: { in: VALID_ORIGINS }
before_validation :set_demographic_info, :set_document_info
scope :web, -> { where(origin: 'web') }
scope :booth, -> { where(origin: 'booth') }
def set_demographic_info
return if user.blank?

View File

@@ -58,14 +58,27 @@ class Proposal < ActiveRecord::Base
scope :sort_by_relevance, -> { all }
scope :sort_by_flags, -> { order(flags_count: :desc, updated_at: :desc) }
scope :sort_by_archival_date, -> { archived.sort_by_confidence_score }
scope :sort_by_recommendations, -> { order(cached_votes_up: :desc) }
scope :archived, -> { where("proposals.created_at <= ?", Setting["months_to_archive_proposals"].to_i.months.ago) }
scope :not_archived, -> { where("proposals.created_at > ?", Setting["months_to_archive_proposals"].to_i.months.ago) }
scope :last_week, -> { where("proposals.created_at >= ?", 7.days.ago)}
scope :retired, -> { where.not(retired_at: nil) }
scope :not_retired, -> { where(retired_at: nil) }
scope :successful, -> { where("cached_votes_up >= ?", Proposal.votes_needed_for_success) }
scope :unsuccessful, -> { where("cached_votes_up < ?", Proposal.votes_needed_for_success) }
scope :public_for_api, -> { all }
def self.recommendations(user)
tagged_with(user.interests, any: true).
where("author_id != ?", user.id).
unsuccessful.
not_followed_by_user(user)
end
def self.not_followed_by_user(user)
where.not(id: followed_by_user(user).pluck(:id))
end
def to_param
"#{id}-#{title}".parameterize
end
@@ -185,6 +198,12 @@ class Proposal < ActiveRecord::Base
(voters + followers).uniq
end
def self.proposals_orders(user)
orders = %w{hot_score confidence_score created_at relevance archival_date}
orders << "recommendations" if user.present?
orders
end
protected
def set_responsible_name

View File

@@ -88,12 +88,12 @@ class User < ActiveRecord::Base
end
def debate_votes(debates)
voted = votes.for_debates(debates)
voted = votes.for_debates(Array(debates).map(&:id))
voted.each_with_object({}) { |v, h| h[v.votable_id] = v.value }
end
def proposal_votes(proposals)
voted = votes.for_proposals(proposals)
voted = votes.for_proposals(Array(proposals).map(&:id))
voted.each_with_object({}) { |v, h| h[v.votable_id] = v.value }
end

View File

@@ -43,8 +43,8 @@
<td><%= l(officer_assignment.date.to_date) %></td>
<td><%= booth_name_with_location(officer_assignment.booth_assignment.booth) %></td>
<td class="text-right">
<% if officer_assignment.total_results.any? %>
<%= officer_assignment.total_results.to_a.sum(&:amount) %>
<% if officer_assignment.recounts.any? %>
<%= officer_assignment.recounts.to_a.sum(&:total_amount) %>
<% else %>
<span>-</span>
<% end %>

View File

@@ -2,4 +2,6 @@
<h2><%= t("admin.questions.edit.title") %></h2>
<%= render "form", form_url: admin_question_path(@question) %>
<div class="poll-question-form">
<%= render "form", form_url: admin_question_path(@question) %>
</div>

View File

@@ -2,4 +2,6 @@
<h2><%= t("admin.questions.new.title") %></h2>
<%= render "form", form_url: admin_questions_path %>
<div class="poll-question-form">
<%= render "form", form_url: admin_questions_path %>
</div>

View File

@@ -18,9 +18,9 @@
</thead>
<tbody>
<tr>
<td id="white_results"><%= @poll.white_results.sum(:amount) %></td>
<td id="null_results"><%= @poll.null_results.sum(:amount) %></td>
<td id="total_results"><%= @poll.total_results.sum(:amount) %></td>
<td id="white_results"><%= @poll.recounts.sum(:white_amount) %></td>
<td id="null_results"><%= @poll.recounts.sum(:null_amount) %></td>
<td id="total_results"><%= @poll.recounts.sum(:total_amount) %></td>
</tr>
</tbody>
</table>

View File

@@ -1,13 +1,15 @@
<div id="<%= dom_id(investment) %>" class="budget-investment clear">
<div class="panel">
<div class="row">
<div class="row" data-equalizer>
<div class="small-12 medium-3 large-2 column">
<% if investment.image.present? %>
<%= image_tag investment.image_url(:thumb), alt: investment.image.title %>
<% else %>
<div class="no-image"></div>
<% end %>
<div data-equalizer-watch>
<% if investment.image.present? %>
<%= image_tag investment.image_url(:thumb), alt: investment.image.title %>
<% else %>
<div class="no-image"></div>
<% end %>
</div>
</div>
<div class="small-12 medium-6 large-7 column">
@@ -53,7 +55,7 @@
<% if investment.should_show_votes? %>
<div id="<%= dom_id(investment) %>_votes"
class="small-12 medium-3 column text-center">
class="small-12 medium-3 column text-center" data-equalizer-watch>
<%= render partial: '/budgets/investments/votes', locals: {
investment: investment,
investment_votes: investment_votes,
@@ -62,7 +64,7 @@
</div>
<% elsif investment.should_show_vote_count? %>
<div id="<%= dom_id(investment) %>_votes"
class="small-12 medium-3 column text-center">
class="small-12 medium-3 column text-center" data-equalizer-watch>
<div class="supports js-participation">
<span class="total-supports no-button">
<%= t("budgets.investments.investment.supports",
@@ -72,7 +74,7 @@
</div>
<% elsif investment.should_show_ballots? %>
<div id="<%= dom_id(investment) %>_ballot"
class="small-12 medium-3 column text-center">
class="small-12 medium-3 column text-center" data-equalizer-watch>
<%= render partial: '/budgets/investments/ballot', locals: {
investment: investment,
investment_ids: investment_ids,
@@ -81,11 +83,13 @@
</div>
<% elsif investment.should_show_price? %>
<div id="<%= dom_id(investment) %>_price"
class="supports small-12 medium-3 column text-center">
class="supports small-12 medium-3 column text-center" data-equalizer-watch>
<p class="investment-project-amount margin-top">
<%= investment.formatted_price %>
</p>
</div>
<% else %>
<div data-equalizer-watch></div>
<% end %>
<% end %>

View File

@@ -36,7 +36,7 @@
<% end %>
<% end %>
<% if @budget.finished? %>
<% if @budget.finished? || (@budget.balloting? && can?(:read_results, @budget)) %>
<%= link_to t("budgets.show.see_results"),
budget_results_path(@budget, heading_id: @budget.headings.first),
class: "button margin-top expanded" %>

View File

@@ -54,7 +54,11 @@
<%= link_to t("debates.index.start_debate"), new_debate_path, class: 'button expanded' %>
</div>
<%= render @debates %>
<% if @debates.any? || current_user.blank? %>
<%= render @debates %>
<% else %>
<%= empty_recommended_debates_message_text(current_user) %>
<% end %>
<%= paginate @debates %>
<% unless @search_terms || @advanced_search_terms || @tag_filter %>

View File

@@ -20,7 +20,4 @@
<div id="max-documents-notice" class="max-documents-notice callout warning text-center <%= "hide" unless max_documents_allowed?(documentable) %>">
<%= t "documents.max_documents_allowed_reached_html" %>
</div>
<hr>
</div>
</div>

View File

@@ -28,4 +28,5 @@
<div class="progress-bar-placeholder"><div class="loading-bar"></div></div>
</div>
<hr>
</div>

View File

@@ -1,12 +1,10 @@
<div>
<%= f.label :image, t("images.form.title") %>
<p class="help-text"><%= imageables_note(imageable) %></p>
<%= f.label :image, t("images.form.title") %>
<p class="help-text"><%= imageables_note(imageable) %></p>
<div id="nested-image">
<%= f.fields_for :image do |image_builder| %>
<%= render 'images/image_fields', f: image_builder, imageable: imageable %>
<% end %>
</div>
<div id="nested-image">
<%= f.fields_for :image do |image_builder| %>
<%= render 'images/image_fields', f: image_builder, imageable: imageable %>
<% end %>
</div>
<%= link_to_add_association t('images.form.add_new_image'), f, :image,
@@ -21,5 +19,3 @@
association_insertion_node: "#nested-image",
association_insertion_method: "append"
} %>
<hr>

View File

@@ -23,7 +23,7 @@
<%= setting['per_page_code_head'].try(:html_safe) %>
</head>
<body>
<body class="<%= yield (:body_class) %>">
<%= setting['per_page_code_body'].try(:html_safe) %>
<h1 class="show-for-sr"><%= setting['org_name'] %></h1>

View File

@@ -21,9 +21,9 @@
</thead>
<tbody>
<tr>
<td id="white_results"><%= @whites %></td>
<td id="null_results"><%= @nulls %></td>
<td id="total_results"><%= @total %></td>
<td id="white_results"><%= @recounts.sum(:white_amount) %></td>
<td id="null_results"><%= @recounts.sum(:null_amount) %></td>
<td id="total_results"><%= @recounts.sum(:total_amount) %></td>
</tr>
</tbody>
</table>

View File

@@ -33,23 +33,30 @@
<span class="show-for-sr"><%= t("polls.index.cant_answer") %></span>
</div>
<% end %>
<div class="row">
<div class="small-12 column">
<div class="dates"><%= poll_dates(poll) %></div>
<div class="row" data-equalizer>
<div class="small-12 medium-3 column" data-equalizer-watch>
<!-- PENDING TO REPLACE THIS BLOCK WITH POLL MAIN IMAGE -->
<div style="background: #eee; width: 100%; height: 100%; display: block; margin: -12px;">&nbsp;</div>
<!-- /. PENDING TO REPLACE THIS BLOCK WITH POLL MAIN IMAGE -->
</div>
<div class="small-12 medium-6 column" data-equalizer-watch>
<div class="dates"></div>
<% if poll.questions.count == 1 %>
<% poll.questions.each do |question| %>
<h4 class="inline-block"><%= link_to question.title, poll %></h4>
<h4><%= link_to question.title, poll %></h4>
<%= poll_dates(poll) %>
<% end %>
<% else %>
<h4 class="inline-block"><%= link_to poll.name, poll %></h4>
<ul>
<h4><%= link_to poll.name, poll %></h4>
<%= poll_dates(poll) %>
<ul class="margin-top">
<% poll.questions.each do |question| %>
<li><%= link_to question.title, question_path(question) %></li>
<% end %>
</ul>
<% end %>
<% if poll.geozones.any? %>
<p class="inline-block">
<p>
<small><%= t("polls.index.geozone_info") %></small>
</p>
<% end %>
@@ -59,16 +66,18 @@
<% end %>
</ul>
</div>
<div class="small-12 medium-6 large-4 column end">
<%= link_to poll, class: "button expanded" do %>
<% if poll.expired? %>
<%= t("polls.index.participate_button_expired") %>
<% elsif poll.incoming? %>
<%= t("polls.index.participate_button_incoming") %>
<% else %>
<%= t("polls.index.participate_button") %>
<div class="small-12 medium-3 column table" data-equalizer-watch>
<div class="table-cell aling-middle">
<%= link_to poll, class: "button hollow expanded" do %>
<% if poll.expired? %>
<%= t("polls.index.participate_button_expired") %>
<% elsif poll.incoming? %>
<%= t("polls.index.participate_button_incoming") %>
<% else %>
<%= t("polls.index.participate_button") %>
<% end %>
<% end %>
<% end %>
</div>
</div>
</div>
</div>

View File

@@ -6,18 +6,22 @@
<%= render "shared/section_header", i18n_namespace: "polls.index.section_header", image: "polls" %>
<div class="row">
<div class="small-12 medium-9 column">
<div class="small-12 column">
<%= render 'shared/filter_subnav', i18n_namespace: "polls.index" %>
<% polls_by_geozone_restriction = @polls.group_by(&:geozone_restricted) %>
<% if polls_by_geozone_restriction[false].present? %>
<h3 class="section-title-divider"><%= t("polls.index.no_geozone_restricted") %></h3>
<h3 class="section-title-divider">
<span><%= t("polls.index.no_geozone_restricted") %></span>
</h3>
<%= render partial: 'poll_group', locals: {poll_group: polls_by_geozone_restriction[false]} %>
<% end %>
<% if polls_by_geozone_restriction[true].present? %>
<h3 class="section-title-divider"><%= t("polls.index.geozone_restricted") %></h3>
<h3 class="section-title-divider">
<span><%= t("polls.index.geozone_restricted") %></span>
</h3>
<%= render partial: 'poll_group', locals: {poll_group: polls_by_geozone_restriction[true]} %>
<% end %>

View File

@@ -31,8 +31,14 @@
<div class="row margin-top">
<div class="small-12 medium-9 column">
<% @questions.each do |question| %>
<%= render 'polls/questions/question', question: question %>
<% if @poll.voted_in_booth?(current_user) %>
<div class="callout warning">
<%= t("polls.show.already_voted_in_booth") %>
</div>
<% else %>
<% @questions.each do |question| %>
<%= render 'polls/questions/question', question: question %>
<% end %>
<% end %>
</div>

View File

@@ -3,14 +3,16 @@
data-type="proposal">
<div class="panel">
<div class="icon-successful"></div>
<div class="row">
<div class="row" data-equalizer>
<div class="small-12 medium-3 large-2 column">
<% if proposal.image.present? %>
<%= image_tag proposal.image_url(:thumb), alt: proposal.image.title %>
<% else %>
<div class="no-image"></div>
<% end %>
<div data-equalizer-watch>
<% if proposal.image.present? %>
<%= image_tag proposal.image_url(:thumb), alt: proposal.image.title %>
<% else %>
<div class="no-image"></div>
<% end %>
</div>
</div>
<div class="small-12 medium-6 large-7 column">
@@ -58,7 +60,7 @@
</div>
</div>
<div id="<%= dom_id(proposal) %>_votes" class="small-12 medium-3 column supports-container">
<div id="<%= dom_id(proposal) %>_votes" class="small-12 medium-3 column supports-container" data-equalizer-watch>
<% if proposal.successful? %>
<div class="padding text-center">

View File

@@ -14,13 +14,13 @@
<div class="row">
<div class="small-12 column">
<% if @search_terms || @advanced_search_terms %>
<h2><%= t("shared.search_results") %></h2>
<p>
<%= page_entries_info @proposals %>
<% if !@advanced_search_terms %>
<%= t("proposals.index.search_results_html", count: @proposals.size, search_term: @search_terms) %>
<% end %>
<p>
<h2><%= t("shared.search_results") %></h2>
<p>
<%= page_entries_info @proposals %>
<% if !@advanced_search_terms %>
<%= t("proposals.index.search_results_html", count: @proposals.size, search_term: @search_terms) %>
<% end %>
<p>
<% elsif @tag_filter %>
<h2><%= t("shared.search_results") %></h2>
<p>
@@ -68,7 +68,11 @@
<% end %>
<div id="proposals-list">
<%= render partial: 'proposals/proposal', collection: @proposals %>
<% if @proposals.any? || current_user.blank? %>
<%= render partial: 'proposals/proposal', collection: @proposals %>
<% else %>
<%= empty_recommended_proposals_message_text(current_user) %>
<% end %>
<%= paginate @proposals %>
<% unless @search_terms || @advanced_search_terms || @tag_filter %>

View File

@@ -117,6 +117,13 @@
<h2><%= t("proposals.show.author") %></h2>
<div class="show-actions-menu">
<% if current_editable?(@proposal) %>
<%= link_to edit_proposal_path(@proposal), class: 'button hollow expanded' do %>
<span class="icon-edit"></span>
<%= t("proposals.show.edit_proposal_link") %>
<% end %>
<% end %>
<% if author_of_proposal?(@proposal) %>
<%= link_to new_proposal_notification_path(proposal_id: @proposal.id),
class: 'button hollow expanded' do %>
@@ -128,20 +135,13 @@
<% if can_destroy_image?(@proposal) %>
<%= link_to image_path(@proposal.image, from: request.url),
method: :delete,
class: 'button hollow expanded',
class: 'button hollow alert expanded',
data: { confirm: t('images.actions.destroy.confirm') } do %>
<span class="icon-document"></span>
<span class="icon-image"></span>
<%= t("images.remove_image") %>
<% end %>
<% end %>
<% if current_editable?(@proposal) %>
<%= link_to edit_proposal_path(@proposal), class: 'edit-proposal button hollow expanded' do %>
<span class="icon-edit"></span>
<%= t("proposals.show.edit_proposal_link") %>
<% end %>
<% end %>
</div>
<% end %>

View File

@@ -0,0 +1,31 @@
<div class="small-12 column section-recommended padding">
<div class="row">
<h2 class="text-center"><%= t("welcome.recommended.title") %></h2>
<div class="small-12 column carousel-image">
<% if recommended_debates.any? %>
<% carousel_size = calculate_carousel_size(recommended_debates, recommended_proposals, true) %>
<%= render "recommended_carousel", recommendeds: recommended_debates,
key: "debates",
image_field: nil,
image_version: nil,
image_default: nil,
carousel_size: carousel_size,
btn_text_link: t("welcome.recommended.debates.btn_text_link"),
btn_path_link: debates_path(order: "recommendations") %>
<% end %>
<% if recommended_proposals.any? %>
<% carousel_size = calculate_carousel_size(recommended_debates, recommended_proposals, false) %>
<%= render "recommended_carousel", recommendeds: recommended_proposals,
key: "proposals",
image_field: :attachment,
image_version: :thumb,
image_default: nil,
carousel_size: carousel_size,
btn_text_link: t("welcome.recommended.proposals.btn_text_link"),
btn_path_link: proposals_path(order: "recommendations") %>
<% end %>
</div>
</div>
</div>

View File

@@ -0,0 +1,42 @@
<div class="small-12 medium-4 large-4 <%= carousel_size %> column text-center <%= key %> ">
<div class="card small-centered <%= key %>-inner">
<h4><%= t("welcome.recommended.#{key.underscore}.title") %></h4>
<div class="orbit" role="region" data-orbit data-use-m-u-i="false">
<div class="orbit-wrapper">
<ul class="orbit-container no-bullet" tabindex="0" >
<% recommendeds.each_with_index do |recommended, index| %>
<li class="orbit-slide <%= active_class(index) %>" data-slide="<%= index %>" style="position: relative; <%= slide_display(index) %>" aria-live="polite">
<div class="card">
<%= render_recommendation_image(recommended, image_default) %>
<div class="card-section">
<%= link_to recommended_path(recommended) do %>
<h5 class="truncate-horizontal-text"><%= recommended.title %></h5>
<% end %>
<p><%= recommended.description %></p>
</div>
</div>
</li>
<% end %>
</ul>
<div class="truncate"></div>
</div>
<nav class="orbit-bullets">
<% recommendeds.each_with_index do |recommended, index| %>
<button data-slide="<%= index %>" class="<%= active_class(index) %>">
<span class="show-for-sr">Second slide details.</span>
</button>
<% end %>
</nav>
</div>
</div>
<%= link_to btn_text_link, btn_path_link, class: 'button hollow expanded' %>
</div>

View File

@@ -1,3 +1,5 @@
<% content_for :body_class, "home-page" %>
<% content_for :canonical do %>
<%= render "shared/canonical", href: root_url %>
<% end %>
@@ -16,37 +18,35 @@
</div>
</div>
<% if feature?("user.recommendations") && (@recommended_debates.present? || @recommended_proposals.present?) %>
<%= render "recommended",
recommended_debates: @recommended_debates,
recommended_proposals: @recommended_proposals %>
<% end %>
<% cache [locale_and_user_status, @featured_debates, @featured_proposals, 'featured'] do %>
<main>
<div class="row">
<div class="small-12 medium-6 column">
<p>
<span class="lead"><strong><%= t("welcome.debates.title") %></strong></span><br>
<%= t("welcome.debates.description") %>
</p>
</div>
<div class="small-12 medium-6 column">
<p>
<span class="lead"><strong><%= t("welcome.proposal.title") %></strong></span><br>
<%= t("welcome.proposal.description") %>
</p>
<div class="small-12 column text-center <%= highlight_background %>">
<div class="row margin padding">
<div class="small-12 medium-3 column">
<h2><%= t("welcome.debates.title") %></h2>
<p><%= t("welcome.debates.description") %></p>
</div>
<div class="small-12 medium-3 column">
<h2><%= t("welcome.proposal.title") %></h2>
<p><%= t("welcome.proposal.description") %></p>
</div>
<div class="small-12 medium-3 column">
<h2><%= t("welcome.decide.title") %></h2>
<p><%= t("welcome.decide.description") %></p>
</div>
<div class="small-12 medium-3 column">
<h2><%= t("welcome.do.title") %></h2>
<p><%= t("welcome.do.description") %></p>
</div>
</div>
</div>
<div class="row margin-top">
<div class="small-12 medium-6 column">
<p>
<span class="lead"><strong><%= t("welcome.decide.title") %></strong></span><br>
<%= t("welcome.decide.description") %>
</p>
</div>
<div class="small-12 medium-6 column">
<p>
<span class="lead"><strong><%= t("welcome.do.title") %></strong></span><br>
<%= t("welcome.do.description") %>
</p>
</div>
</div>
</main>
<% end %>