From d7d29e3654be2c00c483b18813097dfc7080ea18 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Bellet Date: Mon, 14 Mar 2022 17:25:51 +0100 Subject: [PATCH 01/49] Create a new controller for new products page Controller is constrained by FeatureToggle - new_products_page - and available on '/new_products' Add simple view for new products page and a product Add new products page as a new entry in the menu --- app/controllers/admin/products_controller.rb | 7 +++++++ app/views/admin/products/_product.html.haml | 14 ++++++++++++++ app/views/admin/products/index.html.haml | 13 +++++++++++++ .../spree/admin/shared/_product_sub_menu.html.haml | 2 ++ config/routes/admin.rb | 4 ++++ 5 files changed, 40 insertions(+) create mode 100644 app/controllers/admin/products_controller.rb create mode 100644 app/views/admin/products/_product.html.haml create mode 100644 app/views/admin/products/index.html.haml diff --git a/app/controllers/admin/products_controller.rb b/app/controllers/admin/products_controller.rb new file mode 100644 index 0000000000..123aea7531 --- /dev/null +++ b/app/controllers/admin/products_controller.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +module Admin + class ProductsController < Spree::Admin::BaseController + def index; end + end +end diff --git a/app/views/admin/products/_product.html.haml b/app/views/admin/products/_product.html.haml new file mode 100644 index 0000000000..d7be7958bb --- /dev/null +++ b/app/views/admin/products/_product.html.haml @@ -0,0 +1,14 @@ +%div.product + = if product.images[0] + %div.image + %img{src: product.images[0].attachment.url(:mini, false) } + %div.title + = product.name + %div.unit + = product.unit_value + = product.variant_unit + %div.price + = product.price + +%pre + = product.to_json diff --git a/app/views/admin/products/index.html.haml b/app/views/admin/products/index.html.haml new file mode 100644 index 0000000000..55aea2617f --- /dev/null +++ b/app/views/admin/products/index.html.haml @@ -0,0 +1,13 @@ +- content_for :page_title do + New Products Page + += render partial: 'spree/admin/shared/product_sub_menu' + + +%div + %input.search{type: 'text', placeholder: 'Search', id: 'search_query'} + %button.btn.btn-primary{type: 'submit'} + Filter results + +#new_products + = render partial: "product", collection: @products diff --git a/app/views/spree/admin/shared/_product_sub_menu.html.haml b/app/views/spree/admin/shared/_product_sub_menu.html.haml index 66859dec69..41a3f5b3b7 100644 --- a/app/views/spree/admin/shared/_product_sub_menu.html.haml +++ b/app/views/spree/admin/shared/_product_sub_menu.html.haml @@ -4,3 +4,5 @@ = tab :properties = tab :variant_overrides, url: main_app.admin_inventory_path, match_path: '/inventory' = tab :import, url: main_app.admin_product_import_path, match_path: '/product_import' + - if feature?(:new_products_page, spree_current_user) + = tab :new_products, url: main_app.admin_new_products_path, match_path: '/new_products' diff --git a/config/routes/admin.rb b/config/routes/admin.rb index a7e900dce7..8bd8cf7620 100644 --- a/config/routes/admin.rb +++ b/config/routes/admin.rb @@ -67,6 +67,10 @@ Openfoodnetwork::Application.routes.draw do post '/product_import/save_data', to: 'product_import#save_data', as: 'product_import_save_async' post '/product_import/reset_absent', to: 'product_import#reset_absent_products', as: 'product_import_reset_async' + constraints FeatureToggleConstraint.new(:new_products_page) do + get '/new_products', to: 'products#index' + end + resources :variant_overrides do post :bulk_update, on: :collection post :bulk_reset, on: :collection From 1869536529996128ae4d19db4aa04229ee262d72 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Bellet Date: Tue, 15 Mar 2022 16:39:06 +0100 Subject: [PATCH 02/49] Install view_component_reflex + cable_ready + Add stimulus reflex in the admin section + log stimulusreflex + Create channel and connection + Some logging options + Create application_controller each stimulus reflex controller should inherits from this one --- Gemfile | 1 + Gemfile.lock | 5 +++ app/reflexes/example_reflex.rb | 38 ++++++++++++++++++++ app/views/spree/admin/shared/_head.html.haml | 2 ++ app/webpacker/controllers/index.js | 16 +++++++++ app/webpacker/packs/application.js | 2 ++ config/environments/development.rb | 3 ++ config/initializers/action_cable.rb | 0 8 files changed, 67 insertions(+) create mode 100644 app/reflexes/example_reflex.rb create mode 100644 app/webpacker/controllers/index.js create mode 100644 config/initializers/action_cable.rb diff --git a/Gemfile b/Gemfile index 7295ee9f21..1aca227d53 100644 --- a/Gemfile +++ b/Gemfile @@ -135,6 +135,7 @@ gem 'flipper-active_record' gem 'flipper-ui' gem "view_component" +gem 'view_component_reflex', '3.1.14.pre9' gem 'mini_portile2', '~> 2.8' diff --git a/Gemfile.lock b/Gemfile.lock index b7163f5c8d..ce207eddff 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -714,6 +714,10 @@ GEM activesupport (>= 5.0.0, < 8.0) concurrent-ruby (~> 1.0) method_source (~> 1.0) + view_component_reflex (3.1.14.pre9) + rails (>= 5.2, < 8.0) + stimulus_reflex (>= 3.5.0.pre2) + view_component (>= 2.28.0) view_component_storybook (0.11.1) view_component (>= 2.36) warden (1.2.9) @@ -879,6 +883,7 @@ DEPENDENCIES valid_email2 vcr view_component + view_component_reflex (= 3.1.14.pre9) view_component_storybook web! web-console diff --git a/app/reflexes/example_reflex.rb b/app/reflexes/example_reflex.rb new file mode 100644 index 0000000000..85ba85fd80 --- /dev/null +++ b/app/reflexes/example_reflex.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +class ExampleReflex < ApplicationReflex + # Add Reflex methods in this file. + # + # All Reflex instances include CableReady::Broadcaster and expose the following properties: + # + # - connection - the ActionCable connection + # - channel - the ActionCable channel + # - request - an ActionDispatch::Request proxy for the socket connection + # - session - the ActionDispatch::Session store for the current visitor + # - flash - the ActionDispatch::Flash::FlashHash for the current request + # - url - the URL of the page that triggered the reflex + # - params - parameters from the element's closest form (if any) + # - element - a Hash like object that represents the HTML element that triggered the reflex + # - signed - use a signed Global ID to map dataset attribute to a model + # eg. element.signed[:foo] + # - unsigned - use an unsigned Global ID to map dataset attribute to a model + # eg. element.unsigned[:foo] + # - cable_ready - a special cable_ready that can broadcast to the current visitor + # (no brackets needed) + # - reflex_id - a UUIDv4 that uniquely identies each Reflex + # - tab_id - a UUIDv4 that uniquely identifies the browser tab + # + # Example: + # + # before_reflex do + # # throw :abort # this will prevent the Reflex from continuing + # # learn more about callbacks at https://docs.stimulusreflex.com/rtfm/lifecycle + # end + # + # def example(argument=true) + # # Your logic here... + # # Any declared instance variables will be made available to the Rails controller and view. + # end + # + # Learn more at: https://docs.stimulusreflex.com/rtfm/reflex-classes +end diff --git a/app/views/spree/admin/shared/_head.html.haml b/app/views/spree/admin/shared/_head.html.haml index bad186fb78..254e893bfc 100644 --- a/app/views/spree/admin/shared/_head.html.haml +++ b/app/views/spree/admin/shared/_head.html.haml @@ -3,6 +3,8 @@ = csrf_meta_tags = action_cable_meta_tag += action_cable_meta_tag + %title - if content_for? :html_title = yield :html_title diff --git a/app/webpacker/controllers/index.js b/app/webpacker/controllers/index.js new file mode 100644 index 0000000000..64b87d935e --- /dev/null +++ b/app/webpacker/controllers/index.js @@ -0,0 +1,16 @@ +// Load all the controllers within this directory and all subdirectories. +// Controller files must be named *_controller.js. +import { Application } from "stimulus"; +import { definitionsFromContext } from "stimulus/webpack-helpers"; +import StimulusReflex from "stimulus_reflex"; +import consumer from "../channels/consumer"; +import controller from "../controllers/application_controller"; +import CableReady from "cable_ready"; + +const application = Application.start(); +const context = require.context("controllers", true, /_controller\.js$/); +application.load(definitionsFromContext(context)); +application.consumer = consumer; +StimulusReflex.initialize(application, { controller, isolate: true }); +StimulusReflex.debug = process.env.RAILS_ENV === "development"; +CableReady.initialize({ consumer }); diff --git a/app/webpacker/packs/application.js b/app/webpacker/packs/application.js index ff8a4a56ae..db0abbeeb5 100644 --- a/app/webpacker/packs/application.js +++ b/app/webpacker/packs/application.js @@ -31,3 +31,5 @@ application.consumer = consumer; StimulusReflex.initialize(application, { controller, isolate: true }); StimulusReflex.debug = process.env.RAILS_ENV === "development"; CableReady.initialize({ consumer }); + +import "controllers"; diff --git a/config/environments/development.rb b/config/environments/development.rb index 3d75500f51..8ead22c6a3 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -1,4 +1,5 @@ Openfoodnetwork::Application.configure do + config.action_controller.default_url_options = {host: "localhost", port: 3000} # Settings specified here will take precedence over those in config/application.rb # # PROFILE switches several settings to a more "production-like" value @@ -23,6 +24,8 @@ Openfoodnetwork::Application.configure do } end + config.session_store :cache_store, key: "_sessions_development", compress: true, pool_size: 5, expire_after: 1.year + config.eager_load = false # Show full error reports and disable caching diff --git a/config/initializers/action_cable.rb b/config/initializers/action_cable.rb new file mode 100644 index 0000000000..e69de29bb2 From 7692cebbd35b365b63b3b594b098ec25f7dcc287 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Bellet Date: Thu, 17 Mar 2022 17:30:21 +0100 Subject: [PATCH 03/49] Create Product component Update product_component.html.haml --- app/components/product_component.rb | 11 +++++++++++ .../product_component/product_component.html.haml | 12 ++++++++++++ app/views/admin/products/_product.html.haml | 14 -------------- app/views/admin/products/index.html.haml | 3 ++- 4 files changed, 25 insertions(+), 15 deletions(-) create mode 100644 app/components/product_component.rb create mode 100644 app/components/product_component/product_component.html.haml delete mode 100644 app/views/admin/products/_product.html.haml diff --git a/app/components/product_component.rb b/app/components/product_component.rb new file mode 100644 index 0000000000..a9c6a31496 --- /dev/null +++ b/app/components/product_component.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +class ProductComponent < ViewComponentReflex::Component + def initialize(product:, columns:) + @columns = columns + @image = product.images[0] if product.images.any? + @name = product.name + @unit = "#{product.unit_value} #{product.variant_unit}" + @price = product.price + end +end diff --git a/app/components/product_component/product_component.html.haml b/app/components/product_component/product_component.html.haml new file mode 100644 index 0000000000..93d671c30f --- /dev/null +++ b/app/components/product_component/product_component.html.haml @@ -0,0 +1,12 @@ +%tr + %td.products_column.title + - if @image + .image + = image_tag @image.url(:mini) + = @name + - if @columns.include?(:unit) + %td.products_column.unit + = @unit + - if @columns.include?(:price) + %td.products_column.price + = @price diff --git a/app/views/admin/products/_product.html.haml b/app/views/admin/products/_product.html.haml deleted file mode 100644 index d7be7958bb..0000000000 --- a/app/views/admin/products/_product.html.haml +++ /dev/null @@ -1,14 +0,0 @@ -%div.product - = if product.images[0] - %div.image - %img{src: product.images[0].attachment.url(:mini, false) } - %div.title - = product.name - %div.unit - = product.unit_value - = product.variant_unit - %div.price - = product.price - -%pre - = product.to_json diff --git a/app/views/admin/products/index.html.haml b/app/views/admin/products/index.html.haml index 55aea2617f..bd26726ccb 100644 --- a/app/views/admin/products/index.html.haml +++ b/app/views/admin/products/index.html.haml @@ -10,4 +10,5 @@ Filter results #new_products - = render partial: "product", collection: @products + %table.products_table + = render(ProductComponent.with_collection(@products, columns: [:price, :unit])) From 2b7bccf890addafe1efb3e933edb6b8391a63278 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Bellet Date: Fri, 18 Mar 2022 11:39:24 +0100 Subject: [PATCH 04/49] Create Selector component Add a onClickOutside behavior that close the component if clicked outside Selector component doesn't handle its state but receive props from parent u --- app/components/selector_component.rb | 24 +++++++++++++++++++ .../selector_component.html.haml | 10 ++++++++ app/views/admin/products/index.html.haml | 19 +++++++++------ .../controllers/selector_controller.js | 18 ++++++++++++++ config/locales/en.yml | 4 ++++ 5 files changed, 68 insertions(+), 7 deletions(-) create mode 100644 app/components/selector_component.rb create mode 100644 app/components/selector_component/selector_component.html.haml create mode 100644 app/webpacker/controllers/selector_controller.js diff --git a/app/components/selector_component.rb b/app/components/selector_component.rb new file mode 100644 index 0000000000..e3a16c8b0f --- /dev/null +++ b/app/components/selector_component.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +class SelectorComponent < ViewComponentReflex::Component + def initialize(title:, selected:, items:, data: {}) + @title = title + @items = items.map do |item| + { + id: item, + name: I18n.t("admin.products_page.columns_selector.#{item}"), + selected: selected.include?(item) + } + end + @state = :close + @data = data + end + + def toggle + @state = @state == :open ? :close : :open + end + + def close + @state = :close + end +end diff --git a/app/components/selector_component/selector_component.html.haml b/app/components/selector_component/selector_component.html.haml new file mode 100644 index 0000000000..f132ee7945 --- /dev/null +++ b/app/components/selector_component/selector_component.html.haml @@ -0,0 +1,10 @@ += component_controller do + .selector{ class: ("selector_close" if @state == :close) } + .selector_main + .selector_main_title + = @title + .selector_arrow{data: reflex_data_attributes(:toggle)} + .selector_items + - @items.each do |item| + .selector_item{id: item[:id], class: ("selected" if item[:selected]), data: @data, "data-value": item[:id]} + = item[:name] diff --git a/app/views/admin/products/index.html.haml b/app/views/admin/products/index.html.haml index bd26726ccb..d61e0681b8 100644 --- a/app/views/admin/products/index.html.haml +++ b/app/views/admin/products/index.html.haml @@ -1,14 +1,19 @@ + - content_for :page_title do New Products Page = render partial: 'spree/admin/shared/product_sub_menu' +#products_page + #products_page_form + #filter_results + %input.search{type: 'text', placeholder: 'Search', id: 'search_query'} + %button.btn.btn-primary{type: 'submit'} + Filter results -%div - %input.search{type: 'text', placeholder: 'Search', id: 'search_query'} - %button.btn.btn-primary{type: 'submit'} - Filter results + #columns_selector + = render(SelectorComponent.new(title: "Columns", selected: [:price], items: [:price, :unit])) -#new_products - %table.products_table - = render(ProductComponent.with_collection(@products, columns: [:price, :unit])) + #products_table + %table + = render(ProductComponent.with_collection(@products, columns: [:price])) diff --git a/app/webpacker/controllers/selector_controller.js b/app/webpacker/controllers/selector_controller.js new file mode 100644 index 0000000000..83c11a168c --- /dev/null +++ b/app/webpacker/controllers/selector_controller.js @@ -0,0 +1,18 @@ +import ApplicationController from "./application_controller"; + +export default class extends ApplicationController { + connect() { + super.connect(); + window.addEventListener("click", this.closeOnClickOutside); + } + disconnect() { + super.disconnect(); + window.removeEventListener("click", this.closeOnClickOutside); + } + + closeOnClickOutside = (event) => { + if (!this.element.contains(event.target)) { + this.stimulate("SelectorComponent#close", this.element); + } + }; +} diff --git a/config/locales/en.yml b/config/locales/en.yml index 9c38d6b867..a14f4a0bb4 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -458,6 +458,10 @@ en: # Admin # admin: + products_page: + columns_selector: + unit: Unit + price: Price adjustments: skipped_changing_canceled_order: "You can't change a cancelled order." # Common properties / models From 8758a2701c9103766eb9d547938f51f15275d76e Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Bellet Date: Fri, 18 Mar 2022 13:50:07 +0100 Subject: [PATCH 05/49] Configure CSS for VC components Move CSS for components in the component directory --- .../product_component/product_component.scss | 0 .../selector_component.scss | 65 +++++++++++++++++++ app/webpacker/css/admin/all.scss | 2 + config/webpacker.yml | 3 +- 4 files changed, 69 insertions(+), 1 deletion(-) create mode 100644 app/components/product_component/product_component.scss create mode 100644 app/components/selector_component/selector_component.scss diff --git a/app/components/product_component/product_component.scss b/app/components/product_component/product_component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/app/components/selector_component/selector_component.scss b/app/components/selector_component/selector_component.scss new file mode 100644 index 0000000000..7f2eaa7618 --- /dev/null +++ b/app/components/selector_component/selector_component.scss @@ -0,0 +1,65 @@ +.selector { + .selector_main { + border: 1px solid #ccc; + position: relative; + + .selector_main_title { + padding: 5px 10px; + } + + .selector_arrow { + height: 2em; + width: 10px; + position: absolute; + top: 1px; + right: 10px; + cursor: pointer; + + &:after { + display: inline-block; + content: ""; + width: 0; + height: 0; + border-left: 5px solid transparent; + border-right: 5px solid transparent; + border-top: 5px solid #ccc; + } + } + } + + .selector_items { + border: 1px solid #cCC; + position: absolute; + width: 100%; + margin-top: -1px; + background-color: white; + + .selector_item { + padding-left: 10px; + padding-right: 10px; + border-bottom: 1px solid #CCC; + padding-top: 2px; + padding-bottom: 2px; + + &.selected { + &:after { + content: "✓"; + display: inline-block; + position: absolute; + right: 10px; + top: 50%; + } + } + &:hover { + background-color: #eee; + cursor: pointer; + } + } + } + + &.selector_close { + .selector_items { + display: none; + } + } +} diff --git a/app/webpacker/css/admin/all.scss b/app/webpacker/css/admin/all.scss index 673d934971..3bbe056ffc 100644 --- a/app/webpacker/css/admin/all.scss +++ b/app/webpacker/css/admin/all.scss @@ -120,5 +120,7 @@ @import "components/tom_select"; @import 'app/components/help_modal_component/help_modal_component'; +@import "app/components/product_component/product_component"; +@import "app/components/selector_component/selector_component"; @import "v2/main.scss"; diff --git a/config/webpacker.yml b/config/webpacker.yml index aaf278905b..55250fae6c 100644 --- a/config/webpacker.yml +++ b/config/webpacker.yml @@ -15,7 +15,8 @@ default: &default 'app/webpacker/css', 'app/webpacker/fonts', 'app/webpacker/images', - 'engines/web/app/assets/stylesheets' + 'engines/web/app/assets/stylesheets', + 'app/components' ] # Reload manifest.json on all requests so we reload latest compiled packs From 461d31bef17e49706ca0007be101e9e174b612f7 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Bellet Date: Mon, 21 Mar 2022 16:08:08 +0100 Subject: [PATCH 06/49] Create ProductsTable component that handle the table + all filters --- .../product_component.html.haml | 4 ++-- app/components/products_table_component.rb | 22 +++++++++++++++++++ .../products_table_component.html.haml | 13 +++++++++++ .../products_table_component.scss | 16 ++++++++++++++ app/views/admin/products/index.html.haml | 13 +---------- app/webpacker/css/admin/all.scss | 1 + 6 files changed, 55 insertions(+), 14 deletions(-) create mode 100644 app/components/products_table_component.rb create mode 100644 app/components/products_table_component/products_table_component.html.haml create mode 100644 app/components/products_table_component/products_table_component.scss diff --git a/app/components/product_component/product_component.html.haml b/app/components/product_component/product_component.html.haml index 93d671c30f..e9110779b8 100644 --- a/app/components/product_component/product_component.html.haml +++ b/app/components/product_component/product_component.html.haml @@ -4,9 +4,9 @@ .image = image_tag @image.url(:mini) = @name - - if @columns.include?(:unit) + - if @columns.include?("unit") %td.products_column.unit = @unit - - if @columns.include?(:price) + - if @columns.include?("price") %td.products_column.price = @price diff --git a/app/components/products_table_component.rb b/app/components/products_table_component.rb new file mode 100644 index 0000000000..08450cd505 --- /dev/null +++ b/app/components/products_table_component.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +class ProductsTableComponent < ViewComponentReflex::Component + def initialize(user:) + super + @columns = ["price", "unit"] + @selected = ["price", "unit"] + @user = user + + fetch_products + end + + def toggle_column + column = element.dataset['value'] + @selected = @selected.include?(column) ? @selected - [column] : @selected + [column] + end + private + + def fetch_products + @products = Spree::Product.managed_by(@user).order('name asc').limit(@per_page_selected.first) + end +end diff --git a/app/components/products_table_component/products_table_component.html.haml b/app/components/products_table_component/products_table_component.html.haml new file mode 100644 index 0000000000..7b26a021d4 --- /dev/null +++ b/app/components/products_table_component/products_table_component.html.haml @@ -0,0 +1,13 @@ += component_controller do + #products_page_form + #filter_results + %input.search{type: 'text', placeholder: 'Search', id: 'search_query'} + %button.btn.btn-primary{type: 'submit'} + Filter results + + #columns_selector + = render(SelectorComponent.new(title: "Columns", selected: @selected, items: @columns, data: { key: key, reflex: "click->ProductsTableComponent#toggle_column" })) + + #products_table + %table + = render(ProductComponent.with_collection(@products, columns: @selected)) diff --git a/app/components/products_table_component/products_table_component.scss b/app/components/products_table_component/products_table_component.scss new file mode 100644 index 0000000000..1eb4890bb8 --- /dev/null +++ b/app/components/products_table_component/products_table_component.scss @@ -0,0 +1,16 @@ +#products_page_form { + display: grid; + grid-template-columns: 3fr 1fr; + grid-gap: 10px; + margin-bottom: 10px; + + #filter_results { + + } + #columns_selector { + + } +} + +#products_table { +} diff --git a/app/views/admin/products/index.html.haml b/app/views/admin/products/index.html.haml index d61e0681b8..ffb1dc1fc1 100644 --- a/app/views/admin/products/index.html.haml +++ b/app/views/admin/products/index.html.haml @@ -5,15 +5,4 @@ = render partial: 'spree/admin/shared/product_sub_menu' #products_page - #products_page_form - #filter_results - %input.search{type: 'text', placeholder: 'Search', id: 'search_query'} - %button.btn.btn-primary{type: 'submit'} - Filter results - - #columns_selector - = render(SelectorComponent.new(title: "Columns", selected: [:price], items: [:price, :unit])) - - #products_table - %table - = render(ProductComponent.with_collection(@products, columns: [:price])) + = render(ProductsTableComponent.new(user: spree_current_user)) diff --git a/app/webpacker/css/admin/all.scss b/app/webpacker/css/admin/all.scss index 3bbe056ffc..75d5e4a88e 100644 --- a/app/webpacker/css/admin/all.scss +++ b/app/webpacker/css/admin/all.scss @@ -122,5 +122,6 @@ @import 'app/components/help_modal_component/help_modal_component'; @import "app/components/product_component/product_component"; @import "app/components/selector_component/selector_component"; +@import "app/components/products_table_component/products_table_component"; @import "v2/main.scss"; From 5ea7bea9b89eefda60ddf6415e85254e563d35ed Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Bellet Date: Mon, 21 Mar 2022 20:49:19 +0100 Subject: [PATCH 07/49] Selector now handle {label, value} instead of only string --- app/components/products_table_component.rb | 3 ++- app/components/selector_component.rb | 7 ++++--- .../selector_component/selector_component.html.haml | 4 ++-- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/app/components/products_table_component.rb b/app/components/products_table_component.rb index 08450cd505..d9d52d1e18 100644 --- a/app/components/products_table_component.rb +++ b/app/components/products_table_component.rb @@ -3,7 +3,8 @@ class ProductsTableComponent < ViewComponentReflex::Component def initialize(user:) super - @columns = ["price", "unit"] + @columns = [{ label: I18n.t("admin.products_page.columns_selector.price"), value: "price" }, + { label: I18n.t("admin.products_page.columns_selector.unit"), value: "unit" }] @selected = ["price", "unit"] @user = user diff --git a/app/components/selector_component.rb b/app/components/selector_component.rb index e3a16c8b0f..884f143c2f 100644 --- a/app/components/selector_component.rb +++ b/app/components/selector_component.rb @@ -2,12 +2,13 @@ class SelectorComponent < ViewComponentReflex::Component def initialize(title:, selected:, items:, data: {}) + super @title = title @items = items.map do |item| { - id: item, - name: I18n.t("admin.products_page.columns_selector.#{item}"), - selected: selected.include?(item) + label: item[:label], + value: item[:value], + selected: selected.include?(item[:value]) } end @state = :close diff --git a/app/components/selector_component/selector_component.html.haml b/app/components/selector_component/selector_component.html.haml index f132ee7945..5d77f097ec 100644 --- a/app/components/selector_component/selector_component.html.haml +++ b/app/components/selector_component/selector_component.html.haml @@ -6,5 +6,5 @@ .selector_arrow{data: reflex_data_attributes(:toggle)} .selector_items - @items.each do |item| - .selector_item{id: item[:id], class: ("selected" if item[:selected]), data: @data, "data-value": item[:id]} - = item[:name] + .selector_item{ class: ("selected" if item[:selected]), data: @data, "data-value": item[:value] } + = item[:label] From 8ce3c9f44966599cb1433b9aa112326e70091173 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Bellet Date: Mon, 21 Mar 2022 20:50:10 +0100 Subject: [PATCH 08/49] Introduce a new selector: # of results per page --- app/components/products_table_component.rb | 10 ++++++++++ .../products_table_component.html.haml | 2 ++ .../products_table_component.scss | 2 +- 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/app/components/products_table_component.rb b/app/components/products_table_component.rb index d9d52d1e18..38213395d6 100644 --- a/app/components/products_table_component.rb +++ b/app/components/products_table_component.rb @@ -6,6 +6,9 @@ class ProductsTableComponent < ViewComponentReflex::Component @columns = [{ label: I18n.t("admin.products_page.columns_selector.price"), value: "price" }, { label: I18n.t("admin.products_page.columns_selector.unit"), value: "unit" }] @selected = ["price", "unit"] + @per_page = [{ label: "10", value: 10 }, { label: "25", value: 25 }, { label: "50", value: 50 }, + { label: "100", value: 100 }] + @per_page_selected = [10] @user = user fetch_products @@ -15,6 +18,13 @@ class ProductsTableComponent < ViewComponentReflex::Component column = element.dataset['value'] @selected = @selected.include?(column) ? @selected - [column] : @selected + [column] end + + def toggle_per_page + selected = element.dataset['value'].to_i + @per_page_selected = [selected] if [10, 25, 50, 100].include?(selected) + fetch_products + end + private def fetch_products diff --git a/app/components/products_table_component/products_table_component.html.haml b/app/components/products_table_component/products_table_component.html.haml index 7b26a021d4..278690710c 100644 --- a/app/components/products_table_component/products_table_component.html.haml +++ b/app/components/products_table_component/products_table_component.html.haml @@ -5,6 +5,8 @@ %button.btn.btn-primary{type: 'submit'} Filter results + #per-page_selector + = render(SelectorComponent.new(title: "# per page", selected: @per_page_selected, items: @per_page, data: { key: key, reflex: "click->ProductsTableComponent#toggle_per_page" })) #columns_selector = render(SelectorComponent.new(title: "Columns", selected: @selected, items: @columns, data: { key: key, reflex: "click->ProductsTableComponent#toggle_column" })) diff --git a/app/components/products_table_component/products_table_component.scss b/app/components/products_table_component/products_table_component.scss index 1eb4890bb8..d2e0c356b0 100644 --- a/app/components/products_table_component/products_table_component.scss +++ b/app/components/products_table_component/products_table_component.scss @@ -1,6 +1,6 @@ #products_page_form { display: grid; - grid-template-columns: 3fr 1fr; + grid-template-columns: repeat( auto-fit, minmax(250px, 1fr) ); grid-gap: 10px; margin-bottom: 10px; From 5c5a0c98f1af30e0ab8e609b1e62ebb6994cd767 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Bellet Date: Mon, 21 Mar 2022 20:55:33 +0100 Subject: [PATCH 09/49] Rename selected to columns_selected --- app/components/products_table_component.rb | 8 ++++++-- .../products_table_component.html.haml | 4 ++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/app/components/products_table_component.rb b/app/components/products_table_component.rb index 38213395d6..5c0a95f81f 100644 --- a/app/components/products_table_component.rb +++ b/app/components/products_table_component.rb @@ -5,7 +5,7 @@ class ProductsTableComponent < ViewComponentReflex::Component super @columns = [{ label: I18n.t("admin.products_page.columns_selector.price"), value: "price" }, { label: I18n.t("admin.products_page.columns_selector.unit"), value: "unit" }] - @selected = ["price", "unit"] + @columns_selected = ["price", "unit"] @per_page = [{ label: "10", value: 10 }, { label: "25", value: 25 }, { label: "50", value: 50 }, { label: "100", value: 100 }] @per_page_selected = [10] @@ -16,7 +16,11 @@ class ProductsTableComponent < ViewComponentReflex::Component def toggle_column column = element.dataset['value'] - @selected = @selected.include?(column) ? @selected - [column] : @selected + [column] + @columns_selected = if @columns_selected.include?(column) + @columns_selected - [column] + else + @columns_selected + [column] + end end def toggle_per_page diff --git a/app/components/products_table_component/products_table_component.html.haml b/app/components/products_table_component/products_table_component.html.haml index 278690710c..2b1b83b6da 100644 --- a/app/components/products_table_component/products_table_component.html.haml +++ b/app/components/products_table_component/products_table_component.html.haml @@ -8,8 +8,8 @@ #per-page_selector = render(SelectorComponent.new(title: "# per page", selected: @per_page_selected, items: @per_page, data: { key: key, reflex: "click->ProductsTableComponent#toggle_per_page" })) #columns_selector - = render(SelectorComponent.new(title: "Columns", selected: @selected, items: @columns, data: { key: key, reflex: "click->ProductsTableComponent#toggle_column" })) + = render(SelectorComponent.new(title: "Columns", selected: @columns_selected, items: @columns, data: { key: key, reflex: "click->ProductsTableComponent#toggle_column" })) #products_table %table - = render(ProductComponent.with_collection(@products, columns: @selected)) + = render(ProductComponent.with_collection(@products, columns: @columns_selected)) From d4cfa7b3680abf59037a59aa3c1b326cdcfe7f55 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Bellet Date: Mon, 21 Mar 2022 21:12:24 +0100 Subject: [PATCH 10/49] Add header to the table --- .../products_table_component.html.haml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/app/components/products_table_component/products_table_component.html.haml b/app/components/products_table_component/products_table_component.html.haml index 2b1b83b6da..3a0d5724bf 100644 --- a/app/components/products_table_component/products_table_component.html.haml +++ b/app/components/products_table_component/products_table_component.html.haml @@ -12,4 +12,12 @@ #products_table %table - = render(ProductComponent.with_collection(@products, columns: @columns_selected)) + %thead + %tr + %th + Name + - @columns_selected.each do |column| + %th + = @columns.find{ |c| c[:value] == column }[:label] + %tbody + = render(ProductComponent.with_collection(@products, columns: @columns_selected)) From 1adb22be7124d0f4cbf30df7072ec9f4552142cf Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Bellet Date: Tue, 22 Mar 2022 10:42:36 +0100 Subject: [PATCH 11/49] Create SelectorWithFilter component --- app/components/selector_component.rb | 1 + app/components/super_selector_component.rb | 24 ++++ .../super_selector_component.html.haml | 22 +++ .../super_selector_component.scss | 126 ++++++++++++++++++ .../controllers/super_selector_controller.js | 18 +++ app/webpacker/css/admin/all.scss | 1 + 6 files changed, 192 insertions(+) create mode 100644 app/components/super_selector_component.rb create mode 100644 app/components/super_selector_component/super_selector_component.html.haml create mode 100644 app/components/super_selector_component/super_selector_component.scss create mode 100644 app/webpacker/controllers/super_selector_controller.js diff --git a/app/components/selector_component.rb b/app/components/selector_component.rb index 884f143c2f..4837abb318 100644 --- a/app/components/selector_component.rb +++ b/app/components/selector_component.rb @@ -11,6 +11,7 @@ class SelectorComponent < ViewComponentReflex::Component selected: selected.include?(item[:value]) } end + @selected = selected @state = :close @data = data end diff --git a/app/components/super_selector_component.rb b/app/components/super_selector_component.rb new file mode 100644 index 0000000000..bd78d4c550 --- /dev/null +++ b/app/components/super_selector_component.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +class SuperSelectorComponent < SelectorComponent + def initialize(title:, selected:, items:, data: {}) + super(title: title, selected: selected, items: items, data: data) + @query = "" + @selected_items = items.select { |item| @selected.include?(item[:value]) } + + filter_items + end + + def search + @query = element.value + filter_items + end + + def filter_items + @filtered_items = if @query.empty? + @items + else + @items.select { |item| item[:label].downcase.include?(@query.downcase) } + end + end +end diff --git a/app/components/super_selector_component/super_selector_component.html.haml b/app/components/super_selector_component/super_selector_component.html.haml new file mode 100644 index 0000000000..203dd023d0 --- /dev/null +++ b/app/components/super_selector_component/super_selector_component.html.haml @@ -0,0 +1,22 @@ += component_controller do + .super-selector{ class: ("super-selector-close" if @state == :close) } + .super-selector-main + .super-selector-label + = @title + .super-selector-selected-items + - case @selected_items.length + - when 1, 2 + - @selected_items.each do |item| + .super-selector-selected-item + = item[:label] + - else + .super-selector-selected-item + = "#{@selected_items.length} categories selected" + .super-selector-arrow{data: reflex_data_attributes(:toggle)} + .super-selector-wrapper + .super-selector-search + %input{type: "text", placeholder: "Search", data: reflex_data_attributes("debounced:input->search"), value: @query} + .super-selector-items + - @filtered_items.each do |item| + .super-selector-item{ class: ("selected" if item[:selected]), data: @data, "data-value": item[:value] } + = item[:label] diff --git a/app/components/super_selector_component/super_selector_component.scss b/app/components/super_selector_component/super_selector_component.scss new file mode 100644 index 0000000000..81944f5875 --- /dev/null +++ b/app/components/super_selector_component/super_selector_component.scss @@ -0,0 +1,126 @@ + +.super-selector { + margin-top: 5px; + position: relative; + + .super-selector-main { + border: 1px solid #d2d2d2; + height: 3em; + position: relative; + + .super-selector-label { + padding-left: 5px; + padding-right: 5px; + margin-left: 10px; + position: absolute; + top: -1em; + background-color: white; + } + + .super-selector-arrow { + position: absolute; + right: 0px; + height: 3em; + width: 1.5em; + top: -1px; + cursor: pointer; + + &:after { + content: ""; + position: absolute; + top: 50%; + right: 5px; + margin-top: -5px; + width: 0; + height: 0; + border-left: 5px solid transparent; + border-right: 5px solid transparent; + border-top: 5px solid #d2d2d2; + } + } + } + + .super-selector-selected-items { + margin-left: 5px; + margin-right: 2em; + margin-top: 7px; + display: flex; + + .super-selector-selected-item { + border: 1px solid #cee1f4; + background-color: #eff5fc; + border-radius: 20px; + height: 2em; + padding-left: 10px; + padding-right: 10px; + display: inline-block; + margin-right: 5px; + padding-top: 2px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + } + + .super-selector-wrapper { + position: absolute; + left: 0px; + right: 0px; + z-index: 1; + background-color: white; + margin-top: -1px; + border: 1px solid #d2d2d2; + + .super-selector-search { + border-bottom: 1px solid #d2d2d2; + padding: 10px 5px; + + input { + border: 1px solid #d2d2d2; + box-sizing: border-box; + border-radius: 4px; + width: 100%; + } + } + + .super-selector-items { + overflow-y: auto; + min-height: 6em; + + .super-selector-item { + padding-left: 10px; + padding-right: 10px; + border-bottom: 1px solid #d2d2d2; + position: relative; + height: 3em; + line-height: 3em; + + &:hover { + background-color: #eee; + cursor: pointer; + } + + &:last-child { + border-bottom: none; + } + + &.selected { + &:after { + content: "✓"; + display: inline-block; + position: absolute; + right: 10px; + } + } + } + } + } +} + +.super-selector-close { + &.super-selector { + .super-selector-wrapper { + display: none; + } + } +} diff --git a/app/webpacker/controllers/super_selector_controller.js b/app/webpacker/controllers/super_selector_controller.js new file mode 100644 index 0000000000..918862e1f6 --- /dev/null +++ b/app/webpacker/controllers/super_selector_controller.js @@ -0,0 +1,18 @@ +import ApplicationController from "./application_controller"; + +export default class extends ApplicationController { + connect() { + super.connect(); + window.addEventListener("click", this.handleClick); + } + disconnect() { + super.disconnect(); + window.removeEventListener("click", this.handleClick); + } + + handleClick = (event) => { + if (!this.element.contains(event.target)) { + this.stimulate("SuperSelectorComponent#close", this.element); + } + }; +} diff --git a/app/webpacker/css/admin/all.scss b/app/webpacker/css/admin/all.scss index 75d5e4a88e..bbc4b6ee73 100644 --- a/app/webpacker/css/admin/all.scss +++ b/app/webpacker/css/admin/all.scss @@ -123,5 +123,6 @@ @import "app/components/product_component/product_component"; @import "app/components/selector_component/selector_component"; @import "app/components/products_table_component/products_table_component"; +@import "app/components/super_selector_component/super_selector_component"; @import "v2/main.scss"; From 4fa88b9c180863d133e56da5e47f69f374d4515c Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Bellet Date: Tue, 22 Mar 2022 15:57:32 +0100 Subject: [PATCH 12/49] Fatorize CSS between Selector and SuperSelector components --- .../selector_component.html.haml | 17 ++-- .../selector_component.scss | 91 ++++++++++++------- .../super_selector_component.html.haml | 12 +-- .../super_selector_component.scss | 79 +--------------- 4 files changed, 73 insertions(+), 126 deletions(-) diff --git a/app/components/selector_component/selector_component.html.haml b/app/components/selector_component/selector_component.html.haml index 5d77f097ec..34a09a3b5f 100644 --- a/app/components/selector_component/selector_component.html.haml +++ b/app/components/selector_component/selector_component.html.haml @@ -1,10 +1,11 @@ = component_controller do - .selector{ class: ("selector_close" if @state == :close) } - .selector_main - .selector_main_title + .selector{ class: ("selector-close" if @state == :close) } + .selector-main + .selector-main-title = @title - .selector_arrow{data: reflex_data_attributes(:toggle)} - .selector_items - - @items.each do |item| - .selector_item{ class: ("selected" if item[:selected]), data: @data, "data-value": item[:value] } - = item[:label] + .selector-arrow{data: reflex_data_attributes(:toggle)} + .selector-wrapper + .selector-items + - @items.each do |item| + .selector-item{ class: ("selected" if item[:selected]), data: @data, "data-value": item[:value] } + = item[:label] diff --git a/app/components/selector_component/selector_component.scss b/app/components/selector_component/selector_component.scss index 7f2eaa7618..7660e6ceb4 100644 --- a/app/components/selector_component/selector_component.scss +++ b/app/components/selector_component/selector_component.scss @@ -1,64 +1,85 @@ .selector { - .selector_main { - border: 1px solid #ccc; + position: relative; + + .selector-main { + border: 1px solid #d2d2d2; + height: 3em; position: relative; - .selector_main_title { - padding: 5px 10px; + .selector-main-title { + line-height: 3em; + padding-left: 10px; + padding-right: 10px; } - .selector_arrow { - height: 2em; - width: 10px; + .selector-arrow { position: absolute; - top: 1px; - right: 10px; + right: 0px; + height: 3em; + width: 1.5em; + top: -1px; cursor: pointer; - &:after { - display: inline-block; + &:after { content: ""; + position: absolute; + top: 50%; + right: 5px; + margin-top: -5px; width: 0; height: 0; border-left: 5px solid transparent; border-right: 5px solid transparent; - border-top: 5px solid #ccc; + border-top: 5px solid #d2d2d2; } } } - .selector_items { - border: 1px solid #cCC; + .selector-wrapper { position: absolute; - width: 100%; - margin-top: -1px; + left: 0px; + right: 0px; + z-index: 1; background-color: white; + margin-top: -1px; + border: 1px solid #d2d2d2; - .selector_item { - padding-left: 10px; - padding-right: 10px; - border-bottom: 1px solid #CCC; - padding-top: 2px; - padding-bottom: 2px; + .selector-items { + overflow-y: auto; + min-height: 6em; + + .selector-item { + padding-left: 10px; + padding-right: 10px; + border-bottom: 1px solid #d2d2d2; + position: relative; + height: 3em; + line-height: 3em; - &.selected { - &:after { - content: "✓"; - display: inline-block; - position: absolute; - right: 10px; - top: 50%; + &:hover { + background-color: #eee; + cursor: pointer; + } + + &:last-child { + border-bottom: none; + } + + &.selected { + &:after { + content: "✓"; + display: inline-block; + position: absolute; + right: 10px; + } } } - &:hover { - background-color: #eee; - cursor: pointer; - } } } + - &.selector_close { - .selector_items { + &.selector-close { + .selector-wrapper { display: none; } } diff --git a/app/components/super_selector_component/super_selector_component.html.haml b/app/components/super_selector_component/super_selector_component.html.haml index 203dd023d0..4563fbad05 100644 --- a/app/components/super_selector_component/super_selector_component.html.haml +++ b/app/components/super_selector_component/super_selector_component.html.haml @@ -1,6 +1,6 @@ = component_controller do - .super-selector{ class: ("super-selector-close" if @state == :close) } - .super-selector-main + .super-selector.selector{ class: ("selector-close" if @state == :close) } + .selector-main .super-selector-label = @title .super-selector-selected-items @@ -12,11 +12,11 @@ - else .super-selector-selected-item = "#{@selected_items.length} categories selected" - .super-selector-arrow{data: reflex_data_attributes(:toggle)} - .super-selector-wrapper + .selector-arrow{data: reflex_data_attributes(:toggle)} + .selector-wrapper .super-selector-search %input{type: "text", placeholder: "Search", data: reflex_data_attributes("debounced:input->search"), value: @query} - .super-selector-items + .selector-items - @filtered_items.each do |item| - .super-selector-item{ class: ("selected" if item[:selected]), data: @data, "data-value": item[:value] } + .selector-item{ class: ("selected" if item[:selected]), data: @data, "data-value": item[:value] } = item[:label] diff --git a/app/components/super_selector_component/super_selector_component.scss b/app/components/super_selector_component/super_selector_component.scss index 81944f5875..8e5c2e31fe 100644 --- a/app/components/super_selector_component/super_selector_component.scss +++ b/app/components/super_selector_component/super_selector_component.scss @@ -1,13 +1,8 @@ .super-selector { - margin-top: 5px; position: relative; - .super-selector-main { - border: 1px solid #d2d2d2; - height: 3em; - position: relative; - + .selector-main { .super-selector-label { padding-left: 5px; padding-right: 5px; @@ -16,28 +11,6 @@ top: -1em; background-color: white; } - - .super-selector-arrow { - position: absolute; - right: 0px; - height: 3em; - width: 1.5em; - top: -1px; - cursor: pointer; - - &:after { - content: ""; - position: absolute; - top: 50%; - right: 5px; - margin-top: -5px; - width: 0; - height: 0; - border-left: 5px solid transparent; - border-right: 5px solid transparent; - border-top: 5px solid #d2d2d2; - } - } } .super-selector-selected-items { @@ -62,15 +35,7 @@ } } - .super-selector-wrapper { - position: absolute; - left: 0px; - right: 0px; - z-index: 1; - background-color: white; - margin-top: -1px; - border: 1px solid #d2d2d2; - + .selector-wrapper { .super-selector-search { border-bottom: 1px solid #d2d2d2; padding: 10px 5px; @@ -82,45 +47,5 @@ width: 100%; } } - - .super-selector-items { - overflow-y: auto; - min-height: 6em; - - .super-selector-item { - padding-left: 10px; - padding-right: 10px; - border-bottom: 1px solid #d2d2d2; - position: relative; - height: 3em; - line-height: 3em; - - &:hover { - background-color: #eee; - cursor: pointer; - } - - &:last-child { - border-bottom: none; - } - - &.selected { - &:after { - content: "✓"; - display: inline-block; - position: absolute; - right: 10px; - } - } - } - } - } -} - -.super-selector-close { - &.super-selector { - .super-selector-wrapper { - display: none; - } } } From 224daf25911dee8c7fd3dba7bd85bfbece25592a Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Bellet Date: Tue, 22 Mar 2022 16:07:48 +0100 Subject: [PATCH 13/49] Factorize js controller between Selectore and SuperSelector components + add a computeItemsHeight on afterReflex callback --- .../controllers/selector_controller.js | 12 +++++++++++- .../controllers/super_selector_controller.js | 19 +++---------------- 2 files changed, 14 insertions(+), 17 deletions(-) diff --git a/app/webpacker/controllers/selector_controller.js b/app/webpacker/controllers/selector_controller.js index 83c11a168c..c5311ba583 100644 --- a/app/webpacker/controllers/selector_controller.js +++ b/app/webpacker/controllers/selector_controller.js @@ -1,10 +1,14 @@ import ApplicationController from "./application_controller"; export default class extends ApplicationController { + reflex = "SelectorComponent"; + connect() { super.connect(); window.addEventListener("click", this.closeOnClickOutside); + this.computeItemsHeight(); } + disconnect() { super.disconnect(); window.removeEventListener("click", this.closeOnClickOutside); @@ -12,7 +16,13 @@ export default class extends ApplicationController { closeOnClickOutside = (event) => { if (!this.element.contains(event.target)) { - this.stimulate("SelectorComponent#close", this.element); + this.stimulate(`${this.reflex}#close`, this.element); } }; + + computeItemsHeight = () => { + const items = this.element.querySelector(".selector-items"); + const rect = items.getBoundingClientRect(); + items.style.maxHeight = `calc(100vh - ${rect.height}px)`; + }; } diff --git a/app/webpacker/controllers/super_selector_controller.js b/app/webpacker/controllers/super_selector_controller.js index 918862e1f6..445b0fd16d 100644 --- a/app/webpacker/controllers/super_selector_controller.js +++ b/app/webpacker/controllers/super_selector_controller.js @@ -1,18 +1,5 @@ -import ApplicationController from "./application_controller"; +import SelectorController from "./selector_controller"; -export default class extends ApplicationController { - connect() { - super.connect(); - window.addEventListener("click", this.handleClick); - } - disconnect() { - super.disconnect(); - window.removeEventListener("click", this.handleClick); - } - - handleClick = (event) => { - if (!this.element.contains(event.target)) { - this.stimulate("SuperSelectorComponent#close", this.element); - } - }; +export default class extends SelectorController { + reflex = "SuperSelectorComponent"; } From 8adcdf14a72dedb035eec866e5d516cf22028cd4 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Bellet Date: Wed, 23 Mar 2022 10:06:36 +0100 Subject: [PATCH 14/49] Include search by category and producer + use ransack (not sur this one is relevant) + fetch_products in the `before_render` lifecycle method --- app/components/products_table_component.rb | 82 ++++++++++++++++++- .../products_table_component.html.haml | 4 + 2 files changed, 84 insertions(+), 2 deletions(-) diff --git a/app/components/products_table_component.rb b/app/components/products_table_component.rb index 5c0a95f81f..d01101cd88 100644 --- a/app/components/products_table_component.rb +++ b/app/components/products_table_component.rb @@ -1,16 +1,29 @@ # frozen_string_literal: true class ProductsTableComponent < ViewComponentReflex::Component + include Pagy::Backend + def initialize(user:) super + @user = user @columns = [{ label: I18n.t("admin.products_page.columns_selector.price"), value: "price" }, { label: I18n.t("admin.products_page.columns_selector.unit"), value: "unit" }] @columns_selected = ["price", "unit"] @per_page = [{ label: "10", value: 10 }, { label: "25", value: 25 }, { label: "50", value: 50 }, { label: "100", value: 100 }] @per_page_selected = [10] - @user = user + @categories = [{ label: "All", value: "all" }] + + Spree::Taxon.order(:name) + .map { |taxon| { label: taxon.name, value: taxon.id.to_s } } + @categories_selected = ["all"] + @producers = [{ label: "All", value: "all" }] + + OpenFoodNetwork::Permissions.new(@user) + .managed_product_enterprises.is_primary_producer.by_name + .map { |producer| { label: producer.name, value: producer.id.to_s } } + @producers_selected = ["all"] + end + def before_render fetch_products end @@ -29,9 +42,74 @@ class ProductsTableComponent < ViewComponentReflex::Component fetch_products end + def toggle_category + category_clicked = element.dataset['value'] + @categories_selected = toggle_super_selector(category_clicked, @categories_selected) + + fetch_products + end + + def toggle_producer + producer_clicked = element.dataset['value'] + @producers_selected = toggle_super_selector(producer_clicked, @producers_selected) + + fetch_products + end + private + def toggle_super_selector(clicked, selected) + selected = if selected.include?(clicked) + selected - [clicked] + else + selected + [clicked] + end + + if clicked == "all" || selected.empty? + selected = ["all"] + elsif selected.include?("all") && selected.length > 1 + selected -= ["all"] + end + selected + end + def fetch_products - @products = Spree::Product.managed_by(@user).order('name asc').limit(@per_page_selected.first) + product_query = OpenFoodNetwork::Permissions.new(@user).editable_products.merge(product_scope) + @products = product_query.ransack(ransack_query).result + @pagy, @products = pagy(@products, items: @per_page_selected.first) + end + + def product_scope + scope = if @user.has_spree_role?("admin") || @user.enterprises.present? + Spree::Product + else + Spree::Product.active + end + + scope.includes(product_query_includes) + end + + def ransack_query + query = { s: 'created_at desc' } + + query = if @producers_selected.include?("all") + query.merge({ supplier_id_eq: "" }) + else + query.merge({ supplier_id_in: @producers_selected }) + end + + if @categories_selected.include?("all") + query.merge({ primary_taxon_id_eq: "" }) + else + query.merge({ primary_taxon_id_in: @categories_selected }) + end + end + + def product_query_includes + [ + master: [:images], + variants: [:default_price, :stock_locations, :stock_items, :variant_overrides, + { option_values: :option_type }] + ] end end diff --git a/app/components/products_table_component/products_table_component.html.haml b/app/components/products_table_component/products_table_component.html.haml index 3a0d5724bf..a39e28bf6d 100644 --- a/app/components/products_table_component/products_table_component.html.haml +++ b/app/components/products_table_component/products_table_component.html.haml @@ -5,6 +5,10 @@ %button.btn.btn-primary{type: 'submit'} Filter results + #categories_selector + = render(SuperSelectorComponent.new(title: "Categories", selected: @categories_selected, items: @categories, data: { key: key, reflex: "click->ProductsTableComponent#toggle_category" })) + #producers_selector + = render(SuperSelectorComponent.new(title: "Producers", selected: @producers_selected, items: @producers, data: { key: key, reflex: "click->ProductsTableComponent#toggle_producer" })) #per-page_selector = render(SelectorComponent.new(title: "# per page", selected: @per_page_selected, items: @per_page, data: { key: key, reflex: "click->ProductsTableComponent#toggle_per_page" })) #columns_selector From 7716d2734e1bf50043db4363f532e6fa97e7d121 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Bellet Date: Wed, 23 Mar 2022 10:08:25 +0100 Subject: [PATCH 15/49] Title of the items per page selector is clearer --- .../products_table_component/products_table_component.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/components/products_table_component/products_table_component.html.haml b/app/components/products_table_component/products_table_component.html.haml index a39e28bf6d..08038ac73a 100644 --- a/app/components/products_table_component/products_table_component.html.haml +++ b/app/components/products_table_component/products_table_component.html.haml @@ -10,7 +10,7 @@ #producers_selector = render(SuperSelectorComponent.new(title: "Producers", selected: @producers_selected, items: @producers, data: { key: key, reflex: "click->ProductsTableComponent#toggle_producer" })) #per-page_selector - = render(SelectorComponent.new(title: "# per page", selected: @per_page_selected, items: @per_page, data: { key: key, reflex: "click->ProductsTableComponent#toggle_per_page" })) + = render(SelectorComponent.new(title: "#{@per_page_selected[0]} items per page", selected: @per_page_selected, items: @per_page, data: { key: key, reflex: "click->ProductsTableComponent#toggle_per_page" })) #columns_selector = render(SelectorComponent.new(title: "Columns", selected: @columns_selected, items: @columns, data: { key: key, reflex: "click->ProductsTableComponent#toggle_column" })) From 069b314ae7bff15b5c305e7d6dd5c2ceeb4eed95 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Bellet Date: Wed, 23 Mar 2022 10:20:52 +0100 Subject: [PATCH 16/49] Add producer and category to a product in the table + refactor and automatize optional column rendered --- app/components/product_component.rb | 28 +++++++++++++++---- .../product_component.html.haml | 9 ++---- app/components/products_table_component.rb | 6 +++- config/locales/en.yml | 2 ++ 4 files changed, 33 insertions(+), 12 deletions(-) diff --git a/app/components/product_component.rb b/app/components/product_component.rb index a9c6a31496..22dfc885bf 100644 --- a/app/components/product_component.rb +++ b/app/components/product_component.rb @@ -2,10 +2,28 @@ class ProductComponent < ViewComponentReflex::Component def initialize(product:, columns:) - @columns = columns - @image = product.images[0] if product.images.any? - @name = product.name - @unit = "#{product.unit_value} #{product.variant_unit}" - @price = product.price + super + @product = product + @image = @product.images[0] if product.images.any? + @name = @product.name + @columns = columns.map { |c| + { + id: c, + value: column_value(c) + } + } + end + + def column_value(column) + case column + when 'price' + @product.price + when 'unit' + "#{@product.unit_value} #{@product.variant_unit}" + when 'producer' + @product.supplier.name + when 'category' + @product.taxons.map(&:name).join(', ') + end end end diff --git a/app/components/product_component/product_component.html.haml b/app/components/product_component/product_component.html.haml index e9110779b8..8d36d1bc2e 100644 --- a/app/components/product_component/product_component.html.haml +++ b/app/components/product_component/product_component.html.haml @@ -4,9 +4,6 @@ .image = image_tag @image.url(:mini) = @name - - if @columns.include?("unit") - %td.products_column.unit - = @unit - - if @columns.include?("price") - %td.products_column.price - = @price + - @columns.each do |column| + %td.products_column{class: column[:id]} + = column[:value] diff --git a/app/components/products_table_component.rb b/app/components/products_table_component.rb index d01101cd88..4aca2c791f 100644 --- a/app/components/products_table_component.rb +++ b/app/components/products_table_component.rb @@ -7,7 +7,11 @@ class ProductsTableComponent < ViewComponentReflex::Component super @user = user @columns = [{ label: I18n.t("admin.products_page.columns_selector.price"), value: "price" }, - { label: I18n.t("admin.products_page.columns_selector.unit"), value: "unit" }] + { label: I18n.t("admin.products_page.columns_selector.unit"), value: "unit" }, + { label: I18n.t("admin.products_page.columns_selector.producer"), + value: "producer" }, + { label: I18n.t("admin.products_page.columns_selector.category"), + value: "category" }].sort { |a, b| a[:label] <=> b[:label] } @columns_selected = ["price", "unit"] @per_page = [{ label: "10", value: 10 }, { label: "25", value: 25 }, { label: "50", value: 50 }, { label: "100", value: 100 }] diff --git a/config/locales/en.yml b/config/locales/en.yml index a14f4a0bb4..d37024ff3c 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -462,6 +462,8 @@ en: columns_selector: unit: Unit price: Price + producer: Producer + category: Category adjustments: skipped_changing_canceled_order: "You can't change a cancelled order." # Common properties / models From c7197364d13c649840df49b9519b9701024de2cf Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Bellet Date: Wed, 23 Mar 2022 16:40:21 +0100 Subject: [PATCH 17/49] Add a pagination component that handle pagy --- app/components/pagination_component.rb | 15 ++++ .../pagination_component.html.haml | 16 +++++ .../pagination_component.scss | 69 +++++++++++++++++++ app/webpacker/css/admin/all.scss | 1 + 4 files changed, 101 insertions(+) create mode 100644 app/components/pagination_component.rb create mode 100644 app/components/pagination_component/pagination_component.html.haml create mode 100644 app/components/pagination_component/pagination_component.scss diff --git a/app/components/pagination_component.rb b/app/components/pagination_component.rb new file mode 100644 index 0000000000..af3664cbc0 --- /dev/null +++ b/app/components/pagination_component.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +class PaginationComponent < ViewComponentReflex::Component + def initialize(pagy:, data:) + super + @count = pagy.count + @page = pagy.page + @per_page = pagy.items + @pages = pagy.pages + @next = pagy.next + @prev = pagy.prev + @data = data + @series = pagy.series + end +end diff --git a/app/components/pagination_component/pagination_component.html.haml b/app/components/pagination_component/pagination_component.html.haml new file mode 100644 index 0000000000..3f3620b2da --- /dev/null +++ b/app/components/pagination_component/pagination_component.html.haml @@ -0,0 +1,16 @@ += component_controller do + %nav{"aria-label": "pagination"} + .pagination + .pagination-prev{data: @prev.nil? ? nil : @data, "data-page": @prev, class: "#{'inactive' if @prev.nil?}"} + Previous + .pagination-pages + - @series.each do |page| + - if page == :gap + .pagination-gap + … + - else + .pagination-page{data: @data, "data-page": page, class: "#{'active' if page.to_i == @page}"} + = page + .pagination-next{data: @next.nil? ? nil : @data, "data-page": @next, class: "#{'inactive' if @next.nil?}"} + Next + diff --git a/app/components/pagination_component/pagination_component.scss b/app/components/pagination_component/pagination_component.scss new file mode 100644 index 0000000000..d862ede35f --- /dev/null +++ b/app/components/pagination_component/pagination_component.scss @@ -0,0 +1,69 @@ +nav { + .pagination { + display: flex; + justify-content: space-between; + align-items: flex-start; + font-size: 14px; + + .pagination-prev, .pagination-next { + cursor: pointer; + + &:after, &:before { + font-size: 2em; + position: relative; + top: 3px; + } + + &.inactive { + cursor: default; + color: #999; + } + } + + .pagination-prev { + margin-left: 10px; + + &:before { + content: "‹"; + margin-left: 10px; + margin-right: 10px; + } + } + + .pagination-next { + margin-right: 10px; + + &:after { + content: "›"; + margin-left: 10px; + margin-right: 10px; + } + } + + + .pagination-pages { + display: flex; + align-items: flex-end; + + .pagination-gap, .pagination-page { + padding: 0 0.5rem; + margin-left: 10px; + margin-right: 10px; + } + + .pagination-gap { + color: #999; + } + + .pagination-page { + color: #6788A2; + cursor: pointer; + &.active { + border-top: 3px solid #5498DA; + color: #5498DA; + cursor: default; + } + } + } + } +} diff --git a/app/webpacker/css/admin/all.scss b/app/webpacker/css/admin/all.scss index bbc4b6ee73..61cfbd035c 100644 --- a/app/webpacker/css/admin/all.scss +++ b/app/webpacker/css/admin/all.scss @@ -124,5 +124,6 @@ @import "app/components/selector_component/selector_component"; @import "app/components/products_table_component/products_table_component"; @import "app/components/super_selector_component/super_selector_component"; +@import "app/components/pagination_component/pagination_component"; @import "v2/main.scss"; From c23d4f63edd19a18afbdc08c03eac6e3f84c97d8 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Bellet Date: Wed, 23 Mar 2022 16:40:47 +0100 Subject: [PATCH 18/49] Add pagination to ProductsTable component --- app/components/products_table_component.rb | 10 +++++++++- .../products_table_component.html.haml | 2 ++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/app/components/products_table_component.rb b/app/components/products_table_component.rb index 4aca2c791f..8a7fcf94a4 100644 --- a/app/components/products_table_component.rb +++ b/app/components/products_table_component.rb @@ -25,6 +25,7 @@ class ProductsTableComponent < ViewComponentReflex::Component .managed_product_enterprises.is_primary_producer.by_name .map { |producer| { label: producer.name, value: producer.id.to_s } } @producers_selected = ["all"] + @page = 1 end def before_render @@ -60,6 +61,13 @@ class ProductsTableComponent < ViewComponentReflex::Component fetch_products end + def change_page + page = element.dataset['page'].to_i + @page = page if page > 0 + + fetch_products + end + private def toggle_super_selector(clicked, selected) @@ -80,7 +88,7 @@ class ProductsTableComponent < ViewComponentReflex::Component def fetch_products product_query = OpenFoodNetwork::Permissions.new(@user).editable_products.merge(product_scope) @products = product_query.ransack(ransack_query).result - @pagy, @products = pagy(@products, items: @per_page_selected.first) + @pagy, @products = pagy(@products, items: @per_page_selected.first, page: @page) end def product_scope diff --git a/app/components/products_table_component/products_table_component.html.haml b/app/components/products_table_component/products_table_component.html.haml index 08038ac73a..70fcf72a18 100644 --- a/app/components/products_table_component/products_table_component.html.haml +++ b/app/components/products_table_component/products_table_component.html.haml @@ -25,3 +25,5 @@ = @columns.find{ |c| c[:value] == column }[:label] %tbody = render(ProductComponent.with_collection(@products, columns: @columns_selected)) + #pagination + = render(PaginationComponent.new(pagy: @pagy, data: {key: key, reflex: "click->ProductsTableComponent#change_page" })) From cb24efbc4ae540eca832f2f119e5b7218c016e36 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Bellet Date: Wed, 23 Mar 2022 16:40:57 +0100 Subject: [PATCH 19/49] Some styling improvments --- .../products_table_component.scss | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/app/components/products_table_component/products_table_component.scss b/app/components/products_table_component/products_table_component.scss index d2e0c356b0..aa0434aa9e 100644 --- a/app/components/products_table_component/products_table_component.scss +++ b/app/components/products_table_component/products_table_component.scss @@ -13,4 +13,15 @@ } #products_table { + box-shadow: 0 10px 10px -1px rgb(0 0 0 / 10%) +} + +#pagination { + position: relative; + top: -15px; + + nav, .pagination { + margin-top: 0; + padding-top: 0; + } } From d37cd09c8480ec2fc45a0f6794cb42a3821b388a Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Bellet Date: Wed, 23 Mar 2022 16:43:48 +0100 Subject: [PATCH 20/49] Use the reflex_data_attributes helper method --- .../products_table_component.html.haml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/components/products_table_component/products_table_component.html.haml b/app/components/products_table_component/products_table_component.html.haml index 70fcf72a18..adc9055fa4 100644 --- a/app/components/products_table_component/products_table_component.html.haml +++ b/app/components/products_table_component/products_table_component.html.haml @@ -6,13 +6,13 @@ Filter results #categories_selector - = render(SuperSelectorComponent.new(title: "Categories", selected: @categories_selected, items: @categories, data: { key: key, reflex: "click->ProductsTableComponent#toggle_category" })) + = render(SuperSelectorComponent.new(title: "Categories", selected: @categories_selected, items: @categories, data: reflex_data_attributes(:toggle_category))) #producers_selector - = render(SuperSelectorComponent.new(title: "Producers", selected: @producers_selected, items: @producers, data: { key: key, reflex: "click->ProductsTableComponent#toggle_producer" })) + = render(SuperSelectorComponent.new(title: "Producers", selected: @producers_selected, items: @producers, data: reflex_data_attributes(:toggle_producer))) #per-page_selector - = render(SelectorComponent.new(title: "#{@per_page_selected[0]} items per page", selected: @per_page_selected, items: @per_page, data: { key: key, reflex: "click->ProductsTableComponent#toggle_per_page" })) + = render(SelectorComponent.new(title: "#{@per_page_selected[0]} items per page", selected: @per_page_selected, items: @per_page, data: reflex_data_attributes(:toggle_per_page))) #columns_selector - = render(SelectorComponent.new(title: "Columns", selected: @columns_selected, items: @columns, data: { key: key, reflex: "click->ProductsTableComponent#toggle_column" })) + = render(SelectorComponent.new(title: "Columns", selected: @columns_selected, items: @columns, data: reflex_data_attributes(:toggle_column))) #products_table %table @@ -26,4 +26,4 @@ %tbody = render(ProductComponent.with_collection(@products, columns: @columns_selected)) #pagination - = render(PaginationComponent.new(pagy: @pagy, data: {key: key, reflex: "click->ProductsTableComponent#change_page" })) + = render(PaginationComponent.new(pagy: @pagy, data: reflex_data_attributes(:change_page))) From 1de7fb6fe8500969a677b66cb703e9ed95cb8c17 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Bellet Date: Wed, 23 Mar 2022 22:10:16 +0100 Subject: [PATCH 21/49] Create TableHeader component --- app/components/table_header_component.rb | 10 ++++++++ .../table_header_component.html.haml | 7 ++++++ .../table_header_component.scss | 23 +++++++++++++++++++ app/webpacker/css/admin/all.scss | 1 + 4 files changed, 41 insertions(+) create mode 100644 app/components/table_header_component.rb create mode 100644 app/components/table_header_component/table_header_component.html.haml create mode 100644 app/components/table_header_component/table_header_component.scss diff --git a/app/components/table_header_component.rb b/app/components/table_header_component.rb new file mode 100644 index 0000000000..d369e8e0dc --- /dev/null +++ b/app/components/table_header_component.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +class TableHeaderComponent < ViewComponentReflex::Component + def initialize(columns:, sort:, data: {}) + super + @columns = columns + @sort = sort + @data = data + end +end diff --git a/app/components/table_header_component/table_header_component.html.haml b/app/components/table_header_component/table_header_component.html.haml new file mode 100644 index 0000000000..bcc2fd3c4d --- /dev/null +++ b/app/components/table_header_component/table_header_component.html.haml @@ -0,0 +1,7 @@ + += component_controller do + %thead.table-header + %tr + - @columns.each do |column| + %th{class: (column[:sortable] ? "th-sortable " : "" ) + (@sort[:column] == column[:value] ? " th-sorted-#{@sort[:direction]}" : ""), data: (@data if column[:sortable] == true), "data-sort-value": column[:value], "data-sort-direction": @sort[:direction]} + = column[:label] diff --git a/app/components/table_header_component/table_header_component.scss b/app/components/table_header_component/table_header_component.scss new file mode 100644 index 0000000000..b138d56668 --- /dev/null +++ b/app/components/table_header_component/table_header_component.scss @@ -0,0 +1,23 @@ +thead.table-header { + th { + &.th-sortable { + cursor: pointer; + } + &.th-sorted-asc, &.th-sorted-desc { + &:after { + display: inline-block; + padding-left: 10px; + } + } + &.th-sorted-asc { + &:after { + content: "⇧"; + } + } + &.th-sorted-desc { + &:after { + content: "⇩"; + } + } + } +} diff --git a/app/webpacker/css/admin/all.scss b/app/webpacker/css/admin/all.scss index 61cfbd035c..b4a35aadf9 100644 --- a/app/webpacker/css/admin/all.scss +++ b/app/webpacker/css/admin/all.scss @@ -125,5 +125,6 @@ @import "app/components/products_table_component/products_table_component"; @import "app/components/super_selector_component/super_selector_component"; @import "app/components/pagination_component/pagination_component"; +@import "app/components/table_header_component/table_header_component"; @import "v2/main.scss"; From d75c62a62140c83d5948688be5541553161da10e Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Bellet Date: Wed, 23 Mar 2022 22:11:15 +0100 Subject: [PATCH 22/49] Add TableHeader component and better columns implementation --- app/components/product_component.rb | 4 +-- .../product_component.html.haml | 16 +++++---- app/components/products_table_component.rb | 36 +++++++++++++++---- .../products_table_component.html.haml | 12 ++----- config/locales/en.yml | 6 ++++ 5 files changed, 49 insertions(+), 25 deletions(-) diff --git a/app/components/product_component.rb b/app/components/product_component.rb index 22dfc885bf..58aaa59a9c 100644 --- a/app/components/product_component.rb +++ b/app/components/product_component.rb @@ -8,8 +8,8 @@ class ProductComponent < ViewComponentReflex::Component @name = @product.name @columns = columns.map { |c| { - id: c, - value: column_value(c) + id: c[:value], + value: column_value(c[:value]) } } end diff --git a/app/components/product_component/product_component.html.haml b/app/components/product_component/product_component.html.haml index 8d36d1bc2e..200d536c4c 100644 --- a/app/components/product_component/product_component.html.haml +++ b/app/components/product_component/product_component.html.haml @@ -1,9 +1,11 @@ %tr - %td.products_column.title - - if @image - .image - = image_tag @image.url(:mini) - = @name - @columns.each do |column| - %td.products_column{class: column[:id]} - = column[:value] + - if column[:id] == "name" + %td.products_column.title + - if @image + .image + = image_tag @image.url(:mini) + = @name + - else + %td.products_column{class: column[:id]} + = column[:value] diff --git a/app/components/products_table_component.rb b/app/components/products_table_component.rb index 8a7fcf94a4..ebb9983492 100644 --- a/app/components/products_table_component.rb +++ b/app/components/products_table_component.rb @@ -3,15 +3,21 @@ class ProductsTableComponent < ViewComponentReflex::Component include Pagy::Backend + SORTABLE_COLUMNS = ["name"].freeze + def initialize(user:) super @user = user - @columns = [{ label: I18n.t("admin.products_page.columns_selector.price"), value: "price" }, - { label: I18n.t("admin.products_page.columns_selector.unit"), value: "unit" }, - { label: I18n.t("admin.products_page.columns_selector.producer"), - value: "producer" }, - { label: I18n.t("admin.products_page.columns_selector.category"), - value: "category" }].sort { |a, b| a[:label] <=> b[:label] } + @selectable_columns = [{ label: I18n.t("admin.products_page.columns_selector.price"), + value: "price" }, + { label: I18n.t("admin.products_page.columns_selector.unit"), + value: "unit" }, + { label: I18n.t("admin.products_page.columns_selector.producer"), + value: "producer" }, + { label: I18n.t("admin.products_page.columns_selector.category"), + value: "category" }].sort { |a, b| + a[:label] <=> b[:label] + } @columns_selected = ["price", "unit"] @per_page = [{ label: "10", value: 10 }, { label: "25", value: 25 }, { label: "50", value: 50 }, { label: "100", value: 100 }] @@ -26,10 +32,12 @@ class ProductsTableComponent < ViewComponentReflex::Component .map { |producer| { label: producer.name, value: producer.id.to_s } } @producers_selected = ["all"] @page = 1 + @sort = { column: "name", direction: "asc" } end def before_render fetch_products + refresh_columns end def toggle_column @@ -41,6 +49,11 @@ class ProductsTableComponent < ViewComponentReflex::Component end end + def click_sort + @sort = { column: element.dataset['sort-value'], + direction: element.dataset['sort-direction'] == "asc" ? "desc" : "asc" } + end + def toggle_per_page selected = element.dataset['value'].to_i @per_page_selected = [selected] if [10, 25, 50, 100].include?(selected) @@ -70,6 +83,15 @@ class ProductsTableComponent < ViewComponentReflex::Component private + def refresh_columns + @columns = @columns_selected.map { |column| + { label: I18n.t("admin.products_page.columns.#{column}"), value: column, + sortable: SORTABLE_COLUMNS.include?(column) } + }.sort! { |a, b| a[:label] <=> b[:label] } + @columns.unshift({ label: I18n.t("admin.products_page.columns.name"), value: "name", + sortable: SORTABLE_COLUMNS.include?("name") }) + end + def toggle_super_selector(clicked, selected) selected = if selected.include?(clicked) selected - [clicked] @@ -102,7 +124,7 @@ class ProductsTableComponent < ViewComponentReflex::Component end def ransack_query - query = { s: 'created_at desc' } + query = { s: "#{@sort[:column]} #{@sort[:direction]}" } query = if @producers_selected.include?("all") query.merge({ supplier_id_eq: "" }) diff --git a/app/components/products_table_component/products_table_component.html.haml b/app/components/products_table_component/products_table_component.html.haml index adc9055fa4..14709e16e9 100644 --- a/app/components/products_table_component/products_table_component.html.haml +++ b/app/components/products_table_component/products_table_component.html.haml @@ -12,18 +12,12 @@ #per-page_selector = render(SelectorComponent.new(title: "#{@per_page_selected[0]} items per page", selected: @per_page_selected, items: @per_page, data: reflex_data_attributes(:toggle_per_page))) #columns_selector - = render(SelectorComponent.new(title: "Columns", selected: @columns_selected, items: @columns, data: reflex_data_attributes(:toggle_column))) + = render(SelectorComponent.new(title: "Columns", selected: @columns_selected, items: @selectable_columns, data: reflex_data_attributes(:toggle_column))) #products_table %table - %thead - %tr - %th - Name - - @columns_selected.each do |column| - %th - = @columns.find{ |c| c[:value] == column }[:label] + = render(TableHeaderComponent.new(columns: @columns, sort: @sort, data: reflex_data_attributes(:click_sort))) %tbody - = render(ProductComponent.with_collection(@products, columns: @columns_selected)) + = render(ProductComponent.with_collection(@products, columns: @columns)) #pagination = render(PaginationComponent.new(pagy: @pagy, data: reflex_data_attributes(:change_page))) diff --git a/config/locales/en.yml b/config/locales/en.yml index d37024ff3c..c56a06ecd7 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -459,6 +459,12 @@ en: # admin: products_page: + columns: + name: Name + unit: Unit + price: Price + producer: Producer + category: Category columns_selector: unit: Unit price: Price From e801bb5e40a0387b50293b3bc386ef8339b5ee6d Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Bellet Date: Wed, 23 Mar 2022 22:11:31 +0100 Subject: [PATCH 23/49] some CSS linter --- .../products_table_component/products_table_component.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/components/products_table_component/products_table_component.scss b/app/components/products_table_component/products_table_component.scss index aa0434aa9e..d98ee56eba 100644 --- a/app/components/products_table_component/products_table_component.scss +++ b/app/components/products_table_component/products_table_component.scss @@ -13,7 +13,7 @@ } #products_table { - box-shadow: 0 10px 10px -1px rgb(0 0 0 / 10%) + box-shadow: 0 10px 10px -1px rgb(0 0 0 / 10%); } #pagination { From 5e689fcce3139a744447a891c90f5f02ab6ac260 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Bellet Date: Wed, 23 Mar 2022 22:12:51 +0100 Subject: [PATCH 24/49] No need to fetch as it's done inside beforeReflex lifecycle method --- app/components/products_table_component.rb | 7 ------- 1 file changed, 7 deletions(-) diff --git a/app/components/products_table_component.rb b/app/components/products_table_component.rb index ebb9983492..7802ee4641 100644 --- a/app/components/products_table_component.rb +++ b/app/components/products_table_component.rb @@ -57,28 +57,21 @@ class ProductsTableComponent < ViewComponentReflex::Component def toggle_per_page selected = element.dataset['value'].to_i @per_page_selected = [selected] if [10, 25, 50, 100].include?(selected) - fetch_products end def toggle_category category_clicked = element.dataset['value'] @categories_selected = toggle_super_selector(category_clicked, @categories_selected) - - fetch_products end def toggle_producer producer_clicked = element.dataset['value'] @producers_selected = toggle_super_selector(producer_clicked, @producers_selected) - - fetch_products end def change_page page = element.dataset['page'].to_i @page = page if page > 0 - - fetch_products end private From 265b4823a775cbd2fa71ec78fb8534e5a3b239cf Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Bellet Date: Wed, 23 Mar 2022 22:22:21 +0100 Subject: [PATCH 25/49] Add static var + freeze --- app/components/products_table_component.rb | 33 ++++++++++++---------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/app/components/products_table_component.rb b/app/components/products_table_component.rb index 7802ee4641..599aab0bae 100644 --- a/app/components/products_table_component.rb +++ b/app/components/products_table_component.rb @@ -4,23 +4,27 @@ class ProductsTableComponent < ViewComponentReflex::Component include Pagy::Backend SORTABLE_COLUMNS = ["name"].freeze + SELECTABLE_COMUMNS = [{ label: I18n.t("admin.products_page.columns_selector.price"), + value: "price" }, + { label: I18n.t("admin.products_page.columns_selector.unit"), + value: "unit" }, + { label: I18n.t("admin.products_page.columns_selector.producer"), + value: "producer" }, + { label: I18n.t("admin.products_page.columns_selector.category"), + value: "category" }].sort { |a, b| + a[:label] <=> b[:label] + }.freeze + PER_PAGE_VALUE = [10, 25, 50, 100].freeze + PER_PAGE = PER_PAGE_VALUE.map { |value| { label: value, value: value } } + ALL_COLUMN = { label: I18n.t("admin.products_page.columns.name"), value: "name", + sortable: true }.freeze def initialize(user:) super @user = user - @selectable_columns = [{ label: I18n.t("admin.products_page.columns_selector.price"), - value: "price" }, - { label: I18n.t("admin.products_page.columns_selector.unit"), - value: "unit" }, - { label: I18n.t("admin.products_page.columns_selector.producer"), - value: "producer" }, - { label: I18n.t("admin.products_page.columns_selector.category"), - value: "category" }].sort { |a, b| - a[:label] <=> b[:label] - } + @selectable_columns = SELECTABLE_COMUMNS @columns_selected = ["price", "unit"] - @per_page = [{ label: "10", value: 10 }, { label: "25", value: 25 }, { label: "50", value: 50 }, - { label: "100", value: 100 }] + @per_page = PER_PAGE @per_page_selected = [10] @categories = [{ label: "All", value: "all" }] + Spree::Taxon.order(:name) @@ -56,7 +60,7 @@ class ProductsTableComponent < ViewComponentReflex::Component def toggle_per_page selected = element.dataset['value'].to_i - @per_page_selected = [selected] if [10, 25, 50, 100].include?(selected) + @per_page_selected = [selected] if PER_PAGE_VALUE.include?(selected) end def toggle_category @@ -81,8 +85,7 @@ class ProductsTableComponent < ViewComponentReflex::Component { label: I18n.t("admin.products_page.columns.#{column}"), value: column, sortable: SORTABLE_COLUMNS.include?(column) } }.sort! { |a, b| a[:label] <=> b[:label] } - @columns.unshift({ label: I18n.t("admin.products_page.columns.name"), value: "name", - sortable: SORTABLE_COLUMNS.include?("name") }) + @columns.unshift(ALL_COLUMN) end def toggle_super_selector(clicked, selected) From 22d13621f4c40acc1afb3e1d6a5c63b1c87629fc Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Bellet Date: Thu, 24 Mar 2022 09:44:49 +0100 Subject: [PATCH 26/49] Only send close reflex if element is actually visible --- app/webpacker/controllers/selector_controller.js | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/app/webpacker/controllers/selector_controller.js b/app/webpacker/controllers/selector_controller.js index c5311ba583..d2eb881bfc 100644 --- a/app/webpacker/controllers/selector_controller.js +++ b/app/webpacker/controllers/selector_controller.js @@ -14,8 +14,15 @@ export default class extends ApplicationController { window.removeEventListener("click", this.closeOnClickOutside); } + afterReflex() { + this.computeItemsHeight(); + } + closeOnClickOutside = (event) => { - if (!this.element.contains(event.target)) { + if ( + !this.element.contains(event.target) && + this.isVisible(this.element.querySelector(".selector-wrapper")) + ) { this.stimulate(`${this.reflex}#close`, this.element); } }; @@ -25,4 +32,9 @@ export default class extends ApplicationController { const rect = items.getBoundingClientRect(); items.style.maxHeight = `calc(100vh - ${rect.height}px)`; }; + + isVisible = (element) => { + const style = window.getComputedStyle(element); + return style.display !== "none" && style.visibility !== "hidden"; + }; } From 122677bc7a845ded8ddcfee70a5f1350428fd5ad Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Bellet Date: Thu, 24 Mar 2022 10:29:50 +0100 Subject: [PATCH 27/49] Create SearchInput component --- app/components/search_input_component.rb | 9 ++++++ .../search_input_component.html.haml | 5 ++++ .../search_input_component.scss | 23 +++++++++++++++ .../controllers/search-input_controller.js | 28 +++++++++++++++++++ app/webpacker/css/admin/all.scss | 1 + app/webpacker/packs/admin.js | 3 ++ package.json | 1 + yarn.lock | 5 ++++ 8 files changed, 75 insertions(+) create mode 100644 app/components/search_input_component.rb create mode 100644 app/components/search_input_component/search_input_component.html.haml create mode 100644 app/components/search_input_component/search_input_component.scss create mode 100644 app/webpacker/controllers/search-input_controller.js diff --git a/app/components/search_input_component.rb b/app/components/search_input_component.rb new file mode 100644 index 0000000000..11756d443b --- /dev/null +++ b/app/components/search_input_component.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +class SearchInputComponent < ViewComponentReflex::Component + def initialize(value: nil, data: {}) + super + @value = value + @data = data + end +end diff --git a/app/components/search_input_component/search_input_component.html.haml b/app/components/search_input_component/search_input_component.html.haml new file mode 100644 index 0000000000..c3d2e53edf --- /dev/null +++ b/app/components/search_input_component/search_input_component.html.haml @@ -0,0 +1,5 @@ += component_controller do + %div.search-input + %input{type: 'text', placeholder: 'Search', id: 'search_query', data: {action: 'debounced:input->search-input#search'}, value: @value} + .search-button{data: @data} + %i.fa.fa-search diff --git a/app/components/search_input_component/search_input_component.scss b/app/components/search_input_component/search_input_component.scss new file mode 100644 index 0000000000..bc21b72088 --- /dev/null +++ b/app/components/search_input_component/search_input_component.scss @@ -0,0 +1,23 @@ +.search-input { + border: 1px solid #d2d2d2; + height: 3em; + display: flex; + line-height: 3em; + align-items: center; + + input { + border: none; + height: 3em; + width: 100%; + box-sizing: border-box; + padding-right: 5px; + } + + .search-button { + padding-right: 10px; + padding-left: 5px; + cursor: pointer; + color: #6788A2; + } + +} diff --git a/app/webpacker/controllers/search-input_controller.js b/app/webpacker/controllers/search-input_controller.js new file mode 100644 index 0000000000..47aee17134 --- /dev/null +++ b/app/webpacker/controllers/search-input_controller.js @@ -0,0 +1,28 @@ +import ApplicationController from "./application_controller"; + +export default class extends ApplicationController { + connect() { + super.connect(); + this.element + .querySelector("input") + .addEventListener("keydown", this.searchOnEnter); + } + + disconnect() { + super.disconnect(); + this.element + .querySelector("input") + .removeEventListener("keydown", this.searchOnEnter); + } + + searchOnEnter = (e) => { + if (e.key === "Enter") { + this.element.querySelector(".search-button").click(); + } + }; + + search(e) { + this.element.querySelector(".search-button").dataset["value"] = + e.target.value; + } +} diff --git a/app/webpacker/css/admin/all.scss b/app/webpacker/css/admin/all.scss index b4a35aadf9..4022c40967 100644 --- a/app/webpacker/css/admin/all.scss +++ b/app/webpacker/css/admin/all.scss @@ -126,5 +126,6 @@ @import "app/components/super_selector_component/super_selector_component"; @import "app/components/pagination_component/pagination_component"; @import "app/components/table_header_component/table_header_component"; +@import "app/components/search_input_component/search_input_component"; @import "v2/main.scss"; diff --git a/app/webpacker/packs/admin.js b/app/webpacker/packs/admin.js index 02d879c4f1..b0a44a4fe9 100644 --- a/app/webpacker/packs/admin.js +++ b/app/webpacker/packs/admin.js @@ -13,3 +13,6 @@ application.consumer = consumer; StimulusReflex.initialize(application, { controller, isolate: true }); StimulusReflex.debug = process.env.RAILS_ENV === "development"; CableReady.initialize({ consumer }); + +import debounced from "debounced"; +debounced.initialize({ input: { wait: 300 } }); diff --git a/package.json b/package.json index b9ace90630..86970387fc 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "@rails/webpacker": "5.4.3", "babel-loader": "^8.2.3", "cable_ready": "5.0.0-pre9", + "debounced": "^0.0.5", "flatpickr": "^4.6.9", "foundation-sites": "^5.5.2", "jquery-ui": "1.13.0", diff --git a/yarn.lock b/yarn.lock index 2031f68b8d..204d08c9c0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5054,6 +5054,11 @@ date-format@^4.0.3: resolved "https://registry.yarnpkg.com/date-format/-/date-format-4.0.3.tgz#f63de5dc08dc02efd8ef32bf2a6918e486f35873" integrity sha512-7P3FyqDcfeznLZp2b+OMitV9Sz2lUnsT87WaTat9nVwqsBkTzPG3lPLNwW3en6F4pHUiWzr6vb8CLhjdK9bcxQ== +debounced@^0.0.5: + version "0.0.5" + resolved "https://registry.yarnpkg.com/debounced/-/debounced-0.0.5.tgz#e540b6eebfe703d93462711b4f3562ffd101b87f" + integrity sha512-8Bgheu1YxQB7ocqYmK2enbLGVoo4nCtu/V6UD/SMDOeV3g2LocG2CrA5oxudlyl79Ja07UiqGdp9pWZoJn52EQ== + debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.0: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" From 27c1fe2f06c2e47eb814a1a7ca6f215d55f33fc2 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Bellet Date: Thu, 24 Mar 2022 10:30:13 +0100 Subject: [PATCH 28/49] Add search query input to search for product by name --- app/components/products_table_component.rb | 7 +++++++ .../products_table_component.html.haml | 5 +---- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/app/components/products_table_component.rb b/app/components/products_table_component.rb index 599aab0bae..840dae0b66 100644 --- a/app/components/products_table_component.rb +++ b/app/components/products_table_component.rb @@ -37,6 +37,7 @@ class ProductsTableComponent < ViewComponentReflex::Component @producers_selected = ["all"] @page = 1 @sort = { column: "name", direction: "asc" } + @search_term = "" end def before_render @@ -44,6 +45,10 @@ class ProductsTableComponent < ViewComponentReflex::Component refresh_columns end + def search_term + @search_term = element.dataset['value'] + end + def toggle_column column = element.dataset['value'] @columns_selected = if @columns_selected.include?(column) @@ -128,6 +133,8 @@ class ProductsTableComponent < ViewComponentReflex::Component query.merge({ supplier_id_in: @producers_selected }) end + query = query.merge({ name_cont: @search_term }) if @search_term.present? + if @categories_selected.include?("all") query.merge({ primary_taxon_id_eq: "" }) else diff --git a/app/components/products_table_component/products_table_component.html.haml b/app/components/products_table_component/products_table_component.html.haml index 14709e16e9..7976b1d94b 100644 --- a/app/components/products_table_component/products_table_component.html.haml +++ b/app/components/products_table_component/products_table_component.html.haml @@ -1,10 +1,7 @@ = component_controller do #products_page_form #filter_results - %input.search{type: 'text', placeholder: 'Search', id: 'search_query'} - %button.btn.btn-primary{type: 'submit'} - Filter results - + = render(SearchInputComponent.new(value: @search_term, data: reflex_data_attributes(:search_term))) #categories_selector = render(SuperSelectorComponent.new(title: "Categories", selected: @categories_selected, items: @categories, data: reflex_data_attributes(:toggle_category))) #producers_selector From 3ae5db907a91040277d99678b8b9fd80911e2535 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Bellet Date: Thu, 24 Mar 2022 11:26:08 +0100 Subject: [PATCH 29/49] Create js controller for ProductsTable component Handle before/after reflex lifecycle methods on client side Add 'loading" class on products-table component --- .../controllers/products_table_controller.js | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 app/webpacker/controllers/products_table_controller.js diff --git a/app/webpacker/controllers/products_table_controller.js b/app/webpacker/controllers/products_table_controller.js new file mode 100644 index 0000000000..0823d1b617 --- /dev/null +++ b/app/webpacker/controllers/products_table_controller.js @@ -0,0 +1,46 @@ +import ApplicationController from "./application_controller"; + +export default class extends ApplicationController { + connect() { + super.connect(); + document.addEventListener( + "stimulus-reflex:before", + this.handleBeforeReflex.bind(this) + ); + document.addEventListener( + "stimulus-reflex:after", + this.handleAfterReflex.bind(this) + ); + } + + disconnect() { + super.disconnect(); + document.removeEventListener( + "stimulus-reflex:before", + this.handleBeforeReflex.bind(this) + ); + document.removeEventListener( + "stimulus-reflex:after", + this.handleAfterReflex.bind(this) + ); + } + + handleBeforeReflex(event) { + if (event.detail.reflex.indexOf("ProductsTableComponent#") !== -1) { + this.showLoading(); + } + } + + handleAfterReflex(event) { + if (event.detail.reflex.indexOf("ProductsTableComponent#") !== -1) { + this.hideLoading(); + } + } + + showLoading() { + this.element.classList.add("loading"); + } + hideLoading() { + this.element.classList.remove("loading"); + } +} From 3e03208cf5bf7a31e2d984e03dd92f9cf2a7ec18 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Bellet Date: Fri, 25 Mar 2022 14:26:48 +0100 Subject: [PATCH 30/49] Add loading state for ProductsTable component --- .../products_table_component.html.haml | 19 ++++--- .../products_table_component.scss | 56 +++++++++++++------ 2 files changed, 48 insertions(+), 27 deletions(-) diff --git a/app/components/products_table_component/products_table_component.html.haml b/app/components/products_table_component/products_table_component.html.haml index 7976b1d94b..f9e7073c43 100644 --- a/app/components/products_table_component/products_table_component.html.haml +++ b/app/components/products_table_component/products_table_component.html.haml @@ -1,20 +1,21 @@ -= component_controller do - #products_page_form - #filter_results += component_controller(class: "products-table") do + .products-table-form + .products-table-form_filter_results = render(SearchInputComponent.new(value: @search_term, data: reflex_data_attributes(:search_term))) - #categories_selector + .products-table-form_categories_selector = render(SuperSelectorComponent.new(title: "Categories", selected: @categories_selected, items: @categories, data: reflex_data_attributes(:toggle_category))) - #producers_selector + .products-table-form_producers_selector = render(SuperSelectorComponent.new(title: "Producers", selected: @producers_selected, items: @producers, data: reflex_data_attributes(:toggle_producer))) - #per-page_selector + .products-table-form_per-page_selector = render(SelectorComponent.new(title: "#{@per_page_selected[0]} items per page", selected: @per_page_selected, items: @per_page, data: reflex_data_attributes(:toggle_per_page))) - #columns_selector + .products-table-form_columns_selector = render(SelectorComponent.new(title: "Columns", selected: @columns_selected, items: @selectable_columns, data: reflex_data_attributes(:toggle_column))) - #products_table + .products-table_table %table = render(TableHeaderComponent.new(columns: @columns, sort: @sort, data: reflex_data_attributes(:click_sort))) %tbody = render(ProductComponent.with_collection(@products, columns: @columns)) - #pagination + + .products-table-form_pagination = render(PaginationComponent.new(pagy: @pagy, data: reflex_data_attributes(:change_page))) diff --git a/app/components/products_table_component/products_table_component.scss b/app/components/products_table_component/products_table_component.scss index d98ee56eba..f64c589d50 100644 --- a/app/components/products_table_component/products_table_component.scss +++ b/app/components/products_table_component/products_table_component.scss @@ -1,27 +1,47 @@ -#products_page_form { - display: grid; - grid-template-columns: repeat( auto-fit, minmax(250px, 1fr) ); - grid-gap: 10px; - margin-bottom: 10px; - - #filter_results { - +.products-table { + .products-table-form { + display: grid; + grid-template-columns: repeat( auto-fit, minmax(250px, 1fr) ); + grid-gap: 10px; + margin-bottom: 10px; } - #columns_selector { + .products-table_table { + box-shadow: 0 10px 10px -1px rgb(0 0 0 / 10%); + } + + .products-table-form_pagination { + position: relative; + top: -15px; + + nav, .pagination { + margin-top: 0; + padding-top: 0; + } } } -#products_table { - box-shadow: 0 10px 10px -1px rgb(0 0 0 / 10%); -} +.products-table.loading { + .products-table-form_pagination, .products-table_table { + position: relative; -#pagination { - position: relative; - top: -15px; + &:before { + content: ""; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgba(255, 255, 255, 0.5); + } + } - nav, .pagination { - margin-top: 0; - padding-top: 0; + .products-table_table { + &:before { + background-position: center; + background-repeat: no-repeat; + background-size: 50px 50px; + background-image: url("../images/spinning-circles.svg"); + } } } From 98391b60c69a8f38ff38b0acd951337d4d974df5 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Bellet Date: Fri, 25 Mar 2022 14:40:47 +0100 Subject: [PATCH 31/49] Use CSS vars to match linter --- .../pagination_component/pagination_component.scss | 10 +++++----- .../search_input_component/search_input_component.scss | 4 ++-- .../selector_component/selector_component.scss | 8 ++++---- .../super_selector_component.scss | 8 ++++---- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/app/components/pagination_component/pagination_component.scss b/app/components/pagination_component/pagination_component.scss index d862ede35f..437e0a55d1 100644 --- a/app/components/pagination_component/pagination_component.scss +++ b/app/components/pagination_component/pagination_component.scss @@ -16,7 +16,7 @@ nav { &.inactive { cursor: default; - color: #999; + color: $disabled-dark; } } @@ -52,15 +52,15 @@ nav { } .pagination-gap { - color: #999; + color: $disabled-dark; } .pagination-page { - color: #6788A2; + color: $color-4; cursor: pointer; &.active { - border-top: 3px solid #5498DA; - color: #5498DA; + border-top: 3px solid $spree-blue; + color: $spree-blue; cursor: default; } } diff --git a/app/components/search_input_component/search_input_component.scss b/app/components/search_input_component/search_input_component.scss index bc21b72088..add821dd34 100644 --- a/app/components/search_input_component/search_input_component.scss +++ b/app/components/search_input_component/search_input_component.scss @@ -1,5 +1,5 @@ .search-input { - border: 1px solid #d2d2d2; + border: 1px solid $disabled-light; height: 3em; display: flex; line-height: 3em; @@ -17,7 +17,7 @@ padding-right: 10px; padding-left: 5px; cursor: pointer; - color: #6788A2; + color: $color-4; } } diff --git a/app/components/selector_component/selector_component.scss b/app/components/selector_component/selector_component.scss index 7660e6ceb4..fbfc76b9f6 100644 --- a/app/components/selector_component/selector_component.scss +++ b/app/components/selector_component/selector_component.scss @@ -2,7 +2,7 @@ position: relative; .selector-main { - border: 1px solid #d2d2d2; + border: 1px solid $disabled-light; height: 3em; position: relative; @@ -30,7 +30,7 @@ height: 0; border-left: 5px solid transparent; border-right: 5px solid transparent; - border-top: 5px solid #d2d2d2; + border-top: 5px solid $disabled-light; } } } @@ -42,7 +42,7 @@ z-index: 1; background-color: white; margin-top: -1px; - border: 1px solid #d2d2d2; + border: 1px solid $disabled-light; .selector-items { overflow-y: auto; @@ -51,7 +51,7 @@ .selector-item { padding-left: 10px; padding-right: 10px; - border-bottom: 1px solid #d2d2d2; + border-bottom: 1px solid $disabled-light; position: relative; height: 3em; line-height: 3em; diff --git a/app/components/super_selector_component/super_selector_component.scss b/app/components/super_selector_component/super_selector_component.scss index 8e5c2e31fe..88f9e6c5f2 100644 --- a/app/components/super_selector_component/super_selector_component.scss +++ b/app/components/super_selector_component/super_selector_component.scss @@ -20,8 +20,8 @@ display: flex; .super-selector-selected-item { - border: 1px solid #cee1f4; - background-color: #eff5fc; + border: 1px solid $pale-blue; + background-color: $spree-light-blue; border-radius: 20px; height: 2em; padding-left: 10px; @@ -37,11 +37,11 @@ .selector-wrapper { .super-selector-search { - border-bottom: 1px solid #d2d2d2; + border-bottom: 1px solid $disabled-light; padding: 10px 5px; input { - border: 1px solid #d2d2d2; + border: 1px solid $disabled-light; box-sizing: border-box; border-radius: 4px; width: 100%; From 758c40c6463173e84d538fb85618652987c36ccd Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Bellet Date: Mon, 28 Mar 2022 11:04:54 +0200 Subject: [PATCH 32/49] Manage i18n for Products page --- app/views/admin/products/index.html.haml | 2 +- config/locales/en.yml | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/app/views/admin/products/index.html.haml b/app/views/admin/products/index.html.haml index ffb1dc1fc1..31db370aad 100644 --- a/app/views/admin/products/index.html.haml +++ b/app/views/admin/products/index.html.haml @@ -1,6 +1,6 @@ - content_for :page_title do - New Products Page + = t('admin.products_page.title') = render partial: 'spree/admin/shared/product_sub_menu' diff --git a/config/locales/en.yml b/config/locales/en.yml index c56a06ecd7..02dc949b8b 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -459,6 +459,7 @@ en: # admin: products_page: + title: Products columns: name: Name unit: Unit From 56b94342eb7ba4483c395133a9761e5227f9c05c Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Bellet Date: Mon, 28 Mar 2022 11:06:35 +0200 Subject: [PATCH 33/49] Manage i18n for SearchInput component --- .../search_input_component/search_input_component.html.haml | 2 +- config/locales/en.yml | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/app/components/search_input_component/search_input_component.html.haml b/app/components/search_input_component/search_input_component.html.haml index c3d2e53edf..7cabbe54cc 100644 --- a/app/components/search_input_component/search_input_component.html.haml +++ b/app/components/search_input_component/search_input_component.html.haml @@ -1,5 +1,5 @@ = component_controller do %div.search-input - %input{type: 'text', placeholder: 'Search', id: 'search_query', data: {action: 'debounced:input->search-input#search'}, value: @value} + %input{type: 'text', placeholder: t("components.search_input.placeholder"), id: 'search_query', data: {action: 'debounced:input->search-input#search'}, value: @value} .search-button{data: @data} %i.fa.fa-search diff --git a/config/locales/en.yml b/config/locales/en.yml index 02dc949b8b..afe1c14765 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -455,6 +455,9 @@ en: destroy: "Destroy" rename: "Rename" + components: + search_input: + placeholder: Search # Admin # admin: From 18adcb7fa494071ed1621cc131fa1088e303dbb0 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Bellet Date: Mon, 28 Mar 2022 11:15:51 +0200 Subject: [PATCH 34/49] Handle i18n for SuperSelector component --- app/components/super_selector_component.rb | 4 +++- .../super_selector_component.html.haml | 4 ++-- config/locales/en.yml | 3 +++ 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/app/components/super_selector_component.rb b/app/components/super_selector_component.rb index bd78d4c550..33f55440ae 100644 --- a/app/components/super_selector_component.rb +++ b/app/components/super_selector_component.rb @@ -1,10 +1,12 @@ # frozen_string_literal: true class SuperSelectorComponent < SelectorComponent - def initialize(title:, selected:, items:, data: {}) + def initialize(title:, selected:, items:, data: {}, + selected_items_i18n_key: 'components.super_selector.selected_items') super(title: title, selected: selected, items: items, data: data) @query = "" @selected_items = items.select { |item| @selected.include?(item[:value]) } + @selected_items_i18n_key = selected_items_i18n_key filter_items end diff --git a/app/components/super_selector_component/super_selector_component.html.haml b/app/components/super_selector_component/super_selector_component.html.haml index 4563fbad05..7988793bca 100644 --- a/app/components/super_selector_component/super_selector_component.html.haml +++ b/app/components/super_selector_component/super_selector_component.html.haml @@ -11,11 +11,11 @@ = item[:label] - else .super-selector-selected-item - = "#{@selected_items.length} categories selected" + = t(@selected_items_i18n_key, count: @selected_items.length) .selector-arrow{data: reflex_data_attributes(:toggle)} .selector-wrapper .super-selector-search - %input{type: "text", placeholder: "Search", data: reflex_data_attributes("debounced:input->search"), value: @query} + %input{type: "text", placeholder: t("components.super_selector.search_placeholder"), data: reflex_data_attributes("debounced:input->search"), value: @query} .selector-items - @filtered_items.each do |item| .selector-item{ class: ("selected" if item[:selected]), data: @data, "data-value": item[:value] } diff --git a/config/locales/en.yml b/config/locales/en.yml index afe1c14765..de0dfb39d7 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -458,6 +458,9 @@ en: components: search_input: placeholder: Search + super_selector: + selected_items: "%{count} selected" + search_placeholder: Search # Admin # admin: From 20218c00ab0d5ed1468e78343d21885ccdc8ac47 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Bellet Date: Mon, 28 Mar 2022 11:05:06 +0200 Subject: [PATCH 35/49] Manage i18n for ProductsTable component --- .../products_table_component.html.haml | 8 ++++---- config/locales/en.yml | 9 +++++++++ 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/app/components/products_table_component/products_table_component.html.haml b/app/components/products_table_component/products_table_component.html.haml index f9e7073c43..bd9074d7ef 100644 --- a/app/components/products_table_component/products_table_component.html.haml +++ b/app/components/products_table_component/products_table_component.html.haml @@ -3,13 +3,13 @@ .products-table-form_filter_results = render(SearchInputComponent.new(value: @search_term, data: reflex_data_attributes(:search_term))) .products-table-form_categories_selector - = render(SuperSelectorComponent.new(title: "Categories", selected: @categories_selected, items: @categories, data: reflex_data_attributes(:toggle_category))) + = render(SuperSelectorComponent.new(title: t("admin.products_page.filters.categories.title"), selected: @categories_selected, items: @categories, data: reflex_data_attributes(:toggle_category), selected_items_i18n_key: "admin.products_page.filters.categories.selected_categories")) .products-table-form_producers_selector - = render(SuperSelectorComponent.new(title: "Producers", selected: @producers_selected, items: @producers, data: reflex_data_attributes(:toggle_producer))) + = render(SuperSelectorComponent.new(title: t("admin.products_page.filters.producers.title"), selected: @producers_selected, items: @producers, data: reflex_data_attributes(:toggle_producer), selected_items_i18n_key: "admin.products_page.filters.producers.selected_producers")) .products-table-form_per-page_selector - = render(SelectorComponent.new(title: "#{@per_page_selected[0]} items per page", selected: @per_page_selected, items: @per_page, data: reflex_data_attributes(:toggle_per_page))) + = render(SelectorComponent.new(title: t('admin.products_page.filters.per_page', count: @per_page_selected[0]), selected: @per_page_selected, items: @per_page, data: reflex_data_attributes(:toggle_per_page))) .products-table-form_columns_selector - = render(SelectorComponent.new(title: "Columns", selected: @columns_selected, items: @selectable_columns, data: reflex_data_attributes(:toggle_column))) + = render(SelectorComponent.new(title: t("admin.products_page.filters.columns"), selected: @columns_selected, items: @selectable_columns, data: reflex_data_attributes(:toggle_column))) .products-table_table %table diff --git a/config/locales/en.yml b/config/locales/en.yml index de0dfb39d7..ab175088b6 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -466,6 +466,15 @@ en: admin: products_page: title: Products + filters: + categories: + title: Categories + selected_categories: "%{count} categories selected" + producers: + title: Producers + selected_producers: "%{count} producers selected" + per_page: "%{count} items per page" + colums: Columns columns: name: Name unit: Unit From 428256a323d01568bfb7cd3aacd352b298a39ad0 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Bellet Date: Wed, 30 Mar 2022 09:06:05 +0200 Subject: [PATCH 36/49] Exclude products_table_component from Metrics/ClassLength rubocop rule --- .rubocop_todo.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index d42c7b3a2c..1b5bde4065 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -440,6 +440,7 @@ Metrics/BlockNesting: # Configuration parameters: CountComments, Max, CountAsOne. Metrics/ClassLength: Exclude: + - 'app/components/products_table_component.rb' - 'app/controllers/admin/customers_controller.rb' - 'app/controllers/admin/enterprises_controller.rb' - 'app/controllers/admin/order_cycles_controller.rb' From fbf2315a930e2a6b7b8b38bdc49c7c0408665958 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Bellet Date: Wed, 30 Mar 2022 09:22:29 +0200 Subject: [PATCH 37/49] Rename SuperSelector to SelectorWithFilter --- app/components/products_table_component.rb | 6 +++--- .../products_table_component.html.haml | 4 ++-- ...ector_component.rb => selector_with_filter_component.rb} | 4 ++-- .../selector_with_filter_component.html.haml} | 2 +- .../selector_with_filter_component.scss} | 0 ...tor_controller.js => selector_with_filter_controller.js} | 2 +- app/webpacker/css/admin/all.scss | 2 +- config/locales/en.yml | 2 +- 8 files changed, 11 insertions(+), 11 deletions(-) rename app/components/{super_selector_component.rb => selector_with_filter_component.rb} (81%) rename app/components/{super_selector_component/super_selector_component.html.haml => selector_with_filter_component/selector_with_filter_component.html.haml} (82%) rename app/components/{super_selector_component/super_selector_component.scss => selector_with_filter_component/selector_with_filter_component.scss} (100%) rename app/webpacker/controllers/{super_selector_controller.js => selector_with_filter_controller.js} (72%) diff --git a/app/components/products_table_component.rb b/app/components/products_table_component.rb index 840dae0b66..74aaf14c4a 100644 --- a/app/components/products_table_component.rb +++ b/app/components/products_table_component.rb @@ -70,12 +70,12 @@ class ProductsTableComponent < ViewComponentReflex::Component def toggle_category category_clicked = element.dataset['value'] - @categories_selected = toggle_super_selector(category_clicked, @categories_selected) + @categories_selected = toggle_selector_with_filter(category_clicked, @categories_selected) end def toggle_producer producer_clicked = element.dataset['value'] - @producers_selected = toggle_super_selector(producer_clicked, @producers_selected) + @producers_selected = toggle_selector_with_filter(producer_clicked, @producers_selected) end def change_page @@ -93,7 +93,7 @@ class ProductsTableComponent < ViewComponentReflex::Component @columns.unshift(ALL_COLUMN) end - def toggle_super_selector(clicked, selected) + def toggle_selector_with_filter(clicked, selected) selected = if selected.include?(clicked) selected - [clicked] else diff --git a/app/components/products_table_component/products_table_component.html.haml b/app/components/products_table_component/products_table_component.html.haml index bd9074d7ef..b010227636 100644 --- a/app/components/products_table_component/products_table_component.html.haml +++ b/app/components/products_table_component/products_table_component.html.haml @@ -3,9 +3,9 @@ .products-table-form_filter_results = render(SearchInputComponent.new(value: @search_term, data: reflex_data_attributes(:search_term))) .products-table-form_categories_selector - = render(SuperSelectorComponent.new(title: t("admin.products_page.filters.categories.title"), selected: @categories_selected, items: @categories, data: reflex_data_attributes(:toggle_category), selected_items_i18n_key: "admin.products_page.filters.categories.selected_categories")) + = render(SelectorWithFilterComponent.new(title: t("admin.products_page.filters.categories.title"), selected: @categories_selected, items: @categories, data: reflex_data_attributes(:toggle_category), selected_items_i18n_key: "admin.products_page.filters.categories.selected_categories")) .products-table-form_producers_selector - = render(SuperSelectorComponent.new(title: t("admin.products_page.filters.producers.title"), selected: @producers_selected, items: @producers, data: reflex_data_attributes(:toggle_producer), selected_items_i18n_key: "admin.products_page.filters.producers.selected_producers")) + = render(SelectorWithFilterComponent.new(title: t("admin.products_page.filters.producers.title"), selected: @producers_selected, items: @producers, data: reflex_data_attributes(:toggle_producer), selected_items_i18n_key: "admin.products_page.filters.producers.selected_producers")) .products-table-form_per-page_selector = render(SelectorComponent.new(title: t('admin.products_page.filters.per_page', count: @per_page_selected[0]), selected: @per_page_selected, items: @per_page, data: reflex_data_attributes(:toggle_per_page))) .products-table-form_columns_selector diff --git a/app/components/super_selector_component.rb b/app/components/selector_with_filter_component.rb similarity index 81% rename from app/components/super_selector_component.rb rename to app/components/selector_with_filter_component.rb index 33f55440ae..cec7800941 100644 --- a/app/components/super_selector_component.rb +++ b/app/components/selector_with_filter_component.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true -class SuperSelectorComponent < SelectorComponent +class SelectorWithFilterComponent < SelectorComponent def initialize(title:, selected:, items:, data: {}, - selected_items_i18n_key: 'components.super_selector.selected_items') + selected_items_i18n_key: 'components.selector_with_filter.selected_items') super(title: title, selected: selected, items: items, data: data) @query = "" @selected_items = items.select { |item| @selected.include?(item[:value]) } diff --git a/app/components/super_selector_component/super_selector_component.html.haml b/app/components/selector_with_filter_component/selector_with_filter_component.html.haml similarity index 82% rename from app/components/super_selector_component/super_selector_component.html.haml rename to app/components/selector_with_filter_component/selector_with_filter_component.html.haml index 7988793bca..4c82086cff 100644 --- a/app/components/super_selector_component/super_selector_component.html.haml +++ b/app/components/selector_with_filter_component/selector_with_filter_component.html.haml @@ -15,7 +15,7 @@ .selector-arrow{data: reflex_data_attributes(:toggle)} .selector-wrapper .super-selector-search - %input{type: "text", placeholder: t("components.super_selector.search_placeholder"), data: reflex_data_attributes("debounced:input->search"), value: @query} + %input{type: "text", placeholder: t("components.selector_with_filter.search_placeholder"), data: reflex_data_attributes("debounced:input->search"), value: @query} .selector-items - @filtered_items.each do |item| .selector-item{ class: ("selected" if item[:selected]), data: @data, "data-value": item[:value] } diff --git a/app/components/super_selector_component/super_selector_component.scss b/app/components/selector_with_filter_component/selector_with_filter_component.scss similarity index 100% rename from app/components/super_selector_component/super_selector_component.scss rename to app/components/selector_with_filter_component/selector_with_filter_component.scss diff --git a/app/webpacker/controllers/super_selector_controller.js b/app/webpacker/controllers/selector_with_filter_controller.js similarity index 72% rename from app/webpacker/controllers/super_selector_controller.js rename to app/webpacker/controllers/selector_with_filter_controller.js index 445b0fd16d..f53e0bc873 100644 --- a/app/webpacker/controllers/super_selector_controller.js +++ b/app/webpacker/controllers/selector_with_filter_controller.js @@ -1,5 +1,5 @@ import SelectorController from "./selector_controller"; export default class extends SelectorController { - reflex = "SuperSelectorComponent"; + reflex = "SelectorWithFilterComponent"; } diff --git a/app/webpacker/css/admin/all.scss b/app/webpacker/css/admin/all.scss index 4022c40967..35e7b70bc2 100644 --- a/app/webpacker/css/admin/all.scss +++ b/app/webpacker/css/admin/all.scss @@ -123,7 +123,7 @@ @import "app/components/product_component/product_component"; @import "app/components/selector_component/selector_component"; @import "app/components/products_table_component/products_table_component"; -@import "app/components/super_selector_component/super_selector_component"; +@import "app/components/selector_with_filter_component/selector_with_filter_component"; @import "app/components/pagination_component/pagination_component"; @import "app/components/table_header_component/table_header_component"; @import "app/components/search_input_component/search_input_component"; diff --git a/config/locales/en.yml b/config/locales/en.yml index ab175088b6..660846e71e 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -458,7 +458,7 @@ en: components: search_input: placeholder: Search - super_selector: + selector_with_filter: selected_items: "%{count} selected" search_placeholder: Search # Admin From 91d1ecea2ef9af92312cab0164b16fdd84ed85a1 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Bellet Date: Mon, 4 Apr 2022 10:14:27 +0200 Subject: [PATCH 38/49] Click the whole Selector instead of arrow only --- .../selector_component/selector_component.html.haml | 4 ++-- app/components/selector_component/selector_component.scss | 2 +- .../selector_with_filter_component.html.haml | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/components/selector_component/selector_component.html.haml b/app/components/selector_component/selector_component.html.haml index 34a09a3b5f..c1c9728415 100644 --- a/app/components/selector_component/selector_component.html.haml +++ b/app/components/selector_component/selector_component.html.haml @@ -1,9 +1,9 @@ = component_controller do .selector{ class: ("selector-close" if @state == :close) } - .selector-main + .selector-main{data: reflex_data_attributes(:toggle)} .selector-main-title = @title - .selector-arrow{data: reflex_data_attributes(:toggle)} + .selector-arrow .selector-wrapper .selector-items - @items.each do |item| diff --git a/app/components/selector_component/selector_component.scss b/app/components/selector_component/selector_component.scss index fbfc76b9f6..e5ea18583e 100644 --- a/app/components/selector_component/selector_component.scss +++ b/app/components/selector_component/selector_component.scss @@ -5,6 +5,7 @@ border: 1px solid $disabled-light; height: 3em; position: relative; + cursor: pointer; .selector-main-title { line-height: 3em; @@ -18,7 +19,6 @@ height: 3em; width: 1.5em; top: -1px; - cursor: pointer; &:after { content: ""; diff --git a/app/components/selector_with_filter_component/selector_with_filter_component.html.haml b/app/components/selector_with_filter_component/selector_with_filter_component.html.haml index 4c82086cff..4b0a7632d9 100644 --- a/app/components/selector_with_filter_component/selector_with_filter_component.html.haml +++ b/app/components/selector_with_filter_component/selector_with_filter_component.html.haml @@ -1,6 +1,6 @@ = component_controller do .super-selector.selector{ class: ("selector-close" if @state == :close) } - .selector-main + .selector-main{data: reflex_data_attributes(:toggle)} .super-selector-label = @title .super-selector-selected-items @@ -12,7 +12,7 @@ - else .super-selector-selected-item = t(@selected_items_i18n_key, count: @selected_items.length) - .selector-arrow{data: reflex_data_attributes(:toggle)} + .selector-arrow .selector-wrapper .super-selector-search %input{type: "text", placeholder: t("components.selector_with_filter.search_placeholder"), data: reflex_data_attributes("debounced:input->search"), value: @query} From 940b554d239b058b7e252d8b96bd47bcba7bbbdc Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Bellet Date: Thu, 7 Apr 2022 14:48:15 +0200 Subject: [PATCH 39/49] Toggle is now controlled by the browser (and don't requires a reflex) --- app/components/selector_component.rb | 9 --------- .../selector_component.html.haml | 4 ++-- .../controllers/selector_controller.js | 18 +++++++++++++++--- 3 files changed, 17 insertions(+), 14 deletions(-) diff --git a/app/components/selector_component.rb b/app/components/selector_component.rb index 4837abb318..daa81d7663 100644 --- a/app/components/selector_component.rb +++ b/app/components/selector_component.rb @@ -12,15 +12,6 @@ class SelectorComponent < ViewComponentReflex::Component } end @selected = selected - @state = :close @data = data end - - def toggle - @state = @state == :open ? :close : :open - end - - def close - @state = :close - end end diff --git a/app/components/selector_component/selector_component.html.haml b/app/components/selector_component/selector_component.html.haml index c1c9728415..bf56712d59 100644 --- a/app/components/selector_component/selector_component.html.haml +++ b/app/components/selector_component/selector_component.html.haml @@ -1,6 +1,6 @@ = component_controller do - .selector{ class: ("selector-close" if @state == :close) } - .selector-main{data: reflex_data_attributes(:toggle)} + .selector + .selector-main{ data: { action: "click->selector#toggle" } } .selector-main-title = @title .selector-arrow diff --git a/app/webpacker/controllers/selector_controller.js b/app/webpacker/controllers/selector_controller.js index d2eb881bfc..cce4241643 100644 --- a/app/webpacker/controllers/selector_controller.js +++ b/app/webpacker/controllers/selector_controller.js @@ -1,8 +1,6 @@ import ApplicationController from "./application_controller"; export default class extends ApplicationController { - reflex = "SelectorComponent"; - connect() { super.connect(); window.addEventListener("click", this.closeOnClickOutside); @@ -14,16 +12,26 @@ export default class extends ApplicationController { window.removeEventListener("click", this.closeOnClickOutside); } + initialize() { + this.close(); + } + afterReflex() { this.computeItemsHeight(); } + toggle = (event) => { + event.preventDefault(); + this.element.querySelector(".selector").classList.toggle("selector-close"); + }; + + // Private closeOnClickOutside = (event) => { if ( !this.element.contains(event.target) && this.isVisible(this.element.querySelector(".selector-wrapper")) ) { - this.stimulate(`${this.reflex}#close`, this.element); + this.close(); } }; @@ -37,4 +45,8 @@ export default class extends ApplicationController { const style = window.getComputedStyle(element); return style.display !== "none" && style.visibility !== "hidden"; }; + + close = () => { + this.element.querySelector(".selector").classList.add("selector-close"); + }; } From 274ee2c6f63c001c71989270e5a6e9f170d9620c Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Bellet Date: Thu, 7 Apr 2022 14:48:25 +0200 Subject: [PATCH 40/49] Toggle is now controlled by the browser (and don't requires a reflex) --- .../selector_with_filter_component.html.haml | 4 ++-- app/webpacker/controllers/selector_with_filter_controller.js | 4 +--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/app/components/selector_with_filter_component/selector_with_filter_component.html.haml b/app/components/selector_with_filter_component/selector_with_filter_component.html.haml index 4b0a7632d9..7d71aef149 100644 --- a/app/components/selector_with_filter_component/selector_with_filter_component.html.haml +++ b/app/components/selector_with_filter_component/selector_with_filter_component.html.haml @@ -1,6 +1,6 @@ = component_controller do - .super-selector.selector{ class: ("selector-close" if @state == :close) } - .selector-main{data: reflex_data_attributes(:toggle)} + .super-selector.selector + .selector-main{ data: { action: "click->selector-with-filter#toggle" } } .super-selector-label = @title .super-selector-selected-items diff --git a/app/webpacker/controllers/selector_with_filter_controller.js b/app/webpacker/controllers/selector_with_filter_controller.js index f53e0bc873..310b23353a 100644 --- a/app/webpacker/controllers/selector_with_filter_controller.js +++ b/app/webpacker/controllers/selector_with_filter_controller.js @@ -1,5 +1,3 @@ import SelectorController from "./selector_controller"; -export default class extends SelectorController { - reflex = "SelectorWithFilterComponent"; -} +export default class extends SelectorController {} From 3cf01623da4f1f9e6e4bc4271725e055b29e2a0b Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Bellet Date: Thu, 7 Apr 2022 15:39:47 +0200 Subject: [PATCH 41/49] Handle filtering on js controller for SelectorWithFilter --- .../selector_with_filter_component.rb | 17 +---------------- .../selector_with_filter_component.html.haml | 6 +++--- .../selector_with_filter_controller.js | 14 +++++++++++++- 3 files changed, 17 insertions(+), 20 deletions(-) diff --git a/app/components/selector_with_filter_component.rb b/app/components/selector_with_filter_component.rb index cec7800941..cae5182ce3 100644 --- a/app/components/selector_with_filter_component.rb +++ b/app/components/selector_with_filter_component.rb @@ -4,23 +4,8 @@ class SelectorWithFilterComponent < SelectorComponent def initialize(title:, selected:, items:, data: {}, selected_items_i18n_key: 'components.selector_with_filter.selected_items') super(title: title, selected: selected, items: items, data: data) - @query = "" @selected_items = items.select { |item| @selected.include?(item[:value]) } @selected_items_i18n_key = selected_items_i18n_key - - filter_items - end - - def search - @query = element.value - filter_items - end - - def filter_items - @filtered_items = if @query.empty? - @items - else - @items.select { |item| item[:label].downcase.include?(@query.downcase) } - end + @items = items end end diff --git a/app/components/selector_with_filter_component/selector_with_filter_component.html.haml b/app/components/selector_with_filter_component/selector_with_filter_component.html.haml index 7d71aef149..f249846ccf 100644 --- a/app/components/selector_with_filter_component/selector_with_filter_component.html.haml +++ b/app/components/selector_with_filter_component/selector_with_filter_component.html.haml @@ -15,8 +15,8 @@ .selector-arrow .selector-wrapper .super-selector-search - %input{type: "text", placeholder: t("components.selector_with_filter.search_placeholder"), data: reflex_data_attributes("debounced:input->search"), value: @query} + %input{type: "text", placeholder: t("components.selector_with_filter.search_placeholder"), data: { action: "debounced:input->selector-with-filter#filter" } } .selector-items - - @filtered_items.each do |item| - .selector-item{ class: ("selected" if item[:selected]), data: @data, "data-value": item[:value] } + - @items.each do |item| + .selector-item{ class: ("selected" if item[:selected]), data: @data.merge({ "selector-with-filter-target": "items" }), "data-value": item[:value] } = item[:label] diff --git a/app/webpacker/controllers/selector_with_filter_controller.js b/app/webpacker/controllers/selector_with_filter_controller.js index 310b23353a..dc049305b6 100644 --- a/app/webpacker/controllers/selector_with_filter_controller.js +++ b/app/webpacker/controllers/selector_with_filter_controller.js @@ -1,3 +1,15 @@ import SelectorController from "./selector_controller"; -export default class extends SelectorController {} +export default class extends SelectorController { + static targets = ["items"]; + + filter = (event) => { + const query = event.target.value; + + this.itemsTargets.forEach((el, i) => { + el.style.display = el.textContent.toLowerCase().includes(query) + ? "" + : "none"; + }); + }; +} From 9dfc22451e9831629ca0ecc0563428235af02a40 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Bellet Date: Fri, 30 Sep 2022 16:13:32 +0200 Subject: [PATCH 42/49] Selectors are close by default --- app/components/selector_component/selector_component.html.haml | 2 +- .../selector_with_filter_component.html.haml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/components/selector_component/selector_component.html.haml b/app/components/selector_component/selector_component.html.haml index bf56712d59..b6b1893ee6 100644 --- a/app/components/selector_component/selector_component.html.haml +++ b/app/components/selector_component/selector_component.html.haml @@ -1,5 +1,5 @@ = component_controller do - .selector + .selector.selector-close .selector-main{ data: { action: "click->selector#toggle" } } .selector-main-title = @title diff --git a/app/components/selector_with_filter_component/selector_with_filter_component.html.haml b/app/components/selector_with_filter_component/selector_with_filter_component.html.haml index f249846ccf..0a75bdd182 100644 --- a/app/components/selector_with_filter_component/selector_with_filter_component.html.haml +++ b/app/components/selector_with_filter_component/selector_with_filter_component.html.haml @@ -1,5 +1,5 @@ = component_controller do - .super-selector.selector + .super-selector.selector.selector-close .selector-main{ data: { action: "click->selector-with-filter#toggle" } } .super-selector-label = @title From ec31d63c58d7ceb80f0e5b25bf5dc56effd5d89b Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Bellet Date: Fri, 7 Oct 2022 10:38:28 +0200 Subject: [PATCH 43/49] Do not import/initialize twice Already done in "controllers" --- app/webpacker/packs/application.js | 9 --------- 1 file changed, 9 deletions(-) diff --git a/app/webpacker/packs/application.js b/app/webpacker/packs/application.js index db0abbeeb5..86dab8481a 100644 --- a/app/webpacker/packs/application.js +++ b/app/webpacker/packs/application.js @@ -23,13 +23,4 @@ require.context("../fonts", true); const images = require.context("../images", true); const imagePath = (name) => images(name, true); -import StimulusReflex from "stimulus_reflex"; -import consumer from "../channels/consumer"; -import controller from "../controllers/application_controller"; - -application.consumer = consumer; -StimulusReflex.initialize(application, { controller, isolate: true }); -StimulusReflex.debug = process.env.RAILS_ENV === "development"; -CableReady.initialize({ consumer }); - import "controllers"; From f0e0bac3b5944311c1730ae628f2b622a0e99383 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Bellet Date: Tue, 25 Oct 2022 10:50:57 +0200 Subject: [PATCH 44/49] As `components:` key already exists, use it! (this key was added during the development of the PR) --- config/locales/en.yml | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/config/locales/en.yml b/config/locales/en.yml index 660846e71e..d855c4a26c 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -455,12 +455,6 @@ en: destroy: "Destroy" rename: "Rename" - components: - search_input: - placeholder: Search - selector_with_filter: - selected_items: "%{count} selected" - search_placeholder: Search # Admin # admin: @@ -4467,3 +4461,8 @@ See the %{link} to find out more about %{sitename}'s features and to start using components: multiple_checked_select: filter_placeholder: "Filter options" + search_input: + placeholder: Search + selector_with_filter: + selected_items: "%{count} selected" + search_placeholder: Search From d97e9ae8240fde40577abb3c63c72243e0d7bd65 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Bellet Date: Tue, 25 Oct 2022 10:51:34 +0200 Subject: [PATCH 45/49] Remove white space --- config/locales/en.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/locales/en.yml b/config/locales/en.yml index d855c4a26c..30a0dec8eb 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -461,7 +461,7 @@ en: products_page: title: Products filters: - categories: + categories: title: Categories selected_categories: "%{count} categories selected" producers: From 5002870b39b132e9a1380bd9588e91631f5600ca Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Bellet Date: Tue, 25 Oct 2022 11:09:15 +0200 Subject: [PATCH 46/49] Add missing i18n translation for PaginationComponent --- .../pagination_component/pagination_component.html.haml | 4 ++-- config/locales/en.yml | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/app/components/pagination_component/pagination_component.html.haml b/app/components/pagination_component/pagination_component.html.haml index 3f3620b2da..28b2eb8847 100644 --- a/app/components/pagination_component/pagination_component.html.haml +++ b/app/components/pagination_component/pagination_component.html.haml @@ -2,7 +2,7 @@ %nav{"aria-label": "pagination"} .pagination .pagination-prev{data: @prev.nil? ? nil : @data, "data-page": @prev, class: "#{'inactive' if @prev.nil?}"} - Previous + = I18n.t "components.pagination.previous" .pagination-pages - @series.each do |page| - if page == :gap @@ -12,5 +12,5 @@ .pagination-page{data: @data, "data-page": page, class: "#{'active' if page.to_i == @page}"} = page .pagination-next{data: @next.nil? ? nil : @data, "data-page": @next, class: "#{'inactive' if @next.nil?}"} - Next + = I18n.t "components.pagination.next" diff --git a/config/locales/en.yml b/config/locales/en.yml index 30a0dec8eb..f433a4d425 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -4466,3 +4466,6 @@ See the %{link} to find out more about %{sitename}'s features and to start using selector_with_filter: selected_items: "%{count} selected" search_placeholder: Search + pagination: + next: Next + previous: Previous From 500c4e8a2f5fd268619e60fb47581cb6185fb2f1 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Bellet Date: Wed, 7 Dec 2022 15:38:20 +0100 Subject: [PATCH 47/49] Already handled by `app/webpacker/controllers/index.js` Controller were instanced twice. --- app/webpacker/packs/application.js | 9 --------- 1 file changed, 9 deletions(-) diff --git a/app/webpacker/packs/application.js b/app/webpacker/packs/application.js index 86dab8481a..21b1ffce26 100644 --- a/app/webpacker/packs/application.js +++ b/app/webpacker/packs/application.js @@ -1,13 +1,4 @@ /* eslint no-console:0 */ - -// StimulusJS -import { Application } from "stimulus"; -import { definitionsFromContext } from "stimulus/webpack-helpers"; - -const application = Application.start(); -const context = require.context("controllers", true, /.js$/); -application.load(definitionsFromContext(context)); - import CableReady from "cable_ready"; import mrujs from "mrujs"; import { CableCar } from "mrujs/plugins"; From 10bfe00123faaf521d8535ee8e47a5f106a69900 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Bellet Date: Mon, 12 Dec 2022 15:09:51 +0100 Subject: [PATCH 48/49] Rename column "Name" --- app/components/products_table_component.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/components/products_table_component.rb b/app/components/products_table_component.rb index 74aaf14c4a..6d9dc7de65 100644 --- a/app/components/products_table_component.rb +++ b/app/components/products_table_component.rb @@ -16,8 +16,8 @@ class ProductsTableComponent < ViewComponentReflex::Component }.freeze PER_PAGE_VALUE = [10, 25, 50, 100].freeze PER_PAGE = PER_PAGE_VALUE.map { |value| { label: value, value: value } } - ALL_COLUMN = { label: I18n.t("admin.products_page.columns.name"), value: "name", - sortable: true }.freeze + NAME_COLUMN = { label: I18n.t("admin.products_page.columns.name"), value: "name", + sortable: true }.freeze def initialize(user:) super @@ -90,7 +90,7 @@ class ProductsTableComponent < ViewComponentReflex::Component { label: I18n.t("admin.products_page.columns.#{column}"), value: column, sortable: SORTABLE_COLUMNS.include?(column) } }.sort! { |a, b| a[:label] <=> b[:label] } - @columns.unshift(ALL_COLUMN) + @columns.unshift(NAME_COLUMN) end def toggle_selector_with_filter(clicked, selected) From a8278446b30ffd2924827605f1e39f257b47a384 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Bellet Date: Mon, 12 Dec 2022 15:14:48 +0100 Subject: [PATCH 49/49] Simplify the way we render the `name` column --- app/components/product_component.rb | 3 ++- .../product_component/product_component.html.haml | 13 ++++--------- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/app/components/product_component.rb b/app/components/product_component.rb index 58aaa59a9c..e90eaa33be 100644 --- a/app/components/product_component.rb +++ b/app/components/product_component.rb @@ -5,7 +5,6 @@ class ProductComponent < ViewComponentReflex::Component super @product = product @image = @product.images[0] if product.images.any? - @name = @product.name @columns = columns.map { |c| { id: c[:value], @@ -16,6 +15,8 @@ class ProductComponent < ViewComponentReflex::Component def column_value(column) case column + when 'name' + @product.name when 'price' @product.price when 'unit' diff --git a/app/components/product_component/product_component.html.haml b/app/components/product_component/product_component.html.haml index 200d536c4c..e0d9d760a8 100644 --- a/app/components/product_component/product_component.html.haml +++ b/app/components/product_component/product_component.html.haml @@ -1,11 +1,6 @@ %tr - @columns.each do |column| - - if column[:id] == "name" - %td.products_column.title - - if @image - .image - = image_tag @image.url(:mini) - = @name - - else - %td.products_column{class: column[:id]} - = column[:value] + %td.products_column{class: column[:id]} + - if column[:id] == "name" && @image + = image_tag @image.url(:mini) + = column[:value]