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

View File

@@ -258,6 +258,11 @@ $table-header: #ecf1f6;
[type="submit"] ~ a {
margin-left: $line-height / 2;
}
[type="checkbox"] {
margin-bottom: 0;
vertical-align: middle;
}
}
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 {
@include full-width-form;
> fieldset {
border-top: 4px solid $admin-border-color;
@@ -17,8 +18,4 @@
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;
&:focus,
@@ -7,16 +7,20 @@
}
}
%button {
@include base-button;
}
@mixin regular-button($color: $brand) {
@include button($background: $color);
@include inverted-selection;
@extend %button;
@include base-button;
}
@mixin hollow-button($color: $link) {
@include button($style: hollow, $background: $color);
@include normal-selection;
@extend %button;
@include base-button;
margin-bottom: 0;
}

View File

@@ -21,3 +21,9 @@
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 %>
</td>
<td class="text-center">
<%= enabled_text(phase) %>
<%= enabled_cell(phase) %>
</td>
<td>
<%= render Admin::TableActionsComponent.new(phase,

View File

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

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>
<legend><%= t("admin.budgets.edit.info.budget_settings") %></legend>
<%= render "shared/globalize_locales", resource: @budget %>
<%= render "shared/errors", resource: @budget %>
<%= render "shared/globalize_locales", resource: budget %>
<%= render "shared/errors", resource: budget %>
<%= f.translatable_fields do |translations_form| %>
<div class="small-12 medium-9 large-6 column end">
@@ -13,11 +13,11 @@
<% end %>
<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 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>
</fieldset>
@@ -26,52 +26,55 @@
<% %w[administrators valuators].each do |staff| %>
<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",
data: { toggle: "#{staff}_list", texts: t("admin.budgets.edit.#{staff}") } %>
</div>
<% end %>
<%= 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: "administrators", assignables: admins, form: f %>
<%= render "/admin/budgets/association", assignable_type: "valuators", assignables: valuators, form: f %>
</fieldset>
<% unless wizard? %>
<fieldset>
<legend><%= t("admin.budgets.edit.info.phases_settings") %></legend>
<div class="small-12 medium-6 column">
<%= f.select :phase, budget_phases_select_options %>
<%= 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>
<% if @budget.persisted? %>
<%= render "admin/shared/show_results_fields", form: f %>
<% end %>
<div class="small-12 column">
<div class="clear small-12 medium-4 large-3 inline-block">
<% if wizard? %>
<%= f.submit t("admin.budgets_wizard.budgets.continue"), class: "button success expanded" %>
<% else %>
<%= f.submit nil, class: "button success" %>
<% end %>
</div>
<div class="float-right">
<% if display_calculate_winners_button?(@budget) %>
<%= link_to calculate_winner_button_text(@budget),
calculate_winners_admin_budget_path(@budget),
<% if display_calculate_winners_button?(budget) %>
<%= link_to calculate_winner_button_text(budget),
calculate_winners_admin_budget_path(budget),
method: :put,
class: "button hollow" %>
<% end %>
<% if @budget.has_winning_investments? %>
<% if budget.has_winning_investments? %>
<%= link_to t("budgets.show.see_results"),
budget_results_path(@budget),
budget_results_path(budget),
class: "button hollow margin-left" %>
<% end %>
<% if @budget.persisted? %>
<% if budget.persisted? %>
<%= link_to t("admin.budgets.edit.delete"),
admin_budget_path(@budget),
admin_budget_path(budget),
method: :delete,
class: "delete float-right margin-left" %>
<% 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 %>
<%= 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 %>
<%= 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"),
admin_budget_budget_investments_path(budget_id: budget.id),
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
def budgets?
controller_name.starts_with?("budget")
controller_name.starts_with?("budget") || controller_path =~ /budgets_wizard/
end
def polls?

View File

@@ -1,7 +1,7 @@
class Admin::TableActionsComponent < ApplicationComponent
include TableActionLink
include Admin::Namespace
attr_reader :record, :options
delegate :namespace, to: :helpers
def initialize(record = nil, **options)
@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
include Translatable
include FeatureFlags
feature_flag :budgets
include Admin::BudgetGroupsActions
before_action :load_budget
before_action :load_group, except: [:index, :new, :create]
before_action :load_groups, only: :index
def index
@groups = @budget.groups.order(:id)
end
def new
@group = @budget.groups.new
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
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
admin_budget_groups_path(@budget)
end
def budget_group_params
valid_attributes = [:max_votable_headings]
params.require(:budget_group).permit(*valid_attributes, translation_params(Budget::Group))
def new_action
:new
end
end

View File

@@ -1,69 +1,20 @@
class Admin::BudgetHeadingsController < Admin::BaseController
include Translatable
include FeatureFlags
feature_flag :budgets
before_action :load_budget
before_action :load_group
before_action :load_heading, except: [:index, :new, :create]
include Admin::BudgetHeadingsActions
def index
@headings = @group.headings.order(:id)
end
def new
@heading = @group.headings.new
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
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
admin_budget_group_headings_path(@budget, @group)
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))
def new_action
:new
end
end

