Use a switch to enable/disable budget phases

In the past it would have been confusing to add a way to directly
enable/disable a phase in the phases table because it was in the middle
of the form. So we would have had next to each other controls that don't
do anything until the form is sent and controls which modify the
database immediately. That's why we couldn't add the checkboxes we used
when using the wizard.

Now the phases aren't on the same page as the budget form, so we can
edit them independently. We're using a switch, so it's consistent with
the way we enable/disable features. We could have used checkboxes, but
with checkboxes, users expect they aren't changing anything until they
click on a button to send the form, so we'd have to add a button, and it
might be missed since we're going to add "buttons" for headings and
groups to this page which won't send a form but will be links.

Since we're changing the element with JavaScript after an AJAX call, we
need a way to find the button we're changing. The easiest way is adding
an ID attribute to all admin actions buttons/links.
This commit is contained in:
Javi Martín
2021-08-23 02:23:09 +02:00
parent 349dbb74d7
commit 46d8bc4f0e
17 changed files with 137 additions and 89 deletions

View File

@@ -1,27 +1,7 @@
.budget-phases-table {
.budget-phase-enabled {
@include has-fa-icon(check, solid);
color: $check;
}
.budget-phase-disabled {
@include has-fa-icon(times, solid);
color: $black;
}
.budget-phase-enabled,
.budget-phase-disabled {
display: block;
font-size: 1px;
left: $off-screen-left;
line-height: 1;
position: relative;
&::before {
font-size: $base-font-size;
left: -1 * $off-screen-left;
position: relative;
}
[aria-pressed] {
@include switch;
margin-bottom: 0;
}
}

View File

@@ -25,6 +25,7 @@ class Admin::ActionComponent < ApplicationComponent
def html_options
{
class: html_class,
id: (dom_id(record, action) if record.respond_to?(:to_key)),
"aria-label": label,
data: {
confirm: confirmation_text,

View File

@@ -1,9 +1,8 @@
class Admin::BudgetPhases::PhasesComponent < ApplicationComponent
attr_reader :budget, :form
attr_reader :budget
def initialize(budget, form: nil)
def initialize(budget)
@budget = budget
@form = form
end
private
@@ -17,25 +16,7 @@ class Admin::BudgetPhases::PhasesComponent < ApplicationComponent
end
def enabled_cell(phase)
if form
form.fields_for :phases, phase do |phase_fields|
phase_fields.check_box :enabled,
label: false,
aria: {
label: t("admin.budgets.edit.enable_phase", phase: phase.name)
}
end
else
enabled_text(phase)
end
end
def enabled_text(phase)
if phase.enabled?
tag.span t("shared.yes"), class: "budget-phase-enabled"
else
tag.span t("shared.no"), class: "budget-phase-disabled"
end
render Admin::BudgetPhases::ToggleEnabledComponent.new(phase)
end
def edit_path(phase)

View File

@@ -0,0 +1 @@
<%= render Admin::ActionComponent.new(:toggle_enabled, phase, options) %>

View File

@@ -0,0 +1,27 @@
class Admin::BudgetPhases::ToggleEnabledComponent < ApplicationComponent
attr_reader :phase
def initialize(phase)
@phase = phase
end
private
def options
{
text: text,
method: :patch,
remote: true,
"aria-label": t("admin.budgets.edit.enable_phase", phase: phase.name),
"aria-pressed": phase.enabled?
}
end
def text
if phase.enabled?
t("shared.yes")
else
t("shared.no")
end
end
end

View File

@@ -5,7 +5,5 @@
<%= render Admin::Budgets::HelpComponent.new("phases") %>
<%= render Admin::BudgetsWizard::CreationTimelineComponent.new("phases") %>
<%= form_for budget, url: update_all_admin_budgets_wizard_budget_budget_phases_path(budget) do |f| %>
<%= render Admin::BudgetPhases::PhasesComponent.new(budget, form: f) %>
<%= f.submit t("admin.budgets_wizard.phases.continue"), class: "button success" %>
<% end %>
<%= render Admin::BudgetPhases::PhasesComponent.new(budget) %>
<%= link_to t("admin.budgets_wizard.phases.continue"), admin_budgets_path, class: "button success" %>

View File

@@ -4,23 +4,9 @@ class Admin::BudgetsWizard::PhasesController < Admin::BudgetsWizard::BaseControl
def index
end
def update_all
@budget.update!(phases_params)
redirect_to admin_budgets_path, notice: t("admin.budgets_wizard.phases.update_all.notice")
end
private
def phases_index
admin_budgets_wizard_budget_budget_phases_path(@phase.budget, url_params)
end
def phases_params
params.require(:budget).permit(allowed_phases_params)
end
def allowed_phases_params
{ phases_attributes: [:id, :enabled] }
end
end

View File

@@ -6,7 +6,7 @@ module Admin::BudgetPhasesActions
include ImageAttributes
before_action :load_budget
before_action :load_phase, only: [:edit, :update]
before_action :load_phase, only: [:edit, :update, :toggle_enabled]
end
def edit
@@ -20,6 +20,15 @@ module Admin::BudgetPhasesActions
end
end
def toggle_enabled
@phase.update!(enabled: !@phase.enabled)
respond_to do |format|
format.html { redirect_to phases_index, notice: t("flash.actions.save_changes.notice") }
format.js
end
end
private
def load_budget

View File

@@ -0,0 +1 @@
<%= render template: "admin/budgets_wizard/phases/toggle_enabled" %>

View File

@@ -0,0 +1,4 @@
var replacement = $("<%= j render Admin::BudgetPhases::ToggleEnabledComponent.new(@phase) %>");
var form = $("#" + replacement.find("[type='submit']").attr("id")).closest("form");
form.html(replacement.html()).find("[type='submit']").focus();

View File

@@ -340,8 +340,6 @@ en:
back: "Go back to headings"
single:
back: "Go back to edit heading"
update_all:
notice: "Phases configured successfully"
milestones:
index:
table_id: "ID"

View File

@@ -340,8 +340,6 @@ es:
back: "Volver a partidas"
single:
back: "Volver a editar partida"
update_all:
notice: "Fases configuradas con éxito"
milestones:
index:
table_id: "ID"

View File

@@ -69,7 +69,9 @@ namespace :admin do
resources :progress_bars, except: :show, controller: "budget_investment_progress_bars"
end
resources :budget_phases, only: [:edit, :update]
resources :budget_phases, only: [:edit, :update] do
member { patch :toggle_enabled }
end
end
namespace :budgets_wizard do
@@ -79,7 +81,7 @@ namespace :admin do
end
resources :phases, as: "budget_phases", only: [:index, :edit, :update] do
collection { patch :update_all }
member { patch :toggle_enabled }
end
end
end

View File

@@ -16,6 +16,31 @@ describe Admin::ActionComponent do
end
end
describe "HTML id" do
it "is not rendered for non-ActiveModel records" do
render_inline Admin::ActionComponent.new(:edit, double, path: "/")
expect(page).not_to have_css "[id]"
end
it "includes an id based on the model and the action by default" do
record = double(model_name: double(param_key: "computer"), to_key: [1])
render_inline Admin::ActionComponent.new(:edit, record, path: "/")
expect(page).to have_css "a.edit-link#edit_computer_1"
end
it "can be overwritten" do
record = double(model_name: double(param_key: "computer"), to_key: [1])
render_inline Admin::ActionComponent.new(:edit, record, path: "/", id: "my_id")
expect(page).to have_css "a.edit-link#my_id"
expect(page).not_to have_css "#edit_computer_1"
end
end
describe "aria-label attribute" do
it "is not rendered by default" do
record = double(human_name: "Stay home")

View File

@@ -0,0 +1,28 @@
require "rails_helper"
describe Admin::BudgetPhases::ToggleEnabledComponent, controller: Admin::BaseController do
let(:phase) { create(:budget).phases.informing }
let(:component) { Admin::BudgetPhases::ToggleEnabledComponent.new(phase) }
it "is pressed when the phase is enabled" do
phase.update!(enabled: true)
render_inline component
expect(page).to have_button count: 1
expect(page).to have_button exact_text: "Yes"
expect(page).to have_button "Enable Information phase"
expect(page).to have_css "button[aria-pressed='true']"
end
it "is not pressed when the phase is disabled" do
phase.update!(enabled: false)
render_inline component
expect(page).to have_button count: 1
expect(page).to have_button exact_text: "No"
expect(page).to have_button "Enable Information phase"
expect(page).to have_css "button[aria-pressed='false']"
end
end

View File

@@ -23,29 +23,42 @@ describe "Budgets wizard, phases step", :admin do
scenario "Enable and disable phases" do
visit admin_budgets_wizard_budget_budget_phases_path(budget)
uncheck "Enable Information phase"
uncheck "Enable Reviewing voting phase"
click_button "Finish"
expect(page).to have_content "Phases configured successfully"
visit admin_budget_path(budget)
within "tr", text: "Information" do
expect(page).to have_css ".budget-phase-disabled", visible: :all
expect(page).to have_content "Yes"
click_button "Enable Information phase"
expect(page).to have_content "No"
expect(page).not_to have_content "Yes"
end
within "tr", text: "Reviewing voting" do
expect(page).to have_css ".budget-phase-disabled", visible: :all
expect(page).to have_content "Yes"
click_button "Enable Reviewing voting phase"
expect(page).to have_content "No"
expect(page).not_to have_content "Yes"
end
click_link "Finish"
within("tr", text: budget.name) { click_link "Edit" }
within "tr", text: "Information" do
expect(page).to have_content "No"
end
within "tr", text: "Reviewing voting" do
expect(page).to have_content "No"
end
within "tr", text: "Accepting projects" do
expect(page).to have_css ".budget-phase-enabled", visible: :all
expect(page).to have_content "Yes"
end
within "tr", text: "Voting projects" do
expect(page).to have_css ".budget-phase-enabled", visible: :all
expect(page).to have_content "Yes"
end
end
end

View File

@@ -22,9 +22,7 @@ describe "Budgets creation wizard", :admin do
expect(page).to have_css ".budget-phases-table"
click_button "Finish"
expect(page).to have_content "Phases configured successfully"
click_link "Finish"
within "tr", text: "Single heading budget" do
click_link "Heading groups"
@@ -119,9 +117,7 @@ describe "Budgets creation wizard", :admin do
expect(page).not_to have_content "Voting projects"
end
click_button "Finish"
expect(page).to have_content "Phases configured successfully"
click_link "Finish"
within "tr", text: "Multiple headings budget" do
click_link "Heading groups"