diff --git a/decidim-accountability/app/commands/decidim/accountability/admin/create_timeline_entry.rb b/decidim-accountability/app/commands/decidim/accountability/admin/create_timeline_entry.rb index dccd44dfaa782..7def45b2d973a 100644 --- a/decidim-accountability/app/commands/decidim/accountability/admin/create_timeline_entry.rb +++ b/decidim-accountability/app/commands/decidim/accountability/admin/create_timeline_entry.rb @@ -25,13 +25,14 @@ def call private - attr_reader :timeline_entry + attr_reader :timeline_entry, :form def create_timeline_entry @timeline_entry = TimelineEntry.create!( - decidim_accountability_result_id: @form.decidim_accountability_result_id, - entry_date: @form.entry_date, - description: @form.description + decidim_accountability_result_id: form.decidim_accountability_result_id, + entry_date: form.entry_date, + title: form.title, + description: form.description ) end end diff --git a/decidim-accountability/app/commands/decidim/accountability/admin/update_timeline_entry.rb b/decidim-accountability/app/commands/decidim/accountability/admin/update_timeline_entry.rb index 57d1152385954..1feee2a24cb3c 100644 --- a/decidim-accountability/app/commands/decidim/accountability/admin/update_timeline_entry.rb +++ b/decidim-accountability/app/commands/decidim/accountability/admin/update_timeline_entry.rb @@ -34,8 +34,9 @@ def call def update_timeline_entry timeline_entry.update!( - entry_date: @form.entry_date, - description: @form.description + entry_date: form.entry_date, + title: form.title, + description: form.description ) end end diff --git a/decidim-accountability/app/forms/decidim/accountability/admin/timeline_entry_form.rb b/decidim-accountability/app/forms/decidim/accountability/admin/timeline_entry_form.rb index cea17aa1c8e58..06057aac52e2a 100644 --- a/decidim-accountability/app/forms/decidim/accountability/admin/timeline_entry_form.rb +++ b/decidim-accountability/app/forms/decidim/accountability/admin/timeline_entry_form.rb @@ -10,10 +10,11 @@ class TimelineEntryForm < Decidim::Form attribute :decidim_accountability_result_id, Integer attribute :entry_date, Decidim::Attributes::LocalizedDate + translatable_attribute :title, String translatable_attribute :description, String validates :entry_date, presence: true - validates :description, translatable_presence: true + validates :title, translatable_presence: true end end end diff --git a/decidim-accountability/app/models/decidim/accountability/timeline_entry.rb b/decidim-accountability/app/models/decidim/accountability/timeline_entry.rb index 2e7a5e0d58e18..90be57c3e430b 100644 --- a/decidim-accountability/app/models/decidim/accountability/timeline_entry.rb +++ b/decidim-accountability/app/models/decidim/accountability/timeline_entry.rb @@ -7,6 +7,7 @@ module Accountability class TimelineEntry < Accountability::ApplicationRecord include Decidim::TranslatableResource + translatable_fields :title translatable_fields :description belongs_to :result, foreign_key: "decidim_accountability_result_id", class_name: "Decidim::Accountability::Result", inverse_of: :timeline_entries end diff --git a/decidim-accountability/app/views/decidim/accountability/admin/timeline_entries/_form.html.erb b/decidim-accountability/app/views/decidim/accountability/admin/timeline_entries/_form.html.erb index a30eb155c8c66..d3b5b0472017e 100644 --- a/decidim-accountability/app/views/decidim/accountability/admin/timeline_entries/_form.html.erb +++ b/decidim-accountability/app/views/decidim/accountability/admin/timeline_entries/_form.html.erb @@ -9,7 +9,11 @@
- <%= form.translated :text_field, :description %> + <%= form.translated :text_field, :title %> +
+ +
+ <%= form.translated :editor, :description %>
diff --git a/decidim-accountability/app/views/decidim/accountability/admin/timeline_entries/index.html.erb b/decidim-accountability/app/views/decidim/accountability/admin/timeline_entries/index.html.erb index ea442c2c133ae..268e715b55486 100644 --- a/decidim-accountability/app/views/decidim/accountability/admin/timeline_entries/index.html.erb +++ b/decidim-accountability/app/views/decidim/accountability/admin/timeline_entries/index.html.erb @@ -13,7 +13,7 @@ <%= t("models.timeline_entry.fields.entry_date", scope: "decidim.accountability") %> - <%= t("models.timeline_entry.fields.description", scope: "decidim.accountability") %> + <%= t("models.timeline_entry.fields.title", scope: "decidim.accountability") %> <%= t("actions.title", scope: "decidim.accountability") %> @@ -21,7 +21,7 @@ <% timeline_entries.each do |timeline_entry| %> <%= timeline_entry.entry_date %>
- <%= translated_attribute(timeline_entry.description) %> + <%= translated_attribute(timeline_entry.title) %> <% if allowed_to? :update, :timeline_entry, timeline_entry: timeline_entry %> <%= icon_link_to "pencil", edit_result_timeline_entry_path(timeline_entry.result, timeline_entry), t("actions.edit", scope: "decidim.accountability"), class: "action-icon--edit" %> diff --git a/decidim-accountability/app/views/decidim/accountability/results/_timeline.html.erb b/decidim-accountability/app/views/decidim/accountability/results/_timeline.html.erb index 0f975caab877b..35cb582da8be2 100644 --- a/decidim-accountability/app/views/decidim/accountability/results/_timeline.html.erb +++ b/decidim-accountability/app/views/decidim/accountability/results/_timeline.html.erb @@ -13,8 +13,14 @@ <%= timeline_entry.entry_date %>

- <%= translated_attribute timeline_entry.description %> + <%= translated_attribute timeline_entry.title %>

