diff --git a/admin/app/components/solidus_admin/promotions/index/component.html.erb b/admin/app/components/solidus_admin/promotions/index/component.html.erb new file mode 100644 index 00000000000..3740d4acbd0 --- /dev/null +++ b/admin/app/components/solidus_admin/promotions/index/component.html.erb @@ -0,0 +1,34 @@ +<%= page do %> + <%= page_header do %> + <%= page_header_title title %> + <%= page_header_actions do %> + <%= render component("ui/button").new( + tag: :a, + text: t('.add'), + href: spree.new_admin_promotion_path, + icon: "add-line", + ) %> + <% end %> + <% end %> + + <%= render component('ui/table').new( + id: stimulus_id, + data: { + class: Spree::Promotion, + rows: @page.records, + url: ->(promotion) { spree.admin_promotion_path(promotion) }, + prev: prev_page_path, + next: next_page_path, + columns: columns, + batch_actions: batch_actions, + }, + search: { + name: :q, + value: params[:q], + url: solidus_admin.promotions_path, + searchbar_key: :name_or_codes_value_or_path_or_description_cont, + filters: filters, + scopes: scopes, + }, + ) %> +<% end %> diff --git a/admin/app/components/solidus_admin/promotions/index/component.rb b/admin/app/components/solidus_admin/promotions/index/component.rb new file mode 100644 index 00000000000..b80a42bf3e9 --- /dev/null +++ b/admin/app/components/solidus_admin/promotions/index/component.rb @@ -0,0 +1,97 @@ +# frozen_string_literal: true + +class SolidusAdmin::Promotions::Index::Component < SolidusAdmin::BaseComponent + include SolidusAdmin::Layout::PageHelpers + + def initialize(page:) + @page = page + end + + def title + Spree::Promotion.model_name.human.pluralize + end + + def prev_page_path + solidus_admin.url_for(**request.params, page: @page.number - 1, only_path: true) unless @page.first? + end + + def next_page_path + solidus_admin.url_for(**request.params, page: @page.next_param, only_path: true) unless @page.last? + end + + def batch_actions + [ + { + display_name: t('.batch_actions.delete'), + action: solidus_admin.promotions_path, + method: :delete, + icon: 'delete-bin-7-line', + }, + ] + end + + def filters + [ + { + presentation: Spree::PromotionCategory.model_name.human.pluralize, + attribute: "promotion_category_id", + predicate: "in", + options: Spree::PromotionCategory.pluck(:name, :id) + } + ] + end + + def scopes + [ + { name: :active, label: t('.scopes.active'), default: true }, + { name: :draft, label: t('.scopes.draft') }, + { name: :future, label: t('.scopes.future') }, + { name: :expired, label: t('.scopes.expired') }, + { name: :all, label: t('.scopes.all') }, + ] + end + + def columns + [ + { + header: :name, + data: ->(promotion) do + content_tag :div, promotion.name + end + }, + { + header: :code, + data: ->(promotion) do + count = promotion.codes.count + (count == 1) ? promotion.codes.pick(:value) : t('spree.number_of_codes', count: count) + end + }, + { + header: :status, + data: ->(promotion) do + if promotion.active? + render component('ui/badge').new(name: t('.status.active'), color: :green) + else + render component('ui/badge').new(name: t('.status.inactive'), color: :graphite_light) + end + end + }, + { + header: :usage_limit, + data: ->(promotion) { promotion.usage_limit || icon_tag('infinity-line') } + }, + { + header: :uses, + data: ->(promotion) { promotion.usage_count } + }, + { + header: :starts_at, + data: ->(promotion) { promotion.starts_at ? l(promotion.starts_at, format: :long) : icon_tag('infinity-line') } + }, + { + header: :expires_at, + data: ->(promotion) { promotion.expires_at ? l(promotion.expires_at, format: :long) : icon_tag('infinity-line') } + }, + ] + end +end diff --git a/admin/app/components/solidus_admin/promotions/index/component.yml b/admin/app/components/solidus_admin/promotions/index/component.yml new file mode 100644 index 00000000000..6f43121d76f --- /dev/null +++ b/admin/app/components/solidus_admin/promotions/index/component.yml @@ -0,0 +1,14 @@ +en: + promotion_image: 'Image' + add: 'Add Promotion' + batch_actions: + delete: 'Delete' + scopes: + active: Active + draft: Draft + future: Future + expired: Expired + all: All + status: + active: Active + inactive: Inactive diff --git a/admin/app/controllers/solidus_admin/promotions_controller.rb b/admin/app/controllers/solidus_admin/promotions_controller.rb new file mode 100644 index 00000000000..04c83237788 --- /dev/null +++ b/admin/app/controllers/solidus_admin/promotions_controller.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +module SolidusAdmin + class PromotionsController < SolidusAdmin::BaseController + include SolidusAdmin::ControllerHelpers::Search + + search_scope(:active, default: true, &:active) + search_scope(:draft) { _1.where.not(id: _1.has_actions.select(:id)) } + search_scope(:future) { _1.has_actions.where(starts_at: Time.current..) } + search_scope(:expired) { _1.has_actions.where(expires_at: ..Time.current) } + search_scope(:all) + + def index + promotions = apply_search_to( + Spree::Promotion.order(id: :desc), + param: :q, + ) + + set_page_and_extract_portion_from(promotions) + + respond_to do |format| + format.html { render component('promotions/index').new(page: @page) } + end + end + + def destroy + @promotions = Spree::Promotion.where(id: params[:id]) + + Spree::Promotion.transaction { @promotions.destroy_all } + + flash[:notice] = t('.success') + redirect_back_or_to promotions_path, status: :see_other + end + + private + + def load_promotion + @promotion = Spree::Promotion.find_by!(number: params[:id]) + authorize! action_name, @promotion + end + + def promotion_params + params.require(:promotion).permit(:user_id, permitted_promotion_attributes) + end + end +end diff --git a/admin/config/locales/promotions.en.yml b/admin/config/locales/promotions.en.yml new file mode 100644 index 00000000000..338d980ceeb --- /dev/null +++ b/admin/config/locales/promotions.en.yml @@ -0,0 +1,6 @@ +en: + solidus_admin: + promotions: + title: "Promotions" + destroy: + success: "Promotions were successfully removed." diff --git a/admin/config/routes.rb b/admin/config/routes.rb index 5714b72cd08..eeb32c0884e 100644 --- a/admin/config/routes.rb +++ b/admin/config/routes.rb @@ -36,4 +36,10 @@ delete :destroy end end + + resources :promotions, only: [:index] do + collection do + delete :destroy + end + end end diff --git a/admin/spec/features/promotions_spec.rb b/admin/spec/features/promotions_spec.rb new file mode 100644 index 00000000000..0f1087d29c1 --- /dev/null +++ b/admin/spec/features/promotions_spec.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe "Promotions", :js, type: :feature do + before { sign_in create(:admin_user, email: 'admin@example.com') } + + it "lists promotions and allows deleting them" do + create(:promotion, :with_action, name: "My active Promotion") + create(:promotion, name: "My draft Promotion") + create(:promotion, :with_action, name: "My expired Promotion", expires_at: 1.day.ago) + create(:promotion, :with_action, name: "My future Promotion", starts_at: 1.day.from_now) + + visit "/admin/promotions" + expect(page).to have_content("My active Promotion") + click_on "Draft" + expect(page).to have_content("My draft Promotion", wait: 30) + click_on "Future" + expect(page).to have_content("My future Promotion", wait: 30) + click_on "Expired" + expect(page).to have_content("My expired Promotion", wait: 30) + click_on "All" + expect(page).to have_content("My active Promotion", wait: 30) + expect(page).to have_content("My draft Promotion") + expect(page).to have_content("My future Promotion") + expect(page).to have_content("My expired Promotion") + + expect(page).to be_axe_clean + + select_row("My active Promotion") + click_on "Delete" + expect(page).to have_content("Promotions were successfully removed.") + expect(page).not_to have_content("My active Promotion") + expect(Spree::Promotion.count).to eq(3) + end +end