diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index 78c5c7444..928f06edd 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -49,6 +49,7 @@ //= require fixed_bar //= require banners //= require social_share +//= require checkbox_toggle //= require custom var initialize_modules = function() { @@ -74,6 +75,7 @@ var initialize_modules = function() { App.FixedBar.initialize(); App.Banners.initialize(); App.SocialShare.initialize(); + App.CheckboxToggle.initialize(); }; $(function(){ diff --git a/app/assets/javascripts/checkbox_toggle.js.coffee b/app/assets/javascripts/checkbox_toggle.js.coffee new file mode 100644 index 000000000..096ce7e25 --- /dev/null +++ b/app/assets/javascripts/checkbox_toggle.js.coffee @@ -0,0 +1,12 @@ +App.CheckboxToggle = + + initialize: -> + $('[data-checkbox-toggle]').on 'change', -> + $this = $(this) + $target = $($this.data('checkbox-toggle')) + if $this.is(':checked') + $target.show() + else + $target.hide() + + diff --git a/app/assets/stylesheets/_settings.scss b/app/assets/stylesheets/_settings.scss index cc5bf015e..b804d62ba 100644 --- a/app/assets/stylesheets/_settings.scss +++ b/app/assets/stylesheets/_settings.scss @@ -80,6 +80,7 @@ $budget: #7E328A; $budget-hover: #7571BF; $highlight: #E7F2FC; +$light: #F5F7FA; $featured: #FFDC5C; $footer-border: #BFC1C3; diff --git a/app/assets/stylesheets/admin.scss b/app/assets/stylesheets/admin.scss index 252cef324..fac6d35ed 100644 --- a/app/assets/stylesheets/admin.scss +++ b/app/assets/stylesheets/admin.scss @@ -5,6 +5,7 @@ // 03. List elements // 04. Stats // 05. Management +// 06. Polls // // 01. Global styles @@ -20,6 +21,11 @@ body.admin { .top-links { background: darken($admin-color, 15%); } + + .back-web { + padding-top: $line-height/4; + text-decoration: underline; + } } .top-bar { @@ -27,19 +33,37 @@ body.admin { height: auto; } + .top-bar-title { + + h1 { + margin-bottom: 0; + } + } + form { .button { margin-top: 0; + + &.margin-top { + margin-top: $line-height; + } } input[type="text"], textarea { width: 100%; } - .input-group input[type="text"] { - border-radius: 0; - margin-bottom: 0 !important; + .fieldset { + + select { + height: $line-height*2; + } + + .input-group input[type="text"] { + border-radius: 0; + margin-bottom: 0 !important; + } } } @@ -48,6 +72,15 @@ body.admin { th { text-align: left; + &.text-center { + text-align: center; + } + + &.text-right { + padding-right: $line-height; + text-align: right; + } + &.with-button { line-height: $line-height*2; } @@ -62,9 +95,20 @@ body.admin { } } + &.fixed { + table-layout: fixed; + } + input[type="submit"] ~ a, a ~ a { - margin-left: $line-height/2; - margin-right: $line-height/2; + margin-left: 0; + margin-right: 0; + margin-top: $line-height/2; + + @include breakpoint(medium) { + margin-left: $line-height/2; + margin-right: $line-height/2; + margin-top: 0; + } } } @@ -77,6 +121,11 @@ body.admin { color: $admin-color; } + .tabs-panel { + padding-left: 0; + padding-right: 0; + } + #proposals { width: 100% !important; } @@ -148,6 +197,13 @@ body.admin { } } +.input-group { + + .input-group-button { + padding-bottom: rem-calc(16); + } +} + // 02. Sidebar // ----------- @@ -155,7 +211,7 @@ body.admin { border-right: 1px solid $border; @include breakpoint(medium) { - padding-bottom: $line-height*3; + min-height: rem-calc(1100); } ul { @@ -165,10 +221,12 @@ body.admin { padding: 0; [class^="icon-"] { + color: $admin-color; display: inline-block; font-size: rem-calc(24); - padding-right: rem-calc(12); - padding-top: rem-calc(4); + line-height: $line-height; + padding: $line-height/2 $line-height/4; + padding-left: 0; vertical-align: middle; } @@ -177,19 +235,26 @@ body.admin { margin: 0; outline: 0; + ul { + margin-left: $line-height/1.5; + border-left: 1px solid $border; + padding-left: $line-height/2; + } + + &.section-title { + border-bottom: 1px solid $border; + } + &.active a { background: #f3f6f7; + border-radius: rem-calc(6); + -moz-border-radius: rem-calc(6); + -webkit-border-radius: rem-calc(6); color: $admin-color; font-weight: bold; } } - li.section { - border-bottom: 1px dotted #d5d5d5; - border-top: 1px dotted #d5d5d5; - height: $line-height/2; - } - li a { color: $text; display: block; @@ -199,10 +264,38 @@ body.admin { &:hover { background: #f3f6f7; + border-radius: rem-calc(6); + -moz-border-radius: rem-calc(6); + -webkit-border-radius: rem-calc(6); + color: $admin-color; text-decoration: none; } } } + + .is-accordion-submenu-parent { + + & > a::after { + border-color: $admin-color transparent transparent; + } + } + + .submenu { + border-bottom: 0; + margin-left: $line-height; + + li:first-child { + padding-top: $line-height/2; + } + + li:last-child { + padding-bottom: $line-height/2; + } + + a { + font-weight: normal; + } + } } // 03. List elements @@ -404,10 +497,43 @@ table.investment-projects-summary { } } -// 05. CMS +.geozone { + background: #ececec; + border-radius: rem-calc(6); + color: $text; + display: inline-block; + font-size: $small-font-size; + margin-bottom: $line-height/3; + padding: $line-height/4 $line-height/3; + text-decoration: none; + + &:hover { + background: #e0e0e0; + } +} + +// 06. Polls +// ----------------- + +.count-error { + background: $alert-bg !important; + color: $color-alert; + font-weight: bold; +} + +table { + + .callout { + height: $line-height*2; + line-height: $line-height*2; + padding: 0 $line-height/2; + } +} + +// 07. CMS // -------------- .cms_page_list { - + [class^="icon-"] { padding-right: $menu-icon-spacing; vertical-align: middle; diff --git a/app/assets/stylesheets/icons.scss b/app/assets/stylesheets/icons.scss index c02835881..afbcc8bdd 100644 --- a/app/assets/stylesheets/icons.scss +++ b/app/assets/stylesheets/icons.scss @@ -163,6 +163,12 @@ .icon-whatsapp:before { content: "\50"; } +.icon-zip:before { + content: "\4f"; +} +.icon-banner:before { + content: "\51"; +} .icon-arrow-down:before { content: "\52"; } diff --git a/app/assets/stylesheets/layout.scss b/app/assets/stylesheets/layout.scss index 45a131763..94d403528 100644 --- a/app/assets/stylesheets/layout.scss +++ b/app/assets/stylesheets/layout.scss @@ -144,6 +144,10 @@ a { padding-top: $line-height; } +.light { + background: $light; +} + .highlight { background: $highlight; } @@ -204,6 +208,30 @@ a { } } +.menu.vertical { + background: white; + margin: $line-height 0; + padding: $line-height; + + li { + margin-bottom: $line-height; + + a { + color: $text-medium; + padding: 0; + } + + h2 { + font-size: $base-font-size; + } + + &.active { + border-bottom: 2px solid $brand; + color: $brand; + } + } +} + .small { font-size: $small-font-size; } @@ -462,6 +490,7 @@ header { .input-group-button { line-height: $line-height*1.5; + padding-bottom: 0; button { background: $border; @@ -483,7 +512,6 @@ header { } .submenu { - background: white; border-bottom: 1px solid $border; clear: both; margin-bottom: $line-height/2; @@ -585,7 +613,8 @@ footer { // 04. Tags // -------- -.tags a , .tag-cloud a, .categories a, .geozone a, .sidebar-links a { +.tags a , .tag-cloud a, .categories a, .geozone a, .sidebar-links a, +.tags span { background: #ececec; border-radius: rem-calc(6); color: $text; diff --git a/app/assets/stylesheets/participation.scss b/app/assets/stylesheets/participation.scss index feca7f0a8..ad413925b 100644 --- a/app/assets/stylesheets/participation.scss +++ b/app/assets/stylesheets/participation.scss @@ -6,7 +6,8 @@ // 04. List participation // 05. Featured // 06. Budget -// 07. Proposals successfull +// 07. Proposals successful +// 08. Polls // // 01. Votes and supports @@ -297,7 +298,8 @@ .debate-show, .proposal-show, .investment-project-show, -.budget-investment-show { +.budget-investment-show, +.polls-show { p { word-wrap: break-word; @@ -769,7 +771,7 @@ // ------------ .featured-debates, .featured-proposals, -.proposals-ballot, .proposals-ballot-list { +.enquiries-list { padding: $line-height/2 0; @include breakpoint(medium) { @@ -1206,7 +1208,7 @@ ul.ballot-list { } } -// 07. Proposals successfull +// 07. Proposals successful // ------------------------- .dark-heading { @@ -1239,7 +1241,7 @@ ul.ballot-list { } } -.featured-proposals-ballot-banner { +.featured-proposals-ballot-banner, .sucessfull-proposals-banner { background: #2D3E50 image-url("ballot_tiny.gif") no-repeat; background-position: 75% 0; position: relative; @@ -1263,10 +1265,10 @@ ul.ballot-list { } } -.featured-proposals-ballot-banner, -.successfull .panel { +.sucessfull-proposals-banner, +.successful .panel { - .icon-successfull { + .icon-successful { border-right: 60px solid #FFD200; border-top: 0; border-bottom: 60px solid transparent; @@ -1287,17 +1289,7 @@ ul.ballot-list { } } -.proposals-ballot-list { - - .proposal-sucessfull { - background: white; - border-top: 1px solid $border; - padding: $line-height 0; - position: relative; - } -} - -.successfull { +.successful { .panel { position: relative; @@ -1318,3 +1310,199 @@ ul.ballot-list { } } } + +// 08. Polls +// ---------------------- + +.dark-heading { + background: #2D3E50; + color: white; + + .title { + color: #92BA48; + } + + .button { + background: white; + color: $brand; + } + + .callout { + + &.warning a { + color: $color-warning; + } + + &.primary a { + color: $color-info; + } + + &.alert a { + color: $color-alert; + } + } + + .info { + background: #314253; + padding: $line-height; + + @include breakpoint(medium) { + border-top: rem-calc(6) solid #92BA48; + } + } + + a:not(.button) { + color: white; + text-decoration: underline; + } + + .back, .icon-angle-left { + color: white; + } + + &.polls-show-header { + min-height: $line-height*8; + } +} + +.poll, .poll-question { + background: white; + border-radius: rem-calc(6); + margin-bottom: $line-height/2; +} + +.poll { + padding: $line-height; + position: relative; + + .icon-poll-answer { + border-top: 0; + border-bottom: 60px solid transparent; + height: 0; + position: absolute; + right: 0; + top: 0; + width: 0; + + &.can-answer:after, + &.cant-answer:after, + &.not-logged-in:after, + &.already-answer:after, + &.unverified:after { + font-family: "icons" !important; + left: 34px; + position: absolute; + top: 5px; + } + + &.can-answer { + border-right: 60px solid $info-bg; + + &:after { + color: $color-info; + content: "\6c"; + } + } + + &.cant-answer { + border-right: 60px solid $alert-bg; + + &:after { + color: $color-alert; + content: "\74"; + } + } + + &.not-logged-in { + border-right: 60px solid $info-bg; + + &:after { + color: $color-info; + content: "\6f"; + } + } + + &.unverified { + border-right: 60px solid $warning-bg; + + &:after { + color: $color-warning; + content: "\6f"; + } + } + + &.already-answer { + border-right: 60px solid $success-bg; + + &:after { + color: $color-success; + content: "\59"; + } + } + } + + .dates { + color: $text-medium; + font-size: $small-font-size; + margin-bottom: $line-height/2; + } + + h4 { + font-size: rem-calc(30); + line-height: $line-height*1.5; + + a { + color: $text; + } + } +} + +h2.questions-callout { + font-size: $base-font-size; +} + +h3.section-title-divider { + border-bottom: 2px solid $brand; + color: $brand; + margin-bottom: $line-height; +} + +.poll-question { + padding: 0 $line-height; + + h3 { + padding-top: $line-height; + + a { + color: $text; + } + } +} + +.poll-question-answers { + + .button { + margin-right: $line-height/4; + min-width: rem-calc(168); + + &.answered { + background: #F4F8EC; + border: 2px solid #92BA48; + color: $text; + position: relative; + + &:after { + background: #92BA48; + border-radius: rem-calc(20); + content: "\6c"; + color: white; + font-family: "icons" !important; + font-size: rem-calc(12); + padding: $line-height/4; + position: absolute; + right: -6px; + top: -6px; + } + } + } +} diff --git a/app/controllers/admin/poll/booth_assignments_controller.rb b/app/controllers/admin/poll/booth_assignments_controller.rb new file mode 100644 index 000000000..f81382bf8 --- /dev/null +++ b/app/controllers/admin/poll/booth_assignments_controller.rb @@ -0,0 +1,66 @@ +class Admin::Poll::BoothAssignmentsController < Admin::BaseController + + before_action :load_poll, except: [:create, :destroy] + + def index + @booth_assignments = @poll.booth_assignments.includes(:booth).order('poll_booths.name').page(params[:page]).per(50) + end + + def search_booths + load_search + @booths = ::Poll::Booth.search(@search) + respond_to do |format| + format.js + end + end + + def show + @booth_assignment = @poll.booth_assignments.includes(:recounts, :final_recounts, :voters, officer_assignments: [officer: [:user]]).find(params[:id]) + @voters_by_date = @booth_assignment.voters.group_by {|v| v.created_at.to_date} + end + + def create + @booth_assignment = ::Poll::BoothAssignment.new(poll_id: booth_assignment_params[:poll_id], booth_id: booth_assignment_params[:booth_id]) + + if @booth_assignment.save + notice = t("admin.poll_booth_assignments.flash.create") + else + notice = t("admin.poll_booth_assignments.flash.error_create") + end + redirect_to admin_poll_booth_assignments_path(@booth_assignment.poll_id), notice: notice + end + + def destroy + @booth_assignment = ::Poll::BoothAssignment.find(params[:id]) + + if @booth_assignment.destroy + notice = t("admin.poll_booth_assignments.flash.destroy") + else + notice = t("admin.poll_booth_assignments.flash.error_destroy") + end + redirect_to admin_poll_booth_assignments_path(@booth_assignment.poll_id), notice: notice + end + + private + + def load_booth_assignment + @booth_assignment = ::Poll::BoothAssignment.find(params[:id]) + end + + def booth_assignment_params + params.permit(:booth_id, :poll_id) + end + + def load_poll + @poll = ::Poll.find(params[:poll_id]) + end + + def search_params + params.permit(:poll_id, :search) + end + + def load_search + @search = search_params[:search] + end + +end \ No newline at end of file diff --git a/app/controllers/admin/poll/booths_controller.rb b/app/controllers/admin/poll/booths_controller.rb new file mode 100644 index 000000000..ff3700436 --- /dev/null +++ b/app/controllers/admin/poll/booths_controller.rb @@ -0,0 +1,39 @@ +class Admin::Poll::BoothsController < Admin::BaseController + load_and_authorize_resource class: 'Poll::Booth' + + def index + @booths = @booths.order(name: :asc).page(params[:page]) + end + + def show + end + + def new + end + + def create + if @booth.save + redirect_to admin_booths_path, notice: t("flash.actions.create.poll_booth") + else + render :new + end + end + + def edit + end + + def update + if @booth.update(booth_params) + redirect_to admin_booth_path(@booth), notice: t("flash.actions.update.poll_booth") + else + render :edit + end + end + + private + + def booth_params + params.require(:poll_booth).permit(:name, :location) + end + +end \ No newline at end of file diff --git a/app/controllers/admin/poll/officer_assignments_controller.rb b/app/controllers/admin/poll/officer_assignments_controller.rb new file mode 100644 index 000000000..f37ef9ab8 --- /dev/null +++ b/app/controllers/admin/poll/officer_assignments_controller.rb @@ -0,0 +1,92 @@ +class Admin::Poll::OfficerAssignmentsController < Admin::BaseController + + before_action :load_poll + before_action :redirect_if_blank_required_params, only: [:by_officer] + before_action :load_booth_assignment, only: [:create] + + def index + @officers = ::Poll::Officer. + includes(:user). + order('users.username'). + where( + id: @poll.officer_assignments.select(:officer_id).distinct.map(&:officer_id) + ).page(params[:page]).per(50) + end + + def by_officer + @poll = ::Poll.includes(:booths).find(params[:poll_id]) + @officer = ::Poll::Officer.includes(:user).find(officer_assignment_params[:officer_id]) + @officer_assignments = ::Poll::OfficerAssignment. + joins(:booth_assignment). + includes(:recount, :final_recounts, booth_assignment: :booth). + where("officer_id = ? AND poll_booth_assignments.poll_id = ?", @officer.id, @poll.id). + order(:date) + end + + def search_officers + load_search + @officers = User.joins(:poll_officer).search(@search).order(username: :asc) + + respond_to do |format| + format.js + end + end + + def create + @officer_assignment = ::Poll::OfficerAssignment.new(booth_assignment: @booth_assignment, + officer_id: create_params[:officer_id], + date: create_params[:date]) + @officer_assignment.final = true if @officer_assignment.date > @booth_assignment.poll.ends_at.to_date + + if @officer_assignment.save + notice = t("admin.poll_officer_assignments.flash.create") + else + notice = t("admin.poll_officer_assignments.flash.error_create") + end + redirect_to by_officer_admin_poll_officer_assignments_path(poll_id: create_params[:poll_id], officer_id: create_params[:officer_id]), notice: notice + end + + def destroy + @officer_assignment = ::Poll::OfficerAssignment.includes(:booth_assignment).find(params[:id]) + + if @officer_assignment.destroy + notice = t("admin.poll_officer_assignments.flash.destroy") + else + notice = t("admin.poll_officer_assignments.flash.error_destroy") + end + redirect_to by_officer_admin_poll_officer_assignments_path(poll_id: @officer_assignment.poll_id, officer_id: @officer_assignment.officer_id), notice: notice + end + + private + + def officer_assignment_params + params.permit(:officer_id) + end + + def create_params + params.permit(:poll_id, :booth_id, :date, :officer_id) + end + + def load_booth_assignment + @booth_assignment = ::Poll::BoothAssignment.includes(:poll).find_by(poll_id: create_params[:poll_id], booth_id: create_params[:booth_id]) + end + + def load_poll + @poll = ::Poll.find(params[:poll_id]) + end + + def redirect_if_blank_required_params + if officer_assignment_params[:officer_id].blank? + redirect_to admin_poll_path(@poll) + end + end + + def search_params + params.permit(:poll_id, :search) + end + + def load_search + @search = search_params[:search] + end + +end \ No newline at end of file diff --git a/app/controllers/admin/poll/officers_controller.rb b/app/controllers/admin/poll/officers_controller.rb new file mode 100644 index 000000000..2641a12b5 --- /dev/null +++ b/app/controllers/admin/poll/officers_controller.rb @@ -0,0 +1,39 @@ +class Admin::Poll::OfficersController < Admin::BaseController + load_and_authorize_resource :officer, class: "Poll::Officer", except: [:edit, :show] + + def index + @officers = @officers.page(params[:page]) + end + + def search + @user = User.find_by(email: params[:email]) + + respond_to do |format| + if @user + @officer = Poll::Officer.find_or_initialize_by(user: @user) + format.js + else + format.js { render "user_not_found" } + end + end + end + + def create + @officer.user_id = params[:user_id] + @officer.save + + redirect_to admin_officers_path + end + + def destroy + @officer.destroy + redirect_to admin_officers_path + end + + def show + end + + def edit + end + +end \ No newline at end of file diff --git a/app/controllers/admin/poll/polls_controller.rb b/app/controllers/admin/poll/polls_controller.rb new file mode 100644 index 000000000..eda5736b0 --- /dev/null +++ b/app/controllers/admin/poll/polls_controller.rb @@ -0,0 +1,86 @@ +class Admin::Poll::PollsController < Admin::BaseController + load_and_authorize_resource + + before_action :load_search, only: [:search_booths, :search_questions, :search_officers] + before_action :load_geozones, only: [:new, :create, :edit, :update] + + def index + end + + def show + @poll = Poll.includes(:questions). + order('poll_questions.title'). + find(params[:id]) + end + + def new + end + + def create + if @poll.save + redirect_to [:admin, @poll], notice: t("flash.actions.create.poll") + else + render :new + end + end + + def edit + end + + def update + if @poll.update(poll_params) + redirect_to [:admin, @poll], notice: t("flash.actions.update.poll") + else + render :edit + end + end + + def add_question + question = ::Poll::Question.find(params[:question_id]) + + if question.present? + @poll.questions << question + notice = t("admin.polls.flash.question_added") + else + notice = t("admin.polls.flash.error_on_question_added") + end + redirect_to admin_poll_path(@poll), notice: notice + end + + def remove_question + question = ::Poll::Question.find(params[:question_id]) + + if @poll.questions.include? question + @poll.questions.delete(question) + notice = t("admin.polls.flash.question_removed") + else + notice = t("admin.polls.flash.error_on_question_removed") + end + redirect_to admin_poll_path(@poll), notice: notice + end + + def search_questions + @questions = ::Poll::Question.where("poll_id IS ? OR poll_id != ?", nil, @poll.id).search({search: @search}).order(title: :asc) + respond_to do |format| + format.js + end + end + + private + def load_geozones + @geozones = Geozone.all.order(:name) + end + + def poll_params + params.require(:poll).permit(:name, :starts_at, :ends_at, :geozone_restricted, geozone_ids: []) + end + + def search_params + params.permit(:poll_id, :search) + end + + def load_search + @search = search_params[:search] + end + +end diff --git a/app/controllers/admin/poll/questions_controller.rb b/app/controllers/admin/poll/questions_controller.rb new file mode 100644 index 000000000..ab7297a95 --- /dev/null +++ b/app/controllers/admin/poll/questions_controller.rb @@ -0,0 +1,64 @@ +class Admin::Poll::QuestionsController < Admin::BaseController + load_and_authorize_resource :poll + load_and_authorize_resource :question, class: 'Poll::Question' + + def index + @polls = Poll.all + @search = search_params[:search] + + @questions = @questions.search(search_params).page(params[:page]).order("created_at DESC") + + @proposals = Proposal.successful.sort_by_confidence_score + end + + def new + @polls = Poll.all + @question.valid_answers = I18n.t('poll_questions.default_valid_answers') + proposal = Proposal.find(params[:proposal_id]) if params[:proposal_id].present? + @question.copy_attributes_from_proposal(proposal) + end + + def create + @question.author = @question.proposal.try(:author) || current_user + + if @question.save + redirect_to admin_question_path(@question) + else + render :new + end + end + + def show + end + + def edit + end + + def update + if @question.update(question_params) + redirect_to admin_question_path(@question), notice: t("flash.actions.save_changes.notice") + else + render :edit + end + end + + def destroy + if @question.destroy + notice = "Question destroyed succesfully" + else + notice = t("flash.actions.destroy.error") + end + redirect_to admin_questions_path, notice: notice + end + + private + + def question_params + params.require(:poll_question).permit(:poll_id, :title, :question, :description, :proposal_id, :valid_answers) + end + + def search_params + params.permit(:poll_id, :search) + end + +end diff --git a/app/controllers/admin/poll/recounts_controller.rb b/app/controllers/admin/poll/recounts_controller.rb new file mode 100644 index 000000000..fec546d79 --- /dev/null +++ b/app/controllers/admin/poll/recounts_controller.rb @@ -0,0 +1,16 @@ +class Admin::Poll::RecountsController < Admin::BaseController + before_action :load_poll + + def index + @booth_assignments = @poll.booth_assignments. + includes(:booth, :recounts, :final_recounts, :voters). + order("poll_booths.name"). + page(params[:page]).per(50) + end + + private + + def load_poll + @poll = ::Poll.find(params[:poll_id]) + end +end \ No newline at end of file diff --git a/app/controllers/admin/poll/results_controller.rb b/app/controllers/admin/poll/results_controller.rb new file mode 100644 index 000000000..2c5bbba27 --- /dev/null +++ b/app/controllers/admin/poll/results_controller.rb @@ -0,0 +1,13 @@ +class Admin::Poll::ResultsController < Admin::BaseController + before_action :load_poll + + def index + @partial_results = @poll.partial_results + end + + private + + def load_poll + @poll = ::Poll.includes(:questions).find(params[:poll_id]) + end +end \ No newline at end of file diff --git a/app/controllers/debates_controller.rb b/app/controllers/debates_controller.rb index 8262f014a..6d17eb749 100644 --- a/app/controllers/debates_controller.rb +++ b/app/controllers/debates_controller.rb @@ -22,7 +22,7 @@ class DebatesController < ApplicationController def index_customization @featured_debates = @debates.featured - @proposal_successfull_exists = Proposal.successfull.exists? + @proposal_successfull_exists = Proposal.successful.exists? end def show diff --git a/app/controllers/officing/base_controller.rb b/app/controllers/officing/base_controller.rb new file mode 100644 index 000000000..97ef23d30 --- /dev/null +++ b/app/controllers/officing/base_controller.rb @@ -0,0 +1,12 @@ +class Officing::BaseController < ApplicationController + layout 'admin' + + before_action :authenticate_user! + before_action :verify_officer + + skip_authorization_check + + def verify_officer + raise CanCan::AccessDenied unless current_user.try(:poll_officer?) || current_user.try(:administrator?) + end +end \ No newline at end of file diff --git a/app/controllers/officing/dashboard_controller.rb b/app/controllers/officing/dashboard_controller.rb new file mode 100644 index 000000000..4d80a974a --- /dev/null +++ b/app/controllers/officing/dashboard_controller.rb @@ -0,0 +1,6 @@ +class Officing::DashboardController < Officing::BaseController + + def index + end + +end diff --git a/app/controllers/officing/final_recounts_controller.rb b/app/controllers/officing/final_recounts_controller.rb new file mode 100644 index 000000000..e381240e7 --- /dev/null +++ b/app/controllers/officing/final_recounts_controller.rb @@ -0,0 +1,47 @@ +class Officing::FinalRecountsController < Officing::BaseController + before_action :load_poll + before_action :load_officer_assignment, only: :create + + def new + @officer_assignments = ::Poll::OfficerAssignment. + includes(:final_recounts, booth_assignment: [:booth]). + joins(:booth_assignment). + final. + where(id: current_user.poll_officer.officer_assignment_ids). + where("poll_booth_assignments.poll_id = ?", @poll.id). + order(date: :asc) + + @final_recounts = @officer_assignments.select {|oa| oa.final_recounts.any?}.map(&:final_recounts).flatten + end + + def create + @final_recount = ::Poll::FinalRecount.find_or_initialize_by(booth_assignment_id: @officer_assignment.booth_assignment_id, date: final_recount_params[:date]) + @final_recount.officer_assignment_id = @officer_assignment.id + @final_recount.count = final_recount_params[:count] + + if @final_recount.save + msg = { notice: t("officing.final_recounts.flash.create") } + else + msg = { alert: t("officing.final_recounts.flash.error_create") } + end + redirect_to new_officing_poll_final_recount_path(@poll), msg + end + + private + def load_poll + @poll = Poll.expired.find(params[:poll_id]) + end + + def load_officer_assignment + @officer_assignment = current_user.poll_officer. + officer_assignments.final.find_by(id: final_recount_params[:officer_assignment_id]) + if @officer_assignment.blank? + redirect_to new_officing_poll_final_recount_path(@poll), alert: t("officing.final_recounts.flash.error_create") + end + end + + def final_recount_params + params.permit(:officer_assignment_id, :count, :date) + end + +end \ No newline at end of file diff --git a/app/controllers/officing/polls_controller.rb b/app/controllers/officing/polls_controller.rb new file mode 100644 index 000000000..e122284ec --- /dev/null +++ b/app/controllers/officing/polls_controller.rb @@ -0,0 +1,15 @@ +class Officing::PollsController < Officing::BaseController + + def index + @polls = current_user.poll_officer? ? current_user.poll_officer.voting_days_assigned_polls : [] + @polls = @polls.select {|poll| poll.current?(Time.current) || poll.current?(1.day.ago)} + end + + def final + @polls = current_user.poll_officer? ? current_user.poll_officer.final_days_assigned_polls : [] + return unless current_user.poll_officer? + + @polls = @polls.select {|poll| poll.ends_at > 1.week.ago && poll.expired?} + end + +end \ No newline at end of file diff --git a/app/controllers/officing/recounts_controller.rb b/app/controllers/officing/recounts_controller.rb new file mode 100644 index 000000000..1c66e57fd --- /dev/null +++ b/app/controllers/officing/recounts_controller.rb @@ -0,0 +1,46 @@ +class Officing::RecountsController < Officing::BaseController + before_action :load_poll + before_action :load_officer_assignment, only: :create + + def new + @officer_assignments = ::Poll::OfficerAssignment. + includes(:recount, booth_assignment: :booth). + joins(:booth_assignment). + voting_days. + where(id: current_user.poll_officer.officer_assignment_ids). + where("poll_booth_assignments.poll_id = ?", @poll.id). + order(date: :asc) + @recounted = @officer_assignments.select {|oa| oa.recount.present?}.reverse + end + + def create + @recount = ::Poll::Recount.find_or_initialize_by(booth_assignment_id: @officer_assignment.booth_assignment_id, date: @officer_assignment.date) + @recount.officer_assignment_id = @officer_assignment.id + @recount.count = recount_params[:count] + + if @recount.save + msg = { notice: t("officing.recounts.flash.create") } + else + msg = { alert: t("officing.recounts.flash.error_create") } + end + redirect_to new_officing_poll_recount_path(@poll), msg + end + + private + def load_poll + @poll = Poll.find(params[:poll_id]) + end + + def load_officer_assignment + @officer_assignment = current_user.poll_officer. + officer_assignments.find_by(id: recount_params[:officer_assignment_id]) + if @officer_assignment.blank? + redirect_to new_officing_poll_recount_path(@poll), alert: t("officing.recounts.flash.error_create") + end + end + + def recount_params + params.permit(:officer_assignment_id, :count) + end + +end \ No newline at end of file diff --git a/app/controllers/officing/residence_controller.rb b/app/controllers/officing/residence_controller.rb new file mode 100644 index 000000000..9f24d3a90 --- /dev/null +++ b/app/controllers/officing/residence_controller.rb @@ -0,0 +1,37 @@ +class Officing::ResidenceController < Officing::BaseController + + before_action :load_officer_assignment + before_action :validate_officer_assignment, only: :create + + def new + @residence = Officing::Residence.new + end + + def create + @residence = Officing::Residence.new(residence_params.merge(officer: current_user.poll_officer)) + if @residence.save + redirect_to new_officing_voter_path(id: @residence.user.id), notice: t("officing.residence.flash.create") + else + render :new + end + end + + private + + def residence_params + params.require(:residence).permit(:document_number, :document_type, :year_of_birth) + end + + def load_officer_assignment + @officer_assignments = current_user.poll_officer. + officer_assignments. + voting_days. + where(date: Time.current.to_date) + end + + def validate_officer_assignment + if @officer_assignments.blank? + redirect_to officing_root_path, notice: t("officing.residence.flash.not_allowed") + end + end +end diff --git a/app/controllers/officing/results_controller.rb b/app/controllers/officing/results_controller.rb new file mode 100644 index 000000000..b6377e25b --- /dev/null +++ b/app/controllers/officing/results_controller.rb @@ -0,0 +1,141 @@ +class Officing::ResultsController < Officing::BaseController + before_action :load_poll + + before_action :load_officer_assignments, only: :new + before_action :load_partial_results, only: :new + + before_action :load_officer_assignment, only: :create + before_action :check_booth_and_date, only: :create + before_action :build_results, only: :create + + def new + end + + def create + @results.each { |result| result.save! } + + notice = t("officing.results.flash.create") + redirect_to new_officing_poll_result_path(@poll), notice: notice + end + + def index + @booth_assignment = ::Poll::BoothAssignment.includes(:booth).find(index_params[:booth_assignment_id]) + if current_user.poll_officer.officer_assignments.final. + where(booth_assignment_id: @booth_assignment.id).exists? + + @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) + end + end + + private + + def check_booth_and_date + if @officer_assignment.blank? + go_back_to_new(t("officing.results.flash.error_wrong_booth")) + elsif results_params[:date].blank? || + Date.parse(results_params[:date]) < @poll.starts_at.to_date || + Date.parse(results_params[:date]) > @poll.ends_at.to_date + go_back_to_new(t("officing.results.flash.error_wrong_date")) + end + end + + def build_results + @results = [] + + params[:questions].each_pair do |question_id, results| + question = @poll.questions.find(question_id) + go_back_to_new if question.blank? + + results.each_pair do |answer_index, count| + if count.present? + 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) + partial_result.officer_assignment_id = @officer_assignment.id + partial_result.amount = count.to_i + partial_result.author = current_user + partial_result.origin = 'booth' + @results << partial_result + end + end + end + + build_white_results + build_null_results + 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 go_back_to_new(alert = nil) + params[:d] = results_params[:date] + params[:oa] = results_params[:officer_assignment_id] + flash.now[:alert] = (alert || t("officing.results.flash.error_create")) + load_officer_assignments + load_partial_results + render :new + end + + def load_poll + @poll = ::Poll.expired.includes(:questions).find(params[:poll_id]) + end + + def load_officer_assignment + @officer_assignment = current_user.poll_officer. + officer_assignments.final.find_by(id: results_params[:officer_assignment_id]) + end + + def load_officer_assignments + @officer_assignments = ::Poll::OfficerAssignment. + includes(booth_assignment: [:booth]). + joins(:booth_assignment). + final. + where(id: current_user.poll_officer.officer_assignment_ids). + where("poll_booth_assignments.poll_id = ?", @poll.id). + order(date: :asc) + end + + def load_partial_results + if @officer_assignments.present? + @partial_results = ::Poll::PartialResult.where(officer_assignment_id: @officer_assignments.map(&:id)).order(:booth_assignment_id, :date) + end + end + + def results_params + params.permit(:officer_assignment_id, :date, :questions, :whites, :nulls) + end + + def index_params + params.permit(:booth_assignment_id, :date) + end + +end diff --git a/app/controllers/officing/voters_controller.rb b/app/controllers/officing/voters_controller.rb new file mode 100644 index 000000000..dee1e00bd --- /dev/null +++ b/app/controllers/officing/voters_controller.rb @@ -0,0 +1,25 @@ +class Officing::VotersController < Officing::BaseController + respond_to :html, :js + + def new + @user = User.find(params[:id]) + @polls = Poll.answerable_by(@user) + end + + def create + @poll = Poll.find(voter_params[:poll_id]) + @user = User.find(voter_params[:user_id]) + @voter = Poll::Voter.new(document_type: @user.document_type, + document_number: @user.document_number, + user: @user, + poll: @poll) + @voter.save! + end + + private + + def voter_params + params.require(:voter).permit(:poll_id, :user_id) + end + +end diff --git a/app/controllers/polls/questions_controller.rb b/app/controllers/polls/questions_controller.rb new file mode 100644 index 000000000..1849dff97 --- /dev/null +++ b/app/controllers/polls/questions_controller.rb @@ -0,0 +1,27 @@ +class Polls::QuestionsController < ApplicationController + + load_and_authorize_resource :poll + load_and_authorize_resource :question, class: 'Poll::Question' + + has_orders %w{most_voted newest oldest}, only: :show + + def show + @commentable = @question.proposal.present? ? @question.proposal : @question + @comment_tree = CommentTree.new(@commentable, params[:page], @current_order) + set_comment_flags(@comment_tree.comments) + + question_answer = @question.answers.where(author_id: current_user.try(:id)).first + @answers_by_question_id = {@question.id => question_answer.try(:answer)} + end + + def answer + answer = @question.answers.find_or_initialize_by(author: current_user) + + answer.answer = params[:answer] + answer.save! + answer.record_voter_participation + + @answers_by_question_id = {@question.id => params[:answer]} + end + +end diff --git a/app/controllers/polls_controller.rb b/app/controllers/polls_controller.rb new file mode 100644 index 000000000..41a038b46 --- /dev/null +++ b/app/controllers/polls_controller.rb @@ -0,0 +1,23 @@ +class PollsController < ApplicationController + + load_and_authorize_resource + + has_filters %w{current expired incoming} + + ::Poll::Answer # trigger autoload + + def index + @polls = @polls.send(@current_filter).includes(:geozones).sort_for_list.page(params[:page]) + end + + def show + @questions = @poll.questions.for_render.sort_for_list + + @answers_by_question_id = {} + poll_answers = ::Poll::Answer.by_question(@poll.question_ids).by_author(current_user.try(:id)) + poll_answers.each do |answer| + @answers_by_question_id[answer.question_id] = answer.answer + end + end + +end diff --git a/app/controllers/proposal_ballots_controller.rb b/app/controllers/proposal_ballots_controller.rb deleted file mode 100644 index 4171fcda8..000000000 --- a/app/controllers/proposal_ballots_controller.rb +++ /dev/null @@ -1,8 +0,0 @@ -class ProposalBallotsController < ApplicationController - skip_authorization_check - - def index - @proposal_ballots = Proposal.successfull.sort_by_confidence_score - end - -end \ No newline at end of file diff --git a/app/controllers/proposals_controller.rb b/app/controllers/proposals_controller.rb index d0ff9551e..daec4a051 100644 --- a/app/controllers/proposals_controller.rb +++ b/app/controllers/proposals_controller.rb @@ -28,8 +28,8 @@ class ProposalsController < ApplicationController def index_customization discard_archived load_retired - load_proposal_ballots - load_featured unless @proposal_successfull_exists + load_successful_proposals + load_featured unless @proposal_successful_exists end def vote @@ -103,8 +103,8 @@ class ProposalsController < ApplicationController end end - def load_proposal_ballots - @proposal_successfull_exists = Proposal.successfull.exists? + def load_successful_proposals + @proposal_successful_exists = Proposal.successful.exists? end end diff --git a/app/helpers/admin_helper.rb b/app/helpers/admin_helper.rb index 62d31cd05..3f0d4db4b 100644 --- a/app/helpers/admin_helper.rb +++ b/app/helpers/admin_helper.rb @@ -4,6 +4,38 @@ module AdminHelper render "/#{namespace}/menu" end + def namespaced_root_path + "/#{namespace}" + end + + def namespaced_header_title + t("#{namespace}.header.title") + end + + def menu_tags? + ["tags"].include? controller_name + end + + def menu_moderated_content? + ["proposals", "debates", "comments", "users"].include? controller_name + end + + def menu_budget? + ["spending_proposals"].include? controller_name + end + + def menu_polls? + ["polls", "questions", "officers", "booths", "officer_assignments", "booth_assignments", "recounts", "results"].include? controller_name + end + + def menu_profiles? + ["organizations", "officials", "moderators", "valuators", "managers"].include? controller_name + end + + def menu_banners? + ["banners"].include? controller_name + end + def official_level_options options = [["", 0]] (1..5).each do |i| @@ -16,10 +48,14 @@ module AdminHelper Administrator.all.order('users.username asc').includes(:user).collect { |v| [ v.name, v.id ] } end + def admin_submit_action(resource) + resource.persisted? ? "edit" : "new" + end + private def namespace - controller.class.parent.name.downcase + controller.class.parent.name.downcase.gsub("::", "/") end end diff --git a/app/helpers/budgets_helper.rb b/app/helpers/budgets_helper.rb index 4b9dfd311..3a07f0393 100644 --- a/app/helpers/budgets_helper.rb +++ b/app/helpers/budgets_helper.rb @@ -10,7 +10,7 @@ module BudgetsHelper def namespaced_budget_investment_path(investment, options={}) case namespace - when "management::budgets" + when "management/budgets" management_budget_investment_path(investment.budget, investment, options) else budget_investment_path(investment.budget, investment, options) @@ -19,7 +19,7 @@ module BudgetsHelper def namespaced_budget_investment_vote_path(investment, options={}) case namespace - when "management::budgets" + when "management/budgets" vote_management_budget_investment_path(investment.budget, investment, options) else vote_budget_investment_path(investment.budget, investment, options) diff --git a/app/helpers/comments_helper.rb b/app/helpers/comments_helper.rb index b9e69ddf9..df6e57b61 100644 --- a/app/helpers/comments_helper.rb +++ b/app/helpers/comments_helper.rb @@ -23,6 +23,8 @@ module CommentsHelper def commentable_path(comment) if comment.commentable_type == "Budget::Investment" budget_investment_path(comment.commentable.budget_id, comment.commentable) + elsif comment.commentable_type == "Poll::Question" + question_path(comment.commentable) else comment.commentable end diff --git a/app/helpers/officers_helper.rb b/app/helpers/officers_helper.rb new file mode 100644 index 000000000..f29bcf728 --- /dev/null +++ b/app/helpers/officers_helper.rb @@ -0,0 +1,7 @@ +module OfficersHelper + + def officer_label(officer) + truncate([officer.name, officer.email].compact.join(' - '), length: 100) + end + +end \ No newline at end of file diff --git a/app/helpers/officing_helper.rb b/app/helpers/officing_helper.rb new file mode 100644 index 000000000..2cfebf832 --- /dev/null +++ b/app/helpers/officing_helper.rb @@ -0,0 +1,36 @@ +module OfficingHelper + + def officer_assignments_select_options(officer_assignments) + options = [] + officer_assignments.each do |oa| + options << ["#{oa.booth_assignment.booth.name}: #{l(oa.date.to_date, format: :long)}", oa.id] + end + options_for_select(options) + end + + def booths_for_officer_select_options(officer_assignments) + options = [] + officer_assignments.each do |oa| + options << ["#{oa.booth_assignment.booth.name}", oa.id] + end + options.sort! {|x,y| x[0]<=>y[0]} + options_for_select(options, params[:oa]) + end + + def recount_to_compare_with_final_recount(final_recount) + recount = final_recount.booth_assignment.recounts.select {|r| r.date == final_recount.date}.first + recount.present? ? recount.count : "-" + end + + def system_recount_to_compare_with_final_recount(final_recount) + final_recount.booth_assignment.voters.select {|v| v.created_at.to_date == final_recount.date}.size + end + + def answer_result_value(question_id, answer_index) + return nil if params.blank? + return nil if params[:questions].blank? + return nil if params[:questions][question_id.to_s].blank? + params[:questions][question_id.to_s][answer_index.to_s] + end + +end \ No newline at end of file diff --git a/app/helpers/poll_final_recounts_helper.rb b/app/helpers/poll_final_recounts_helper.rb new file mode 100644 index 000000000..e196cb65b --- /dev/null +++ b/app/helpers/poll_final_recounts_helper.rb @@ -0,0 +1,7 @@ +module PollFinalRecountsHelper + + def final_recount_for_date(final_recounts, date) + final_recounts.select {|f| f.date == date}.first + end + +end \ No newline at end of file diff --git a/app/helpers/poll_recounts_helper.rb b/app/helpers/poll_recounts_helper.rb new file mode 100644 index 000000000..c47402163 --- /dev/null +++ b/app/helpers/poll_recounts_helper.rb @@ -0,0 +1,15 @@ +module PollRecountsHelper + + def recount_for_date(recounts, date) + recounts.select {|r| r.date == date}.first + end + + def booth_assignment_sum_recounts(ba) + ba.recounts.any? ? ba.recounts.to_a.sum(&:count) : nil + end + + def booth_assignment_sum_final_recounts(ba) + ba.final_recounts.any? ? ba.final_recounts.to_a.sum(&:count) :nil + end + +end \ No newline at end of file diff --git a/app/helpers/polls_helper.rb b/app/helpers/polls_helper.rb new file mode 100644 index 000000000..5425a6b52 --- /dev/null +++ b/app/helpers/polls_helper.rb @@ -0,0 +1,49 @@ +module PollsHelper + + def poll_select_options(include_all=nil) + options = @polls.collect {|poll| + [poll.name, current_path_with_query_params(poll_id: poll.id)] + } + options << all_polls if include_all + options_for_select(options, request.fullpath) + end + + def all_polls + [I18n.t("polls.all"), admin_questions_path] + end + + def poll_dates(poll) + if poll.starts_at.blank? || poll.ends_at.blank? + I18n.t("polls.no_dates") + else + I18n.t("polls.dates", open_at: l(poll.starts_at.to_date), closed_at: l(poll.ends_at.to_date)) + end + end + + def poll_dates_select_options(poll) + options = [] + (poll.starts_at.to_date..poll.ends_at.to_date).each do |date| + options << [l(date, format: :long), l(date)] + end + options_for_select(options, params[:d]) + end + + def poll_final_recount_option(poll) + final_date = poll.ends_at.to_date + 1.day + options_for_select([[I18n.t("polls.final_date"), l(final_date)]]) + end + + def poll_booths_select_options(poll) + options = [] + poll.booths.each do |booth| + options << [booth_name_with_location(booth), booth.id] + end + options_for_select(options) + end + + def booth_name_with_location(booth) + location = booth.location.blank? ? "" : " (#{booth.location})" + booth.name + location + end + +end \ No newline at end of file diff --git a/app/models/abilities/administrator.rb b/app/models/abilities/administrator.rb index a99b98fea..8df52e6eb 100644 --- a/app/models/abilities/administrator.rb +++ b/app/models/abilities/administrator.rb @@ -32,7 +32,7 @@ module Abilities can :mark_featured, Debate can :unmark_featured, Debate - can :comment_as_administrator, [Debate, Comment, Proposal, Budget::Investment] + can :comment_as_administrator, [Debate, Comment, Proposal, Poll::Question, Budget::Investment] can [:search, :create, :index, :destroy], ::Moderator can [:search, :create, :index, :summary], ::Valuator @@ -50,8 +50,17 @@ module Abilities can :create, Budget::ValuatorAssignment can [:search, :edit, :update, :create, :index, :destroy], Banner + can [:index, :create, :edit, :update, :destroy], Geozone + can [:read, :create, :update, :destroy, :add_question, :remove_question, :search_booths, :search_questions, :search_officers], Poll + can [:read, :create, :update, :destroy], Poll::Booth + can [:search, :create, :index, :destroy], ::Poll::Officer + can [:create, :destroy], ::Poll::BoothAssignment + can [:create, :destroy], ::Poll::OfficerAssignment + can [:read, :create, :update], Poll::Question + can :destroy, Poll::Question # , comments_count: 0, votes_up: 0 + can :manage, SiteCustomization::Page can :manage, SiteCustomization::Image can :manage, SiteCustomization::ContentBlock diff --git a/app/models/abilities/common.rb b/app/models/abilities/common.rb index 7ac6a12f4..6a8ef594c 100644 --- a/app/models/abilities/common.rb +++ b/app/models/abilities/common.rb @@ -53,6 +53,12 @@ module Abilities can :create, DirectMessage can :show, DirectMessage, sender_id: user.id + can :answer, Poll do |poll| + poll.answerable_by?(user) + end + can :answer, Poll::Question do |question| + question.answerable_by?(user) + end end can [:create, :show], ProposalNotification, proposal: { author_id: user.id } diff --git a/app/models/abilities/everyone.rb b/app/models/abilities/everyone.rb index 98080aa4c..c2b6cd6d7 100644 --- a/app/models/abilities/everyone.rb +++ b/app/models/abilities/everyone.rb @@ -6,6 +6,8 @@ module Abilities can [:read, :map], Debate can [:read, :map, :summary], Proposal can :read, Comment + can :read, Poll + can :read, Poll::Question can [:read, :welcome], Budget can :read, Budget::Investment can :read, SpendingProposal diff --git a/app/models/abilities/moderator.rb b/app/models/abilities/moderator.rb index 52d838f9c..5740e302e 100644 --- a/app/models/abilities/moderator.rb +++ b/app/models/abilities/moderator.rb @@ -5,7 +5,7 @@ module Abilities def initialize(user) self.merge Abilities::Moderation.new(user) - can :comment_as_moderator, [Debate, Comment, Proposal, Budget::Investment] + can :comment_as_moderator, [Debate, Comment, Proposal, Budget::Investment, Poll::Question] end end end diff --git a/app/models/comment.rb b/app/models/comment.rb index d4b1cc8e7..cd84a3578 100644 --- a/app/models/comment.rb +++ b/app/models/comment.rb @@ -10,7 +10,8 @@ class Comment < ActiveRecord::Base validates :body, presence: true validates :user, presence: true - validates_inclusion_of :commentable_type, in: ["Debate", "Proposal", "Budget::Investment"] + + validates_inclusion_of :commentable_type, in: ["Debate", "Proposal", "Budget::Investment", "Poll::Question"] validate :validate_body_length diff --git a/app/models/concerns/searchable.rb b/app/models/concerns/searchable.rb index 4d717959e..147a37fbc 100644 --- a/app/models/concerns/searchable.rb +++ b/app/models/concerns/searchable.rb @@ -12,7 +12,7 @@ module Searchable }, ignoring: :accents, ranked_by: '(:tsearch)', - order_within_rank: "#{self.table_name}.cached_votes_up DESC" + order_within_rank: (self.column_names.include?('cached_votes_up') ? "#{self.table_name}.cached_votes_up DESC" : nil) } end diff --git a/app/models/failed_census_call.rb b/app/models/failed_census_call.rb index ac792d7b7..b7d60e63a 100644 --- a/app/models/failed_census_call.rb +++ b/app/models/failed_census_call.rb @@ -1,3 +1,4 @@ class FailedCensusCall < ActiveRecord::Base belongs_to :user, counter_cache: true + belongs_to :poll_officer, class_name: 'Poll::Officer', counter_cache: true end diff --git a/app/models/geozone.rb b/app/models/geozone.rb index 7e38ce97d..824879ec6 100644 --- a/app/models/geozone.rb +++ b/app/models/geozone.rb @@ -9,6 +9,10 @@ class Geozone < ActiveRecord::Base Geozone.pluck(:name) end + def self.city + where(name: 'city').first + end + def safe_to_destroy? Geozone.reflect_on_all_associations(:has_many).all? do |association| association.klass.where(geozone: self).empty? diff --git a/app/models/officing/residence.rb b/app/models/officing/residence.rb new file mode 100644 index 000000000..6343fc5f7 --- /dev/null +++ b/app/models/officing/residence.rb @@ -0,0 +1,126 @@ +class Officing::Residence + include ActiveModel::Model + include ActiveModel::Validations::Callbacks + + attr_accessor :user, :officer, :document_number, :document_type, :year_of_birth + + before_validation :call_census_api + + validates_presence_of :document_number + validates_presence_of :document_type + validates_presence_of :year_of_birth + + validate :allowed_age + validate :residence_in_madrid + + def initialize(attrs={}) + super + clean_document_number + end + + def save + return false unless valid? + + if user_exists? + self.user = find_user_by_document + self.user.update(verified_at: Time.current) + else + user_params = { + document_number: document_number, + document_type: document_type, + geozone: self.geozone, + date_of_birth: date_of_birth.to_datetime, + gender: gender, + residence_verified_at: Time.current, + verified_at: Time.current, + erased_at: Time.current, + password: random_password, + terms_of_service: '1', + email: nil + } + self.user = User.create!(user_params) + end + end + + def store_failed_census_call + FailedCensusCall.create({ + user: user, + document_number: document_number, + document_type: document_type, + year_of_birth: year_of_birth, + poll_officer: officer + }) + + end + + def user_exists? + find_user_by_document.present? + end + + def find_user_by_document + User.where(document_number: document_number, + document_type: document_type).first + end + + def residence_in_madrid + return if errors.any? + + unless residency_valid? + store_failed_census_call + errors.add(:residence_in_madrid, false) + end + end + + def allowed_age + return if errors[:year_of_birth].any? + return unless @census_api_response.valid? + + unless allowed_age? + errors.add(:year_of_birth, I18n.t('verification.residence.new.error_not_allowed_age')) + end + end + + def allowed_age? + Age.in_years(date_of_birth) >= User.minimum_required_age + end + + def geozone + Geozone.where(census_code: district_code).first + end + + def district_code + @census_api_response.district_code + end + + def gender + @census_api_response.gender + end + + def date_of_birth + @census_api_response.date_of_birth + end + + private + + def call_census_api + @census_api_response = CensusApi.new.call(document_type, document_number) + end + + def residency_valid? + @census_api_response.valid? && + @census_api_response.date_of_birth.year.to_s == year_of_birth.to_s + end + + def census_year_of_birth + @census_api_response.date_of_birth.year + end + + def clean_document_number + self.document_number = self.document_number.gsub(/[^a-z0-9]+/i, "").upcase unless self.document_number.blank? + end + + def random_password + (0...20).map { ('a'..'z').to_a[rand(26)] }.join + end + +end diff --git a/app/models/poll.rb b/app/models/poll.rb new file mode 100644 index 000000000..c6be3073a --- /dev/null +++ b/app/models/poll.rb @@ -0,0 +1,65 @@ +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 :voters + has_many :officer_assignments, through: :booth_assignments + has_many :officers, through: :officer_assignments + has_many :questions + + has_and_belongs_to_many :geozones + + validates :name, presence: true + + validate :date_range + + scope :current, -> { where('starts_at <= ? and ? <= ends_at', Time.current, Time.current) } + scope :incoming, -> { where('? < starts_at', Time.current) } + scope :expired, -> { where('ends_at < ?', Time.current) } + scope :published, -> { where('published = ?', true) } + scope :by_geozone_id, ->(geozone_id) { where(geozones: {id: geozone_id}.joins(:geozones)) } + + scope :sort_for_list, -> { order(:geozone_restricted, :starts_at, :name) } + + def current?(timestamp = DateTime.current) + starts_at <= timestamp && timestamp <= ends_at + end + + def incoming?(timestamp = DateTime.current) + timestamp < starts_at + end + + def expired?(timestamp = DateTime.current) + ends_at < timestamp + end + + def answerable_by?(user) + user.present? && + user.level_two_or_three_verified? && + current? && + (!geozone_restricted || geozone_ids.include?(user.geozone_id)) + end + + def self.answerable_by(user) + return none if user.nil? || user.unverified? + current.joins('LEFT JOIN "geozones_polls" ON "geozones_polls"."poll_id" = "polls"."id"') + .where('geozone_restricted = ? OR geozones_polls.geozone_id = ?', false, user.geozone_id) + end + + def votable_by?(user) + !document_has_voted?(user.document_number, user.document_type) + end + + def document_has_voted?(document_number, document_type) + voters.where(document_number: document_number, document_type: document_type).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')) + end + end + +end diff --git a/app/models/poll/answer.rb b/app/models/poll/answer.rb new file mode 100644 index 000000000..52fb11469 --- /dev/null +++ b/app/models/poll/answer.rb @@ -0,0 +1,19 @@ +class Poll::Answer < ActiveRecord::Base + + belongs_to :question, -> { with_hidden } + belongs_to :author, -> { with_hidden }, class_name: 'User', foreign_key: 'author_id' + + delegate :poll, :poll_id, to: :question + + validates :question, presence: true + validates :author, presence: true + validates :answer, presence: true + validates :answer, inclusion: {in: ->(a) { a.question.valid_answers }} + + 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) + end +end \ No newline at end of file diff --git a/app/models/poll/booth.rb b/app/models/poll/booth.rb new file mode 100644 index 000000000..c7fb63efc --- /dev/null +++ b/app/models/poll/booth.rb @@ -0,0 +1,13 @@ +class Poll + class Booth < ActiveRecord::Base + has_many :booth_assignments, class_name: "Poll::BoothAssignment" + has_many :polls, through: :booth_assignments + + validates :name, presence: true, uniqueness: true + + def self.search(terms) + return Booth.none if terms.blank? + Booth.where("name ILIKE ? OR location ILIKE ?", "%#{terms}%", "%#{terms}%") + end + end +end \ No newline at end of file diff --git a/app/models/poll/booth_assignment.rb b/app/models/poll/booth_assignment.rb new file mode 100644 index 000000000..0519fffa6 --- /dev/null +++ b/app/models/poll/booth_assignment.rb @@ -0,0 +1,15 @@ +class Poll + class BoothAssignment < ActiveRecord::Base + belongs_to :booth + belongs_to :poll + + has_many :officer_assignments, class_name: "Poll::OfficerAssignment", dependent: :destroy + has_many :recounts, class_name: "Poll::Recount", dependent: :destroy + has_many :final_recounts, class_name: "Poll::FinalRecount", dependent: :destroy + has_many :officers, through: :officer_assignments + has_many :voters + has_many :partial_results + has_many :white_results + has_many :null_results + end +end diff --git a/app/models/poll/final_recount.rb b/app/models/poll/final_recount.rb new file mode 100644 index 000000000..6ebf5eede --- /dev/null +++ b/app/models/poll/final_recount.rb @@ -0,0 +1,19 @@ +class Poll + class FinalRecount < ActiveRecord::Base + belongs_to :booth_assignment, class_name: "Poll::BoothAssignment" + belongs_to :officer_assignment, class_name: "Poll::OfficerAssignment" + + validates :booth_assignment_id, presence: true + validates :date, presence: true, uniqueness: {scope: :booth_assignment_id} + validates :count, presence: true, numericality: {only_integer: true} + + before_save :update_logs + + def update_logs + if self.count_changed? && self.count_was.present? + self.count_log += ":#{self.count_was.to_s}" + self.officer_assignment_id_log += ":#{self.officer_assignment_id_was.to_s}" + end + end + end +end \ No newline at end of file diff --git a/app/models/poll/null_result.rb b/app/models/poll/null_result.rb new file mode 100644 index 000000000..222432c7f --- /dev/null +++ b/app/models/poll/null_result.rb @@ -0,0 +1,23 @@ +class Poll::NullResult < ActiveRecord::Base + + VALID_ORIGINS = %w{ web booth } + + 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 :by_author, -> (author_id) { where(author_id: author_id) } + + before_save :update_logs + + def update_logs + if self.amount_changed? && self.amount_was.present? + self.amount_log += ":#{self.amount_was.to_s}" + self.officer_assignment_id_log += ":#{self.officer_assignment_id_was.to_s}" + self.author_id_log += ":#{self.author_id_was.to_s}" + end + end +end \ No newline at end of file diff --git a/app/models/poll/officer.rb b/app/models/poll/officer.rb new file mode 100644 index 000000000..bf4c73c36 --- /dev/null +++ b/app/models/poll/officer.rb @@ -0,0 +1,26 @@ +class Poll + class Officer < ActiveRecord::Base + belongs_to :user + has_many :officer_assignments, class_name: "Poll::OfficerAssignment" + has_many :failed_census_calls, foreign_key: :poll_officer_id + + validates :user_id, presence: true, uniqueness: true + + delegate :name, :email, to: :user + + def voting_days_assigned_polls + officer_assignments.voting_days.includes(booth_assignment: :poll). + map(&:booth_assignment). + map(&:poll).uniq.compact. + sort {|x, y| y.ends_at <=> x.ends_at} + end + + def final_days_assigned_polls + officer_assignments.final.includes(booth_assignment: :poll). + map(&:booth_assignment). + map(&:poll).uniq.compact. + sort {|x, y| y.ends_at <=> x.ends_at} + end + + end +end diff --git a/app/models/poll/officer_assignment.rb b/app/models/poll/officer_assignment.rb new file mode 100644 index 000000000..cd4f53266 --- /dev/null +++ b/app/models/poll/officer_assignment.rb @@ -0,0 +1,25 @@ +class Poll + class OfficerAssignment < ActiveRecord::Base + belongs_to :officer + belongs_to :booth_assignment + has_one :recount + has_many :final_recounts + has_many :partial_results + has_many :voters + + validates :officer_id, presence: true + validates :booth_assignment_id, presence: true + validates :date, presence: true, uniqueness: { scope: [:officer_id, :booth_assignment_id] } + + delegate :poll_id, :booth_id, to: :booth_assignment + + scope :voting_days, -> { where(final: false) } + scope :final, -> { where(final: true) } + + before_create :log_user_data + + def log_user_data + self.user_data_log = "#{officer.user_id} - #{officer.user.name_and_email}" + end + end +end diff --git a/app/models/poll/partial_result.rb b/app/models/poll/partial_result.rb new file mode 100644 index 000000000..e42589a03 --- /dev/null +++ b/app/models/poll/partial_result.rb @@ -0,0 +1,28 @@ +class Poll::PartialResult < ActiveRecord::Base + + VALID_ORIGINS = %w{ web booth } + + belongs_to :question, -> { with_hidden } + belongs_to :author, -> { with_hidden }, class_name: 'User', foreign_key: 'author_id' + belongs_to :booth_assignment + belongs_to :officer_assignment + + validates :question, presence: true + validates :author, presence: true + validates :answer, presence: true + validates :answer, inclusion: {in: ->(a) { a.question.valid_answers }} + validates :origin, inclusion: {in: VALID_ORIGINS} + + scope :by_author, -> (author_id) { where(author_id: author_id) } + scope :by_question, -> (question_id) { where(question_id: question_id) } + + before_save :update_logs + + def update_logs + if self.amount_changed? && self.amount_was.present? + self.amount_log += ":#{self.amount_was.to_s}" + self.officer_assignment_id_log += ":#{self.officer_assignment_id_was.to_s}" + self.author_id_log += ":#{self.author_id_was.to_s}" + end + end +end \ No newline at end of file diff --git a/app/models/poll/question.rb b/app/models/poll/question.rb new file mode 100644 index 000000000..dd4eae3bc --- /dev/null +++ b/app/models/poll/question.rb @@ -0,0 +1,70 @@ +class Poll::Question < ActiveRecord::Base + include Measurable + include Searchable + + acts_as_paranoid column: :hidden_at + include ActsAsParanoidAliases + + belongs_to :poll + belongs_to :author, -> { with_hidden }, class_name: 'User', foreign_key: 'author_id' + + has_many :comments, as: :commentable + has_many :answers + has_many :partial_results + belongs_to :proposal + + validates :title, presence: true + validates :author, presence: true + + validates :title, length: { minimum: 4 } + validates :description, length: { maximum: Poll::Question.description_max_length } + + scope :by_poll_id, ->(poll_id) { where(poll_id: poll_id) } + + scope :sort_for_list, -> { order('poll_questions.proposal_id IS NULL', :created_at)} + scope :for_render, -> { includes(:author, :proposal) } + + def self.search(params) + results = self.all + results = results.by_poll_id(params[:poll_id]) if params[:poll_id].present? + results = results.pg_search(params[:search]) if params[:search].present? + results + end + + def searchable_values + { title => 'A', + proposal.try(:title) => 'A', + description => 'B', + author.username => 'C', + author_visible_name => 'C' } + end + + def description + super.try :html_safe + end + + def valid_answers + (super.try(:split, ',').compact || []).map(&:strip) + end + + def copy_attributes_from_proposal(proposal) + if proposal.present? + self.author = proposal.author + self.author_visible_name = proposal.author.name + self.proposal_id = proposal.id + self.title = proposal.title + self.description = proposal.description + self.valid_answers = I18n.t('poll_questions.default_valid_answers') + end + end + + def answerable_by?(user) + poll.answerable_by?(user) + end + + def self.answerable_by(user) + return none if user.nil? || user.unverified? + where(poll_id: Poll.answerable_by(user).pluck(:id)) + end + +end diff --git a/app/models/poll/recount.rb b/app/models/poll/recount.rb new file mode 100644 index 000000000..b4e28583e --- /dev/null +++ b/app/models/poll/recount.rb @@ -0,0 +1,20 @@ +class Poll + class Recount < ActiveRecord::Base + belongs_to :booth_assignment, class_name: "Poll::BoothAssignment" + belongs_to :officer_assignment, class_name: "Poll::OfficerAssignment" + + validates :booth_assignment_id, presence: true + validates :date, presence: true, uniqueness: {scope: :booth_assignment_id} + validates :officer_assignment_id, presence: true, uniqueness: {scope: :booth_assignment_id} + validates :count, presence: true, numericality: {only_integer: true} + + before_save :update_logs + + def update_logs + if self.count_changed? && self.count_was.present? + self.count_log += ":#{self.count_was.to_s}" + self.officer_assignment_id_log += ":#{self.officer_assignment_id_was.to_s}" + end + end + end +end \ No newline at end of file diff --git a/app/models/poll/voter.rb b/app/models/poll/voter.rb new file mode 100644 index 000000000..8fe612151 --- /dev/null +++ b/app/models/poll/voter.rb @@ -0,0 +1,59 @@ +class Poll + class Voter < ActiveRecord::Base + belongs_to :poll + belongs_to :user + belongs_to :geozone + belongs_to :booth_assignment + belongs_to :officer_assignment + + validates :poll_id, presence: true + validates :user_id, presence: true + + validates :document_number, presence: true, uniqueness: { scope: [:poll_id, :document_type], message: :has_voted } + + before_validation :set_demographic_info, :set_document_info + + def set_demographic_info + return unless user.present? + + self.gender = user.gender + self.age = user.age + self.geozone = user.geozone + end + + def set_document_info + return unless user.present? + + self.document_type = user.document_type + self.document_number = user.document_number + end + + private + + def in_census? + census_api_response.valid? + end + + def census_api_response + @census_api_response ||= CensusApi.new.call(document_type, document_number) + end + + def fill_stats_fields + if in_census? + self.gender = census_api_response.gender + self.geozone_id = Geozone.select(:id).where(census_code: census_api_response.district_code).first.try(:id) + self.age = voter_age(census_api_response.date_of_birth) + end + end + + def voter_age(dob) + if dob.blank? + nil + else + now = Time.now.utc.to_date + now.year - dob.year - ((now.month > dob.month || (now.month == dob.month && now.day >= dob.day)) ? 0 : 1) + end + end + + end +end \ No newline at end of file diff --git a/app/models/poll/white_result.rb b/app/models/poll/white_result.rb new file mode 100644 index 000000000..5b0aa4966 --- /dev/null +++ b/app/models/poll/white_result.rb @@ -0,0 +1,23 @@ +class Poll::WhiteResult < ActiveRecord::Base + + VALID_ORIGINS = %w{ web booth } + + 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 :by_author, -> (author_id) { where(author_id: author_id) } + + before_save :update_logs + + def update_logs + if self.amount_changed? && self.amount_was.present? + self.amount_log += ":#{self.amount_was.to_s}" + self.officer_assignment_id_log += ":#{self.officer_assignment_id_was.to_s}" + self.author_id_log += ":#{self.author_id_was.to_s}" + end + end +end \ No newline at end of file diff --git a/app/models/proposal.rb b/app/models/proposal.rb index 32175f9f7..0abde3584 100644 --- a/app/models/proposal.rb +++ b/app/models/proposal.rb @@ -45,12 +45,12 @@ 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 :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 :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 :successfull, -> { where("cached_votes_up >= ?", Proposal.votes_needed_for_success)} + scope :successful, -> { where("cached_votes_up >= ?", Proposal.votes_needed_for_success) } def to_param "#{id}-#{title}".parameterize @@ -155,7 +155,7 @@ class Proposal < ActiveRecord::Base Setting['votes_for_proposal_success'].to_i end - def successfull? + def successful? total_votes >= Proposal.votes_needed_for_success end diff --git a/app/models/signature.rb b/app/models/signature.rb index a645fbfb6..543965aed 100644 --- a/app/models/signature.rb +++ b/app/models/signature.rb @@ -46,8 +46,8 @@ class Signature < ActiveRecord::Base user_params = { document_number: document_number, created_from_signature: true, - verified_at: Time.now, - erased_at: Time.now, + verified_at: Time.current, + erased_at: Time.current, password: random_password, terms_of_service: '1', email: nil, diff --git a/app/models/user.rb b/app/models/user.rb index f8991d676..c3038c88a 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -13,6 +13,7 @@ class User < ActiveRecord::Base has_one :moderator has_one :valuator has_one :manager + has_one :poll_officer, class_name: "Poll::Officer" has_one :organization has_one :lock has_many :flags @@ -55,6 +56,7 @@ class User < ActiveRecord::Base scope :by_document, -> (document_type, document_number) { where(document_type: document_type, document_number: document_number) } scope :email_digest, -> { where(email_digest: true) } scope :active, -> { where(erased_at: nil) } + scope :erased, -> { where.not(erased_at: nil) } before_validation :clean_document_number @@ -123,6 +125,10 @@ class User < ActiveRecord::Base manager.present? end + def poll_officer? + poll_officer.present? + end + def organization? organization.present? end @@ -188,6 +194,22 @@ class User < ActiveRecord::Base erased_at.present? end + def take_votes_if_erased_document(document_number, document_type) + erased_user = User.erased.where(document_number: document_number).where(document_type: document_type).first + if erased_user.present? + self.take_votes_from(erased_user) + erased_user.update(document_number: nil, document_type: nil) + end + end + + def take_votes_from(other_user) + return if other_user.blank? + Poll::Voter.where(user_id: other_user.id).update_all(user_id: self.id) + Budget::Ballot.where(user_id: other_user.id).update_all(user_id: self.id) + Vote.where("voter_id = ? AND voter_type = ?", other_user.id, "User").update_all(voter_id: self.id) + self.update(former_users_data_log: "#{self.former_users_data_log} | id: #{other_user.id} - #{Time.current.strftime('%Y-%m-%d %H:%M:%S')}") + end + def locked? Lock.find_or_create_by(user: self).locked? end @@ -241,6 +263,10 @@ class User < ActiveRecord::Base "#{name} (#{email})" end + def age + Age.in_years(date_of_birth) + end + def save_requiring_finish_signup begin self.registering_with_oauth = true diff --git a/app/models/verification/management/document.rb b/app/models/verification/management/document.rb index 4264154d3..420dcf49c 100644 --- a/app/models/verification/management/document.rb +++ b/app/models/verification/management/document.rb @@ -32,7 +32,7 @@ class Verification::Management::Document end def under_age?(response) - User.minimum_required_age.years.ago.beginning_of_day < response.date_of_birth.beginning_of_day + response.date_of_birth.blank? || Age.in_years(response.date_of_birth) < User.minimum_required_age end def verified? diff --git a/app/models/verification/residence.rb b/app/models/verification/residence.rb index eda562671..ea000677f 100644 --- a/app/models/verification/residence.rb +++ b/app/models/verification/residence.rb @@ -26,6 +26,9 @@ class Verification::Residence def save return false unless valid? + + user.take_votes_if_erased_document(document_number, document_type) + user.update(document_number: document_number, document_type: document_type, geozone: self.geozone, @@ -36,11 +39,11 @@ class Verification::Residence def allowed_age return if errors[:date_of_birth].any? - errors.add(:date_of_birth, I18n.t('verification.residence.new.error_not_allowed_age')) unless self.date_of_birth <= User.minimum_required_age.years.ago + errors.add(:date_of_birth, I18n.t('verification.residence.new.error_not_allowed_age')) unless Age.in_years(self.date_of_birth) >= User.minimum_required_age end def document_number_uniqueness - errors.add(:document_number, I18n.t('errors.messages.taken')) if User.where(document_number: document_number).any? + errors.add(:document_number, I18n.t('errors.messages.taken')) if User.active.where(document_number: document_number).any? end def store_failed_attempt diff --git a/app/views/admin/_menu.html.erb b/app/views/admin/_menu.html.erb index 65d8bed79..fecd29419 100644 --- a/app/views/admin/_menu.html.erb +++ b/app/views/admin/_menu.html.erb @@ -1,144 +1,166 @@ - + diff --git a/app/views/admin/dashboard/index.html.erb b/app/views/admin/dashboard/index.html.erb index 793b4e407..2a2ed1c56 100644 --- a/app/views/admin/dashboard/index.html.erb +++ b/app/views/admin/dashboard/index.html.erb @@ -1,4 +1,12 @@ -<%= link_to t("admin.dashboard.index.back") + " " + setting['org_name'], root_path, class: "button float-right" %> +<%= link_to admin_settings_path, class: "button float-right" do %> + + <%= t("admin.menu.settings") %> +<% end %> + +<%= link_to admin_stats_path, class: "button float-right" do %> + + <%= t("admin.menu.stats") %> +<% end %>

