From 13ab92ac6e363b8c8c878f910852b9df6f1843b9 Mon Sep 17 00:00:00 2001 From: Elia Schito Date: Thu, 23 Nov 2023 12:42:57 +0100 Subject: [PATCH 01/14] Don't add filter hidden inputs if not required in `ui/table` This helps keeping the query URL short. Co-Authored-By: Rainer Dema --- .../ui/table/ransack_filter/component.html.erb | 2 +- .../solidus_admin/ui/table/ransack_filter/component.js | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/admin/app/components/solidus_admin/ui/table/ransack_filter/component.html.erb b/admin/app/components/solidus_admin/ui/table/ransack_filter/component.html.erb index 9838a4d1f47..951fac2f21c 100644 --- a/admin/app/components/solidus_admin/ui/table/ransack_filter/component.html.erb +++ b/admin/app/components/solidus_admin/ui/table/ransack_filter/component.html.erb @@ -52,7 +52,7 @@ checked: selection.checked, size: :s, form: @form, - "data-action": "#{stimulus_id}#search #{stimulus_id}#sortCheckboxes", + "data-action": "#{stimulus_id}#search #{stimulus_id}#sortCheckboxes #{stimulus_id}#updateHiddenInputs", "data-#{stimulus_id}-target": "checkbox" ) %> diff --git a/admin/app/components/solidus_admin/ui/table/ransack_filter/component.js b/admin/app/components/solidus_admin/ui/table/ransack_filter/component.js index ac9489cea99..106133d0292 100644 --- a/admin/app/components/solidus_admin/ui/table/ransack_filter/component.js +++ b/admin/app/components/solidus_admin/ui/table/ransack_filter/component.js @@ -11,6 +11,7 @@ export default class extends Controller { useDebounce(this, { wait: 50 }) useClickOutside(this) this.init() + this.updateHiddenInputs() } clickOutside(event) { @@ -44,6 +45,15 @@ export default class extends Controller { this.highlightFilter() } + updateHiddenInputs() { + this.checkboxTargets.forEach((checkbox) => { + const hiddenElements = checkbox.parentElement.querySelectorAll("input[type='hidden']") + checkbox.checked + ? hiddenElements.forEach(e => e.removeAttribute("disabled")) + : hiddenElements.forEach(e => e.setAttribute("disabled", true)) + }) + } + sortCheckboxes() { const checkboxes = this.checkboxTargets From 12298c4c4feb49153338d419c65f011261503dc1 Mon Sep 17 00:00:00 2001 From: Elia Schito Date: Fri, 24 Nov 2023 10:52:29 +0100 Subject: [PATCH 02/14] Don't change the cursor for disabled button Using not-allowed can be confusing when a button is disabled temporarily while submitting a form. Co-Authored-By: Rainer Dema --- .../solidus_admin/ui/button/component.rb | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/admin/app/components/solidus_admin/ui/button/component.rb b/admin/app/components/solidus_admin/ui/button/component.rb index 39dd4d0d493..dc3daae8097 100644 --- a/admin/app/components/solidus_admin/ui/button/component.rb +++ b/admin/app/components/solidus_admin/ui/button/component.rb @@ -34,32 +34,32 @@ class SolidusAdmin::UI::Button::Component < SolidusAdmin::BaseComponent hover:text-white hover:bg-gray-600 active:text-white active:bg-gray-800 focus:text-white focus:bg-gray-700 - disabled:text-gray-400 disabled:bg-gray-100 disabled:cursor-not-allowed - aria-disabled:text-gray-400 aria-disabled:bg-gray-100 aria-disabled:aria-disabled:cursor-not-allowed + disabled:text-gray-400 disabled:bg-gray-100 + aria-disabled:text-gray-400 aria-disabled:bg-gray-100 }, secondary: %{ text-gray-700 bg-white border border-1 border-gray-200 hover:bg-gray-50 active:bg-gray-100 focus:bg-gray-50 - disabled:text-gray-300 disabled:bg-white disabled:cursor-not-allowed - aria-disabled:text-gray-300 aria-disabled:bg-white aria-disabled:cursor-not-allowed + disabled:text-gray-300 disabled:bg-white + aria-disabled:text-gray-300 aria-disabled:bg-white }, danger: %{ text-red-500 bg-white border border-1 border-red-500 hover:bg-red-500 hover:border-red-600 hover:text-white active:bg-red-600 active:border-red-700 active:text-white focus:bg-red-50 focus:bg-red-500 focus:border-red-600 focus:text-white - disabled:text-red-300 disabled:bg-white disabled:border-red-200 disabled:cursor-not-allowed - aria-disabled:text-red-300 aria-disabled:bg-white aria-disabled:border-red-200 aria-disabled:cursor-not-allowed + disabled:text-red-300 disabled:bg-white disabled:border-red-200 + aria-disabled:text-red-300 aria-disabled:bg-white aria-disabled:border-red-200 }, ghost: %{ text-gray-700 bg-transparent hover:bg-gray-50 active:bg-gray-100 focus:bg-gray-50 focus:ring-gray-300 focus:ring-2 - disabled:text-gray-300 disabled:bg-transparent disabled:border-gray-300 disabled:cursor-not-allowed - aria-disabled:text-gray-300 aria-disabled:bg-transparent aria-disabled:border-gray-300 aria-disabled:cursor-not-allowed + disabled:text-gray-300 disabled:bg-transparent disabled:border-gray-300 + aria-disabled:text-gray-300 aria-disabled:bg-transparent aria-disabled:border-gray-300 }, } From d7191a279da526daf0b64707b802f8540f7df5fd Mon Sep 17 00:00:00 2001 From: Elia Schito Date: Fri, 24 Nov 2023 10:54:58 +0100 Subject: [PATCH 03/14] Fix the store url to always have an HTTP(S) protocol defined This helps not interpreting a domain as a relative path. Also points the logo to the root of the admin. Co-Authored-By: Rainer Dema --- .../solidus_admin/layout/navigation/component.html.erb | 6 +++--- .../components/solidus_admin/layout/navigation/component.rb | 6 ++++++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/admin/app/components/solidus_admin/layout/navigation/component.html.erb b/admin/app/components/solidus_admin/layout/navigation/component.html.erb index fe5aacf0b79..1d91983404a 100644 --- a/admin/app/components/solidus_admin/layout/navigation/component.html.erb +++ b/admin/app/components/solidus_admin/layout/navigation/component.html.erb @@ -4,14 +4,14 @@ p-4 w-full " data-controller="<%= stimulus_id %>" data-<%= stimulus_id %>-cookie-value="solidus_admin"> - <%= link_to @store.url, class: "py-3 px-2 text-left flex mb-4" do %> + <%= link_to spree.admin_path, class: "py-3 px-2 text-left flex mb-4" do %> <%= image_tag @logo_path, alt: t('.visit_store'), class: "max-h-7" %> <% end %> - <%= link_to @store.url, target: :_blank, class: "flex mb-4 px-2 py-1.5 border border-gray-100 rounded-sm shadow-sm" do %> + <%= link_to @store_url, target: :_blank, class: "flex mb-4 px-2 py-1.5 border border-gray-100 rounded-sm shadow-sm" do %>

<%= @store.name %>

-

<%= @store.url %>

+

<%= @store_url %>

