Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[admin] Add scopes and controller helpers for ui/table #5516

Merged
merged 14 commits into from
Nov 24, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
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 <[email protected]>
  • Loading branch information
elia and rainerdema committed Nov 24, 2023
commit 6def880bc625daed487c0918d8c839d506aba876
Original file line number Diff line number Diff line change
Expand Up @@ -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,
},
) %>
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -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 %>
91 changes: 29 additions & 62 deletions admin/app/components/solidus_admin/ui/table/component.rb
Original file line number Diff line number Diff line change
@@ -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<Hash>] 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<Hash>] 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<Hash>] 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<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
Expand Down Expand Up @@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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