Skip to content

Commit 3660447

Browse files
authored
Merge pull request #6283 from chaimann/admin-product-option-types-list
[Admin][Products] Product option types list
2 parents 1b14ee9 + bb3a05e commit 3660447

13 files changed

Lines changed: 168 additions & 18 deletions

File tree

admin/app/components/solidus_admin/products/show/component.html.erb

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,44 @@
7676
) %>
7777
<% end %>
7878

79-
<%= render component("ui/panel").new(title: t(".options")) do %>
80-
<%= f.select(:option_type_ids, option_type_options, multiple: true) %>
79+
<%= render component("ui/panel").new(title: t(".options")) do |panel| %>
80+
<% if @product.product_option_types.present? %>
81+
<% panel.with_section do %>
82+
<div class="flex flex-col gap-4" data-controller="sortable" data-sortable-handle-value=".handle">
83+
<% @product.product_option_types.includes(option_type: :option_values).order(:position).each do |product_option| %>
84+
<div class="flex gap-2 justify-between" data-sortable-url=<%= solidus_admin.move_product_option_type_path(product_option) %>>
85+
<div class="flex gap-2">
86+
<div class="flex items-center">
87+
<%= render component("ui/icon").new(name: "draggable", class: "w-6 h-6 cursor-grab handle fill-gray-500") %>
88+
</div>
89+
<div class="flex flex-col gap-2">
90+
<span class="font-semibold text-sm"><%= product_option.option_type.name %>:<%= product_option.option_type.presentation %></span>
91+
<div class="flex gap-2 flex-wrap">
92+
<% product_option.option_type.option_values.each do |value| %>
93+
<%= render component("ui/badge").new(name: "#{value.name}:#{value.presentation}") %>
94+
<% end %>
95+
</div>
96+
</div>
97+
</div>
98+
<div class="flex items-center">
99+
<%= render component("ui/button").new(tag: :a, href: spree.edit_admin_option_type_path(product_option.option_type), scheme: :secondary, text: t(".edit")) %>
100+
</div>
101+
</div>
102+
<% end %>
103+
</div>
104+
<% end %>
105+
<% end %>
106+
107+
<div class="flex gap-4 justify-between items-end">
108+
<%= hidden_field_tag "#{f.object_name}[option_type_ids][]", nil %>
109+
<%= f.select(:option_type_ids, option_type_options, multiple: true) %>
110+
<%= render component("ui/button").new(type: :submit, text: t(".save")) %>
111+
</div>
112+
113+
<% panel.with_action(
114+
name: t(".manage_options"),
115+
href: solidus_admin.option_types_path
116+
) %>
81117
<% end %>
82118

83119
<%= render component("ui/panel").new(title: t(".specifications")) do |panel| %>

admin/app/components/solidus_admin/products/show/component.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ def taxon_options
2222

2323
def option_type_options
2424
@option_type_options ||= Spree::OptionType.order(:presentation).pluck(:presentation, :name, :id).map do
25-
["#{_1} (#{_2})", _3]
25+
["#{_2}:#{_1}", _3]
2626
end
2727
end
2828

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,17 @@
11
en:
2-
duplicate: "Duplicate"
3-
view: "View online"
2+
back: "Back"
43
delete: "Delete"
54
delete_confirmation: "Are you sure you want to delete this product?"
5+
duplicate: "Duplicate"
6+
edit: "Edit"
7+
hints:
8+
available_on_html: "Product availability starts from the set date.<br> Empty date indicates no availability."
9+
discontinue_on_html: "Product availability ends from the set date.<br> Empty date indicates continuous availability."
10+
promotionable_html: "Promotions can apply to this product"
11+
shipping_category_html: "Manage Shipping in Settings"
12+
tax_category_html: "Manage Taxes in Settings"
613
manage_images: "Manage images"
14+
manage_options: "Manage option types"
715
manage_properties: "Manage product specifications"
816
manage_stock: "Manage stock"
917
media: "Media"
@@ -12,13 +20,9 @@ en:
1220
pricing: "Pricing"
1321
product_organization: "Product organization"
1422
publishing: "Publishing"
23+
save: "Save"
1524
seo: "SEO"
1625
stock: "Stock"
1726
shipping: "Shipping"
1827
specifications: "Specifications"
19-
hints:
20-
available_on_html: "Product availability starts from the set date.<br> Empty date indicates no availability."
21-
discontinue_on_html: "Product availability ends from the set date.<br> Empty date indicates continuous availability."
22-
promotionable_html: "Promotions can apply to this product"
23-
shipping_category_html: "Manage Shipping in Settings"
24-
tax_category_html: "Manage Taxes in Settings"
28+
view: "View online"
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# frozen_string_literal: true
2+
3+
class SolidusAdmin::ProductOptionTypesController < SolidusAdmin::BaseController
4+
include SolidusAdmin::Moveable
5+
end

admin/app/controllers/solidus_admin/products_controller.rb

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,11 +43,7 @@ def update
4343
@product = Spree::Product.friendly.find(params[:id])
4444

4545
if @product.update(product_params)
46-
flash[:notice] = t('spree.successfully_updated', resource: [
47-
Spree::Product.model_name.human,
48-
@product.name.inspect,
49-
].join(' '))
50-
46+
flash[:success] = t('.success')
5147
redirect_to action: :show, status: :see_other
5248
else
5349
flash.now[:error] = @product.errors.full_messages.join(", ")

admin/config/locales/products.en.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,5 @@ en:
88
success: "Products were successfully discontinued."
99
activate:
1010
success: "Products were successfully activated."
11+
update:
12+
success: "Product was successfully updated."