<%= render component("ui/icon").new(name: 'arrow-right-up-line', class: 'w-4 h-4 fill-gray-400') %> <% end %> diff --git a/admin/app/components/solidus_admin/layout/navigation/component.rb b/admin/app/components/solidus_admin/layout/navigation/component.rb index ccddd86e5a0..d1945e48570 100644 --- a/admin/app/components/solidus_admin/layout/navigation/component.rb +++ b/admin/app/components/solidus_admin/layout/navigation/component.rb @@ -15,6 +15,12 @@ def initialize( @store = store end + def before_render + url = @store.url + url = "https://#{url}" unless url.start_with?("http") + @store_url = url + end + def items @items.sort_by(&:position) end From 6def880bc625daed487c0918d8c839d506aba876 Mon Sep 17 00:00:00 2001 From: Elia Schito Date: Fri, 24 Nov 2023 11:42:14 +0100 Subject: [PATCH 04/14] Reorganize `ui/table` intializer The list of kwargs and the number of objects was very high, after this we can delegate some of the logic to supporting classes. Grouping by data and search kwargs might also help in future splitting or extraction of subcomponents. Co-Authored-By: Rainer Dema --- .../orders/index/component.html.erb | 28 +++--- .../products/index/component.html.erb | 26 ++++-- .../solidus_admin/ui/table/component.rb | 91 ++++++------------- .../ui/table/component_preview.rb | 32 ++++--- 4 files changed, 80 insertions(+), 97 deletions(-) diff --git a/admin/app/components/solidus_admin/orders/index/component.html.erb b/admin/app/components/solidus_admin/orders/index/component.html.erb index 344cce236b1..d7517f59a37 100644 --- a/admin/app/components/solidus_admin/orders/index/component.html.erb +++ b/admin/app/components/solidus_admin/orders/index/component.html.erb @@ -16,16 +16,22 @@ <%= render component('ui/table').new( id: 'orders-list', - model_class: Spree::Order, - rows: @page.records, - row_fade: row_fade, - row_url: ->(order) { spree.edit_admin_order_path(order) }, - search_key: SolidusAdmin::Config[:order_search_key], - search_url: solidus_admin.orders_path, - batch_actions: batch_actions, - filters: filters, - columns: columns, - prev_page_link: prev_page_link, - next_page_link: next_page_link, + data: { + class: Spree::Order, + rows: @page.records, + fade: row_fade, + url: ->(order) { spree.edit_admin_order_path(order) }, + batch_actions: batch_actions, + columns: columns, + prev: prev_page_link, + next: next_page_link, + }, + search: { + name: :q, + value: params[:q], + searchbar_key: SolidusAdmin::Config[:order_search_key], + url: solidus_admin.orders_path(scope: params[:scope]), + filters: filters, + }, ) %> diff --git a/admin/app/components/solidus_admin/products/index/component.html.erb b/admin/app/components/solidus_admin/products/index/component.html.erb index 5eee57b7229..0703a1839ae 100644 --- a/admin/app/components/solidus_admin/products/index/component.html.erb +++ b/admin/app/components/solidus_admin/products/index/component.html.erb @@ -13,15 +13,21 @@ <%= render component('ui/table').new( id: 'products-list', - model_class: Spree::Product, - rows: @page.records, - row_url: ->(product) { solidus_admin.product_path(product) }, - search_key: SolidusAdmin::Config[:product_search_key], - search_url: solidus_admin.products_path, - batch_actions: batch_actions, - filters: filters, - columns: columns, - prev_page_link: prev_page_link, - next_page_link: next_page_link, + data: { + class: Spree::Product, + rows: @page.records, + url: ->(product) { solidus_admin.product_path(product) }, + prev: prev_page_link, + next: next_page_link, + columns: columns, + batch_actions: batch_actions, + }, + search: { + name: :q, + value: params[:q], + url: solidus_admin.products_path, + searchbar_key: SolidusAdmin::Config[:product_search_key], + filters: filters, + }, ) %> <% end %> diff --git a/admin/app/components/solidus_admin/ui/table/component.rb b/admin/app/components/solidus_admin/ui/table/component.rb index 8008d4f38cd..da4ee4f9779 100644 --- a/admin/app/components/solidus_admin/ui/table/component.rb +++ b/admin/app/components/solidus_admin/ui/table/component.rb @@ -1,65 +1,36 @@ # frozen_string_literal: true class SolidusAdmin::UI::Table::Component < SolidusAdmin::BaseComponent - # @param id [String] A unique identifier for the table component. - # @param model_class [ActiveModel::Translation] The model class used for translations. - # @param rows [Array] The collection of objects that will be passed to columns for display. - # @param row_fade [Proc, nil] A proc determining if a row should have a faded appearance. - # @param row_url [Proc, nil] A proc that takes a row object as a parameter and returns the URL to navigate to when the row is clicked. - # @param search_param [Symbol] The param for searching. - # @param search_key [Symbol] The key for searching. - # @param search_url [String] The base URL for searching. - # - # @param columns [Array] The array of column definitions. - # @option columns [Symbol|Proc|#to_s] :header The column header. - # @option columns [Symbol|Proc|#to_s] :data The data accessor for the column. - # @option columns [String] :class_name (optional) The class name for the column. - # - # @param batch_actions [Array] The array of batch action definitions. - # @option batch_actions [String] :display_name The batch action display name. - # @option batch_actions [String] :icon The batch action icon. - # @option batch_actions [String] :action The batch action path. - # @option batch_actions [String] :method The batch action HTTP method for the provided path. - # - # @param filters [Array] The list of filter configurations to render. - # @option filters [String] :presentation The display name of the filter dropdown. - # @option filters [String] :combinator The combining logic of the filter dropdown. - # @option filters [String] :attribute The database attribute this filter modifies. - # @option filters [String] :predicate The predicate used for this filter (e.g., "eq" for equals). - # @option filters [Array] :options An array of arrays, each containing two elements: - # 1. A human-readable presentation of the filter option (e.g., "Active"). - # 2. The actual value used for filtering (e.g., "active"). - # - # @param prev_page_link [String, nil] The link to the previous page. - # @param next_page_link [String, nil] The link to the next page. - def initialize( - id:, - model_class:, - rows:, - search_key:, search_url:, search_param: :q, - row_fade: nil, - row_url: nil, - columns: [], - batch_actions: [], - filters: [], - prev_page_link: nil, - next_page_link: nil - ) - @columns = columns.map { Column.new(wrap: true, **_1) } - @batch_actions = batch_actions.map { BatchAction.new(**_1) } - @filters = filters.map { Filter.new(**_1) } + BatchAction = Struct.new(:display_name, :icon, :action, :method, keyword_init: true) # rubocop:disable Lint/StructNewOverride + Column = Struct.new(:header, :data, :col, :wrap, keyword_init: true) + Filter = Struct.new(:presentation, :combinator, :attribute, :predicate, :options, keyword_init: true) + private_constant :BatchAction, :Column, :Filter + + Data = Struct.new(:rows, :class, :url, :prev, :next, :columns, :fade, :batch_actions, keyword_init: true) # rubocop:disable Lint/StructNewOverride + Search = Struct.new(:name, :value, :url, :searchbar_key, :filters, :scopes, keyword_init: true) + + def initialize(id:, data:, search: nil) @id = id - @model_class = model_class - @rows = rows - @row_fade = row_fade - @row_url = row_url - @search_param = search_param - @search_key = search_key - @search_url = search_url - @prev_page_link = prev_page_link - @next_page_link = next_page_link - - @columns.unshift selectable_column if batch_actions.present? + @data = Data.new(**data) + @search = Search.new(**search) + + # Data + @columns = @data.columns.map { Column.new(wrap: true, **_1) } + @columns.unshift selectable_column if @data.batch_actions.present? + @batch_actions = @data.batch_actions&.map { BatchAction.new(**_1) } + @model_class = data[:class] + @rows = @data.rows + @row_fade = @data.fade + @row_url = @data.url + @prev_page_link = @data.prev + @next_page_link = @data.next + + # Search + @filters = @search.filters.map { Filter.new(**_1) } + @search_param = @search.name + @search_params = @search.value + @search_key = @search.searchbar_key + @search_url = @search.url end def resource_plural_name @@ -161,8 +132,4 @@ def render_data_cell(column, data) ") end - Column = Struct.new(:header, :data, :col, :wrap, keyword_init: true) - BatchAction = Struct.new(:display_name, :icon, :action, :method, keyword_init: true) # rubocop:disable Lint/StructNewOverride - Filter = Struct.new(:presentation, :combinator, :attribute, :predicate, :options, keyword_init: true) - private_constant :Column, :BatchAction, :Filter end diff --git a/admin/spec/components/previews/solidus_admin/ui/table/component_preview.rb b/admin/spec/components/previews/solidus_admin/ui/table/component_preview.rb index c781544b555..c5f357fa6b2 100644 --- a/admin/spec/components/previews/solidus_admin/ui/table/component_preview.rb +++ b/admin/spec/components/previews/solidus_admin/ui/table/component_preview.rb @@ -14,21 +14,25 @@ class SolidusAdmin::UI::Table::ComponentPreview < ViewComponent::Preview def simple render current_component.new( id: 'simple-list', - model_class: Spree::Product, - rows: Array.new(10) { |n| - Spree::Product.new(id: n, name: "Product #{n}", price: n * 10.0, available_on: n.days.ago) + data: { + class: Spree::Product, + rows: Array.new(10) { |n| + Spree::Product.new(id: n, name: "Product #{n}", price: n * 10.0, available_on: n.days.ago) + }, + columns: [ + { header: :id, data: -> { _1.id.to_s } }, + { header: :name, data: :name }, + { header: -> { "Availability at #{Time.current}" }, data: -> { "#{time_ago_in_words _1.available_on} ago" } }, + { header: -> { component("ui/badge").new(name: "$$$") }, data: -> { component("ui/badge").new(name: _1.display_price, color: :green) } }, + { header: "Generated at", data: Time.current.to_s }, + ], + prev: nil, + next: '#2', + }, + search: { + name: :no_key, + url: '#', }, - search_key: :no_key, - search_url: '#', - columns: [ - { header: :id, data: -> { _1.id.to_s } }, - { header: :name, data: :name }, - { header: -> { "Availability at #{Time.current}" }, data: -> { "#{time_ago_in_words _1.available_on} ago" } }, - { header: -> { component("ui/badge").new(name: "$$$") }, data: -> { component("ui/badge").new(name: _1.display_price, color: :green) } }, - { header: "Generated at", data: Time.current.to_s }, - ], - prev_page_link: nil, - next_page_link: '#2', ) end end From c09369acd44673183c26aa28cfb47e5d89488fe2 Mon Sep 17 00:00:00 2001 From: Elia Schito Date: Fri, 24 Nov 2023 11:45:29 +0100 Subject: [PATCH 05/14] Let the first render of `ui/table` already hide toolbars based on the mode Co-Authored-By: Rainer Dema --- .../solidus_admin/ui/table/component.html.erb | 11 ++++++----- .../components/solidus_admin/ui/table/component.rb | 4 ++++ 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/admin/app/components/solidus_admin/ui/table/component.html.erb b/admin/app/components/solidus_admin/ui/table/component.html.erb index bf456657e1d..8624fd3a0af 100644 --- a/admin/app/components/solidus_admin/ui/table/component.html.erb +++ b/admin/app/components/solidus_admin/ui/table/component.html.erb @@ -6,13 +6,14 @@ " data-controller="<%= stimulus_id %>" data-<%= stimulus_id %>-selected-row-class="bg-gray-15" + data-<%= stimulus_id %>-mode-value="<%= initial_mode %>" data-action=" <%= component("ui/table/ransack_filter").stimulus_id %>:search-><%= stimulus_id %>#search <%= component("ui/table/ransack_filter").stimulus_id %>:showSearch-><%= stimulus_id %>#showSearch " >
- <%= render component("ui/table/toolbar").new("data-#{stimulus_id}-target": "searchToolbar") do %> + <%= render component("ui/table/toolbar").new("data-#{stimulus_id}-target": "searchToolbar", hidden: initial_mode != "search") do %> <%= form_with( url: @search_url, method: :get, @@ -30,7 +31,7 @@ value: params.dig(@search_param, @search_key), placeholder: t('.search_placeholder', resources: resource_plural_name), "data-#{stimulus_id}-target": "searchField", - "aria-label": t('.search_placeholder', resources: resource_plural_name) + "aria-label": t('.search_placeholder', resources: resource_plural_name), ) %> <% end %> @@ -44,14 +45,14 @@ <% end %> <% if @filters.any? %> - <%= render component("ui/table/toolbar").new("data-#{stimulus_id}-target": "filterToolbar") do %> + <%= render component("ui/table/toolbar").new("data-#{stimulus_id}-target": "filterToolbar", hidden: initial_mode != "search") do %> <% @filters.each_with_index do |filter, index| %> <%= render_ransack_filter_dropdown(filter, index) %> <% end %> <% end %> <% end %> - <%= render component("ui/table/toolbar").new("data-#{stimulus_id}-target": "scopesToolbar") do %> + <%= render component("ui/table/toolbar").new("data-#{stimulus_id}-target": "scopesToolbar", hidden: initial_mode != "scopes") do %>
<%= render component("ui/tab").new(text: "All", current: true, href: "") %>
@@ -65,7 +66,7 @@ <% end %>
- <%= render component("ui/table/toolbar").new("data-#{stimulus_id}-target": "batchToolbar", role: "toolbar", "aria-label": t(".batch_actions")) do %> + <%= render component("ui/table/toolbar").new("data-#{stimulus_id}-target": "batchToolbar", role: "toolbar", "aria-label": t(".batch_actions"), hidden: true) do %> <%= form_tag '', id: batch_actions_form_id %> <% @batch_actions.each do |batch_action| %> <%= render_batch_action_button(batch_action) %> diff --git a/admin/app/components/solidus_admin/ui/table/component.rb b/admin/app/components/solidus_admin/ui/table/component.rb index da4ee4f9779..0cc60507f2d 100644 --- a/admin/app/components/solidus_admin/ui/table/component.rb +++ b/admin/app/components/solidus_admin/ui/table/component.rb @@ -132,4 +132,8 @@ def render_data_cell(column, data) ") end + + def initial_mode + @initial_mode ||= params.dig(@search_param, @search_key) ? "search" : "scopes" + end end From bfae4927858a95daaf58593f9e1363b81b4b533b Mon Sep 17 00:00:00 2001 From: Elia Schito Date: Fri, 24 Nov 2023 11:47:24 +0100 Subject: [PATCH 06/14] Reset `ui/table` filters when exiting clicking on "cancel" from search mode Co-Authored-By: Rainer Dema --- .../solidus_admin/ui/table/component.js | 20 ++++++++++--------- .../table/ransack_filter/component.html.erb | 4 ++-- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/admin/app/components/solidus_admin/ui/table/component.js b/admin/app/components/solidus_admin/ui/table/component.js index d8e4ca7bf54..d6a5b196f9e 100644 --- a/admin/app/components/solidus_admin/ui/table/component.js +++ b/admin/app/components/solidus_admin/ui/table/component.js @@ -29,12 +29,6 @@ export default class extends Controller { this.search = debounce(this.search.bind(this), 200) } - connect() { - if (this.searchFieldTarget.value !== "") this.modeValue = "search" - - this.render() - } - showSearch(event) { this.modeValue = "search" this.render() @@ -51,10 +45,18 @@ export default class extends Controller { } cancelSearch() { - this.clearSearch() + this.resetFilters() + this.search() + } - this.modeValue = "scopes" - this.render() + resetFilters() { + if (!this.hasFilterToolbarTarget) return + + for (const fieldset of this.filterToolbarTarget.querySelectorAll('fieldset')) { + fieldset.setAttribute('disabled', true) + } + this.searchFieldTarget.setAttribute('disabled', true) + this.searchFormTarget.submit() } selectRow(event) { diff --git a/admin/app/components/solidus_admin/ui/table/ransack_filter/component.html.erb b/admin/app/components/solidus_admin/ui/table/ransack_filter/component.html.erb index 951fac2f21c..76d2a121392 100644 --- a/admin/app/components/solidus_admin/ui/table/ransack_filter/component.html.erb +++ b/admin/app/components/solidus_admin/ui/table/ransack_filter/component.html.erb @@ -1,4 +1,4 @@ -
+
@@ -68,4 +68,4 @@
- + From cddacffceece7f2800e80c363f7c98ef2f06f696 Mon Sep 17 00:00:00 2001 From: Elia Schito Date: Fri, 24 Nov 2023 11:55:35 +0100 Subject: [PATCH 07/14] Extract common search patterns to a controller helper This will be also the home for the upcoming scope support. Co-Authored-By: Rainer Dema --- .../controller_helpers/search.rb | 17 ++++++++++ .../solidus_admin/orders_controller.rb | 9 ++--- .../solidus_admin/products_controller.rb | 34 ++++++++++--------- admin/config/routes.rb | 1 + 4 files changed, 41 insertions(+), 20 deletions(-) create mode 100644 admin/app/controllers/solidus_admin/controller_helpers/search.rb diff --git a/admin/app/controllers/solidus_admin/controller_helpers/search.rb b/admin/app/controllers/solidus_admin/controller_helpers/search.rb new file mode 100644 index 00000000000..18b7732745a --- /dev/null +++ b/admin/app/controllers/solidus_admin/controller_helpers/search.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module SolidusAdmin::ControllerHelpers::Search + extend ActiveSupport::Concern + + private + + def apply_search_to(relation, param:) + apply_ransack_search_to(relation, param: param) + end + + def apply_ransack_search_to(relation, param:) + relation + .ransack(params[param]&.except(:scope)) + .result(distinct: true) + end +end diff --git a/admin/app/controllers/solidus_admin/orders_controller.rb b/admin/app/controllers/solidus_admin/orders_controller.rb index 9b45c6bce3b..6d36d2849a3 100644 --- a/admin/app/controllers/solidus_admin/orders_controller.rb +++ b/admin/app/controllers/solidus_admin/orders_controller.rb @@ -3,12 +3,13 @@ module SolidusAdmin class OrdersController < SolidusAdmin::BaseController include Spree::Core::ControllerHelpers::StrongParameters + include SolidusAdmin::ControllerHelpers::Search def index - orders = Spree::Order - .order(created_at: :desc, id: :desc) - .ransack(params[:q]) - .result(distinct: true) + orders = apply_search_to( + Spree::Order.order(created_at: :desc, id: :desc), + param: :q, + ) set_page_and_extract_portion_from( orders, diff --git a/admin/app/controllers/solidus_admin/products_controller.rb b/admin/app/controllers/solidus_admin/products_controller.rb index d4a2d6550d8..c970d13004d 100644 --- a/admin/app/controllers/solidus_admin/products_controller.rb +++ b/admin/app/controllers/solidus_admin/products_controller.rb @@ -2,6 +2,24 @@ module SolidusAdmin class ProductsController < SolidusAdmin::BaseController + include SolidusAdmin::ControllerHelpers::Search + + def index + products = apply_search_to( + Spree::Product.order(created_at: :desc, id: :desc), + param: :q, + ) + + set_page_and_extract_portion_from( + products, + per_page: SolidusAdmin::Config[:products_per_page] + ) + + respond_to do |format| + format.html { render component('products/index').new(page: @page) } + end + end + def edit redirect_to action: :show end @@ -33,22 +51,6 @@ def update end end - def index - products = Spree::Product - .order(created_at: :desc, id: :desc) - .ransack(params[:q]) - .result(distinct: true) - - set_page_and_extract_portion_from( - products, - per_page: SolidusAdmin::Config[:products_per_page] - ) - - respond_to do |format| - format.html { render component('products/index').new(page: @page) } - end - end - def destroy @products = Spree::Product.where(id: params[:id]) diff --git a/admin/config/routes.rb b/admin/config/routes.rb index 271e8b33b26..587c5599468 100644 --- a/admin/config/routes.rb +++ b/admin/config/routes.rb @@ -2,6 +2,7 @@ SolidusAdmin::Engine.routes.draw do resource :account, only: :show + resources( :products, only: [:index, :show, :edit, :update], From 1406dad17af6bdd0a49c469ef9658b5d30dace54 Mon Sep 17 00:00:00 2001 From: Elia Schito Date: Fri, 24 Nov 2023 11:58:33 +0100 Subject: [PATCH 08/14] Add support for scopes to `ui/table` Scopes are alternative pools of record on which further filtering can be performed. Co-Authored-By: Rainer Dema --- .../orders/index/component.html.erb | 1 + .../solidus_admin/orders/index/component.rb | 10 ++++++ .../solidus_admin/orders/index/component.yml | 6 ++++ .../products/index/component.html.erb | 1 + .../solidus_admin/products/index/component.rb | 7 +++++ .../products/index/component.yml | 3 ++ .../solidus_admin/ui/table/component.html.erb | 15 ++++++++- .../solidus_admin/ui/table/component.rb | 11 ++++++- .../controller_helpers/search.rb | 31 +++++++++++++++++++ .../solidus_admin/orders_controller.rb | 6 ++++ .../solidus_admin/products_controller.rb | 3 ++ admin/spec/features/orders_spec.rb | 1 + 12 files changed, 93 insertions(+), 2 deletions(-) diff --git a/admin/app/components/solidus_admin/orders/index/component.html.erb b/admin/app/components/solidus_admin/orders/index/component.html.erb index d7517f59a37..5a0440033ac 100644 --- a/admin/app/components/solidus_admin/orders/index/component.html.erb +++ b/admin/app/components/solidus_admin/orders/index/component.html.erb @@ -32,6 +32,7 @@ searchbar_key: SolidusAdmin::Config[:order_search_key], url: solidus_admin.orders_path(scope: params[:scope]), filters: filters, + scopes: scopes, }, ) %> diff --git a/admin/app/components/solidus_admin/orders/index/component.rb b/admin/app/components/solidus_admin/orders/index/component.rb index 57df251d56e..c635d8540a6 100644 --- a/admin/app/components/solidus_admin/orders/index/component.rb +++ b/admin/app/components/solidus_admin/orders/index/component.rb @@ -23,6 +23,16 @@ def batch_actions [] end + def scopes + [ + { label: t('.scopes.complete'), name: 'completed', default: true }, + { label: t('.scopes.in_progress'), name: 'in_progress' }, + { label: t('.scopes.returned'), name: 'returned' }, + { label: t('.scopes.canceled'), name: 'canceled' }, + { label: t('.scopes.all_orders'), name: 'all' }, + ] + end + def filters [ { diff --git a/admin/app/components/solidus_admin/orders/index/component.yml b/admin/app/components/solidus_admin/orders/index/component.yml index a94a884aa5e..85613782601 100644 --- a/admin/app/components/solidus_admin/orders/index/component.yml +++ b/admin/app/components/solidus_admin/orders/index/component.yml @@ -15,3 +15,9 @@ en: date: formats: short: '%d %b %y' + scopes: + all_orders: All + canceled: Canceled + complete: Complete + returned: Returned + in_progress: In Progress diff --git a/admin/app/components/solidus_admin/products/index/component.html.erb b/admin/app/components/solidus_admin/products/index/component.html.erb index 0703a1839ae..f3033efaeb2 100644 --- a/admin/app/components/solidus_admin/products/index/component.html.erb +++ b/admin/app/components/solidus_admin/products/index/component.html.erb @@ -28,6 +28,7 @@ url: solidus_admin.products_path, searchbar_key: SolidusAdmin::Config[:product_search_key], filters: filters, + scopes: scopes, }, ) %> <% end %> diff --git a/admin/app/components/solidus_admin/products/index/component.rb b/admin/app/components/solidus_admin/products/index/component.rb index eca8bee1509..a53adb8ce7e 100644 --- a/admin/app/components/solidus_admin/products/index/component.rb +++ b/admin/app/components/solidus_admin/products/index/component.rb @@ -59,6 +59,13 @@ def filters end end + def scopes + [ + { name: :all, label: t('.scopes.all'), default: true }, + { name: :deleted, label: t('.scopes.deleted') }, + ] + end + def columns [ image_column, diff --git a/admin/app/components/solidus_admin/products/index/component.yml b/admin/app/components/solidus_admin/products/index/component.yml index cc6fd827a10..4de6e6f3e48 100644 --- a/admin/app/components/solidus_admin/products/index/component.yml +++ b/admin/app/components/solidus_admin/products/index/component.yml @@ -7,3 +7,6 @@ en: activate: 'Activate' filters: with_deleted: Include deleted + scopes: + all: All + deleted: Deleted diff --git a/admin/app/components/solidus_admin/ui/table/component.html.erb b/admin/app/components/solidus_admin/ui/table/component.html.erb index 8624fd3a0af..dd1e7eccc86 100644 --- a/admin/app/components/solidus_admin/ui/table/component.html.erb +++ b/admin/app/components/solidus_admin/ui/table/component.html.erb @@ -26,6 +26,7 @@ "data-action": "input->#{stimulus_id}#search change->#{stimulus_id}#search", }, ) do |form| %> + <%= hidden_field_tag scope_param_name, current_scope_name %> <%= render component('ui/forms/search_field').new( name: "#{@search_param}[#{@search_key}]", value: params.dig(@search_param, @search_key), @@ -54,7 +55,19 @@ <%= render component("ui/table/toolbar").new("data-#{stimulus_id}-target": "scopesToolbar", hidden: initial_mode != "scopes") do %>
- <%= render component("ui/tab").new(text: "All", current: true, href: "") %> + <%= form_with(url: @search_url, method: :get) do %> + <% @scopes.each do |scope| %> + <%= render component("ui/tab").new( + tag: :button, + type: :submit, + text: scope.label, + current: current_scope_name == scope.name.to_s, + name: scope_param_name, + value: scope.name, + ) %> + <%#= render component("ui/tab").new(text: scope.label, current: current, href: scope.path) %> + <% end %> + <% end %>
<%= render component("ui/button").new( diff --git a/admin/app/components/solidus_admin/ui/table/component.rb b/admin/app/components/solidus_admin/ui/table/component.rb index 0cc60507f2d..c5d9e1ab380 100644 --- a/admin/app/components/solidus_admin/ui/table/component.rb +++ b/admin/app/components/solidus_admin/ui/table/component.rb @@ -4,7 +4,8 @@ class SolidusAdmin::UI::Table::Component < SolidusAdmin::BaseComponent BatchAction = Struct.new(:display_name, :icon, :action, :method, keyword_init: true) # rubocop:disable Lint/StructNewOverride Column = Struct.new(:header, :data, :col, :wrap, keyword_init: true) Filter = Struct.new(:presentation, :combinator, :attribute, :predicate, :options, keyword_init: true) - private_constant :BatchAction, :Column, :Filter + Scope = Struct.new(:name, :label, :default, keyword_init: true) + private_constant :BatchAction, :Column, :Filter, :Scope Data = Struct.new(:rows, :class, :url, :prev, :next, :columns, :fade, :batch_actions, keyword_init: true) # rubocop:disable Lint/StructNewOverride Search = Struct.new(:name, :value, :url, :searchbar_key, :filters, :scopes, keyword_init: true) @@ -27,6 +28,7 @@ def initialize(id:, data:, search: nil) # Search @filters = @search.filters.map { Filter.new(**_1) } + @scopes = @search.scopes.map { Scope.new(**_1) } @search_param = @search.name @search_params = @search.value @search_key = @search.searchbar_key @@ -132,6 +134,13 @@ def render_data_cell(column, data) ") end + def current_scope_name + @current_scope_name ||= params.dig(@search_param, :scope).presence || @scopes.find(&:default)&.name&.to_s + end + + def scope_param_name + @scope_param_name ||= "#{@search_param}[scope]" + end def initial_mode @initial_mode ||= params.dig(@search_param, @search_key) ? "search" : "scopes" diff --git a/admin/app/controllers/solidus_admin/controller_helpers/search.rb b/admin/app/controllers/solidus_admin/controller_helpers/search.rb index 18b7732745a..3f40fb20fc5 100644 --- a/admin/app/controllers/solidus_admin/controller_helpers/search.rb +++ b/admin/app/controllers/solidus_admin/controller_helpers/search.rb @@ -3,9 +3,24 @@ module SolidusAdmin::ControllerHelpers::Search extend ActiveSupport::Concern + module ClassMethods + def search_scope(name, default: false, &block) + search_scopes << SearchScope.new( + name: name.to_s, + block: block, + default: default, + ) + end + + def search_scopes + @search_scopes ||= [] + end + end + private def apply_search_to(relation, param:) + relation = apply_scopes_to(relation, param: param) apply_ransack_search_to(relation, param: param) end @@ -14,4 +29,20 @@ def apply_ransack_search_to(relation, param:) .ransack(params[param]&.except(:scope)) .result(distinct: true) end + + def apply_scopes_to(relation, param:) + current_scope_name = params.dig(param, :scope) + + search_block = ( + self.class.search_scopes.find { _1.name == current_scope_name } || + self.class.search_scopes.find { _1.default } + )&.block + + # Run the search if a block is present, fall back to the relation even if the + # block is present but returns nil. + (search_block && instance_exec(relation, &search_block)) || relation + end + + SearchScope = Struct.new(:name, :block, :default, keyword_init: true) + private_constant :SearchScope end diff --git a/admin/app/controllers/solidus_admin/orders_controller.rb b/admin/app/controllers/solidus_admin/orders_controller.rb index 6d36d2849a3..263a484fd7e 100644 --- a/admin/app/controllers/solidus_admin/orders_controller.rb +++ b/admin/app/controllers/solidus_admin/orders_controller.rb @@ -5,6 +5,12 @@ class OrdersController < SolidusAdmin::BaseController include Spree::Core::ControllerHelpers::StrongParameters include SolidusAdmin::ControllerHelpers::Search + search_scope(:completed, default: true) { _1.complete } + search_scope(:canceled) { _1.canceled } + search_scope(:returned) { _1.with_state(:returned) } + search_scope(:in_progress) { _1.with_state([:cart] + _1.checkout_step_names) } + search_scope(:all) { _1 } + def index orders = apply_search_to( Spree::Order.order(created_at: :desc, id: :desc), diff --git a/admin/app/controllers/solidus_admin/products_controller.rb b/admin/app/controllers/solidus_admin/products_controller.rb index c970d13004d..c82a5ebd1a8 100644 --- a/admin/app/controllers/solidus_admin/products_controller.rb +++ b/admin/app/controllers/solidus_admin/products_controller.rb @@ -4,6 +4,9 @@ module SolidusAdmin class ProductsController < SolidusAdmin::BaseController include SolidusAdmin::ControllerHelpers::Search + search_scope(:all, default: true) + search_scope(:deleted) { _1.with_discarded.discarded } + def index products = apply_search_to( Spree::Product.order(created_at: :desc, id: :desc), diff --git a/admin/spec/features/orders_spec.rb b/admin/spec/features/orders_spec.rb index 4761d320585..ddc8f83f5ff 100644 --- a/admin/spec/features/orders_spec.rb +++ b/admin/spec/features/orders_spec.rb @@ -9,6 +9,7 @@ create(:order, number: "R123456789", total: 19.99) visit "/admin/orders" + click_on "In Progress" expect(page).to have_content("R123456789") expect(page).to have_content("$19.99") From c7f7af54b25afb234222c1527e19c16dcfbc7e23 Mon Sep 17 00:00:00 2001 From: Elia Schito Date: Fri, 24 Nov 2023 11:58:48 +0100 Subject: [PATCH 09/14] Add a state column to `orders/index` Co-Authored-By: Rainer Dema --- .../solidus_admin/orders/index/component.rb | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/admin/app/components/solidus_admin/orders/index/component.rb b/admin/app/components/solidus_admin/orders/index/component.rb index c635d8540a6..407e5b710fa 100644 --- a/admin/app/components/solidus_admin/orders/index/component.rb +++ b/admin/app/components/solidus_admin/orders/index/component.rb @@ -102,6 +102,7 @@ def filters def columns [ number_column, + state_column, date_column, customer_column, total_column, @@ -124,6 +125,21 @@ def number_column } end + def state_column + { + header: :state, + data: ->(order) do + color = { + 'complete' => :green, + 'returned' => :red, + 'canceled' => :blue, + 'cart' => :graphite_light, + }[order.state] || :yellow + component('ui/badge').new(name: order.state.humanize, color: color) + end + } + end + def date_column { header: :date, From 9ad6bd6597f3c3ae533f89728e5662015ab01ed8 Mon Sep 17 00:00:00 2001 From: Elia Schito Date: Fri, 24 Nov 2023 12:21:55 +0100 Subject: [PATCH 10/14] Only hide `ui/table` cells overflow horizontally when wrapped --- admin/app/components/solidus_admin/ui/table/component.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/admin/app/components/solidus_admin/ui/table/component.rb b/admin/app/components/solidus_admin/ui/table/component.rb index c5d9e1ab380..edc454ea995 100644 --- a/admin/app/components/solidus_admin/ui/table/component.rb +++ b/admin/app/components/solidus_admin/ui/table/component.rb @@ -126,7 +126,7 @@ def render_data_cell(column, data) cell = cell.call(data) if cell.respond_to?(:call) cell = data.public_send(cell) if cell.is_a?(Symbol) cell = cell.render_in(self) if cell.respond_to?(:render_in) - cell = tag.div(cell, class: "flex items-center gap-1.5 justify-start overflow-hidden") if column.wrap + cell = tag.div(cell, class: "flex items-center gap-1.5 justify-start overflow-x-hidden") if column.wrap tag.td(cell, class: " py-2 px-4 h-10 vertical-align-middle leading-none From 00d145eeadd7ddc5655d95cb421eb87f80d88832 Mon Sep 17 00:00:00 2001 From: Elia Schito Date: Fri, 24 Nov 2023 12:35:42 +0100 Subject: [PATCH 11/14] Allow the `ui/tab` component to pick an arbitrary tag --- admin/app/components/solidus_admin/ui/tab/component.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/admin/app/components/solidus_admin/ui/tab/component.rb b/admin/app/components/solidus_admin/ui/tab/component.rb index fbd13487b1e..20dac725aa6 100644 --- a/admin/app/components/solidus_admin/ui/tab/component.rb +++ b/admin/app/components/solidus_admin/ui/tab/component.rb @@ -7,7 +7,8 @@ class SolidusAdmin::UI::Tab::Component < SolidusAdmin::BaseComponent l: %w[h-12 px-4 body-text-bold], } - def initialize(text:, size: :m, current: false, disabled: false, **attributes) + def initialize(text:, tag: :a, size: :m, current: false, disabled: false, **attributes) + @tag = tag @text = text @size = size @attributes = attributes @@ -35,7 +36,7 @@ def initialize(text:, size: :m, current: false, disabled: false, **attributes) def call content_tag( - :a, + @tag, @text, **@attributes ) From ff3c195e9586680b8bd60682a2ed70163d67ab6d Mon Sep 17 00:00:00 2001 From: Elia Schito Date: Fri, 24 Nov 2023 12:36:31 +0100 Subject: [PATCH 12/14] Adjust the `ui/tab` colors so when disabled by a form submission they won't flash --- admin/app/components/solidus_admin/ui/tab/component.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/admin/app/components/solidus_admin/ui/tab/component.rb b/admin/app/components/solidus_admin/ui/tab/component.rb index 20dac725aa6..f9b74dd4bee 100644 --- a/admin/app/components/solidus_admin/ui/tab/component.rb +++ b/admin/app/components/solidus_admin/ui/tab/component.rb @@ -23,11 +23,11 @@ def initialize(text:, tag: :a, size: :m, current: false, disabled: false, **attr hover:bg-gray-75 hover:text-gray-700 focus:bg-gray-25 focus:text-gray-700 - active:bg-gray-50 active:text-black + active:bg-gray-75 active:text-black aria-current:bg-gray-50 aria-current:text-black - disabled:bg-gray-100 disabled:text-gray-400 - aria-disabled:bg-gray-100 aria-disabled:text-gray-400 + disabled:bg-gray-75 disabled:text-gray-400 + aria-disabled:bg-gray-75 aria-disabled:text-gray-400 ], SIZES.fetch(@size.to_sym), @attributes.delete(:class), From 34faee9fcc13150083afc2d9d2dcaa3968586a19 Mon Sep 17 00:00:00 2001 From: Elia Schito Date: Fri, 24 Nov 2023 13:05:08 +0100 Subject: [PATCH 13/14] Refactor `ui/table` to use the internal objects instead of proxy ivars --- .../solidus_admin/ui/table/component.html.erb | 61 +++++++------- .../solidus_admin/ui/table/component.rb | 79 +++++++++++-------- 2 files changed, 77 insertions(+), 63 deletions(-) diff --git a/admin/app/components/solidus_admin/ui/table/component.html.erb b/admin/app/components/solidus_admin/ui/table/component.html.erb index dd1e7eccc86..8c6d7635bd4 100644 --- a/admin/app/components/solidus_admin/ui/table/component.html.erb +++ b/admin/app/components/solidus_admin/ui/table/component.html.erb @@ -15,7 +15,7 @@
<%= render component("ui/table/toolbar").new("data-#{stimulus_id}-target": "searchToolbar", hidden: initial_mode != "search") do %> <%= form_with( - url: @search_url, + url: @search.url, method: :get, html: { id: search_form_id, @@ -26,13 +26,15 @@ "data-action": "input->#{stimulus_id}#search change->#{stimulus_id}#search", }, ) do |form| %> - <%= hidden_field_tag scope_param_name, current_scope_name %> + <%= hidden_field_tag @search.scope_param_name, @search.current_scope.name if @search.scopes.present? %> <%= render component('ui/forms/search_field').new( - name: "#{@search_param}[#{@search_key}]", - value: params.dig(@search_param, @search_key), - placeholder: t('.search_placeholder', resources: resource_plural_name), + name: @search.searchbar_param_name, + value: @search.value[@search.searchbar_key], + placeholder: t('.search_placeholder', resources: @data.plural_name), + "aria-label": t('.search_placeholder', resources: @data.plural_name), "data-#{stimulus_id}-target": "searchField", - "aria-label": t('.search_placeholder', resources: resource_plural_name), + "data-turbo-permanent": "true", + id: "#{stimulus_id}-search-field-#{@id}", ) %> <% end %> @@ -45,9 +47,9 @@
<% end %> - <% if @filters.any? %> + <% if @search.filters.any? %> <%= render component("ui/table/toolbar").new("data-#{stimulus_id}-target": "filterToolbar", hidden: initial_mode != "search") do %> - <% @filters.each_with_index do |filter, index| %> + <% @search.filters.each_with_index do |filter, index| %> <%= render_ransack_filter_dropdown(filter, index) %> <% end %> <% end %> @@ -55,17 +57,16 @@ <%= render component("ui/table/toolbar").new("data-#{stimulus_id}-target": "scopesToolbar", hidden: initial_mode != "scopes") do %>
- <%= form_with(url: @search_url, method: :get) do %> - <% @scopes.each do |scope| %> + <%= form_with(url: @search.url, method: :get) do %> + <% @search.scopes.each do |scope| %> <%= render component("ui/tab").new( tag: :button, type: :submit, text: scope.label, - current: current_scope_name == scope.name.to_s, - name: scope_param_name, + current: scope == @search.current_scope, + name: @search.scope_param_name, value: scope.name, ) %> - <%#= render component("ui/tab").new(text: scope.label, current: current, href: scope.path) %> <% end %> <% end %>
@@ -81,7 +82,7 @@ <%= render component("ui/table/toolbar").new("data-#{stimulus_id}-target": "batchToolbar", role: "toolbar", "aria-label": t(".batch_actions"), hidden: true) do %> <%= form_tag '', id: batch_actions_form_id %> - <% @batch_actions.each do |batch_action| %> + <% @data.batch_actions.each do |batch_action| %> <%= render_batch_action_button(batch_action) %> <% end %> <% end %> @@ -89,7 +90,7 @@ <%= turbo_frame_tag table_frame_id, target: "_top" do %> - <% @columns.each do |column| %> + <% @data.columns.each do |column| %> "> <% end %> @@ -99,13 +100,13 @@ data-<%= stimulus_id %>-target="defaultHeader" > - <% @columns.each do |column| %> + <% @data.columns.each do |column| %> <%= render_header_cell(column.header) %> <% end %> - <% if @batch_actions %> + <% if @data.batch_actions %> -target="batchHeader" class="bg-white color-black text-xs leading-none text-left" @@ -116,46 +117,46 @@ <%= render_header_cell(content_tag(:div, safe_join([ content_tag(:span, "0", "data-#{stimulus_id}-target": "selectedRowsCount"), " #{t('.rows_selected')}.", - ])), colspan: @columns.count - 1) %> + ])), colspan: @data.columns.count - 1) %> <% end %> - <% @rows.each do |row| %> + <% @data.rows.each do |row| %> + class="border-b border-gray-100 last:border-0 hover:bg-gray-50 cursor-pointer <%= 'bg-gray-15 text-gray-700' if @data.fade&.call(row) %>" + <% if @data.url %> data-action="click-><%= stimulus_id %>#rowClicked" - data-<%= stimulus_id %>-url-param="<%= @row_url.call(row) %>" + data-<%= stimulus_id %>-url-param="<%= @data.url.call(row) %>" <% end %> > - <% @columns.each do |column| %> + <% @data.columns.each do |column| %> <%= render_data_cell(column, row) %> <% end %> <% end %> - <% if @rows.empty? && @model_class %> + <% if @data.rows.empty? && @data.plural_name %> <% end %> - <% if @prev_page_link || @next_page_link %> + <% if @data.prev || @data.next %> - diff --git a/admin/app/components/solidus_admin/ui/table/component.rb b/admin/app/components/solidus_admin/ui/table/component.rb index edc454ea995..481ba744511 100644 --- a/admin/app/components/solidus_admin/ui/table/component.rb +++ b/admin/app/components/solidus_admin/ui/table/component.rb @@ -7,36 +7,53 @@ class SolidusAdmin::UI::Table::Component < SolidusAdmin::BaseComponent Scope = Struct.new(:name, :label, :default, keyword_init: true) private_constant :BatchAction, :Column, :Filter, :Scope - Data = Struct.new(:rows, :class, :url, :prev, :next, :columns, :fade, :batch_actions, keyword_init: true) # rubocop:disable Lint/StructNewOverride - Search = Struct.new(:name, :value, :url, :searchbar_key, :filters, :scopes, keyword_init: true) + class Data < Struct.new(:rows, :class, :url, :prev, :next, :columns, :fade, :batch_actions, keyword_init: true) # rubocop:disable Lint/StructNewOverride,Style/StructInheritance + def initialize(**args) + super + + self.columns = columns.map { |column| Column.new(wrap: false, **column) } + self.batch_actions = batch_actions.to_a.map { |action| BatchAction.new(**action) } + end + + def plural_name + self[:class].model_name.human.pluralize if self[:class] + end + end + + class Search < Struct.new(:name, :value, :url, :searchbar_key, :filters, :scopes, keyword_init: true) # rubocop:disable Style/StructInheritance + def initialize(**args) + super + + self.filters = filters.to_a.map { |filter| Filter.new(**filter) } + self.scopes = scopes.to_a.map { |scope| Scope.new(**scope) } + end + + def current_scope + scopes.find { |scope| scope.name.to_s == value[:scope].presence } || default_scope + end + + def default_scope + scopes.find(&:default) + end + + def scope_param_name + "#{name}[scope]" + end + + def searchbar_param_name + "#{name}[#{searchbar_key}]" + end + + def value + super || {} + end + end def initialize(id:, data:, search: nil) @id = id @data = Data.new(**data) + @data.columns.unshift selectable_column if @data.batch_actions.present? @search = Search.new(**search) - - # Data - @columns = @data.columns.map { Column.new(wrap: true, **_1) } - @columns.unshift selectable_column if @data.batch_actions.present? - @batch_actions = @data.batch_actions&.map { BatchAction.new(**_1) } - @model_class = data[:class] - @rows = @data.rows - @row_fade = @data.fade - @row_url = @data.url - @prev_page_link = @data.prev - @next_page_link = @data.next - - # Search - @filters = @search.filters.map { Filter.new(**_1) } - @scopes = @search.scopes.map { Scope.new(**_1) } - @search_param = @search.name - @search_params = @search.value - @search_key = @search.searchbar_key - @search_url = @search.url - end - - def resource_plural_name - @model_class.model_name.human.pluralize end def selectable_column @@ -95,7 +112,7 @@ def render_batch_action_button(batch_action) def render_ransack_filter_dropdown(filter, index) render component("ui/table/ransack_filter").new( presentation: filter.presentation, - search_param: @search_param, + search_param: @search.name, combinator: filter.combinator, attribute: filter.attribute, predicate: filter.predicate, @@ -107,7 +124,7 @@ def render_ransack_filter_dropdown(filter, index) def render_header_cell(cell, **attrs) cell = cell.call if cell.respond_to?(:call) - cell = @model_class.human_attribute_name(cell) if cell.is_a?(Symbol) + cell = @data[:class].human_attribute_name(cell) if cell.is_a?(Symbol) cell = cell.render_in(self) if cell.respond_to?(:render_in) content_tag(:th, cell, class: %{ @@ -135,14 +152,10 @@ def render_data_cell(column, data) end def current_scope_name - @current_scope_name ||= params.dig(@search_param, :scope).presence || @scopes.find(&:default)&.name&.to_s - end - - def scope_param_name - @scope_param_name ||= "#{@search_param}[scope]" + @search.current_scope.name end def initial_mode - @initial_mode ||= params.dig(@search_param, @search_key) ? "search" : "scopes" + @initial_mode ||= @search.value[@search.searchbar_key] ? "search" : "scopes" end end From 73b1dedcb22bb9d9dfa12d01b3644b0a179d3897 Mon Sep 17 00:00:00 2001 From: Elia Schito Date: Fri, 24 Nov 2023 11:48:26 +0100 Subject: [PATCH 14/14] Use turbo-permanent in `ui/table` for navigating out of search mode Co-Authored-By: Rainer Dema --- .../solidus_admin/ui/table/component.html.erb | 127 +++++++++--------- .../solidus_admin/ui/table/component.rb | 4 - 2 files changed, 62 insertions(+), 69 deletions(-) diff --git a/admin/app/components/solidus_admin/ui/table/component.html.erb b/admin/app/components/solidus_admin/ui/table/component.html.erb index 8c6d7635bd4..9cc1c93d653 100644 --- a/admin/app/components/solidus_admin/ui/table/component.html.erb +++ b/admin/app/components/solidus_admin/ui/table/component.html.erb @@ -20,7 +20,6 @@ html: { id: search_form_id, class: 'flex-grow', - "data-turbo-frame": table_frame_id, "data-turbo-action": "replace", "data-#{stimulus_id}-target": "searchForm", "data-action": "input->#{stimulus_id}#search change->#{stimulus_id}#search", @@ -87,83 +86,81 @@ <% end %> <% end %> - <%= turbo_frame_tag table_frame_id, target: "_top" do %> -
- <%= t('.no_resources_found', resources: resource_plural_name) %> + <%= t('.no_resources_found', resources: @data.plural_name) %>
+
<%= render component('ui/table/pagination').new( - prev_link: @prev_page_link, - next_link: @next_page_link + prev_link: @data.prev, + next_link: @data.next ) %>
- +
+ + <% @data.columns.each do |column| %> + "> + <% end %> + + + -target="defaultHeader" + > + <% @data.columns.each do |column| %> - "> + <%= render_header_cell(column.header) %> <% end %> - + + + <% if @data.batch_actions %> -target="defaultHeader" + data-<%= stimulus_id %>-target="batchHeader" + class="bg-white color-black text-xs leading-none text-left" + hidden > - <% @data.columns.each do |column| %> - <%= render_header_cell(column.header) %> - <% end %> + <%= render_header_cell(selectable_column.header) %> + <%= render_header_cell(content_tag(:div, safe_join([ + content_tag(:span, "0", "data-#{stimulus_id}-target": "selectedRowsCount"), + " #{t('.rows_selected')}.", + ])), colspan: @data.columns.count - 1) %> + <% end %> - <% if @data.batch_actions %> - -target="batchHeader" - class="bg-white color-black text-xs leading-none text-left" - hidden + + <% @data.rows.each do |row| %> + + data-action="click-><%= stimulus_id %>#rowClicked" + data-<%= stimulus_id %>-url-param="<%= @data.url.call(row) %>" + <% end %> > - - <%= render_header_cell(selectable_column.header) %> - <%= render_header_cell(content_tag(:div, safe_join([ - content_tag(:span, "0", "data-#{stimulus_id}-target": "selectedRowsCount"), - " #{t('.rows_selected')}.", - ])), colspan: @data.columns.count - 1) %> - - + <% @data.columns.each do |column| %> + <%= render_data_cell(column, row) %> + <% end %> + <% end %> - - <% @data.rows.each do |row| %> - - data-action="click-><%= stimulus_id %>#rowClicked" - data-<%= stimulus_id %>-url-param="<%= @data.url.call(row) %>" - <% end %> + <% if @data.rows.empty? && @data.plural_name %> + + - <% end %> - - <% if @data.rows.empty? && @data.plural_name %> - - - - <% end %> - - - <% if @data.prev || @data.next %> - - - - - + <%= t('.no_resources_found', resources: @data.plural_name) %> + + <% end %> + -
- <% @data.columns.each do |column| %> - <%= render_data_cell(column, row) %> - <% end %> -
- <%= t('.no_resources_found', resources: @data.plural_name) %> -
-
- <%= render component('ui/table/pagination').new( - prev_link: @data.prev, - next_link: @data.next - ) %> -
-
- <% end %> + <% if @data.prev || @data.next %> + + + +
+ <%= render component('ui/table/pagination').new( + prev_link: @data.prev, + next_link: @data.next + ) %> +
+ + + + <% end %> + + diff --git a/admin/app/components/solidus_admin/ui/table/component.rb b/admin/app/components/solidus_admin/ui/table/component.rb index 481ba744511..fbc7edda4f9 100644 --- a/admin/app/components/solidus_admin/ui/table/component.rb +++ b/admin/app/components/solidus_admin/ui/table/component.rb @@ -84,10 +84,6 @@ def batch_actions_form_id @batch_actions_form_id ||= "#{stimulus_id}--batch-actions-#{@id}" end - def table_frame_id - @table_frame_id ||= "#{stimulus_id}--table-frame-#{@id}" - end - def search_form_id @search_form_id ||= "#{stimulus_id}--search-form-#{@id}" end