View File

@@ -1,28 +1,9 @@
class Admin::BudgetPhasesController < Admin::BaseController
include Translatable
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
include Admin::BudgetPhasesActions
private
def load_phase
@phase = Budget::Phase.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))
def phases_index
edit_admin_budget_path(@phase.budget)
end
end

View File

@@ -6,8 +6,7 @@ class Admin::BudgetsController < Admin::BaseController
has_filters %w[all open finished], only: :index
before_action :load_budget, except: [:index, :new, :create]
before_action :load_staff, only: [:new, :create, :edit, :update, :show]
before_action :load_budget, except: [:index]
load_and_authorize_resource
def index
@@ -18,9 +17,6 @@ class Admin::BudgetsController < Admin::BaseController
render :edit
end
def new
end
def edit
end
@@ -47,15 +43,6 @@ class Admin::BudgetsController < Admin::BaseController
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
if @budget.investments.any?
redirect_to admin_budgets_path, alert: t("admin.budgets.destroy.unable_notice")
@@ -83,9 +70,4 @@ class Admin::BudgetsController < Admin::BaseController
def load_budget
@budget = Budget.find_by_slug_or_id! params[:id]
end
def load_staff
@admins = Administrator.includes(:user)
@valuators = Valuator.includes(:user).order(description: :asc).order("users.email ASC")
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
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
csv_params = params.clone.merge(format: :csv)
csv_params = csv_params.to_unsafe_h.map { |k, v| [k.to_sym, v] }.to_h
@@ -12,14 +6,6 @@ module BudgetsHelper
csv_params
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 = {})
case namespace
when "management"

View File

@@ -42,6 +42,7 @@ class Budget < ApplicationRecord
has_one :poll
after_create :generate_phases
accepts_nested_attributes_for :phases
scope :published, -> { where(published: true) }
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 %>
<div class="row">
<%= 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">
<% if group.persisted? && group.headings.any? %>
<div class="small-12 medium-6 column margin">
<%= f.select :max_votable_headings,
(1..@group.headings.count),
(1..group.headings.count),
hint: t("admin.budget_groups.form.max_votable_headings_info") %>
</div>
</div>
<% end %>
<div class="row">
<div class="small-12 medium-6 column">
<%= f.submit t("admin.budget_groups.form.#{action}"), class: "button success" %>
</div>
<div class="clear">
<%= f.submit t("admin.budget_groups.form.#{action}"), class: "button hollow" %>
</div>
<% end %>

View File