<%= t("admin.dashboard.index.title") %>

diff --git a/app/views/admin/moderators/index.html.erb b/app/views/admin/moderators/index.html.erb index d2a5d45e0..0fc8703fd 100644 --- a/app/views/admin/moderators/index.html.erb +++ b/app/views/admin/moderators/index.html.erb @@ -1,4 +1,6 @@ -

<%= t("admin.moderators.index.title") %>

+<%= link_to t('admin.menu.activity'), admin_activity_path, class: "button hollow float-right" %> + +

<%= t("admin.moderators.index.title") %>

<%= form_tag search_admin_moderators_path, method: :get, remote: true do %> diff --git a/app/views/admin/poll/_menu.html.erb b/app/views/admin/poll/_menu.html.erb new file mode 100644 index 000000000..08efd87dc --- /dev/null +++ b/app/views/admin/poll/_menu.html.erb @@ -0,0 +1 @@ +<%= render "admin/menu" %> \ No newline at end of file diff --git a/app/views/admin/poll/booth_assignments/_search_booths.html.erb b/app/views/admin/poll/booth_assignments/_search_booths.html.erb new file mode 100644 index 000000000..e96e334c7 --- /dev/null +++ b/app/views/admin/poll/booth_assignments/_search_booths.html.erb @@ -0,0 +1,16 @@ +
+
+ <%= form_tag(search_booths_admin_poll_booth_assignments_path(@poll), method: :get, remote: true) do |f| %> +
+ <%= text_field_tag :search, + @search, + placeholder: t("admin.shared.booths_search.placeholder"), id: "search-booths" %> +
+ <%= submit_tag t("admin.shared.booths_search.button"), class: "button" %> +
+
+ <% end %> +
+
+ +
diff --git a/app/views/admin/poll/booth_assignments/_search_booths_results.html.erb b/app/views/admin/poll/booth_assignments/_search_booths_results.html.erb new file mode 100644 index 000000000..3fa7fc080 --- /dev/null +++ b/app/views/admin/poll/booth_assignments/_search_booths_results.html.erb @@ -0,0 +1,39 @@ +<% if @booths.blank? %> +
+ <%= t('admin.shared.no_search_results') %> +
+<% else %> +

