Use a switch to toggle visibility to valuators

Using a checkbox wasn't very intuitive because checkboxes are
checked/unchecked when clicked on even if there's an error in the
request. Usually, when checkboxes appear on a form, they don't send any
information to the server unless we click a button to send the form.

So we're using a switch instead of a checkbox, like we did to
enable/disable phases in commit 46d8bc4f0.

Note that, since we've got two switches that match the default
`dom_id(record) .toggle-switch` selector, we need to find a way to
differentiate them. We're adding the `form_class` option for that.

Also note that we're now using a separate action and removing the
JavaScript in the `update` action which assumed that AJAX requests to
this action were always related to updating the `visible_to_valuators`
attribute.
This commit is contained in:
Javi Martín
2024-10-08 16:04:18 +02:00
parent 76b0971b4a
commit fc5103881d
15 changed files with 191 additions and 63 deletions

View File

@@ -39,6 +39,7 @@ class Admin::BudgetInvestments::ToggleSelectionComponent < ApplicationComponent
def options
{
"aria-label": label,
form_class: "toggle-selection",
path: path
}
end

View File

@@ -1,10 +1,5 @@
<% if can?(:admin_update, investment) %>
<%= form_for [:admin, 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_#{investment.id}" %>
<% end %>
<%= render Admin::ToggleSwitchComponent.new(action, investment, pressed: visible_to_valuators?, **options) %>
<% else %>
<%= investment.visible_to_valuators? ? t("shared.yes") : t("shared.no") %>
<%= text %>
<% end %>

View File

@@ -1,6 +1,7 @@
class Admin::BudgetInvestments::ToggleVisibleToValuatorsComponent < ApplicationComponent
attr_reader :investment
use_helpers :can?
delegate :visible_to_valuators?, to: :investment
def initialize(investment)
@investment = investment
@@ -8,7 +9,30 @@ class Admin::BudgetInvestments::ToggleVisibleToValuatorsComponent < ApplicationC
private
def budget
investment.budget
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

View File

@@ -1 +1 @@
<%= render Admin::ActionComponent.new(action, record, **default_options.merge(options)) %>
<%= render Admin::ActionComponent.new(action, record, **html_options) %>

View File

@@ -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

View File

@@ -39,24 +39,36 @@ 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
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
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
@@ -108,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

View File

@@ -1,3 +1,4 @@
<%= render "admin/shared/toggle_switch",
record: @investment,
form_class: "toggle-selection",
new_content: render(Admin::BudgetInvestments::ToggleSelectionComponent.new(@investment)) %>

View File

@@ -0,0 +1,4 @@
<%= render "admin/shared/toggle_switch",
record: @investment,
form_class: "visible-to-valuators",
new_content: render(Admin::BudgetInvestments::ToggleVisibleToValuatorsComponent.new(@investment)) %>

View File

@@ -1,5 +1,5 @@
var new_toggle_switch = $("<%= j new_content %>");
var current_toggle_switch = $("#<%= dom_id(record) %> .toggle-switch");
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();

View File

@@ -17,6 +17,7 @@ en:
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:

View File

@@ -17,6 +17,7 @@ es:
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:

View File

@@ -69,6 +69,8 @@ namespace :admin do
member do
patch :select
patch :deselect
patch :show_to_valuators
patch :hide_from_valuators
end
resources :audits, only: :show, controller: "budget_investment_audits"

View File

@@ -1,9 +1,21 @@
require "rails_helper"
describe Admin::BudgetInvestments::ToggleVisibleToValuatorsComponent, :admin do
it "uses a JSON request to update visible to valuators" do
render_inline Admin::BudgetInvestments::ToggleVisibleToValuatorsComponent.new(create(:budget_investment))
describe "aria-pressed attribute" do
it "is true for investments visible to valuators" do
investment = create(:budget_investment, :visible_to_valuators)
expect(page).to have_css "form[action$='json'] input[name$='[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

View File

@@ -23,18 +23,61 @@ 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

View File

@@ -1497,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
@@ -1511,14 +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
refresh
within("#budget_investment_#{investment1.id}") do
expect(page).to have_field "budget_investment[visible_to_valuators]", checked: true
within("tr", text: "Investment 1") do
within("[data-field=visible_to_valuators]") do
expect(page).to have_content "Yes"
end
end
end
@@ -1557,14 +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
refresh
within("#budget_investment_#{investment1.id}") do
expect(page).to have_field "budget_investment[visible_to_valuators]", checked: false
within("tr", text: "Investment 1") do
within("[data-field=visible_to_valuators]") do
expect(page).to have_content "No"
end
end
end
@@ -1576,16 +1592,16 @@ 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
@@ -1602,12 +1618,18 @@ describe "Admin budget investments", :admin do
check "Under valuation"
click_button "Filter"
within("#budget_investment_#{investment1.id}") do
expect(page).to have_field "budget_investment[visible_to_valuators]", checked: true
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
expect(page).to have_field "budget_investment[visible_to_valuators]", checked: false
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
@@ -1617,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)