diff --git a/app/assets/stylesheets/budgets/executions/heading.scss b/app/assets/stylesheets/budgets/executions/heading.scss new file mode 100644 index 000000000..605f0debc --- /dev/null +++ b/app/assets/stylesheets/budgets/executions/heading.scss @@ -0,0 +1,20 @@ +.budgets-executions-heading { + .budgets-executions-heading-investments { + $spacing: rem-calc(map-get($grid-column-gutter, medium)); + + @include flex-with-gap($spacing); + flex-wrap: wrap; + + > * { + margin-bottom: $line-height; + + @include breakpoint(medium) { + width: calc(100% / 2 - #{$spacing}); + } + + @include breakpoint(large) { + width: calc(100% / 3 - #{$spacing}); + } + } + } +} diff --git a/app/assets/stylesheets/budgets/executions/investment.scss b/app/assets/stylesheets/budgets/executions/investment.scss new file mode 100644 index 000000000..bd8176af1 --- /dev/null +++ b/app/assets/stylesheets/budgets/executions/investment.scss @@ -0,0 +1,54 @@ +.budget-executions-investment { + @include card; + border: 1px solid $border; + overflow: hidden; + position: relative; + + a { + color: inherit; + display: block; + + img { + height: $line-height * 9; + max-width: none; + min-width: 100%; + transition-duration: 0.3s; + transition-property: transform; + } + + &:hover { + text-decoration: none; + + img { + transform: scale(1.05); + } + } + } + + h5 { + font-size: $base-font-size; + margin-bottom: 0; + } + + .budget-execution-info { + padding: calc(#{$line-height} / 2); + } + + .author { + color: $text-medium; + font-size: $small-font-size; + } + + .budget-execution-content { + min-height: $line-height * 3; + } + + .price { + color: $budget; + font-size: rem-calc(24); + } + + &:hover:not(:focus-within) { + box-shadow: 0 0 12px 0 rgba(0, 0, 0, 0.2); + } +} diff --git a/app/assets/stylesheets/participation.scss b/app/assets/stylesheets/participation.scss index 407b94077..2dd68776b 100644 --- a/app/assets/stylesheets/participation.scss +++ b/app/assets/stylesheets/participation.scss @@ -1290,61 +1290,6 @@ } } -.budget-execution { - @include card; - border: 1px solid $border; - overflow: hidden; - position: relative; - - a { - color: inherit; - display: block; - - img { - height: $line-height * 9; - max-width: none; - min-width: 100%; - transition-duration: 0.3s; - transition-property: transform; - } - - &:hover { - text-decoration: none; - - img { - transform: scale(1.05); - } - } - } - - h5 { - font-size: $base-font-size; - margin-bottom: 0; - } - - .budget-execution-info { - padding: calc(#{$line-height} / 2); - } - - .author { - color: $text-medium; - font-size: $small-font-size; - } - - .budget-execution-content { - min-height: $line-height * 3; - } - - .price { - color: $budget; - font-size: rem-calc(24); - } - - &:hover:not(:focus-within) { - box-shadow: 0 0 12px 0 rgba(0, 0, 0, 0.2); - } -} - // 07. Proposals successful // ------------------------- diff --git a/app/components/budgets/executions/heading_component.html.erb b/app/components/budgets/executions/heading_component.html.erb new file mode 100644 index 000000000..6a1599527 --- /dev/null +++ b/app/components/budgets/executions/heading_component.html.erb @@ -0,0 +1,11 @@ +
+

+ <%= heading.name %> (<%= investments.count %>) +

+ +
+ <% investments.each do |investment| %> + <%= render Budgets::Executions::InvestmentComponent.new(investment) %> + <% end %> +
+
diff --git a/app/components/budgets/executions/heading_component.rb b/app/components/budgets/executions/heading_component.rb new file mode 100644 index 000000000..4f38ac5a7 --- /dev/null +++ b/app/components/budgets/executions/heading_component.rb @@ -0,0 +1,8 @@ +class Budgets::Executions::HeadingComponent < ApplicationComponent + attr_reader :heading, :investments + + def initialize(heading, investments) + @heading = heading + @investments = investments + end +end diff --git a/app/components/budgets/executions/investment_component.html.erb b/app/components/budgets/executions/investment_component.html.erb new file mode 100644 index 000000000..c990b2193 --- /dev/null +++ b/app/components/budgets/executions/investment_component.html.erb @@ -0,0 +1,15 @@ +
+ <%= render Budgets::Executions::ImageComponent.new(investment) %> +
+
+
+ <%= link_to investment.title, + polymorphic_path(investment, anchor: "tab-milestones") %> +
+ <%= investment.author.name %> +
+

+ <%= investment.formatted_price %> +

+
+
diff --git a/app/components/budgets/executions/investment_component.rb b/app/components/budgets/executions/investment_component.rb new file mode 100644 index 000000000..4ba229d5d --- /dev/null +++ b/app/components/budgets/executions/investment_component.rb @@ -0,0 +1,7 @@ +class Budgets::Executions::InvestmentComponent < ApplicationComponent + attr_reader :investment + + def initialize(investment) + @investment = investment + end +end diff --git a/app/components/budgets/executions/investments_component.html.erb b/app/components/budgets/executions/investments_component.html.erb new file mode 100644 index 000000000..93ffbb281 --- /dev/null +++ b/app/components/budgets/executions/investments_component.html.erb @@ -0,0 +1,3 @@ +<% investments_by_heading.each do |heading, investments| %> + <%= render Budgets::Executions::HeadingComponent.new(heading, investments) %> +<% end %> diff --git a/app/components/budgets/executions/investments_component.rb b/app/components/budgets/executions/investments_component.rb new file mode 100644 index 000000000..d6f50bac7 --- /dev/null +++ b/app/components/budgets/executions/investments_component.rb @@ -0,0 +1,7 @@ +class Budgets::Executions::InvestmentsComponent < ApplicationComponent + attr_reader :investments_by_heading + + def initialize(investments_by_heading) + @investments_by_heading = investments_by_heading + end +end diff --git a/app/views/budgets/executions/_investments.html.erb b/app/views/budgets/executions/_investments.html.erb deleted file mode 100644 index e0f3abfdd..000000000 --- a/app/views/budgets/executions/_investments.html.erb +++ /dev/null @@ -1,26 +0,0 @@ -<% @investments_by_heading.each do |heading, investments| %> -

- <%= heading.name %> (<%= investments.count %>) -

-
- <% investments.each do |investment| %> -
-
- <%= render Budgets::Executions::ImageComponent.new(investment) %> -
-
-
- <%= link_to investment.title, - budget_investment_path(@budget, investment, anchor: "tab-milestones") %> -
- <%= investment.author.name %> -
-

- <%= investment.formatted_price %> -

-
-
-
- <% end %> -
-<% end %> diff --git a/app/views/budgets/executions/show.html.erb b/app/views/budgets/executions/show.html.erb index 01c16fc44..4db70fb8f 100644 --- a/app/views/budgets/executions/show.html.erb +++ b/app/views/budgets/executions/show.html.erb @@ -45,7 +45,7 @@ <%= render Budgets::Executions::FiltersComponent.new(@budget, @statuses) %> <% if @investments_by_heading.any? %> - <%= render "budgets/executions/investments" %> + <%= render Budgets::Executions::InvestmentsComponent.new(@investments_by_heading) %> <% else %>
<%= t("budgets.executions.no_winner_investments") %> diff --git a/spec/components/budgets/executions/image_component_spec.rb b/spec/components/budgets/executions/image_component_spec.rb index a40d8d1ce..4cead4790 100644 --- a/spec/components/budgets/executions/image_component_spec.rb +++ b/spec/components/budgets/executions/image_component_spec.rb @@ -1,13 +1,50 @@ require "rails_helper" describe Budgets::Executions::ImageComponent do + let(:component) { Budgets::Executions::ImageComponent.new(investment) } + + context "investment with image" do + let(:investment) { create(:budget_investment, :with_image) } + + it "shows a milestone image if available" do + create(:milestone, :with_image, image_title: "Building in progress", milestoneable: investment) + + render_inline component + + expect(page).to have_css "img[alt='Building in progress']" + expect(page).not_to have_css "img[alt='#{investment.image.title}']" + end + + it "shows the investment image if no milestone image is available" do + create(:milestone, :with_image, image_title: "Not related", milestoneable: create(:budget_investment)) + + render_inline component + + expect(page).to have_css "img[alt='#{investment.image.title}']" + expect(page).not_to have_css "img[alt='Not related']" + end + + it "shows the last milestone's image if the investment has multiple milestones with images associated" do + create(:milestone, milestoneable: investment) + create(:milestone, :with_image, image_title: "First image", milestoneable: investment) + create(:milestone, :with_image, image_title: "Second image", milestoneable: investment) + create(:milestone, milestoneable: investment) + + render_inline component + + expect(page).to have_css "img[alt='Second image']" + expect(page).not_to have_css "img[alt='First image']" + expect(page).not_to have_css "img[alt='#{investment.image.title}']" + end + end + context "investment and milestone without image" do - let(:component) { Budgets::Executions::ImageComponent.new(Budget::Investment.new) } + let(:investment) { Budget::Investment.new(title: "New and empty") } it "shows the default image" do render_inline component - expect(page).to have_css "img[src*='budget_execution_no_image']" + expect(page).to have_css "img[src*='budget_execution_no_image'][alt='New and empty']" end it "shows a custom default image when available" do @@ -18,7 +55,7 @@ describe Budgets::Executions::ImageComponent do render_inline component - expect(page).to have_css "img[src$='logo_header-260x80.png']" + expect(page).to have_css "img[src$='logo_header-260x80.png'][alt='New and empty']" expect(page).not_to have_css "img[src*='budget_execution_no_image']" end end diff --git a/spec/system/budgets/executions_spec.rb b/spec/system/budgets/executions_spec.rb index 5b721e257..88d21ec02 100644 --- a/spec/system/budgets/executions_spec.rb +++ b/spec/system/budgets/executions_spec.rb @@ -83,31 +83,6 @@ describe "Executions" do end context "Images" do - scenario "renders milestone image if available" do - milestone1 = create(:milestone, :with_image, milestoneable: investment1) - - visit budget_path(budget) - - click_link "See results" - click_link "Milestones" - - expect(page).to have_content(investment1.title) - expect(page).to have_css("img[alt='#{milestone1.image.title}']") - end - - scenario "renders investment image if no milestone image is available" do - create(:milestone, milestoneable: investment2) - create(:image, imageable: investment2) - - visit budget_path(budget) - - click_link "See results" - click_link "Milestones" - - expect(page).to have_content(investment2.title) - expect(page).to have_css("img[alt='#{investment2.image.title}']") - end - scenario "renders default image if no milestone nor investment images are available" do create(:milestone, milestoneable: investment4) @@ -116,23 +91,8 @@ describe "Executions" do click_link "See results" click_link "Milestones" - expect(page).to have_content(investment4.title) - expect(page).to have_css("img[alt='#{investment4.title}']") - end - - scenario "renders last milestone's image if investment has multiple milestones with images associated" do - create(:milestone, milestoneable: investment1) - create(:milestone, :with_image, image_title: "First image", milestoneable: investment1) - create(:milestone, :with_image, image_title: "Second image", milestoneable: investment1) - create(:milestone, milestoneable: investment1) - - visit budget_path(budget) - - click_link "See results" - click_link "Milestones" - - expect(page).to have_content(investment1.title) - expect(page).to have_css("img[alt='Second image']") + expect(page).to have_content investment4.title + expect(page).to have_css "img[alt='#{investment4.title}']" end end @@ -294,7 +254,7 @@ describe "Executions" do visit budget_executions_path(budget) - expect(page).to have_css(".budget-execution", count: 3) + expect(page).to have_css(".budget-executions-investment", count: 3) expect(a_heading.name).to appear_before(m_heading.name) expect(m_heading.name).to appear_before(z_heading.name) end