<%= t('admin.shared.search_results') %>

+<% end %> + +<% if @booths.any? %> + + + + + + + + + + <% @booths.each do |booth| %> + + + + + + <% end %> + +
<%= t("admin.poll_booth_assignments.index.table_name") %><%= t("admin.poll_booth_assignments.index.table_location") %><%= t("admin.poll_booth_assignments.index.table_assignment") %>
+ <%= booth.name %> + + <%= booth.location %> + + <% unless @poll.booth_ids.include?(booth.id) %> + <%= link_to t("admin.poll_booth_assignments.index.add_booth"), + admin_poll_booth_assignments_path(@poll, booth_id: booth.id), + method: :post, + class: "button hollow" %> + <% end %> +
+<% end %> diff --git a/app/views/admin/poll/booth_assignments/index.html.erb b/app/views/admin/poll/booth_assignments/index.html.erb new file mode 100644 index 000000000..f14e59ef1 --- /dev/null +++ b/app/views/admin/poll/booth_assignments/index.html.erb @@ -0,0 +1,43 @@ +<%= render "/admin/poll/polls/poll_header" %> +
+ <%= render "/admin/poll/polls/subnav" %> + <%= render "search_booths" %> + +

<%= t("admin.poll_booth_assignments.index.booths_title") %>

