Merge pull request #10385 from rioug/10350-BUU-read-only-product-list-take-2

New product page, add missing column and handle formatting
This commit is contained in:
Filipe
2023-02-16 18:41:05 +00:00
committed by GitHub
4 changed files with 193 additions and 24 deletions

View File

@@ -1,18 +1,26 @@
# frozen_string_literal: true
class ProductComponent < ViewComponentReflex::Component
DATETIME_FORMAT = '%F %T'
def initialize(product:, columns:)
super
@product = product
@image = @product.images[0] if product.images.any?
@columns = columns.map { |c|
@columns = columns.map do |c|
{
id: c[:value],
value: column_value(c[:value])
}
}
end
end
# This must be define when using ProductComponent.with_collection()
def collection_key
@product.id
end
# rubocop:disable Metrics/CyclomaticComplexity, Metrics/MethodLength
def column_value(column)
case column
when 'name'
@@ -25,6 +33,27 @@ class ProductComponent < ViewComponentReflex::Component
@product.supplier.name
when 'category'
@product.taxons.map(&:name).join(', ')
when 'sku'
@product.sku
when 'on_hand'
@product.on_hand || 0
when 'on_demand'
@product.on_demand
when 'tax_category'
@product.tax_category.name
when 'inherits_properties'
@product.inherits_properties
when 'available_on'
format_date(@product.available_on)
when 'import_date'
format_date(@product.import_date)
end
end
# rubocop:enable Metrics/CyclomaticComplexity, Metrics/MethodLength
private
def format_date(date)
date&.strftime(DATETIME_FORMAT) || ''
end
end

View File

@@ -3,27 +3,37 @@
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|
SORTABLE_COLUMNS = ['name', 'import_date'].freeze
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" },
{ label: I18n.t("admin.products_page.columns_selector.sku"), value: "sku" },
{ label: I18n.t("admin.products_page.columns_selector.on_hand"), value: "on_hand" },
{ label: I18n.t("admin.products_page.columns_selector.on_demand"), value: "on_demand" },
{ label: I18n.t("admin.products_page.columns_selector.tax_category"), value: "tax_category" },
{
label: I18n.t("admin.products_page.columns_selector.inherits_properties"),
value: "inherits_properties"
},
{ label: I18n.t("admin.products_page.columns_selector.available_on"), value: "available_on" },
{ label: I18n.t("admin.products_page.columns_selector.import_date"), value: "import_date" }
].sort do |a, b|
a[:label] <=> b[:label]
}.freeze
end.freeze
PER_PAGE_VALUE = [10, 25, 50, 100].freeze
PER_PAGE = PER_PAGE_VALUE.map { |value| { label: value, value: value } }
NAME_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
@user = user
@selectable_columns = SELECTABLE_COMUMNS
@columns_selected = ["price", "unit"]
@selectable_columns = SELECTABLE_COLUMNS
@columns_selected = ['unit', 'price', 'on_hand', 'category', 'import_date']
@per_page = PER_PAGE
@per_page_selected = [10]
@categories = [{ label: "All", value: "all" }] +
@@ -40,16 +50,20 @@ class ProductsTableComponent < ViewComponentReflex::Component
@search_term = ""
end
# any change on a "reflex_data_attributes" (defined in the template) will trigger a re render
def before_render
fetch_products
refresh_columns
end
# Element refers to the component the data is set on
def search_term
# Element is SearchInputComponent
@search_term = element.dataset['value']
end
def toggle_column
# Element is SelectorComponent
column = element.dataset['value']
@columns_selected = if @columns_selected.include?(column)
@columns_selected - [column]
@@ -59,26 +73,33 @@ class ProductsTableComponent < ViewComponentReflex::Component
end
def click_sort
@sort = { column: element.dataset['sort-value'],
direction: element.dataset['sort-direction'] == "asc" ? "desc" : "asc" }
# Element is TableHeaderComponent
@sort = {
column: element.dataset['sort-value'],
direction: element.dataset['sort-direction'] == "asc" ? "desc" : "asc"
}
end
def toggle_per_page
# Element is SelectorComponent
selected = element.dataset['value'].to_i
@per_page_selected = [selected] if PER_PAGE_VALUE.include?(selected)
end
def toggle_category
# Element is SelectorWithFilterComponent
category_clicked = element.dataset['value']
@categories_selected = toggle_selector_with_filter(category_clicked, @categories_selected)
end
def toggle_producer
# Element is SelectorWithFilterComponent
producer_clicked = element.dataset['value']
@producers_selected = toggle_selector_with_filter(producer_clicked, @producers_selected)
end
def change_page
# Element is PaginationComponent
page = element.dataset['page'].to_i
@page = page if page > 0
end
@@ -86,10 +107,13 @@ 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 = @columns_selected.map do |column|
{
label: I18n.t("admin.products_page.columns.#{column}"),
value: column,
sortable: SORTABLE_COLUMNS.include?(column)
}
end.sort! { |a, b| a[:label] <=> b[:label] }
@columns.unshift(NAME_COLUMN)
end
@@ -145,8 +169,13 @@ class ProductsTableComponent < ViewComponentReflex::Component
def product_query_includes
[
master: [:images],
variants: [:default_price, :stock_locations, :stock_items, :variant_overrides,
{ option_values: :option_type }]
variants: [
:default_price,
:stock_locations,
:stock_items,
:variant_overrides,
{ option_values: :option_type }
]
]
end
end

