From 0847f6b0f65d4c500e24a11e1fc9e7accd84aff3 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Bellet Date: Tue, 19 Sep 2023 14:25:55 +0200 Subject: [PATCH 1/6] Create a new column actions for each rows (product+variant) --- app/views/admin/products_v3/_table.html.haml | 4 ++++ config/locales/en.yml | 1 + 2 files changed, 5 insertions(+) diff --git a/app/views/admin/products_v3/_table.html.haml b/app/views/admin/products_v3/_table.html.haml index 7c3aa22d5a..b8a8deae5c 100644 --- a/app/views/admin/products_v3/_table.html.haml +++ b/app/views/admin/products_v3/_table.html.haml @@ -21,6 +21,7 @@ %col{ width:"10%" } %col{ width:"5%" } %col{ width:"5%", style: "max-width:5em" } + %col{ width:"5%", style: "max-width:5em" } %thead %tr %th.align-left.with-input= t('admin.products_page.columns.name') @@ -32,6 +33,7 @@ %th.align-left= t('admin.products_page.columns.category') %th.align-left= t('admin.products_page.columns.tax_category') %th.align-left= t('admin.products_page.columns.inherits_properties') + %th.align-right= t('admin.products_page.columns.actions') - products.each do |product| = form.fields_for("products", product, index: nil) do |product_form| %tbody.relaxed @@ -58,6 +60,7 @@ %td.align-left %td.align-left .line-clamp-1= product.inherits_properties ? 'YES' : 'NO' #TODO: consider using https://github.com/RST-J/human_attribute_values, else use I18n.t (also below) + %td.align-right - product.variants.each do |variant| - prefix = "[products][][variants_attributes][]" # Couldn't convince the formbuilder to generate this for me, so for now we manually add the prefix = form.fields_for(variant) do |variant_form| @@ -81,3 +84,4 @@ .line-clamp-1= variant.tax_category&.name || "None" # TODO: convert to dropdown, else translate hardcoded string. %td.align-left -# empty + %td.align-right diff --git a/config/locales/en.yml b/config/locales/en.yml index 245f23ea60..e4146d2085 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -550,6 +550,7 @@ en: tax_category: "Tax Category" inherits_properties: "Inherits Properties?" import_date: "Import Date" + actions: Actions columns_selector: unit: Unit price: Price From 2c478f1d8e79f90b20c6d4fce920b5345b29b03e Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Bellet Date: Tue, 19 Sep 2023 14:26:14 +0200 Subject: [PATCH 2/6] Link to product/variant edit page --- app/views/admin/products_v3/_table.html.haml | 2 ++ config/locales/en.yml | 2 ++ 2 files changed, 4 insertions(+) diff --git a/app/views/admin/products_v3/_table.html.haml b/app/views/admin/products_v3/_table.html.haml index b8a8deae5c..ba404e519d 100644 --- a/app/views/admin/products_v3/_table.html.haml +++ b/app/views/admin/products_v3/_table.html.haml @@ -61,6 +61,7 @@ %td.align-left .line-clamp-1= product.inherits_properties ? 'YES' : 'NO' #TODO: consider using https://github.com/RST-J/human_attribute_values, else use I18n.t (also below) %td.align-right + = link_to t('admin.products_page.actions.edit'), edit_admin_product_path(product) - product.variants.each do |variant| - prefix = "[products][][variants_attributes][]" # Couldn't convince the formbuilder to generate this for me, so for now we manually add the prefix = form.fields_for(variant) do |variant_form| @@ -85,3 +86,4 @@ %td.align-left -# empty %td.align-right + = link_to t('admin.products_page.actions.edit'), edit_admin_product_variant_path(product, variant) diff --git a/config/locales/en.yml b/config/locales/en.yml index e4146d2085..63d1c9d2cd 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -562,6 +562,8 @@ en: tax_category: "Tax Category" inherits_properties: "Inherits Properties?" import_date: "Import Date" + actions: + edit: Edit adjustments: skipped_changing_canceled_order: "You can't change a cancelled order." # Common properties / models From c76bc07f7f4975c4fb29c12e6c94f5d64ea4f8bf Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Bellet Date: Tue, 19 Sep 2023 15:32:50 +0200 Subject: [PATCH 3/6] Creates a `vertical-ellipsis-menu` component --- .../vertical_ellipsis_menu_controller.js | 21 +++++++ app/webpacker/css/admin_v3/all.scss | 1 + .../components/vertical_ellipsis_menu.scss | 62 +++++++++++++++++++ .../vertical_ellipsis_menu_controller_test.js | 55 ++++++++++++++++ 4 files changed, 139 insertions(+) create mode 100644 app/webpacker/controllers/vertical_ellipsis_menu_controller.js create mode 100644 app/webpacker/css/admin_v3/components/vertical_ellipsis_menu.scss create mode 100644 spec/javascripts/stimulus/vertical_ellipsis_menu_controller_test.js diff --git a/app/webpacker/controllers/vertical_ellipsis_menu_controller.js b/app/webpacker/controllers/vertical_ellipsis_menu_controller.js new file mode 100644 index 0000000000..4a0da14e87 --- /dev/null +++ b/app/webpacker/controllers/vertical_ellipsis_menu_controller.js @@ -0,0 +1,21 @@ +import { Controller } from "stimulus"; + +export default class extends Controller { + static targets = ["content"]; + + connect() { + super.connect(); + window.addEventListener("click", (e) => { + if (this.element.contains(e.target)) return; + this.#hide(); + }); + } + + toggle() { + this.contentTarget.classList.toggle("show"); + } + + #hide() { + this.contentTarget.classList.remove("show"); + } +} diff --git a/app/webpacker/css/admin_v3/all.scss b/app/webpacker/css/admin_v3/all.scss index d9358e7a1b..f2d6fe5bd4 100644 --- a/app/webpacker/css/admin_v3/all.scss +++ b/app/webpacker/css/admin_v3/all.scss @@ -113,6 +113,7 @@ @import "../admin/reports"; @import "components/select2"; // admin_v3 @import "components/sidebar-item"; // admin_v3 +@import "components/vertical_ellipsis_menu"; // admin_v3 and only V3 @import "../admin/side_menu"; @import "../admin/tables"; @import "../admin/tag_rules"; diff --git a/app/webpacker/css/admin_v3/components/vertical_ellipsis_menu.scss b/app/webpacker/css/admin_v3/components/vertical_ellipsis_menu.scss new file mode 100644 index 0000000000..de35c98455 --- /dev/null +++ b/app/webpacker/css/admin_v3/components/vertical_ellipsis_menu.scss @@ -0,0 +1,62 @@ +.vertical-ellipsis-menu { + position: relative; + width: $btn-relaxed-height; + + i.fa-ellipsis-v { + cursor: pointer; + display: block; + height: $btn-relaxed-height; + width: $btn-relaxed-height; + line-height: $btn-relaxed-height; + text-align: center; + border-radius: 3px; + background-color: white; + } + + .vertical-ellipsis-menu-content { + position: absolute; + top: 0; + right: 0; + padding-top: 5px; + padding-bottom: 5px; + background-color: white; + @include defaultBoxShadow; + border-radius: 3px; + min-width: 80px; + display: none; + z-index: 100; + + &.show { + display: block; + } + + .vertical-ellipsis-menu-content-item { + display: block; + padding: 5px 10px; + cursor: pointer; + text-align: left; + border-left: 3px solid white; + color: $near-black; + text-decoration: none; + border-bottom: none; + + &:hover { + background-color: $light-grey; + border-left: 3px solid $spree-blue; + } + + &.delete { + color: $red; + + &:hover { + border-left: 3px solid $red; + background-color: $fair-pink; + } + } + } + } +} + +table.products td .vertical-ellipsis-menu { + float: right; +} diff --git a/spec/javascripts/stimulus/vertical_ellipsis_menu_controller_test.js b/spec/javascripts/stimulus/vertical_ellipsis_menu_controller_test.js new file mode 100644 index 0000000000..4e5fd9d0b3 --- /dev/null +++ b/spec/javascripts/stimulus/vertical_ellipsis_menu_controller_test.js @@ -0,0 +1,55 @@ +/** + * @jest-environment jsdom + */ + +import { Application } from "stimulus"; +import vertical_ellipsis_menu_controller from "../../../app/webpacker/controllers/vertical_ellipsis_menu_controller"; + +describe("VerticalEllipsisMenuController test", () => { + beforeAll(() => { + const application = Application.start(); + application.register("vertical-ellipsis-menu", vertical_ellipsis_menu_controller); + }); + + beforeEach(() => { + document.body.innerHTML = ` +
+
...
+
+ +
+
+ `; + }); + + it("add show class to content when toggle is called", () => { + const button = document.getElementById("button"); + const content = document.getElementById("content"); + + expect(content.classList.contains("show")).toBe(false); + button.click(); + expect(content.classList.contains("show")).toBe(true); + }); + + + it("remove show class from content when clicking button", () => { + const button = document.getElementById("button"); + const content = document.getElementById("content"); + + button.click(); + expect(content.classList.contains("show")).toBe(true); + button.click(); + expect(content.classList.contains("show")).toBe(false); + }); + + + it("remove show class from content when clicking outside", () => { + const button = document.getElementById("button"); + const content = document.getElementById("content"); + + button.click(); + expect(content.classList.contains("show")).toBe(true); + document.body.click(); + expect(content.classList.contains("show")).toBe(false); + }); +}); From 2a98789571fde0b89e36cdf2e4566474ac25d878 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Bellet Date: Tue, 19 Sep 2023 16:08:14 +0200 Subject: [PATCH 4/6] Add `vertical-ellipsis-menu` as product/variant actions menu in products table + add specs --- app/views/admin/products_v3/_table.html.haml | 4 +-- .../components/_product_actions.html.haml | 7 +++++ .../system/admin/products_v3/products_spec.rb | 30 +++++++++++++++++++ 3 files changed, 39 insertions(+), 2 deletions(-) create mode 100644 app/views/admin/products_v3/components/_product_actions.html.haml diff --git a/app/views/admin/products_v3/_table.html.haml b/app/views/admin/products_v3/_table.html.haml index ba404e519d..21cf8f37ab 100644 --- a/app/views/admin/products_v3/_table.html.haml +++ b/app/views/admin/products_v3/_table.html.haml @@ -61,7 +61,7 @@ %td.align-left .line-clamp-1= product.inherits_properties ? 'YES' : 'NO' #TODO: consider using https://github.com/RST-J/human_attribute_values, else use I18n.t (also below) %td.align-right - = link_to t('admin.products_page.actions.edit'), edit_admin_product_path(product) + = render partial: 'admin/products_v3/components/product_actions', locals: { product: product } - product.variants.each do |variant| - prefix = "[products][][variants_attributes][]" # Couldn't convince the formbuilder to generate this for me, so for now we manually add the prefix = form.fields_for(variant) do |variant_form| @@ -86,4 +86,4 @@ %td.align-left -# empty %td.align-right - = link_to t('admin.products_page.actions.edit'), edit_admin_product_variant_path(product, variant) + = render partial: 'admin/products_v3/components/product_actions', locals: { product: product, variant: variant } diff --git a/app/views/admin/products_v3/components/_product_actions.html.haml b/app/views/admin/products_v3/components/_product_actions.html.haml new file mode 100644 index 0000000000..a465498eac --- /dev/null +++ b/app/views/admin/products_v3/components/_product_actions.html.haml @@ -0,0 +1,7 @@ +.vertical-ellipsis-menu{ "data-controller": "vertical-ellipsis-menu" } + %i.fa.fa-ellipsis-v{ "data-action": "click->vertical-ellipsis-menu#toggle" } + .vertical-ellipsis-menu-content{ "data-vertical-ellipsis-menu-target": "content" } + - if defined?(variant) + = link_to t('admin.products_page.actions.edit'), edit_admin_product_variant_path(product, variant), class: "vertical-ellipsis-menu-content-item" + - else + = link_to t('admin.products_page.actions.edit'), edit_admin_product_path(product), class: "vertical-ellipsis-menu-content-item" diff --git a/spec/system/admin/products_v3/products_spec.rb b/spec/system/admin/products_v3/products_spec.rb index 0a0c83cbe4..53b5993cd6 100644 --- a/spec/system/admin/products_v3/products_spec.rb +++ b/spec/system/admin/products_v3/products_spec.rb @@ -157,6 +157,36 @@ describe 'As an admin, I can see the new product page' do end end + describe "Actions columns (edit)" do + let!(:variant_a1) { + create(:variant, + product: product_a, + display_name: "Medium box", + sku: "APL-01", + price: 5.25) + } + let!(:product_a) { create(:simple_product, name: "Apples", sku: "APL-00") } + + before do + visit admin_products_v3_index_url + end + + it "shows an actions memu with an edit link when clicking on icon for product" do + within row_containing_name("Apples") do + page.find(".vertical-ellipsis-menu").click + expect(page).to have_link "Edit", href: spree.edit_admin_product_path(product_a) + end + end + + it "shows an actions memu with an edit link when clicking on icon for variant" do + within row_containing_name("Medium box") do + page.find(".vertical-ellipsis-menu").click + expect(page).to have_link "Edit", + href: spree.edit_admin_product_variant_path(product_a, variant_a1) + end + end + end + describe "updating" do let!(:variant_a1) { create(:variant, From 458a031558a393cabe73124c016223830084b344 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Bellet Date: Fri, 22 Sep 2023 09:30:40 +0200 Subject: [PATCH 5/6] Create a private function for event listener --- .../controllers/vertical_ellipsis_menu_controller.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/app/webpacker/controllers/vertical_ellipsis_menu_controller.js b/app/webpacker/controllers/vertical_ellipsis_menu_controller.js index 4a0da14e87..c783adf170 100644 --- a/app/webpacker/controllers/vertical_ellipsis_menu_controller.js +++ b/app/webpacker/controllers/vertical_ellipsis_menu_controller.js @@ -5,16 +5,20 @@ export default class extends Controller { connect() { super.connect(); - window.addEventListener("click", (e) => { - if (this.element.contains(e.target)) return; - this.#hide(); - }); + window.addEventListener("click", this.#hideIfClickedOutside); } toggle() { this.contentTarget.classList.toggle("show"); } + #hideIfClickedOutside = (event) => { + if (this.element.contains(event.target)) { + return; + } + this.#hide(); + }; + #hide() { this.contentTarget.classList.remove("show"); } From b9cd8ee46278c96b7cbc82900c20bbd1a2623e71 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Bellet Date: Fri, 22 Sep 2023 09:34:36 +0200 Subject: [PATCH 6/6] Factorize elements into `beforeEach` block --- .../vertical_ellipsis_menu_controller_test.js | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/spec/javascripts/stimulus/vertical_ellipsis_menu_controller_test.js b/spec/javascripts/stimulus/vertical_ellipsis_menu_controller_test.js index 4e5fd9d0b3..da53c53a14 100644 --- a/spec/javascripts/stimulus/vertical_ellipsis_menu_controller_test.js +++ b/spec/javascripts/stimulus/vertical_ellipsis_menu_controller_test.js @@ -20,12 +20,11 @@ describe("VerticalEllipsisMenuController test", () => { `; + const button = document.getElementById("button"); + const content = document.getElementById("content"); }); it("add show class to content when toggle is called", () => { - const button = document.getElementById("button"); - const content = document.getElementById("content"); - expect(content.classList.contains("show")).toBe(false); button.click(); expect(content.classList.contains("show")).toBe(true); @@ -33,9 +32,6 @@ describe("VerticalEllipsisMenuController test", () => { it("remove show class from content when clicking button", () => { - const button = document.getElementById("button"); - const content = document.getElementById("content"); - button.click(); expect(content.classList.contains("show")).toBe(true); button.click(); @@ -44,9 +40,6 @@ describe("VerticalEllipsisMenuController test", () => { it("remove show class from content when clicking outside", () => { - const button = document.getElementById("button"); - const content = document.getElementById("content"); - button.click(); expect(content.classList.contains("show")).toBe(true); document.body.click();