@@ -1,3 +1,3 @@
<%= 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>
<%= render Admin::Budgets::HelpComponent.new("budget_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 %>
<%= render Admin::BudgetGroups::GroupsComponent.new(@groups) %>

View File

@@ -1,3 +1,3 @@
<%= 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,25 +1,20 @@
<%= 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 %>
<div class="row">
<%= f.translatable_fields do |translations_form| %>
<div class="small-12 medium-6 column end">
<%= translations_form.text_field :name, maxlength: 50 %>
</div>
<% end %>
</div>
<div class="row">
<div class="small-12 medium-6 column">
<div class="small-12 medium-6">
<%= f.text_field :price, maxlength: 8 %>
<% if @heading.budget.approval_voting? %>
<% 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,
@@ -37,8 +32,9 @@
<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 class="clear">
<%= f.submit t("admin.budget_headings.form.#{action}"), class: "button hollow" %>
</div>
<% end %>

View File

@@ -1,3 +1,3 @@
<%= 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") %>
<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 %>
</header>
<%= render Admin::Budgets::HelpComponent.new("budget_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 %>
<%= render Admin::BudgetHeadings::HeadingsComponent.new(@headings) %>

View File

@@ -1,3 +1,3 @@
<%= 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 %>
<%= 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 %>
<%= render Admin::BudgetPhases::FormComponent.new(@phase) %>

View File

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

View File

@@ -5,4 +5,4 @@
<%= render Admin::Budgets::DraftingComponent.new(@budget) %>
</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.budgets.index.filter*"
- "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.organizations.index.filter*"
- "admin.hidden_users.index.filter*"

View File

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

View File

@@ -14,6 +14,7 @@ en:
edit: Edit
configure: Configure
delete: Delete
confirm_delete: "Are you sure? This action will delete %{resource_name} '%{name}' and can't be undone."
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:
@@ -106,6 +107,7 @@ en:
enabled: Enabled
actions: Actions
edit_phase: Edit phase
enable_phase: "Enable %{phase} phase"
active: Active
blank_dates: Dates are blank
administrators:
@@ -133,6 +135,8 @@ en:
calculate: Calculate Winner Investments
calculated: Winners being calculated, it may take a minute.
recalculate: Recalculate Winner Investments
shared:
resource_name: "the budget"
budget_groups:
name: "Name"
headings_name: "Headings"
@@ -156,6 +160,7 @@ en:
index:
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."
new_button: "Add new group"
budget_headings:
no_headings: "There are no headings."
amount:
@@ -176,11 +181,18 @@ en:
create: "Create new heading"
edit: "Edit 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:
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."
new_button: "Add new heading"
title: "%{budget} / %{group} headings"
budget_phases:
edit:
title: "Edit phase"
description_help_text: This text will appear in the header when the phase is active
duration: "Phase's duration"
duration_description: "The period of time this phase will be active."
@@ -189,6 +201,7 @@ en:
save_changes: Save changes
index:
help: "Participatory budgets have different phases. Here you can enable or disable phases and also customize each individual phase."
title: "%{budget} phases"
budget_investments:
index:
heading_filter_all: All headings
@@ -284,6 +297,24 @@ en:
tags_placeholder: "Write the tags you want separated by commas (,)"
undefined: Undefined
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:
index:
table_id: "ID"

View File

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

View File

@@ -13,6 +13,7 @@ es:
unmark_featured: Quitar destacado
edit: Editar
configure: Configurar
confirm_delete: "¿Estás seguro? Esta acción borrará %{resource_name} '%{name}' y no se puede deshacer."
delete: Borrar
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."
@@ -106,6 +107,7 @@ es:
enabled: Habilitada
actions: Acciones
edit_phase: Editar fase
enable_phase: "Habilitar fase de %{phase}"
active: Activa
blank_dates: Sin fechas
administrators:
@@ -133,6 +135,8 @@ es:
calculate: Calcular proyectos ganadores
calculated: Calculando ganadores, puede tardar un minuto.
recalculate: Recalcular proyectos ganadores
shared:
resource_name: "el presupuesto"
budget_groups:
name: "Nombre"
headings_name: "Partidas"
@@ -156,6 +160,7 @@ es:
index:
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."
new_button: "Añadir un grupo nuevo"
budget_headings:
no_headings: "No hay partidas."
amount:
@@ -176,9 +181,15 @@ es:
create: "Crear nueva partida"
edit: "Editar 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:
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."
new_button: "Añadir una partida nueva"
title: "Partidas de %{budget} / %{group}"
budget_phases:
edit:
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
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
title: "Editar fase"
index:
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:
index:
heading_filter_all: Todas las partidas
@@ -284,6 +297,24 @@ es:
tags_placeholder: "Escribe las etiquetas que desees separadas por comas (,)"
undefined: Sin definir
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:
index:
table_id: "ID"

View File

@@ -51,7 +51,7 @@ namespace :admin do
end
end
resources :budgets do
resources :budgets, except: [:create, :new] do
member do
patch :publish
put :calculate_winners
@@ -72,6 +72,18 @@ namespace :admin do
resources :budget_phases, only: [:edit, :update]
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 :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")
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
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 "Edit headings groups", href: /groups/
expect(page).to have_link "Edit budget", href: /edit/
expect(page).to have_link "Admin ballots"
expect(page).to have_link "Preview budget", href: /budgets/
expect(page).to have_link "Delete", href: /budgets/
end
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"
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
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"
fill_in "Heading name", with: "All City"
fill_in "Amount", with: "1000"
fill_in "Money amount", with: "1000"
fill_in "Population (optional)", with: "10000"
check "Allow content block"
@@ -115,7 +115,7 @@ describe "Admin budget headings", :admin do
click_button "Create new heading"
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"
end
@@ -134,7 +134,7 @@ describe "Admin budget headings", :admin do
expect(page).to have_field "Votes allowed", with: 1
fill_in "Heading name", with: "All City"
fill_in "Amount", with: "1000"
fill_in "Money amount", with: "1000"
fill_in "Votes allowed", with: 14
click_button "Create new heading"
@@ -152,7 +152,7 @@ describe "Admin budget headings", :admin do
within("#budget_heading_#{heading.id}") { click_link "Edit" }
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 "Longitude (optional)", with: heading.longitude
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)
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 "Longitude (optional)", with: 20.50
expect(page).to have_field "Latitude (optional)", with: -10.50
expect(find_field("Allow content block")).to be_checked
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 "Longitude (optional)", with: "-40.47"
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)
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 "Longitude (optional)", with: -40.47
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" }
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"
click_button "Save changes"