+ + <% if @booth_assignments.empty? %> +
+ <%= t("admin.poll_booth_assignments.index.no_booths") %> +
+ <% else %> + + + + + + + + <% @booth_assignments.each do |booth_assignment| %> + + + + + + <% end %> + +
<%= t("admin.poll_booth_assignments.index.table_name") %><%= t("admin.poll_booth_assignments.index.table_location") %><%= t("admin.poll_booth_assignments.index.table_assignment") %>
+ + <%= link_to booth_assignment.booth.name, admin_poll_booth_assignment_path(@poll, booth_assignment) %> + + + <%= booth_assignment.booth.location %> + + <%= link_to t("admin.poll_booth_assignments.index.remove_booth"), + admin_poll_booth_assignment_path(@poll, booth_assignment), + method: :delete, + class: "button hollow alert" %> +
+ + <%= paginate @booth_assignments %> + <% end %> +
diff --git a/app/views/admin/poll/booth_assignments/search_booths.js.erb b/app/views/admin/poll/booth_assignments/search_booths.js.erb new file mode 100644 index 000000000..72fd96f68 --- /dev/null +++ b/app/views/admin/poll/booth_assignments/search_booths.js.erb @@ -0,0 +1 @@ +$("#search-booths-results").html("<%= j render 'search_booths_results' %>"); \ No newline at end of file diff --git a/app/views/admin/poll/booth_assignments/show.html.erb b/app/views/admin/poll/booth_assignments/show.html.erb new file mode 100644 index 000000000..519b36202 --- /dev/null +++ b/app/views/admin/poll/booth_assignments/show.html.erb @@ -0,0 +1,87 @@ +<%= link_to admin_poll_booth_assignments_path(@poll) do %> + + <%= @poll.name %> +<% end %> + +