View File

@@ -492,11 +492,25 @@ en:
price: Price
producer: Producer
category: Category
sku: SKU
on_hand: "On Hand"
on_demand: "On Demand"
tax_category: "Tax Category"
inherits_properties: "Inherits Properties?"
available_on: "Available On"
import_date: "Import Date"
columns_selector:
unit: Unit
price: Price
producer: Producer
category: Category
sku: SKU
on_hand: "On Hand"
on_demand: "On Demand"
tax_category: "Tax Category"
inherits_properties: "Inherits Properties?"
available_on: "Available On"
import_date: "Import Date"
adjustments:
skipped_changing_canceled_order: "You can't change a cancelled order."
# Common properties / models

View File

@@ -0,0 +1,97 @@
# frozen_string_literal: true
require "spec_helper"
describe ProductComponent, type: :component do
let(:product) { create(:simple_product) }
describe 'unit' do
before do
render_inline(
ProductComponent.new(
product: product, columns: [{ label: "Unit", value: "unit", sortable: false }]
)
)
end
it 'concatenates the unit value and the unit description' do
expect(page.find('.unit')).to have_content '1.0 weight'
end
end
describe 'category' do
let(:product) do
product = create(:simple_product)
product.taxons = taxons
product
end
let(:taxons) { [create(:taxon, name: 'random 1'), create(:taxon, name: 'random 2')] }
before do
render_inline(
ProductComponent.new(
product: product, columns: [{ label: "Category", value: "category", sortable: false }]
)
)
end
it "joins the categories' name" do
expect(page.find('.category')).to have_content(
/random 1, random 2/, exact: true, normalize_ws: true
)
end
end
describe 'on_hand' do
let(:product) { create(:simple_product, on_hand: on_hand) }
let(:on_hand) { 5 }
before do
render_inline(
ProductComponent.new(
product: product, columns: [{ label: "On Hand", value: "on_hand", sortable: false }]
)
)
end
it 'returns product on_hand' do
expect(page.find('.on_hand')).to have_content(on_hand)
end
context 'when on_hand is nil' do
let(:on_hand) { nil }
it 'returns 0' do
expect(page.find('.on_hand')).to have_content(0.to_s)
end
end
end
# This also covers import_date
describe 'available_on' do
let(:product) { create(:simple_product, available_on: available_on) }
let(:available_on) { Time.zone.now }
before do
render_inline(
ProductComponent.new(
product: product,
columns: [{ label: "Available On", value: "available_on", sortable: false }]
)
)
end
it 'returns formated available_on' do
expect(page.find('.available_on')).to have_content(available_on.strftime('%F %T'))
end
context 'when available_on is nil' do
let(:available_on) { nil }
it 'returns an empty string' do
expect(page.find('.available_on')).to have_content('')
end
end
end
end