mirror of
https://github.com/openfoodfoundation/openfoodnetwork
synced 2026-02-02 21:57:17 +00:00
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:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
97
spec/components/product_component_spec.rb
Normal file
97
spec/components/product_component_spec.rb
Normal 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
|
||||
Reference in New Issue
Block a user