Merge pull request #4531 from consul/budget-steps

Split budget creation in steps
This commit is contained in:
Javi Martín
2021-06-09 13:48:28 +02:00
committed by GitHub
104 changed files with 1850 additions and 500 deletions

View File

@@ -0,0 +1,20 @@
(function() {
"use strict";
App.AdminBudgetsWizardCreationStep = {
initialize: function() {
var element, add_button, cancel_button;
element = $(".admin .budget-creation-step");
add_button = element.find(".add");
cancel_button = element.find(".delete");
add_button.click(function() {
$(this).attr("aria-expanded", true).parent().find(":input:visible:first").focus();
});
cancel_button.click(function() {
add_button.attr("aria-expanded", false).focus();
});
}
};
}).call(this);

View File

@@ -113,6 +113,7 @@
//= require columns_selector //= require columns_selector
//= require budget_edit_associations //= require budget_edit_associations
//= require datepicker //= require datepicker
//= require_tree ./admin
//= require_tree ./sdg //= require_tree ./sdg
//= require_tree ./sdg_management //= require_tree ./sdg_management
@@ -166,6 +167,7 @@ var initialize_modules = function() {
if ($("#js-columns-selector").length) { if ($("#js-columns-selector").length) {
App.ColumnsSelector.initialize(); App.ColumnsSelector.initialize();
} }
App.AdminBudgetsWizardCreationStep.initialize();
App.BudgetEditAssociations.initialize(); App.BudgetEditAssociations.initialize();
App.Datepicker.initialize(); App.Datepicker.initialize();
App.SDGRelatedListSelector.initialize(); App.SDGRelatedListSelector.initialize();

View File

@@ -258,6 +258,11 @@ $table-header: #ecf1f6;
[type="submit"] ~ a { [type="submit"] ~ a {
margin-left: $line-height / 2; margin-left: $line-height / 2;
} }
[type="checkbox"] {
margin-bottom: 0;
vertical-align: middle;
}
} }
hr { hr {

View File

@@ -0,0 +1,3 @@
.admin .budget-groups-form {
@include full-width-form;
}

View File

@@ -0,0 +1,3 @@
.admin .budget-headings-form {
@include full-width-form;
}

View File

@@ -1,4 +1,5 @@
.admin .budgets-form { .admin .budgets-form {
@include full-width-form;
> fieldset { > fieldset {
border-top: 4px solid $admin-border-color; border-top: 4px solid $admin-border-color;
@@ -17,8 +18,4 @@
text-transform: uppercase; text-transform: uppercase;
} }
} }
.globalize-languages {
max-width: none;
}
} }

View File

@@ -0,0 +1,50 @@
.budget-creation-step {
.add {
@include has-fa-icon(plus-square, solid);
@include regular-button;
font-weight: bold;
padding-left: rem-calc(10);
&::before {
margin-right: rem-calc(12);
}
&[aria-expanded="false"] {
~ :not(.next-step) {
display: none;
}
}
&[aria-expanded="true"] {
display: none;
~ .next-step {
display: none;
}
}
}
.cancel {
display: block;
margin-bottom: $line-height;
}
.next-step {
@include regular-button;
}
a {
&.next-step {
@include button-style($success-color, auto, auto);
}
}
p {
&.next-step {
@include button-style($secondary-color, auto, auto);
@include button-disabled;
}
}
}

View File

@@ -0,0 +1,34 @@
.creation-timeline {
display: flex;
list-style-type: none;
margin: $line-height * 2 0;
position: relative;
li {
border-top: 4px solid $brand;
display: inline-block;
font-size: $small-font-size;
font-weight: bold;
padding: $line-height / 2 $line-height * 3 0;
text-transform: uppercase;
&::before {
background: $brand;
border-radius: 50%;
content: "";
height: 20px;
margin-left: $line-height / 2;
position: absolute;
top: -8px;
width: 20px;
}
&[aria-current] ~ * {
border-color: $admin-border-color;
&::before {
background: $admin-border-color;
}
}
}
}

View File

@@ -0,0 +1,50 @@
.budget-group-switcher {
margin-bottom: $line-height;
p {
margin-bottom: 0;
}
> .menu > li {
> button {
align-items: center;
border: $admin-border;
border-radius: $button-radius;
display: inline-flex;
padding: rem-calc(11) rem-calc(16);
&:active {
color: inherit;
}
&::after {
@include css-triangle($dropdownmenu-arrow-size, currentcolor, down);
margin-left: 0.2em;
}
}
}
.menu.is-dropdown-submenu {
margin: 0;
min-width: 100%;
padding: 0;
li {
margin-bottom: 0;
}
a {
cursor: default;
padding: rem-calc(11) rem-calc(16);
width: 100%;
&:focus,
&:hover {
@include brand-background;
text-decoration: none;
outline: none;
}
}
}
}

View File

@@ -1,4 +1,4 @@
%button { @mixin base-button {
font-size: $base-font-size; font-size: $base-font-size;
&:focus, &:focus,
@@ -7,16 +7,20 @@
} }
} }
%button {
@include base-button;
}
@mixin regular-button($color: $brand) { @mixin regular-button($color: $brand) {
@include button($background: $color); @include button($background: $color);
@include inverted-selection; @include inverted-selection;
@extend %button; @include base-button;
} }
@mixin hollow-button($color: $link) { @mixin hollow-button($color: $link) {
@include button($style: hollow, $background: $color); @include button($style: hollow, $background: $color);
@include normal-selection; @include normal-selection;
@extend %button; @include base-button;
margin-bottom: 0; margin-bottom: 0;
} }

View File

@@ -21,3 +21,9 @@
margin-top: 0; margin-top: 0;
} }
} }
@mixin full-width-form {
.globalize-languages {
max-width: none;
}
}

View File

@@ -0,0 +1,33 @@
<% if groups.any? %>
<h3><%= t("admin.budget_groups.amount", count: groups.count) %></h3>
<table>
<thead>
<tr id="<%= dom_id(budget) %>">
<th><%= t("admin.budget_groups.name") %></th>
<th><%= Budget::Group.human_attribute_name(:max_votable_headings) %></th>
<th><%= t("admin.budget_groups.headings_name") %></th>
<th><%= t("admin.actions.actions") %></th>
</tr>
</thead>
<tbody>
<% groups.each do |group| %>
<tr id="<%= dom_id(group) %>">
<td><%= group.name %></td>
<td><%= group.max_votable_headings %></td>
<td><%= group.headings.count %></td>
<td>
<%= render Admin::TableActionsComponent.new(group) do |actions| %>
<%= actions.link_to t("admin.budget_groups.headings_manage"),
headings_path(actions, group),
class: "headings-link" %>
<% end %>
</td>
</tr>
<% end %>
</tbody>
</table>
<% else %>
<div class="callout primary clear">
<%= t("admin.budget_groups.no_groups") %>
</div>
<% end %>

View File

@@ -0,0 +1,17 @@
class Admin::BudgetGroups::GroupsComponent < ApplicationComponent
attr_reader :groups
def initialize(groups)
@groups = groups
end
private
def budget
@budget ||= groups.first.budget
end
def headings_path(table_actions_component, group)
send("#{table_actions_component.namespace}_budget_group_headings_path", group.budget, group)
end
end

View File

@@ -0,0 +1,39 @@
<% if headings.any? %>
<h3><%= t("admin.budget_headings.amount", count: headings.count) %></h3>
<table>
<thead>
<tr id="<%= dom_id(group) %>">
<th><%= Budget::Heading.human_attribute_name(:name) %></th>
<th><%= Budget::Heading.human_attribute_name(:price) %></th>
<% if budget.approval_voting? %>
<th><%= Budget::Heading.human_attribute_name(:max_ballot_lines) %></th>
<% end %>
<th><%= Budget::Heading.human_attribute_name(:population) %></th>
<th><%= Budget::Heading.human_attribute_name(:allow_custom_content) %></th>
<th><%= t("admin.actions.actions") %></th>
</tr>
</thead>
<tbody>
<% headings.each do |heading| %>
<tr id="<%= dom_id(heading) %>" class="heading">
<td><%= heading.name %></td>
<td><%= budget.formatted_heading_price(heading) %></td>
<% if budget.approval_voting? %>
<td><%= heading.max_ballot_lines %></td>
<% end %>
<td><%= heading.population %></td>
<td>
<%= heading.allow_custom_content ? t("admin.shared.true_value") : t("admin.shared.false_value") %>
</td>
<td>
<%= render Admin::TableActionsComponent.new(heading) %>
</td>
</tr>
<% end %>
</tbody>
</table>
<% else %>
<div class="callout primary clear">
<%= t("admin.budget_headings.no_headings") %>
</div>
<% end %>

View File

@@ -0,0 +1,17 @@
class Admin::BudgetHeadings::HeadingsComponent < ApplicationComponent
attr_reader :headings
def initialize(headings)
@headings = headings
end
private
def group
@group ||= headings.first.group
end
def budget
@budget ||= headings.first.budget
end
end

View File

@@ -0,0 +1,49 @@
<%= render "shared/globalize_locales", resource: phase %>
<%= translatable_form_for [namespace, phase.budget, phase], html: { class: "budget-phases-form" } do |f| %>
<%= render "shared/errors", resource: phase %>
<fieldset>
<legend aria-describedby="phase_duration_description">
<%= t("admin.budget_phases.edit.duration") %>
</legend>
<p class="help-text" id="phase_duration_description">
<%= t("admin.budget_phases.edit.duration_description") %>
</p>
<div class="date-field">
<%= f.date_field :starts_at, id: "start_date" %>
</div>
<div class="date-field">
<%= f.date_field :ends_at, id: "end_date" %>
</div>
</fieldset>
<div class="small-12 column margin">
<%= f.check_box :enabled %>
<span class="help-text" id="phase-summary-help-text">
<%= t("admin.budget_phases.edit.enabled_help_text") %>
</span>
</div>
<%= f.translatable_fields do |translations_form| %>
<div class="small-12 column">
<%= translations_form.text_field :name, hint: t("admin.budget_phases.edit.name_help_text") %>
</div>
<div class="small-12 column">
<%= translations_form.text_area :description,
maxlength: Budget::Phase::DESCRIPTION_MAX_LENGTH,
class: "html-area",
hint: t("admin.budget_phases.edit.description_help_text") %>
</div>
<% end %>
<div class="small-12 column">
<%= f.submit t("admin.budget_phases.edit.save_changes"), class: "button success" %>
</div>
<% end %>

View File

@@ -0,0 +1,11 @@
class Admin::BudgetPhases::FormComponent < ApplicationComponent
include TranslatableFormHelper
include GlobalizeHelper
include Admin::Namespace
attr_reader :phase
def initialize(phase)
@phase = phase
end
end