admin/config/routes.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,4 +87,5 @@
8787
admin_resources :roles, except: [:show]
8888
admin_resources :adjustment_reasons, except: [:show]
8989
admin_resources :store_credit_reasons, except: [:show]
90+
admin_resources :product_option_types, only: [], sortable: true
9091
end

admin/lib/solidus_admin/testing_support/feature_helpers.rb

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,10 @@ def select_row(text)
3333
end
3434
end
3535

36+
def panel(title:)
37+
find("section", text: title).find(:xpath, "..")
38+
end
39+
3640
# Select options from a "solidus-select" field
3741
#
3842
# @param value [String, Array<String>] which option(s) to select
@@ -52,6 +56,14 @@ def solidus_select(value, from:)
5256
end
5357
end
5458

59+
def solidus_unselect(value, from:)
60+
input = find_field(from, visible: :all)
61+
Array.wrap(value).each do |val|
62+
item = input.sibling("div", text: val)
63+
item.find("a").click
64+
end
65+
end
66+
5567
def checkbox(locator)
5668
find(:checkbox, locator)
5769
end

admin/lib/solidus_admin/testing_support/shared_examples/moveable.rb

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
RSpec.shared_examples_for "features: sortable" do
2323
let(:factory_attrs) { {} }
2424
let(:scope) { "body" }
25+
let(:handle) { nil }
2526

2627
before do
2728
create(factory, displayed_attribute => "First", position: 1, **factory_attrs)
@@ -35,7 +36,10 @@
3536
expect(find("[data-controller='sortable']").all(:xpath, "./*").last).to have_text("Second")
3637

3738
rows = find("[data-controller='sortable']").all(:xpath, "./*")
38-
rows[1].drag_to rows[0]
39+
target = rows[0]
40+
source = rows[1]
41+
source = source.find(handle) if handle
42+
source.drag_to target
3943

4044
expect(find("[data-controller='sortable']").all(:xpath, "./*").first).to have_text("Second")
4145
expect(find("[data-controller='sortable']").all(:xpath, "./*").last).to have_text("First")

admin/spec/features/product_spec.rb

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# frozen_string_literal: true
22

33
require 'spec_helper'
4+
require "solidus_admin/testing_support/shared_examples/moveable"
45

56
describe "Product", type: :feature do
67
before do
@@ -52,4 +53,81 @@
5253
expect(page).to have_content("Name can't be blank")
5354
expect(page).to be_axe_clean
5455
end
56+
57+
describe "option types", :js do
58+
before do
59+
create(:option_type, name: "clothing-size", presentation: "Size").tap do |option_type|
60+
option_type.option_values << [
61+
create(:option_value, name: "S", presentation: "Small"),
62+
create(:option_value, name: "M", presentation: "Medium")
63+
]
64+
end
65+
66+
create(:option_type, name: "clothing-color", presentation: "Color").tap do |option_type|
67+
option_type.option_values << [
68+
create(:option_value, name: "brown", presentation: "Brown"),
69+
create(:option_value, name: "red", presentation: "Red")
70+
]
71+
end
72+
end
73+
74+
let!(:product) { create(:product, name: "Just a product", slug: 'just-a-prod', price: 19.99) }
75+
76+
it "updates option types" do
77+
visit "/admin/products/just-a-prod"
78+
solidus_select(%w[clothing-size:Size clothing-color:Color], from: "Option Types")
79+
options_panel = panel(title: "Options")
80+
# for some reason capybara on circle ci does not register a form submit when clicking "Save" within options panel,
81+
# so we have to resort to Save button in the header
82+
within("header") { click_on "Save" }
83+
84+
expect(options_panel).to have_content("clothing-size:Size")
85+
expect(options_panel).to have_content("S:Small")
86+
expect(options_panel).to have_content("M:Medium")
87+
expect(options_panel).to have_content("clothing-color:Color")
88+
expect(options_panel).to have_content("brown:Brown")
89+
expect(options_panel).to have_content("red:Red")
90+
91+
solidus_unselect(%w[clothing-size:Size clothing-color:Color], from: "Option Types")
92+
within(options_panel) { click_on "Save" }
93+
94+
expect(options_panel).not_to have_content("clothing-size:Size")
95+
expect(options_panel).not_to have_content("S:Small")
96+
expect(options_panel).not_to have_content("M:Medium")
97+
expect(options_panel).not_to have_content("clothing-color:Color")
98+
expect(options_panel).not_to have_content("brown:Brown")
99+
expect(options_panel).not_to have_content("red:Red")
100+
end
101+
102+
context "clicking on Edit" do
103+
# skipping test until updated option types UI is merged
104+
# https://github.com/solidusio/solidus/pull/6236
105+
xit "leads to option type edit page" do
106+
option_type = create(:option_type)
107+
product.option_types << option_type
108+
visit "/admin/products/just-a-prod"
109+
110+
within(panel(title: "Options")) { click_on "Edit" }
111+
expect(page).to have_current_path("/admin/option_types/#{option_type.id}/edit")
112+
end
113+
end
114+
115+
context "clicking on Manage option types" do
116+
it "leads to option types index page" do
117+
visit "/admin/products/just-a-prod"
118+
119+
within(panel(title: "Options")) { click_on "Manage option types" }
120+
expect(page).to have_current_path("/admin/option_types")
121+
end
122+
end
123+
124+
it_behaves_like "features: sortable" do
125+
let(:product) { create(:product) }
126+
let(:factory) { :option_type }
127+
let(:factory_attrs) { { products: [product] } }
128+
let(:displayed_attribute) { :name }
129+
let(:handle) { ".handle" }
130+
let(:path) { solidus_admin.product_path(product) }
131+
end
132+
end
55133
end

0 commit comments

Comments
 (0)