mirror of
https://github.com/openfoodfoundation/openfoodnetwork
synced 2026-02-05 22:26:07 +00:00
Merge pull request #12546 from dacook/buu/change-columns-11055
[BUU] Change product columns to be shown
This commit is contained in:
@@ -31,7 +31,7 @@ angular.module("admin.indexUtils").factory 'Columns', ($rootScope, $http, $injec
|
||||
savePreferences: (action_name) =>
|
||||
$http
|
||||
method: "PUT"
|
||||
url: "/admin/column_preferences/bulk_update"
|
||||
url: "/admin/column_preferences/bulk_update.json"
|
||||
data:
|
||||
action_name: action_name
|
||||
column_preferences: (preference for column_name, preference of @columns)
|
||||
|
||||
@@ -5,8 +5,9 @@
|
||||
%div.menu{ 'ng-show' => "expanded" }
|
||||
.menu_items
|
||||
.menu_item{ "ng-repeat": "column in columns", "ng-click": "toggle(column);" }
|
||||
%input.redesigned-input{ type: "checkbox", "ng-checked": "column.visible" }
|
||||
{{ column.name }}
|
||||
%input{ type: "checkbox", "ng-checked": "column.visible" }
|
||||
%span
|
||||
{{ column.name }}
|
||||
%hr
|
||||
%div.menu_item.text-center
|
||||
%input.fullwidth.orange{ type: "button", "ng-value": "saved() ? 'Saved': 'Saving'", "ng-show": "saved() || saving", "ng-disabled": "saved()" }
|
||||
|
||||
@@ -9,5 +9,5 @@
|
||||
%div.menu_items
|
||||
- @options.each do |option|
|
||||
%label.menu_item{ "data-multiple-checked-select-target": "option", "data-value": option[1], "data-label": option[0] }
|
||||
%input.redesigned-input{ type: "checkbox", checked: @selected.include?(option[1]), name: "#{@name}[]", value: option[1] }
|
||||
= option[0]
|
||||
%input{ type: "checkbox", checked: @selected.include?(option[1]), name: "#{@name}[]", value: option[1] }
|
||||
%span= option[0]
|
||||
|
||||
@@ -4,17 +4,25 @@ module Admin
|
||||
class ColumnPreferencesController < Admin::ResourceController
|
||||
before_action :load_collection, only: [:bulk_update]
|
||||
|
||||
respond_to :json
|
||||
|
||||
def bulk_update
|
||||
@cp_set.collection.each { |cp| authorize! :bulk_update, cp }
|
||||
|
||||
if @cp_set.save
|
||||
render json: @cp_set.collection, each_serializer: Api::Admin::ColumnPreferenceSerializer
|
||||
elsif @cp_set.errors.present?
|
||||
render json: { errors: @cp_set.errors }, status: :bad_request
|
||||
else
|
||||
render body: nil, status: :internal_server_error
|
||||
respond_to do |format|
|
||||
if @cp_set.save
|
||||
format.json {
|
||||
render json: @cp_set.collection, each_serializer: Api::Admin::ColumnPreferenceSerializer
|
||||
}
|
||||
format.turbo_stream {
|
||||
flash.now[:success] = t('.success')
|
||||
render :bulk_update, locals: { action: permitted_params[:action_name] }
|
||||
}
|
||||
else
|
||||
format.json { render json: { errors: @cp_set.errors }, status: :bad_request }
|
||||
format.turbo_stream {
|
||||
flash.now[:error] = @cp_set.errors.full_messages.to_sentence
|
||||
render :bulk_update, locals: { action: permitted_params[:action_name] }
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -28,11 +36,26 @@ module Admin
|
||||
end
|
||||
|
||||
def load_collection
|
||||
collection_hash = Hash[permitted_params[:column_preferences].
|
||||
each_with_index.map { |cp, i| [i, cp] }]
|
||||
collection_hash.select!{ |_i, cp| cp[:action_name] == permitted_params[:action_name] }
|
||||
@cp_set = Sets::ColumnPreferenceSet.new(@column_preferences,
|
||||
collection_attributes: collection_hash)
|
||||
collection_attributes = nil
|
||||
|
||||
respond_to do |format|
|
||||
format.json do
|
||||
collection_attributes = Hash[permitted_params[:column_preferences].
|
||||
each_with_index.map { |cp, i| [i, cp] }]
|
||||
collection_attributes.select!{ |_i, cp|
|
||||
cp[:action_name] == permitted_params[:action_name]
|
||||
}
|
||||
end
|
||||
format.all do
|
||||
# Inject action name and user ID for each column_preference
|
||||
collection_attributes = permitted_params[:column_preferences].to_h.each_value { |cp|
|
||||
cp[:action_name] = permitted_params[:action_name]
|
||||
cp[:user_id] = spree_current_user.id
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
@cp_set = Sets::ColumnPreferenceSet.new(@column_preferences, collection_attributes:)
|
||||
end
|
||||
|
||||
def collection
|
||||
|
||||
23
app/views/admin/column_preferences/_form.html.haml
Normal file
23
app/views/admin/column_preferences/_form.html.haml
Normal file
@@ -0,0 +1,23 @@
|
||||
= form_with url: bulk_update_admin_column_preferences_path, method: :put,
|
||||
id: :bulk_admin_column_preferences_form, class: "column-preferences",
|
||||
html: { 'data-controller': "column-preferences" } do |f|
|
||||
= hidden_field_tag :action_name, action
|
||||
|
||||
/ DC: this makes my Chrome DevTools crash when inspecting the <details> element. If problem continues, we need to use a different method.
|
||||
%details.ofn-drop-down.ofn-drop-down-v2.right{ 'data-controller': "dropdown" }
|
||||
%summary.ofn-drop-down-label
|
||||
= t('admin.columns')
|
||||
%span.icon-caret
|
||||
|
||||
.menu
|
||||
.menu_items
|
||||
- ColumnPreference.for(spree_current_user, action).each_with_index do |column_preference, index|
|
||||
= f.fields_for("column_preferences", column_preference, index:) do |cp_form|
|
||||
= cp_form.hidden_field :id
|
||||
= cp_form.hidden_field :column_name
|
||||
%label.menu_item
|
||||
= cp_form.check_box :visible, 'data-column-name': column_preference.column_name
|
||||
%span= t("admin.products_page.columns." + column_preference.column_name)
|
||||
|
||||
.actions
|
||||
= f.submit t('admin.column_save_as_default'), class: "secondary fullwidth"
|
||||
@@ -0,0 +1,3 @@
|
||||
= turbo_stream.replace "bulk_admin_column_preferences_form" do
|
||||
= render partial: "admin/shared/flashes", locals: { flashes: flash } if defined? flash
|
||||
= render partial: 'form', locals: { action: }
|
||||
@@ -1,13 +1,13 @@
|
||||
%td.with-image{ id: "image-#{product.id}" }
|
||||
%td.col-image.with-image{ id: "image-#{product.id}" }
|
||||
= render partial: "product_image", locals: { product: }
|
||||
%td.field.align-left.header.naked_inputs
|
||||
%td.col-name.field.align-left.header.naked_inputs
|
||||
= f.hidden_field :id
|
||||
= f.text_field :name, 'aria-label': t('admin.products_page.columns.name')
|
||||
= error_message_on product, :name
|
||||
%td.field.naked_inputs
|
||||
%td.col-sku.field.naked_inputs
|
||||
= f.text_field :sku, 'aria-label': t('admin.products_page.columns.sku')
|
||||
= error_message_on product, :sku
|
||||
%td.field.naked_inputs{ 'data-controller': 'toggle-control', 'data-toggle-control-match-value': 'items' }
|
||||
%td.col-unit_scale.field.naked_inputs{ 'data-controller': 'toggle-control', 'data-toggle-control-match-value': 'items' }
|
||||
= f.hidden_field :variant_unit
|
||||
= f.hidden_field :variant_unit_scale
|
||||
= f.select :variant_unit_with_scale,
|
||||
@@ -19,23 +19,23 @@
|
||||
.field
|
||||
= f.text_field :variant_unit_name, 'aria-label': t('items'), 'data-toggle-control-target': 'control', style: (product.variant_unit == "items" ? "" : "display: none")
|
||||
= error_message_on product, :variant_unit_name, 'data-toggle-control-target': 'control'
|
||||
%td.align-right
|
||||
%td.col-unit.align-right
|
||||
-# empty
|
||||
%td.align-right
|
||||
%td.col-price.align-right
|
||||
-# empty
|
||||
%td.align-right
|
||||
%td.col-on_hand.align-right
|
||||
-# empty
|
||||
%td.naked_inputs
|
||||
%td.col-producer.naked_inputs
|
||||
= render(SearchableDropdownComponent.new(form: f,
|
||||
name: :supplier_id,
|
||||
aria_label: t('.producer_field_name'),
|
||||
options: producer_options,
|
||||
selected_option: product.supplier_id,
|
||||
placeholder_value: t('admin.products_v3.filters.search_for_producers')))
|
||||
%td.align-left
|
||||
%td.col-category.align-left
|
||||
-# empty
|
||||
%td.align-left
|
||||
%td.align-left
|
||||
%td.col-tax_category.align-left
|
||||
%td.col-inherits_properties.align-left
|
||||
.content= product.inherits_properties ? 'YES' : 'NO' #TODO: consider using https://github.com/RST-J/human_attribute_values, else use I18n.t (also below)
|
||||
%td.align-right
|
||||
= render(VerticalEllipsisMenu::Component.new) do
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#sort
|
||||
%div
|
||||
%div.pagination-description
|
||||
- if pagy.present?
|
||||
= t(".pagination.total_html", total: pagy.count, from: pagy.from, to: pagy.to)
|
||||
|
||||
@@ -13,3 +13,6 @@
|
||||
options_for_select([15, 25, 50, 100].collect{|i| [t('.pagination.per_page.per_page', num: i), i]}, pagy&.items),
|
||||
class: "no-input per-page",
|
||||
data: { controller: "tom-select search", action: "change->search#changePerPage", "tom-select-options-value": '{ "plugins": [] }'}
|
||||
|
||||
/ Columns dropdown
|
||||
= render partial: "admin/column_preferences/form", locals: { action: "products_v3_index" }
|
||||
|
||||
@@ -13,20 +13,21 @@
|
||||
= hidden_field_tag :producer_id, @producer_id
|
||||
= hidden_field_tag :category_id, @category_id
|
||||
|
||||
%table.products
|
||||
%table.products{ 'data-column-preferences-target': "table" }
|
||||
%colgroup
|
||||
%col{ width:"56" }= # Img (size + padding)
|
||||
%col= # (grow to fill) Name
|
||||
%col{ width:"5%"}
|
||||
%col{ width:"8%"}
|
||||
%col{ width:"8%"}
|
||||
%col{ width:"5%"}
|
||||
%col{ width:"10%"}
|
||||
%col{ width:"15%"}= # Producer
|
||||
%col{ width:"8%"}
|
||||
%col{ width:"8%"}
|
||||
%col{ width:"8%"}
|
||||
%col{ width:"8%"}= # Actions
|
||||
-# The `min-width` property works in Chrome but not Firefox so is considered progressive enhancement.
|
||||
%col.col-image{ width:"56px" }= # (image size + padding)
|
||||
%col.col-name{ style:"min-width: 6em" }= # (grow to fill)
|
||||
%col.col-sku{ width:"8%", style:"min-width: 6em" }
|
||||
%col.col-unit_scale{ width:"8%" }
|
||||
%col.col-unit{ width:"8%" }
|
||||
%col.col-price{ width:"5%", style:"min-width: 5em" }
|
||||
%col.col-on_hand{ width:"10%"}
|
||||
%col.col-producer{ style:"min-width: 6em" }= # (grow to fill)
|
||||
%col.col-category{ width:"8%" }
|
||||
%col.col-tax_category{ width:"8%" }
|
||||
%col.col-inherits_properties{ width:"5%" }
|
||||
%col{ width:"5%", style:"min-width: 3em"}= # Actions
|
||||
%thead
|
||||
%tr
|
||||
%td.form-actions-wrapper{ colspan: 12 }
|
||||
@@ -48,18 +49,18 @@
|
||||
= t('.reset')
|
||||
= form.submit t('.save'), class: "medium"
|
||||
%tr
|
||||
%th.align-left= # image
|
||||
%th.col-image.align-left= # image
|
||||
= render partial: 'spree/admin/shared/stimulus_sortable_header',
|
||||
locals: { column: :name, sorted: params.dig(:q, :s), default: 'name asc' }
|
||||
%th.align-left.with-input= t('admin.products_page.columns.sku')
|
||||
%th.align-left.with-input= t('admin.products_page.columns.unit_scale')
|
||||
%th.align-left.with-input= t('admin.products_page.columns.unit')
|
||||
%th.align-left.with-input= t('admin.products_page.columns.price')
|
||||
%th.align-left.with-input= t('admin.products_page.columns.on_hand')
|
||||
%th.align-left= t('admin.products_page.columns.producer')
|
||||
%th.align-left= t('admin.products_page.columns.category')
|
||||
%th.align-left= t('admin.products_page.columns.tax_category')
|
||||
%th.align-left= t('admin.products_page.columns.inherits_properties')
|
||||
%th.align-left.col-sku.with-input= t('admin.products_page.columns.sku')
|
||||
%th.align-left.col-unit_scale.with-input= t('admin.products_page.columns.unit_scale')
|
||||
%th.align-left.col-unit.with-input= t('admin.products_page.columns.unit')
|
||||
%th.align-left.col-price.with-input= t('admin.products_page.columns.price')
|
||||
%th.align-left.col-on_hand.with-input= t('admin.products_page.columns.on_hand')
|
||||
%th.align-left.col-producer= t('admin.products_page.columns.producer')
|
||||
%th.align-left.col-category= t('admin.products_page.columns.category')
|
||||
%th.align-left.col-tax_category= t('admin.products_page.columns.tax_category')
|
||||
%th.align-left.col-inherits_properties= t('admin.products_page.columns.inherits_properties')
|
||||
%th.align-right= t('admin.products_page.columns.actions')
|
||||
- products.each_with_index do |product, product_index|
|
||||
= form.fields_for("products", product, index: product_index) do |product_form|
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
%td
|
||||
%td.col-image
|
||||
-# empty
|
||||
%td.field.naked_inputs
|
||||
%td.col-name.field.naked_inputs
|
||||
= f.hidden_field :id
|
||||
= f.text_field :display_name, 'aria-label': t('admin.products_page.columns.name'), placeholder: variant.product.name
|
||||
= error_message_on variant, :display_name
|
||||
%td.field.naked_inputs
|
||||
%td.col-sku.field.naked_inputs
|
||||
= f.text_field :sku, 'aria-label': t('admin.products_page.columns.sku')
|
||||
= error_message_on variant, :sku
|
||||
%td
|
||||
%td.col-unit_scale
|
||||
-# empty
|
||||
%td.field.popout{'data-controller': "popout", 'data-popout-update-display-value': "false"}
|
||||
%td.col-unit.field.popout{'data-controller': "popout", 'data-popout-update-display-value': "false"}
|
||||
= f.button :unit_to_display, class: "popout__button", 'aria-label': t('admin.products_page.columns.unit'), 'data-popout-target': "button" do
|
||||
= variant.unit_to_display # Show the generated summary of unit values
|
||||
%div.popout__container{ style: 'display: none;', 'data-controller': 'toggle-control', 'data-popout-target': "dialog" }
|
||||
@@ -25,10 +25,10 @@
|
||||
= f.label :display_as, t('admin.products_page.columns.display_as')
|
||||
= f.text_field :display_as, placeholder: VariantUnits::OptionValueNamer.new(variant).name
|
||||
= error_message_on variant, :unit_value
|
||||
%td.field.naked_inputs
|
||||
%td.col-price.field.naked_inputs
|
||||
= f.text_field :price, 'aria-label': t('admin.products_page.columns.price'), value: number_to_currency(variant.price, unit: '')&.strip # TODO: add a spec to prove that this formatting is necessary. If so, it should be in a shared form helper for currency inputs
|
||||
= error_message_on variant, :price
|
||||
%td.field.popout{'data-controller': "popout"}
|
||||
%td.col-on_hand.field.popout{'data-controller': "popout"}
|
||||
%button.popout__button{'data-popout-target': "button", 'aria-label': t('admin.products_page.columns.on_hand')}
|
||||
= variant.on_demand ? t(:on_demand) : variant.on_hand
|
||||
%div.popout__container{ style: 'display: none;', 'data-controller': 'toggle-control', 'data-popout-target': "dialog" }
|
||||
@@ -39,16 +39,16 @@
|
||||
= f.label :on_demand do
|
||||
= f.check_box :on_demand, 'data-action': 'change->toggle-control#disableIfPresent change->popout#closeIfChecked'
|
||||
= t(:on_demand)
|
||||
%td.align-left
|
||||
%td.col-producer.align-left
|
||||
-# empty producer name
|
||||
%td.field.naked_inputs
|
||||
%td.col-category.field.naked_inputs
|
||||
= render(SearchableDropdownComponent.new(form: f,
|
||||
name: :primary_taxon_id,
|
||||
options: category_options,
|
||||
selected_option: variant.primary_taxon_id,
|
||||
aria_label: t('.category_field_name'),
|
||||
placeholder_value: t('admin.products_v3.filters.search_for_categories')))
|
||||
%td.field.naked_inputs
|
||||
%td.col-tax_category.field.naked_inputs
|
||||
= render(SearchableDropdownComponent.new(form: f,
|
||||
name: :tax_category_id,
|
||||
options: tax_category_options,
|
||||
@@ -57,7 +57,7 @@
|
||||
aria_label: t('.tax_category_field_name'),
|
||||
placeholder_value: t('.search_for_tax_categories')))
|
||||
= error_message_on variant, :tax_category
|
||||
%td.align-left
|
||||
%td.col-inherits_properties.align-left
|
||||
-# empty
|
||||
%td.align-right
|
||||
= render(VerticalEllipsisMenu::Component.new) do
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
%th
|
||||
%th{ class: "col-#{column}" }
|
||||
%a{ "data-controller": "search", "data-action": "click->search#changeSorting", "data-column": "#{column}", "data-current": (sorted || default).to_s }
|
||||
= t("spree.admin.shared.sortable_header.#{column.to_s}")
|
||||
|
||||
|
||||
43
app/webpacker/controllers/column_preferences_controller.js
Normal file
43
app/webpacker/controllers/column_preferences_controller.js
Normal file
@@ -0,0 +1,43 @@
|
||||
import { Controller } from "stimulus";
|
||||
|
||||
// Manage column visibility according to checkbox selection
|
||||
//
|
||||
export default class ColumnPreferencesController extends Controller {
|
||||
connect() {
|
||||
this.table = document.querySelector('table[data-column-preferences-target="table"]');
|
||||
this.cols = Array.from(this.table.querySelectorAll('col'));
|
||||
this.colSpanCells = this.table.querySelectorAll('th[colspan],td[colspan]');
|
||||
// Initialise data-default-col-span
|
||||
this.colSpanCells.forEach((cell)=> {
|
||||
cell.dataset.defaultColSpan ||= cell.colSpan;
|
||||
});
|
||||
|
||||
this.checkboxes = Array.from(this.element.querySelectorAll("input[type=checkbox]"));
|
||||
for (const element of this.checkboxes) {
|
||||
// On initial load
|
||||
this.#showHideColumn(element);
|
||||
// On checkbox changed
|
||||
element.addEventListener("change", this.#showHideColumn.bind(this));
|
||||
}
|
||||
}
|
||||
|
||||
// private
|
||||
|
||||
#showHideColumn(e) {
|
||||
const element = e.target || e;
|
||||
const name = element.dataset.columnName;
|
||||
|
||||
this.table.classList.toggle(`hide-${name}`, !element.checked);
|
||||
|
||||
// Reset cell colspans
|
||||
const hiddenColCount = this.checkboxes.filter((checkbox)=> !checkbox.checked).length;
|
||||
for(const cell of this.colSpanCells) {
|
||||
const span = parseInt(cell.dataset.defaultColSpan, 10) - hiddenColCount;
|
||||
cell.colSpan = span;
|
||||
};
|
||||
}
|
||||
|
||||
#showHideElement(element, show) {
|
||||
element.style.display = show ? "" : "none";
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Controller } from "stimulus";
|
||||
|
||||
// Close a <details> element when click outside
|
||||
export default class extends Controller {
|
||||
|
||||
connect() {
|
||||
|
||||
@@ -6,32 +6,3 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
input[type="checkbox"].redesigned-input {
|
||||
position: relative;
|
||||
top: 1px;
|
||||
-moz-appearance: none;
|
||||
-webkit-appearance: none;
|
||||
-o-appearance: none;
|
||||
appearance: none;
|
||||
outline: none;
|
||||
content: none;
|
||||
cursor: pointer;
|
||||
|
||||
&:before {
|
||||
font-family: "FontAwesome";
|
||||
content: "\f00c";
|
||||
font-size: 15px;
|
||||
color: transparent !important;
|
||||
background: transparent !important;
|
||||
display: block;
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
border: 1px solid #809cb1;
|
||||
margin-right: 7px;
|
||||
}
|
||||
|
||||
&:checked:before {
|
||||
color: $color-txt-text !important;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,7 +96,8 @@
|
||||
|
||||
// "Naked" inputs. Row hover helps reveal them.
|
||||
.naked_inputs {
|
||||
input:not([type="checkbox"]), .ts-control {
|
||||
input:not([type="checkbox"]),
|
||||
.ts-control {
|
||||
background-color: $color-tbl-cell-bg;
|
||||
}
|
||||
}
|
||||
@@ -179,6 +180,18 @@
|
||||
.ts-control {
|
||||
z-index: 0; // Avoid hovering over thead
|
||||
}
|
||||
|
||||
// Hide columns
|
||||
$columns:
|
||||
"image", "name", "sku", "unit_scale", "unit", "price", "on_hand", "producer", "category",
|
||||
"tax_category", "inherits_properties";
|
||||
@each $col in $columns {
|
||||
&.hide-#{$col} {
|
||||
.col-#{$col} {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#no-products {
|
||||
@@ -214,6 +227,10 @@
|
||||
display: none;
|
||||
}
|
||||
|
||||
.pagination-description {
|
||||
flex-grow: 1; // Grow to fill space
|
||||
}
|
||||
|
||||
.with-dropdown {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
@@ -221,7 +238,11 @@
|
||||
gap: 10px;
|
||||
}
|
||||
.per-page {
|
||||
width: 10em;
|
||||
width: 9em;
|
||||
}
|
||||
|
||||
.column-preferences .ofn-drop-down-label {
|
||||
width: 13em;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -370,7 +391,7 @@
|
||||
border-radius: $border-radius;
|
||||
box-shadow: 0px 0px 8px 0px rgba($near-black, 0.25);
|
||||
|
||||
.field{
|
||||
.field {
|
||||
margin-bottom: 0.75em;
|
||||
|
||||
&:last-child {
|
||||
@@ -424,7 +445,7 @@
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.slide-out {
|
||||
animation: slideOutLeft 0.5s forwards;
|
||||
}
|
||||
|
||||
@@ -101,8 +101,6 @@
|
||||
|
||||
> span {
|
||||
width: auto;
|
||||
text-transform: uppercase;
|
||||
font-size: 85%;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
@@ -113,7 +111,7 @@
|
||||
top: 100%;
|
||||
left: 0px;
|
||||
padding: 5px 0px;
|
||||
border: 1px solid #adadad;
|
||||
border-radius: $border-radius;
|
||||
background-color: #ffffff;
|
||||
box-shadow: 1px 3px 10px #888888;
|
||||
z-index: 100;
|
||||
@@ -181,6 +179,25 @@
|
||||
}
|
||||
}
|
||||
|
||||
summary:after {
|
||||
content: "\f078";
|
||||
font-family: FontAwesome;
|
||||
position: relative;
|
||||
top: 3px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
|
||||
&[open] >,
|
||||
details[open] > {
|
||||
summary:after {
|
||||
content: "\f077";
|
||||
font-family: FontAwesome;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ofn-drop-down:not(.ofn-dropdown-v2) {
|
||||
> details {
|
||||
margin: -7px -15px;
|
||||
padding: 7px 15px;
|
||||
@@ -190,21 +207,14 @@
|
||||
display: inline-block;
|
||||
list-style: none;
|
||||
width: auto;
|
||||
text-transform: uppercase;
|
||||
font-size: 85%;
|
||||
font-weight: 600;
|
||||
margin: -8px -15px;
|
||||
padding: 8px 15px;
|
||||
}
|
||||
|
||||
> details > summary:after {
|
||||
content: "\f0d7";
|
||||
font-family: FontAwesome;
|
||||
}
|
||||
|
||||
> details[open] > summary:after {
|
||||
content: "\f0d8";
|
||||
font-family: FontAwesome;
|
||||
&:after {
|
||||
position: relative;
|
||||
top: -1px;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -212,8 +222,11 @@
|
||||
border: 1px solid $lighter-grey;
|
||||
background-color: $lighter-grey;
|
||||
padding: 0px;
|
||||
line-height: normal;
|
||||
@include border-radius($border-radius);
|
||||
|
||||
&:hover {
|
||||
&:hover,
|
||||
&.expanded {
|
||||
border-color: $lighter-grey;
|
||||
}
|
||||
|
||||
@@ -263,17 +276,22 @@
|
||||
cursor: pointer;
|
||||
padding-top: 4px;
|
||||
padding-bottom: 5px;
|
||||
text-transform: uppercase;
|
||||
font-size: 85%;
|
||||
font-size: inherit;
|
||||
|
||||
// Align checkbox and text
|
||||
& > * {
|
||||
vertical-align: middle;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.actions {
|
||||
margin-top: 5px;
|
||||
margin-right: 15px; // Compensate for scrollbar on menu_items
|
||||
padding: 2px 10px;
|
||||
|
||||
&:hover {
|
||||
background-color: inherit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ofn-drop-down.ofn-drop-down-v2 {
|
||||
// Add very specific styling here for components that are in transition:
|
||||
// ie. the ones using the two classes above
|
||||
.ofn-drop-down-label {
|
||||
padding-top: 7px;
|
||||
padding-bottom: 7px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -571,6 +571,7 @@ en:
|
||||
per_page: "%{count} items per page"
|
||||
colums: Columns
|
||||
columns:
|
||||
image: Image
|
||||
name: Name
|
||||
unit_scale: Unit scale
|
||||
unit: Unit
|
||||
@@ -661,7 +662,7 @@ en:
|
||||
choose: "Choose..."
|
||||
please_select: Please select...
|
||||
|
||||
column_save_as_default: Save As Default
|
||||
column_save_as_default: Save as default
|
||||
columns: Columns
|
||||
actions: Actions
|
||||
viewing: "Viewing: %{current_view_name}"
|
||||
@@ -760,6 +761,9 @@ en:
|
||||
balance_due: "Balance Due"
|
||||
destroy:
|
||||
has_associated_subscriptions: "Delete failed: This customer has active subscriptions. Cancel them first."
|
||||
column_preferences:
|
||||
bulk_update:
|
||||
success: "Column preferences saved"
|
||||
contents:
|
||||
edit:
|
||||
title: Content
|
||||
|
||||
@@ -95,7 +95,7 @@ Openfoodnetwork::Application.routes.draw do
|
||||
|
||||
resource :contents
|
||||
|
||||
resources :column_preferences, only: [], format: :json do
|
||||
resources :column_preferences, only: [] do
|
||||
put :bulk_update, on: :collection
|
||||
end
|
||||
|
||||
|
||||
@@ -77,6 +77,24 @@ module OpenFoodNetwork
|
||||
}
|
||||
end
|
||||
|
||||
def products_v3_index_columns
|
||||
I18n.with_options scope: 'admin.products_page.columns' do
|
||||
{
|
||||
image: { name: t(:image), visible: true },
|
||||
name: { name: t(:name), visible: true },
|
||||
sku: { name: t(:sku), visible: true },
|
||||
unit: { name: t(:unit), visible: true },
|
||||
unit_scale: { name: t(:unit_scale), visible: true },
|
||||
price: { name: t(:price), visible: true },
|
||||
on_hand: { name: t(:on_hand), visible: true },
|
||||
producer: { name: t(:producer), visible: true },
|
||||
category: { name: t(:category), visible: true },
|
||||
tax_category: { name: t(:tax_category), visible: true },
|
||||
inherits_properties: { name: t(:inherits_properties), visible: true },
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
def enterprises_index_columns
|
||||
node = "admin.enterprises.index"
|
||||
{
|
||||
|
||||
@@ -9,13 +9,26 @@ RSpec.describe Admin::ColumnPreferencesController, type: :controller do
|
||||
let!(:user1) { create(:user) }
|
||||
let!(:user2) { create(:user) }
|
||||
let!(:enterprise) { create(:enterprise, owner: user1, users: [user1, user2]) }
|
||||
let!(:column_preference) {
|
||||
ColumnPreference.create(user_id: user1.id, action_name: 'enterprises_index',
|
||||
column_name: "name", visible: true)
|
||||
}
|
||||
|
||||
shared_examples "where I own the preferences submitted" do
|
||||
before do
|
||||
allow(controller).to receive(:spree_current_user) { user1 }
|
||||
end
|
||||
|
||||
it "allows me to update the column preferences" do
|
||||
spree_put :bulk_update, format: request_format, action_name: "enterprises_index",
|
||||
column_preferences: column_preference_params
|
||||
expect(ColumnPreference.where(user_id: user1.id,
|
||||
action_name: 'enterprises_index').count).to be 3
|
||||
end
|
||||
end
|
||||
|
||||
context "json" do
|
||||
let!(:column_preference) {
|
||||
ColumnPreference.create(user_id: user1.id, action_name: 'enterprises_index',
|
||||
column_name: "name", visible: true)
|
||||
}
|
||||
|
||||
let(:request_format) { :json }
|
||||
let(:column_preference_params) {
|
||||
[
|
||||
{ id: column_preference.id, user_id: user1.id, action_name: "enterprises_index",
|
||||
@@ -27,28 +40,47 @@ RSpec.describe Admin::ColumnPreferencesController, type: :controller do
|
||||
]
|
||||
}
|
||||
|
||||
it_behaves_like "where I own the preferences submitted"
|
||||
|
||||
context "where I don't own the preferences submitted" do
|
||||
before do
|
||||
allow(controller).to receive(:spree_current_user) { user2 }
|
||||
end
|
||||
|
||||
it "prevents me from updating the column preferences" do
|
||||
spree_put :bulk_update, format: :json, action_name: "enterprises_index",
|
||||
spree_put :bulk_update, format: request_format, action_name: "enterprises_index",
|
||||
column_preferences: column_preference_params
|
||||
expect(ColumnPreference.count).to be 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "where I own the preferences submitted" do
|
||||
context "turbo_stream" do
|
||||
let(:request_format) { :turbo_stream }
|
||||
let(:column_preference_params) {
|
||||
{
|
||||
'0': { id: column_preference.id, column_name: "name", visible: "0" },
|
||||
'1': { id: nil, column_name: "producer", visible: "1" },
|
||||
'2': { id: nil, column_name: "status", visible: "1" },
|
||||
}
|
||||
}
|
||||
|
||||
it_behaves_like "where I own the preferences submitted"
|
||||
|
||||
context "where I don't own the preferences submitted" do
|
||||
before do
|
||||
allow(controller).to receive(:spree_current_user) { user1 }
|
||||
allow(controller).to receive(:spree_current_user) { user2 }
|
||||
end
|
||||
|
||||
it "allows me to update the column preferences" do
|
||||
spree_put :bulk_update, format: :json, action_name: "enterprises_index",
|
||||
column_preferences: column_preference_params
|
||||
expect(ColumnPreference.where(user_id: user1.id,
|
||||
action_name: 'enterprises_index').count).to be 3
|
||||
# This has the same effect as the JSON action, but due to differing implementation,
|
||||
# it has different expections.
|
||||
it "prevents me from updating the column preferences" do
|
||||
expect {
|
||||
spree_put :bulk_update, format: request_format, action_name: "enterprises_index",
|
||||
column_preferences: column_preference_params
|
||||
}.to raise_error(ActiveRecord::RecordNotUnique)
|
||||
|
||||
expect(column_preference.reload.visible).to eq true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
module AdminHelper
|
||||
def toggle_columns(*labels)
|
||||
# open dropdown
|
||||
# case insensitive search for "Columns" text
|
||||
find("div#columns-dropdown", text: /columns/i).click
|
||||
columns_dropdown = ofn_drop_down("Columns")
|
||||
columns_dropdown.click
|
||||
|
||||
within "div#columns-dropdown" do
|
||||
within columns_dropdown do
|
||||
labels.each do |label|
|
||||
# Convert label to case-insensitive regexp if not one already
|
||||
label = /#{label}/i unless label.is_a?(Regexp)
|
||||
@@ -16,6 +16,10 @@ module AdminHelper
|
||||
end
|
||||
|
||||
# close dropdown
|
||||
find("div#columns-dropdown", text: /columns/i).click
|
||||
columns_dropdown.click
|
||||
end
|
||||
|
||||
def ofn_drop_down(label)
|
||||
find(".ofn-drop-down", text: /#{label}/i)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
require "system_helper"
|
||||
|
||||
RSpec.describe 'As an enterprise user, I can manage my products', feature: :admin_style_v3 do
|
||||
include AdminHelper
|
||||
include WebHelper
|
||||
include AuthenticationHelper
|
||||
include FileHelper
|
||||
@@ -30,29 +31,45 @@ RSpec.describe 'As an enterprise user, I can manage my products', feature: :admi
|
||||
end
|
||||
end
|
||||
|
||||
describe "using the page" do
|
||||
describe "using column display dropdown" do
|
||||
let(:product) { create(:simple_product) }
|
||||
describe "column selector" do
|
||||
let!(:product) { create(:simple_product) }
|
||||
|
||||
before do
|
||||
pending "Pending implementation, issue #11055"
|
||||
login_as_admin
|
||||
visit spree.admin_products_path
|
||||
before do
|
||||
visit admin_products_url
|
||||
end
|
||||
|
||||
it "hides column and remembers saved preference" do
|
||||
# Name shows by default
|
||||
expect(page).to have_checked_field "Name"
|
||||
expect(page).to have_selector "th", text: "Name"
|
||||
expect_other_columns_visible
|
||||
|
||||
# Name is hidden
|
||||
ofn_drop_down("Columns").click
|
||||
within ofn_drop_down("Columns") do
|
||||
uncheck "Name"
|
||||
end
|
||||
expect(page).not_to have_selector "th", text: "Name"
|
||||
expect_other_columns_visible
|
||||
|
||||
it "shows a column display dropdown, which shows a list of columns when clicked" do
|
||||
expect(page).to have_selector "th", text: "NAME"
|
||||
expect(page).to have_selector "th", text: "PRODUCER"
|
||||
expect(page).to have_selector "th", text: "PRICE"
|
||||
expect(page).to have_selector "th", text: "ON HAND"
|
||||
# Preference saved
|
||||
click_on "Save as default"
|
||||
expect(page).to have_content "Column preferences saved"
|
||||
refresh
|
||||
|
||||
toggle_columns /^.{0,1}Producer$/i
|
||||
|
||||
expect(page).not_to have_selector "th", text: "PRODUCER"
|
||||
expect(page).to have_selector "th", text: "NAME"
|
||||
expect(page).to have_selector "th", text: "PRICE"
|
||||
expect(page).to have_selector "th", text: "ON HAND"
|
||||
# Preference remembered
|
||||
ofn_drop_down("Columns").click
|
||||
within ofn_drop_down("Columns") do
|
||||
expect(page).to have_unchecked_field "Name"
|
||||
end
|
||||
expect(page).not_to have_selector "th", text: "Name"
|
||||
expect_other_columns_visible
|
||||
end
|
||||
|
||||
def expect_other_columns_visible
|
||||
expect(page).to have_selector "th", text: "Producer"
|
||||
expect(page).to have_selector "th", text: "Price"
|
||||
expect(page).to have_selector "th", text: "On Hand"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -321,6 +338,8 @@ RSpec.describe 'As an enterprise user, I can manage my products', feature: :admi
|
||||
end
|
||||
end
|
||||
|
||||
describe "columns"
|
||||
|
||||
describe "updating" do
|
||||
let!(:variant_a1) {
|
||||
product_a.variants.first.tap{ |v|
|
||||
|
||||
Reference in New Issue
Block a user