View File

@@ -26,7 +26,7 @@
<% end %> <% end %>
</td> </td>
<td class="text-center"> <td class="text-center">
<%= enabled_text(phase) %> <%= enabled_cell(phase) %>
</td> </td>
<td> <td>
<%= render Admin::TableActionsComponent.new(phase, <%= render Admin::TableActionsComponent.new(phase,

View File

@@ -1,8 +1,9 @@
class Admin::BudgetPhases::PhasesComponent < ApplicationComponent class Admin::BudgetPhases::PhasesComponent < ApplicationComponent
attr_reader :budget attr_reader :budget, :form
def initialize(budget) def initialize(budget, form: nil)
@budget = budget @budget = budget
@form = form
end end
private private
@@ -15,6 +16,20 @@ class Admin::BudgetPhases::PhasesComponent < ApplicationComponent
Admin::Budgets::DurationComponent.new(phase).dates Admin::Budgets::DurationComponent.new(phase).dates
end 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) def enabled_text(phase)
if phase.enabled? if phase.enabled?
tag.span t("shared.yes"), class: "budget-phase-enabled" tag.span t("shared.yes"), class: "budget-phase-enabled"

View File

@@ -1,8 +1,8 @@
<%= translatable_form_for [:admin, @budget], html: { class: "budgets-form" } do |f| %> <%= translatable_form_for [namespace, budget], html: { class: "budgets-form" } do |f| %>
<fieldset> <fieldset>
<legend><%= t("admin.budgets.edit.info.budget_settings") %></legend> <legend><%= t("admin.budgets.edit.info.budget_settings") %></legend>
<%= render "shared/globalize_locales", resource: @budget %> <%= render "shared/globalize_locales", resource: budget %>
<%= render "shared/errors", resource: @budget %> <%= render "shared/errors", resource: budget %>
<%= f.translatable_fields do |translations_form| %> <%= f.translatable_fields do |translations_form| %>
<div class="small-12 medium-9 large-6 column end"> <div class="small-12 medium-9 large-6 column end">
@@ -13,11 +13,11 @@
<% end %> <% end %>
<div class="small-12 medium-4 column"> <div class="small-12 medium-4 column">
<%= f.select :voting_style, budget_voting_styles_select_options %> <%= f.select :voting_style, voting_styles_select_options %>
</div> </div>
<div class="small-12 medium-2 column end"> <div class="small-12 medium-2 column end">
<%= f.select :currency_symbol, budget_currency_symbol_select_options %> <%= f.select :currency_symbol, currency_symbol_select_options %>
</div> </div>
</fieldset> </fieldset>
@@ -26,52 +26,55 @@
<% %w[administrators valuators].each do |staff| %> <% %w[administrators valuators].each do |staff| %>
<div class="small-12 medium-6 large-3 column end"> <div class="small-12 medium-6 large-3 column end">
<%= link_to t("admin.budgets.edit.#{staff}", count: @budget.send(staff).count), <%= link_to t("admin.budgets.edit.#{staff}", count: budget.send(staff).count),
"#", "#",
class: "button expanded hollow js-budget-show-#{staff}-list js-budget-show-users-list", class: "button expanded hollow js-budget-show-#{staff}-list js-budget-show-users-list",
data: { toggle: "#{staff}_list", texts: t("admin.budgets.edit.#{staff}") } %> data: { toggle: "#{staff}_list", texts: t("admin.budgets.edit.#{staff}") } %>
</div> </div>
<% end %> <% end %>
<%= render "/admin/budgets/association", assignable_type: "administrators", assignables: @admins, form: f %> <%= render "/admin/budgets/association", assignable_type: "administrators", assignables: admins, form: f %>
<%= render "/admin/budgets/association", assignable_type: "valuators", assignables: @valuators, form: f %> <%= render "/admin/budgets/association", assignable_type: "valuators", assignables: valuators, form: f %>
</fieldset> </fieldset>
<fieldset> <% unless wizard? %>
<legend><%= t("admin.budgets.edit.info.phases_settings") %></legend> <fieldset>
<div class="small-12 medium-6 column"> <legend><%= t("admin.budgets.edit.info.phases_settings") %></legend>
<%= f.select :phase, budget_phases_select_options %> <div class="small-12 medium-6 column">
</div> <%= f.select :phase, phases_select_options %>
</div>
<%= render Admin::Budgets::HelpComponent.new("budget_phases") %> <%= render Admin::BudgetPhases::PhasesComponent.new(budget) %>
<%= render Admin::BudgetPhases::PhasesComponent.new(@budget) %> </fieldset>
</fieldset>
<% if @budget.persisted? %>
<%= render "admin/shared/show_results_fields", form: f %> <%= render "admin/shared/show_results_fields", form: f %>
<% end %> <% end %>
<div class="small-12 column"> <div class="small-12 column">
<div class="clear small-12 medium-4 large-3 inline-block"> <div class="clear small-12 medium-4 large-3 inline-block">
<%= f.submit nil, class: "button success" %> <% if wizard? %>
<%= f.submit t("admin.budgets_wizard.budgets.continue"), class: "button success expanded" %>
<% else %>
<%= f.submit nil, class: "button success" %>
<% end %>
</div> </div>
<div class="float-right"> <div class="float-right">
<% if display_calculate_winners_button?(@budget) %> <% if display_calculate_winners_button?(budget) %>
<%= link_to calculate_winner_button_text(@budget), <%= link_to calculate_winner_button_text(budget),
calculate_winners_admin_budget_path(@budget), calculate_winners_admin_budget_path(budget),
method: :put, method: :put,
class: "button hollow" %> class: "button hollow" %>
<% end %> <% end %>
<% if @budget.has_winning_investments? %> <% if budget.has_winning_investments? %>
<%= link_to t("budgets.show.see_results"), <%= link_to t("budgets.show.see_results"),
budget_results_path(@budget), budget_results_path(budget),
class: "button hollow margin-left" %> class: "button hollow margin-left" %>
<% end %> <% end %>
<% if @budget.persisted? %> <% if budget.persisted? %>
<%= link_to t("admin.budgets.edit.delete"), <%= link_to t("admin.budgets.edit.delete"),
admin_budget_path(@budget), admin_budget_path(budget),
method: :delete, method: :delete,
class: "delete float-right margin-left" %> class: "delete float-right margin-left" %>
<% end %> <% end %>

View File

@@ -0,0 +1,41 @@
class Admin::Budgets::FormComponent < ApplicationComponent
include TranslatableFormHelper
include GlobalizeHelper
include Admin::Namespace
attr_reader :budget, :wizard
alias_method :wizard?, :wizard
delegate :display_calculate_winners_button?,
:calculate_winner_button_text,
:calculate_winners_admin_budget_path,
to: :helpers
def initialize(budget, wizard: false)
@budget = budget
@wizard = wizard
end
def voting_styles_select_options
Budget::VOTING_STYLES.map do |style|
[Budget.human_attribute_name("voting_style_#{style}"), style]
end
end
def currency_symbol_select_options
Budget::CURRENCY_SYMBOLS.map { |cs| [cs, cs] }
end
def phases_select_options
Budget::Phase::PHASE_KINDS.map { |ph| [t("budgets.phase.#{ph}"), ph] }
end
private
def admins
@admins ||= Administrator.includes(:user)
end
def valuators
@valuators ||= Valuator.includes(:user).order(description: :asc).order("users.email ASC")
end
end

View File

@@ -1,5 +1,5 @@
<%= header do %> <%= header do %>
<%= link_to t("admin.budgets.index.new_link"), new_admin_budget_path %> <%= link_to t("admin.budgets.index.new_link"), new_admin_budgets_wizard_budget_path %>
<% end %> <% end %>
<%= render Admin::Budgets::HelpComponent.new("budgets") %> <%= render Admin::Budgets::HelpComponent.new("budgets") %>

View File

@@ -1,4 +1,8 @@
<%= render Admin::TableActionsComponent.new(budget, actions: [:edit], edit_text: t("admin.budgets.index.edit_budget")) do %> <%= render Admin::TableActionsComponent.new(budget,
edit_text: t("admin.budgets.index.edit_budget"),
destroy_confirmation: t("admin.actions.confirm_delete", resource_name: t("admin.budgets.shared.resource_name"),
name: budget.name)
) do %>
<%= link_to t("admin.budgets.index.budget_investments"), <%= link_to t("admin.budgets.index.budget_investments"),
admin_budget_budget_investments_path(budget_id: budget.id), admin_budget_budget_investments_path(budget_id: budget.id),
class: "investments-link" %> class: "investments-link" %>

View File

@@ -0,0 +1,6 @@
<%= back_link_to admin_budgets_path %>
<%= header %>
<%= render Admin::BudgetsWizard::CreationTimelineComponent.new("budget") %>
<%= render Admin::Budgets::FormComponent.new(budget, wizard: true) %>

View File

@@ -0,0 +1,12 @@
class Admin::BudgetsWizard::Budgets::EditComponent < ApplicationComponent
include Header
attr_reader :budget
def initialize(budget)
@budget = budget
end
def title
t("admin.budgets.edit.title")
end
end

View File

@@ -0,0 +1,6 @@
<%= back_link_to admin_budgets_path %>
<%= header %>
<%= render Admin::BudgetsWizard::CreationTimelineComponent.new("budget") %>
<%= render Admin::Budgets::FormComponent.new(budget, wizard: true) %>

View File

@@ -0,0 +1,12 @@
class Admin::BudgetsWizard::Budgets::NewComponent < ApplicationComponent
include Header
attr_reader :budget
def initialize(budget)
@budget = budget
end
def title
t("admin.budgets.new.title")
end
end

View File

@@ -0,0 +1,19 @@
<div class="budget-creation-step">
<button type="button" class="add" aria-expanded="<%= show_form? %>">
<%= t("admin.#{i18n_namespace_with_budget}.index.new_button") %>
</button>
<%= content %>
<button type="button" class="cancel delete"><%= t("links.form.cancel_button") %></button>
<% if next_step_path %>
<%= link_to t("admin.budgets_wizard.#{i18n_namespace}.continue"),
next_step_path,
class: "next-step" %>
<% else %>
<p class="next-step">
<%= t("admin.budgets_wizard.#{i18n_namespace}.continue") %>
</p>
<% end %>
</div>

View File

@@ -0,0 +1,22 @@
class Admin::BudgetsWizard::CreationStepComponent < ApplicationComponent
attr_reader :record, :next_step_path
def initialize(record, next_step_path)
@record = record
@next_step_path = next_step_path
end
private
def show_form?
record.errors.any?
end
def i18n_namespace
i18n_namespace_with_budget.gsub("budget_", "")
end
def i18n_namespace_with_budget
record.class.table_name
end
end

View File

@@ -0,0 +1,7 @@
<ol class="creation-timeline">
<% steps.each do |step| %>
<li <%= "aria-current='step'" if step == current_step %>>
<%= t("admin.budgets_wizard.creation_timeline.#{step}") %>
</li>
<% end %>
</ol>

View File

@@ -0,0 +1,13 @@
class Admin::BudgetsWizard::CreationTimelineComponent < ApplicationComponent
attr_reader :current_step
def initialize(current_step)
@current_step = current_step
end
private
def steps
%w[budget groups headings phases]
end
end

View File

@@ -0,0 +1,3 @@
<%= render Admin::BudgetsWizard::CreationStepComponent.new(group, next_step_path) do %>
<%= render "/admin/budget_groups/form", group: group, path: form_path, action: "create" %>
<% end %>

View File

@@ -0,0 +1,26 @@
class Admin::BudgetsWizard::Groups::CreationStepComponent < ApplicationComponent
attr_reader :group, :next_step_group
def initialize(group, next_step_group)
@group = group
@next_step_group = next_step_group
end
private
def budget
group.budget
end
def form_path
admin_budgets_wizard_budget_groups_path(budget)
end
def next_step_path
admin_budgets_wizard_budget_group_headings_path(budget, next_step_group) if next_step_enabled?
end
def next_step_enabled?
next_step_group.present?
end
end

View File

@@ -0,0 +1,7 @@
<%= back_link_to admin_budgets_wizard_budget_groups_path(budget) %>
<%= header %>
<%= render Admin::BudgetsWizard::CreationTimelineComponent.new("groups") %>
<%= render "/admin/budget_groups/form", group: group, path: form_path, action: "submit" %>

View File

@@ -0,0 +1,22 @@
class Admin::BudgetsWizard::Groups::EditComponent < ApplicationComponent
include Header
attr_reader :group
def initialize(group)
@group = group
end
def budget
group.budget
end
def title
budget.name
end
private
def form_path
admin_budgets_wizard_budget_group_path(budget, group)
end
end

View File

@@ -0,0 +1,9 @@
<%= back_link_to edit_admin_budgets_wizard_budget_path(budget), t("admin.budgets_wizard.groups.back") %>
<%= header %>
<%= render Admin::Budgets::HelpComponent.new("budget_groups") %>
<%= render Admin::BudgetsWizard::CreationTimelineComponent.new("groups") %>
<%= render Admin::BudgetGroups::GroupsComponent.new(groups) %>
<%= render Admin::BudgetsWizard::Groups::CreationStepComponent.new(new_group, groups.first) %>

View File

@@ -0,0 +1,17 @@
class Admin::BudgetsWizard::Groups::IndexComponent < ApplicationComponent
include Header
attr_reader :groups, :new_group
def initialize(groups, new_group)
@groups = groups
@new_group = new_group
end
def budget
@new_group.budget
end
def title
budget.name
end
end

View File

@@ -0,0 +1,3 @@
<%= render Admin::BudgetsWizard::CreationStepComponent.new(heading, next_step_path) do %>
<%= render "/admin/budget_headings/form", heading: heading, path: form_path, action: "create" %>
<% end %>

View File

@@ -0,0 +1,25 @@
class Admin::BudgetsWizard::Headings::CreationStepComponent < ApplicationComponent
attr_reader :heading
def initialize(heading)
@heading = heading
end
private
def budget
heading.budget
end
def form_path
admin_budgets_wizard_budget_group_headings_path(heading.group.budget, heading.group)
end
def next_step_path
admin_budgets_wizard_budget_budget_phases_path(budget) if next_step_enabled?
end
def next_step_enabled?
budget.headings.any?
end
end

View File

@@ -0,0 +1,7 @@
<%= back_link_to admin_budgets_wizard_budget_group_headings_path(budget, group) %>
<%= header %>
<%= render Admin::BudgetsWizard::CreationTimelineComponent.new("headings") %>
<%= render "/admin/budget_headings/form", heading: heading, path: form_path, action: "submit" %>

View File

@@ -0,0 +1,26 @@
class Admin::BudgetsWizard::Headings::EditComponent < ApplicationComponent
include Header
attr_reader :heading
def initialize(heading)
@heading = heading
end
def budget
heading.budget
end
def group
heading.group
end
def title
heading.name
end
private
def form_path
admin_budgets_wizard_budget_group_heading_path(budget, group, heading)
end
end

View File

@@ -0,0 +1,21 @@
<div class="budget-group-switcher">
<% if other_groups.one? %>
<p>
<%= t("admin.budget_headings.group_switcher.currently_showing", group: group.name) %>
<%= link_to t("admin.budget_headings.group_switcher.the_other_group", group: other_groups.first.name),
headings_path(other_groups.first) %>
</p>
<% else %>
<p><%= t("admin.budget_headings.group_switcher.currently_showing", group: group.name) %></p>
<ul class="dropdown menu" data-dropdown-menu data-disable-hover="true" data-click-open="true">
<li class="has-submenu">
<button type="button"><%= t("admin.budget_headings.group_switcher.different_group") %></button>
<ul class="menu" data-submenu>
<% other_groups.each do |other_group| %>
<li><%= link_to other_group.name, headings_path(other_group) %></li>
<% end %>
</ul>
</li>
</ul>
<% end %>
</div>

View File

@@ -0,0 +1,25 @@
class Admin::BudgetsWizard::Headings::GroupSwitcherComponent < ApplicationComponent
attr_reader :group
def initialize(group)
@group = group
end
def render?
other_groups.any?
end
private
def budget
group.budget
end
def other_groups
@other_groups ||= budget.groups.sort_by_name - [group]
end
def headings_path(group)
admin_budgets_wizard_budget_group_headings_path(budget, group)
end
end

View File

@@ -0,0 +1,10 @@
<%= back_link_to admin_budgets_wizard_budget_groups_path(budget), t("admin.budget_headings.index.back") %>
<%= header %>
<%= render Admin::Budgets::HelpComponent.new("budget_headings") %>
<%= render Admin::BudgetsWizard::CreationTimelineComponent.new("headings") %>
<%= render Admin::BudgetsWizard::Headings::GroupSwitcherComponent.new(group) %>
<%= render Admin::BudgetHeadings::HeadingsComponent.new(headings) %>
<%= render Admin::BudgetsWizard::Headings::CreationStepComponent.new(new_heading) %>

View File

@@ -0,0 +1,21 @@
class Admin::BudgetsWizard::Headings::IndexComponent < ApplicationComponent
include Header
attr_reader :headings, :new_heading
def initialize(headings, new_heading)
@headings = headings
@new_heading = new_heading
end
def budget
group.budget
end
def group
new_heading.group
end
def title
t("admin.budget_headings.index.title", budget: budget.name, group: group.name)
end
end

View File

@@ -0,0 +1,6 @@
<%= back_link_to admin_budgets_wizard_budget_budget_phases_path(budget) %>
<%= header %>
<%= render Admin::BudgetsWizard::CreationTimelineComponent.new("phases") %>
<%= render "/admin/budget_phases/form" %>

View File

@@ -0,0 +1,22 @@
class Admin::BudgetsWizard::Phases::EditComponent < ApplicationComponent
include Header
attr_reader :phase
def initialize(phase)
@phase = phase
end
def budget
phase.budget
end
def title
"#{t("admin.budget_phases.edit.title")} - #{phase.name}"
end
private
def form_path
admin_budgets_wizard_budget_budget_phases_path(budget)
end
end

View File

@@ -0,0 +1,11 @@
<%= back_link_to back_link_path, t("admin.budgets_wizard.phases.back") %>
<%= header %>
<%= render Admin::Budgets::HelpComponent.new("budget_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 %>

View File

@@ -0,0 +1,18 @@
class Admin::BudgetsWizard::Phases::IndexComponent < ApplicationComponent
include Header
attr_reader :budget
def initialize(budget)
@budget = budget
end
def title
t("admin.budget_phases.index.title", budget: budget.name)
end
private
def back_link_path
admin_budgets_wizard_budget_group_headings_path(budget, budget.groups.first)
end
end

View File

@@ -13,7 +13,7 @@ class Admin::MenuComponent < ApplicationComponent
end end
def budgets? def budgets?
controller_name.starts_with?("budget") controller_name.starts_with?("budget") || controller_path =~ /budgets_wizard/
end end
def polls? def polls?

View File

@@ -1,7 +1,7 @@
class Admin::TableActionsComponent < ApplicationComponent class Admin::TableActionsComponent < ApplicationComponent
include TableActionLink include TableActionLink
include Admin::Namespace
attr_reader :record, :options attr_reader :record, :options
delegate :namespace, to: :helpers
def initialize(record = nil, **options) def initialize(record = nil, **options)
@record = record @record = record

View File

@@ -0,0 +1,9 @@
module Admin::Namespace
def namespace
if controller.class.name.starts_with?("Admin::BudgetsWizard")
:admin_budgets_wizard
else
helpers.namespace.to_sym
end
end
end

View File

@@ -1,64 +1,22 @@
class Admin::BudgetGroupsController < Admin::BaseController class Admin::BudgetGroupsController < Admin::BaseController
include Translatable include Admin::BudgetGroupsActions
include FeatureFlags
feature_flag :budgets
before_action :load_budget before_action :load_groups, only: :index
before_action :load_group, except: [:index, :new, :create]
def index def index
@groups = @budget.groups.order(:id)
end end
def new def new
@group = @budget.groups.new @group = @budget.groups.new
end end
def edit
end
def create
@group = @budget.groups.new(budget_group_params)
if @group.save
redirect_to groups_index, notice: t("admin.budget_groups.create.notice")
else
render :new
end
end
def update
if @group.update(budget_group_params)
redirect_to groups_index, notice: t("admin.budget_groups.update.notice")
else
render :edit
end
end
def destroy
if @group.headings.any?
redirect_to groups_index, alert: t("admin.budget_groups.destroy.unable_notice")
else
@group.destroy!
redirect_to groups_index, notice: t("admin.budget_groups.destroy.success_notice")
end
end
private private
def load_budget
@budget = Budget.find_by_slug_or_id! params[:budget_id]
end
def load_group
@group = @budget.groups.find_by_slug_or_id! params[:id]
end
def groups_index def groups_index
admin_budget_groups_path(@budget) admin_budget_groups_path(@budget)
end end
def budget_group_params def new_action
valid_attributes = [:max_votable_headings] :new
params.require(:budget_group).permit(*valid_attributes, translation_params(Budget::Group))
end end
end end

View File

@@ -1,69 +1,20 @@
class Admin::BudgetHeadingsController < Admin::BaseController class Admin::BudgetHeadingsController < Admin::BaseController
include Translatable include Admin::BudgetHeadingsActions
include FeatureFlags
feature_flag :budgets
before_action :load_budget
before_action :load_group
before_action :load_heading, except: [:index, :new, :create]
def index def index
@headings = @group.headings.order(:id)
end end
def new def new
@heading = @group.headings.new @heading = @group.headings.new
end end
def edit
end
def create
@heading = @group.headings.new(budget_heading_params)
if @heading.save
redirect_to headings_index, notice: t("admin.budget_headings.create.notice")
else
render :new
end
end
def update
if @heading.update(budget_heading_params)
redirect_to headings_index, notice: t("admin.budget_headings.update.notice")
else
render :edit
end
end
def destroy
if @heading.can_be_deleted?
@heading.destroy!
redirect_to headings_index, notice: t("admin.budget_headings.destroy.success_notice")
else
redirect_to headings_index, alert: t("admin.budget_headings.destroy.unable_notice")
end
end
private private
def load_budget
@budget = Budget.find_by_slug_or_id! params[:budget_id]
end
def load_group
@group = @budget.groups.find_by_slug_or_id! params[:group_id]
end
def load_heading
@heading = @group.headings.find_by_slug_or_id! params[:id]
end
def headings_index def headings_index
admin_budget_group_headings_path(@budget, @group) admin_budget_group_headings_path(@budget, @group)
end end
def budget_heading_params def new_action
valid_attributes = [:price, :population, :allow_custom_content, :latitude, :longitude, :max_ballot_lines] :new
params.require(:budget_heading).permit(*valid_attributes, translation_params(Budget::Heading))
end end
end end

View File

@@ -1,28 +1,9 @@
class Admin::BudgetPhasesController < Admin::BaseController class Admin::BudgetPhasesController < Admin::BaseController
include Translatable include Admin::BudgetPhasesActions
before_action :load_phase, only: [:edit, :update]
def edit
end
def update
if @phase.update(budget_phase_params)
notice = t("flash.actions.save_changes.notice")
redirect_to edit_admin_budget_path(@phase.budget), notice: notice
else
render :edit
end
end
private private
def load_phase def phases_index
@phase = Budget::Phase.find(params[:id]) edit_admin_budget_path(@phase.budget)
end
def budget_phase_params
valid_attributes = [:starts_at, :ends_at, :enabled]
params.require(:budget_phase).permit(*valid_attributes, translation_params(Budget::Phase))
end end
end end

View File

@@ -6,8 +6,7 @@ class Admin::BudgetsController < Admin::BaseController
has_filters %w[all open finished], only: :index has_filters %w[all open finished], only: :index
before_action :load_budget, except: [:index, :new, :create] before_action :load_budget, except: [:index]
before_action :load_staff, only: [:new, :create, :edit, :update, :show]
load_and_authorize_resource load_and_authorize_resource
def index def index
@@ -18,9 +17,6 @@ class Admin::BudgetsController < Admin::BaseController
render :edit render :edit
end end
def new
end
def edit def edit
end end
@@ -47,15 +43,6 @@ class Admin::BudgetsController < Admin::BaseController
end end
end end
def create
@budget = Budget.new(budget_params.merge(published: false))
if @budget.save
redirect_to edit_admin_budget_path(@budget), notice: t("admin.budgets.create.notice")
else
render :new
end
end
def destroy def destroy
if @budget.investments.any? if @budget.investments.any?
redirect_to admin_budgets_path, alert: t("admin.budgets.destroy.unable_notice") redirect_to admin_budgets_path, alert: t("admin.budgets.destroy.unable_notice")
@@ -83,9 +70,4 @@ class Admin::BudgetsController < Admin::BaseController
def load_budget def load_budget
@budget = Budget.find_by_slug_or_id! params[:id] @budget = Budget.find_by_slug_or_id! params[:id]
end end
def load_staff
@admins = Administrator.includes(:user)
@valuators = Valuator.includes(:user).order(description: :asc).order("users.email ASC")
end
end end

View File

@@ -0,0 +1,47 @@
class Admin::BudgetsWizard::BudgetsController < Admin::BaseController
include Translatable
include FeatureFlags
feature_flag :budgets
load_and_authorize_resource
def new
end
def edit
end
def create
@budget.published = false
if @budget.save
redirect_to groups_index, notice: t("admin.budgets.create.notice")
else
render :new
end
end
def update
if @budget.update(budget_params)
redirect_to groups_index, notice: t("admin.budgets.update.notice")
else
render :edit
end
end
private
def budget_params
params.require(:budget).permit(*allowed_params)
end
def allowed_params
valid_attributes = [:currency_symbol, :voting_style, administrator_ids: [], valuator_ids: []]
valid_attributes + [translation_params(Budget)]
end
def groups_index
admin_budgets_wizard_budget_groups_path(@budget)
end
end

View File

@@ -0,0 +1,19 @@
class Admin::BudgetsWizard::GroupsController < Admin::BaseController
include Admin::BudgetGroupsActions
before_action :load_groups, only: [:index, :create]
def index
@group = @budget.groups.new
end
private
def groups_index
admin_budgets_wizard_budget_groups_path(@budget)
end
def new_action
:index
end
end

View File

@@ -0,0 +1,19 @@
class Admin::BudgetsWizard::HeadingsController < Admin::BaseController
include Admin::BudgetHeadingsActions
before_action :load_headings, only: [:index, :create]
def index
@heading = @group.headings.new
end
private
def headings_index
admin_budgets_wizard_budget_group_headings_path(@budget, @group)
end
def new_action
:index
end
end

View File

@@ -0,0 +1,26 @@
class Admin::BudgetsWizard::PhasesController < Admin::BaseController
include Admin::BudgetPhasesActions
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)
end
def phases_params
params.require(:budget).permit(allowed_phases_params)
end
def allowed_phases_params
{ phases_attributes: [:id, :enabled] }
end
end

View File

@@ -0,0 +1,60 @@
module Admin::BudgetGroupsActions
extend ActiveSupport::Concern
included do
include Translatable
include FeatureFlags
feature_flag :budgets
before_action :load_budget
before_action :load_group, only: [:edit, :update, :destroy]
end
def edit
end
def create
@group = @budget.groups.new(budget_group_params)
if @group.save
redirect_to groups_index, notice: t("admin.budget_groups.create.notice")
else
render new_action
end
end
def update
if @group.update(budget_group_params)
redirect_to groups_index, notice: t("admin.budget_groups.update.notice")
else
render :edit
end
end
def destroy
if @group.headings.any?
redirect_to groups_index, alert: t("admin.budget_groups.destroy.unable_notice")
else
@group.destroy!
redirect_to groups_index, notice: t("admin.budget_groups.destroy.success_notice")
end
end
private
def load_budget
@budget = Budget.find_by_slug_or_id! params[:budget_id]
end
def load_groups
@groups = @budget.groups.order(:id)
end
def load_group
@group = @budget.groups.find_by_slug_or_id! params[:id]
end
def budget_group_params
valid_attributes = [:max_votable_headings]
params.require(:budget_group).permit(*valid_attributes, translation_params(Budget::Group))
end
end

View File

@@ -0,0 +1,66 @@
module Admin::BudgetHeadingsActions
extend ActiveSupport::Concern
included do
include Translatable
include FeatureFlags
feature_flag :budgets
before_action :load_budget
before_action :load_group
before_action :load_headings, only: :index
before_action :load_heading, only: [:edit, :update, :destroy]
end
def edit
end
def create
@heading = @group.headings.new(budget_heading_params)
if @heading.save
redirect_to headings_index, notice: t("admin.budget_headings.create.notice")
else
render new_action
end
end
def update
if @heading.update(budget_heading_params)
redirect_to headings_index, notice: t("admin.budget_headings.update.notice")
else
render :edit
end
end
def destroy
if @heading.can_be_deleted?
@heading.destroy!
redirect_to headings_index, notice: t("admin.budget_headings.destroy.success_notice")
else
redirect_to headings_index, alert: t("admin.budget_headings.destroy.unable_notice")
end
end
private
def load_budget
@budget = Budget.find_by_slug_or_id! params[:budget_id]
end
def load_group
@group = @budget.groups.find_by_slug_or_id! params[:group_id]
end
def load_headings
@headings = @group.headings.order(:id)
end
def load_heading
@heading = @group.headings.find_by_slug_or_id! params[:id]
end
def budget_heading_params
valid_attributes = [:price, :population, :allow_custom_content, :latitude, :longitude, :max_ballot_lines]
params.require(:budget_heading).permit(*valid_attributes, translation_params(Budget::Heading))
end
end

View File

@@ -0,0 +1,36 @@
module Admin::BudgetPhasesActions
extend ActiveSupport::Concern
included do
include Translatable
before_action :load_budget
before_action :load_phase, only: [:edit, :update]
end
def edit
end
def update
if @phase.update(budget_phase_params)
redirect_to phases_index, notice: t("flash.actions.save_changes.notice")
else
render :edit
end
end
private
def load_budget
@budget = Budget.find_by_slug_or_id!(params[:budget_id])
end
def load_phase
@phase = @budget.phases.find(params[:id])
end
def budget_phase_params
valid_attributes = [:starts_at, :ends_at, :enabled]
params.require(:budget_phase).permit(*valid_attributes, translation_params(Budget::Phase))
end
end

View File

@@ -1,10 +1,4 @@
module BudgetsHelper module BudgetsHelper
def budget_voting_styles_select_options
Budget::VOTING_STYLES.map do |style|
[Budget.human_attribute_name("voting_style_#{style}"), style]
end
end
def csv_params def csv_params
csv_params = params.clone.merge(format: :csv) csv_params = params.clone.merge(format: :csv)
csv_params = csv_params.to_unsafe_h.map { |k, v| [k.to_sym, v] }.to_h csv_params = csv_params.to_unsafe_h.map { |k, v| [k.to_sym, v] }.to_h
@@ -12,14 +6,6 @@ module BudgetsHelper
csv_params csv_params
end end
def budget_phases_select_options
Budget::Phase::PHASE_KINDS.map { |ph| [t("budgets.phase.#{ph}"), ph] }
end
def budget_currency_symbol_select_options
Budget::CURRENCY_SYMBOLS.map { |cs| [cs, cs] }
end
def namespaced_budget_investment_path(investment, options = {}) def namespaced_budget_investment_path(investment, options = {})
case namespace case namespace
when "management" when "management"

View File

@@ -42,6 +42,7 @@ class Budget < ApplicationRecord
has_one :poll has_one :poll
after_create :generate_phases after_create :generate_phases
accepts_nested_attributes_for :phases
scope :published, -> { where(published: true) } scope :published, -> { where(published: true) }
scope :drafting, -> { where.not(id: published) } scope :drafting, -> { where.not(id: published) }

View File

@@ -1,30 +1,23 @@
<%= render "shared/globalize_locales", resource: @group %> <%= translatable_form_for group, url: path, html: { class: "budget-groups-form" } do |f| %>
<%= render "shared/globalize_locales", resource: group %>
<%= translatable_form_for [:admin, @budget, @group], url: path do |f| %> <%= render "shared/errors", resource: group %>
<%= render "shared/errors", resource: @group %> <%= f.translatable_fields do |translations_form| %>
<div class="small-12 medium-6 column end">
<div class="row"> <%= translations_form.text_field :name, maxlength: 50 %>
<%= f.translatable_fields do |translations_form| %>
<div class="small-12 medium-6 column end">
<%= translations_form.text_field :name, maxlength: 50 %>
</div>
<% end %>
</div>
<% if @group.persisted? %>
<div class="row">
<div class="small-12 medium-6 column">
<%= f.select :max_votable_headings,
(1..@group.headings.count),
hint: t("admin.budget_groups.form.max_votable_headings_info") %>
</div>
</div> </div>
<% end %> <% end %>
<div class="row"> <% if group.persisted? && group.headings.any? %>
<div class="small-12 medium-6 column"> <div class="small-12 medium-6 column margin">
<%= f.submit t("admin.budget_groups.form.#{action}"), class: "button success" %> <%= f.select :max_votable_headings,
(1..group.headings.count),
hint: t("admin.budget_groups.form.max_votable_headings_info") %>
</div> </div>
<% end %>
<div class="clear">
<%= f.submit t("admin.budget_groups.form.#{action}"), class: "button hollow" %>
</div> </div>
<% end %> <% end %>

View File

@@ -1,3 +1,3 @@
<%= render "header", action: "edit" %> <%= render "header", action: "edit" %>
<%= render "form", path: admin_budget_group_path(@budget, @group), action: "submit" %> <%= render "form", group: @group, path: admin_budget_group_path(@budget, @group), action: "submit" %>

View File

@@ -8,37 +8,4 @@
</header> </header>
<%= render Admin::Budgets::HelpComponent.new("budget_groups") %> <%= render Admin::Budgets::HelpComponent.new("budget_groups") %>
<%= render Admin::BudgetGroups::GroupsComponent.new(@groups) %>
<% if @groups.any? %>
<h3><%= t("admin.budget_groups.amount", count: @groups.count) %></h3>
<table>
<thead>
<tr id="<%= dom_id(@budget) %>">
<th><%= t("admin.budget_groups.name") %></th>
<th><%= Budget::Group.human_attribute_name(:max_votable_headings) %></th>
<th><%= t("admin.budget_groups.headings_name") %></th>
<th><%= t("admin.actions.actions") %></th>
</tr>
</thead>
<tbody>
<% @groups.each do |group| %>
<tr id="<%= dom_id(group) %>">
<td><%= group.name %></td>
<td><%= group.max_votable_headings %></td>
<td><%= group.headings.count %></td>
<td>
<%= render Admin::TableActionsComponent.new(group) do |actions| %>
<%= actions.link_to t("admin.budget_groups.headings_manage"),
admin_budget_group_headings_path(@budget, group),
class: "headings-link" %>
<% end %>
</td>
</tr>
<% end %>
</tbody>
</table>
<% else %>
<div class="callout primary clear">
<%= t("admin.budget_groups.no_groups") %>
</div>
<% end %>

View File

@@ -1,3 +1,3 @@
<%= render "header", action: "create" %> <%= render "header", action: "create" %>
<%= render "form", path: admin_budget_groups_path(@budget), action: "create" %> <%= render "form", group: @group, path: admin_budget_groups_path(@budget), action: "create" %>

View File

@@ -1,44 +1,40 @@
<%= render "shared/globalize_locales", resource: @heading %> <%= translatable_form_for heading, url: path, html: { class: "budget-headings-form" } do |f| %>
<%= render "shared/globalize_locales", resource: heading %>
<%= translatable_form_for [:admin, @budget, @group, @heading], url: path do |f| %> <%= render "shared/errors", resource: heading %>
<%= render "shared/errors", resource: @heading %> <%= f.translatable_fields do |translations_form| %>
<div class="small-12 medium-6 column end">
<%= translations_form.text_field :name, maxlength: 50 %>
</div>
<% end %>
<div class="row"> <div class="small-12 medium-6">
<%= f.translatable_fields do |translations_form| %> <%= f.text_field :price, maxlength: 8 %>
<div class="small-12 medium-6 column end">
<%= translations_form.text_field :name, maxlength: 50 %> <% if heading.budget.approval_voting? %>
</div> <%= f.number_field :max_ballot_lines,
hint: t("admin.budget_headings.form.max_ballot_lines_info") %>
<% end %> <% end %>
<%= f.text_field :population,
maxlength: 8,
data: { toggle_focus: "population-info" },
hint: t("admin.budget_headings.form.population_info") %>
<%= f.text_field :latitude, maxlength: 22 %>
<%= f.text_field :longitude, maxlength: 22 %>
<p class="help-text" id="budgets-coordinates-help-text">
<%= t("admin.budget_headings.form.coordinates_info") %>
</p>
<%= f.check_box :allow_custom_content %>
<p class="help-text" id="budgets-content-blocks-help-text">
<%= t("admin.budget_headings.form.content_blocks_info") %>
</p>
</div> </div>
<div class="row"> <div class="clear">
<div class="small-12 medium-6 column"> <%= f.submit t("admin.budget_headings.form.#{action}"), class: "button hollow" %>
<%= f.text_field :price, maxlength: 8 %>
<% if @heading.budget.approval_voting? %>
<%= f.number_field :max_ballot_lines,
hint: t("admin.budget_headings.form.max_ballot_lines_info") %>
<% end %>
<%= f.text_field :population,
maxlength: 8,
data: { toggle_focus: "population-info" },
hint: t("admin.budget_headings.form.population_info") %>
<%= f.text_field :latitude, maxlength: 22 %>
<%= f.text_field :longitude, maxlength: 22 %>
<p class="help-text" id="budgets-coordinates-help-text">
<%= t("admin.budget_headings.form.coordinates_info") %>
</p>
<%= f.check_box :allow_custom_content %>
<p class="help-text" id="budgets-content-blocks-help-text">
<%= t("admin.budget_headings.form.content_blocks_info") %>
</p>
<%= f.submit t("admin.budget_headings.form.#{action}"), class: "button success" %>
</div>
</div> </div>
<% end %> <% end %>

View File

@@ -1,3 +1,3 @@
<%= render "header", action: "edit" %> <%= render "header", action: "edit" %>
<%= render "form", path: admin_budget_group_heading_path(@budget, @group, @heading), action: "submit" %> <%= render "form", heading: @heading, path: admin_budget_group_heading_path(@budget, @group, @heading), action: "submit" %>

View File

@@ -1,48 +1,9 @@
<%= back_link_to admin_budget_groups_path(@budget), t("admin.budget_headings.index.back") %> <%= back_link_to admin_budget_groups_path(@budget), t("admin.budget_headings.index.back") %>
<header> <header>
<h2><%= "#{@budget.name} / #{@group.name}" %></h2> <h2><%= t("admin.budget_headings.index.title", budget: @budget.name, group: @group.name) %></h2>
<%= link_to t("admin.budget_headings.form.create"), new_admin_budget_group_heading_path %> <%= link_to t("admin.budget_headings.form.create"), new_admin_budget_group_heading_path %>
</header> </header>
<%= render Admin::Budgets::HelpComponent.new("budget_headings") %> <%= render Admin::Budgets::HelpComponent.new("budget_headings") %>
<%= render Admin::BudgetHeadings::HeadingsComponent.new(@headings) %>
<% if @headings.any? %>
<h3><%= t("admin.budget_headings.amount", count: @headings.count) %></h3>
<table>
<thead>
<tr id="<%= dom_id(@group) %>">
<th><%= Budget::Heading.human_attribute_name(:name) %></th>
<th><%= Budget::Heading.human_attribute_name(:price) %></th>
<% if @budget.approval_voting? %>
<th><%= Budget::Heading.human_attribute_name(:max_ballot_lines) %></th>
<% end %>
<th><%= Budget::Heading.human_attribute_name(:population) %></th>
<th><%= Budget::Heading.human_attribute_name(:allow_custom_content) %></th>
<th><%= t("admin.actions.actions") %></th>
</tr>
</thead>
<tbody>
<% @headings.each do |heading| %>
<tr id="<%= dom_id(heading) %>" class="heading">
<td><%= heading.name %></td>
<td><%= @budget.formatted_heading_price(heading) %></td>
<% if @budget.approval_voting? %>
<td><%= heading.max_ballot_lines %></td>
<% end %>
<td><%= heading.population %></td>
<td>
<%= heading.allow_custom_content ? t("admin.shared.true_value") : t("admin.shared.false_value") %>
</td>
<td>
<%= render Admin::TableActionsComponent.new(heading) %>
</td>
</tr>
<% end %>
</tbody>
</table>
<% else %>
<div class="callout primary clear">
<%= t("admin.budget_headings.no_headings") %>
</div>
<% end %>

View File

@@ -1,3 +1,3 @@
<%= render "header", action: "create" %> <%= render "header", action: "create" %>
<%= render "form", path: admin_budget_group_headings_path(@budget, @group), action: "create" %> <%= render "form", heading: @heading, path: admin_budget_group_headings_path(@budget, @group), action: "create" %>

View File

@@ -1,49 +1 @@
<%= render "shared/globalize_locales", resource: @phase %> <%= render Admin::BudgetPhases::FormComponent.new(@phase) %>
<%= translatable_form_for [:admin, @phase.budget, @phase], html: { class: "budget-phases-form" } do |f| %>
<%= render "shared/errors", resource: @phase %>
<fieldset>
<legend aria-describedby="phase_duration_description">
<%= t("admin.budget_phases.edit.duration") %>
</legend>
<p class="help-text" id="phase_duration_description">
<%= t("admin.budget_phases.edit.duration_description") %>
</p>
<div class="date-field">
<%= f.date_field :starts_at, id: "start_date" %>
</div>
<div class="date-field">
<%= f.date_field :ends_at, id: "end_date" %>
</div>
</fieldset>
<div class="small-12 column margin">
<%= f.check_box :enabled %>
<span class="help-text" id="phase-summary-help-text">
<%= t("admin.budget_phases.edit.enabled_help_text") %>
</span>
</div>
<%= f.translatable_fields do |translations_form| %>
<div class="small-12 column">
<%= translations_form.text_field :name, hint: t("admin.budget_phases.edit.name_help_text") %>
</div>
<div class="small-12 column">
<%= translations_form.text_area :description,
maxlength: Budget::Phase::DESCRIPTION_MAX_LENGTH,
class: "html-area",
hint: t("admin.budget_phases.edit.description_help_text") %>
</div>
<% end %>
<div class="small-12 column">
<%= f.submit t("admin.budget_phases.edit.save_changes"), class: "button success" %>
</div>
<% end %>

View File

@@ -1,5 +1 @@
<%= back_link_to edit_admin_budget_path(@phase.budget) %> <%= render Admin::BudgetsWizard::Phases::EditComponent.new(@phase) %>
<h2><%= t("admin.budgets.edit.title") %> - <%= @phase.name %></h2>
<%= render "/admin/budget_phases/form" %>

View File

@@ -5,4 +5,4 @@
<%= render Admin::Budgets::DraftingComponent.new(@budget) %> <%= render Admin::Budgets::DraftingComponent.new(@budget) %>
</header> </header>
<%= render "/admin/budgets/form" %> <%= render Admin::Budgets::FormComponent.new(@budget) %>

View File

@@ -1,7 +0,0 @@
<%= back_link_to admin_budgets_path %>
<header>
<h2><%= t("admin.budgets.new.title") %></h2>
</header>
<%= render "/admin/budgets/form" %>

View File

@@ -0,0 +1 @@
<%= render Admin::BudgetsWizard::Budgets::EditComponent.new(@budget) %>

View File

@@ -0,0 +1 @@
<%= render Admin::BudgetsWizard::Budgets::NewComponent.new(@budget) %>

View File

@@ -0,0 +1 @@
<%= render Admin::BudgetsWizard::Groups::EditComponent.new(@group) %>

View File

@@ -0,0 +1 @@
<%= render Admin::BudgetsWizard::Groups::IndexComponent.new(@groups, @group) %>

View File

@@ -0,0 +1 @@
<%= render Admin::BudgetsWizard::Headings::EditComponent.new(@heading) %>

View File

@@ -0,0 +1 @@
<%= render Admin::BudgetsWizard::Headings::IndexComponent.new(@headings, @heading) %>

View File

@@ -0,0 +1 @@
<%= render Admin::BudgetsWizard::Phases::EditComponent.new(@phase) %>

View File

@@ -0,0 +1 @@
<%= render Admin::BudgetsWizard::Phases::IndexComponent.new(@budget) %>

View File

@@ -147,6 +147,9 @@ ignore_unused:
- "admin.hidden_proposal_notifications.index.filter*" - "admin.hidden_proposal_notifications.index.filter*"
- "admin.budgets.index.filter*" - "admin.budgets.index.filter*"
- "admin.budgets.edit.(administrators|valuators).*" - "admin.budgets.edit.(administrators|valuators).*"
- "admin.budget_groups.index.*.help_block"
- "admin.budget_headings.index.*.help_block"
- "admin.budget_phases.index.help_block"
- "admin.budget_investments.index.filter*" - "admin.budget_investments.index.filter*"
- "admin.organizations.index.filter*" - "admin.organizations.index.filter*"
- "admin.hidden_users.index.filter*" - "admin.hidden_users.index.filter*"

View File

@@ -220,7 +220,7 @@ en:
latitude: "Latitude (optional)" latitude: "Latitude (optional)"
longitude: "Longitude (optional)" longitude: "Longitude (optional)"
name: "Heading name" name: "Heading name"
price: "Amount" price: "Money amount"
population: "Population (optional)" population: "Population (optional)"
max_ballot_lines: "Votes allowed" max_ballot_lines: "Votes allowed"
budget/heading/translation: budget/heading/translation:

View File

@@ -14,6 +14,7 @@ en:
edit: Edit edit: Edit
configure: Configure configure: Configure
delete: Delete delete: Delete
confirm_delete: "Are you sure? This action will delete %{resource_name} '%{name}' and can't be undone."
officing_booth: 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." 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: banners:
@@ -106,6 +107,7 @@ en:
enabled: Enabled enabled: Enabled
actions: Actions actions: Actions
edit_phase: Edit phase edit_phase: Edit phase
enable_phase: "Enable %{phase} phase"
active: Active active: Active
blank_dates: Dates are blank blank_dates: Dates are blank
administrators: administrators:
@@ -133,6 +135,8 @@ en:
calculate: Calculate Winner Investments calculate: Calculate Winner Investments
calculated: Winners being calculated, it may take a minute. calculated: Winners being calculated, it may take a minute.
recalculate: Recalculate Winner Investments recalculate: Recalculate Winner Investments
shared:
resource_name: "the budget"
budget_groups: budget_groups:
name: "Name" name: "Name"
headings_name: "Headings" headings_name: "Headings"
@@ -156,6 +160,7 @@ en:
index: index:
back: "Go back to budgets" back: "Go back to budgets"
help: "Groups are meant to organize headings. After a group is created and it contais headings, it's possible to determine in how many headings a user can vote per group." help: "Groups are meant to organize headings. After a group is created and it contais headings, it's possible to determine in how many headings a user can vote per group."
new_button: "Add new group"
budget_headings: budget_headings:
no_headings: "There are no headings." no_headings: "There are no headings."
amount: amount:
@@ -176,11 +181,18 @@ en:
create: "Create new heading" create: "Create new heading"
edit: "Edit heading" edit: "Edit heading"
submit: "Save heading" submit: "Save heading"
group_switcher:
currently_showing: "Showing headings from the %{group} group."
different_group: "Manage headings from a different group"
the_other_group: "Manage headings from the %{group} group."
index: index:
back: "Go back to groups" back: "Go back to groups"
help: "Headings are meant to divide the money of the participatory budget. Here you can add headings for this group and assign the amount of money that will be used for each heading." help: "Headings are meant to divide the money of the participatory budget. Here you can add headings for this group and assign the amount of money that will be used for each heading."
new_button: "Add new heading"
title: "%{budget} / %{group} headings"
budget_phases: budget_phases:
edit: edit:
title: "Edit phase"
description_help_text: This text will appear in the header when the phase is active description_help_text: This text will appear in the header when the phase is active
duration: "Phase's duration" duration: "Phase's duration"
duration_description: "The period of time this phase will be active." duration_description: "The period of time this phase will be active."
@@ -189,6 +201,7 @@ en:
save_changes: Save changes save_changes: Save changes
index: index:
help: "Participatory budgets have different phases. Here you can enable or disable phases and also customize each individual phase." help: "Participatory budgets have different phases. Here you can enable or disable phases and also customize each individual phase."
title: "%{budget} phases"
budget_investments: budget_investments:
index: index:
heading_filter_all: All headings heading_filter_all: All headings
@@ -284,6 +297,24 @@ en:
tags_placeholder: "Write the tags you want separated by commas (,)" tags_placeholder: "Write the tags you want separated by commas (,)"
undefined: Undefined undefined: Undefined
search_unfeasible: Search unfeasible search_unfeasible: Search unfeasible
budgets_wizard:
creation_timeline:
budget: Budget
groups: Groups
headings: Headings
phases: Phases
budgets:
continue: "Continue to groups"
groups:
back: "Go back to edit budget"
continue: "Continue to headings"
headings:
continue: "Continue to phases"
phases:
back: "Go back to headings"
continue: "Finish"
update_all:
notice: "Phases configured successfully"
milestones: milestones:
index: index:
table_id: "ID" table_id: "ID"

View File

@@ -220,7 +220,7 @@ es:
latitude: "Latitud (opcional)" latitude: "Latitud (opcional)"
longitude: "Longitud (opcional)" longitude: "Longitud (opcional)"
name: "Nombre de la partida" name: "Nombre de la partida"
price: "Cantidad" price: "Cantidad de dinero"
population: "Población (opcional)" population: "Población (opcional)"
max_ballot_lines: "Votos permitidos" max_ballot_lines: "Votos permitidos"
budget/heading/translation: budget/heading/translation:

View File

@@ -13,6 +13,7 @@ es:
unmark_featured: Quitar destacado unmark_featured: Quitar destacado
edit: Editar edit: Editar
configure: Configurar configure: Configurar
confirm_delete: "¿Estás seguro? Esta acción borrará %{resource_name} '%{name}' y no se puede deshacer."
delete: Borrar delete: Borrar
officing_booth: 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." 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."
@@ -106,6 +107,7 @@ es:
enabled: Habilitada enabled: Habilitada
actions: Acciones actions: Acciones
edit_phase: Editar fase edit_phase: Editar fase
enable_phase: "Habilitar fase de %{phase}"
active: Activa active: Activa
blank_dates: Sin fechas blank_dates: Sin fechas
administrators: administrators:
@@ -133,6 +135,8 @@ es:
calculate: Calcular proyectos ganadores calculate: Calcular proyectos ganadores
calculated: Calculando ganadores, puede tardar un minuto. calculated: Calculando ganadores, puede tardar un minuto.
recalculate: Recalcular proyectos ganadores recalculate: Recalcular proyectos ganadores
shared:
resource_name: "el presupuesto"
budget_groups: budget_groups:
name: "Nombre" name: "Nombre"
headings_name: "Partidas" headings_name: "Partidas"
@@ -156,6 +160,7 @@ es:
index: index:
back: "Volver a presupuestos" back: "Volver a presupuestos"
help: "Los grupos sirven para organizar las partidas del presupuesto. Después de que un grupo sea creado y éste contenga partidas, es posible determinar el número de partidas a las que un usuario puede votar por grupo." help: "Los grupos sirven para organizar las partidas del presupuesto. Después de que un grupo sea creado y éste contenga partidas, es posible determinar el número de partidas a las que un usuario puede votar por grupo."
new_button: "Añadir un grupo nuevo"
budget_headings: budget_headings:
no_headings: "No hay partidas." no_headings: "No hay partidas."
amount: amount:
@@ -176,9 +181,15 @@ es:
create: "Crear nueva partida" create: "Crear nueva partida"
edit: "Editar partida" edit: "Editar partida"
submit: "Guardar partida" submit: "Guardar partida"
group_switcher:
currently_showing: "Mostrando las partidas del grupo: %{group}"
different_group: "Ir a partidas de otro grupo"
the_other_group: "Ir a partidas del grupo %{group}."
index: index:
back: "Volver a grupos" back: "Volver a grupos"
help: "Las partidas sirven para dividir el dinero del presupuesto participativo. Aquí puedes ir añadiendo partidas para cada grupo y establecer la cantidad de dinero que se gastará en cada partida." help: "Las partidas sirven para dividir el dinero del presupuesto participativo. Aquí puedes ir añadiendo partidas para cada grupo y establecer la cantidad de dinero que se gastará en cada partida."
new_button: "Añadir una partida nueva"
title: "Partidas de %{budget} / %{group}"
budget_phases: budget_phases:
edit: edit:
description_help_text: Este texto aparecerá en la cabecera cuando la fase esté activa description_help_text: Este texto aparecerá en la cabecera cuando la fase esté activa
@@ -187,8 +198,10 @@ es:
enabled_help_text: Esta fase será pública en el calendario de fases del presupuesto y estará activa para otros propósitos enabled_help_text: Esta fase será pública en el calendario de fases del presupuesto y estará activa para otros propósitos
name_help_text: "Este es el título de la fase que los usuarios leerán en el encabezado cuando la fase esté activa." name_help_text: "Este es el título de la fase que los usuarios leerán en el encabezado cuando la fase esté activa."
save_changes: Guardar cambios save_changes: Guardar cambios
title: "Editar fase"
index: index:
help: "Los presupuestos participativos tienen distintas fases. Aquí puedes habilitar o deshabilitar fases y también personalizar cada una de las fases." help: "Los presupuestos participativos tienen distintas fases. Aquí puedes habilitar o deshabilitar fases y también personalizar cada una de las fases."
title: "Fases de %{budget}"
budget_investments: budget_investments:
index: index:
heading_filter_all: Todas las partidas heading_filter_all: Todas las partidas
@@ -284,6 +297,24 @@ es:
tags_placeholder: "Escribe las etiquetas que desees separadas por comas (,)" tags_placeholder: "Escribe las etiquetas que desees separadas por comas (,)"
undefined: Sin definir undefined: Sin definir
search_unfeasible: Buscar inviables search_unfeasible: Buscar inviables
budgets_wizard:
creation_timeline:
budget: Presupuesto
groups: Grupos
headings: Partidas
phases: Fases
budgets:
continue: "Continuar a grupos"
groups:
back: "Volver a editar presupuesto"
continue: "Continuar a partidas"
headings:
continue: "Continuar a fases"
phases:
back: "Volver a partidas"
continue: "Finalizar"
update_all:
notice: "Fases configuradas con éxito"
milestones: milestones:
index: index:
table_id: "ID" table_id: "ID"

View File

@@ -51,7 +51,7 @@ namespace :admin do
end end
end end
resources :budgets do resources :budgets, except: [:create, :new] do
member do member do
patch :publish patch :publish
put :calculate_winners put :calculate_winners
@@ -72,6 +72,18 @@ namespace :admin do
resources :budget_phases, only: [:edit, :update] resources :budget_phases, only: [:edit, :update]
end end
namespace :budgets_wizard do
resources :budgets, only: [:create, :new, :edit, :update] do
resources :groups, only: [:index, :create, :edit, :update, :destroy] do
resources :headings, only: [:index, :create, :edit, :update, :destroy]
end
resources :phases, as: "budget_phases", only: [:index, :edit, :update] do
collection { patch :update_all }
end
end
end
resources :milestone_statuses, only: [:index, :new, :create, :update, :edit, :destroy] resources :milestone_statuses, only: [:index, :new, :create, :update, :edit, :destroy]
resources :signature_sheets, only: [:index, :new, :create, :show] resources :signature_sheets, only: [:index, :new, :create, :show]

View File

@@ -0,0 +1,16 @@
require "rails_helper"
describe Admin::Budgets::FormComponent, type: :component do
describe "#voting_styles_select_options" do
it "provides vote kinds" do
types = [
["Knapsack", "knapsack"],
["Approval", "approval"]
]
component = Admin::Budgets::FormComponent.new(double)
expect(component.voting_styles_select_options).to eq(types)
end
end
end

View File

@@ -8,15 +8,16 @@ describe Admin::Budgets::TableActionsComponent, type: :component do
allow(ViewComponent::Base).to receive(:test_controller).and_return("Admin::BaseController") allow(ViewComponent::Base).to receive(:test_controller).and_return("Admin::BaseController")
end end
it "renders links to edit budget, manage investments and edit groups and manage ballots" do it "renders links to edit and delete budget, manage investments and edit groups and manage ballots" do
render_inline component render_inline component
expect(page).to have_css "a", count: 5 expect(page).to have_css "a", count: 6
expect(page).to have_link "Manage projects", href: /investments/ expect(page).to have_link "Manage projects", href: /investments/
expect(page).to have_link "Edit headings groups", href: /groups/ expect(page).to have_link "Edit headings groups", href: /groups/
expect(page).to have_link "Edit budget", href: /edit/ expect(page).to have_link "Edit budget", href: /edit/
expect(page).to have_link "Admin ballots" expect(page).to have_link "Admin ballots"
expect(page).to have_link "Preview budget", href: /budgets/ expect(page).to have_link "Preview budget", href: /budgets/
expect(page).to have_link "Delete", href: /budgets/
end end
it "renders link to create new poll for budgets without polls" do it "renders link to create new poll for budgets without polls" do

View File

@@ -0,0 +1,41 @@
require "rails_helper"
describe Admin::BudgetsWizard::Headings::GroupSwitcherComponent, type: :component do
it "is not rendered for budgets with one group" do
group = create(:budget_group, budget: create(:budget))
render_inline Admin::BudgetsWizard::Headings::GroupSwitcherComponent.new(group)
expect(page.text).to be_empty
expect(page).not_to have_css ".budget-group-switcher"
end
it "renders a link to switch group for budgets with two groups" do
budget = create(:budget)
group = create(:budget_group, budget: budget, name: "Parks")
create(:budget_group, budget: budget, name: "Recreation")
render_inline Admin::BudgetsWizard::Headings::GroupSwitcherComponent.new(group)
expect(page).to have_content "Showing headings from the Parks group"
expect(page).to have_link "Manage headings from the Recreation group."
expect(page).not_to have_css "ul"
end
it "renders a menu to switch group for budgets with more than two groups" do
budget = create(:budget)
group = create(:budget_group, budget: budget, name: "Parks")
create(:budget_group, budget: budget, name: "Recreation")
create(:budget_group, budget: budget, name: "Entertainment")
render_inline Admin::BudgetsWizard::Headings::GroupSwitcherComponent.new(group)
expect(page).to have_content "Showing headings from the Parks group"
expect(page).to have_button "Manage headings from a different group"
within "button + ul" do
expect(page).to have_link "Recreation"
expect(page).to have_link "Entertainment"
end
end
end

View File

@@ -1,14 +0,0 @@
require "rails_helper"
describe BudgetsHelper do
describe "#budget_voting_styles_select_options" do
it "provides vote kinds" do
types = [
["Knapsack", "knapsack"],
["Approval", "approval"]
]
expect(budget_voting_styles_select_options).to eq(types)
end
end
end

View File

@@ -129,6 +129,25 @@ describe "Admin budget groups", :admin do
expect(page).to have_field "Maximum number of headings in which a user can select projects", with: "2" expect(page).to have_field "Maximum number of headings in which a user can select projects", with: "2"
end end
describe "Select for maxium number of headings to select projects" do
scenario "is present if there are headings in the group" do
group = create(:budget_group, budget: budget)
create(:budget_heading, group: group)
visit edit_admin_budget_group_path(budget, group)
expect(page).to have_field "Maximum number of headings in which a user can select projects"
end
scenario "is not present if there are no headings in the group" do
group = create(:budget_group, budget: budget)
visit edit_admin_budget_group_path(budget, group)
expect(page).not_to have_field "Maximum number of headings in which a user can select projects"
end
end
scenario "Changing name for current locale will update the slug if budget is in draft phase" do scenario "Changing name for current locale will update the slug if budget is in draft phase" do
group = create(:budget_group, budget: budget, name: "Old English Name") group = create(:budget_group, budget: budget, name: "Old English Name")

View File

@@ -88,7 +88,7 @@ describe "Admin budget headings", :admin do
click_link "Create new heading" click_link "Create new heading"
fill_in "Heading name", with: "All City" fill_in "Heading name", with: "All City"
fill_in "Amount", with: "1000" fill_in "Money amount", with: "1000"
fill_in "Population (optional)", with: "10000" fill_in "Population (optional)", with: "10000"
check "Allow content block" check "Allow content block"
@@ -115,7 +115,7 @@ describe "Admin budget headings", :admin do
click_button "Create new heading" click_button "Create new heading"
expect(page).not_to have_content "Heading created successfully!" expect(page).not_to have_content "Heading created successfully!"
expect(page).to have_css(".is-invalid-label", text: "Amount") expect(page).to have_css(".is-invalid-label", text: "Money amount")
expect(page).to have_content "can't be blank" expect(page).to have_content "can't be blank"
end end
@@ -134,7 +134,7 @@ describe "Admin budget headings", :admin do
expect(page).to have_field "Votes allowed", with: 1 expect(page).to have_field "Votes allowed", with: 1
fill_in "Heading name", with: "All City" fill_in "Heading name", with: "All City"
fill_in "Amount", with: "1000" fill_in "Money amount", with: "1000"
fill_in "Votes allowed", with: 14 fill_in "Votes allowed", with: 14
click_button "Create new heading" click_button "Create new heading"
@@ -152,7 +152,7 @@ describe "Admin budget headings", :admin do
within("#budget_heading_#{heading.id}") { click_link "Edit" } within("#budget_heading_#{heading.id}") { click_link "Edit" }
expect(page).to have_field "Heading name", with: heading.name expect(page).to have_field "Heading name", with: heading.name
expect(page).to have_field "Amount", with: heading.price expect(page).to have_field "Money amount", with: heading.price
expect(page).to have_field "Population (optional)", with: heading.population expect(page).to have_field "Population (optional)", with: heading.population
expect(page).to have_field "Longitude (optional)", with: heading.longitude expect(page).to have_field "Longitude (optional)", with: heading.longitude
expect(page).to have_field "Latitude (optional)", with: heading.latitude expect(page).to have_field "Latitude (optional)", with: heading.latitude
@@ -204,14 +204,14 @@ describe "Admin budget headings", :admin do
visit edit_admin_budget_group_heading_path(budget, group, heading) visit edit_admin_budget_group_heading_path(budget, group, heading)
expect(page).to have_field "Heading name", with: "All City" expect(page).to have_field "Heading name", with: "All City"
expect(page).to have_field "Amount", with: 1000 expect(page).to have_field "Money amount", with: 1000
expect(page).to have_field "Population (optional)", with: 10000 expect(page).to have_field "Population (optional)", with: 10000
expect(page).to have_field "Longitude (optional)", with: 20.50 expect(page).to have_field "Longitude (optional)", with: 20.50
expect(page).to have_field "Latitude (optional)", with: -10.50 expect(page).to have_field "Latitude (optional)", with: -10.50
expect(find_field("Allow content block")).to be_checked expect(find_field("Allow content block")).to be_checked
fill_in "Heading name", with: "Districts" fill_in "Heading name", with: "Districts"
fill_in "Amount", with: "2000" fill_in "Money amount", with: "2000"
fill_in "Population (optional)", with: "20000" fill_in "Population (optional)", with: "20000"
fill_in "Longitude (optional)", with: "-40.47" fill_in "Longitude (optional)", with: "-40.47"
fill_in "Latitude (optional)", with: "25.25" fill_in "Latitude (optional)", with: "25.25"
@@ -222,7 +222,7 @@ describe "Admin budget headings", :admin do
visit edit_admin_budget_group_heading_path(budget, group, heading) visit edit_admin_budget_group_heading_path(budget, group, heading)
expect(page).to have_field "Heading name", with: "Districts" expect(page).to have_field "Heading name", with: "Districts"
expect(page).to have_field "Amount", with: 2000 expect(page).to have_field "Money amount", with: 2000
expect(page).to have_field "Population (optional)", with: 20000 expect(page).to have_field "Population (optional)", with: 20000
expect(page).to have_field "Longitude (optional)", with: -40.47 expect(page).to have_field "Longitude (optional)", with: -40.47
expect(page).to have_field "Latitude (optional)", with: 25.25 expect(page).to have_field "Latitude (optional)", with: 25.25

View File

@@ -32,7 +32,7 @@ describe "Admin budget phases" do
within("tr", text: "Accepting projects") { click_link "Edit phase" } within("tr", text: "Accepting projects") { click_link "Edit phase" }
end end
expect(page).to have_css "h2", exact_text: "Edit Participatory budget - Accepting projects" expect(page).to have_css "h2", exact_text: "Edit phase - Accepting projects"
fill_in "Name", with: "My phase custom name" fill_in "Name", with: "My phase custom name"
click_button "Save changes" click_button "Save changes"

View File

@@ -82,87 +82,21 @@ describe "Admin budgets", :admin do
end end
end end
end end
end
context "New" do scenario "Delete budget from index" do
scenario "Create budget - Knapsack voting (default)" do create(:budget, name: "To be deleted")
visit admin_budgets_path
click_link "Create new budget"
fill_in "Name", with: "M30 - Summer campaign"
select "Accepting projects", from: "budget[phase]"
click_button "Create Budget"
expect(page).to have_content "New participatory budget created successfully!"
expect(page).to have_field "Name", with: "M30 - Summer campaign"
expect(page).to have_select "Final voting style", selected: "Knapsack"
end
scenario "Create budget - Approval voting" do
admin = Administrator.first
visit admin_budgets_path visit admin_budgets_path
click_link "Create new budget"
fill_in "Name", with: "M30 - Summer campaign" within "tr", text: "To be deleted" do
select "Accepting projects", from: "budget[phase]" message = "Are you sure? This action will delete the budget 'To be deleted' and can't be undone."
select "Approval", from: "Final voting style"
click_button "Create Budget"
expect(page).to have_content "New participatory budget created successfully!" accept_confirm(message) { click_link "Delete" }
expect(page).to have_field "Name", with: "M30 - Summer campaign" end
expect(page).to have_select "Final voting style", selected: "Approval"
click_link "Select administrators" expect(page).to have_content("Budget deleted successfully")
expect(page).to have_content("There are no budgets.")
expect(page).to have_field admin.name expect(page).not_to have_content "To be deleted"
end
scenario "Name is mandatory" do
visit new_admin_budget_path
click_button "Create Budget"
expect(page).not_to have_content "New participatory budget created successfully!"
expect(page).to have_css(".is-invalid-label", text: "Name")
end
scenario "Name should be unique" do
create(:budget, name: "Existing Name")
visit new_admin_budget_path
fill_in "Name", with: "Existing Name"
click_button "Create Budget"
expect(page).not_to have_content "New participatory budget created successfully!"
expect(page).to have_css(".is-invalid-label", text: "Name")
expect(page).to have_css("small.form-error", text: "has already been taken")
end
scenario "Do not show results and stats settings on new budget" do
visit new_admin_budget_path
expect(page).not_to have_content "Show results and stats"
expect(page).not_to have_field "Show results"
expect(page).not_to have_field "Show stats"
expect(page).not_to have_field "Show advanced stats"
end
end
context "Create" do
scenario "A new budget is always created in draft mode" do
visit admin_budgets_path
click_link "Create new budget"
fill_in "Name", with: "M30 - Summer campaign"
select "Accepting projects", from: "budget[phase]"
click_button "Create Budget"
expect(page).to have_content "New participatory budget created successfully!"
expect(page).to have_content "This participatory budget is in draft mode"
expect(page).to have_link "Preview budget"
expect(page).to have_link "Publish budget"
end end
end end
@@ -197,8 +131,7 @@ describe "Admin budgets", :admin do
let(:heading) { create(:budget_heading, budget: budget) } let(:heading) { create(:budget_heading, budget: budget) }
scenario "Destroy a budget without investments" do scenario "Destroy a budget without investments" do
visit admin_budgets_path visit edit_admin_budget_path(budget)
click_link "Edit budget"
click_link "Delete budget" click_link "Delete budget"
expect(page).to have_content("Budget deleted successfully") expect(page).to have_content("Budget deleted successfully")
@@ -209,8 +142,7 @@ describe "Admin budgets", :admin do
budget.administrators << Administrator.first budget.administrators << Administrator.first
budget.valuators << create(:valuator) budget.valuators << create(:valuator)
visit admin_budgets_path visit edit_admin_budget_path(budget)
click_link "Edit budget"
click_link "Delete budget" click_link "Delete budget"
expect(page).to have_content "Budget deleted successfully" expect(page).to have_content "Budget deleted successfully"
@@ -220,8 +152,7 @@ describe "Admin budgets", :admin do
scenario "Try to destroy a budget with investments" do scenario "Try to destroy a budget with investments" do
create(:budget_investment, heading: heading) create(:budget_investment, heading: heading)
visit admin_budgets_path visit edit_admin_budget_path(budget)
click_link "Edit budget"
click_link "Delete budget" click_link "Delete budget"
expect(page).to have_content("You cannot delete a budget that has associated investments") expect(page).to have_content("You cannot delete a budget that has associated investments")

View File

@@ -0,0 +1,132 @@
require "rails_helper"
describe "Budgets wizard, first step", :admin do
describe "New" do
scenario "Create budget - Knapsack voting (default)" do
visit admin_budgets_path
click_link "Create new budget"
fill_in "Name", with: "M30 - Summer campaign"
click_button "Continue to groups"
expect(page).to have_content "New participatory budget created successfully!"
click_link "Go back to edit budget"
expect(page).to have_field "Name", with: "M30 - Summer campaign"
expect(page).to have_select "Final voting style", selected: "Knapsack"
end
scenario "Create budget - Approval voting" do
admin = Administrator.first
visit admin_budgets_path
click_link "Create new budget"
fill_in "Name", with: "M30 - Summer campaign"
select "Approval", from: "Final voting style"
click_button "Continue to groups"
expect(page).to have_content "New participatory budget created successfully!"
click_link "Go back to edit budget"
expect(page).to have_field "Name", with: "M30 - Summer campaign"
expect(page).to have_select "Final voting style", selected: "Approval"
click_link "Select administrators"
expect(page).to have_field admin.name
end
scenario "Submit the form with errors" do
visit new_admin_budgets_wizard_budget_path
click_button "Continue to groups"
expect(page).not_to have_content "New participatory budget created successfully!"
expect(page).to have_css ".is-invalid-label", text: "Name"
expect(page).to have_css ".creation-timeline"
end
scenario "Name should be unique" do
create(:budget, name: "Existing Name")
visit new_admin_budgets_wizard_budget_path
fill_in "Name", with: "Existing Name"
click_button "Continue to groups"
expect(page).not_to have_content "New participatory budget created successfully!"
expect(page).to have_css(".is-invalid-label", text: "Name")
expect(page).to have_css("small.form-error", text: "has already been taken")
end
scenario "Do not show results and stats settings on new budget" do
visit new_admin_budgets_wizard_budget_path
expect(page).not_to have_content "Show results and stats"
expect(page).not_to have_field "Show results"
expect(page).not_to have_field "Show stats"
expect(page).not_to have_field "Show advanced stats"
end
end
describe "Create" do
scenario "A new budget is always created in draft mode" do
visit admin_budgets_path
click_link "Create new budget"
fill_in "Name", with: "M30 - Summer campaign"
click_button "Continue to groups"
expect(page).to have_content "New participatory budget created successfully!"
within("#side_menu") { click_link "Participatory budgets" }
within("tr", text: "M30 - Summer campaign") { click_link "Edit budget" }
expect(page).to have_content "This participatory budget is in draft mode"
expect(page).to have_link "Preview budget"
expect(page).to have_link "Publish budget"
end
end
describe "Edit" do
scenario "update budget" do
budget = create(:budget, name: "Budget wiht typo")
visit admin_budgets_wizard_budget_groups_path(budget)
click_link "Go back to edit budget"
expect(page).to have_content "Edit Participatory budget"
expect(page).to have_css ".creation-timeline"
expect(page).to have_field "Name", with: "Budget wiht typo"
fill_in "Name", with: "Budget without typos"
click_button "Continue to groups"
expect(page).to have_content "Participatory budget updated successfully"
expect(page).to have_content "Budget without typos"
expect(page).to have_css ".creation-timeline"
expect(page).to have_content "There are no groups"
end
scenario "submit the form with errors and then without errors" do
budget = create(:budget, name: "Budget wiht typo")
visit edit_admin_budgets_wizard_budget_path(budget)
fill_in "Name", with: ""
click_button "Continue to groups"
expect(page).to have_css "#error_explanation"
fill_in "Name", with: "Budget without typos"
click_button "Continue to groups"
expect(page).to have_content "Participatory budget updated successfully"
expect(page).to have_content "Budget without typos"
expect(page).to have_css ".creation-timeline"
expect(page).to have_content "There are no groups"
end
end
end

Some files were not shown because too many files have changed in this diff Show More