<%= @booth_assignment.booth.name %>

+ +<% if @booth_assignment.booth.location.present? %> +

+ <%= t("admin.poll_booth_assignments.show.location") %>: + <%= @booth_assignment.booth.location %> +

+<% end %> + +
+ + +
+ <% if @booth_assignment.officers.empty? %> +
+ <%= t("admin.poll_booth_assignments.show.no_officers") %> +
+ <% else %> +

<%= t("admin.poll_booth_assignments.show.officers_list") %>

+ + + + <% @booth_assignment.officers.uniq.each do |officer| %> + + + + + <% end %> + +
<%= link_to officer.name, by_officer_admin_poll_officer_assignments_path(@poll, officer_id: officer.id) %><%= officer.email %>
+ <% end %> +
+ +
+ <% if @booth_assignment.recounts.empty? %> +
+ <%= t("admin.poll_booth_assignments.show.no_recounts") %> +
+ <% else %> +

<%= t("admin.poll_booth_assignments.show.recounts_list") %>

+ + + + + + + + + + + <% (@poll.starts_at.to_date..@poll.ends_at.to_date).each do |voting_date| %> + <% recount = recount_for_date(@booth_assignment.recounts, voting_date) %> + <% final_recount = final_recount_for_date(@booth_assignment.final_recounts, voting_date) %> + <% system_count = @voters_by_date[voting_date].present? ? @voters_by_date[voting_date].size : 0 %> + + + <% if recount.present? %> + + + <% else %> + + <% end %> + <% if final_recount.present? %> + + <% else %> + + <% end %> + + + <% end %> + +
<%= t("admin.poll_booth_assignments.show.date") %><%= t("admin.poll_booth_assignments.show.count_by_officer") %><%= t("admin.poll_booth_assignments.show.count_final") %><%= t("admin.poll_booth_assignments.show.count_by_system") %>
<%= l voting_date %><%= recount.count %> - <%= final_recount.count %> - <%= system_count %>
+ <% end %> +
+
diff --git a/app/views/admin/poll/booths/_booth.html.erb b/app/views/admin/poll/booths/_booth.html.erb new file mode 100644 index 000000000..5732400a8 --- /dev/null +++ b/app/views/admin/poll/booths/_booth.html.erb @@ -0,0 +1,13 @@ + + + <%= booth.name %> + + + <%= booth.location %> + + + <%= link_to t("admin.actions.edit"), + edit_admin_booth_path(booth), + class: "button hollow" %> + + \ No newline at end of file diff --git a/app/views/admin/poll/booths/_form.html.erb b/app/views/admin/poll/booths/_form.html.erb new file mode 100644 index 000000000..31d60c0f0 --- /dev/null +++ b/app/views/admin/poll/booths/_form.html.erb @@ -0,0 +1,20 @@ +
+
+ <%= f.text_field :name, + placeholder: t('admin.booths.new.name'), + label: t("admin.booths.new.name") %> +
+ +
+ <%= f.text_field :location, + placeholder: t("admin.booths.new.location"), + label: t("admin.booths.new.location") %> +
+
+ +
+
+ <%= f.submit t("admin.booths.#{admin_submit_action(@booth)}.submit_button"), + class: "button success expanded" %> +
+
\ No newline at end of file diff --git a/app/views/admin/poll/booths/edit.html.erb b/app/views/admin/poll/booths/edit.html.erb new file mode 100644 index 000000000..66bfa8355 --- /dev/null +++ b/app/views/admin/poll/booths/edit.html.erb @@ -0,0 +1,7 @@ +<%= back_link_to admin_booths_path %> + +

<%= t("admin.booths.edit.title") %>

+ +<%= form_for @booth, url: admin_booth_path(@booth) do |f| %> + <%= render "form", f: f %> +<% end %> diff --git a/app/views/admin/poll/booths/index.html.erb b/app/views/admin/poll/booths/index.html.erb new file mode 100644 index 000000000..9618aec59 --- /dev/null +++ b/app/views/admin/poll/booths/index.html.erb @@ -0,0 +1,28 @@ +

<%= t("admin.booths.index.title") %>

+ +<%= link_to t("admin.booths.index.add_booth"), new_admin_booth_path, + class: "button success float-right" %> + +<% if @booths.empty? %> +
+ <%= t("admin.booths.index.no_booths") %> +
+<% end %> + +<% if @booths.any? %> +

<%= page_entries_info @booths %>

+ + + + + + + + <% @booths.each do |booth| %> + <%= render partial: "booth", locals: { booth: booth } %> + <% end %> + +
<%= t("admin.booths.index.name") %><%= t("admin.booths.index.location") %> 
+ + <%= paginate @booths %> +<% end %> diff --git a/app/views/admin/poll/booths/new.html.erb b/app/views/admin/poll/booths/new.html.erb new file mode 100644 index 000000000..cf9581fa3 --- /dev/null +++ b/app/views/admin/poll/booths/new.html.erb @@ -0,0 +1,7 @@ +<%= back_link_to admin_booths_path %> + +

<%= t("admin.booths.new.title") %>

+ +<%= form_for @booth, url: admin_booths_path(@booth) do |f| %> + <%= render "form", f: f %> +<% end %> \ No newline at end of file diff --git a/app/views/admin/poll/booths/show.html.erb b/app/views/admin/poll/booths/show.html.erb new file mode 100644 index 000000000..bd9355f69 --- /dev/null +++ b/app/views/admin/poll/booths/show.html.erb @@ -0,0 +1,12 @@ +<%= back_link_to admin_booths_path %> + +
+ +

+ <%= @booth.name %> +

+ +

+ <%= t("admin.booths.show.location") %>: + <%= @booth.location %> +

diff --git a/app/views/admin/poll/officer_assignments/_search_officers.html.erb b/app/views/admin/poll/officer_assignments/_search_officers.html.erb new file mode 100644 index 000000000..7ed39f10d --- /dev/null +++ b/app/views/admin/poll/officer_assignments/_search_officers.html.erb @@ -0,0 +1,16 @@ +
+
+ <%= form_tag(search_officers_admin_poll_officer_assignments_path(@poll), method: :get, remote: true) do |f| %> +
+ <%= text_field_tag :search, + @search, + placeholder: t("admin.shared.poll_officers_search.placeholder"), id: "search-officers" %> +
+ <%= submit_tag t("admin.shared.poll_officers_search.button"), class: "button" %> +
+
+ <% end %> +
+
+ +
diff --git a/app/views/admin/poll/officer_assignments/_search_officers_results.html.erb b/app/views/admin/poll/officer_assignments/_search_officers_results.html.erb new file mode 100644 index 000000000..f665c6c0b --- /dev/null +++ b/app/views/admin/poll/officer_assignments/_search_officers_results.html.erb @@ -0,0 +1,42 @@ +<% if @officers.blank? %> +
+ <%= t('admin.shared.no_search_results') %> +
+<% else %> +

<%= t('admin.shared.search_results') %>

+<% end %> + +<% if @officers.any? %> + + + + + + + + + + <% @officers.each do |user| %> + + + + + + <% end %> + +
<%= t("admin.poll_officer_assignments.index.table_name") %><%= t("admin.poll_officer_assignments.index.table_email") %><%= t("admin.polls.show.table_assignment") %>
+ <%= user.name %> + + <%= user.email %> + + <% if @poll.officer_ids.include?(user.poll_officer.id) %> + <%= link_to t("admin.poll_officer_assignments.index.edit_officer_assignments"), + by_officer_admin_poll_officer_assignments_path(@poll, officer_id: user.poll_officer.id), + class: "button hollow alert" %> + <% else %> + <%= link_to t("admin.poll_officer_assignments.index.add_officer_assignments"), + by_officer_admin_poll_officer_assignments_path(@poll, officer_id: user.poll_officer.id), + class: "button hollow" %> + <% end %> +
+<% end %> diff --git a/app/views/admin/poll/officer_assignments/by_officer.html.erb b/app/views/admin/poll/officer_assignments/by_officer.html.erb new file mode 100644 index 000000000..205692535 --- /dev/null +++ b/app/views/admin/poll/officer_assignments/by_officer.html.erb @@ -0,0 +1,127 @@ +<%= link_to admin_poll_officer_assignments_path(@poll) do %> + + <%= @poll.name %> +<% end %> + +

<%= @officer.name %> - <%= @officer.email %>

+ +<%= form_tag(admin_poll_officer_assignments_path(@poll), {id: "officer_assignment_form"}) do %> +
+ <%= t("admin.poll_officer_assignments.by_officer.new_assignment") %> +
+ + <%= select_tag :date, + poll_dates_select_options(@poll) + poll_final_recount_option(@poll), + { prompt: t("admin.poll_officer_assignments.by_officer.select_date"), + label: false } %> +
+ +
+ + <%= select_tag :booth_id, + poll_booths_select_options(@poll), + { prompt: t("admin.poll_officer_assignments.by_officer.select_booth"), + label: false } %> +
+ +
+ <%= hidden_field_tag :officer_id, @officer.id %> + <%= hidden_field_tag :poll_id, @poll.id %> + <%= submit_tag t("admin.poll_officer_assignments.by_officer.add_assignment"), + class: "button expanded hollow margin-top" %> +
+
+<% end %> + + +<% if @officer_assignments.empty? %> +
+ <%= t("admin.poll_officer_assignments.by_officer.no_assignments") %> +
+<% else %> +

<%= t("admin.poll_officer_assignments.by_officer.assignments") %>

+ + + + + + + + + + <% @officer_assignments.each do |officer_assignment| %> + + + + + + <% end %> + +
<%= t("admin.poll_officer_assignments.by_officer.date") %><%= t("admin.poll_officer_assignments.by_officer.booth") %><%= t("admin.poll_officer_assignments.by_officer.assignment") %>
<%= officer_assignment.final? ? t('polls.final_date') : l(officer_assignment.date.to_date) %><%= booth_name_with_location(officer_assignment.booth_assignment.booth) %> + <%= link_to t("admin.poll_officer_assignments.by_officer.remove_assignment"), + admin_poll_officer_assignment_path(@poll, officer_assignment), + method: :delete, + class: "button hollow alert" %> +
+<% end %> + +<% voting_days_officer_assignments = @officer_assignments.select{|oa| oa.final == false} %> +<% if voting_days_officer_assignments.any? %> +

