diff --git a/app/assets/javascripts/columns_selector.js b/app/assets/javascripts/columns_selector.js index 888dd2c76..40a562510 100644 --- a/app/assets/javascripts/columns_selector.js +++ b/app/assets/javascripts/columns_selector.js @@ -80,9 +80,6 @@ App.ColumnsSelector.toggleColumn(event); } }); - $(".column-selectable").on("inserted", function() { - App.ColumnsSelector.initColumns(); - }); }, destroy: function() { $("#js-columns-selector-wrapper").children(":not(#column_selector_item_template)").remove(); diff --git a/app/assets/stylesheets/admin/budget_investments/investments.scss b/app/assets/stylesheets/admin/budget_investments/investments.scss new file mode 100644 index 000000000..5d931e6ff --- /dev/null +++ b/app/assets/stylesheets/admin/budget_investments/investments.scss @@ -0,0 +1,19 @@ +.admin .admin-budget-investments { + td { + &[data-field=supports], + &[data-field=valuation_finished], + &[data-field=visible_to_valuators], + &[data-field=selected], + &[data-field=incompatible] { + text-align: center; + } + + &:not([data-field=id], [data-field=title], [data-field=supports]) { + font-size: $small-font-size; + } + } + + .toggle-switch [aria-pressed] { + font-size: inherit; + } +} diff --git a/app/assets/stylesheets/mixins/buttons.scss b/app/assets/stylesheets/mixins/buttons.scss index ee09362ac..72691b9f1 100644 --- a/app/assets/stylesheets/mixins/buttons.scss +++ b/app/assets/stylesheets/mixins/buttons.scss @@ -85,7 +85,7 @@ @include regular-button; border-radius: $line-height; font-weight: bold; - min-width: rem-calc(100); + min-width: 6em; position: relative; &::after { diff --git a/app/components/admin/budget_investments/investments_component.html.erb b/app/components/admin/budget_investments/investments_component.html.erb new file mode 100644 index 000000000..f5e0f4a59 --- /dev/null +++ b/app/components/admin/budget_investments/investments_component.html.erb @@ -0,0 +1,64 @@ +
+ <%= link_to t("admin.budget_investments.index.download_current_selection"), + admin_budget_budget_investments_path(csv_params), + class: "float-right small clear" %> + + <% if params[:advanced_filters].include?("winners") %> + <%= render Admin::Budgets::CalculateWinnersButtonComponent.new(budget, from_investments: true) %> + <% end %> + + <% if investments.any? %> +

<%= page_entries_info investments %>

+ <%= render "admin/shared/columns_selector", + cookie: "investments-columns", + default: %w[id title supports admin valuator geozone feasibility price valuation_finished visible_to_valuators selected incompatible] %> +
+ + <%= render "filters_description", i18n_namespace: "admin.budget_investments.index" %> + + + + + + + + + + + + + <% if budget.show_money? %> + + <% end %> + + + + <% if params[:advanced_filters]&.include?("selected") %> + + <% end %> + + + + + <% investments.each do |investment| %> + <%= render Admin::BudgetInvestments::RowComponent.new(investment) %> + <% end %> + +
<%= link_to_investments_sorted_by :id %><%= link_to_investments_sorted_by :title %><%= link_to_investments_sorted_by :supports %><%= t("admin.budget_investments.index.list.admin") %> + <%= t("admin.budget_investments.index.list.author") %> + + <%= t("admin.budget_investments.index.list.valuation_group") %> / + <%= t("admin.budget_investments.index.list.valuator") %> + <%= t("admin.budget_investments.index.list.geozone") %><%= t("admin.budget_investments.index.list.feasibility") %><%= t("admin.budget_investments.index.list.price") %> + <%= t("admin.budget_investments.index.list.valuation_finished") %> + + <%= t("admin.budget_investments.index.list.visible_to_valuators") %> + <%= t("admin.budget_investments.index.list.selected") %><%= t("admin.budget_investments.index.list.incompatible") %>
+ + <%= paginate investments %> + <% else %> +
+ <%= t("admin.budget_investments.index.no_budget_investments") %> +
+ <% end %> +
diff --git a/app/components/admin/budget_investments/investments_component.rb b/app/components/admin/budget_investments/investments_component.rb new file mode 100644 index 000000000..74f7f439b --- /dev/null +++ b/app/components/admin/budget_investments/investments_component.rb @@ -0,0 +1,30 @@ +class Admin::BudgetInvestments::InvestmentsComponent < ApplicationComponent + attr_reader :budget, :investments + use_helpers :set_direction, :set_sorting_icon + + def initialize(budget, investments) + @budget = budget + @investments = investments + end + + private + + def csv_params + csv_params = params.clone.merge(format: :csv) + csv_params = csv_params.to_unsafe_h.transform_keys(&:to_sym) + csv_params.delete(:page) + csv_params + end + + def link_to_investments_sorted_by(column) + direction = set_direction(params[:direction]) + icon = set_sorting_icon(direction, column) + + translation = t("admin.budget_investments.index.list.#{column}") + + link_to( + safe_join([translation, tag.span(class: "icon-sortable #{icon}")]), + admin_budget_budget_investments_path(sort_by: column, direction: direction) + ) + end +end diff --git a/app/components/admin/budget_investments/row_component.html.erb b/app/components/admin/budget_investments/row_component.html.erb new file mode 100644 index 000000000..3c191a726 --- /dev/null +++ b/app/components/admin/budget_investments/row_component.html.erb @@ -0,0 +1,57 @@ + + + <%= investment.id %> + + + + <%= link_to investment.title, investment_path, target: "_blank" %> + + + + <%= investment.total_votes %> + + + + <%= administrator_info %> + + + + <%= investment.author.name %> + + + + <%= valuators_info %> + + + + <%= investment.heading.name %> + + + + <%= t("admin.budget_investments.index.feasibility.#{investment.feasibility}") %> + + + <% if budget.show_money? %> + + <%= investment.formatted_price %> + + <% end %> + + + <%= investment.valuation_finished? ? t("shared.yes") : t("shared.no") %> + + + + <%= render Admin::BudgetInvestments::ToggleVisibleToValuatorsComponent.new(investment) %> + + + + <%= render Admin::BudgetInvestments::ToggleSelectionComponent.new(investment) %> + + + <% if params[:advanced_filters]&.include?("selected") %> + + <%= investment.incompatible? ? t("shared.yes") : t("shared.no") %> + + <% end %> + diff --git a/app/components/admin/budget_investments/row_component.rb b/app/components/admin/budget_investments/row_component.rb new file mode 100644 index 000000000..c48156735 --- /dev/null +++ b/app/components/admin/budget_investments/row_component.rb @@ -0,0 +1,38 @@ +class Admin::BudgetInvestments::RowComponent < ApplicationComponent + attr_reader :investment + + def initialize(investment) + @investment = investment + end + + private + + def budget + investment.budget + end + + def investment_path + admin_budget_budget_investment_path(budget_id: budget.id, + id: investment.id, + params: Budget::Investment.filter_params(params).to_h) + end + + def administrator_info + if investment.administrator.present? + tag.span(investment.administrator.description_or_name, + title: t("admin.budget_investments.index.assigned_admin")) + else + t("admin.budget_investments.index.no_admin_assigned") + end + end + + def valuators_info + valuators = [investment.assigned_valuation_groups, investment.assigned_valuators].compact + + if valuators.present? + valuators.join(", ") + else + t("admin.budget_investments.index.no_valuators_assigned") + end + end +end diff --git a/app/components/admin/budget_investments/toggle_selection_component.html.erb b/app/components/admin/budget_investments/toggle_selection_component.html.erb new file mode 100644 index 000000000..a417c6043 --- /dev/null +++ b/app/components/admin/budget_investments/toggle_selection_component.html.erb @@ -0,0 +1,5 @@ +<% if can?(action, investment) %> + <%= render Admin::ToggleSwitchComponent.new(action, investment, pressed: selected?, **options) %> +<% elsif selected? %> + <%= selected_text %> +<% end %> diff --git a/app/components/admin/budget_investments/toggle_selection_component.rb b/app/components/admin/budget_investments/toggle_selection_component.rb new file mode 100644 index 000000000..4e35f425d --- /dev/null +++ b/app/components/admin/budget_investments/toggle_selection_component.rb @@ -0,0 +1,50 @@ +class Admin::BudgetInvestments::ToggleSelectionComponent < ApplicationComponent + attr_reader :investment + use_helpers :can? + delegate :selected?, to: :investment + + def initialize(investment) + @investment = investment + end + + private + + def selected_text + t("admin.budget_investments.index.selected") + end + + def action + if selected? + :deselect + else + :select + end + end + + def path + url_for({ + controller: "admin/budget_investments", + action: action, + budget_id: investment.budget, + id: investment, + filter: params[:filter], + sort_by: params[:sort_by], + min_total_supports: params[:min_total_supports], + max_total_supports: params[:max_total_supports], + advanced_filters: params[:advanced_filters], + page: params[:page] + }) + end + + def options + { + "aria-label": label, + form_class: "toggle-selection", + path: path + } + end + + def label + t("admin.actions.label", action: t("admin.actions.select"), name: investment.title) + end +end diff --git a/app/components/admin/budget_investments/toggle_visible_to_valuators_component.html.erb b/app/components/admin/budget_investments/toggle_visible_to_valuators_component.html.erb new file mode 100644 index 000000000..6233b0355 --- /dev/null +++ b/app/components/admin/budget_investments/toggle_visible_to_valuators_component.html.erb @@ -0,0 +1,5 @@ +<% if can?(:admin_update, investment) %> + <%= render Admin::ToggleSwitchComponent.new(action, investment, pressed: visible_to_valuators?, **options) %> +<% else %> + <%= text %> +<% end %> diff --git a/app/components/admin/budget_investments/toggle_visible_to_valuators_component.rb b/app/components/admin/budget_investments/toggle_visible_to_valuators_component.rb new file mode 100644 index 000000000..2ecdfd8ef --- /dev/null +++ b/app/components/admin/budget_investments/toggle_visible_to_valuators_component.rb @@ -0,0 +1,38 @@ +class Admin::BudgetInvestments::ToggleVisibleToValuatorsComponent < ApplicationComponent + attr_reader :investment + use_helpers :can? + delegate :visible_to_valuators?, to: :investment + + def initialize(investment) + @investment = investment + end + + private + + def action + if visible_to_valuators? + :hide_from_valuators + else + :show_to_valuators + end + end + + def text + if visible_to_valuators? + t("shared.yes") + else + t("shared.no") + end + end + + def options + { + "aria-label": label, + form_class: "visible-to-valuators" + } + end + + def label + t("admin.actions.show_to_valuators", name: investment.title) + end +end diff --git a/app/components/admin/proposals/toggle_selection_component.html.erb b/app/components/admin/proposals/toggle_selection_component.html.erb new file mode 100644 index 000000000..bdb2aaa99 --- /dev/null +++ b/app/components/admin/proposals/toggle_selection_component.html.erb @@ -0,0 +1 @@ +<%= render Admin::ToggleSwitchComponent.new(action, proposal, pressed: selected?, **options) %> diff --git a/app/components/admin/proposals/toggle_selection_component.rb b/app/components/admin/proposals/toggle_selection_component.rb new file mode 100644 index 000000000..dfcb9ea8e --- /dev/null +++ b/app/components/admin/proposals/toggle_selection_component.rb @@ -0,0 +1,31 @@ +class Admin::Proposals::ToggleSelectionComponent < ApplicationComponent + attr_reader :proposal + + def initialize(proposal) + @proposal = proposal + end + + private + + def action + if selected? + :deselect + else + :select + end + end + + def selected? + proposal.selected? + end + + def options + { + "aria-label": label + } + end + + def label + t("admin.actions.label", action: t("admin.actions.select"), name: proposal.title) + end +end diff --git a/app/components/admin/toggle_switch_component.html.erb b/app/components/admin/toggle_switch_component.html.erb index 35b566c48..20639357f 100644 --- a/app/components/admin/toggle_switch_component.html.erb +++ b/app/components/admin/toggle_switch_component.html.erb @@ -1 +1 @@ -<%= render Admin::ActionComponent.new(action, record, **default_options.merge(options)) %> +<%= render Admin::ActionComponent.new(action, record, **html_options) %> diff --git a/app/components/admin/toggle_switch_component.rb b/app/components/admin/toggle_switch_component.rb index ceee74d5b..c61c2dfe0 100644 --- a/app/components/admin/toggle_switch_component.rb +++ b/app/components/admin/toggle_switch_component.rb @@ -25,7 +25,11 @@ class Admin::ToggleSwitchComponent < ApplicationComponent method: :patch, remote: true, "aria-pressed": pressed?, - form_class: "toggle-switch" + form_class: "toggle-switch #{options[:form_class]}".strip } end + + def html_options + default_options.merge(options.except(:form_class)) + end end diff --git a/app/controllers/admin/budget_investments_controller.rb b/app/controllers/admin/budget_investments_controller.rb index c4d68ae43..6b031dd3b 100644 --- a/app/controllers/admin/budget_investments_controller.rb +++ b/app/controllers/admin/budget_investments_controller.rb @@ -6,19 +6,18 @@ class Admin::BudgetInvestmentsController < Admin::BaseController feature_flag :budgets has_orders %w[oldest], only: [:show, :edit] - has_filters %w[all], only: [:index, :toggle_selection] + has_filters %w[all], only: :index before_action :load_budget - before_action :load_investment, only: [:show, :edit, :update, :toggle_selection] + before_action :load_investment, except: [:index] before_action :load_ballot, only: [:show, :index] before_action :parse_valuation_filters - before_action :load_investments, only: [:index, :toggle_selection] + before_action :load_investments, only: :index def index load_tags respond_to do |format| format.html - format.js format.csv do send_data Budget::Investment::Exporter.new(@investments).to_csv, filename: "budget_investments.csv" @@ -40,32 +39,57 @@ class Admin::BudgetInvestmentsController < Admin::BaseController def update authorize! :admin_update, @investment - respond_to do |format| - format.html do - if @investment.update(budget_investment_params) - redirect_to admin_budget_budget_investment_path(@budget, - @investment, - Budget::Investment.filter_params(params).to_h), - notice: t("flash.actions.update.budget_investment") - else - load_staff - load_valuator_groups - load_tags - render :edit - end - end - - format.json do - @investment.update!(budget_investment_params) - end + if @investment.update(budget_investment_params) + redirect_to admin_budget_budget_investment_path(@budget, + @investment, + Budget::Investment.filter_params(params).to_h), + notice: t("flash.actions.update.budget_investment") + else + load_staff + load_valuator_groups + load_tags + render :edit end end - def toggle_selection - authorize! :toggle_selection, @investment - @investment.toggle :selected - @investment.save! - load_investments + def show_to_valuators + authorize! :admin_update, @investment + @investment.update!(visible_to_valuators: true) + + respond_to do |format| + format.html { redirect_to request.referer, notice: t("flash.actions.update.budget_investment") } + format.js { render :toggle_visible_to_valuators } + end + end + + def hide_from_valuators + authorize! :admin_update, @investment + @investment.update!(visible_to_valuators: false) + + respond_to do |format| + format.html { redirect_to request.referer, notice: t("flash.actions.update.budget_investment") } + format.js { render :toggle_visible_to_valuators } + end + end + + def select + authorize! :select, @investment + @investment.update!(selected: true) + + respond_to do |format| + format.html { redirect_to request.referer, notice: t("flash.actions.update.budget_investment") } + format.js { render :toggle_selection } + end + end + + def deselect + authorize! :deselect, @investment + @investment.update!(selected: false) + + respond_to do |format| + format.html { redirect_to request.referer, notice: t("flash.actions.update.budget_investment") } + format.js { render :toggle_selection } + end end private @@ -96,7 +120,7 @@ class Admin::BudgetInvestmentsController < Admin::BaseController def allowed_params attributes = [:external_url, :heading_id, :administrator_id, :tag_list, - :valuation_tag_list, :incompatible, :visible_to_valuators, :selected, + :valuation_tag_list, :incompatible, :selected, :milestone_tag_list, valuator_ids: [], valuator_group_ids: []] [*attributes, translation_params(Budget::Investment)] end diff --git a/app/controllers/admin/legislation/proposals_controller.rb b/app/controllers/admin/legislation/proposals_controller.rb index c2711aa99..8022e8628 100644 --- a/app/controllers/admin/legislation/proposals_controller.rb +++ b/app/controllers/admin/legislation/proposals_controller.rb @@ -8,8 +8,21 @@ class Admin::Legislation::ProposalsController < Admin::Legislation::BaseControll @proposals = @proposals.send("sort_by_#{@current_order}").page(params[:page]) end - def toggle_selection - @proposal.toggle :selected - @proposal.save! + def select + @proposal.update!(selected: true) + + respond_to do |format| + format.html { redirect_to request.referer, notice: t("flash.actions.update.proposal") } + format.js { render :toggle_selection } + end + end + + def deselect + @proposal.update!(selected: false) + + respond_to do |format| + format.html { redirect_to request.referer, notice: t("flash.actions.update.proposal") } + format.js { render :toggle_selection } + end end end diff --git a/app/controllers/admin/proposals_controller.rb b/app/controllers/admin/proposals_controller.rb index 18b2866ec..5e9e52b49 100644 --- a/app/controllers/admin/proposals_controller.rb +++ b/app/controllers/admin/proposals_controller.rb @@ -19,9 +19,22 @@ class Admin::ProposalsController < Admin::BaseController end end - def toggle_selection - @proposal.toggle :selected - @proposal.save! + def select + @proposal.update!(selected: true) + + respond_to do |format| + format.html { redirect_to request.referer, notice: t("flash.actions.update.proposal") } + format.js { render :toggle_selection } + end + end + + def deselect + @proposal.update!(selected: false) + + respond_to do |format| + format.html { redirect_to request.referer, notice: t("flash.actions.update.proposal") } + format.js { render :toggle_selection } + end end private diff --git a/app/helpers/budget_investments_helper.rb b/app/helpers/budget_investments_helper.rb index dc0920630..f3a49cde6 100644 --- a/app/helpers/budget_investments_helper.rb +++ b/app/helpers/budget_investments_helper.rb @@ -3,18 +3,6 @@ module BudgetInvestmentsHelper params.map { |af| t("admin.budget_investments.index.filters.#{af}") }.join(", ") end - def link_to_investments_sorted_by(column) - direction = set_direction(params[:direction]) - icon = set_sorting_icon(direction, column) - - translation = t("admin.budget_investments.index.list.#{column}") - - link_to( - safe_join([translation, tag.span(class: "icon-sortable #{icon}")]), - admin_budget_budget_investments_path(sort_by: column, direction: direction) - ) - end - def set_sorting_icon(direction, sort_by) if sort_by.to_s == params[:sort_by] if direction == "desc" diff --git a/app/helpers/budgets_helper.rb b/app/helpers/budgets_helper.rb index ffcfc548c..7ad8d9a52 100644 --- a/app/helpers/budgets_helper.rb +++ b/app/helpers/budgets_helper.rb @@ -1,11 +1,4 @@ module BudgetsHelper - def csv_params - csv_params = params.clone.merge(format: :csv) - csv_params = csv_params.to_unsafe_h.transform_keys(&:to_sym) - csv_params.delete(:page) - csv_params - end - def namespaced_budget_investment_path(investment, options = {}) case namespace when "management" diff --git a/app/helpers/proposals_helper.rb b/app/helpers/proposals_helper.rb index f2b82e8dc..a497c7037 100644 --- a/app/helpers/proposals_helper.rb +++ b/app/helpers/proposals_helper.rb @@ -64,25 +64,6 @@ module ProposalsHelper proposals_current_view == "default" ? "minimal" : "default" end - def link_to_toggle_proposal_selection(proposal) - if proposal.selected? - button_text = t("admin.proposals.index.selected") - html_class = "button expanded" - else - button_text = t("admin.proposals.index.select") - html_class = "button hollow expanded" - end - - case proposal.class.to_s - when "Proposal" - path = toggle_selection_admin_proposal_path(proposal) - when "Legislation::Proposal" - path = toggle_selection_admin_legislation_process_proposal_path(proposal.process, proposal) - end - - link_to button_text, path, remote: true, method: :patch, class: html_class - end - def css_for_proposal_info_row(proposal) if proposal.image.present? if params[:selected].present? diff --git a/app/models/abilities/administrator.rb b/app/models/abilities/administrator.rb index 9422a4cf5..5c9fb6dee 100644 --- a/app/models/abilities/administrator.rb +++ b/app/models/abilities/administrator.rb @@ -71,10 +71,13 @@ module Abilities can [:read, :create, :update, :destroy], Budget::Group can [:read, :create, :update, :destroy], Budget::Heading - can [:hide, :admin_update, :toggle_selection], Budget::Investment + can [:hide, :admin_update], Budget::Investment can [:valuate, :comment_valuation], Budget::Investment - cannot [:admin_update, :toggle_selection, :valuate, :comment_valuation], + cannot [:admin_update, :valuate, :comment_valuation], Budget::Investment, budget: { phase: "finished" } + can [:select, :deselect], Budget::Investment do |investment| + investment.feasible? && investment.valuation_finished? && !investment.budget.finished? + end can :create, Budget::ValuatorAssignment diff --git a/app/views/admin/budget_investments/_investments.html.erb b/app/views/admin/budget_investments/_investments.html.erb deleted file mode 100644 index f378fd63a..000000000 --- a/app/views/admin/budget_investments/_investments.html.erb +++ /dev/null @@ -1,64 +0,0 @@ -<%= link_to t("admin.budget_investments.index.download_current_selection"), - admin_budget_budget_investments_path(csv_params), - class: "float-right small clear" %> - -<% if params[:advanced_filters].include?("winners") %> - <%= render Admin::Budgets::CalculateWinnersButtonComponent.new(@budget, from_investments: true) %> -<% end %> - -<% if @investments.any? %> -

<%= page_entries_info @investments %>

- <%= render "admin/shared/columns_selector", - cookie: "investments-columns", - default: %w[id title supports admin valuator geozone feasibility price valuation_finished visible_to_valuators selected incompatible] %> -
- - <%= render "filters_description", i18n_namespace: "admin.budget_investments.index" %> - - - - - - - - - - - - - <% if @budget.show_money? %> - - <% end %> - - - - <% if params[:advanced_filters]&.include?("selected") %> - - <% end %> - - - - - <% @investments.each do |investment| %> - - <%= render "/admin/budget_investments/select_investment", investment: investment %> - - <% end %> - -
<%= link_to_investments_sorted_by :id %><%= link_to_investments_sorted_by :title %><%= link_to_investments_sorted_by :supports %><%= t("admin.budget_investments.index.list.admin") %> - <%= t("admin.budget_investments.index.list.author") %> - - <%= t("admin.budget_investments.index.list.valuation_group") %> / - <%= t("admin.budget_investments.index.list.valuator") %> - <%= t("admin.budget_investments.index.list.geozone") %><%= t("admin.budget_investments.index.list.feasibility") %><%= t("admin.budget_investments.index.list.price") %> - <%= t("admin.budget_investments.index.list.valuation_finished") %> - - <%= t("admin.budget_investments.index.list.visible_to_valuators") %> - <%= t("admin.budget_investments.index.list.selected") %><%= t("admin.budget_investments.index.list.incompatible") %>
- - <%= paginate @investments %> -<% else %> -
- <%= t("admin.budget_investments.index.no_budget_investments") %> -
-<% end %> diff --git a/app/views/admin/budget_investments/_select_investment.html.erb b/app/views/admin/budget_investments/_select_investment.html.erb deleted file mode 100644 index 9753b35f9..000000000 --- a/app/views/admin/budget_investments/_select_investment.html.erb +++ /dev/null @@ -1,109 +0,0 @@ - - <%= investment.id %> - - - - <%= link_to investment.title, - admin_budget_budget_investment_path(budget_id: @budget.id, - id: investment.id, - params: Budget::Investment.filter_params(params).to_h), - target: "_blank" %> - - - - <%= investment.total_votes %> - - - - <% if investment.administrator.present? %> - "> - <%= investment.administrator.description_or_name %> - - <% else %> - <%= t("admin.budget_investments.index.no_admin_assigned") %> - <% end %> - - - - <%= investment.author.name %> - - - - <% valuators = [investment.assigned_valuation_groups, investment.assigned_valuators].compact %> - <% no_valuators_assigned = t("admin.budget_investments.index.no_valuators_assigned") %> - <%= valuators.present? ? valuators.join(", ") : no_valuators_assigned %> - - - - <%= investment.heading.name %> - - - - <%= t("admin.budget_investments.index.feasibility.#{investment.feasibility}") %> - - -<% if @budget.show_money? %> - - <%= investment.formatted_price %> - -<% end %> - - - <%= investment.valuation_finished? ? t("shared.yes") : t("shared.no") %> - - - - <% if can?(:admin_update, investment) %> - <%= form_for [:admin, investment.budget, investment], remote: true, format: :json do |f| %> - <%= f.check_box :visible_to_valuators, - label: false, - class: "js-submit-on-change", - id: "budget_investment_visible_to_valuators" %> - <% end %> - <% else %> - <%= investment.visible_to_valuators? ? t("shared.yes") : t("shared.no") %> - <% end %> - - - - <% if investment.selected? %> - <%= link_to_if can?(:toggle_selection, investment), - t("admin.budget_investments.index.selected"), - toggle_selection_admin_budget_budget_investment_path( - @budget, - investment, - filter: params[:filter], - sort_by: params[:sort_by], - min_total_supports: params[:min_total_supports], - max_total_supports: params[:max_total_supports], - advanced_filters: params[:advanced_filters], - page: params[:page] - ), - method: :patch, - remote: true, - class: "button small expanded" %> - <% elsif investment.feasible? && investment.valuation_finished? %> - <% if can?(:toggle_selection, investment) %> - <%= link_to t("admin.budget_investments.index.select"), - toggle_selection_admin_budget_budget_investment_path( - @budget, - investment, - filter: params[:filter], - sort_by: params[:sort_by], - min_total_supports: params[:min_total_supports], - max_total_supports: params[:max_total_supports], - advanced_filters: params[:advanced_filters], - page: params[:page] - ), - method: :patch, - remote: true, - class: "button small hollow expanded" %> - <% end %> - <% end %> - - -<% if params[:advanced_filters]&.include?("selected") %> - - <%= investment.incompatible? ? t("shared.yes") : t("shared.no") %> - -<% end %> diff --git a/app/views/admin/budget_investments/index.html.erb b/app/views/admin/budget_investments/index.html.erb index 4878421cd..7a8e46741 100644 --- a/app/views/admin/budget_investments/index.html.erb +++ b/app/views/admin/budget_investments/index.html.erb @@ -12,6 +12,4 @@ <%= render "/shared/filter_subnav", i18n_namespace: "admin.budget_investments.index" %> -
- <%= render "investments" %> -
+<%= render Admin::BudgetInvestments::InvestmentsComponent.new(@budget, @investments) %> diff --git a/app/views/admin/budget_investments/index.js.erb b/app/views/admin/budget_investments/index.js.erb deleted file mode 100644 index c1569cc59..000000000 --- a/app/views/admin/budget_investments/index.js.erb +++ /dev/null @@ -1 +0,0 @@ -$("#investments").html("<%= j render("admin/budget_investments/investments") %>"); diff --git a/app/views/admin/budget_investments/toggle_selection.js.erb b/app/views/admin/budget_investments/toggle_selection.js.erb index ecf457e8c..bac042d52 100644 --- a/app/views/admin/budget_investments/toggle_selection.js.erb +++ b/app/views/admin/budget_investments/toggle_selection.js.erb @@ -1 +1,4 @@ -$("#<%= dom_id(@investment) %>").html("<%= j render("select_investment", investment: @investment) %>").trigger("inserted"); +<%= render "admin/shared/toggle_switch", + record: @investment, + form_class: "toggle-selection", + new_content: render(Admin::BudgetInvestments::ToggleSelectionComponent.new(@investment)) %> diff --git a/app/views/admin/budget_investments/toggle_visible_to_valuators.js.erb b/app/views/admin/budget_investments/toggle_visible_to_valuators.js.erb new file mode 100644 index 000000000..207826f07 --- /dev/null +++ b/app/views/admin/budget_investments/toggle_visible_to_valuators.js.erb @@ -0,0 +1,4 @@ +<%= render "admin/shared/toggle_switch", + record: @investment, + form_class: "visible-to-valuators", + new_content: render(Admin::BudgetInvestments::ToggleVisibleToValuatorsComponent.new(@investment)) %> diff --git a/app/views/admin/budgets_wizard/phases/toggle_enabled.js.erb b/app/views/admin/budgets_wizard/phases/toggle_enabled.js.erb index d0688ab25..764515939 100644 --- a/app/views/admin/budgets_wizard/phases/toggle_enabled.js.erb +++ b/app/views/admin/budgets_wizard/phases/toggle_enabled.js.erb @@ -1,5 +1,3 @@ -var replacement = $("<%= j render Admin::BudgetPhases::ToggleEnabledComponent.new(@phase) %>"); -var form = $("#<%= dom_id(@phase) %> .toggle-switch"); - -form.replaceWith(replacement); -replacement.find("[type='submit']").focus(); +<%= render "admin/shared/toggle_switch", + record: @phase, + new_content: render(Admin::BudgetPhases::ToggleEnabledComponent.new(@phase)) %> diff --git a/app/views/admin/legislation/proposals/_proposals.html.erb b/app/views/admin/legislation/proposals/_proposals.html.erb index 264f92cda..4694b1732 100644 --- a/app/views/admin/legislation/proposals/_proposals.html.erb +++ b/app/views/admin/legislation/proposals/_proposals.html.erb @@ -19,7 +19,7 @@ <%= proposal.id %> <%= proposal.title %> <%= proposal.votes_score %> - <%= render "select_proposal", proposal: proposal %> + <%= render Admin::Proposals::ToggleSelectionComponent.new(proposal) %> <% end %> diff --git a/app/views/admin/legislation/proposals/_select_proposal.html.erb b/app/views/admin/legislation/proposals/_select_proposal.html.erb deleted file mode 100644 index d5d2069ab..000000000 --- a/app/views/admin/legislation/proposals/_select_proposal.html.erb +++ /dev/null @@ -1 +0,0 @@ -<%= link_to_toggle_proposal_selection(proposal) %> diff --git a/app/views/admin/legislation/proposals/toggle_selection.js.erb b/app/views/admin/legislation/proposals/toggle_selection.js.erb index d38292e9d..258c2901c 100644 --- a/app/views/admin/legislation/proposals/toggle_selection.js.erb +++ b/app/views/admin/legislation/proposals/toggle_selection.js.erb @@ -1 +1,3 @@ -$("#<%= dom_id(@proposal) %> .select").html("<%= j render("select_proposal", proposal: @proposal) %>"); +<%= render "admin/shared/toggle_switch", + record: @proposal, + new_content: render(Admin::Proposals::ToggleSelectionComponent.new(@proposal)) %> diff --git a/app/views/admin/proposals/_select_proposal.html.erb b/app/views/admin/proposals/_select_proposal.html.erb deleted file mode 100644 index d5d2069ab..000000000 --- a/app/views/admin/proposals/_select_proposal.html.erb +++ /dev/null @@ -1 +0,0 @@ -<%= link_to_toggle_proposal_selection(proposal) %> diff --git a/app/views/admin/proposals/index.html.erb b/app/views/admin/proposals/index.html.erb index 5cad75632..27964da5b 100644 --- a/app/views/admin/proposals/index.html.erb +++ b/app/views/admin/proposals/index.html.erb @@ -27,7 +27,7 @@ <%= link_to proposal.title, admin_proposal_path(proposal) %> <%= proposal.author.username %> <%= proposal.milestones.count %> - <%= render "select_proposal", proposal: proposal %> + <%= render Admin::Proposals::ToggleSelectionComponent.new(proposal) %> <% end %> diff --git a/app/views/admin/proposals/toggle_selection.js.erb b/app/views/admin/proposals/toggle_selection.js.erb index 0f7f171ba..258c2901c 100644 --- a/app/views/admin/proposals/toggle_selection.js.erb +++ b/app/views/admin/proposals/toggle_selection.js.erb @@ -1 +1,3 @@ -$("#<%= dom_id(@proposal) %> .js-select").html("<%= j render("select_proposal", proposal: @proposal) %>"); +<%= render "admin/shared/toggle_switch", + record: @proposal, + new_content: render(Admin::Proposals::ToggleSelectionComponent.new(@proposal)) %> diff --git a/app/views/admin/shared/_toggle_switch.js.erb b/app/views/admin/shared/_toggle_switch.js.erb new file mode 100644 index 000000000..3be8b17dc --- /dev/null +++ b/app/views/admin/shared/_toggle_switch.js.erb @@ -0,0 +1,5 @@ +var new_toggle_switch = $("<%= j new_content %>"); +var current_toggle_switch = $("#<%= dom_id(record) %> .<%= local_assigns[:form_class] || "toggle-switch" %>"); + +current_toggle_switch.replaceWith(new_toggle_switch); +new_toggle_switch.find("[type='submit']").focus(); diff --git a/config/initializers/routes_hierarchy.rb b/config/initializers/routes_hierarchy.rb index fc9eac7f0..9f15c4587 100644 --- a/config/initializers/routes_hierarchy.rb +++ b/config/initializers/routes_hierarchy.rb @@ -24,7 +24,7 @@ module ActionDispatch::Routing::UrlFor end def namespaced_polymorphic_path(namespace, resource, options = {}) - if %w[Budget::Group Budget::Heading Legislation::DraftVersion Legislation::Question + if %w[Budget::Group Budget::Heading Legislation::DraftVersion Legislation::Proposal Legislation::Question Poll::Booth Poll::BoothAssignment Poll::Officer Poll::Question Poll::Question::Option Poll::Question::Option::Video Poll::Shift SDG::LocalTarget].include?(resource.class.name) resolve = resolve_for(resource) diff --git a/config/locales/en/admin.yml b/config/locales/en/admin.yml index 3b70a864d..d07775899 100644 --- a/config/locales/en/admin.yml +++ b/config/locales/en/admin.yml @@ -16,6 +16,8 @@ en: unmark_featured: Unmark featured edit: Edit configure: Configure + select: Select + show_to_valuators: "Show %{name} to valuators" officing_booth: title: "You are officing the booth located at %{booth}. If this is not correct, do not continue and call the help phone number. Thank you." banners: @@ -268,7 +270,6 @@ en: unfeasible: "Unfeasible" undecided: "Undecided" selected: "Selected" - select: "Select" list: id: ID title: Title @@ -1294,7 +1295,6 @@ en: title: Proposals id: ID author: Author - select: Select selected: Selected milestones: Milestones no_proposals: There are no proposals. diff --git a/config/locales/es/admin.yml b/config/locales/es/admin.yml index 513ae0306..d2b103454 100644 --- a/config/locales/es/admin.yml +++ b/config/locales/es/admin.yml @@ -16,6 +16,8 @@ es: unmark_featured: Quitar destacado edit: Editar configure: Configurar + select: Seleccionar + show_to_valuators: "Mostrar %{name} a evaluadores" officing_booth: title: "Estás ahora mismo en la mesa ubicada en %{booth}. Si esto no es correcto no sigas adelante y llama al teléfono de incidencias. Gracias." banners: @@ -268,7 +270,6 @@ es: unfeasible: "Inviable" undecided: "Sin decidir" selected: "Seleccionado" - select: "Seleccionar" list: id: ID title: Título @@ -1295,7 +1296,6 @@ es: id: ID author: Autor milestones: Hitos - select: Seleccionar selected: Seleccionada no_proposals: No hay propuestas. show: diff --git a/config/routes/admin.rb b/config/routes/admin.rb index 6f3f3d561..9709300d3 100644 --- a/config/routes/admin.rb +++ b/config/routes/admin.rb @@ -32,7 +32,11 @@ namespace :admin do resources :debates, only: [:index, :show] resources :proposals, only: [:index, :show, :update] do - member { patch :toggle_selection } + member do + patch :select + patch :deselect + end + resources :milestones, controller: "proposal_milestones" resources :progress_bars, except: :show, controller: "proposal_progress_bars" end @@ -62,7 +66,12 @@ namespace :admin do end resources :budget_investments, only: [:index, :show, :edit, :update] do - member { patch :toggle_selection } + member do + patch :select + patch :deselect + patch :show_to_valuators + patch :hide_from_valuators + end resources :audits, only: :show, controller: "budget_investment_audits" resources :milestones, controller: "budget_investment_milestones" @@ -232,7 +241,10 @@ namespace :admin do resources :processes do resources :questions resources :proposals do - member { patch :toggle_selection } + member do + patch :select + patch :deselect + end end resources :draft_versions resources :milestones diff --git a/spec/components/admin/budget_investments/toggle_selection_component_spec.rb b/spec/components/admin/budget_investments/toggle_selection_component_spec.rb new file mode 100644 index 000000000..aff7da09f --- /dev/null +++ b/spec/components/admin/budget_investments/toggle_selection_component_spec.rb @@ -0,0 +1,66 @@ +require "rails_helper" + +describe Admin::BudgetInvestments::ToggleSelectionComponent, :admin do + context "open budget" do + let(:budget) { create(:budget) } + + it "is not rendered for not-yet-evaluated investments" do + unfeasible_investment = create(:budget_investment, :unfeasible, budget: budget) + feasible_investment = create(:budget_investment, :feasible, budget: budget) + + render_inline Admin::BudgetInvestments::ToggleSelectionComponent.new(unfeasible_investment) + expect(page).not_to be_rendered + + render_inline Admin::BudgetInvestments::ToggleSelectionComponent.new(feasible_investment) + expect(page).not_to be_rendered + end + + it "renders a button to select unselected evaluated investments" do + valuation_finished_investment = create(:budget_investment, :feasible, :finished, budget: budget) + + render_inline Admin::BudgetInvestments::ToggleSelectionComponent.new(valuation_finished_investment) + + expect(page).to have_button count: 1 + expect(page).to have_button exact_text: "No" + expect(page).to have_css "button[aria-pressed='false']" + end + + it "renders a button to deselect selected investments" do + selected_investment = create(:budget_investment, :selected, budget: budget) + + render_inline Admin::BudgetInvestments::ToggleSelectionComponent.new(selected_investment) + + expect(page).to have_button count: 1 + expect(page).to have_button exact_text: "Yes" + expect(page).to have_css "button[aria-pressed='true']" + end + end + + context "finished budget" do + let(:budget) { create(:budget, :finished) } + + it "is not rendered for unselected investments" do + unfeasible_investment = create(:budget_investment, :unfeasible, budget: budget) + feasible_investment = create(:budget_investment, :feasible, budget: budget) + valuation_finished_investment = create(:budget_investment, :feasible, :finished, budget: budget) + + render_inline Admin::BudgetInvestments::ToggleSelectionComponent.new(unfeasible_investment) + expect(page).not_to be_rendered + + render_inline Admin::BudgetInvestments::ToggleSelectionComponent.new(feasible_investment) + expect(page).not_to be_rendered + + render_inline Admin::BudgetInvestments::ToggleSelectionComponent.new(valuation_finished_investment) + expect(page).not_to be_rendered + end + + it "renders plain text for selected investments" do + selected_investment = create(:budget_investment, :selected, budget: budget) + + render_inline Admin::BudgetInvestments::ToggleSelectionComponent.new(selected_investment) + + expect(page).to have_content "Selected" + expect(page).not_to have_button + end + end +end diff --git a/spec/components/admin/budget_investments/toggle_visible_to_valuators_component_spec.rb b/spec/components/admin/budget_investments/toggle_visible_to_valuators_component_spec.rb new file mode 100644 index 000000000..cf73b05f2 --- /dev/null +++ b/spec/components/admin/budget_investments/toggle_visible_to_valuators_component_spec.rb @@ -0,0 +1,21 @@ +require "rails_helper" + +describe Admin::BudgetInvestments::ToggleVisibleToValuatorsComponent, :admin do + describe "aria-pressed attribute" do + it "is true for investments visible to valuators" do + investment = create(:budget_investment, :visible_to_valuators) + + render_inline Admin::BudgetInvestments::ToggleVisibleToValuatorsComponent.new(investment) + + expect(page).to have_css "[aria-pressed=true]" + end + + it "is true for investments invisible to valuators" do + investment = create(:budget_investment, :invisible_to_valuators) + + render_inline Admin::BudgetInvestments::ToggleVisibleToValuatorsComponent.new(investment) + + expect(page).to have_css "[aria-pressed=false]" + end + end +end diff --git a/spec/components/admin/proposals/toggle_selection_component_spec.rb b/spec/components/admin/proposals/toggle_selection_component_spec.rb new file mode 100644 index 000000000..2350c92f8 --- /dev/null +++ b/spec/components/admin/proposals/toggle_selection_component_spec.rb @@ -0,0 +1,21 @@ +require "rails_helper" + +describe Admin::Proposals::ToggleSelectionComponent, :admin do + describe "aria-pressed attribute" do + it "is true for selected proposals" do + proposal = create(:proposal, :selected) + + render_inline Admin::Proposals::ToggleSelectionComponent.new(proposal) + + expect(page).to have_css "button[aria-pressed='true']" + end + + it "is false for not selected proposals" do + proposal = create(:proposal) + + render_inline Admin::Proposals::ToggleSelectionComponent.new(proposal) + + expect(page).to have_css "button[aria-pressed='false']" + end + end +end diff --git a/spec/controllers/admin/budget_investments_controller_spec.rb b/spec/controllers/admin/budget_investments_controller_spec.rb index 6fd1cc4e7..53122d970 100644 --- a/spec/controllers/admin/budget_investments_controller_spec.rb +++ b/spec/controllers/admin/budget_investments_controller_spec.rb @@ -23,18 +23,139 @@ describe Admin::BudgetInvestmentsController, :admin do end end - describe "PATCH update" do - it "does not redirect on AJAX requests" do - investment = create(:budget_investment) + describe "PATCH show_to_valuators" do + let(:investment) { create(:budget_investment, :invisible_to_valuators) } - patch :update, params: { - id: investment, - budget_id: investment.budget, - format: :json, - budget_investment: { visible_to_valuators: true } - } + it "marks the investment as visible to valuators" do + expect do + patch :show_to_valuators, xhr: true, params: { id: investment, budget_id: investment.budget } + end.to change { investment.reload.visible_to_valuators? }.from(false).to(true) - expect(response).not_to be_redirect + expect(response).to be_successful + end + + it "does not modify investments visible to valuators" do + investment.update!(visible_to_valuators: true) + + expect do + patch :show_to_valuators, xhr: true, params: { id: investment, budget_id: investment.budget } + end.not_to change { investment.reload.visible_to_valuators? } + end + + it "redirects admins without JavaScript to the same page" do + request.env["HTTP_REFERER"] = admin_budget_budget_investments_path(investment.budget) + + patch :show_to_valuators, params: { id: investment, budget_id: investment.budget } + + expect(response).to redirect_to admin_budget_budget_investments_path(investment.budget) + expect(flash[:notice]).to eq "Investment project updated successfully." + end + end + + describe "PATCH hide_from_valuators" do + let(:investment) { create(:budget_investment, :visible_to_valuators) } + + it "marks the investment as visible to valuators" do + expect do + patch :hide_from_valuators, xhr: true, params: { id: investment, budget_id: investment.budget } + end.to change { investment.reload.visible_to_valuators? }.from(true).to(false) + + expect(response).to be_successful + end + + it "does not modify investments visible to valuators" do + investment.update!(visible_to_valuators: false) + + expect do + patch :hide_from_valuators, xhr: true, params: { id: investment, budget_id: investment.budget } + end.not_to change { investment.reload.visible_to_valuators? } + end + + it "redirects admins without JavaScript to the same page" do + request.env["HTTP_REFERER"] = admin_budget_budget_investments_path(investment.budget) + + patch :hide_from_valuators, params: { id: investment, budget_id: investment.budget } + + expect(response).to redirect_to admin_budget_budget_investments_path(investment.budget) + expect(flash[:notice]).to eq "Investment project updated successfully." + end + end + + describe "PATCH select" do + let(:investment) { create(:budget_investment, :feasible, :finished) } + + it "selects the investment" do + expect do + patch :select, xhr: true, params: { id: investment, budget_id: investment.budget } + end.to change { investment.reload.selected? }.from(false).to(true) + + expect(response).to be_successful + end + + it "does not modify already selected investments" do + investment.update!(selected: true) + + expect do + patch :select, xhr: true, params: { id: investment, budget_id: investment.budget } + end.not_to change { investment.reload.selected? } + end + + it "uses the select/deselect authorization rules" do + investment.update!(valuation_finished: false) + + patch :select, xhr: true, params: { id: investment, budget_id: investment.budget } + + expect(flash[:alert]).to eq "You do not have permission to carry out the action " \ + "'select' on Investment." + expect(investment).not_to be_selected + end + + it "redirects admins without JavaScript to the same page" do + request.env["HTTP_REFERER"] = admin_budget_budget_investments_path(investment.budget) + + patch :select, params: { id: investment, budget_id: investment.budget } + + expect(response).to redirect_to admin_budget_budget_investments_path(investment.budget) + expect(flash[:notice]).to eq "Investment project updated successfully." + end + end + + describe "PATCH deselect" do + let(:investment) { create(:budget_investment, :feasible, :finished, :selected) } + + it "deselects the investment" do + expect do + patch :deselect, xhr: true, params: { id: investment, budget_id: investment.budget } + end.to change { investment.reload.selected? }.from(true).to(false) + + expect(response).to be_successful + end + + it "does not modify non-selected investments" do + investment.update!(selected: false) + + expect do + patch :deselect, xhr: true, params: { id: investment, budget_id: investment.budget } + end.not_to change { investment.reload.selected? } + end + + it "uses the select/deselect authorization rules" do + investment.update!(valuation_finished: false) + + patch :deselect, xhr: true, params: { id: investment, budget_id: investment.budget } + + expect(flash[:alert]).to eq "You do not have permission to carry out the action " \ + "'deselect' on Investment." + expect(investment).to be_selected + end + + it "redirects admins without JavaScript to the same page" do + request.env["HTTP_REFERER"] = admin_budget_budget_investments_path(investment.budget) + + patch :deselect, params: { id: investment, budget_id: investment.budget } + + expect(response).to redirect_to admin_budget_budget_investments_path(investment.budget) + expect(flash[:notice]).to eq "Investment project updated successfully." end end end diff --git a/spec/controllers/admin/legislation/proposals_controller_spec.rb b/spec/controllers/admin/legislation/proposals_controller_spec.rb new file mode 100644 index 000000000..6cd64562c --- /dev/null +++ b/spec/controllers/admin/legislation/proposals_controller_spec.rb @@ -0,0 +1,61 @@ +require "rails_helper" + +describe Admin::Legislation::ProposalsController, :admin do + describe "PATCH select" do + let(:proposal) { create(:legislation_proposal) } + + it "selects the proposal" do + expect do + patch :select, xhr: true, params: { id: proposal.id, process_id: proposal.process.id } + end.to change { proposal.reload.selected? }.from(false).to(true) + + expect(response).to be_successful + end + + it "does not modify already selected proposals" do + proposal.update!(selected: true) + + expect do + patch :select, xhr: true, params: { id: proposal.id, process_id: proposal.process.id } + end.not_to change { proposal.reload.selected? } + end + + it "redirects admins without JavaScript to the same page" do + request.env["HTTP_REFERER"] = admin_proposals_path + + patch :select, params: { id: proposal.id, process_id: proposal.process.id } + + expect(response).to redirect_to admin_proposals_path + expect(flash[:notice]).to eq "Proposal updated successfully." + end + end + + describe "PATCH deselect" do + let(:proposal) { create(:legislation_proposal, :selected) } + + it "deselects the proposal" do + expect do + patch :deselect, xhr: true, params: { id: proposal.id, process_id: proposal.process.id } + end.to change { proposal.reload.selected? }.from(true).to(false) + + expect(response).to be_successful + end + + it "does not modify non-selected proposals" do + proposal.update!(selected: false) + + expect do + patch :deselect, xhr: true, params: { id: proposal.id, process_id: proposal.process.id } + end.not_to change { proposal.reload.selected? } + end + + it "redirects admins without JavaScript to the same page" do + request.env["HTTP_REFERER"] = admin_proposals_path + + patch :deselect, params: { id: proposal.id, process_id: proposal.process.id } + + expect(response).to redirect_to admin_proposals_path + expect(flash[:notice]).to eq "Proposal updated successfully." + end + end +end diff --git a/spec/controllers/admin/proposals_controller_spec.rb b/spec/controllers/admin/proposals_controller_spec.rb new file mode 100644 index 000000000..b23369adb --- /dev/null +++ b/spec/controllers/admin/proposals_controller_spec.rb @@ -0,0 +1,61 @@ +require "rails_helper" + +describe Admin::ProposalsController, :admin do + describe "PATCH select" do + let(:proposal) { create(:proposal) } + + it "selects the proposal" do + expect do + patch :select, xhr: true, params: { id: proposal.id } + end.to change { proposal.reload.selected? }.from(false).to(true) + + expect(response).to be_successful + end + + it "does not modify already selected proposals" do + proposal.update!(selected: true) + + expect do + patch :select, xhr: true, params: { id: proposal.id } + end.not_to change { proposal.reload.selected? } + end + + it "redirects admins without JavaScript to the same page" do + request.env["HTTP_REFERER"] = admin_proposals_path + + patch :select, params: { id: proposal.id } + + expect(response).to redirect_to admin_proposals_path + expect(flash[:notice]).to eq "Proposal updated successfully." + end + end + + describe "PATCH deselect" do + let(:proposal) { create(:proposal, :selected) } + + it "deselects the proposal" do + expect do + patch :deselect, xhr: true, params: { id: proposal.id } + end.to change { proposal.reload.selected? }.from(true).to(false) + + expect(response).to be_successful + end + + it "does not modify non-selected proposals" do + proposal.update!(selected: false) + + expect do + patch :deselect, xhr: true, params: { id: proposal.id } + end.not_to change { proposal.reload.selected? } + end + + it "redirects admins without JavaScript to the same page" do + request.env["HTTP_REFERER"] = admin_proposals_path + + patch :deselect, params: { id: proposal.id } + + expect(response).to redirect_to admin_proposals_path + expect(flash[:notice]).to eq "Proposal updated successfully." + end + end +end diff --git a/spec/factories/legislations.rb b/spec/factories/legislations.rb index 4ae250317..5c27fc3f5 100644 --- a/spec/factories/legislations.rb +++ b/spec/factories/legislations.rb @@ -177,5 +177,9 @@ FactoryBot.define do trait :hidden do hidden_at { Time.current } end + + trait :selected do + selected { true } + end end end diff --git a/spec/models/abilities/administrator_spec.rb b/spec/models/abilities/administrator_spec.rb index f24a6f4b4..f75aac370 100644 --- a/spec/models/abilities/administrator_spec.rb +++ b/spec/models/abilities/administrator_spec.rb @@ -115,7 +115,11 @@ describe Abilities::Administrator do it { should_not be_able_to(:admin_update, finished_investment) } it { should_not be_able_to(:valuate, finished_investment) } it { should_not be_able_to(:comment_valuation, finished_investment) } - it { should_not be_able_to(:toggle_selection, finished_investment) } + + it { should be_able_to([:select, :deselect], create(:budget_investment, :feasible, :finished)) } + it { should_not be_able_to([:select, :deselect], create(:budget_investment, :feasible, :open)) } + it { should_not be_able_to([:select, :deselect], create(:budget_investment, :unfeasible, :finished)) } + it { should_not be_able_to([:select, :deselect], finished_investment) } it { should be_able_to(:destroy, proposal_image) } it { should be_able_to(:destroy, proposal_document) } diff --git a/spec/system/admin/budget_investments_spec.rb b/spec/system/admin/budget_investments_spec.rb index 506562d17..bd695df79 100644 --- a/spec/system/admin/budget_investments_spec.rb +++ b/spec/system/admin/budget_investments_spec.rb @@ -43,21 +43,6 @@ describe "Admin budget investments", :admin do expect(page).not_to have_content("€") end - scenario "If budget is finished do not show 'Selected' button" do - finished_budget = create(:budget, :finished) - budget_investment = create(:budget_investment, budget: finished_budget, cached_votes_up: 77) - - visit admin_budget_budget_investments_path(budget_id: finished_budget.id) - - within("#budget_investment_#{budget_investment.id}") do - expect(page).to have_content(budget_investment.title) - expect(page).to have_content(budget_investment.heading.name) - expect(page).to have_content(budget_investment.id) - expect(page).to have_content(budget_investment.total_votes) - expect(page).not_to have_link("Selected") - end - end - scenario "Display admin and valuator assignments" do olga = create(:user, username: "Olga") miriam = create(:user, username: "Miriam") @@ -1422,62 +1407,19 @@ describe "Admin budget investments", :admin do expect(page).not_to have_content(feasible_vf_bi.title) end - scenario "Showing the selection buttons" do - visit admin_budget_budget_investments_path(budget) - - within("#budget_investment_#{unfeasible_bi.id}") do - expect(page).not_to have_link("Select") - expect(page).not_to have_link("Selected") - end - - within("#budget_investment_#{feasible_bi.id}") do - expect(page).not_to have_link("Select") - expect(page).not_to have_link("Selected") - end - - within("#budget_investment_#{feasible_vf_bi.id}") do - expect(page).to have_link("Select") - expect(page).not_to have_link("Selected") - end - - within("#budget_investment_#{selected_bi.id}") do - expect(page).not_to have_link("Select") - expect(page).to have_link("Selected") - end - end - - scenario "Show only selected text when budget is finished" do - budget.update!(phase: "finished") - - visit admin_budget_budget_investments_path(budget) - - within("#budget_investment_#{unfeasible_bi.id} #selection") do - expect(page).not_to have_content("Select") - expect(page).not_to have_content("Selected") - end - - within("#budget_investment_#{feasible_bi.id} #selection") do - expect(page).not_to have_content("Select") - expect(page).not_to have_content("Selected") - end - - within("#budget_investment_#{feasible_vf_bi.id} #selection") do - expect(page).not_to have_content("Select") - expect(page).not_to have_content("Selected") - end - - within("#budget_investment_#{selected_bi.id} #selection") do - expect(page).not_to contain_exactly("Select") - expect(page).to have_content("Selected") - end - end - scenario "Selecting an investment" do visit admin_budget_budget_investments_path(budget) - within("#budget_investment_#{feasible_vf_bi.id}") do - click_link("Select") - expect(page).to have_link("Selected") + expect(page).to have_content "Unfeasible project" + + within("tr", text: "Feasible, VF project") do + within("[data-field=selected]") do + expect(page).to have_content "No" + + click_button "Select Feasible, VF project" + + expect(page).to have_content "Yes" + end end click_link "Advanced filters" @@ -1485,9 +1427,12 @@ describe "Admin budget investments", :admin do within("#advanced_filters") { check("Selected") } click_button("Filter") - within("#budget_investment_#{feasible_vf_bi.id}") do - expect(page).not_to have_link("Select") - expect(page).to have_link("Selected") + expect(page).not_to have_content "Unfeasible project" + + within("tr", text: "Feasible, VF project") do + within("[data-field=selected]") do + expect(page).to have_content "Yes" + end end end @@ -1500,21 +1445,26 @@ describe "Admin budget investments", :admin do expect(page).to have_content("There are 2 investments") - within("#budget_investment_#{selected_bi.id}") do - click_link("Selected") + within("tr", text: "Selected project") do + within("[data-field=selected]") do + expect(page).to have_content "Yes" - expect(page).to have_link("Select") + click_button "Select Selected project" + + expect(page).to have_content "No" + end end - click_button("Filter") - expect(page).not_to have_content(selected_bi.title) - expect(page).to have_content("There is 1 investment") + click_button "Filter" + expect(page).not_to have_content "Selected project" + expect(page).to have_content "There is 1 investment" visit admin_budget_budget_investments_path(budget) - within("#budget_investment_#{selected_bi.id}") do - expect(page).to have_link("Select") - expect(page).not_to have_link("Selected") + within("tr", text: "Selected project") do + within("[data-field=selected]") do + expect(page).to have_content "No" + end end end @@ -1526,10 +1476,12 @@ describe "Admin budget investments", :admin do visit admin_budget_budget_investments_path(budget) - within("#budget_investment_#{selected_bi.id}") do - click_link("Selected") + within("tr", text: "Selected project") do + within("[data-field=selected]") do + click_button "Select Selected project" - expect(page).to have_link "Select" + expect(page).to have_content "No" + end end click_link("Next") @@ -1545,8 +1497,8 @@ describe "Admin budget investments", :admin do let(:heading) { create(:budget_heading, budget: budget) } - let(:investment1) { create(:budget_investment, heading: heading) } - let(:investment2) { create(:budget_investment, heading: heading) } + let(:investment1) { create(:budget_investment, heading: heading, title: "Investment 1") } + let(:investment2) { create(:budget_investment, heading: heading, title: "Investment 2") } scenario "Mark as visible to valuator" do investment1.valuators << valuator @@ -1559,17 +1511,22 @@ describe "Admin budget investments", :admin do check "Under valuation" click_button "Filter" - within("#budget_investment_#{investment1.id}") do - check "budget_investment_visible_to_valuators" + within("tr", text: "Investment 1") do + within("[data-field=visible_to_valuators]") do + expect(page).to have_content "No" + + click_button "Show Investment 1 to valuators" + + expect(page).to have_content "Yes" + end end - visit admin_budget_budget_investments_path(budget) - click_link "Advanced filters" - check "Under valuation" - click_button "Filter" + refresh - within("#budget_investment_#{investment1.id}") do - expect(find("#budget_investment_visible_to_valuators")).to be_checked + within("tr", text: "Investment 1") do + within("[data-field=visible_to_valuators]") do + expect(page).to have_content "Yes" + end end end @@ -1608,18 +1565,22 @@ describe "Admin budget investments", :admin do check "Under valuation" click_button "Filter" - within("#budget_investment_#{investment1.id}") do - uncheck "budget_investment_visible_to_valuators" + within("tr", text: "Investment 1") do + within("[data-field=visible_to_valuators]") do + expect(page).to have_content "Yes" + + click_button "Show Investment 1 to valuators" + + expect(page).to have_content "No" + end end - visit admin_budget_budget_investments_path(budget) + refresh - click_link "Advanced filters" - check "Under valuation" - click_button "Filter" - - within("#budget_investment_#{investment1.id}") do - expect(find("#budget_investment_visible_to_valuators")).not_to be_checked + within("tr", text: "Investment 1") do + within("[data-field=visible_to_valuators]") do + expect(page).to have_content "No" + end end end @@ -1631,42 +1592,44 @@ describe "Admin budget investments", :admin do visit admin_budget_budget_investments_path(budget) within "tr", text: "Visible" do - within "td[data-field=visible_to_valuators]" do + within "[data-field=visible_to_valuators]" do expect(page).to have_text "Yes" - expect(page).not_to have_field "budget_investment_visible_to_valuators" + expect(page).not_to have_button end end within "tr", text: "Invisible" do - within "td[data-field=visible_to_valuators]" do + within "[data-field=visible_to_valuators]" do expect(page).to have_text "No" - expect(page).not_to have_field "budget_investment_visible_to_valuators" + expect(page).not_to have_button end end end scenario "Showing the valuating checkbox" do - investment1 = create(:budget_investment, :with_administrator, :with_valuator, :visible_to_valuators, - budget: budget) - investment2 = create(:budget_investment, :with_administrator, :with_valuator, :invisible_to_valuators, - budget: budget) + investment1.valuators << valuator + investment2.valuators << valuator + investment1.update!(administrator: admin, visible_to_valuators: true) + investment2.update!(administrator: admin, visible_to_valuators: false) visit admin_budget_budget_investments_path(budget) - expect(page).to have_css("#budget_investment_visible_to_valuators") - click_link "Advanced filters" check "Under valuation" click_button "Filter" - within("#budget_investment_#{investment1.id}") do - valuating_checkbox = find("#budget_investment_visible_to_valuators") - expect(valuating_checkbox).to be_checked + within "tr", text: "Investment 1" do + within "[data-field=visible_to_valuators]" do + expect(page).to have_content "Yes" + expect(page).to have_css "button[aria-pressed='true']" + end end - within("#budget_investment_#{investment2.id}") do - valuating_checkbox = find("#budget_investment_visible_to_valuators") - expect(valuating_checkbox).not_to be_checked + within "tr", text: "Investment 2" do + within "[data-field=visible_to_valuators]" do + expect(page).to have_content "No" + expect(page).to have_css "button[aria-pressed='false']" + end end end @@ -1676,8 +1639,14 @@ describe "Admin budget investments", :admin do visit admin_budget_budget_investments_path(budget) - within("#budget_investment_#{investment1.id}") do - check "budget_investment_visible_to_valuators" + within "tr", text: "Investment 1" do + within "[data-field=visible_to_valuators]" do + expect(page).to have_content "No" + + click_button "Show Investment 1 to valuators" + + expect(page).to have_content "Yes" + end end visit edit_admin_budget_budget_investment_path(budget, investment1) @@ -1867,11 +1836,16 @@ describe "Admin budget investments", :admin do within("#js-columns-selector-wrapper") { uncheck "Title" } within("#budget_investment_#{investment.id}") do - click_link "Selected" + within("[data-field=selected]") do + expect(page).to have_content "Yes" - expect(page).to have_link "Select" - expect(page).not_to have_content "Don't display me, please!" + click_button "Select Don't display me, please!" + + expect(page).to have_content "No" + end end + + expect(page).not_to have_content "Don't display me, please!" end scenario "When restoring the page from browser history renders columns selectors only once" do diff --git a/spec/system/admin/legislation/proposals_spec.rb b/spec/system/admin/legislation/proposals_spec.rb index 8653594f2..0b2a1d6eb 100644 --- a/spec/system/admin/legislation/proposals_spec.rb +++ b/spec/system/admin/legislation/proposals_spec.rb @@ -11,18 +11,18 @@ describe "Admin collaborative legislation", :admin do expect(page).to have_content(proposal.title) expect(page).to have_content(proposal.id) expect(page).to have_content(proposal.cached_votes_score) - expect(page).to have_content("Select") + expect(page).to have_content("No") end end scenario "Selecting legislation proposals" do - proposal = create(:legislation_proposal, cached_votes_score: 10) + proposal = create(:legislation_proposal, title: "Add more accessibility tests") visit admin_legislation_process_proposals_path(proposal.legislation_process_id) - click_link "Select" + click_button "Select Add more accessibility tests" within "#legislation_proposal_#{proposal.id}" do - expect(page).to have_content("Selected") + expect(page).to have_content "Yes" end end diff --git a/spec/system/admin/proposals_spec.rb b/spec/system/admin/proposals_spec.rb index 473470c67..d0377a70b 100644 --- a/spec/system/admin/proposals_spec.rb +++ b/spec/system/admin/proposals_spec.rb @@ -22,31 +22,39 @@ describe "Admin proposals", :admin do end scenario "Select a proposal" do - proposal = create(:proposal) + proposal = create(:proposal, title: "Forbid door-to-door sales") visit admin_proposals_path - within("#proposal_#{proposal.id}") { click_link "Select" } + within("#proposal_#{proposal.id}") do + expect(page).to have_content "No" - within("#proposal_#{proposal.id}") { expect(page).to have_link "Selected" } + click_button "Select Forbid door-to-door sales" + + expect(page).to have_content "Yes" + end refresh - within("#proposal_#{proposal.id}") { expect(page).to have_link "Selected" } + within("#proposal_#{proposal.id}") { expect(page).to have_content "Yes" } end scenario "Unselect a proposal" do - proposal = create(:proposal, :selected) + proposal = create(:proposal, :selected, title: "Allow door-to-door sales") visit admin_proposals_path - within("#proposal_#{proposal.id}") { click_link "Selected" } + within("#proposal_#{proposal.id}") do + expect(page).to have_content "Yes" - within("#proposal_#{proposal.id}") { expect(page).to have_link "Select" } + click_button "Select Allow door-to-door sales" + + expect(page).to have_content "No" + end refresh - within("#proposal_#{proposal.id}") { expect(page).to have_link "Select" } + within("#proposal_#{proposal.id}") { expect(page).to have_content "No" } end end diff --git a/spec/views/admin/budget_investments/select_investment_spec.rb b/spec/views/admin/budget_investments/select_investment_spec.rb deleted file mode 100644 index e04c6dd8f..000000000 --- a/spec/views/admin/budget_investments/select_investment_spec.rb +++ /dev/null @@ -1,13 +0,0 @@ -require "rails_helper" - -describe "investment row" do - it "uses a JSON request to update visible to valuators" do - investment = create(:budget_investment) - @budget = investment.budget - sign_in(create(:administrator).user) - - render "admin/budget_investments/select_investment", investment: investment - - expect(rendered).to have_css "form[action$='json'] input[name$='[visible_to_valuators]']" - end -end