View File

@@ -82,87 +82,21 @@ describe "Admin budgets", :admin do
end
end
end
end
context "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"
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
scenario "Delete budget from index" 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]"
select "Approval", from: "Final voting style"
click_button "Create Budget"
within "tr", text: "To be deleted" do
message = "Are you sure? This action will delete the budget 'To be deleted' and can't be undone."
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: "Approval"
click_link "Select administrators"
expect(page).to have_field admin.name
accept_confirm(message) { click_link "Delete" }
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"
expect(page).to have_content("Budget deleted successfully")
expect(page).to have_content("There are no budgets.")
expect(page).not_to have_content "To be deleted"
end
end
@@ -197,8 +131,7 @@ describe "Admin budgets", :admin do
let(:heading) { create(:budget_heading, budget: budget) }
scenario "Destroy a budget without investments" do
visit admin_budgets_path
click_link "Edit budget"
visit edit_admin_budget_path(budget)
click_link "Delete budget"
expect(page).to have_content("Budget deleted successfully")
@@ -209,8 +142,7 @@ describe "Admin budgets", :admin do
budget.administrators << Administrator.first
budget.valuators << create(:valuator)
visit admin_budgets_path
click_link "Edit budget"
visit edit_admin_budget_path(budget)
click_link "Delete budget"
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
create(:budget_investment, heading: heading)
visit admin_budgets_path
click_link "Edit budget"
visit edit_admin_budget_path(budget)
click_link "Delete budget"
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