<%= t("admin.poll_officer_assignments.by_officer.recounts") %>

+ + + + + + + + + + <% voting_days_officer_assignments.each do |officer_assignment| %> + + + + + + <% end %> + +
<%= t("admin.poll_officer_assignments.by_officer.date") %><%= t("admin.poll_officer_assignments.by_officer.booth") %><%= t("admin.poll_officer_assignments.by_officer.recount") %>
<%= l(officer_assignment.date.to_date) %><%= booth_name_with_location(officer_assignment.booth_assignment.booth) %> + <% if officer_assignment.recount.present? %> + <%= officer_assignment.recount.count %> + <% else %> + - + <% end %> +
+<% end %> + +<% final_officer_assignments = @officer_assignments.select{|oa| oa.final == true} %> +<% if final_officer_assignments.any? %> +

<%= t("admin.poll_officer_assignments.by_officer.final_recounts") %>

+ + + + + + + + + + <% final_officer_assignments.each do |officer_assignment| %> + + + + + + <% end %> + +
<%= t("admin.poll_officer_assignments.by_officer.date") %><%= t("admin.poll_officer_assignments.by_officer.booth") %><%= t("admin.poll_officer_assignments.by_officer.final_recount") %>
<%= l(officer_assignment.date.to_date) %><%= booth_name_with_location(officer_assignment.booth_assignment.booth) %> + <% if officer_assignment.final_recounts.any? %> + <%= officer_assignment.final_recounts.to_a.sum(&:count) %> + <% else %> + - + <% end %> +
+<% end %> + + + diff --git a/app/views/admin/poll/officer_assignments/index.html.erb b/app/views/admin/poll/officer_assignments/index.html.erb new file mode 100644 index 000000000..f6e75c602 --- /dev/null +++ b/app/views/admin/poll/officer_assignments/index.html.erb @@ -0,0 +1,43 @@ +<%= render "/admin/poll/polls/poll_header" %> +
+ <%= render "/admin/poll/polls/subnav" %> + + <%= render "search_officers" %> + +

<%= t("admin.poll_officer_assignments.index.officers_title") %>

+ + <% if @officers.empty? %> +
+ <%= t("admin.poll_officer_assignments.index.no_officers") %> +
+ <% else %> + + + + + + + + <% @officers.each do |officer| %> + + + + + + <% end %> + +
<%= t("admin.poll_officer_assignments.index.table_name") %><%= t("admin.poll_officer_assignments.index.table_email") %><%= t("admin.actions.actions") %>
+ + <%= link_to officer.name, by_officer_admin_poll_officer_assignments_path(@poll, officer_id: officer.id) %> + + + <%= officer.email %> + + <%= link_to t("admin.poll_officer_assignments.index.edit_officer_assignments"), + by_officer_admin_poll_officer_assignments_path(@poll, officer_id: officer.id), + class: "button hollow" %> +
+ + <%= paginate @officers %> + <% end %> +
\ No newline at end of file diff --git a/app/views/admin/poll/officer_assignments/search_officers.js.erb b/app/views/admin/poll/officer_assignments/search_officers.js.erb new file mode 100644 index 000000000..ba621d8f7 --- /dev/null +++ b/app/views/admin/poll/officer_assignments/search_officers.js.erb @@ -0,0 +1 @@ +$("#search-officers-results").html("<%= j render 'search_officers_results' %>"); \ No newline at end of file diff --git a/app/views/admin/poll/officers/_officer.html.erb b/app/views/admin/poll/officers/_officer.html.erb new file mode 100644 index 000000000..80434f385 --- /dev/null +++ b/app/views/admin/poll/officers/_officer.html.erb @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + +
<%= t('admin.poll_officers.officer.name') %><%= t('admin.poll_officers.officer.email') %>
+ <%= officer.name %> + + <%= officer.email %> + + <% if officer.persisted? %> + <%= link_to t('admin.poll_officers.officer.delete'), + admin_poll_officer_path(officer), + method: :delete, + class: "button hollow alert" %> + <% else %> + <%= link_to t('admin.poll_officers.officer.add'),{ controller: "admin/poll/officers", action: :create, user_id: officer.user_id }, + method: :post, + class: "button success" %> + <% end %> +
diff --git a/app/views/admin/poll/officers/_search.html.erb b/app/views/admin/poll/officers/_search.html.erb new file mode 100644 index 000000000..dda22dee2 --- /dev/null +++ b/app/views/admin/poll/officers/_search.html.erb @@ -0,0 +1,9 @@ +<%= form_tag search_admin_officers_path, method: :get, remote: true do %> +
+ <%= text_field_tag :email, '', + placeholder: t("admin.poll_officers.search.email_placeholder") %> +
+ <%= submit_tag t("admin.poll_officers.search.search"), class: "button" %> +
+
+<% end %> diff --git a/app/views/admin/poll/officers/edit.html.erb b/app/views/admin/poll/officers/edit.html.erb new file mode 100644 index 000000000..5c64d3aeb --- /dev/null +++ b/app/views/admin/poll/officers/edit.html.erb @@ -0,0 +1 @@ +officer edit \ No newline at end of file diff --git a/app/views/admin/poll/officers/index.html.erb b/app/views/admin/poll/officers/index.html.erb new file mode 100644 index 000000000..fd9619167 --- /dev/null +++ b/app/views/admin/poll/officers/index.html.erb @@ -0,0 +1,53 @@ +

<%= t("admin.poll_officers.index.title") %>

+ +
+
+ <%= render 'search' %> +
+
+ +
+ +

+ <%= page_entries_info @officers, entry_name: t('admin.poll_officers.officer.entry_name') %> +