+ + <% if translated_attribute(timeline_entry.description).present? %> +
+ <%== translated_attribute timeline_entry.description %> +
+ <% end %> diff --git a/decidim-accountability/config/locales/en.yml b/decidim-accountability/config/locales/en.yml index b3ab4e64ef22e..4c8ad030e1079 100644 --- a/decidim-accountability/config/locales/en.yml +++ b/decidim-accountability/config/locales/en.yml @@ -22,6 +22,7 @@ en: timeline_entry: description: Description entry_date: Date + title: Title models: decidim/accountability/proposal_linked_event: Proposal included in a result decidim/accountability/result_progress_updated_event: Result progress updated @@ -159,8 +160,8 @@ en: progress: Progress timeline_entry: fields: - description: Description entry_date: Date + title: Title result_m: executed: Executed view: View diff --git a/decidim-accountability/db/migrate/20220331150008_add_title_to_timeline_entries.rb b/decidim-accountability/db/migrate/20220331150008_add_title_to_timeline_entries.rb new file mode 100644 index 0000000000000..b972be37822c1 --- /dev/null +++ b/decidim-accountability/db/migrate/20220331150008_add_title_to_timeline_entries.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class AddTitleToTimelineEntries < ActiveRecord::Migration[5.2] + def change + add_column :decidim_accountability_timeline_entries, :title, :jsonb + end +end diff --git a/decidim-accountability/db/migrate/20220331150155_move_legacy_description_to_title_of_timeline_entries.rb b/decidim-accountability/db/migrate/20220331150155_move_legacy_description_to_title_of_timeline_entries.rb new file mode 100644 index 0000000000000..c7f7ed98b85f9 --- /dev/null +++ b/decidim-accountability/db/migrate/20220331150155_move_legacy_description_to_title_of_timeline_entries.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +class MoveLegacyDescriptionToTitleOfTimelineEntries < ActiveRecord::Migration[5.2] + class TimelineEntry < ApplicationRecord + self.table_name = :decidim_accountability_timeline_entries + end + + def up + TimelineEntry.find_each do |timeline_entry| + timeline_entry.update!(title: timeline_entry.description, description: nil) + end + end +end diff --git a/decidim-accountability/lib/decidim/accountability/component.rb b/decidim-accountability/lib/decidim/accountability/component.rb index 24a8d9f538bb6..9d8a5388df73c 100644 --- a/decidim-accountability/lib/decidim/accountability/component.rb +++ b/decidim-accountability/lib/decidim/accountability/component.rb @@ -155,7 +155,8 @@ rand(0..5).times do |i| child_result.timeline_entries.create!( entry_date: child_result.start_date + i.days, - description: Decidim::Faker::Localized.sentence(word_count: 2) + title: Decidim::Faker::Localized.sentence(word_count: 2), + description: Decidim::Faker::Localized.paragraph(sentence_count: 1) ) end diff --git a/decidim-accountability/lib/decidim/accountability/test/factories.rb b/decidim-accountability/lib/decidim/accountability/test/factories.rb index ae1cbeb025c2a..3afc298e19fe6 100644 --- a/decidim-accountability/lib/decidim/accountability/test/factories.rb +++ b/decidim-accountability/lib/decidim/accountability/test/factories.rb @@ -45,6 +45,7 @@ factory :timeline_entry, class: "Decidim::Accountability::TimelineEntry" do result { create(:result) } entry_date { "12/7/2017" } + title { generate_localized_title } description { generate_localized_title } end end diff --git a/decidim-accountability/lib/decidim/api/timeline_entry_type.rb b/decidim-accountability/lib/decidim/api/timeline_entry_type.rb index 9a4fde63275e0..e08bc6090318b 100644 --- a/decidim-accountability/lib/decidim/api/timeline_entry_type.rb +++ b/decidim-accountability/lib/decidim/api/timeline_entry_type.rb @@ -7,6 +7,7 @@ class TimelineEntryType < Decidim::Api::Types::BaseObject field :id, GraphQL::Types::ID, "The internal ID for this timeline entry", null: false field :entry_date, Decidim::Core::DateType, "The entry date for this timeline entry", null: true + field :title, Decidim::Core::TranslatedFieldType, "The title for this timeline entry", null: true field :description, Decidim::Core::TranslatedFieldType, "The description for this timeline entry", null: true field :created_at, Decidim::Core::DateTimeType, "When this timeline entry was created", null: true field :updated_at, Decidim::Core::DateTimeType, "When this timeline entry was updated", null: true diff --git a/decidim-accountability/spec/commands/admin/create_timeline_entry_spec.rb b/decidim-accountability/spec/commands/admin/create_timeline_entry_spec.rb index 3f2166f9f8c2c..6431a0c2fb8a3 100644 --- a/decidim-accountability/spec/commands/admin/create_timeline_entry_spec.rb +++ b/decidim-accountability/spec/commands/admin/create_timeline_entry_spec.rb @@ -12,6 +12,7 @@ module Decidim::Accountability let(:result) { create :result, component: current_component } let(:date) { "2017-8-23" } + let(:title) { "Title" } let(:description) { "description" } let(:form) do @@ -19,6 +20,7 @@ module Decidim::Accountability invalid?: invalid, decidim_accountability_result_id: result.id, entry_date: date, + title: { en: title }, description: { en: description } ) end @@ -44,6 +46,11 @@ module Decidim::Accountability expect(timeline_entry.entry_date).to eq(Date.new(2017, 8, 23)) end + it "sets the title" do + subject.call + expect(translated(timeline_entry.title)).to eq title + end + it "sets the description" do subject.call expect(translated(timeline_entry.description)).to eq description diff --git a/decidim-accountability/spec/commands/admin/update_timeline_entry_spec.rb b/decidim-accountability/spec/commands/admin/update_timeline_entry_spec.rb index 43c27687964b3..47d4f57596c78 100644 --- a/decidim-accountability/spec/commands/admin/update_timeline_entry_spec.rb +++ b/decidim-accountability/spec/commands/admin/update_timeline_entry_spec.rb @@ -14,12 +14,14 @@ module Decidim::Accountability let(:timeline_entry) { create :timeline_entry, result: result } let(:date) { "2017-9-23" } + let(:title) { "New title" } let(:description) { "new description" } let(:form) do double( invalid?: invalid, entry_date: date, + title: { en: title }, description: { en: description } ) end diff --git a/decidim-accountability/spec/forms/admin/timeline_entry_form_spec.rb b/decidim-accountability/spec/forms/admin/timeline_entry_form_spec.rb index 19fc8591e8a73..69f49d2195eab 100644 --- a/decidim-accountability/spec/forms/admin/timeline_entry_form_spec.rb +++ b/decidim-accountability/spec/forms/admin/timeline_entry_form_spec.rb @@ -18,6 +18,9 @@ module Decidim::Accountability let(:result) { create :result, component: current_component } let(:entry_date) { "12/3/2017" } + let(:title) do + Decidim::Faker::Localized.sentence(word_count: 3) + end let(:description) do Decidim::Faker::Localized.sentence(word_count: 3) end @@ -26,6 +29,7 @@ module Decidim::Accountability { decidim_accountability_result_id: result.id, entry_date: entry_date, + title_en: title[:en], description_en: description[:en] } end @@ -38,10 +42,16 @@ module Decidim::Accountability it { is_expected.not_to be_valid } end + describe "when title is missing" do + let(:title) { { en: nil } } + + it { is_expected.not_to be_valid } + end + describe "when description is missing" do let(:description) { { en: nil } } - it { is_expected.not_to be_valid } + it { is_expected.to be_valid } end end end diff --git a/decidim-accountability/spec/types/integration_schema_spec.rb b/decidim-accountability/spec/types/integration_schema_spec.rb index f8971ecee75c7..eb3bb95a8947e 100644 --- a/decidim-accountability/spec/types/integration_schema_spec.rb +++ b/decidim-accountability/spec/types/integration_schema_spec.rb @@ -50,6 +50,7 @@ "timelineEntries" => [ { "createdAt" => result.timeline_entries.first.created_at.iso8601.to_s.gsub("Z", "+00:00"), + "title" => { "translation" => result.timeline_entries.first.title[locale] }, "description" => { "translation" => result.timeline_entries.first.description[locale] }, "entryDate" => result.timeline_entries.first.entry_date.to_s, "id" => result.timeline_entries.first.id.to_s, @@ -159,6 +160,9 @@ timelineEntries { id createdAt + title { + translation(locale:"#{locale}") + } description { translation(locale:"#{locale}") } @@ -265,6 +269,9 @@ timelineEntries { id createdAt + title { + translation(locale:"#{locale}") + } description { translation(locale:"#{locale}") } diff --git a/decidim-accountability/spec/types/timeline_entry_type_spec.rb b/decidim-accountability/spec/types/timeline_entry_type_spec.rb index 8d28c62e7c6fc..b264b1fa0d76b 100644 --- a/decidim-accountability/spec/types/timeline_entry_type_spec.rb +++ b/decidim-accountability/spec/types/timeline_entry_type_spec.rb @@ -26,6 +26,14 @@ module Accountability end end + describe "title" do + let(:query) { '{ title { translation(locale:"en")}}' } + + it "returns the title field" do + expect(response["title"]["translation"]).to eq(model.title["en"]) + end + end + describe "description" do let(:query) { '{ description { translation(locale:"en")}}' } diff --git a/decidim-budgets/app/assets/javascripts/decidim/budgets/projects.js.es6 b/decidim-budgets/app/assets/javascripts/decidim/budgets/projects.js.es6 index 6098f5e4a647b..d8573edf8305a 100644 --- a/decidim-budgets/app/assets/javascripts/decidim/budgets/projects.js.es6 +++ b/decidim-budgets/app/assets/javascripts/decidim/budgets/projects.js.es6 @@ -1,4 +1,4 @@ -// = require ./progressFixed +// = require decidim/focus_mode // = require_self $(() => { @@ -6,9 +6,11 @@ $(() => { const $budgetSummaryTotal = $(".budget-summary__total"); const $budgetExceedModal = $("#budget-excess"); const $budgetSummary = $(".budget-summary__progressbox"); + const $voteButton = $(".budget-vote-button"); const totalAllocation = parseInt($budgetSummaryTotal.attr("data-total-allocation"), 10); const cancelEvent = (event) => { + $(event.currentTarget).removeClass("loading-spinner"); event.stopPropagation(); event.preventDefault(); }; @@ -23,11 +25,19 @@ $(() => { return false; } + $voteButton.on("click", "span", (event) => { + $(".budget-list__action").click(); + }); + $projects.on("click", ".budget-list__action", (event) => { const currentAllocation = parseInt($budgetSummary.attr("data-current-allocation"), 10); const $currentTarget = $(event.currentTarget); const projectAllocation = parseInt($currentTarget.attr("data-allocation"), 10); + if(!$currentTarget.attr("data-open")) { + $currentTarget.addClass("loading-spinner"); + } + if ($currentTarget.attr("disabled")) { cancelEvent(event); } else if (($currentTarget.attr("data-add") === "true") && ((currentAllocation + projectAllocation) > totalAllocation)) { diff --git a/decidim-budgets/app/assets/stylesheets/decidim/budgets/budget/_budget-list.scss b/decidim-budgets/app/assets/stylesheets/decidim/budgets/budget/_budget-list.scss index 304389298e1fe..5402fb237d831 100644 --- a/decidim-budgets/app/assets/stylesheets/decidim/budgets/budget/_budget-list.scss +++ b/decidim-budgets/app/assets/stylesheets/decidim/budgets/budget/_budget-list.scss @@ -19,6 +19,26 @@ border-bottom-right-radius: $card-border-radius; border-bottom: $border; } + + &-cell { + display: grid; + grid-template-columns: 1fr 40px; + + .budget-list__data { + grid-column: span 2; + background: none; + } + + @include breakpoint(medium){ + grid-template-columns: 1fr 40px 15em; + grid-template-rows: 1fr; + + .budget-list__data { + background: $card-secondary-bg; + grid-column: 3; + } + } + } } &__image{ @@ -54,6 +74,11 @@ padding: $card-padding; display: flex; align-items: center; + + &.flex-horizontal { + flex-direction: column; + align-items: flex-start; + } } .card__text--status{ @@ -79,10 +104,18 @@ min-width: 7rem; flex-direction: row; justify-content: flex-end; - flex-basis: 12rem; + flex-basis: 14rem; padding: 1rem $card-padding; } + .loading-spinner { + margin: 0 !important; + position: relative; + &:before { + position: absolute; + } + } + &:last-child{ margin-bottom: 0; } diff --git a/decidim-budgets/app/assets/stylesheets/decidim/budgets/budget/_budget-meter.scss b/decidim-budgets/app/assets/stylesheets/decidim/budgets/budget/_budget-meter.scss index 3ab5ae966f434..386dc55822eba 100644 --- a/decidim-budgets/app/assets/stylesheets/decidim/budgets/budget/_budget-meter.scss +++ b/decidim-budgets/app/assets/stylesheets/decidim/budgets/budget/_budget-meter.scss @@ -1,5 +1,22 @@ .budget-summary__total{ margin-bottom: .5rem; + + .mini-title { + display: block; + + &__strong { + line-height: 1rem; + } + } + + @include breakpoint(medium) { + align-content: center; + display: flex; + + .mini-title:nth-of-type(2) { + margin-left: auto; + } + } } .budget-summary__progressbox{ diff --git a/decidim-budgets/app/assets/stylesheets/decidim/budgets/budget/_budget-vote-button.scss b/decidim-budgets/app/assets/stylesheets/decidim/budgets/budget/_budget-vote-button.scss new file mode 100644 index 0000000000000..23c8fc9065362 --- /dev/null +++ b/decidim-budgets/app/assets/stylesheets/decidim/budgets/budget/_budget-vote-button.scss @@ -0,0 +1,22 @@ +.budget-vote-button.expanded { + display: flex; + text-align: left; + justify-content: space-between; + align-items: center; + + span { + font-size: 1rem; + } + + &.added { + background-color: tint($success, 80%); + color: $body-font-color; + } + + &:not(.added) { + .budget-list__action { + border-color: white; + color: white; + } + } +} diff --git a/decidim-budgets/app/assets/stylesheets/decidim/budgets/budget/_progress.scss b/decidim-budgets/app/assets/stylesheets/decidim/budgets/budget/_progress.scss index 97c4d9cd73f14..a282f6c60c497 100644 --- a/decidim-budgets/app/assets/stylesheets/decidim/budgets/budget/_progress.scss +++ b/decidim-budgets/app/assets/stylesheets/decidim/budgets/budget/_progress.scss @@ -4,6 +4,17 @@ position: relative; } +.budget-progress .progress-meter{ + &:not(&--minimum) { + background-color: tint($success, 80%); + } + + .progress-meter-text{ + color: $body-font-color; + font-size: 1rem; + } +} + .progress-meter--minimum{ background: repeating-linear-gradient(-55deg, rgba(white, .2), rgba(white, .2) 8px, rgba(black, .03) 8px, rgba(black, .03) 16px); position: absolute; @@ -16,4 +27,22 @@ text-align: right; padding-left: 1rem; padding-right: .5rem; + + @include breakpoint(smallmedium down) { + text-align: center; + padding: 0; + left: 0; + } } + +.progress-text-wrapper { + height: 100%; + width: 100%; + display: flex; + align-items: center; + padding: 1em; +} + +.progress-text { + font-size: 1em; +} \ No newline at end of file diff --git a/decidim-budgets/app/cells/decidim/budgets/budget_information_modal/show.erb b/decidim-budgets/app/cells/decidim/budgets/budget_information_modal/show.erb index 26a8e819745d4..b96ec1457a990 100644 --- a/decidim-budgets/app/cells/decidim/budgets/budget_information_modal/show.erb +++ b/decidim-budgets/app/cells/decidim/budgets/budget_information_modal/show.erb @@ -1,28 +1,10 @@ - - -
-
-

<%= decidim_sanitize(component_name) %>

- -
- - <%= content_tag(:p, more_information) if more_information %> - - <% unless current_workflow.single? %> -

- <%= link_to t(:back_to, scope: i18n_scope, component_name: component_name), budgets_path %> -

- <% end %> - -
-
- -
+<% if more_information %> + + +
+ <%= content_tag(:p, more_information) %>
-
+<% end %> diff --git a/decidim-budgets/app/cells/decidim/budgets/budget_information_modal_cell.rb b/decidim-budgets/app/cells/decidim/budgets/budget_information_modal_cell.rb index 2e44153bc4072..66342a7b0280a 100644 --- a/decidim-budgets/app/cells/decidim/budgets/budget_information_modal_cell.rb +++ b/decidim-budgets/app/cells/decidim/budgets/budget_information_modal_cell.rb @@ -7,7 +7,7 @@ class BudgetInformationModalCell < BaseCell alias budget model def more_information - translated_attribute(current_settings.more_information_modal).presence || translated_attribute(settings.more_information_modal) + translated_attribute(current_settings.more_information_modal).presence || translated_attribute(settings.more_information_modal).presence end def component_name diff --git a/decidim-budgets/app/cells/decidim/budgets/budget_list_item/show.erb b/decidim-budgets/app/cells/decidim/budgets/budget_list_item/show.erb index 8a26bf498a59e..c30c332eaf3b8 100644 --- a/decidim-budgets/app/cells/decidim/budgets/budget_list_item/show.erb +++ b/decidim-budgets/app/cells/decidim/budgets/budget_list_item/show.erb @@ -1,22 +1,38 @@ -
-
+
+
<%= link_to budget_path(budget), class: link_class do %> - <% if voted? && !voting_finished? %> - - <%= translated_attribute(title) %> - - - <%= icon "check", class: "icon--small", role: "img" %> - - <% else %> +
<%= translated_attribute(title) %> +
+ <% end %> + +
+ + <%= budget_to_currency(total_budget) %> + +
+ + <%= decidim_sanitize html_truncate(translated_attribute(description), length: 140) %> +
- <% if progress? && !voting_finished? %> - - <%= icon "bookmark", class: "icon--small", role: "img" %> - - <% end %> + <% if !voting_finished? %> +
+ <% if voted? %> + + <%= icon "check", class: "icon--small", role: "img" %> + + <% elsif progress? %> + + <%= icon "bookmark", class: "icon--small", role: "img" %> + <% end %> +
+ <% end %> + +
+ <%= link_to budget_path(budget), class: "button button--sc expanded #{button_class} mb-none" do %> + <%= button_text %> + <%= icon "chevron-right", class: "icon--small", role: "img" %> <% end %>
diff --git a/decidim-budgets/app/cells/decidim/budgets/budget_list_item_cell.rb b/decidim-budgets/app/cells/decidim/budgets/budget_list_item_cell.rb index 2716b347e418c..35e299f904d29 100644 --- a/decidim-budgets/app/cells/decidim/budgets/budget_list_item_cell.rb +++ b/decidim-budgets/app/cells/decidim/budgets/budget_list_item_cell.rb @@ -4,9 +4,14 @@ module Decidim module Budgets # This cell renders the budget item list in the budgets list class BudgetListItemCell < BaseCell + include Decidim::SanitizeHelper + include Decidim::ApplicationHelper + include Decidim::Budgets::ProjectsHelper + delegate :voting_finished?, to: :controller + delegate :highlighted, to: :current_workflow - property :title + property :title, :description, :total_budget alias budget model private @@ -17,6 +22,7 @@ def card_class list << "card--list__data-added" if voted? list << "card--list__data-progress" if progress? end + list << "budget--highlighted" if highlighted? end.join(" ") end @@ -32,9 +38,31 @@ def progress? current_user && status == :progress end + def highlighted? + highlighted.include?(budget) + end + def status @status ||= current_workflow.status(budget) end + + def button_class + "hollow" if voted? || !highlighted? + end + + def button_text + key = if current_workflow.vote_allowed?(budget) && !voted? + progress? ? :progress : :vote + else + :show + end + + t(key, scope: i18n_scope) + end + + def i18n_scope + "decidim.budgets.budgets_list" + end end end end diff --git a/decidim-budgets/app/cells/decidim/budgets/budget_m_cell.rb b/decidim-budgets/app/cells/decidim/budgets/budget_m_cell.rb index ab05907a4801f..244e1909c5dfd 100644 --- a/decidim-budgets/app/cells/decidim/budgets/budget_m_cell.rb +++ b/decidim-budgets/app/cells/decidim/budgets/budget_m_cell.rb @@ -5,7 +5,6 @@ module Budgets # This cell renders the Medium (:m) budget card # for an given instance of a Decidim::Budgets::Budget class BudgetMCell < Decidim::CardMCell - include ActiveSupport::NumberHelper include Decidim::Budgets::ProjectsHelper def statuses diff --git a/decidim-budgets/app/cells/decidim/budgets/budgets_header/show.erb b/decidim-budgets/app/cells/decidim/budgets/budgets_header/show.erb index 12fe6b4a64731..a881b59840962 100644 --- a/decidim-budgets/app/cells/decidim/budgets/budgets_header/show.erb +++ b/decidim-budgets/app/cells/decidim/budgets/budgets_header/show.erb @@ -1,7 +1,3 @@ -
-
-
- <%= decidim_sanitize(landing_page_content) %> -
-
+
+ <%= decidim_sanitize(landing_page_content) %>
diff --git a/decidim-budgets/app/cells/decidim/budgets/budgets_header_cell.rb b/decidim-budgets/app/cells/decidim/budgets/budgets_header_cell.rb index 0cabeaaa6e718..45710a6d0cc93 100644 --- a/decidim-budgets/app/cells/decidim/budgets/budgets_header_cell.rb +++ b/decidim-budgets/app/cells/decidim/budgets/budgets_header_cell.rb @@ -9,6 +9,10 @@ class BudgetsHeaderCell < BaseCell def landing_page_content translated_attribute(current_settings.landing_page_content).presence || translated_attribute(settings.landing_page_content) end + + def landing_page_instructions + translated_attribute(current_settings.landing_page_instructions).presence || translated_attribute(settings.landing_page_instructions) + end end end end diff --git a/decidim-budgets/app/cells/decidim/budgets/budgets_list/card_list.erb b/decidim-budgets/app/cells/decidim/budgets/budgets_list/card_list.erb index 8cbbe75b3a228..e22c514657368 100644 --- a/decidim-budgets/app/cells/decidim/budgets/budgets_list/card_list.erb +++ b/decidim-budgets/app/cells/decidim/budgets/budgets_list/card_list.erb @@ -1,7 +1,18 @@ -
- <% budgets.each do |budget| %> - <% next if highlighted.include?(budget) && !voting_finished? %> +<%# show highlighted budgets first %> +<% if highlighted.any? %> +
+ <% highlighted.each do |budget| %> + <%= cell("decidim/budgets/budget_list_item", budget) %> + <% end %> +
+<% end %> - <%= cell("decidim/budgets/budget_list_item", budget) %> - <% end %> -
+<% non_highlighted = (budgets - highlighted - voted) %> + +<% if non_highlighted.any? %> +
+ <% non_highlighted.each do |budget| %> + <%= cell("decidim/budgets/budget_list_item", budget) %> + <% end %> +
+<% end %> diff --git a/decidim-budgets/app/cells/decidim/budgets/budgets_list/highlighted.erb b/decidim-budgets/app/cells/decidim/budgets/budgets_list/highlighted.erb deleted file mode 100644 index e680002273126..0000000000000 --- a/decidim-budgets/app/cells/decidim/budgets/budgets_list/highlighted.erb +++ /dev/null @@ -1,11 +0,0 @@ -<% if highlighted? %> -

- <% highlighted.each do |budget| %> - <%= link_to( - t("highlighted_cta", scope: i18n_scope, - name: translated_attribute(budget.title)), - resource_locator(budget).path, - class: :button ) %> - <% end %> -

-<% end %> diff --git a/decidim-budgets/app/cells/decidim/budgets/budgets_list/show.erb b/decidim-budgets/app/cells/decidim/budgets/budgets_list/show.erb index 51d5a1ffa0054..3bfb8ff27de70 100644 --- a/decidim-budgets/app/cells/decidim/budgets/budgets_list/show.erb +++ b/decidim-budgets/app/cells/decidim/budgets/budgets_list/show.erb @@ -1,20 +1,15 @@ -
-
- <% if !voting_finished? && (highlighted? || voted?) %> -
-

- <%= t(:my_budgets, scope: i18n_scope) %> -

+<% if voted? %> +
+

+ <%= t(:my_budgets, scope: i18n_scope) %> +

- <%= render :highlighted %> - <%= render :voted %> -
- <% end %> + <%= render :voted %> +
+<% end %> -
-
- <%= render :card_list %> -
-
+
+
+ <%= render :card_list %>
diff --git a/decidim-budgets/app/cells/decidim/budgets/budgets_list/voted.erb b/decidim-budgets/app/cells/decidim/budgets/budgets_list/voted.erb index 0ca04f4acdef2..70efe6f7b0d9d 100644 --- a/decidim-budgets/app/cells/decidim/budgets/budgets_list/voted.erb +++ b/decidim-budgets/app/cells/decidim/budgets/budgets_list/voted.erb @@ -4,6 +4,12 @@ <%= t(:voted_on, scope: i18n_scope, links: budgets_link_list(voted)) %>

+
+ <% voted.each do |budget| %> + <%= cell("decidim/budgets/budget_list_item", budget) %> + <% end %> +
+ <% if finished? %>

<%= t(:finished_message, scope: i18n_scope) %> diff --git a/decidim-budgets/app/cells/decidim/budgets/project_list_item/project_data.erb b/decidim-budgets/app/cells/decidim/budgets/project_list_item/project_data.erb index 53abf5cf3d717..e6f3b53350d61 100644 --- a/decidim-budgets/app/cells/decidim/budgets/project_list_item/project_data.erb +++ b/decidim-budgets/app/cells/decidim/budgets/project_list_item/project_data.erb @@ -14,6 +14,6 @@ <%= cell("decidim/budgets/project_voted_hint", model, class: "display-block margin-top-1") if current_order_checked_out? && resource_added? %> - <%= render :project_data_vote_button if !current_order_checked_out? && voting_open? %> + <%= cell("decidim/budgets/project_vote_button", model) if !current_order_checked_out? && voting_open? %> <% end %>

diff --git a/decidim-budgets/app/cells/decidim/budgets/project_list_item/project_text.erb b/decidim-budgets/app/cells/decidim/budgets/project_list_item/project_text.erb index 6e4d5e7e61b93..bfb13f2bb9557 100644 --- a/decidim-budgets/app/cells/decidim/budgets/project_list_item/project_text.erb +++ b/decidim-budgets/app/cells/decidim/budgets/project_list_item/project_text.erb @@ -12,6 +12,12 @@ <%= cell "decidim/budgets/project_tags", model, context: { extra_classes: ["tags--project"] } %>
+ <% unless voting_finished? %> +
+ <%= budget_to_currency(model.budget_amount) %> +
+ <% end %> +
<%= cell("decidim/budgets/project_votes_count", model, diff --git a/decidim-budgets/app/cells/decidim/budgets/project_list_item_cell.rb b/decidim-budgets/app/cells/decidim/budgets/project_list_item_cell.rb index 2de429ae8de9c..222df75e40b7d 100644 --- a/decidim-budgets/app/cells/decidim/budgets/project_list_item_cell.rb +++ b/decidim-budgets/app/cells/decidim/budgets/project_list_item_cell.rb @@ -5,7 +5,6 @@ module Budgets # This cell renders a horizontal project card # for an given instance of a Project in a budget list class ProjectListItemCell < Decidim::ViewModel - include ActiveSupport::NumberHelper include Decidim::LayoutHelper include Decidim::ActionAuthorizationHelper include Decidim::Budgets::ProjectsHelper diff --git a/decidim-budgets/app/cells/decidim/budgets/project_m_cell.rb b/decidim-budgets/app/cells/decidim/budgets/project_m_cell.rb index 71e70c6550552..e0a2cede5a1e6 100644 --- a/decidim-budgets/app/cells/decidim/budgets/project_m_cell.rb +++ b/decidim-budgets/app/cells/decidim/budgets/project_m_cell.rb @@ -5,7 +5,6 @@ module Budgets # This cell renders the Medium (:m) project card # for an given instance of a Project class ProjectMCell < Decidim::CardMCell - include ActiveSupport::NumberHelper include Decidim::Budgets::ProjectsHelper private diff --git a/decidim-budgets/app/cells/decidim/budgets/project_list_item/project_data_vote_button.erb b/decidim-budgets/app/cells/decidim/budgets/project_vote_button/show.erb similarity index 84% rename from decidim-budgets/app/cells/decidim/budgets/project_list_item/project_data_vote_button.erb rename to decidim-budgets/app/cells/decidim/budgets/project_vote_button/show.erb index b2b13e701ebb9..523ac38479849 100644 --- a/decidim-budgets/app/cells/decidim/budgets/project_list_item/project_data_vote_button.erb +++ b/decidim-budgets/app/cells/decidim/budgets/project_vote_button/show.erb @@ -3,12 +3,13 @@ method: vote_button_method, remote: true, class: "button tiny budget-list__action #{vote_button_class}", + id: "project-vote-button-#{model.id}", data: { add: !resource_added?, disable: true, budget: model.budget_amount, allocation: resource_allocation, - "redirect-url": resource_path + "redirect-url": budget_projects_path(model.budget) }, disabled: vote_button_disabled?, title: vote_button_label do %> diff --git a/decidim-budgets/app/cells/decidim/budgets/project_vote_button_cell.rb b/decidim-budgets/app/cells/decidim/budgets/project_vote_button_cell.rb new file mode 100644 index 0000000000000..bd4d9d6d43c76 --- /dev/null +++ b/decidim-budgets/app/cells/decidim/budgets/project_vote_button_cell.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +module Decidim + module Budgets + # This cell renders an authorized_action button + # to vote a given instance of a Project in a budget list + class ProjectVoteButtonCell < ProjectListItemCell + end + end +end diff --git a/decidim-budgets/app/commands/decidim/budgets/add_line_item.rb b/decidim-budgets/app/commands/decidim/budgets/add_line_item.rb index ab2f512ceb520..388fc87a78f8f 100644 --- a/decidim-budgets/app/commands/decidim/budgets/add_line_item.rb +++ b/decidim-budgets/app/commands/decidim/budgets/add_line_item.rb @@ -23,7 +23,7 @@ def initialize(current_order, project, current_user) # Returns nothing. def call transaction do - return broadcast(:invalid) if voting_not_enabled? || order.checked_out? + return broadcast(:invalid) if voting_not_enabled? || order.checked_out? || exceeds_budget? add_line_item broadcast(:ok, order) @@ -44,6 +44,10 @@ def add_line_item end end + def exceeds_budget? + order.allocation_for(project) + order.total > order.available_allocation + end + def voting_not_enabled? project.component.current_settings.votes != "enabled" end diff --git a/decidim-budgets/app/controllers/decidim/budgets/application_controller.rb b/decidim-budgets/app/controllers/decidim/budgets/application_controller.rb index 9b254a38e7a51..964b5c025fe6b 100644 --- a/decidim-budgets/app/controllers/decidim/budgets/application_controller.rb +++ b/decidim-budgets/app/controllers/decidim/budgets/application_controller.rb @@ -8,6 +8,7 @@ module Budgets # Note that it inherits from `Decidim::Components::BaseController`, which # override its layout and provide all kinds of useful methods. class ApplicationController < Decidim::Components::BaseController + helper Decidim::FocusModeHelper helper_method :current_workflow, :voting_finished?, :voting_open? def current_workflow diff --git a/decidim-budgets/app/controllers/decidim/budgets/line_items_controller.rb b/decidim-budgets/app/controllers/decidim/budgets/line_items_controller.rb index 227612c0b8a4b..f81b01e2f3ed9 100644 --- a/decidim-budgets/app/controllers/decidim/budgets/line_items_controller.rb +++ b/decidim-budgets/app/controllers/decidim/budgets/line_items_controller.rb @@ -20,7 +20,7 @@ def create end on(:invalid) do - render nothing: true, status: :unprocessable_entity + format.js { render "update_budget", status: :unprocessable_entity } end end end @@ -35,7 +35,7 @@ def destroy end on(:invalid) do - render nothing: true, status: :unprocessable_entity + format.js { render "update_budget", status: :unprocessable_entity } end end end diff --git a/decidim-budgets/app/helpers/decidim/budgets/projects_helper.rb b/decidim-budgets/app/helpers/decidim/budgets/projects_helper.rb index 3aa9b0d04223a..a41affeff9aa9 100644 --- a/decidim-budgets/app/helpers/decidim/budgets/projects_helper.rb +++ b/decidim-budgets/app/helpers/decidim/budgets/projects_helper.rb @@ -4,6 +4,8 @@ module Decidim module Budgets # A helper to render order and budgets actions module ProjectsHelper + include ActiveSupport::NumberHelper + # Render a budget as a currency # # budget - A integer to represent a budget @@ -13,7 +15,7 @@ def budget_to_currency(budget) # Return a percentage of the current order budget from the total budget def current_order_budget_percent - current_order&.budget_percent.to_f.floor + current_order&.budget_percent.to_f.floor.clamp(0, 100) end # Return the minimum percentage of the current order budget from the total budget diff --git a/decidim-budgets/app/views/decidim/budgets/budgets/index.html.erb b/decidim-budgets/app/views/decidim/budgets/budgets/index.html.erb index 8f57b162f9d98..7b52505e5be94 100644 --- a/decidim-budgets/app/views/decidim/budgets/budgets/index.html.erb +++ b/decidim-budgets/app/views/decidim/budgets/budgets/index.html.erb @@ -1,5 +1,34 @@ <%= render partial: "decidim/shared/component_announcement" %> +<% start_date, end_date = %w(start_date end_date).map { |attribute| current_component.participatory_space.try(:active_step)&.try(attribute) || current_component.participatory_space.try(attribute) } %> +<% instructions = translated_attribute(current_component.current_settings.landing_page_instructions).presence || translated_attribute(current_component.settings.landing_page_instructions) %> -<%= cell("decidim/budgets/budgets_header", current_workflow) %> +
+
+ <%= cell("decidim/budgets/budgets_header", current_workflow) %> + <%= cell("decidim/budgets/budgets_list", current_workflow) %> +
-<%= cell("decidim/budgets/budgets_list", current_workflow) %> + <% if start_date.present? || instructions.present? %> +
+ <% if start_date %> +
+
+

<%= t(".date") %>

+ <%= cell("decidim/date", { start: start_date, end: end_date, extra_classes: "extra__date-container--horizontal" }) %> +
+
+ <% end %> + + <% if instructions.present? %> +
+
+

<%= t(".instructions") %>

+
+ <%== instructions %> +
+
+
+ <% end %> +
+ <% end %> +
diff --git a/decidim-budgets/app/views/decidim/budgets/line_items/update_budget.js.erb b/decidim-budgets/app/views/decidim/budgets/line_items/update_budget.js.erb index 93c41777c7f0e..f2ed14195849c 100644 --- a/decidim-budgets/app/views/decidim/budgets/line_items/update_budget.js.erb +++ b/decidim-budgets/app/views/decidim/budgets/line_items/update_budget.js.erb @@ -1,4 +1,5 @@ var $orderTotalBudget = $('#order-total-budget'); +var $orderRemainingBudget = $('#order-remaining-budget'); var $orderSelectedProjects = $('#order-selected-projects'); var $orderProgress = $('#order-progress'); var $projectItem = $('#project-<%= project.id %>-item'); @@ -6,6 +7,7 @@ var $projectBudgetButton = $('#project-<%= project.id %>-budget-button'); var $budgetConfirm = $('#budget-confirm'); morphdom($orderTotalBudget[0], '<%= j(render partial: "decidim/budgets/projects/order_total_budget").strip.html_safe %>'); +morphdom($orderRemainingBudget[0], '<%= j(render partial: "decidim/budgets/projects/order_remaining_budget").strip.html_safe %>'); morphdom($orderSelectedProjects[0], '<%= j(render partial: "decidim/budgets/projects/order_selected_projects").strip.html_safe %>'); morphdom($orderProgress[0], '<%= j(render partial: "decidim/budgets/projects/order_progress").strip.html_safe %>'); morphdom($budgetConfirm[0], '<%= j(render partial: "decidim/budgets/projects/budget_confirm").strip.html_safe %>') @@ -20,5 +22,3 @@ if ($projectItem.length > 0) { if ($projectBudgetButton.length > 0) { morphdom($projectBudgetButton[0], '<%= j(render partial: "decidim/budgets/projects/project_budget_button", locals: { project: project }).strip.html_safe %>'); } - -window.DecidimBudgets.checkProgressPosition(); diff --git a/decidim-budgets/app/views/decidim/budgets/projects/_budget_summary.html.erb b/decidim-budgets/app/views/decidim/budgets/projects/_budget_summary.html.erb index 60db9a5412ea8..adb4f20687095 100644 --- a/decidim-budgets/app/views/decidim/budgets/projects/_budget_summary.html.erb +++ b/decidim-budgets/app/views/decidim/budgets/projects/_budget_summary.html.erb @@ -1,20 +1,24 @@ -
-
-
-
-

- <%= t(".rules.title") %> -

-
    - <%= raw current_rule_explanation %> -
-
-
-
-
-
-
- <% if include_heading %> +<%= focus_mode( + title: translated_attribute(current_component.participatory_space.title), + url: Decidim::EngineRouter.main_proxy(current_component).root_path, + opener_button: false +) do %> + + <% unless current_workflow.single? %> + <% project_detail = action_name == "show" %> + <% back_path = project_detail ? budget_path(budget) : Decidim::EngineRouter.main_proxy(current_component).root_path %> + <% back_title = project_detail ? t(".back_to_projects", budget_name: translated_attribute(budget.title)) : t(".back_to_budgets", component_name: translated_attribute(current_component.name)) %> + +

+ <%= link_to back_path, class: "muted-link" do %> + <%= icon "chevron-left", class: "icon--small", role: "img", "aria-hidden": true %> + <%= back_title %> + <% end %> +

+ <% end %> + +
+
<% if current_order_checked_out? %>

<%= t(".checked_out.title") %> @@ -33,44 +37,54 @@ <%= translated_attribute(budget.title) %> <% end %>

-

- <%= raw current_rule_description %> -

<% end %> - <% end %> - <% if current_order.projects_rule? %> -
- <%= t(".total_projects") %> - - <%= current_order.maximum_projects %> - - -
- <% else %> -
- <%= t(".total_budget") %> - - <%= budget_to_currency(budget.total_budget) %> - - +
+
+
+
+ <%= raw current_rule_explanation %> +
+
+
- <% end %> - <%= render partial: "order_progress" %> + <%= cell("decidim/budgets/budget_information_modal", budget) %> + + <% if current_order.projects_rule? %> +
+ <%= t(".total_projects") %> + + <%= current_order.maximum_projects %> + + +
+ <% else %> +
+ <%= t(".total_budget") %> + + <%= budget_to_currency(budget.total_budget) %> + + -
- - <%= t(".assigned") %> - <%= render partial: "order_total_budget" %> - + + <%= t(".assigned") %> + <%= render partial: "order_total_budget" %> + + + + <%= t(".remaining") %> + <%= render partial: "order_remaining_budget" %> + +
+ <% end %> + + <%= render partial: "order_progress" %>
- <%= cell("decidim/budgets/budget_information_modal", budget) %> + <%= render partial: "order_selected_projects" %>
- <%= render partial: "order_selected_projects" %> -
- -<%= render partial: "budget_excess" %> -<%= render partial: "budget_confirm" %> + <%= render partial: "budget_excess" %> + <%= render partial: "budget_confirm" %> +<% end %> diff --git a/decidim-budgets/app/views/decidim/budgets/projects/_order_progress.html.erb b/decidim-budgets/app/views/decidim/budgets/projects/_order_progress.html.erb index 384d8f553c1c1..eff9eb1079934 100644 --- a/decidim-budgets/app/views/decidim/budgets/projects/_order_progress.html.erb +++ b/decidim-budgets/app/views/decidim/budgets/projects/_order_progress.html.erb @@ -1,10 +1,14 @@
+ <% projects_count = current_order.projects.size %> + <% selected = "#{projects_count} #{t("selected_projects", scope: "decidim.budgets.projects.order_selected_projects", count: projects_count)}" %> + <% label = current_order.minimum_projects_rule? ? selected : current_order_budget_percent.to_s + "%" %> +
-
+
- <%= current_order_budget_percent %>% + <%= label %>
<% if !current_order_checked_out? && voting_open? %> @@ -13,20 +17,4 @@ <% end %>
- -
-
-
-
-
- <%= current_order_budget_percent %>% -
-
- <% if !current_order_checked_out? && voting_open? %> - - <% end %> -
-
diff --git a/decidim-budgets/app/views/decidim/budgets/projects/_order_remaining_budget.html.erb b/decidim-budgets/app/views/decidim/budgets/projects/_order_remaining_budget.html.erb new file mode 100644 index 0000000000000..085d7c6de794f --- /dev/null +++ b/decidim-budgets/app/views/decidim/budgets/projects/_order_remaining_budget.html.erb @@ -0,0 +1,5 @@ + + <% unless current_order.projects_rule? %> + <%= budget_to_currency (current_order&.budget&.total_budget.to_f - current_order&.total_budget.to_f) %> + <% end %> + diff --git a/decidim-budgets/app/views/decidim/budgets/projects/_project_budget_button.html.erb b/decidim-budgets/app/views/decidim/budgets/projects/_project_budget_button.html.erb index 1f75846964739..204692aa33749 100644 --- a/decidim-budgets/app/views/decidim/budgets/projects/_project_budget_button.html.erb +++ b/decidim-budgets/app/views/decidim/budgets/projects/_project_budget_button.html.erb @@ -1,40 +1,15 @@ -
- <% if voted_for?(project) %> - <%= action_authorized_button_to( - "vote", - t(".added"), - budget_order_line_item_path(budget, project_id: project), - method: :delete, - remote: true, - data: { - disable: true, - budget: project.budget_amount, - "redirect-url": budget_project_path(budget, project) - }, - disabled: !can_have_order? || current_order_checked_out?, - class: "button expanded button--sc success", - "aria-label": t(".added_descriptive", resource_name: translated_attribute(project.title)) - ) %> - <% elsif current_user.present? %> - <%= action_authorized_button_to( - "vote", - t(".add"), - budget_order_line_item_path(budget, project_id: project), - method: :post, - remote: true, - data: { - disable: true, - budget: project.budget_amount, - add: true, - "redirect-url": budget_project_path(budget, project) - }, - disabled: !can_have_order? || current_order_checked_out?, - class: "button expanded button--sc", - "aria-label": t(".add_descriptive", resource_name: translated_attribute(project.title)) - ) %> - <% else %> - - <% end %> -
+
+<% else %> +
button expanded primary button--sc budget-vote-button budget-list__data"> + + <%= t(voted_for?(project) ? ".added" : ".add") %> + + + <%= cell("decidim/budgets/project_vote_button", project) %> +
+<% end %> diff --git a/decidim-budgets/app/views/decidim/budgets/projects/index.html.erb b/decidim-budgets/app/views/decidim/budgets/projects/index.html.erb index 5dedc65eb2092..dc5cfc548b3e5 100644 --- a/decidim-budgets/app/views/decidim/budgets/projects/index.html.erb +++ b/decidim-budgets/app/views/decidim/budgets/projects/index.html.erb @@ -8,7 +8,7 @@ <%= t("decidim.budgets.projects.projects_for", name: translated_attribute(budget.title)) %> <% else %> - <%= render partial: "budget_summary", locals: { include_heading: true } %> + <%= render partial: "budget_summary" %> <% end %>
diff --git a/decidim-budgets/app/views/decidim/budgets/projects/show.html.erb b/decidim-budgets/app/views/decidim/budgets/projects/show.html.erb index 0355d7df72aca..94872d1907888 100644 --- a/decidim-budgets/app/views/decidim/budgets/projects/show.html.erb +++ b/decidim-budgets/app/views/decidim/budgets/projects/show.html.erb @@ -16,7 +16,7 @@ edit_link(
<% unless voting_finished? %> - <%= render partial: "budget_summary", locals: { include_heading: false } %> + <%= render partial: "budget_summary" %> <% end %>
diff --git a/decidim-budgets/config/locales/ca.yml b/decidim-budgets/config/locales/ca.yml index d2629dfa62e25..55cc62e4939ce 100644 --- a/decidim-budgets/config/locales/ca.yml +++ b/decidim-budgets/config/locales/ca.yml @@ -103,6 +103,10 @@ ca: update: "%{user_name} ha actualitzat el projecte %{resource_name} de l'espai %{space_name}" budget: view: Veure tots els projectes de pressupost + budgets: + index: + date: Dates de votació + instructions: Com participar budget_information_modal: back_to: Tornar a %{component_name} close_modal: Tancar el modal @@ -116,6 +120,9 @@ ca: highlighted_cta: Votar a %{name} if_change_opinion: Si has canviat d'opinió, pots my_budgets: Els meus pressupostos + progress: Finalitza la votació + show: Veure projectes + vote: Vota voted_on: Has votat a %{links} limit_announcement: cant_vote: No pots votar a aquests pressupostos. Prova amb un altre pressupost . @@ -155,27 +162,31 @@ ca: budget_summary: are_you_sure: Segur que vols cancel·lar el teu vot? assigned: 'Assignat:' + back_to_budgets: Tornar a %{component_name} + back_to_projects: Tornar a %{budget_name} cancel_order: eliminar el teu vot i començar de nou checked_out: description: Ja has votat pel pressupost. Si has canviat d'idea, pots %{cancel_link}. title: Vot pels pressupostos completat minimum_projects_rule: - description: A quins projectes creus que hem de destinar el pressupost? Selecciona com a mínim %{minimum_number} projectes i vota segons les teves preferències per a definir el pressupost. - instruction: "
  • Selecciona com a mínim %{minimum_number} projectes i vota segons les teves preferències per a definir el pressupost.
  • " + description: A quins projectes creus que hem de destinar el pressupost? Selecciona com a mínim %{minimum_number} projectes i vota segons les teves preferències per a definir el pressupost. + instruction: "Selecciona com a mínim %{minimum_number} projectes i vota segons les teves preferències per a definir el pressupost." projects_rule: - description: A quins projectes creus que hem de destinar el pressupost? Selecciona entre %{minimum_number} i %{maximum_number} projectes i vota segons les teves preferències per a definir el pressupost. - instruction: "
  • Selecciona entre %{minimum_number} i %{maximum_number} projectes i vota segons les teves preferències per a definir el pressupost.
  • " + description: A quins projectes creus que hem de destinar el pressupost? Selecciona entre %{minimum_number} i %{maximum_number} projectes i vota segons les teves preferències per a definir el pressupost. + instruction: "Selecciona entre %{minimum_number} i %{maximum_number} projectes i vota segons les teves preferències per a definir el pressupost." projects_rule_maximum_only: - description: A quins projectes creus que hem de destinar el pressupost? Selecciona fins a %{maximum_number} projectes i vota segons les teves preferències per a definir el pressupost. - instruction: "
  • Selecciona fins a %{maximum_number} projectes i vota segons les teves preferències per a definir el pressupost.
  • " + description: A quins projectes creus que hem de destinar el pressupost? Selecciona fins a %{maximum_number} projectes i vota segons les teves preferències per a definir el pressupost. + instruction: "Selecciona fins a %{maximum_number} projectes i vota segons les teves preferències per a definir el pressupost." + remaining: 'Restant:' rules: title: Regles de votació title: Tu decideixes el pressupost total_budget: Pressupost total total_projects: Vots totals vote_threshold_percent_rule: - description: A quins projectes creus que hem de destinar el pressupost? Assigna com a mínim %{minimum_budget} als projectes que vulguis i vota segons les teves preferències per a definir el pressupost. - instruction: "
  • Assigna com a mínim %{minimum_budget} als projectes que vulguis i vota segons les teves preferències per a definir el pressupost.
  • " + description: A quins projectes creus que hem de destinar el pressupost? Assigna com a mínim %{minimum_budget} als projectes que vulguis i vota segons les teves preferències per a definir el pressupost. + instruction: "Assigna com a mínim %{minimum_budget} als projectes que vulguis i vota segons les teves preferències per a definir el pressupost." + vote: Vota count: projects_count: one: 1 projecte @@ -242,6 +253,7 @@ ca: budget_voting_rule_only_one: Cal activar com a mínim una norma per a la votació budget_voting_rule_required: Es requereix una norma per a la votació landing_page_content: Pàgina d'inici de pressupostos + landing_page_instructions: Instruccions per la pàgina d'inici de pressupostos more_information_modal: Finestra de "Més informació" projects_per_page: Projectes per pàgina resources_permissions_enabled: Es poden establir permisos d'accions per a cada projecte @@ -265,6 +277,7 @@ ca: comments_blocked: Comentaris bloquejats highlighted_heading: Capçalera destacada landing_page_content: Pàgina d'inici de pressupostos + landing_page_instructions: Instruccions per la pàgina d'inici de pressupostos list_heading: Títol de la llista more_information_modal: Finestra de "Més informació" show_votes: Mostra els suports diff --git a/decidim-budgets/config/locales/en.yml b/decidim-budgets/config/locales/en.yml index 3025cec149899..a721cadad6652 100644 --- a/decidim-budgets/config/locales/en.yml +++ b/decidim-budgets/config/locales/en.yml @@ -109,6 +109,10 @@ en: close_modal: Close modal continue: Continue more_information: More information + budgets: + index: + date: Voting dates + instructions: How to participate budgets_list: cancel_order: more_than_one: delete your vote on %{name} and start over @@ -117,6 +121,9 @@ en: highlighted_cta: Vote on %{name} if_change_opinion: If you've changed your mind, you can my_budgets: My budgets + progress: Finish voting + show: See projects + vote: Vote voted_on: You've voted on %{links} limit_announcement: cant_vote: You can't vote on this budget. Try on another budget. @@ -156,27 +163,30 @@ en: budget_summary: are_you_sure: Are you sure you want to cancel your vote? assigned: 'Assigned:' + back_to_budgets: Back to %{component_name} + back_to_projects: Back to %{budget_name} cancel_order: delete your vote and start over checked_out: description: You've already voted for the budget. If you've changed your mind, you can %{cancel_link}. title: Budget vote completed minimum_projects_rule: - description: What projects do you think we should allocate budget for? Select at least %{minimum_number} projects you want and vote according to your preferences to define the budget. - instruction: "
  • Select at least %{minimum_number} projects you want and vote according to your preferences to define the budget.
  • " + description: What projects do you think we should allocate budget for? Select at least %{minimum_number} projects you want and vote according to your preferences to define the budget. + instruction: "Select at least %{minimum_number} projects you want and vote according to your preferences to define the budget." projects_rule: - description: What projects do you think we should allocate budget for? Select at least %{minimum_number} and up to %{maximum_number} projects you want and vote according to your preferences to define the budget. - instruction: "
  • Select at least %{minimum_number} and up to %{maximum_number} projects you want and vote according to your preferences to define the budget.
  • " + description: What projects do you think we should allocate budget for? Select at least %{minimum_number} and up to %{maximum_number} projects you want and vote according to your preferences to define the budget. + instruction: "Select at least %{minimum_number} and up to %{maximum_number} projects you want and vote according to your preferences to define the budget." projects_rule_maximum_only: - description: What projects do you think we should allocate budget for? Select up to %{maximum_number} projects you want and vote according to your preferences to define the budget. - instruction: "
  • Select up to %{maximum_number} projects you want and vote according to your preferences to define the budget.
  • " + description: What projects do you think we should allocate budget for? Select up to %{maximum_number} projects you want and vote according to your preferences to define the budget. + instruction: "Select up to %{maximum_number} projects you want and vote according to your preferences to define the budget." + remaining: 'Remaining:' rules: title: Budget rules title: You decide the budget total_budget: Total budget total_projects: Total votes vote_threshold_percent_rule: - description: What projects do you think we should allocate budget for? Assign at least %{minimum_budget} to the projects you want and vote according to your preferences to define the budget. - instruction: "
  • Assign at least %{minimum_budget} to the projects you want and vote according to your preferences to define the budget.
  • " + description: What projects do you think we should allocate budget for? Assign at least %{minimum_budget} to the projects you want and vote according to your preferences to define the budget. + instruction: "Assign at least %{minimum_budget} to the projects you want and vote according to your preferences to define the budget." count: projects_count: one: 1 project @@ -243,6 +253,7 @@ en: budget_voting_rule_only_one: Only one voting rule must be enabled budget_voting_rule_required: One voting rule is required landing_page_content: Budgets landing page + landing_page_instructions: Budgets landing page instructions more_information_modal: More information modal projects_per_page: Projects per page resources_permissions_enabled: Actions permissions can be set for each project @@ -266,6 +277,7 @@ en: comments_blocked: Comments blocked highlighted_heading: Highlighted heading landing_page_content: Budgets landing page + landing_page_instructions: Budgets landing page instructions list_heading: List heading more_information_modal: More information modal show_votes: Show votes diff --git a/decidim-budgets/config/locales/es.yml b/decidim-budgets/config/locales/es.yml index 7a534df44266c..bd7daaf6a478a 100644 --- a/decidim-budgets/config/locales/es.yml +++ b/decidim-budgets/config/locales/es.yml @@ -103,6 +103,10 @@ es: update: "%{user_name} actualizó el proyecto %{resource_name} en el espacio %{space_name}" budget: view: Ver todos los proyectos de presupuesto + budgets: + index: + date: Fechas de votación + instructions: Cómo participar budget_information_modal: back_to: Volver a %{component_name} close_modal: Cierra el modal @@ -116,6 +120,9 @@ es: highlighted_cta: Votar en %{name} if_change_opinion: Si has cambiado de opinión, puedes my_budgets: Mis presupuestos + progress: Finaliza la votación + show: Ver proyectos + vote: Vota voted_on: Has votado en %{links} limit_announcement: cant_vote: No puedes votar en este presupuesto. Prueba con otro presupuesto. @@ -155,27 +162,31 @@ es: budget_summary: are_you_sure: '¿Estás seguro de que deseas cancelar tu voto?' assigned: 'Asignado:' + back_to_budgets: Volver a %{component_name} + back_to_projects: Volver a %{budget_name} cancel_order: eliminar tu voto y empezar de nuevo checked_out: description: Ya has votado para el presupuesto. Si has cambiado de idea, puedes %{cancel_link}. title: Voto para los presupuestos completado minimum_projects_rule: - description: '¿A qué proyectos crees que deberíamos destinar el presupuesto? Selecciona por lo menos %{minimum_number} y vota según tus preferencias para definir el presupuesto.' - instruction: "
  • Selecciona al menos %{minimum_number} proyectos que quieras y vota de acuerdo a tus preferencias para definir el presupuesto.
  • " + description: '¿A qué proyectos crees que deberíamos destinar el presupuesto? Selecciona por lo menos %{minimum_number} proyectos y vota según tus preferencias para definir el presupuesto.' + instruction: "Selecciona al menos %{minimum_number} proyectos que quieras y vota de acuerdo a tus preferencias para definir el presupuesto." projects_rule: - description: '¿A qué proyectos crees que deberíamos destinar el presupuesto? Selecciona entre %{minimum_number} y %{maximum_number} proyectos, y vota según tus preferencias para definir el presupuesto.' - instruction: "
  • Selecciona al menos %{minimum_number} y hasta %{maximum_number} proyectos que quieras y vota de acuerdo a tus preferencias para definir el presupuesto.
  • " + description: '¿A qué proyectos crees que deberíamos destinar el presupuesto? Selecciona entre %{minimum_number} y %{maximum_number} proyectos, y vota según tus preferencias para definir el presupuesto.' + instruction: "Selecciona al menos %{minimum_number} y hasta %{maximum_number} proyectos que quieras y vota de acuerdo a tus preferencias para definir el presupuesto." projects_rule_maximum_only: - description: '¿A qué proyectos crees que deberíamos destinar el presupuesto? Selecciona hasta %{maximum_number} y vota según tus preferencias para definir el presupuesto.' - instruction: "
  • Selecciona hasta %{maximum_number} proyectos que quieras y vota de acuerdo a tus preferencias para definir el presupuesto.
  • " + description: '¿A qué proyectos crees que deberíamos destinar el presupuesto? Selecciona hasta %{maximum_number} proyectos y vota según tus preferencias para definir el presupuesto.' + instruction: "Selecciona hasta %{maximum_number} proyectos que quieras y vota de acuerdo a tus preferencias para definir el presupuesto." + remaining: 'Restante:' rules: title: Reglas de presupuesto title: Tú decides el presupuesto total_budget: Presupuesto total total_projects: Total de votos vote_threshold_percent_rule: - description: '¿A qué proyectos crees que deberíamos asignar el presupuesto? Asigna por lo menos %{minimum_budget} a los proyectos que desees y vota para definir el presupuesto.' - instruction: "
  • Selecciona al menos %{minimum_budget} proyectos que quieras y vota de acuerdo a tus preferencias para definir el presupuesto.
  • " + description: '¿A qué proyectos crees que deberíamos asignar el presupuesto? Asigna por lo menos %{minimum_budget} a los proyectos que desees y vota para definir el presupuesto.' + instruction: "Selecciona al menos %{minimum_budget} proyectos que quieras y vota de acuerdo a tus preferencias para definir el presupuesto." + vote: Vota count: projects_count: one: 1 proyecto @@ -242,6 +253,7 @@ es: budget_voting_rule_only_one: Hay que activar por lo menos una norma para la votación budget_voting_rule_required: Se requiere una norma para la votación landing_page_content: Página de inicio de presupuestos + landing_page_instructions: Instrucciones para la página de inicio de presupuestos more_information_modal: Modal de "Más información" projects_per_page: Proyectos por página resources_permissions_enabled: Se pueden establecer permisos de acciones para cada proyecto @@ -265,6 +277,7 @@ es: comments_blocked: Comentarios bloqueados highlighted_heading: Encabezado destacado landing_page_content: Página de inicio de presupuestos + landing_page_instructions: Instrucciones para la página de inicio de presupuestos list_heading: Título de lista more_information_modal: Modal de "Más información" show_votes: Mostrar los votos diff --git a/decidim-budgets/lib/decidim/budgets/component.rb b/decidim-budgets/lib/decidim/budgets/component.rb index 52883293498fd..209b98b2e832a 100644 --- a/decidim-budgets/lib/decidim/budgets/component.rb +++ b/decidim-budgets/lib/decidim/budgets/component.rb @@ -94,6 +94,7 @@ settings.attribute :announcement, type: :text, translated: true, editor: true settings.attribute :landing_page_content, type: :text, translated: true, editor: true + settings.attribute :landing_page_instructions, type: :text, translated: true, editor: true settings.attribute :more_information_modal, type: :text, translated: true end @@ -104,6 +105,7 @@ settings.attribute :announcement, type: :text, translated: true, editor: true settings.attribute :landing_page_content, type: :text, translated: true, editor: true + settings.attribute :landing_page_instructions, type: :text, translated: true, editor: true settings.attribute :more_information_modal, type: :text, translated: true settings.attribute :announcement, type: :text, translated: true, editor: true end diff --git a/decidim-budgets/lib/decidim/budgets/test/factories.rb b/decidim-budgets/lib/decidim/budgets/test/factories.rb index 251c0066f9c90..419ae4e2d15c2 100644 --- a/decidim-budgets/lib/decidim/budgets/test/factories.rb +++ b/decidim-budgets/lib/decidim/budgets/test/factories.rb @@ -98,6 +98,22 @@ } end end + + trait :with_landing_page_content do + transient do + landing_page_content { Decidim::Faker::Localized.wrapped("

    ", "

    ") } + landing_page_instructions { Decidim::Faker::Localized.wrapped("

    ", "

    ") } + end + + step_settings do + { + participatory_space.active_step.id => { + landing_page_content: landing_page_content, + landing_page_instructions: landing_page_instructions + } + } + end + end end factory :budget, class: "Decidim::Budgets::Budget" do diff --git a/decidim-budgets/spec/system/explore_budgets_spec.rb b/decidim-budgets/spec/system/explore_budgets_spec.rb index b28c02250df48..2c5ba00c074a4 100644 --- a/decidim-budgets/spec/system/explore_budgets_spec.rb +++ b/decidim-budgets/spec/system/explore_budgets_spec.rb @@ -6,6 +6,29 @@ include_context "with a component" let(:manifest_name) { "budgets" } + let!(:component) do + create(:budgets_component, + :with_vote_threshold_percent, + :with_landing_page_content, + manifest: manifest, + participatory_space: participatory_process, + landing_page_content: { en: "

    Big title

    " }, + landing_page_instructions: { en: "

    Follow your instincts

    " }) + end + + let!(:current_step) do + create(:participatory_process_step, + start_date: 1.day.ago, + end_date: 3.days.from_now, + participatory_process: participatory_process + ) + end + + before do + participatory_process.steps.first.update(start_date: 1.month.ago, end_date: 1.day.ago - 1.hour, active: false) + participatory_process.steps.last.update(active: true) + end + context "with only one budget" do let!(:budgets) { create_list(:budget, 1, component: component) } @@ -17,15 +40,86 @@ end context "with many budgets" do - let!(:budgets) { create_list(:budget, 6, component: component) } + let!(:budgets) do + 1.upto(6).to_a.map { |x| create(:budget, component: component, total_budget: x * 10_000_000, description: { en: "This is budget #{x}" }) } + end - it "lists all the budgets" do + before do visit_component + end + it "shows the content" do + expect(page).to have_content("Big title") + end + + it "shows the dates" do + expect(page).to have_content("Voting dates") + + within ".extra__date-container" do + expect(page).to have_content(I18n.l(current_step.start_date, format: "%B")) + expect(page).to have_content(current_step.start_date.day) + expect(page).to have_content(I18n.l(current_step.end_date, format: "%B")) + expect(page).to have_content(current_step.end_date.day) + end + end + + it "shows the instructions" do + expect(page).to have_content("How to participate") + expect(page).to have_content("Follow your instincts") + end + + it "lists all the budgets" do expect(page).to have_selector(".card--list__item", count: 6) budgets.each do |budget| expect(page).to have_content(translated(budget.title)) + expect(page).to have_content("This is budget 1") + expect(page).to have_content("€10,000,000") + end + end + + describe "budget list item" do + let(:budget) { budgets.first } + let(:item) { page.find(".budget-list .card--list__item:first-child", match: :first) } + let!(:projects) { create_list(:project, 3, budget: budget, budget_amount: 10_000_000) } + + before do + login_as user, scope: :user + end + + it "has a clickable title" do + expect(item).to have_link(translated(budget.title), href: budget_path(budget)) + end + + context "when an item is bookmarked" do + let!(:order) { create(:order, user: user, budget: budget) } + let!(:line_item) { create(:line_item, order: order, project: projects.first) } + + it "shows the bookmark icon" do + visit_component + + expect(item).to have_selector(".budget-list__icon .icon--bookmark") + expect(item).to have_link("Finish voting", href: budget_path(budget)) + end + end + + context "when an item is voted" do + let(:item) { page.find("#voted-budgets .card--list__item:first-child") } + + let!(:order) do + order = create(:order, user: user, budget: budget) + order.projects = [projects.first] + order.checked_out_at = Time.current + order.save! + order + end + + it "shows the check icon" do + visit_component + + expect(item).to have_selector(".budget-list__icon .icon--check") + expect(item).to have_link("Show", href: budget_path(budget)) + end end end end @@ -35,4 +129,8 @@ let(:target_path) { Decidim::EngineRouter.main_proxy(component).budget_path(99_999_999) } end end + + def budget_path(budget) + Decidim::EngineRouter.main_proxy(component).budget_path(budget.id) + end end diff --git a/decidim-budgets/spec/system/explore_projects_spec.rb b/decidim-budgets/spec/system/explore_projects_spec.rb index ca97bbbd83b63..60734722f56d6 100644 --- a/decidim-budgets/spec/system/explore_projects_spec.rb +++ b/decidim-budgets/spec/system/explore_projects_spec.rb @@ -14,8 +14,13 @@ let(:categories) { create_list(:category, 3, participatory_space: component.participatory_space) } describe "index" do - it "shows all resources for the given component" do + before do visit_budget + end + + it_behaves_like "has focus mode", "TOTAL BUDGET" + + it "shows all resources for the given component" do within "#projects" do expect(page).to have_selector(".budget-list__item", count: projects_count) end diff --git a/decidim-budgets/spec/system/orders_spec.rb b/decidim-budgets/spec/system/orders_spec.rb index 13e279c3a0046..f87a5430101b1 100644 --- a/decidim-budgets/spec/system/orders_spec.rb +++ b/decidim-budgets/spec/system/orders_spec.rb @@ -21,9 +21,13 @@ context "when the user is not logged in" do let!(:projects) { create_list(:project, 1, budget: budget, budget_amount: 25_000_000) } - it "is given the option to sign in" do + before do visit_budget + end + it_behaves_like "has focus mode", "TOTAL BUDGET" + + it "is given the option to sign in" do within "#project-#{project.id}-item" do page.find(".budget-list__action").click end @@ -44,15 +48,12 @@ visit_budget end + it_behaves_like "has focus mode", "You decide the budget" + context "when voting by percentage threshold" do - it "displays description messages" do + it "displays description messages and voting rules" do within ".budget-summary" do - expect(page).to have_content("You decide the budget\nWhat projects do you think we should allocate budget for? Assign at least €70,000,000 to the projects you want and vote according to your preferences to define the budget.") - end - end - - it "displays rules" do - within ".voting-rules" do + expect(page).to have_content("You decide the budget") expect(page).to have_content("Assign at least €70,000,000 to the projects you want and vote according to your preferences to define the budget.") end end @@ -66,15 +67,23 @@ participatory_space: participatory_process) end - it "displays description messages" do + it "displays voting rules" do within ".budget-summary" do - expect(page).to have_content("What projects do you think we should allocate budget for? Select at least 3 projects you want and vote according to your preferences to define the budget.") + expect(page).to have_content("Select at least 3 projects you want and vote according to your preferences to define the budget.") end end - it "displays rules" do - within ".voting-rules" do - expect(page).to have_content("Select at least 3 projects you want and vote according to your preferences to define the budget.") + it "shows the project count in the progress bar" do + within ".progress-meter-text" do + expect(page).to have_content("0 projects selected") + end + + within "#project-#{project.id}-item" do + page.find(".budget-list__action").click + end + + within ".progress-meter-text" do + expect(page).to have_content("1 project selected") end end end @@ -88,14 +97,8 @@ participatory_space: participatory_process) end - it "displays description messages" do + it "displays voting rules" do within ".budget-summary" do - expect(page).to have_content("What projects do you think we should allocate budget for? Select up to 6 projects you want and vote according to your preferences to define the budget.") - end - end - - it "displays rules" do - within ".voting-rules" do expect(page).to have_content("Select up to 6 projects you want and vote according to your preferences to define the budget.") end end @@ -109,14 +112,8 @@ participatory_space: participatory_process) end - it "displays description messages" do + it "displays voting rules" do within ".budget-summary" do - expect(page).to have_content("What projects do you think we should allocate budget for? Select at least 3 and up to 6 projects you want and vote according to your preferences to define the budget.") - end - end - - it "displays rules" do - within ".voting-rules" do expect(page).to have_content("Select at least 3 and up to 6 projects you want and vote according to your preferences to define the budget.") end end @@ -137,6 +134,7 @@ expect(page).to have_selector ".budget-list__data--added", count: 1 expect(page).to have_content "ASSIGNED: €25,000,000" + expect(page).to have_content "REMAINING: €75,000,000" expect(page).to have_content "1 project selected" within ".budget-summary__selected" do @@ -172,6 +170,7 @@ expect(page).to have_selector ".budget-list__data--added", count: 1 expect(page).to have_content "ASSIGNED: €25,000,000" + expect(page).to have_content "REMAINING: €75,000,000" expect(page).to have_content "1 project selected" within ".budget-summary__selected" do @@ -179,7 +178,7 @@ end within "#order-progress .budget-summary__progressbox" do - expect(page).to have_content "25%" + expect(page).to have_content "1 project selected" expect(page).to have_selector("button.small:disabled") end end @@ -295,6 +294,7 @@ visit_budget expect(page).to have_content "ASSIGNED: €25,000,000" + expect(page).to have_content "REMAINING: €75,000,000" within "#project-#{project.id}-item" do page.find(".budget-list__action").click @@ -317,11 +317,13 @@ visit_budget expect(page).to have_content "ASSIGNED: €25,000,000" + expect(page).to have_content "REMAINING: €75,000,000" # Note that this is not a default alert box, this is the default browser # prompt for verifying the page unload. Therefore, `dismiss_prompt` is # used instead of `dismiss_confirm`. dismiss_prompt do + page.find(".focus-mode__close").click page.find(".logo-wrapper a").click end @@ -491,6 +493,7 @@ expect(page).to have_content("Budget vote completed") + page.find(".focus-mode__close").click page.find(".logo-wrapper a").click expect(page).to have_current_path decidim.root_path @@ -562,7 +565,7 @@ visit_budget - expect(page).to have_selector("[id^=project-]", count: 1) + expect(page).to have_selector("[id^=project-][id$=-item]", count: 1) end it "respects the projects_per_page setting when it matches total projects" do @@ -572,7 +575,7 @@ visit_budget - expect(page).to have_selector("[id^=project-]", count: 2) + expect(page).to have_selector("[id^=project-][id$=-item]", count: 2) end it "respects the projects_per_page setting when over total projects" do @@ -582,7 +585,7 @@ visit_budget - expect(page).to have_selector("[id^=project-]", count: 2) + expect(page).to have_selector("[id^=project-][id$=-item]", count: 2) end end diff --git a/decidim-core/app/assets/javascripts/decidim/focus_mode.js.es6 b/decidim-core/app/assets/javascripts/decidim/focus_mode.js.es6 new file mode 100644 index 0000000000000..400386661c03f --- /dev/null +++ b/decidim-core/app/assets/javascripts/decidim/focus_mode.js.es6 @@ -0,0 +1,124 @@ +$(() => { + const $focusModeOn = $("[data-focus-on]"); + const $focusModeOff = $("[data-focus-off]"); + const $wrapper = $(".focus-mode__body"); + const $focusContent = $("[data-focus-body]"); + const $pageContent = $("#content"); + const $closer = $("[data-focus-close]"); + const $opener = $("[data-focus-open]"); + const $flashMessagesContainer = $(".focus-mode__flash-messages"); + + const $background = $(".title-bar, [data-set='nav-holder'], .process-header"); + + const $overlay = $(".omnipresent-banner, .cookie-warning"); + const $cookieButton = $(".cookie-bar__button"); + + const FADEOUT_TIME = 200; + + const flashMessagesObserver = new MutationObserver((mutations) => { + mutations.forEach((mutation) => { + if (mutation.addedNodes !== null) { + var $nodes = $(mutation.addedNodes); + if ($nodes.filter(".flash.callout").length > 0) { + moveFlashMessages(); + } + } + }); + }); + + const watchFlashMessages = () => { + // Pass in the target node, as well as the observer options + flashMessagesObserver.observe($("#content")[0], { attributes: true, childList: true, characterData: true }); + }; + + const unwatchFlashMessages = () => { + flashMessagesObserver.disconnect(); + }; + + const moveFlashMessages = () => { + $(".flash.callout").appendTo($flashMessagesContainer); + }; + + const overlayHeight = () => { + var h = 0; + $overlay.outerHeight((i, v) => { + if ($($overlay[i]).is(":visible")) h += v; + }); + return h; + }; + + const moveToShowOverlay = () => { + const top = $(document).scrollTop(); + const height = overlayHeight(); + + if (top <= height) { + $focusModeOn.css({ top: `${height}px` }) + } else { + $focusModeOn.css({ top: "0px" }) + } + } + + const moveOverlay = () => { + if (!$overlay.length) return; + + if ($cookieButton.length) { + $cookieButton.on("click", moveToShowOverlay); + } + + moveToShowOverlay(); + window.addEventListener("scroll", moveToShowOverlay); + } + + const focusModeOn = function(fadeTime) { + if ($opener.length) $opener.fadeOut(fadeTime); + + $background.hide(fadeTime); + $pageContent.animate({ "margin-top": `${$focusModeOn.outerHeight() + overlayHeight() - 50}px` }, fadeTime); + + moveOverlay(); + moveFlashMessages(); + + $focusContent.fadeOut(fadeTime, () => { + $focusContent.detach().prependTo($wrapper); + $focusModeOn.fadeIn(fadeTime, () => { + $focusContent.fadeIn(fadeTime, () => {}); + }); + }); + + watchFlashMessages(); + } + + const focusModeOff = function(fadeTime) { + $focusContent.fadeOut(fadeTime); + $background.show(fadeTime); + $pageContent.animate({ "margin-top": "0px" }, fadeTime); + + $focusModeOn.fadeOut(fadeTime, () => { + $focusContent.detach().prependTo($focusModeOff); + $focusModeOff.fadeIn(fadeTime, () => { + $focusContent.fadeIn(fadeTime, () => {}); + if ($opener.length) $opener.fadeIn(fadeTime); + }); + }); + + unwatchFlashMessages(); + } + + const initializeFocusMode = () => { + const focusModePresent = !!$focusModeOn.length; + + $closer.on("click", () => { focusModeOff(FADEOUT_TIME) }); + + if ($opener.length) $opener.on("click", () => { focusModeOn(FADEOUT_TIME) }); + + if (focusModePresent > 0 && window.matchMedia('(min-width: 800px)').matches) { + focusModeOn(0); + } else { + focusModeOff(0); + } + } + + initializeFocusMode(); + + $(window).resize(initializeFocusMode); +}); diff --git a/decidim-core/app/assets/stylesheets/decidim/layouts/_focus_mode.scss b/decidim-core/app/assets/stylesheets/decidim/layouts/_focus_mode.scss new file mode 100644 index 0000000000000..660657ad44588 --- /dev/null +++ b/decidim-core/app/assets/stylesheets/decidim/layouts/_focus_mode.scss @@ -0,0 +1,63 @@ +.focus-mode { + position: fixed; + z-index: 1000; + width: 100%; + top: 0; + left: 0; + background: $medium-gray; + box-shadow: 0 3px 5px rgba(0, 0, 0, 0.3); + + &__title { + text-align: center; + } + + &__bar { + padding: 0.5em; + display: grid; + grid-template-columns: 1fr 1fr 1fr; + align-items: center; + + &--right { + display: flex; + justify-content: right; + align-items: center; + gap: 1em; + text-align: right; + } + } + + &__close { + position: relative; + margin: 0; + right: 0; + top: 0; + line-height: 0.5em; + } + + &__wrapper { + background: white; + } + + &__body { + max-width: 75rem; + padding: 1em; + font-size: 80%; + margin: auto; + + // Reduce sizes of content as much as possible + .card, + .card__content { + margin: 0; + padding: 0; + border: none; + } + + ul { + margin-bottom: 0; + } + + .budget-summary__selected-list { + margin-top: 0; + } + } +} diff --git a/decidim-core/app/assets/stylesheets/decidim/layouts/_layouts.scss b/decidim-core/app/assets/stylesheets/decidim/layouts/_layouts.scss index b8d3070c76821..1039549967edf 100644 --- a/decidim-core/app/assets/stylesheets/decidim/layouts/_layouts.scss +++ b/decidim-core/app/assets/stylesheets/decidim/layouts/_layouts.scss @@ -1,3 +1,4 @@ +@import "decidim/layouts/focus_mode"; @import "decidim/layouts/highlighted_banner"; @import "decidim/layouts/home"; @import "decidim/layouts/logo"; diff --git a/decidim-core/app/assets/stylesheets/decidim/modules/_cards.scss b/decidim-core/app/assets/stylesheets/decidim/modules/_cards.scss index 7814ebc313d04..1d9806002eaba 100644 --- a/decidim-core/app/assets/stylesheets/decidim/modules/_cards.scss +++ b/decidim-core/app/assets/stylesheets/decidim/modules/_cards.scss @@ -634,6 +634,10 @@ a .card__title{ pointer-events: none; } + &--inline{ + display: inline-block; + } + svg{ flex-basis: auto; } diff --git a/decidim-core/app/assets/stylesheets/decidim/modules/_extra.scss b/decidim-core/app/assets/stylesheets/decidim/modules/_extra.scss index c63ca46ba1ec7..7e5e94ba4df9d 100644 --- a/decidim-core/app/assets/stylesheets/decidim/modules/_extra.scss +++ b/decidim-core/app/assets/stylesheets/decidim/modules/_extra.scss @@ -32,6 +32,14 @@ .extra__date-container{ margin-bottom: 1rem; + + &--horizontal{ + margin-bottom: 1rem; + display: flex; + justify-content: center; + align-items: center; + gap: 2rem; + } } .extra__month{ diff --git a/decidim-core/app/cells/decidim/date/show.erb b/decidim-core/app/cells/decidim/date/show.erb index 7f294f07796f4..84d6efdf2377d 100644 --- a/decidim-core/app/cells/decidim/date/show.erb +++ b/decidim-core/app/cells/decidim/date/show.erb @@ -1,4 +1,4 @@ -
    +
    <% if same_day? %>
    <%= l start_time, format: "%d" %> diff --git a/decidim-core/app/cells/decidim/date_cell.rb b/decidim-core/app/cells/decidim/date_cell.rb index cae40687951c2..ec6a95a19d3bf 100644 --- a/decidim-core/app/cells/decidim/date_cell.rb +++ b/decidim-core/app/cells/decidim/date_cell.rb @@ -31,6 +31,10 @@ def end_time model[:end] end + def extra_classes + model[:extra_classes] + end + def same_day? start_time.beginning_of_day == end_time.beginning_of_day end diff --git a/decidim-core/app/cells/decidim/focus_mode/show.erb b/decidim-core/app/cells/decidim/focus_mode/show.erb new file mode 100644 index 0000000000000..16e0310fe1449 --- /dev/null +++ b/decidim-core/app/cells/decidim/focus_mode/show.erb @@ -0,0 +1,39 @@ +
    + + +
    +
    +
    + <%= yield %> +
    +
    +
    +
    + +
    +
    + <% if opener_button %> + + <% end %> +
    diff --git a/decidim-core/app/cells/decidim/focus_mode_cell.rb b/decidim-core/app/cells/decidim/focus_mode_cell.rb new file mode 100644 index 0000000000000..1ccbe519047bf --- /dev/null +++ b/decidim-core/app/cells/decidim/focus_mode_cell.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +require "decidim/diffy_extension" + +module Decidim + # This cell renders a dismissable overlay to highlight information about the current + # action being performed by the user + class FocusModeCell < Decidim::ViewModel + include Cell::ViewModel::Partial + include LayoutHelper + + def show(&block) + render(&block) + end + + def title + options[:title] + end + + def user + return unless current_user + + link_to current_user.name, decidim.account_path + end + + def opener_button + options[:opener_button] + end + end +end diff --git a/decidim-core/app/helpers/decidim/focus_mode_helper.rb b/decidim-core/app/helpers/decidim/focus_mode_helper.rb new file mode 100644 index 0000000000000..0bd961a4356e6 --- /dev/null +++ b/decidim-core/app/helpers/decidim/focus_mode_helper.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +module Decidim + # Helpers related to focus mode + module FocusModeHelper + def focus_mode(opts, &body) + cell(FocusModeCell, nil, opts).call do + capture(&body) + end + end + end +end \ No newline at end of file diff --git a/decidim-core/config/locales/en.yml b/decidim-core/config/locales/en.yml index 367608c363780..703324dc4ccfc 100644 --- a/decidim-core/config/locales/en.yml +++ b/decidim-core/config/locales/en.yml @@ -688,6 +688,8 @@ en: source: Source title: Fingerprint value: Value + focus_mode: + focus_mode: Enter focus mode followers: followers_count: one: "%{count} follower" diff --git a/decidim-core/lib/decidim/core/test.rb b/decidim-core/lib/decidim/core/test.rb index b7cc7f779f78f..b165140b60d32 100644 --- a/decidim-core/lib/decidim/core/test.rb +++ b/decidim-core/lib/decidim/core/test.rb @@ -10,6 +10,7 @@ require "decidim/core/test/shared_examples/has_attachments" require "decidim/core/test/shared_examples/has_attachment_collections" require "decidim/core/test/shared_examples/has_component" +require "decidim/core/test/shared_examples/has_focus_mode" require "decidim/core/test/shared_examples/has_scope" require "decidim/core/test/shared_examples/has_category" require "decidim/core/test/shared_examples/has_reference" diff --git a/decidim-core/lib/decidim/core/test/shared_examples/has_focus_mode.rb b/decidim-core/lib/decidim/core/test/shared_examples/has_focus_mode.rb new file mode 100644 index 0000000000000..44ccd99f1acf8 --- /dev/null +++ b/decidim-core/lib/decidim/core/test/shared_examples/has_focus_mode.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +require "spec_helper" + +shared_examples_for "has focus mode" do |content| + it "has the necessary elements" do + expect(page).to have_selector("#focus-mode") + expect(page).to have_selector("#off-focus-mode") + end + + it "is opened by default" do + within "#focus-mode" do + expect(page).to have_content(content) + end + end + + it "can be closed" do + expect(page).to have_selector(".focus-mode__close") + + page.find(".focus-mode__close").click + + within "#focus-mode" do + expect(page).not_to have_content(content) + end + + within "#off-focus-mode" do + expect(page).to have_content(content) + end + end +end diff --git a/decidim-core/spec/cells/decidim/date_cell_spec.rb b/decidim-core/spec/cells/decidim/date_cell_spec.rb index 3b00bb367b652..c3094c374a79a 100644 --- a/decidim-core/spec/cells/decidim/date_cell_spec.rb +++ b/decidim-core/spec/cells/decidim/date_cell_spec.rb @@ -10,7 +10,8 @@ let(:my_cell) { cell("decidim/date", model) } let!(:organization) { create(:organization) } let(:user) { create(:user, :confirmed, organization: organization) } - let(:model) { { start: start_time, end: end_time } } + let(:model) { { start: start_time, end: end_time, extra_classes: extra_classes } } + let(:extra_classes) { "extra__class" } let(:start_time) { Time.zone.now - 1.hour } let(:start_time_past_year) { Time.zone.now - 1.year } let(:end_time_same_date) { Time.zone.now + 1.hour } @@ -24,6 +25,10 @@ it "renders a Date card" do expect(subject).to have_css(".extra__date-container") end + + it "renders a the extra classes" do + expect(subject).to have_css(".extra__class") + end end context "when start and end time are on the same date" do diff --git a/decidim-core/spec/cells/decidim/focus_mode_cell_spec.rb b/decidim-core/spec/cells/decidim/focus_mode_cell_spec.rb new file mode 100644 index 0000000000000..e170685ca7814 --- /dev/null +++ b/decidim-core/spec/cells/decidim/focus_mode_cell_spec.rb @@ -0,0 +1,59 @@ +# frozen_string_literal: true + +require "spec_helper" + +describe Decidim::FocusModeCell, type: :cell do + subject { my_cell.call { "content" } } + + controller Decidim::Budgets::BudgetsController + + let(:my_cell) { cell("decidim/focus_mode", nil, options) } + let!(:organization) { create(:organization) } + let(:user) { create(:user, :confirmed, organization: organization) } + let(:options) { { title: "A title for focus mode", opener_button: opener_button, context: { current_user: user, current_organization: organization } } } + let(:opener_button) { false } + let(:decidim) { Decidim::Core::Engine.routes } + + it "renders the content passed as a block" do + expect(subject).to have_content("content") + end + + it "renders the title passed in the options" do + expect(subject).to have_content("A title for focus mode") + end + + it "renders the organization name linking to the home page" do + expect(subject).to have_link(organization.name, href: "http://#{organization.host}/") + end + + context "when opener_button option is false" do + let(:opener_button) { false } + + it "does not render the opener button" do + expect(subject).not_to have_css("[data-focus-open]") + end + end + + context "when opener_button option is true" do + let(:opener_button) { true } + + it "renders the opener button" do + expect(subject).to have_css("[data-focus-open]") + end + end + + context "when the user is logged in" do + it "renders the user name with a link to the account page" do + expect(subject).to have_css(".focus-mode__user") + expect(subject).to have_link(user.name, href: "/account") + end + end + + context "when the user is not logged in" do + let(:user) { nil } + + it "does not render the user name" do + expect(subject).not_to have_css(".focus-mode__user") + end + end +end diff --git a/decidim-proposals/app/cells/decidim/proposals/proposal_m_cell.rb b/decidim-proposals/app/cells/decidim/proposals/proposal_m_cell.rb index 893c33458a24b..a0bf38dd14e53 100644 --- a/decidim-proposals/app/cells/decidim/proposals/proposal_m_cell.rb +++ b/decidim-proposals/app/cells/decidim/proposals/proposal_m_cell.rb @@ -140,6 +140,10 @@ def cache_hash end hash << model.follows_count hash << Digest::MD5.hexdigest(model.authors.map(&:cache_key_with_version).to_s) + hash << (model.must_render_translation?(model.organization) ? 1 : 0) if model.respond_to?(:must_render_translation?) + hash << model.component.participatory_space.active_step.id if model.component.participatory_space.try(:active_step) + hash << has_footer? + hash << has_actions? hash.join("/") end