+ +<% if @officers.any? %> + + + + + + + + + <% @officers.each do |officer| %> + + + + + + <% end %> + +
<%= t('admin.poll_officers.officer.name') %><%= t('admin.poll_officers.officer.email') %>
+ <%= officer.name %> + + <%= officer.email %> + + <% if officer.persisted? %> + <%= link_to t('admin.poll_officers.officer.delete'), + admin_officer_path(officer), + method: :delete, + class: "button hollow alert" + %> + <% else %> + <%= link_to t('admin.poll_officers.officer.add'), + { controller: "admin/poll/officers", action: :create, + user_id: officer.user_id }, + method: :post, + class: "button success" %> + <% end %> +
+ + <%= paginate @officers %> +<% end %> \ No newline at end of file diff --git a/app/views/admin/poll/officers/search.js.erb b/app/views/admin/poll/officers/search.js.erb new file mode 100644 index 000000000..bd259f7fb --- /dev/null +++ b/app/views/admin/poll/officers/search.js.erb @@ -0,0 +1 @@ +$("#search-result").html("<%= j render 'officer', officer: @officer %>"); diff --git a/app/views/admin/poll/officers/show.html.erb b/app/views/admin/poll/officers/show.html.erb new file mode 100644 index 000000000..fc702276e --- /dev/null +++ b/app/views/admin/poll/officers/show.html.erb @@ -0,0 +1 @@ +officer show \ No newline at end of file diff --git a/app/views/admin/poll/officers/user_not_found.js.erb b/app/views/admin/poll/officers/user_not_found.js.erb new file mode 100644 index 000000000..a6444dc61 --- /dev/null +++ b/app/views/admin/poll/officers/user_not_found.js.erb @@ -0,0 +1 @@ +$("#search-result").html("
<%= j t('admin.poll_officers.search.user_not_found') %>
"); diff --git a/app/views/admin/poll/polls/_form.html.erb b/app/views/admin/poll/polls/_form.html.erb new file mode 100644 index 000000000..0b99ea9f5 --- /dev/null +++ b/app/views/admin/poll/polls/_form.html.erb @@ -0,0 +1,46 @@ +<%= form_for [:admin, @poll] do |f| %> +
+
+ <%= f.text_field :name %> +
+
+ +
+
+ <%= f.text_field :starts_at, + value: @poll.starts_at.present? ? l(@poll.starts_at.to_date) : nil, + class: "js-calendar-full" %> +
+ +
+ <%= f.text_field :ends_at, + value: @poll.ends_at.present? ? l(@poll.ends_at.to_date) : nil, + class: "js-calendar-full" %> +
+
+ +
+
+ <%= f.check_box :geozone_restricted, data: { checkbox_toggle: "#geozones" } %> +
+
+ +
+
+ <%= f.collection_check_boxes(:geozone_ids, @geozones, :id, :name) do |b| %> +
+ <%= b.label do %> + <%= b.check_box + b.text %> + <% end %> +
+ <% end %> +
+
+ +
+
+ <%= f.submit t("admin.polls.#{admin_submit_action(@poll)}.submit_button"), + class: "button success expanded" %> +
+
+<% end %> diff --git a/app/views/admin/poll/polls/_poll.html.erb b/app/views/admin/poll/polls/_poll.html.erb new file mode 100644 index 000000000..b65ce3e71 --- /dev/null +++ b/app/views/admin/poll/polls/_poll.html.erb @@ -0,0 +1,18 @@ + + + + <%= link_to poll.name, admin_poll_path(poll) %> + + + + <%= l poll.starts_at.to_date %> - <%= l poll.ends_at.to_date %> + + + <%= link_to t("admin.actions.edit"), + edit_admin_poll_path(poll), + class: "button hollow" %> + <%= link_to t("admin.actions.configure"), + admin_poll_path(poll), + class: "button hollow" %> + + \ No newline at end of file diff --git a/app/views/admin/poll/polls/_poll_header.html.erb b/app/views/admin/poll/polls/_poll_header.html.erb new file mode 100644 index 000000000..a88dd03ee --- /dev/null +++ b/app/views/admin/poll/polls/_poll_header.html.erb @@ -0,0 +1,17 @@ +<%= link_to t("admin.actions.edit"), + edit_admin_poll_path(@poll), + class: "button hollow float-right" %> + +

+ <%= @poll.name %> +

+
+ + (<%= l @poll.starts_at.to_date %> - <%= l @poll.ends_at.to_date %>) + +<% if @poll.geozone_restricted %> +  •  + + <%= @poll.geozones.pluck(:name).to_sentence %> + +<% end %> \ No newline at end of file diff --git a/app/views/admin/poll/polls/_questions.html.erb b/app/views/admin/poll/polls/_questions.html.erb new file mode 100644 index 000000000..e41d2017e --- /dev/null +++ b/app/views/admin/poll/polls/_questions.html.erb @@ -0,0 +1,31 @@ +

<%= t("admin.polls.show.questions_title") %>

+ +<% if @poll.questions.empty? %> +
+ <%= t('admin.polls.show.no_questions') %> +
+<% else %> + + + + + + + + <% @poll.questions.each do |question| %> + + + + + <% end %> +
<%= t('admin.polls.show.table_title') %><%= t('admin.polls.show.table_assignment') %>
+ + <%= link_to question.title, admin_question_path(question) %> + + + <%= link_to t('admin.polls.show.remove_question'), + remove_question_admin_poll_path(poll_id: @poll.id, question_id: question.id), + class: "button hollow alert", + method: :patch %> +
+<% end %> diff --git a/app/views/admin/poll/polls/_search_questions.html.erb b/app/views/admin/poll/polls/_search_questions.html.erb new file mode 100644 index 000000000..659cdcd37 --- /dev/null +++ b/app/views/admin/poll/polls/_search_questions.html.erb @@ -0,0 +1,17 @@ +
+
+ <%= form_tag(search_questions_admin_poll_path(@poll), method: :get, remote: true) do |f| %> +
+ <%= text_field_tag :search, + @search, + placeholder: t("admin.shared.poll_questions_search.placeholder"), id: "search-questions" %> + +
+ <%= submit_tag t("admin.shared.poll_questions_search.button"), class: "button" %> +
+
+ <% end %> +
+
+ +
diff --git a/app/views/admin/poll/polls/_search_questions_results.html.erb b/app/views/admin/poll/polls/_search_questions_results.html.erb new file mode 100644 index 000000000..f6f11e1f1 --- /dev/null +++ b/app/views/admin/poll/polls/_search_questions_results.html.erb @@ -0,0 +1,33 @@ +<% if @questions.blank? %> +
+ <%= t('admin.shared.no_search_results') %> +
+<% else %> +

<%= t('admin.shared.search_results') %>

+<% end %> + +<% if @questions.any? %> + + + + + + + + + <% @questions.each do |question| %> + + + + + <% end %> + +
<%= t("admin.polls.show.table_name") %><%= t("admin.polls.show.table_assignment") %>
+ <%= question.title %> + + <%= link_to t("admin.polls.show.add_question"), + add_question_admin_poll_path(poll_id: @poll.id, question_id: question.id), + method: :patch, + class: "button hollow" %> +
+<% end %> diff --git a/app/views/admin/poll/polls/_subnav.html.erb b/app/views/admin/poll/polls/_subnav.html.erb new file mode 100644 index 000000000..249ff407c --- /dev/null +++ b/app/views/admin/poll/polls/_subnav.html.erb @@ -0,0 +1,63 @@ + diff --git a/app/views/admin/poll/polls/edit.html.erb b/app/views/admin/poll/polls/edit.html.erb new file mode 100644 index 000000000..cf131203f --- /dev/null +++ b/app/views/admin/poll/polls/edit.html.erb @@ -0,0 +1,5 @@ +<%= render 'shared/back_link' %> + +

<%= t("admin.polls.edit.title") %>

+ +<%= render "form" %> \ No newline at end of file diff --git a/app/views/admin/poll/polls/index.html.erb b/app/views/admin/poll/polls/index.html.erb new file mode 100644 index 000000000..5bd310e54 --- /dev/null +++ b/app/views/admin/poll/polls/index.html.erb @@ -0,0 +1,22 @@ +

<%= t("admin.polls.index.title") %>

+ +<%= link_to t("admin.polls.index.create"), + new_admin_poll_path, + class: "button success float-right" %> + +<% if @polls.any? %> + + + + + + + + <%= render @polls %> + +
<%= t("admin.polls.index.name") %><%= t("admin.polls.index.dates") %><%= t("admin.actions.actions") %>
+<% else %> +
+ <%= t("admin.polls.index.no_polls") %> +
+<% end %> \ No newline at end of file diff --git a/app/views/admin/poll/polls/new.html.erb b/app/views/admin/poll/polls/new.html.erb new file mode 100644 index 000000000..ba1ae7260 --- /dev/null +++ b/app/views/admin/poll/polls/new.html.erb @@ -0,0 +1,5 @@ +<%= render 'shared/back_link' %> + +

<%= t("admin.polls.new.title") %>

+ +<%= render "form" %> \ No newline at end of file diff --git a/app/views/admin/poll/polls/search_questions.js.erb b/app/views/admin/poll/polls/search_questions.js.erb new file mode 100644 index 000000000..05f5c5167 --- /dev/null +++ b/app/views/admin/poll/polls/search_questions.js.erb @@ -0,0 +1 @@ +$("#search-questions-results").html("<%= j render 'search_questions_results' %>"); \ No newline at end of file diff --git a/app/views/admin/poll/polls/show.html.erb b/app/views/admin/poll/polls/show.html.erb new file mode 100644 index 000000000..93ce52e47 --- /dev/null +++ b/app/views/admin/poll/polls/show.html.erb @@ -0,0 +1,8 @@ +<%= render "poll_header" %> + +
+ <%= render "subnav" %> + + <%= render "search_questions" %> + <%= render "questions" %> +
diff --git a/app/views/admin/poll/questions/_filter.html.erb b/app/views/admin/poll/questions/_filter.html.erb new file mode 100644 index 000000000..0fd800067 --- /dev/null +++ b/app/views/admin/poll/questions/_filter.html.erb @@ -0,0 +1,7 @@ +<%= form_tag '', method: :get do %> + <%= label_tag :poll_id, t("admin.questions.index.filter_poll") %> + <%= select_tag "poll_id", + poll_select_options(true), + prompt: t("admin.questions.index.select_poll"), + class: "js-location-changer" %> +<% end %> diff --git a/app/views/admin/poll/questions/_filter_subnav.html.erb b/app/views/admin/poll/questions/_filter_subnav.html.erb new file mode 100644 index 000000000..7f641d390 --- /dev/null +++ b/app/views/admin/poll/questions/_filter_subnav.html.erb @@ -0,0 +1,12 @@ +
    +
  • + <%= link_to "#tab-questions" do %> + <%= t("admin.questions.index.questions_tab") %> + <% end %> +
  • +
  • + <%= link_to "#tab-successful-proposals" do %> + <%= t("admin.questions.index.successful_proposals_tab") %> + <% end %> +
  • +
diff --git a/app/views/admin/poll/questions/_form.html.erb b/app/views/admin/poll/questions/_form.html.erb new file mode 100644 index 000000000..0da36913a --- /dev/null +++ b/app/views/admin/poll/questions/_form.html.erb @@ -0,0 +1,37 @@ +<%= form_for(@question, url: form_url) do |f| %> + + <%= render 'shared/errors', resource: @question %> + + <%= f.hidden_field :proposal_id %> + +
+ +
+
+ <%= f.select :poll_id, + options_for_select(Poll.pluck(:name, :id)), + prompt: t("admin.questions.index.select_poll"), + label: t("admin.questions.new.poll_label") %> +
+ + <%= f.text_field :title, maxlength: Poll::Question.title_max_length %> + + <%= f.label :valid_answers %> +

<%= t("admin.questions.new.valid_answers_note") %>

+ <%= f.text_field :valid_answers, label: false %> + +
+ <%= f.cktext_area :description, + maxlength: Poll::Question.description_max_length, + ckeditor: { language: I18n.locale } %> +
+ +
+
+ <%= f.submit(class: "button expanded", value: t("shared.save")) %> +
+
+
+
+ +<% end %> diff --git a/app/views/admin/poll/questions/_questions.html.erb b/app/views/admin/poll/questions/_questions.html.erb new file mode 100644 index 000000000..c142ad084 --- /dev/null +++ b/app/views/admin/poll/questions/_questions.html.erb @@ -0,0 +1,31 @@ +
+ <%= render 'filter' %> +
+ +<% if @questions.count == 0 %> +
+ <%= t('admin.questions.index.no_questions') %> +
+<% else %> + + + + + + + + + <% @questions.each do |question| %> + + + + + <% end %> + +
<%= t('admin.questions.index.table_question') %><%= t("admin.actions.actions") %>
<%= link_to question.title, admin_question_path(question) %> + <%= link_to t('shared.edit'), edit_admin_question_path(question), class: "button hollow" %> + <%= link_to t('shared.delete'), admin_question_path(question), class: "button hollow alert", method: :delete %> +
+ + <%= paginate @questions %> +<% end %> diff --git a/app/views/admin/poll/questions/_search.html.erb b/app/views/admin/poll/questions/_search.html.erb new file mode 100644 index 000000000..e8a2c52c5 --- /dev/null +++ b/app/views/admin/poll/questions/_search.html.erb @@ -0,0 +1,10 @@ +<%= form_tag(admin_questions_path, method: :get) do |f| %> +
+ <%= text_field_tag :search, + @search, + placeholder: t("admin.shared.poll_questions_search.placeholder") %> +
+ <%= submit_tag t("admin.shared.poll_questions_search.button"), class: "button" %> +
+
+<% end %> diff --git a/app/views/admin/poll/questions/_successful_proposals.html.erb b/app/views/admin/poll/questions/_successful_proposals.html.erb new file mode 100644 index 000000000..2b192bccd --- /dev/null +++ b/app/views/admin/poll/questions/_successful_proposals.html.erb @@ -0,0 +1,26 @@ + + + + + + + + + <% @proposals.each do |proposal| %> + + + + + <% end %> + +
<%= t('admin.questions.index.table_proposal') %><%= t("admin.actions.actions") %>
+ <%= link_to proposal.title, proposal_path(proposal) %> +

+ <%= proposal.summary %>
+ <%= proposal.question %> +

+
+ <%= link_to t("admin.questions.index.create_question"), + new_admin_question_path(proposal_id: proposal.id), + class: "button hollow" %> +
diff --git a/app/views/admin/poll/questions/edit.html.erb b/app/views/admin/poll/questions/edit.html.erb new file mode 100644 index 000000000..6c4d24adf --- /dev/null +++ b/app/views/admin/poll/questions/edit.html.erb @@ -0,0 +1,5 @@ +<%= render "shared/back_link" %> + +

<%= t("admin.questions.edit.title") %>

+ +<%= render "form", form_url: admin_question_path(@question) %> \ No newline at end of file diff --git a/app/views/admin/poll/questions/index.html.erb b/app/views/admin/poll/questions/index.html.erb new file mode 100644 index 000000000..f45497af0 --- /dev/null +++ b/app/views/admin/poll/questions/index.html.erb @@ -0,0 +1,22 @@ +

<%= t('admin.questions.index.title') %>

+ +<%= link_to t('admin.questions.index.create'), new_admin_question_path, + class: "button success float-right" %> + +
+
+ <%= render 'search' %> +
+
+ +
+ <%= render "filter_subnav" %> + +
+ <%= render "questions" %> +
+ +
+ <%= render "successful_proposals" %> +
+
diff --git a/app/views/admin/poll/questions/new.html.erb b/app/views/admin/poll/questions/new.html.erb new file mode 100644 index 000000000..830492d6d --- /dev/null +++ b/app/views/admin/poll/questions/new.html.erb @@ -0,0 +1,5 @@ +<%= render "shared/back_link" %> + +

<%= t("admin.questions.new.title") %>

+ +<%= render "form", form_url: admin_questions_path %> \ No newline at end of file diff --git a/app/views/admin/poll/questions/show.html.erb b/app/views/admin/poll/questions/show.html.erb new file mode 100644 index 000000000..a8f25f2bd --- /dev/null +++ b/app/views/admin/poll/questions/show.html.erb @@ -0,0 +1,46 @@ +<%= render "shared/back_link" %> + +<%= link_to t('shared.edit'), edit_admin_question_path(@question), + class: "button hollow float-right" %> + +
+ +
+
+ <%= t("admin.questions.show.title") %> +

<%= @question.title %>

+ + <% if @question.proposal.present? %> +

+ <%= t("admin.questions.show.proposal") %> +
+ <%= link_to @question.proposal.title, proposal_path(@question.proposal) %> +

+ <% end %> + +

+ <%= t("admin.questions.show.author") %> +
+ <%= link_to @question.author.name, user_path(@question.author) %> +

+ +

+ <%= t("admin.questions.show.valid_answers") %> +

+ + <% @question.valid_answers.each do |answer| %> + + <%= answer %> + + <% end %> + +

+ <%= t("admin.questions.show.description") %> +
+ <%= @question.description %> +

+ + + <%= link_to t("admin.questions.show.preview"), question_path(@question) %> +
+
diff --git a/app/views/admin/poll/recounts/index.html.erb b/app/views/admin/poll/recounts/index.html.erb new file mode 100644 index 000000000..98055f553 --- /dev/null +++ b/app/views/admin/poll/recounts/index.html.erb @@ -0,0 +1,58 @@ +<%= render "/admin/poll/polls/poll_header" %> +
+ <%= render "/admin/poll/polls/subnav" %> + +

<%= t("admin.recounts.index.title") %>

+ + <% if @booth_assignments.empty? %> +
+ <%= t("admin.recounts.index.no_recounts") %> +
+ <% else %> + + + + + + + + + <% @booth_assignments.each do |booth_assignment| %> + <% recount = booth_assignment_sum_recounts(booth_assignment) %> + <% final_recount = booth_assignment_sum_final_recounts(booth_assignment) %> + <% system_count = booth_assignment.voters.size %> + + + + + + + <% end %> + +
<%= t("admin.recounts.index.table_booth_name") %><%= t("admin.recounts.index.table_recounts") %><%= t("admin.recounts.index.table_final_recount") %><%= t("admin.recounts.index.table_system_count") %>
+ + <%= link_to booth_assignment.booth.name, admin_poll_booth_assignment_path(@poll, booth_assignment, anchor: 'tab-recounts') %> + + + <% if recount.present? %> + <%= recount %> + <% else %> + - + <% end %> + + <% if final_recount.present? %> + <%= final_recount %> + <% else %> + - + <% end %> + + <% if system_count.present? %> + <%= system_count %> + <% else %> + 0 + <% end %> +
+ + <%= paginate @booth_assignments %> + <% end %> +
\ No newline at end of file diff --git a/app/views/admin/poll/results/index.html.erb b/app/views/admin/poll/results/index.html.erb new file mode 100644 index 000000000..9d3a6abef --- /dev/null +++ b/app/views/admin/poll/results/index.html.erb @@ -0,0 +1,50 @@ +<%= render "/admin/poll/polls/poll_header" %> +
+ <%= render "/admin/poll/polls/subnav" %> + +

<%= t("admin.results.index.title") %>

+ + <% if @partial_results.empty? %> +
+ <%= t("admin.results.index.no_results") %> +
+ <% else %> + + + + + + + + + + + + +
<%= t("admin.results.index.table_whites") %><%= t("admin.results.index.table_nulls") %>
<%= @poll.white_results.sum(:amount) %><%= @poll.null_results.sum(:amount) %>
+ + + <% by_question = @partial_results.group_by(&:question_id) %> + <% @poll.questions.each do |question| %> +

<%= question.title %>

+ + + + + + + + + <% question.valid_answers.each_with_index do |answer, i| %> + <% by_answer = by_question[question.id].present? ? by_question[question.id].group_by(&:answer) : {} %> + + + + + <% end %> + +
<%= t("admin.results.index.table_answer") %><%= t("admin.results.index.table_votes") %>
<%= answer %><%= by_answer[answer].present? ? by_answer[answer].sum(&:amount) : 0 %>
+ <% end %> + + <% end %> +
\ No newline at end of file diff --git a/app/views/admin/site_customization/pages/_form.html.erb b/app/views/admin/site_customization/pages/_form.html.erb index 3881885d2..197520631 100644 --- a/app/views/admin/site_customization/pages/_form.html.erb +++ b/app/views/admin/site_customization/pages/_form.html.erb @@ -41,7 +41,7 @@

<%= f.label :status %>

<% ::SiteCustomization::Page::VALID_STATUSES.each do |status| %> <%= f.radio_button :status, status, label: false %> - <%= f.label "status_#{status}", t("admin.site_customization.pages.status_#{status}") %> + <%= f.label "status_#{status}", t("admin.site_customization.pages.page.status_#{status}") %>
<% end %> diff --git a/app/views/admin/stats/show.html.erb b/app/views/admin/stats/show.html.erb index ef17536c3..cd83201a4 100644 --- a/app/views/admin/stats/show.html.erb +++ b/app/views/admin/stats/show.html.erb @@ -11,7 +11,8 @@ direct_messages_admin_stats_path, class: "button hollow" %> <%= link_to t("admin.stats.show.proposal_notifications"), proposal_notifications_admin_stats_path, class: "button hollow" %> - + <%= link_to t("admin.stats.show.incomplete_verifications"), + admin_verifications_path, class: "button hollow" %>
diff --git a/app/views/debates/index.html.erb b/app/views/debates/index.html.erb index 45d27bd56..e38c52233 100644 --- a/app/views/debates/index.html.erb +++ b/app/views/debates/index.html.erb @@ -31,11 +31,11 @@ <%= render "shared/banner" %> <% end %> - <% if @proposal_successfull_exists %> - <%= render "proposals/proposal_ballots_banner" %> + <% if @proposal_successful_exists %> + <%= render "proposals/successful_banner" %> <% end %> - <% unless @tag_filter || @search_terms || !has_featured? || @proposal_ballots.present? || @proposal_successfull_exists %> + <% unless @tag_filter || @search_terms || !has_featured? || @proposal_successful_exists %> <%= render "featured_debates" %> <% end %> diff --git a/app/views/layouts/_admin_header.html.erb b/app/views/layouts/_admin_header.html.erb index 72617386c..8597b41de 100644 --- a/app/views/layouts/_admin_header.html.erb +++ b/app/views/layouts/_admin_header.html.erb @@ -1,14 +1,16 @@
- +
- <%= link_to setting['org_name'], admin_root_path, class: "logo show-for-small-only" %> + <%= link_to setting['org_name'], namespaced_root_path, class: "logo show-for-small-only" %> @@ -17,11 +19,13 @@
- <%= link_to admin_root_path, class: "hide-for-small-only" do %> - <%= image_tag(image_path_for('logo_header.png'), class: 'hide-for-small-only float-left', size: '80x80', alt: t("layouts.header.logo")) %> - <%= setting['org_name'] %> -  | <%= t("admin.dashboard.index.title") %> - <% end %> +

+ <%= link_to namespaced_root_path, class: "hide-for-small-only" do %> + <%= image_tag(image_path_for('logo_header.png'), class: 'hide-for-small-only float-left', size: '80x80', alt: t("layouts.header.logo")) %> + <%= setting['org_name'] %> +  | <%= namespaced_header_title %> + <% end %> +

diff --git a/app/views/layouts/admin.html.erb b/app/views/layouts/admin.html.erb index 95bcae3e3..198bf3adf 100644 --- a/app/views/layouts/admin.html.erb +++ b/app/views/layouts/admin.html.erb @@ -30,7 +30,7 @@
<%= render 'layouts/admin_header' %> -
+
@@ -42,11 +42,11 @@
-
+
<%= render 'layouts/flash' %> <%= yield %>
-
+
diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index 7ae505aad..7f66b524e 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -27,7 +27,7 @@

<%= setting['org_name'] %>

-
+
<%= render 'layouts/header' %> +
+
+ + <%= select_tag :officer_assignment_id, + officer_assignments_select_options(@officer_assignments), + { prompt: t("officing.recounts.new.select_booth_date"), + label: false } %> +
+
+ +
+
+ + <%= text_field_tag :count, nil, placeholder: t("officing.recounts.new.count_placeholder") %> +
+
+ +
+
+ <%= submit_tag t("officing.recounts.new.submit"), class: "button expanded" %> +
+
+ <% end %> +<% else %> +

<%= @poll.name %>

+
+ <%= t("officing.recounts.new.not_allowed") %> +
+<% end %> + + +<% if @recounted.any? %> +
+

<%= t("officing.recounts.new.recount_list") %>

+ + + + + + + + + <% @recounted.each do |oa| %> + + + + + + <% end %> + +
<%= t("officing.recounts.new.date") %><%= t("officing.recounts.new.booth") %><%= t("officing.recounts.new.count") %>
+ <%= l(oa.date.to_date, format: :long) %> + + <%= oa.booth_assignment.booth.name %> + + <%= oa.recount.count %> +
+<% end %> diff --git a/app/views/officing/residence/_errors.html.erb b/app/views/officing/residence/_errors.html.erb new file mode 100644 index 000000000..4f8895a35 --- /dev/null +++ b/app/views/officing/residence/_errors.html.erb @@ -0,0 +1,14 @@ +<% if @residence.errors[:residence_in_madrid].present? %> + +
+ + <%= t("officing.residence.new.error_verifying_census") %> +
+ +<% else %> + <%= render "shared/errors", + resource: @residence, + message: t("officing.residence.new.form_errors") %> +<% end %> diff --git a/app/views/officing/residence/new.html.erb b/app/views/officing/residence/new.html.erb new file mode 100644 index 000000000..a508fae52 --- /dev/null +++ b/app/views/officing/residence/new.html.erb @@ -0,0 +1,32 @@ +

<%= t("officing.residence.new.title") %>

+ +<% if @officer_assignments.present? %> + +<% else %> +
+ <%= t("officing.residence.new.no_assignments") %> +
+<% end %> diff --git a/app/views/officing/results/index.html.erb b/app/views/officing/results/index.html.erb new file mode 100644 index 000000000..70da6d8d3 --- /dev/null +++ b/app/views/officing/results/index.html.erb @@ -0,0 +1,57 @@ +<%= back_link_to new_officing_poll_result_path(@poll) %> +

<%= @poll.name %> - <%= t("officing.results.index.results") %>

+ +<% if @partial_results.present? %> +
+

+ <%= @booth_assignment.booth.name %> - <%= l @partial_results.first.date, format: :long %> +

+
+ + +
+
+ + + + + + + + + + + + + +
<%= t("officing.results.index.table_whites") %><%= t("officing.results.index.table_nulls") %>
<%= @whites %><%= @nulls %>
+ + <% by_question = @partial_results.group_by(&:question_id) %> + <% @poll.questions.each do |question| %> +

<%= question.title %>

+ + + + + + + + + + <% question.valid_answers.each_with_index do |answer, i| %> + <% by_answer = by_question[question.id].present? ? by_question[question.id].group_by(&:answer) : {} %> + + + + + <% end %> + +
<%= t("officing.results.index.table_answer") %><%= t("officing.results.index.table_votes") %>
<%= answer %><%= by_answer[answer].present? ? by_answer[answer].first.amount : 0 %>
+ <% end %> +
+
+<% else %> +
+ <%= t("officing.results.index.no_results") %> +
+<% end %> \ No newline at end of file diff --git a/app/views/officing/results/new.html.erb b/app/views/officing/results/new.html.erb new file mode 100644 index 000000000..74af8af10 --- /dev/null +++ b/app/views/officing/results/new.html.erb @@ -0,0 +1,99 @@ +<% if @officer_assignments.any? %> +

<%= t("officing.results.new.title", poll: @poll.name) %>

+ + <%= form_tag(officing_poll_results_path(@poll), {id: "officer_assignment_form"}) do %> +
+
+ + <%= select_tag :officer_assignment_id, + booths_for_officer_select_options(@officer_assignments), + { prompt: t("officing.results.new.select_booth"), + label: false } %> +
+
+ +
+
+ + <%= select_tag :date, + poll_dates_select_options(@poll), + { prompt: t("officing.results.new.select_date"), + label: false } %> +
+
+ + <% @poll.questions.each do |question| %> +
+
+

<%= question.title %>

+
+ <% question.valid_answers.each_with_index do |answer, i| %> +
+ + <%= text_field_tag "questions[#{question.id}][#{i}]", answer_result_value(question.id, i), placeholder: "0" %> +
+ <% end %> +
+
+ <% end %> + +
+
+

<%= t("officing.results.new.ballots_white") %>

+ <%= text_field_tag :whites, params[:whites].presence, placeholder: "0" %> +
+ +
+

<%= t("officing.results.new.ballots_null") %>

+ <%= text_field_tag :nulls, params[:nulls].presence, placeholder: "0" %> +
+
+
+ +
+
+ <%= submit_tag t("officing.results.new.submit"), class: "button expanded" %> +
+
+ <% end %> + +<% else %> +

<%= @poll.name %>

+
+ <%= t("officing.results.new.not_allowed") %> +
+<% end %> + +<% if @partial_results.present? %> + +
+

<%= t("officing.results.new.results_list") %>

+ + + + + + + + + <% results_by_booth = @partial_results.group_by(&:booth_assignment_id) %> + <% results_by_booth.keys.each do |booth_assignment| %> + <% results_by_booth[booth_assignment].group_by(&:date).keys.each do |date| %> + + + + + + <% end %> + <% end %> + +
<%= t("officing.results.new.date") %><%= t("officing.results.new.booth") %> 
+ <%= l(date, format: :long) %> + + <%= results_by_booth[booth_assignment].first.booth_assignment.booth.name %> + + <%= link_to t("officing.results.new.see_results"), officing_poll_results_path(@poll, date: l(date), booth_assignment_id: booth_assignment) %> +
+ +<% end %> + diff --git a/app/views/officing/voters/_already_voted.html.erb b/app/views/officing/voters/_already_voted.html.erb new file mode 100644 index 000000000..1476913f9 --- /dev/null +++ b/app/views/officing/voters/_already_voted.html.erb @@ -0,0 +1,7 @@ +
+
+
+ <%= t("officing.voters.show.error_already_voted") %> +
+
+
diff --git a/app/views/officing/voters/_can_vote.html.erb b/app/views/officing/voters/_can_vote.html.erb new file mode 100644 index 000000000..27c5e9dbc --- /dev/null +++ b/app/views/officing/voters/_can_vote.html.erb @@ -0,0 +1,14 @@ +
+
+
+ <%= t("officing.voters.show.can_vote") %> +
+
+
+ <%= form_for @user, as: :voter, url: officing_voters_path, method: :post, remote: true do |f| %> + <%= f.hidden_field :poll_id, value: poll.id %> + <%= f.hidden_field :user_id, value: @user.id %> + <%= f.submit t("officing.voters.show.submit"), class: "button success expanded" %> + <% end %> +
+
diff --git a/app/views/officing/voters/_voted.html.erb b/app/views/officing/voters/_voted.html.erb new file mode 100644 index 000000000..edd098403 --- /dev/null +++ b/app/views/officing/voters/_voted.html.erb @@ -0,0 +1,7 @@ +
+
+
+ <%= t("officing.voters.show.success") %> +
+
+
diff --git a/app/views/officing/voters/create.js.erb b/app/views/officing/voters/create.js.erb new file mode 100644 index 000000000..ee280faa2 --- /dev/null +++ b/app/views/officing/voters/create.js.erb @@ -0,0 +1 @@ +$("#<%= dom_id(@poll) %> #actions").html('<%= j render("voted") %>'); \ No newline at end of file diff --git a/app/views/officing/voters/new.html.erb b/app/views/officing/voters/new.html.erb new file mode 100644 index 000000000..0f8fa2958 --- /dev/null +++ b/app/views/officing/voters/new.html.erb @@ -0,0 +1,30 @@ +<%= back_link_to new_officing_residence_path %> + +

<%= t("officing.voters.new.title") %>

+ +<% if @polls.any? %> + + + + + + + + + <% @polls.each do |poll| %> + + + + + <% end %> + +
<%= t("officing.voters.new.table_poll") %><%= t("officing.voters.new.table_actions") %>
+ <%= poll.name %> + + <% if poll.votable_by?(@user) %> + <%= render "can_vote", poll: poll %> + <% else %> + <%= render "already_voted" %> + <% end %> +
+<% end %> \ No newline at end of file diff --git a/app/views/polls/_callout.html.erb b/app/views/polls/_callout.html.erb new file mode 100644 index 000000000..2fad7d07d --- /dev/null +++ b/app/views/polls/_callout.html.erb @@ -0,0 +1,22 @@ +<% unless can?(:answer, @poll) %> + <% if current_user.nil? %> +
+ <%= t("polls.show.cant_answer_not_logged_in", + signin: link_to(t("polls.show.signin"), new_user_session_path, class: "probe-message"), + signup: link_to(t("polls.show.signup"), new_user_registration_path, class: "probe-message")).html_safe %> +
+ <% elsif current_user.unverified? %> +
+ <%= t('polls.show.cant_answer_verify_html', + verify_link: link_to(t('polls.show.verify_link'), verification_path)) %> +
+ <% elsif @poll.incoming? %> +
+ <%= t('polls.show.cant_answer_incoming') %> +
+ <% elsif @poll.expired? %> +
+ <%= t('polls.show.cant_answer_expired') %> +
+ <% end %> +<% end %> diff --git a/app/views/polls/_poll_group.html.erb b/app/views/polls/_poll_group.html.erb new file mode 100644 index 000000000..f4dcc5423 --- /dev/null +++ b/app/views/polls/_poll_group.html.erb @@ -0,0 +1,75 @@ +<% poll_group.each do |poll| %> +
+ <% if poll.answerable_by?(current_user) && poll.votable_by?(current_user) %> + <%= link_to poll, + class: "icon-poll-answer can-answer", + title: t("polls.index.can_answer") do %> + + <%= t("polls.index.can_answer") %> + + <% end %> + <% elsif current_user.nil? %> + <%= link_to new_user_session_path, + class: "icon-poll-answer not-logged-in", + title: t("polls.index.cant_answer_not_logged_in") do %> + + <%= t("polls.index.cant_answer_not_logged_in") %> + + <% end %> + <% elsif current_user.unverified? %> + <%= link_to verification_path, + class: "icon-poll-answer unverified", + title: t("polls.index.cant_answer_verify") do %> + + <%= t("polls.index.cant_answer_verify") %> + + <% end %> + <% elsif !poll.votable_by?(current_user) %> +
"> + <%= t("polls.index.already_answer") %> +
+ <% else %> +
"> + <%= t("polls.index.cant_answer") %> +
+ <% end %> +
+
+
<%= poll_dates(poll) %>
+ <% if poll.questions.count == 1 %> + <% poll.questions.each do |question| %> +

<%= link_to question.title, poll %>

+ <% end %> + <% else %> +

<%= link_to poll.name, poll %>

+
    + <% poll.questions.each do |question| %> +
  • <%= link_to question.title, question_path(question) %>
  • + <% end %> +
+ <% end %> + <% if poll.geozones.any? %> +

+ <%= t("polls.index.geozone_info") %> +

+ <% end %> +
    + <% poll.geozones.each do |g| %> +
  • <%= g.name %>
  • + <% 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") %> + <% end %> + <% end %> +
+
+
+<% end %> diff --git a/app/views/polls/_reasons_for_not_answering.html.erb b/app/views/polls/_reasons_for_not_answering.html.erb new file mode 100644 index 000000000..bd0bab3ad --- /dev/null +++ b/app/views/polls/_reasons_for_not_answering.html.erb @@ -0,0 +1,24 @@ +<% if poll.incoming? %> +
+ <%= t('poll_questions.show.cant_answer_incoming') %> +
+<% elsif poll.expired? %> +
+ <%= t('poll_questions.show.cant_answer_expired') %> +
+<% elsif current_user.nil? %> +
+ <%= t("poll_questions.show.not_logged_in", + signin: link_to(t("poll_questions.show.signin"), new_user_session_path, class: "probe-message"), + signup: link_to(t("poll_questions.show.signup"), new_user_registration_path, class: "probe-message")).html_safe %> +
+<% elsif current_user.unverified? %> +
+ <%= t('poll_questions.show.cant_answer_verify_html', + verify_link: link_to(t('poll_questions.show.verify_link'), verification_path)) %> +
+<% else %> +
+ <%= t('poll_questions.show.cant_answer_wrong_geozone') %> +
+<% end %> diff --git a/app/views/polls/index.html.erb b/app/views/polls/index.html.erb new file mode 100644 index 000000000..d8c328267 --- /dev/null +++ b/app/views/polls/index.html.erb @@ -0,0 +1,32 @@ +<% provide :title do %><%= t("polls.index.title") %><% end %> +<% content_for :wrapper_class, "light" %> + +
+
+
+

<%= t("polls.index.title") %>

+
+
+
+ +
+
+ <%= render 'shared/filter_subnav_vertical', i18n_namespace: "polls.index" %> +
+ +
+ <% polls_by_geozone_restriction = @polls.group_by(&:geozone_restricted) %> + + <% if polls_by_geozone_restriction[false].present? %> +

<%= t("polls.index.no_geozone_restricted") %>

+ <%= render partial: 'poll_group', locals: {poll_group: polls_by_geozone_restriction[false]} %> + <% end %> + + <% if polls_by_geozone_restriction[true].present? %> +

<%= t("polls.index.geozone_restricted") %>

+ <%= render partial: 'poll_group', locals: {poll_group: polls_by_geozone_restriction[true]} %> + <% end %> + + <%= paginate @polls %> +
+
diff --git a/app/views/polls/questions/_answers.html.erb b/app/views/polls/questions/_answers.html.erb new file mode 100644 index 000000000..c88156b68 --- /dev/null +++ b/app/views/polls/questions/_answers.html.erb @@ -0,0 +1,22 @@ +
+ <% if can? :answer, question %> + <% question.valid_answers.each do |answer| %> + <% if @answers_by_question_id[question.id] == answer %> + "> + <%= answer %> + + <% else %> + <%= link_to answer, + answer_question_path(question, answer: answer), + method: :post, + remote: true, + title: t("poll_questions.show.vote_answer", answer: answer), + class: "button secondary hollow" %> + <% end %> + <% end %> + <% else %> + <% question.valid_answers.each do |answer| %> + <%= answer %> + <% end %> + <% end %> +
diff --git a/app/views/polls/questions/_comments.html.erb b/app/views/polls/questions/_comments.html.erb new file mode 100644 index 000000000..a967406aa --- /dev/null +++ b/app/views/polls/questions/_comments.html.erb @@ -0,0 +1,31 @@ +<% cache [locale_and_user_status, @current_order, commentable_cache_key(@commentable), @comment_tree.comments, @comment_tree.comment_authors, @commentable.comments_count, @comment_flags] do %> +
+
+
+

+ <%= t("shared.comments.title") %> + (<%= @commentable.comments_count %>) +

+ + <%= render 'shared/wide_order_selector', i18n_namespace: "comments" %> + + <% if user_signed_in? %> + <%= render 'comments/form', {commentable: @commentable, parent_id: nil, toggeable: false} %> + <% else %> +
+ +
+ <%= t("shared.comments.login_to_comment", + signin: link_to(t("votes.signin"), new_user_session_path), + signup: link_to(t("votes.signup"), new_user_registration_path)).html_safe %> +
+ <% end %> + + <% @comment_tree.root_comments.each do |comment| %> + <%= render 'comments/comment', comment: comment %> + <% end %> + <%= paginate @comment_tree.root_comments %> +
+
+
+<% end %> \ No newline at end of file diff --git a/app/views/polls/questions/_question.html.erb b/app/views/polls/questions/_question.html.erb new file mode 100644 index 000000000..f0958b6e4 --- /dev/null +++ b/app/views/polls/questions/_question.html.erb @@ -0,0 +1,9 @@ +
+

+ <%= link_to question.title, question_path(question) %> +

+ +
+ <%= render 'polls/questions/answers', question: question %> +
+
diff --git a/app/views/polls/questions/answer.js.erb b/app/views/polls/questions/answer.js.erb new file mode 100644 index 000000000..aabbd8d89 --- /dev/null +++ b/app/views/polls/questions/answer.js.erb @@ -0,0 +1 @@ +$("#<%= dom_id(@question) %>_answers").html('<%= j render("polls/questions/answers", question: @question) %>'); diff --git a/app/views/polls/questions/show.html.erb b/app/views/polls/questions/show.html.erb new file mode 100644 index 000000000..2b7225f9f --- /dev/null +++ b/app/views/polls/questions/show.html.erb @@ -0,0 +1,65 @@ +<% provide :title do %><%= @question.title %><% end %> + +
+
+
+ <%= render "shared/back_link" %> + +

<%= @question.title %>

+ + <% if @question.proposal.present? %> +
+ <%= link_to t('poll_questions.show.original_proposal'), @question.proposal %> +
+ <% end %> + + <% if can? :answer, @question %> + <%= link_to t('poll_questions.show.answer_this_question'), + @question.poll, + class: 'large button' %> + <% else %> + <%= render 'polls/reasons_for_not_answering', poll: @question.poll %> + <% end %> +
+ +
+

+ + <%= t('poll_questions.show.author') %> + +
+ <% if @question.author_visible_name.present? %> + <%= @question.author_visible_name %> + <% else %> + <%= link_to @question.author.name, @question.author %> + <% end %> + +

+ +

+ + <%= t('poll_questions.show.poll') %> + +
+ <%= link_to @question.poll.name, @question.poll %> +

+ +

+ + <%= t('poll_questions.show.dates_title') %> + +
+ <%= poll_dates(@question.poll) %> +

+
+
+
+ +
+
+

<%= t('poll_questions.show.more_info') %>

+ <%= @question.description %> +
+
+ +<%= render "comments" %> diff --git a/app/views/polls/show.html.erb b/app/views/polls/show.html.erb new file mode 100644 index 000000000..8178928b6 --- /dev/null +++ b/app/views/polls/show.html.erb @@ -0,0 +1,53 @@ +<% provide :title do %><%= @poll.name %><% end %> +<% content_for :wrapper_class, "light" %> + +
+
+
+
+ <%= back_link_to polls_path %> + +

<%= @poll.name %>

+
    + <% @poll.geozones.each do |g| %> +
  • <%= g.name %>
  • + <% end %> +
+ + <%= render "callout" %> +
+
+
+

+ + <%= t("polls.show.dates_title") %> + +
+ <%= poll_dates(@poll) %> +

+
+
+
+
+ +
+
+ <% @questions.each do |question| %> + <%= render 'polls/questions/question', question: question %> + <% end %> +
+ + +
+
diff --git a/app/views/proposal_ballots/_successfull_proposal.html.erb b/app/views/proposal_ballots/_successfull_proposal.html.erb deleted file mode 100644 index 870e61605..000000000 --- a/app/views/proposal_ballots/_successfull_proposal.html.erb +++ /dev/null @@ -1,13 +0,0 @@ -
-

<%= link_to proposal.title, proposal %>

-
- <% if proposal.author.hidden? || proposal.author.erased? %> - <%= t("proposals.show.author_deleted") %> - <% else %> - <%= proposal.author.name %> - <% end %> - -  •  - <%= l proposal.created_at.to_date %> -
-
diff --git a/app/views/proposal_ballots/index.html.erb b/app/views/proposal_ballots/index.html.erb deleted file mode 100644 index fe5bdef70..000000000 --- a/app/views/proposal_ballots/index.html.erb +++ /dev/null @@ -1,36 +0,0 @@ -<% provide :title do %><%= t("proposal_ballots.title") %><% end %> -
-
-
-

- <%= t("proposal_ballots.title") %> -

-

- <%= t("proposal_ballots.description_html").html_safe %> -

-
- -
-

<%= t("proposal_ballots.date_title") %>

-

<%= t("proposal_ballots.date") %>

-
-
-
- -
-
-
- <% if @proposal_ballots.present? %> -
- <% @proposal_ballots.each do |proposal_for_vote| %> - <%= render "successfull_proposal", proposal: proposal_for_vote %> - <% end %> -
- <% else %> -

- <%= t("proposal_ballots.nothing_to_vote") %> -

- <% end %> -
-
-
diff --git a/app/views/proposals/_proposal.html.erb b/app/views/proposals/_proposal.html.erb index 69bf8d164..735e8bcec 100644 --- a/app/views/proposals/_proposal.html.erb +++ b/app/views/proposals/_proposal.html.erb @@ -1,8 +1,8 @@
Proposal.votes_needed_for_success) %>" + class="proposal clear <%= ("successful" if proposal.total_votes > Proposal.votes_needed_for_success) %>" data-type="proposal">
-
+
@@ -53,13 +53,21 @@
- <% if proposal.successfull? %> + <% if proposal.successful? %>
+

- <%= t("proposal_ballots.successfull", - voting: link_to(t("proposal_ballots.voting"), proposal_ballots_path)).html_safe %> + <%= t("proposals.proposal.successful", + voting: link_to(t("proposals.proposal.voting"), polls_path)).html_safe %>

+ <% if can? :create, Poll::Question %> +

+ <%= link_to t('poll_questions.create_question'), + new_admin_question_path(proposal_id: proposal.id), + class: "button hollow" %> +

+ <% end %> <% elsif proposal.archived? %>
<%= t("proposals.proposal.supports", count: proposal.total_votes) %> diff --git a/app/views/proposals/_proposal_ballots_banner.html.erb b/app/views/proposals/_proposal_ballots_banner.html.erb deleted file mode 100644 index df210a535..000000000 --- a/app/views/proposals/_proposal_ballots_banner.html.erb +++ /dev/null @@ -1,9 +0,0 @@ - diff --git a/app/views/proposals/_successful_banner.html.erb b/app/views/proposals/_successful_banner.html.erb new file mode 100644 index 000000000..3bde0e097 --- /dev/null +++ b/app/views/proposals/_successful_banner.html.erb @@ -0,0 +1,9 @@ +
+ <%= link_to polls_path do %> +
+
+

<%= t("poll_questions.banner.featured_title") %>

+

<%= t("poll_questions.banner.info") %>

+
+ <% end %> +
diff --git a/app/views/proposals/index.html.erb b/app/views/proposals/index.html.erb index fd781c04b..e04e563e2 100644 --- a/app/views/proposals/index.html.erb +++ b/app/views/proposals/index.html.erb @@ -33,8 +33,8 @@ <%= render "shared/banner" %> <% end %> - <% if @proposal_successfull_exists %> - <%= render "proposal_ballots_banner" %> + <% if @proposal_successful_exists %> + <%= render "successful_banner" %> <% elsif @featured_proposals.present? %>