Merge pull request #13436 from chahmedejaz/task/13432-decommission-old-products-screen

Decommission Old Products UI and Related Code
This commit is contained in:
Filipe
2025-08-07 17:39:43 +02:00
committed by GitHub
58 changed files with 28 additions and 4181 deletions

View File

@@ -1,390 +0,0 @@
angular.module("ofn.admin").controller "AdminProductEditCtrl", ($scope, $timeout, $filter, $http, $window, $location, BulkProducts, DisplayProperties, DirtyProducts, VariantUnitManager, StatusMessage, producers, Taxons, Columns, tax_categories, RequestMonitor, SortOptions, ErrorsParser, ProductFiltersUrl) ->
$scope.StatusMessage = StatusMessage
$scope.columns = Columns.columns
$scope.variant_unit_options = VariantUnitManager.variantUnitOptions()
$scope.RequestMonitor = RequestMonitor
$scope.pagination = BulkProducts.pagination
$scope.per_page_options = [
{id: 15, name: t('js.admin.orders.index.per_page', results: 15)},
{id: 50, name: t('js.admin.orders.index.per_page', results: 50)},
{id: 100, name: t('js.admin.orders.index.per_page', results: 100)}
]
$scope.q = {
producerFilter: ""
categoryFilter: ""
importDateFilter: ""
query: ""
sorting: ""
}
$scope.sorting = "name asc"
$scope.producers = producers
$scope.taxons = Taxons.all
$scope.tax_categories = tax_categories
$scope.page = 1
$scope.per_page = 15
$scope.products = BulkProducts.products
$scope.DisplayProperties = DisplayProperties
$scope.sortOptions = SortOptions
$scope.initialise = ->
$scope.q = ProductFiltersUrl.loadFromUrl($location.search())
$scope.fetchProducts()
$scope.$watchCollection '[q.query, q.producerFilter, q.categoryFilter, q.importDateFilter, per_page]', ->
$scope.page = 1 # Reset page when changing filters for new search
$scope.changePage = (newPage) ->
$scope.page = newPage
$scope.fetchProducts()
$scope.fetchProducts = ->
removeClearedValues()
params = {
'q[name_cont]': $scope.q.query,
'q[variants_supplier_id_eq]': $scope.q.producerFilter,
'q[variants_primary_taxon_id_eq]': $scope.q.categoryFilter,
'q[s]': $scope.sorting,
import_date: $scope.q.importDateFilter,
page: $scope.page,
per_page: $scope.per_page
}
RequestMonitor.load(BulkProducts.fetch(params).$promise).then ->
# update url with the filters used
$location.search(ProductFiltersUrl.generate($scope.q))
$scope.resetProducts()
removeClearedValues = ->
delete $scope.q.producerFilter if $scope.q.producerFilter == "0"
delete $scope.q.categoryFilter if $scope.q.categoryFilter == "0"
delete $scope.q.importDateFilter if $scope.q.importDateFilter == "0"
$timeout ->
if $scope.showLatestImport
$scope.q.importDateFilter = $scope.importDates[1].id
$scope.resetProducts = ->
DirtyProducts.clear()
StatusMessage.clear()
$scope.updateOnHand = (product) ->
on_demand_variants = []
if product.variants
on_demand_variants = (variant for id, variant of product.variants when variant.on_demand)
unless product.on_demand || on_demand_variants.length > 0
product.on_hand = $scope.onHand(product)
$scope.onHand = (product) ->
onHand = 0
if product.hasOwnProperty("variants") and product.variants instanceof Object
for id, variant of product.variants
onHand = onHand + parseInt(if variant.on_hand > 0 then variant.on_hand else 0)
else
onHand = "error"
onHand
$scope.shiftTab = (tab) ->
$scope.visibleTab.visible = false unless $scope.visibleTab == tab || $scope.visibleTab == undefined
tab.visible = !tab.visible
$scope.visibleTab = tab
$scope.resetSelectFilters = ->
$scope.q.query = ""
$scope.q.producerFilter = "0"
$scope.q.categoryFilter = "0"
$scope.q.importDateFilter = "0"
$scope.fetchProducts()
$scope.$watch 'sortOptions', (sort) ->
return unless sort && sort.predicate != ""
$scope.sorting = sort.getSortingExpr(defaultDirection: "asc")
$scope.fetchProducts()
, true
confirm_unsaved_changes = () ->
(DirtyProducts.count() > 0 and confirm(t("unsaved_changes_confirmation"))) or (DirtyProducts.count() == 0)
editProductUrl = (product, variant) ->
"/admin/products/" + product.id + ((if variant then "/variants/" + variant.id else "")) + "/edit"
$scope.editWarn = (product, variant) ->
if confirm_unsaved_changes()
$window.location.href = ProductFiltersUrl.buildUrl(editProductUrl(product, variant), $scope.q)
$scope.toggleShowAllVariants = ->
showVariants = !DisplayProperties.showVariants 0
$scope.products.forEach (product) ->
DisplayProperties.setShowVariants product.id, showVariants
DisplayProperties.setShowVariants 0, showVariants
$scope.addVariant = (product) ->
# Set new variant category to same as last product variant category to keep compactibility with deleted variant callback to set new variant category
newVariantId = $scope.nextVariantId();
newVariantCategoryId = product.variants[product.variants.length - 1]?.category_id
product.variants.push
id: newVariantId
unit_value: null
unit_description: null
on_demand: false
display_as: null
display_name: null
on_hand: null
price: null
tax_category_id: null
category_id: newVariantCategoryId
DisplayProperties.setShowVariants product.id, true
DirtyProducts.addVariantProperty(product.id, newVariantId, 'category_id', newVariantCategoryId)
$scope.nextVariantId = ->
$scope.variantIdCounter = 0 unless $scope.variantIdCounter?
$scope.variantIdCounter -= 1
$scope.variantIdCounter
$scope.deleteProduct = (product) ->
if confirm(t('are_you_sure'))
$http(
method: "DELETE"
url: "/api/v0/products/" + product.id
).then (response) ->
$scope.products.splice $scope.products.indexOf(product), 1
DirtyProducts.deleteProduct product.id
$scope.displayDirtyProducts()
$scope.deleteVariant = (product, variant) ->
if product.variants.length > 1
if !$scope.variantSaved(variant)
$scope.removeVariant(product, variant)
else
if confirm(t("are_you_sure"))
$http(
method: "DELETE"
url: "/api/v0/products/" + product.id + "/variants/" + variant.id
).then (response) ->
$scope.removeVariant(product, variant)
else
alert(t("delete_product_variant"))
$scope.removeVariant = (product, variant) ->
product.variants.splice product.variants.indexOf(variant), 1
DirtyProducts.deleteVariant product.id, variant.id
$scope.displayDirtyProducts()
$scope.cloneProduct = (product) ->
BulkProducts.cloneProduct product
$scope.hasVariants = (product) ->
product.variants.length > 0
$scope.hasUnit = (variant) ->
variant.variant_unit_with_scale?
$scope.variantSaved = (variant) ->
variant.hasOwnProperty('id') && variant.id > 0
$scope.hasOnDemandVariants = (product) ->
(variant for id, variant of product.variants when variant.on_demand).length > 0
$scope.submitProducts = ->
# Pack pack $scope.products, so they will match the list returned from the server,
# then pack $scope.dirtyProducts, ensuring that the correct product info is sent to the server.
$scope.packProduct product for id, product of $scope.products
$scope.packProduct product for id, product of DirtyProducts.all()
productsToSubmit = filterSubmitProducts(DirtyProducts.all())
if productsToSubmit.length > 0
$scope.updateProducts productsToSubmit # Don't submit an empty list
else
StatusMessage.display 'alert', t("products_change")
$scope.updateProducts = (productsToSubmit) ->
$scope.displayUpdating()
$http(
method: "POST"
url: "/admin/products/bulk_update"
data:
products: productsToSubmit
filters:
'q[name_cont]': $scope.q.query
'q[variants_supplier_id_eq]': $scope.q.producerFilter
'q[variants_primary_taxon_id_eq]': $scope.q.categoryFilter
'q[s]': $scope.sorting
import_date: $scope.q.importDateFilter
page: $scope.page
per_page: $scope.per_page
).then((response) ->
DirtyProducts.clear()
BulkProducts.updateVariantLists(response.data.products || [])
$timeout -> $scope.displaySuccess()
).catch (response) ->
if response.status == 400 && response.data.errors?
errorsString = ErrorsParser.toString(response.data.errors, response.status)
$scope.displayFailure t("products_update_error") + "\n" + errorsString
else
$scope.displayFailure t("products_update_error_data") + response.status
$scope.cancel = (destination) ->
$window.location = destination
$scope.packProduct = (product) ->
if product.variants
for id, variant of product.variants
$scope.packVariant variant
$scope.packVariant = (variant) ->
if variant.variant_unit_with_scale
match = variant.variant_unit_with_scale.match(/^([^_]+)_([\d\.]+)$/)
if match
variant.variant_unit = match[1]
variant.variant_unit_scale = parseFloat(match[2])
else
variant.variant_unit = variant.variant_unit_with_scale
variant.variant_unit_scale = null
if variant.hasOwnProperty("unit_value_with_description")
match = variant.unit_value_with_description.match(/^([\d\.\,]+(?= |$)|)( |)(.*)$/)
if match
variant.unit_value = parseFloat(match[1].replace(",", "."))
variant.unit_value = null if isNaN(variant.unit_value)
if variant.unit_value && variant.variant_unit_scale
variant.unit_value = parseFloat(window.bigDecimal.multiply(variant.unit_value, variant.variant_unit_scale, 2))
variant.unit_description = match[3]
$scope.incrementLimit = ->
if $scope.limit < $scope.products.length
$scope.limit = $scope.limit + 5
$scope.displayUpdating = ->
StatusMessage.display 'progress', t("saving")
$scope.displaySuccess = ->
StatusMessage.display 'success',t("products_changes_saved")
$scope.bulk_product_form.$setPristine()
$scope.displayFailure = (failMessage) ->
StatusMessage.display 'failure', t("products_update_error_msg") + " #{failMessage}"
$scope.displayDirtyProducts = ->
count = DirtyProducts.count()
switch count
when 0 then StatusMessage.clear()
when 1 then StatusMessage.display 'notice', t("one_product_unsaved")
else StatusMessage.display 'notice', t("products_unsaved", n: count)
filterSubmitProducts = (productsToFilter) ->
filteredProducts = []
if productsToFilter instanceof Object
angular.forEach productsToFilter, (product) ->
if product.hasOwnProperty("id")
filteredProduct = {id: product.id}
filteredVariants = []
hasUpdatableProperty = false
if product.hasOwnProperty("variants")
angular.forEach product.variants, (variant) ->
result = filterSubmitVariant variant
filteredVariant = result.filteredVariant
variantHasUpdatableProperty = result.hasUpdatableProperty
filteredVariants.push filteredVariant if variantHasUpdatableProperty
if product.hasOwnProperty("name")
filteredProduct.name = product.name
hasUpdatableProperty = true
if product.hasOwnProperty("price")
filteredProduct.price = product.price
hasUpdatableProperty = true
if product.hasOwnProperty("on_hand") and filteredVariants.length == 0 #only update if no variants present
filteredProduct.on_hand = product.on_hand
hasUpdatableProperty = true
if product.hasOwnProperty("on_demand") and filteredVariants.length == 0 #only update if no variants present
filteredProduct.on_demand = product.on_demand
hasUpdatableProperty = true
if product.hasOwnProperty("inherits_properties")
filteredProduct.inherits_properties = product.inherits_properties
hasUpdatableProperty = true
if filteredVariants.length > 0 # Note that the name of the property changes to enable mass assignment of variants attributes with rails
filteredProduct.variants_attributes = filteredVariants
hasUpdatableProperty = true
filteredProducts.push filteredProduct if hasUpdatableProperty
filteredProducts
filterSubmitVariant = (variant) ->
hasUpdatableProperty = false
filteredVariant = {}
if not variant.deleted_at? and variant.hasOwnProperty("id")
filteredVariant.id = variant.id unless variant.id <= 0
if variant.hasOwnProperty("sku")
filteredVariant.sku = variant.sku
hasUpdatableProperty = true
if variant.hasOwnProperty("on_hand")
filteredVariant.on_hand = variant.on_hand
hasUpdatableProperty = true
if variant.hasOwnProperty("on_demand")
filteredVariant.on_demand = variant.on_demand
hasUpdatableProperty = true
if variant.hasOwnProperty("price")
filteredVariant.price = variant.price
hasUpdatableProperty = true
if variant.hasOwnProperty("unit_value")
filteredVariant.unit_value = variant.unit_value
hasUpdatableProperty = true
if variant.hasOwnProperty("unit_description")
filteredVariant.unit_description = variant.unit_description
hasUpdatableProperty = true
if variant.hasOwnProperty("display_name")
filteredVariant.display_name = variant.display_name
hasUpdatableProperty = true
if variant.hasOwnProperty("tax_category_id")
filteredVariant.tax_category_id = variant.tax_category_id
hasUpdatableProperty = true
if variant.hasOwnProperty("category_id")
filteredVariant.primary_taxon_id = variant.category_id
hasUpdatableProperty = true
if variant.hasOwnProperty("display_as")
filteredVariant.display_as = variant.display_as
hasUpdatableProperty = true
if variant.hasOwnProperty("producer_id")
filteredVariant.supplier_id = variant.producer_id
hasUpdatableProperty = true
if variant.hasOwnProperty("variant_unit_with_scale")
filteredVariant.variant_unit = variant.variant_unit
filteredVariant.variant_unit_scale = variant.variant_unit_scale
hasUpdatableProperty = true
if variant.hasOwnProperty("variant_unit_name")
filteredVariant.variant_unit_name = variant.variant_unit_name
hasUpdatableProperty = true
{filteredVariant: filteredVariant, hasUpdatableProperty: hasUpdatableProperty}
toObjectWithIDKeys = (array) ->
object = {}
for i of array
if array[i] instanceof Object and array[i].hasOwnProperty("id")
object[array[i].id] = angular.copy(array[i])
object[array[i].id].variants = toObjectWithIDKeys(array[i].variants) if array[i].hasOwnProperty("variants") and array[i].variants instanceof Array
object

View File

@@ -1,8 +0,0 @@
angular.module("ofn.admin").controller "ProductImageCtrl", ($scope, ProductImageService) ->
$scope.imageUploader = ProductImageService.imageUploader
$scope.imagePreview = ProductImageService.imagePreview
$scope.$watch 'product.image_url', (newValue, oldValue) ->
if newValue != oldValue
$scope.imagePreview = newValue
$scope.uploadModal.close()

View File

@@ -1,6 +0,0 @@
angular.module("ofn.admin").directive "imageModal", ($modal, ProductImageService) ->
restrict: 'C'
link: (scope, elem, attrs, ctrl) ->
elem.on "click", (ev) =>
scope.uploadModal = $modal.open(templateUrl: 'admin/modals/image_upload.html', controller: ctrl, scope: scope, windowClass: 'simple-modal')
ProductImageService.configure(scope.product)

View File

@@ -1,6 +0,0 @@
angular.module("admin.resources").factory 'ProductResource', ($resource) ->
$resource('/admin/product/:id/:action.json', {}, {
'index':
url: '/api/v0/products/bulk_products.json'
method: 'GET'
})

View File

@@ -1,76 +0,0 @@
angular.module("ofn.admin").factory "BulkProducts", (ProductResource, dataFetcher, $http) ->
new class BulkProducts
products: []
pagination: {}
fetch: (params) ->
ProductResource.index params, (data) =>
@products.length = 0
@addProducts data.products
angular.extend(@pagination, data.pagination)
cloneProduct: (product) ->
$http.post("/api/v0/products/" + product.id + "/clone").then (response) =>
dataFetcher("/api/v0/products/" + response.data.id + "?template=bulk_show").then (newProduct) =>
@unpackProduct newProduct
@insertProductAfter(product, newProduct)
updateVariantLists: (serverProducts) ->
for server_product in serverProducts
product = @findProductInList(server_product.id, @products)
product.variants = server_product.variants
@loadVariantUnitValues product.variants
find: (id) ->
@findProductInList id, @products
findProductInList: (id, product_list) ->
products = (product for product in product_list when product.id == id)
if products.length == 0 then null else products[0]
addProducts: (products) ->
for product in products
@unpackProduct product
@products.push product
insertProductAfter: (product, newProduct) ->
index = @products.indexOf(product)
@products.splice(index + 1, 0, newProduct)
unpackProduct: (product) ->
@loadVariantUnit product
loadVariantUnit: (product) ->
@loadVariantUnitValues product.variants if product.variants
loadVariantUnitValues: (variants) ->
for variant in variants
@loadVariantUnitValue variant
loadVariantUnitValue: (variant) ->
variant.variant_unit_with_scale =
if variant.variant_unit && variant.variant_unit_scale && variant.variant_unit != 'items'
"#{variant.variant_unit}_#{variant.variant_unit_scale}"
else if variant.variant_unit
variant.variant_unit
else
null
unit_value = @variantUnitValue variant
unit_value = if unit_value? then unit_value else ''
variant.unit_value_with_description = "#{unit_value} #{variant.unit_description || ''}".trim()
variantUnitValue: (variant) ->
if variant.unit_value?
if variant.variant_unit_scale
variant_unit_value = @divideAsInteger variant.unit_value, variant.variant_unit_scale
parseFloat(window.bigDecimal.round(variant_unit_value, 2))
else
variant.unit_value
else
null
# forces integer division to avoid javascript floating point imprecision
# using one billion as the multiplier so that it works for numbers with up to 9 decimal places
divideAsInteger: (a, b) ->
(a * 1000000000) / (b * 1000000000)

View File

@@ -19,11 +19,6 @@ module Spree
before_action :load_spree_api_key, only: [:index, :variant_overrides]
before_action :strip_new_properties, only: [:create, :update]
def index
@current_user = spree_current_user
@show_latest_import = params[:latest_import] || false
end
def show
session[:return_to] ||= request.referer
redirect_to( action: :edit )
@@ -65,28 +60,6 @@ module Spree
end
end
def bulk_update
product_set = product_set_from_params
product_set.collection.each { |p| authorize! :update, p }
if product_set.save
redirect_to main_app.bulk_products_api_v0_products_path(bulk_index_query)
elsif product_set.errors.present?
render json: { errors: product_set.errors }, status: :bad_request
else
render body: nil, status: :internal_server_error
end
end
def clone
@new = @product.duplicate
raise "Clone failed" unless @new.save
flash[:success] = t('.success')
redirect_to spree.admin_products_url
end
def group_buy_options
@url_filters = ::ProductFilters.new.extract(request.query_parameters)
end

View File

@@ -32,12 +32,8 @@ module Admin
[precised_unit_value, variant.unit_description].compact_blank.join(" ")
end
def products_return_to_url(url_filters)
if feature?(:admin_style_v3, spree_current_user)
return session[:products_return_to_url] || admin_products_url
end
"#{admin_products_path}#{url_filters.empty? ? '' : "#?#{url_filters.to_query}"}"
def products_return_to_url
session[:products_return_to_url] || admin_products_url
end
# if user hasn't saved any preferences on products page and there's only one producer;

View File

@@ -29,13 +29,7 @@ module Spree
scope: [:admin, :tab]).capitalize
css_classes = []
if options[:icon] && !feature?(:admin_style_v3, spree_current_user)
link = link_to_with_icon(options[:icon], titleized_label, destination_url)
css_classes << 'tab-with-icon'
else
link = link_to(titleized_label, destination_url)
end
link = link_to(titleized_label, destination_url)
selected = if options[:match_path]
PathChecker

View File

@@ -1,4 +1,4 @@
= form_with url: bulk_update_admin_products_path, method: :post, id: "products-form",
= form_with url: admin_products_bulk_update_path, method: :post, id: "products-form",
builder: BulkFormBuilder,
html: { data: { 'turbo-frame': "_self",
controller: "bulk-form",

View File

@@ -3,10 +3,7 @@
.pagination{ "data-controller": "search" }
- if pagy.prev
%button.page.prev{ data: { action: 'click->search#changePage', page: pagy.prev } }
- if feature?(:admin_style_v3, spree_current_user)
%i.icon-chevron-left{ data: { action: 'click->search#changePage', page: pagy.prev } }
- else
!= pagy_t('pagy.prev')
%i.icon-chevron-left{ data: { action: 'click->search#changePage', page: pagy.prev } }
- else
%button.page.disabled{disabled: "disabled"}!= pagy_t('pagy.prev')
@@ -22,9 +19,6 @@
- if pagy.next
%button.page.next{ data: { action: 'click->search#changePage', page: pagy.next } }
- if feature?(:admin_style_v3, spree_current_user)
%i.icon-chevron-right{ data: { action: 'click->search#changePage', page: pagy.next } }
- else
!= pagy_t('pagy.next')
%i.icon-chevron-right{ data: { action: 'click->search#changePage', page: pagy.next } }
- else
%button.page.disabled.pagination-next{disabled: "disabled"}!= pagy_t('pagy.next')

View File

@@ -1,7 +1,7 @@
= admin_inject_available_units
- content_for :page_actions do
%li= button_link_to t('admin.products.back_to_products_list'), products_return_to_url(@url_filters), :icon => 'icon-arrow-left'
%li= button_link_to t('admin.products.back_to_products_list'), products_return_to_url, :icon => 'icon-arrow-left'
%li#new_product_link
= button_link_to t(:new_product), new_object_url, { :icon => 'icon-plus', :id => 'admin_new_product' }
@@ -18,6 +18,6 @@
= button t(:update), 'icon-refresh'
= button_link_to t(:cancel), products_return_to_url(@url_filters), icon: 'icon-remove'
= button_link_to t(:cancel), products_return_to_url, icon: 'icon-remove'
#product-preview-modal-container

View File

@@ -1,14 +0,0 @@
= render 'spree/admin/products/index/header'
= render 'spree/admin/products/index/data'
= admin_inject_available_units
%div{ "ng-app": 'ofn.admin', "ng-controller": 'AdminProductEditCtrl', "ng-init": 'initialise()' }
= render 'spree/admin/products/index/filters'
%div{ 'ng-cloak' => true }
= render 'spree/admin/products/index/actions'
= render 'spree/admin/products/index/indicators'
= render 'spree/admin/products/index/products'
%div{'ng-show' => "!RequestMonitor.loading && products.length > 0" }
= render partial: 'admin/shared/angular_pagination'

View File

@@ -1,11 +0,0 @@
.controls.sixteen.columns.alpha{ 'ng-hide' => 'RequestMonitor.loading || products.length == 0' }
.ten.columns.alpha.index-controls
.per-page{'ng-show' => '!RequestMonitor.loading && products.length > 0'}
%input.per-page-select.ofn-select2{type: 'number', data: 'per_page_options', 'min-search' => 999, 'ng-model' => 'per_page', 'ng-change' => 'fetchProducts()'}
%span.per-page-feedback
{{ 'spree.admin.orders.index.results_found' | t:{number: pagination.results} }}
{{ 'spree.admin.orders.index.viewing' | t:{start: ((pagination.page -1) * pagination.per_page) +1, end: ((pagination.page -1) * pagination.per_page) + products.length} }}
.six.columns.omega
%columns-dropdown{ action: "#{controller_name}_#{action_name}" }

View File

@@ -1,5 +0,0 @@
= admin_inject_producers(@producers)
= admin_inject_taxons(@taxons)
= admin_inject_tax_categories(@tax_categories)
= admin_inject_spree_api_key(@spree_api_key)
= admin_inject_column_preferences

View File

@@ -1,33 +0,0 @@
%fieldset
%legend{align: 'center'}= t(:search)
.filters.sixteen.columns.alpha.omega
.quick_search.three.columns.alpha
%label{ for: 'quick_filter' }
%br
%input.quick-search.fullwidth{ name: "quick_filter", type: 'text', placeholder: t('admin.quick_search'), "ng-keypress": "$event.keyCode === 13 && fetchProducts()", "ng-model": 'q.query' }
.one.columns &nbsp;
.filter_select.three.columns
%label{ for: 'producer_filter' }= t 'producer'
%br
%select.fullwidth{ id: 'producer_filter', "ofn-select2-min-search": 5, "ng-model": 'q.producerFilter', "ng-options": 'producer.id as producer.name for producer in producers' }
.filter_select.three.columns
%label{ for: 'category_filter' }= t 'category'
%br
%select.fullwidth{ id: 'category_filter', "ofn-select2-min-search": 5, "ng-model": 'q.categoryFilter', "ng-options": 'taxon.id as taxon.name for taxon in taxons' }
.filter_select.three.columns
%label{ for: 'import_filter' }= t 'import_date'
%br
%select.fullwidth{ id: 'import_date_filter', "ofn-select2-min-search": 5, "ng-model": 'q.importDateFilter', "ng-init": "importDates = #{@import_dates}; showLatestImport = #{@show_latest_import}", "ng-options": 'date.id as date.name for date in importDates' }
.filter_clear.three.columns.omega
%label{ for: 'clear_all_filters' }
%br
%input.fullwidth.red{ :type => 'button', :id => 'clear_all_filters', :value => t('admin.clear_filters'), 'ng-click' => "resetSelectFilters()" }
.clearfix
.actions.filter-actions
%div
%a.button.icon-search{'ng-click' => 'fetchProducts()'}
= t(:filter_results)

View File

@@ -1,10 +0,0 @@
- content_for :page_title do
= t('.title')
- content_for :page_actions do
%div{ :class => "toolbar" }
%ul{ :class => "actions header-action-links inline-menu" }
%li#new_product_link
= button_link_to t(:new_product), new_object_url, { :icon => 'icon-plus', :id => 'admin_new_product' }
= render partial: 'spree/admin/shared/product_sub_menu'

View File

@@ -1,14 +0,0 @@
.sixteen.columns.alpha#loading{ 'ng-if' => 'RequestMonitor.loading' }
= render partial: "components/admin_spinner"
%h1
= t('.title')
.sixteen.columns.alpha{ 'ng-show' => '!RequestMonitor.loading && products.length == 0 && q.query.length == 0' }
%h1#no_results= t('.no_products')
.sixteen.columns.alpha{ 'ng-show' => '!RequestMonitor.loading && products.length == 0 && q.query.length != 0' }
%h1#no_results
= t('.no_results')
'
{{q.query}}
'

View File

@@ -1,14 +0,0 @@
.sixteen.columns.alpha{ 'ng-hide' => 'RequestMonitor.loading || products.length == 0' }
%form{ name: 'bulk_product_form', autocomplete: "off" }
%save-bar{ dirty: "bulk_product_form.$dirty", persist: "false" }
%input.red{ type: "button", value: t(:save_changes), "ng-click": "submitProducts()", "ng-disabled": "!bulk_product_form.$dirty" }
%input{ type: "button", value: t(:close), 'ng-click' => "cancel('#{admin_products_path}')" }
%table.index#listing_products.bulk
= render 'spree/admin/products/index/products_head'
%tbody{ 'ng-repeat' => 'product in products', 'ng-class-even' => "'even'", 'ng-class-odd' => "'odd'" }
= render 'spree/admin/products/index/products_product'
= render 'spree/admin/products/index/products_variant'

View File

@@ -1,41 +0,0 @@
%colgroup
%col.actions
%col.image{ "ng-show": 'columns.image.visible' }
%col.producer{ "ng-show": 'columns.producer.visible' }
%col.sku{ "ng-show": 'columns.sku.visible' }
%col.name{ "ng-show": 'columns.name.visible' }
%col.unit{ "ng-show": 'columns.unit.visible' }
%col.display_as{ "ng-show": 'columns.unit.visible' }
%col.price{ "ng-show": 'columns.price.visible' }
%col.on_hand{ "ng-show": 'columns.on_hand.visible' }
%col.on_demand{ "ng-show": 'columns.on_demand.visible' }
%col.category{ "ng-show": 'columns.category.visible' }
%col.tax_category{ "ng-show": 'columns.tax_category.visible' }
%col.inherits_properties{ "ng-show": 'columns.inherits_properties.visible' }
%col.import_date{ "ng-show": 'columns.import_date.visible' }
%col.actions
%col.actions
%col.actions
%thead
%tr{ "ng-controller": "ColumnsCtrl" }
%th.left-actions
%a{ 'ng-click' => 'toggleShowAllVariants()', :style => 'color: red; cursor: pointer' }
= t(:expand_all)
%th.image{ 'ng-show' => 'columns.image.visible' }
%th.producer{ 'ng-show' => 'columns.producer.visible' }=t('admin.producer')
%th.sku{ 'ng-show' => 'columns.sku.visible' }=t('admin.sku')
%th.name{ 'ng-show' => 'columns.name.visible' }
= render partial: 'spree/admin/shared/sortable_header', locals: {column_name: 'name'}
%th.unit{ 'ng-show' => 'columns.unit.visible' }=t('.unit')
%th.display_as{ 'ng-show' => 'columns.unit.visible' }=t('.display_as')
%th.price{ 'ng-show' => 'columns.price.visible' }=t('admin.price')
%th.on_hand{ 'ng-show' => 'columns.on_hand.visible' }=t('admin.on_hand')
%th.on_demand{ 'ng-show' => 'columns.on_demand.visible' }=t('admin.on_demand?')
%th.category{ 'ng-show' => 'columns.category.visible' }=t('.category')
%th.tax_category{ 'ng-show' => 'columns.tax_category.visible' }=t('.tax_category')
%th.inherits_properties{ 'ng-show' => 'columns.inherits_properties.visible' }=t('.inherits_properties?')
%th.import_date{ 'ng-show' => 'columns.import_date.visible' }=t('.import_date')
%th.actions
%th.actions
%th.actions

View File

@@ -1,33 +0,0 @@
%tr.product{ :id => "p_{{product.id}}" }
%td.left-actions
%a{ 'ofn-toggle-variants' => 'true', :class => "view-variants", 'ng-show' => 'hasVariants(product)' }
%a{ :class => "add-variant icon-plus-sign", 'ng-click' => "addVariant(product)", 'ng-show' => "!hasVariants(product) && hasUnit(product)" }
%td.image{ 'ng-show' => 'columns.image.visible' }
%a{class: 'image-modal'}
%img{'ng-src' => '{{ product.thumb_url }}'}
%td.producer{ 'ng-show' => 'columns.producer.visible' }
%td.sku{ 'ng-show' => 'columns.sku.visible' }
%input{ 'ng-model' => "product.sku", :name => 'product_sku', 'ofn-track-product' => 'sku', :type => 'text' }
%td.name{ 'ng-show' => 'columns.name.visible' }
%input{ 'ng-model' => "product.name", :name => 'product_name', 'ofn-track-product' => 'name', :type => 'text' }
%td.unit{ 'ng-show' => 'columns.unit.visible' }
%td.display_as{ 'ng-show' => 'columns.unit.visible' }
%td.price{ 'ng-show' => 'columns.price.visible' }
%input{ 'ng-model' => 'product.price', 'ofn-decimal' => :true, :name => 'price', 'ofn-track-product' => 'price', :type => 'text', 'ng-hide' => 'hasVariants(product)' }
%td.on_hand{ 'ng-show' => 'columns.on_hand.visible' }
%span{ 'ng-bind' => 'product.on_hand', :name => 'on_hand', 'ng-if' => '!hasOnDemandVariants(product) && (hasVariants(product) || product.on_demand)' }
%input.field{ 'ng-model' => 'product.on_hand', :name => 'on_hand', 'ofn-track-product' => 'on_hand', 'ng-if' => '!(hasVariants(product) || product.on_demand)', :type => 'number' }
%td.on_demand{ 'ng-show' => 'columns.on_demand.visible' }
%input.field{ 'ng-model' => 'product.on_demand', :name => 'on_demand', 'ofn-track-product' => 'on_demand', :type => 'checkbox', 'ng-hide' => 'hasVariants(product)' }
%td.category{ 'ng-if' => 'columns.category.visible' }
%td.tax_category{ 'ng-if' => 'columns.tax_category.visible' }
%td.inherits_properties{ 'ng-show' => 'columns.inherits_properties.visible' }
%input{ 'ng-model' => 'product.inherits_properties', :name => 'inherits_properties', 'ofn-track-product' => 'inherits_properties', type: "checkbox" }
%td.import_date{ 'ng-show' => 'columns.import_date.visible' }
%span {{(product.import_date | date:"MMMM dd, yyyy HH:mm") || ""}}
%td.actions
%a{ 'ng-click' => 'editWarn(product)', :class => "edit-product icon-edit no-text", 'ofn-with-tip' => t(:edit) }
%td.actions
%a{ 'ng-click' => 'cloneProduct(product)', :class => "clone-product icon-copy no-text", 'ofn-with-tip' => t(:clone) }
%td.actions
%a{ 'ng-click' => 'deleteProduct(product)', :class => "delete-product icon-trash no-text", 'ofn-with-tip' => t(:remove) }

View File

@@ -1,39 +0,0 @@
%tr.variant{ :id => "v_{{variant.id}}", 'ng-repeat' => 'variant in product.variants', 'ng-show' => 'DisplayProperties.showVariants(product.id)', 'ng-class-even' => "'even'", 'ng-class-odd' => "'odd'" }
%td.left-actions
%a{ :class => "variant-item icon-caret-right", 'ng-hide' => "$last" }
%a{ :class => "add-variant icon-plus-sign", 'ng-click' => "addVariant(product)", 'ng-show' => "$last", 'ofn-with-tip' => t('.new_variant') }
%td{ 'ng-show' => 'columns.image.visible' }
%td{ 'ng-show' => 'columns.producer.visible' }
%select.fullwidth{ "data-controller": "tom-select", 'ng-model' => 'variant.producer_id', :name => 'producer_id', 'ofn-track-variant' => 'producer_id', 'ng-options' => 'producer.id as producer.name for producer in producers' }
%td{ 'ng-show' => 'columns.sku.visible' }
%input{ 'ng-model' => "variant.sku", :name => 'variant_sku', 'ofn-track-variant' => 'sku', :type => 'text' }
%td{ 'ng-show' => 'columns.name.visible' }
%input{ 'ng-model' => 'variant.display_name', :name => 'variant_display_name', 'ofn-track-variant' => 'display_name', :type => 'text', placeholder: "{{ product.name }}" }
%td.unit_value{ 'ng-show' => 'columns.unit.visible' }
%select.no-search{ "data-controller": "tom-select", 'ng-model' => 'variant.variant_unit_with_scale', :name => 'variant_unit_with_scale', 'ofn-track-variant' => 'variant_unit_with_scale', 'ng-options' => 'unit[1] as unit[0] for unit in variant_unit_options' }
%input{ 'ng-model' => 'variant.unit_value_with_description', :name => 'variant_unit_value_with_description', 'ofn-track-variant' => 'unit_value_with_description', :type => 'text', :placeholder => 'value', 'ng-show' => "hasUnit(variant)" }
%input{ 'ng-model' => 'variant.variant_unit_name', :name => 'variant_unit_name', 'ofn-track-variant' => 'variant_unit_name', :placeholder => 'unit', 'ng-show' => "variant.variant_unit_with_scale == 'items'", :type => 'text' }
%td.display_as{ 'ng-show' => 'columns.unit.visible' }
%input{ 'ofn-display-as' => 'variant', 'ng-model' => 'variant.display_as', name: 'variant_display_as', 'ofn-track-variant' => 'display_as', type: 'text', placeholder: '{{ placeholder_text }}' }
%td{ 'ng-show' => 'columns.price.visible' }
%input{ 'ng-model' => 'variant.price', 'ofn-decimal' => :true, :name => 'variant_price', 'ofn-track-variant' => 'price', :type => 'text' }
%td{ 'ng-show' => 'columns.on_hand.visible' }
%input.field{ 'ng-model' => 'variant.on_hand', 'ng-change' => 'updateOnHand(product)', :name => 'variant_on_hand', 'ng-if' => '!variant.on_demand', 'ofn-track-variant' => 'on_hand', :type => 'number' }
%span{ :name => 'variant_on_hand', 'ng-if' => 'variant.on_demand' }
= t(:on_demand)
%td{ 'ng-show' => 'columns.on_demand.visible' }
%input.field{ 'ng-model' => 'variant.on_demand', :name => 'variant_on_demand', 'ofn-track-variant' => 'on_demand', :type => 'checkbox' }
%td{ 'ng-show' => 'columns.category.visible' }
%input.fullwidth{ type: 'text', id: "p{{product.id}}_category_id", 'ng-model' => 'variant.category_id', 'ofn-taxon-autocomplete' => '', 'ofn-track-variant' => 'category_id', 'multiple-selection' => 'false', placeholder: 'Category' }
%td{ 'ng-show' => 'columns.tax_category.visible' }
%select.select2{ name: 'variant_tax_category_id', "ofn-track-variant": 'tax_category_id', "ng-model": 'variant.tax_category_id', "ng-options": 'tax_category.id as tax_category.name for tax_category in tax_categories' }
%option{ value: '' }= t(:none)
%td{ 'ng-show' => 'columns.inherits_properties.visible' }
%td{ 'ng-show' => 'columns.import_date.visible' }
%span {{variant.import_date | date:"MMMM dd, yyyy HH:mm"}}
%td.actions
%a{ 'ng-click' => 'editWarn(product,variant)', :class => "edit-variant icon-edit no-text", 'ng-show' => "variantSaved(variant)", 'ofn-with-tip' => t(:edit) }
%td.actions
%span.icon-warning-sign{ 'ng-if' => 'variant.variant_overrides_count > 0', 'ofn-with-tip' => "{{ 'spree.admin.products.index.products_variant.variant_has_n_overrides' | t:{n: variant.variant_overrides_count} }}" }
%td.actions
%a{ 'ng-click' => 'deleteVariant(product,variant)', "ng-class" => '{disabled: product.variants.length < 2}', :class => "delete-variant icon-trash no-text", 'ofn-with-tip' => t(:remove) }

View File

@@ -1,3 +0,0 @@
.sixteen.columns.alpha{ 'ng-hide' => 'RequestMonitor.loading || products.length == 0', style: "margin-bottom: 10px" }
.four.columns.alpha
%input.four.columns.alpha{ :type => 'button', :value => t(:save_changes), 'ng-click' => 'submitProducts()'}

View File

@@ -14,12 +14,7 @@
= " - OFN #{t(:administration)}"
%link{:href => "https://fonts.googleapis.com/css?family=Open+Sans:400italic,600italic,400,600&subset=latin,cyrillic,greek,vietnamese", :rel => "stylesheet", :type => "text/css"}
- if feature?(:admin_style_v3, spree_current_user)
= stylesheet_pack_tag 'admin-style-v3', media: "screen, print"
- else
= stylesheet_pack_tag 'admin-styles', media: "screen, print"
= stylesheet_pack_tag 'admin-style-v3', media: "screen, print"
= render "layouts/bugsnag_js"
- if content_for? :minimal_js

View File

@@ -1,6 +1,6 @@
- content_for :sub_menu do
%ul#sub_nav.inline-menu
= tab :products, :products_v3
= tab :products, :products_v3, url: admin_products_path
= tab :properties
= tab :variant_overrides, url: main_app.admin_inventory_path, match_path: '/inventory' if feature?(:inventory, spree_current_user.enterprises)
= tab :import, url: main_app.admin_product_import_path, match_path: '/product_import'

View File

@@ -1,11 +1,11 @@
= tab :overview, label: 'dashboard', url: spree.admin_dashboard_path, icon: 'icon-dashboard'
= tab :products, :properties, :inventory, :product_import, :images, :variants, :product_properties, :group_buy_options, :seo, :products_v3, :variant_overrides, url: admin_products_path, icon: 'icon-th-large'
= tab :order_cycles, url: main_app.admin_order_cycles_path, icon: 'icon-refresh'
= tab :orders, :subscriptions, :customer_details, :adjustments, :payments, :return_authorizations, url: admin_orders_path, icon: 'icon-shopping-cart'
= tab :reports, url: main_app.admin_reports_path, icon: 'icon-file'
= tab :general_settings, :terms_of_service_files, :mail_methods, :tax_categories, :tax_rates, :tax_settings, :zones, :countries, :states, :payment_methods, :taxons, :shipping_methods, :shipping_categories, :enterprise_fees, :contents, :invoice_settings, :matomo_settings, :stripe_connect_settings, :connected_app_settings, label: 'configuration', icon: 'icon-wrench', url: edit_admin_general_settings_path
= tab :overview, label: 'dashboard', url: spree.admin_dashboard_path
= tab :products, :properties, :inventory, :product_import, :images, :variants, :product_properties, :group_buy_options, :seo, :products_v3, :variant_overrides, url: admin_products_path
= tab :order_cycles, url: main_app.admin_order_cycles_path
= tab :orders, :subscriptions, :customer_details, :adjustments, :payments, :return_authorizations, url: admin_orders_path
= tab :reports, url: main_app.admin_reports_path
= tab :general_settings, :terms_of_service_files, :mail_methods, :tax_categories, :tax_rates, :tax_settings, :zones, :countries, :states, :payment_methods, :taxons, :shipping_methods, :shipping_categories, :enterprise_fees, :contents, :invoice_settings, :matomo_settings, :stripe_connect_settings, :connected_app_settings, label: 'configuration', url: edit_admin_general_settings_path
= tab :enterprises, :enterprise_relationships, :vouchers, :oidc_settings, url: main_app.admin_enterprises_path
= tab :customers, url: main_app.admin_customers_path
= tab :enterprise_groups, url: main_app.admin_enterprise_groups_path, label: 'groups'
- if can? :admin, Spree::User
= tab(:users, :enterprise_roles, url: spree.admin_users_path, icon: 'icon-user')
= tab(:users, :enterprise_roles, url: spree.admin_users_path)

View File

@@ -1,125 +0,0 @@
@import "vendor/assets/stylesheets/normalize";
@import "vendor/assets/stylesheets/responsive-tables";
@import "vendor/assets/stylesheets/jquery.powertip";
@import "~jquery-ui/themes/base/core";
@import "~jquery-ui/themes/base/button";
@import "~jquery-ui/themes/base/resizable";
@import "vendor/assets/stylesheets/jquery-ui-theme";
@import "~jquery-ui/themes/base/dialog";
@import "../shared/ng-tags-input.min";
@import "vendor/assets/stylesheets/select2.css.scss";
@import "~flatpickr/dist/flatpickr";
@import "~flatpickr/dist/themes/material_blue";
@import "~shortcut-buttons-flatpickr/dist/themes/light";
@import "globals/functions";
@import "globals/palette";
@import "globals/variables";
@import "globals/mixins";
@import "plugins/font-awesome";
@import "../shared/variables/layout";
@import "../shared/variables/variables";
@import "../shared/utilities";
@import "shared/typography";
@import "shared/tables";
@import "shared/icons";
@import "shared/forms";
@import "shared/layout";
@import "shared/scroll_bar";
@import "../shared/trix";
@import "plugins/flatpickr-customization";
@import "plugins/powertip";
@import "plugins/select2";
@import "sections/orders";
@import "sections/products";
@import "hacks/mozilla";
@import "hacks/opera";
@import "hacks/ie";
@import "components/actions";
@import "components/alert-box";
@import "components/alert_row";
@import "components/buttons";
@import "components/date-picker";
@import "components/dialogs";
@import "components/input";
@import "components/jquery_dialog";
@import "components/messages";
@import "components/navigation";
@import "components/ng-cloak";
@import "components/page_actions";
@import "components/pagination";
@import "components/per_page_controls";
@import "components/product_autocomplete";
@import "components/progress";
@import "components/save_bar";
@import "components/sidebar";
@import "components/simple_modal";
@import "components/states";
@import "components/stripe_connect_button";
@import "components/subscriptions_states";
@import "components/table-filter";
@import "components/table_loading";
@import "components/timepicker";
@import "components/todo";
@import "components/tooltip";
@import "components/wizard_progress";
@import "pages/enterprise_form";
@import "pages/subscription_form";
@import "pages/subscription_line_items";
@import "pages/subscription_review";
@import "advanced_settings";
@import "alert";
@import "animations";
@import "change_type_form";
@import "connected_apps";
@import "customers";
@import "dashboard_item";
@import "dashboard-single-ent";
@import "dialog";
@import "disabled";
@import "dropdown";
@import "enterprise_index_panels";
@import "enterprises";
@import "filters_and_controls";
@import "grid";
@import "icons";
@import "index_panel_buttons";
@import "index_panels";
@import "modals";
@import "offsets";
@import "openfoodnetwork";
@import "order_cycles";
@import "orders";
@import "product_import";
@import "products";
@import "question-mark-tooltip";
@import "relationships";
@import "reports";
@import "select2";
@import "sidebar-item";
@import "side_menu";
@import "tables";
@import "tag_rules";
@import "terms_of_service_banner";
@import "terms_of_service_files";
@import "validation";
@import "variant_overrides";
@import "welcome";
@import "../shared/question-mark-icon";
@import "question-mark-tooltip";
@import "~tom-select/src/scss/tom-select.default";
@import "components/tom_select";
@import "app/components/modal_component/modal_component";
@import "app/webpacker/css/admin/trix.scss";

View File

@@ -1,31 +0,0 @@
table tbody tr {
&.highlight {
@each $action in $actions {
&.action-#{$action} td {
background-color: get-value($actions, $actions-bg-colors, $action);
border-color: get-value($actions, $actions-brd-colors, $action);
}
}
&.action-remove td,
&.action-void td {
text-decoration: line-through;
&.actions {
text-decoration: none;
}
}
}
&.before-highlight {
@each $action in $actions {
&.action-#{$action} td {
border-bottom-color: get-value($actions, $actions-brd-colors, $action);
}
}
}
td.actions {
background-color: transparent !important;
}
}

View File

@@ -1,93 +0,0 @@
input[type="submit"],
input[type="button"]:not(.trix-button),
button:not(.plain):not(.trix-button),
.button {
position: relative;
cursor: pointer;
font-size: 85%;
@include border-radius($border-radius);
display: inline-block;
padding: 8px 15px;
border: none;
background-color: $color-btn-bg;
color: $color-btn-text;
text-transform: uppercase;
font-weight: 600 !important;
&:before {
font-weight: normal !important;
}
&:visited,
&:active,
&:focus {
color: $color-btn-text;
}
&:hover {
background-color: $color-btn-hover-bg;
color: $color-btn-hover-text;
}
&:active:focus {
box-shadow: 0 0 8px 0 darken($color-btn-hover-bg, 5) inset;
}
&.fullwidth {
width: 100%;
text-align: center;
}
&.secondary {
background-color: transparent;
border: 1px solid $color-btn-bg;
color: $color-btn-bg;
&:hover,
&:active,
&:focus {
background-color: #ebf3fb;
}
&:active:focus {
box-shadow: none;
}
}
.badge {
position: absolute;
top: 0;
right: 0;
transform: translateY(-50%);
font-size: 10px;
text-transform: capitalize;
padding: 0px 5px;
border-radius: 3px;
&:before {
padding: 0;
}
&.danger {
background-color: $color-warning;
}
&.success {
background-color: $spree-green;
}
}
}
input.red,
a.button.red,
button.red {
background-color: $color-warning;
margin-right: 5px;
color: #ffffff;
}
a.button.red {
&:not(:hover) {
color: #fff;
background-color: $color-warning;
}
}

View File

@@ -1,20 +0,0 @@
// scss-lint:disable QualifyingElement
input.datetimepicker {
min-width: 12.9em;
}
.container input[readonly].flatpickr-input,
.container input[readonly].datepicker,
.container input[readonly].datetimepicker {
background-color: $white;
cursor: pointer;
}
img.ui-datepicker-trigger {
margin-left: -1.75em;
position: absolute;
margin-top: 0.5em;
}
// scss-lint:enable QualifyingElement

View File

@@ -1,76 +0,0 @@
.errorExplanation {
padding: 10px;
border: 1px solid very-light($color-error, 12);
background-color: very-light($color-error, 6);
border-radius: 3px;
color: $color-error;
margin-bottom: 15px;
h2 {
font-size: 140%;
color: $color-error;
margin-bottom: 5px;
}
p {
padding: 10px 0;
}
ul {
list-style-position: inside;
li {
font-weight: $font-weight-bold;
}
}
}
.flash-container {
position: fixed;
top: 0;
left: 0;
width: 100%;
z-index: 1000;
.flash {
padding: 18px;
text-align: center;
font-size: 120%;
color: $color-1;
font-weight: 600;
margin-top: 0;
&.notice {
background-color: rgba($color-notice, 0.8);
}
&.success {
background-color: rgba($color-success, 0.8);
}
&.error {
background-color: rgba($color-error, 0.8);
}
// Adjust heights to fit main layout dimension (header, navbar...)
&:nth-child(2) {
padding: 24px;
}
&:nth-child(3) {
padding: 20px;
}
.actions {
display: none; /* avoid adding new button on old design */
}
}
}
.notice:not(.flash) {
padding: 1rem;
margin-bottom: 1.5rem;
background-color: $spree-light-blue;
border-radius: $border-radius;
a {
font-weight: bold;
}
}

View File

@@ -1,174 +0,0 @@
// Navigation
//---------------------------------------------------
.inline-menu {
margin: 0;
-webkit-margin-before: 0;
-webkit-padding-start: 0;
}
nav.menu {
ul {
list-style: none;
li {
a {
padding: 10px 0;
display: block;
position: relative;
text-align: left;
border: 1px solid transparent;
text-transform: uppercase;
font-weight: 600;
font-size: 90%;
}
&.active a {
color: $color-2;
border-left-width: 0;
border-bottom-color: $color-2;
}
&:hover a {
color: $color-2;
}
}
}
}
.admin-login-navigation-bar {
ul {
text-align: right;
li {
padding: 5px 0 5px 10px;
text-align: right;
font-size: 90%;
color: $color-link;
margin-top: 8px;
&.user-logged-in-as {
width: 50%;
color: $color-body-text;
}
&:hover {
i {
color: $color-2;
}
}
}
}
}
#admin-menu {
background-color: $color-3;
ul {
display: flex;
}
li {
min-width: 90px;
flex-grow: 1;
a {
display: block;
padding: 25px 5px;
color: $color-1 !important;
text-transform: uppercase;
position: relative;
text-align: center;
font-weight: 600;
i {
display: inline;
}
&:hover {
background-color: $color-2;
&:after {
content: "";
position: absolute;
border-left: 10px solid transparent;
border-right: 10px solid transparent;
border-top: 5px solid $color-2;
bottom: 0px;
margin-bottom: -5px;
left: 50%;
margin-left: -10px;
z-index: 1;
}
}
span.text {
font-weight: 600;
}
}
a::before {
font-weight: normal;
padding-top: 0;
}
.dropdown {
width: 300px;
background-color: $color-3;
width: 200px;
z-index: 100000;
> li {
width: 200px !important;
a:after {
display: none;
}
}
}
&.selected a {
@extend a, :hover;
}
}
}
#sub-menu {
background-color: $color-2;
padding-bottom: 0;
li {
a {
display: block;
padding: 12px 20px;
color: $color-1;
text-align: center;
text-transform: uppercase;
position: relative;
font-size: 85%;
}
&.selected a,
a:hover {
&:after {
content: "";
position: absolute;
border-left: 10px solid transparent;
border-right: 10px solid transparent;
border-top: 5px solid $color-2;
bottom: 0px;
margin-bottom: -5px;
left: 50%;
margin-left: -10px;
z-index: 0;
}
}
}
}
#header figure {
margin: 0.25em 0;
}
#login-nav {
line-height: 1.75em;
}

View File

@@ -1,32 +0,0 @@
.pagination {
text-align: center;
margin: 2em 0 1em;
padding: 10px 0;
.page {
padding: 5px 8px;
text-align: center;
display: inline-block;
text-align: center;
&.current {
background-color: $color-2;
border-radius: 3px;
color: $color-1;
}
}
button {
margin: 0 0.35em;
&.active {
background-color: darken($spree-blue, 15%);
cursor: default;
}
&.disabled {
background-color: $color-btn-disabled-bg;
cursor: default;
}
}
}

View File

@@ -1,26 +0,0 @@
// Sidebar
//---------------------------------------------------
#sidebar {
overflow: visible;
border-top: 1px solid $color-border;
margin-top: 17px;
.sidebar-title {
color: $color-2;
text-transform: uppercase;
text-align: center;
font-size: 14px;
font-weight: 600;
> span {
display: inline;
background: #fff;
padding: 5px 10px;
position: relative;
top: -14px;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
}
}

View File

@@ -1,141 +0,0 @@
.ts-wrapper {
min-height: initial;
}
.ts-wrapper.multi {
.ts-control {
box-shadow: none;
border-color: $pale-blue;
&:focus {
border-color: $spree-green;
}
[data-value] {
text-shadow: none;
background-image: none;
background-repeat: initial;
box-shadow: none;
background-color: $spree-blue;
}
}
.ts-control > div {
border: none;
background-color: $spree-blue;
}
}
.ts-wrapper.plugin-remove_button .item .remove {
font-weight: bold;
}
.ts-dropdown {
margin-top: 0;
.option {
min-height: 2.25em;
display: block;
}
}
.ts-wrapper.single .ts-control,
.ts-dropdown.single {
border-color: $color-tbl-border;
}
.ts-control,
.ts-wrapper.single.input-active .ts-control {
cursor: pointer;
padding: 6px 8px;
outline: 0 !important;
min-height: 2.5em;
}
.ts-wrapper.single .ts-control {
padding-right: 2rem;
}
.ts-wrapper.inline,
.ts-wrapper.inline.input-active {
width: fit-content;
.ts-control {
padding-right: 2rem;
}
}
.ts-wrapper.single .ts-control {
box-shadow: none;
background-image: none;
background-color: $white;
}
.ts-wrapper.primary.focus .ts-control,
.ts-wrapper.primary .ts-control {
background-color: $spree-blue;
border-color: $spree-blue;
color: $white;
&:after {
border-color: $white transparent transparent transparent;
}
}
.ts-wrapper .select-multiple {
cursor: pointer;
}
.ts-wrapper.dropdown-active.primary .ts-control {
background-color: $spree-green;
border-color: $spree-green;
color: $white;
&:after {
border-color: transparent transparent $white transparent;
}
}
.dropdown-input-wrap {
padding: 0.2em;
position: relative;
&:before {
@extend [class^="icon-"];
@extend .icon-search;
position: absolute;
opacity: 0.4;
line-height: 2em;
left: 0.75em;
}
.dropdown-input {
outline: 0;
padding: 4px 6px;
border: 1px solid $spree-green;
border-radius: 0.3em;
padding-left: 1.75em;
}
}
.ts-wrapper.no-search {
.dropdown-input-wrap {
display: none;
}
}
.ts-dropdown .create:hover,
.ts-dropdown #admin-menu li.selected a.create,
#admin-menu li.selected .ts-dropdown a.create,
.ts-dropdown .option:hover,
.ts-dropdown #admin-menu li.selected a.option,
#admin-menu li.selected .ts-dropdown a.option,
.ts-dropdown .active {
background-color: $spree-blue;
color: $white;
}
.ts-dropdown [data-selectable] .highlight {
background: rgba(149, 180, 255, 0.26);
}

View File

@@ -1,156 +0,0 @@
// -------------------------------------------------------------
// Variables used in all other files
//--------------------------------------------------------------
// Fonts
//--------------------------------------------------------------
$base-font-family: "Open Sans", "Helvetica Neue", "Helvetica", Helvetica, Arial, sans-serif;
// Colors
//--------------------------------------------------------------
// Body base colors
$color-body-bg: $color-1 !default;
$color-body-text: $color-4 !default;
$color-headers: $color-4 !default;
$color-link: $color-3 !default;
$color-link-hover: $color-2 !default;
$color-link-active: $color-2 !default;
$color-link-focus: $color-2 !default;
$color-link-visited: $color-3 !default;
$color-border: very-light($color-3, 12) !default;
// Basic flash colors
$color-success: $color-2 !default;
$color-notice: $color-6 !default;
$color-warning: $color-5 !default;
$color-error: $color-5 !default;
// Table colors
$color-tbl-odd: $color-1 !default;
$color-tbl-even: very-light($color-3, 4) !default;
$color-tbl-thead: very-light($color-3, 4) !default;
$color-tbl-border: $pale-blue !default;
// Button colors
$color-btn-bg: $color-3 !default;
$color-btn-text: $color-1 !default;
$color-btn-hover-bg: $color-2 !default;
$color-btn-hover-text: $color-1 !default;
$color-btn-disabled-bg: $light-grey !default;
// Actions colors
$color-action-edit-bg: very-light($color-success, 5 ) !default;
$color-action-edit-brd: very-light($color-success, 20 ) !default;
$color-action-clone-bg: very-light($color-notice, 5 ) !default;
$color-action-clone-brd: very-light($color-notice, 15 ) !default;
$color-action-remove-bg: very-light($color-error, 5 ) !default;
$color-action-remove-brd: very-light($color-error, 10 ) !default;
$color-action-void-bg: very-light($color-error, 10 ) !default;
$color-action-void-brd: very-light($color-error, 20 ) !default;
$color-action-cancel-bg: very-light($color-notice, 10 ) !default;
$color-action-cancel-brd: very-light($color-notice, 20 ) !default;
$color-action-capture-bg: very-light($color-success, 5 ) !default;
$color-action-capture-brd: very-light($color-success, 20 ) !default;
$color-action-save-bg: very-light($color-success, 5 ) !default;
$color-action-save-brd: very-light($color-success, 20 ) !default;
$color-action-mail-bg: very-light($color-success, 5 ) !default;
$color-action-mail-brd: very-light($color-success, 20 ) !default;
// Select2 select field colors
$color-sel-bg: $color-3 !default;
$color-sel-text: $color-1 !default;
$color-sel-hover-bg: $color-2 !default;
$color-sel-hover-text: $color-1 !default;
// Text inputs colors
$color-txt-brd: $color-border !default;
$color-txt-text: $color-3 !default;
$color-txt-hover-brd: $color-2 !default;
$vpadding-txt: 7px;
$hpadding-txt: 10px;
// Modal colors
$color-modal-close-btn: $color-5 !default;
$color-modal-close-btn-hover: darken($color-5, 5%) !default;
// States label colors
$color-ste-complete-bg: $color-success !default;
$color-ste-complete-text: $color-1 !default;
$color-ste-completed-bg: $color-success !default;
$color-ste-completed-text: $color-1 !default;
$color-ste-sold-bg: $color-success !default;
$color-ste-sold-text: $color-1 !default;
$color-ste-pending-bg: $color-notice !default;
$color-ste-pending-text: $color-1 !default;
$color-ste-requires_authorization-bg: $color-notice !default;
$color-ste-requires_authorization-text: $color-1 !default;
$color-ste-awaiting_return-bg: $color-notice !default;
$color-ste-awaiting_return-text: $color-1 !default;
$color-ste-returned-bg: $color-notice !default;
$color-ste-returned-text: $color-1 !default;
$color-ste-credit_owed-bg: $color-notice !default;
$color-ste-credit_owed-text: $color-1 !default;
$color-ste-paid-bg: $color-success !default;
$color-ste-paid-text: $color-1 !default;
$color-ste-shipped-bg: $color-success !default;
$color-ste-shipped-text: $color-1 !default;
$color-ste-balance_due-bg: $color-notice !default;
$color-ste-balance_due-text: $color-1 !default;
$color-ste-backorder-bg: $color-notice !default;
$color-ste-backorder-text: $color-1 !default;
$color-ste-none-bg: $color-error !default;
$color-ste-none-text: $color-1 !default;
$color-ste-ready-bg: $color-success !default;
$color-ste-ready-text: $color-1 !default;
$color-ste-void-bg: $color-error !default;
$color-ste-void-text: $color-1 !default;
$color-ste-canceled-bg: $color-error !default;
$color-ste-canceled-text: $color-1 !default;
$color-ste-address-bg: $color-error !default;
$color-ste-address-text: $color-1 !default;
$color-ste-checkout-bg: $color-notice !default;
$color-ste-checkout-text: $color-1 !default;
$color-ste-cart-bg: $color-notice !default;
$color-ste-cart-text: $color-1 !default;
$color-ste-payment-bg: $color-error !default;
$color-ste-payment-text: $color-1 !default;
$color-ste-delivery-bg: $color-success !default;
$color-ste-delivery-text: $color-1 !default;
$color-ste-confirmation-bg: $color-error !default;
$color-ste-confirmation-text: $color-1 !default;
$color-ste-active-bg: $color-success !default;
$color-ste-active-text: $color-1 !default;
$color-ste-inactive-bg: $color-notice !default;
$color-ste-inactive-text: $color-1 !default;
// Available states
$states: completed, complete, sold, pending, awaiting_return, returned, credit_owed, paid, shipped, balance_due, backorder, checkout, cart, address, delivery, payment, confirmation, canceled, ready, void, requires_authorization, active, inactive !default;
$states-bg-colors: $color-ste-completed-bg, $color-ste-complete-bg, $color-ste-sold-bg, $color-ste-pending-bg, $color-ste-awaiting_return-bg, $color-ste-returned-bg, $color-ste-credit_owed-bg, $color-ste-paid-bg, $color-ste-shipped-bg, $color-ste-balance_due-bg, $color-ste-backorder-bg, $color-ste-checkout-bg, $color-ste-cart-bg, $color-ste-address-bg, $color-ste-delivery-bg, $color-ste-payment-bg, $color-ste-confirmation-bg, $color-ste-canceled-bg, $color-ste-ready-bg, $color-ste-void-bg, $color-ste-requires_authorization-bg, $color-ste-active-bg, $color-ste-inactive-bg !default;
$states-text-colors: $color-ste-completed-text, $color-ste-complete-text, $color-ste-sold-text, $color-ste-pending-text, $color-ste-awaiting_return-text, $color-ste-returned-text, $color-ste-credit_owed-text, $color-ste-paid-text, $color-ste-shipped-text, $color-ste-balance_due-text, $color-ste-backorder-text, $color-ste-checkout-text, $color-ste-cart-text, $color-ste-address-text, $color-ste-delivery-text, $color-ste-payment-text, $color-ste-confirmation-text, $color-ste-canceled-text, $color-ste-ready-text, $color-ste-void-text, $color-ste-requires_authorization-text, $color-ste-active-text, $color-ste-inactive-text !default;
// Available actions
$actions: edit, clone, remove, void, capture, save, cancel, mail !default;
$actions-bg-colors: $color-action-edit-bg, $color-action-clone-bg, $color-action-remove-bg, $color-action-void-bg, $color-action-capture-bg, $color-action-save-bg, $color-action-cancel-bg, $color-action-mail-bg !default;
$actions-brd-colors: $color-action-edit-brd, $color-action-clone-brd, $color-action-remove-brd, $color-action-void-brd, $color-action-capture-brd, $color-action-save-brd, $color-action-cancel-brd, $color-action-mail-brd !default;
// Sizes
//--------------------------------------------------------------
$body-font-size: 13px !default;
$h6-size: $body-font-size + 2 !default;
$h5-size: $h6-size + 2 !default;
$h4-size: $h5-size + 2 !default;
$h3-size: $h4-size + 2 !default;
$h2-size: $h3-size + 2 !default;
$h1-size: $h2-size + 2 !default;
$border-radius: 3px !default;
$border-input: 1px solid #2e3132; // Copied over from admin_v3 variables as a temporary solution
$font-weight-bold: 600 !default;
$font-weight-normal: 400 !default;
// z-index
//--------------------------------------------------------------
$tos-banner-z-index: 102;

View File

@@ -1,68 +0,0 @@
$background-grey: #eceef1;
$background-blue: $color-3;
// scss-lint:disable SelectorFormat
.flatpickr-calendar {
border-radius: 0;
// Disable animation
&.animate.open {
animation: none;
}
&.arrowBottom::after {
border-top-color: $background-grey;
}
&.arrowTop::after {
border-bottom-color: $background-blue;
}
.flatpickr-months .flatpickr-month {
border-radius: 0;
}
.flatpickr-months .flatpickr-month,
.flatpickr-current-month .flatpickr-monthDropdown-months {
background: $background-blue;
}
.flatpickr-weekdays {
background: $background-blue;
.flatpickr-weekday {
background: $background-blue;
}
}
.flatpickr-day.selected,
.flatpickr-day.startRange,
.flatpickr-day.endRange,
.flatpickr-day.selected.inRange,
.flatpickr-day.startRange.inRange,
.flatpickr-day.endRange.inRange,
.flatpickr-day.selected:focus,
.flatpickr-day.startRange:focus,
.flatpickr-day.endRange:focus,
.flatpickr-day.selected:hover,
.flatpickr-day.startRange:hover,
.flatpickr-day.endRange:hover,
.flatpickr-day.selected.prevMonthDay,
.flatpickr-day.startRange.prevMonthDay,
.flatpickr-day.endRange.prevMonthDay,
.flatpickr-day.selected.nextMonthDay,
.flatpickr-day.startRange.nextMonthDay,
.flatpickr-day.endRange.nextMonthDay {
background: $background-blue;
border-color: $background-blue;
}
}
// scss-lint:enable SelectorFormat
// customization for shortcut-buttons
.shortcut-buttons-flatpickr-wrapper > .shortcut-buttons-flatpickr-buttons {
justify-content: space-between;
flex-grow: 1;
}

View File

@@ -1,118 +0,0 @@
#powerTip {
background-color: $color-3;
padding: 5px 15px;
@include border-radius($border-radius);
&.n:before,
&.ne:before,
&.nw:before {
border-top-width: 5px;
border-top-color: $color-3;
bottom: -5px;
}
&.e:before {
border-right-width: 5px;
border-right-color: $color-3;
left: -5px;
}
&.s:before,
&.se:before,
&.sw:before {
border-bottom-width: 5px;
border-bottom-color: $color-3;
top: -5px;
}
&.w:before {
border-left-width: 5px;
border-left-color: $color-3;
right: -5px;
}
&.ne:before,
&.se:before {
border-right-width: 5px;
border-right-color: $color-3;
left: -5px;
}
&.nw:before,
&.sw:before {
border-left-width: 5px;
border-right-color: $color-3;
right: -5px;
}
&.clone,
&.yellow,
&.cancel {
background-color: $color-notice;
&.n:before,
&.ne:before,
&.nw:before {
border-top-color: $color-notice;
}
&.e:before,
&.nw:before,
&.sw:before {
border-right-color: $color-notice;
}
&.s:before,
&.se:before,
&.sw:before {
border-bottom-color: $color-notice;
}
&.w:before {
border-left-color: $color-notice;
}
}
&.edit,
&.green,
&.capture,
&.save,
&.add {
background-color: $color-success;
&.n:before,
&.ne:before,
&.nw:before {
border-top-color: $color-success;
}
&.e:before,
&.nw:before,
&.sw:before {
border-right-color: $color-success;
}
&.s:before,
&.se:before,
&.sw:before {
border-bottom-color: $color-success;
}
&.w:before {
border-left-color: $color-success;
}
}
&.remove,
&.red,
&.void {
background-color: $color-error;
&.n:before,
&.ne:before,
&.nw:before {
border-top-color: $color-error;
}
&.e:before,
&.nw:before,
&.sw:before {
border-right-color: $color-error;
}
&.s:before,
&.se:before,
&.sw:before {
border-bottom-color: $color-error;
}
&.w:before {
border-left-color: $color-error;
}
}
}

View File

@@ -1,65 +0,0 @@
// Customize orders filter
.admin-orders-index-search {
select[data-placeholder="Status"] {
width: 100%;
}
.select2-container {
width: 100% !important;
}
}
// Order-total
.order-details-total {
text-align: center;
.order-total {
font-size: 35px;
font-weight: 600;
color: $color-success;
}
}
.admin-order-form-fields {
legend.stock-location {
color: $color-body-text;
.shipment-number {
color: $color-success;
}
.stock-location-name {
color: $color-success;
}
}
}
.insufficient-stock-items {
legend {
color: $color-error;
}
table tr:last-child th {
border-bottom: 1px solid $color-tbl-border;
}
}
// Customize orduct add fieldset
#add-line-item {
fieldset {
padding: 10px 0;
.field {
margin-bottom: 0;
input[type="text"],
input[type="number"] {
width: 100%;
}
}
.actions {
.button {
margin-top: 28px;
}
}
}
}

View File

@@ -1,261 +0,0 @@
$text-inputs: "input[type=text], input[type=password], input[type=email], input[type=url], input[type=tel]";
#{$text-inputs},
input[type="date"],
input[type="datetime"],
input[type="time"],
input[type="number"],
textarea,
fieldset {
@include border-radius($border-radius);
padding: $vpadding-txt $hpadding-txt;
border: 1px solid $color-txt-brd;
color: $color-txt-text;
font-size: 90%;
&:focus {
outline: none;
border-color: $color-txt-hover-brd;
}
&[disabled] {
opacity: 0.7;
}
}
textarea {
line-height: 19px;
}
.fullwidth {
width: 100%;
}
label {
font-weight: 600;
text-transform: uppercase;
font-size: 85%;
display: inline;
margin-bottom: 5px;
color: $color-4;
&.inline {
display: inline-block !important;
}
&.block {
display: block !important;
}
}
.label-block label {
display: block;
}
span.info {
font-style: italic;
font-size: 85%;
color: lighten($color-body-text, 15);
display: block;
line-height: 20px;
margin: 5px 0;
}
.field {
padding: 10px 0;
&.checkbox {
min-height: 70px;
input[type="checkbox"] {
display: inline-block;
width: auto;
}
label {
cursor: pointer;
display: block;
}
}
ul {
border-top: 1px solid $color-border;
list-style: none;
padding-top: 5px;
li {
display: inline-block;
padding-right: 10px;
label {
font-weight: normal;
text-transform: none;
}
&.white-space-nowrap {
white-space: nowrap;
}
}
}
// Errors described by default form builder
.field_with_errors {
label {
color: $color-error;
}
input {
border-color: $color-error;
}
}
// Errors described by Spree::Admin::BaseHelper
.formError {
color: $color-error;
font-style: italic;
font-size: 85%;
}
}
fieldset {
box-shadow: none;
box-sizing: border-box;
border-color: $color-border;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
margin-left: 0;
margin-right: 0;
position: relative;
margin-bottom: 35px;
padding: 10px 0 15px 0;
background-color: transparent;
border-left: none;
border-right: none;
border-radius: 0;
&.no-border-bottom {
border-bottom: none;
margin-bottom: 0;
}
&.no-border-top {
border-top: none;
padding-top: 0;
}
legend {
background-color: $color-1;
color: $color-2;
font-size: 14px;
font-weight: 600;
text-transform: uppercase;
text-align: center;
padding: 8px 15px;
margin: 0 auto;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
i {
color: $color-link;
}
}
label {
color: lighten($color-body-text, 8);
}
.filter-actions {
margin-bottom: -32px;
margin-top: 15px;
text-align: center;
form {
display: inline-block;
}
button,
.button,
input[type="submit"],
input[type="button"],
span.or {
@include border-radius($border-radius);
-webkit-box-shadow: 0 0 0 15px $color-1;
-moz-box-shadow: 0 0 0 15px $color-1;
-ms-box-shadow: 0 0 0 15px $color-1;
-o-box-shadow: 0 0 0 15px $color-1;
box-shadow: 0 0 0 15px $color-1;
&:hover {
border-color: $color-1;
}
&:first-of-type {
margin-right: 1.25em;
}
}
span.or {
background-color: $color-1;
border-width: 5px;
margin-left: 5px;
margin-right: 5px;
position: relative;
-webkit-box-shadow: 0 0 0 5px $color-1;
-moz-box-shadow: 0 0 0 5px $color-1;
-ms-box-shadow: 0 0 0 5px $color-1;
-o-box-shadow: 0 0 0 5px $color-1;
box-shadow: 0 0 0 5px $color-1;
}
}
&.labels-inline {
.field {
margin-bottom: 0;
display: table;
width: 100%;
label,
input {
display: table-cell !important;
}
input {
width: 100%;
}
&.checkbox {
input {
width: auto !important;
}
}
}
.actions {
padding: 0;
text-align: right;
}
}
}
.form-buttons {
text-align: center;
}
select {
@extend input, [type="text"];
background-color: white;
}
.inline-checkbox {
display: inline-flex;
align-items: center;
margin-top: 3px;
input,
label {
cursor: pointer;
}
label {
margin: 0;
padding-left: 0.4rem;
}
}

View File

@@ -1,25 +0,0 @@
// Some fixes for fontwesome stylesheets
[class^="icon-"], [class*=" icon-"] {
&:before {
padding-right: 5px;
}
&.button, &.icon_link {
width: auto;
&:before {
padding-top: 3px;
}
}
}
.icon-email:before { @extend .icon-envelope, :before; }
.icon-resend_authorization_email:before { @extend .icon-envelope, :before; }
.icon-resume:before { @extend .icon-refresh, :before; }
.icon-cancel:before,
.icon-void:before { @extend .icon-remove, :before; }
.icon-capture,
.icon-capture_and_complete_order { @extend .icon-ok }
.icon-credit:before { @extend .icon-ok, :before ; }

View File

@@ -1,134 +0,0 @@
// Basics
//---------------------------------------------------
* {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
.admin {
&__section-header {
padding: 15px 0;
background-color: very-light($color-3, 4);
border-bottom: 1px solid $color-border;
.ofn-drop-down {
border: 0;
background-color: $spree-blue;
color: $color-1;
float: none;
margin-left: 3px;
&:hover,
&.expanded {
border: 0;
color: $color-1;
}
}
&__content {
display: flex;
align-items: center;
justify-content: space-between;
flex-wrap: wrap;
@media all and (min-width: $tablet_breakpoint) {
flex-wrap: nowrap;
}
}
&__title {
width: 100%;
margin-bottom: 10px;
@media all and (min-width: $tablet_breakpoint) {
margin-bottom: 0;
}
}
&__actions {
display: flex;
flex: 1 0 auto;
align-items: center;
list-style: none;
@media all and (min-width: $tablet_breakpoint) {
justify-content: flex-end;
}
> li {
display: flex;
margin-right: 10px;
&:empty {
display: none;
}
&:last-child {
margin-right: 0;
}
}
}
}
}
.hidden {
display: none;
}
.float-right {
float: right;
}
.float-left {
float: left;
}
.mr-0 {
margin-right: 0 !important;
}
.ml-0 {
margin-left: 0 !important;
}
@media print {
.print-hidden {
display: none !important;
}
}
// Header
//---------------------------------------------------
#header {
background-color: $color-1;
padding: 5px 0;
}
#logo {
height: 40px;
}
.page-title {
i {
color: $color-2;
}
}
// Content
//---------------------------------------------------
#content {
background-color: $color-1;
position: relative;
z-index: 0;
padding: 0;
margin-top: 15px;
}
// Footer
//---------------------------------------------------
#footer {
margin-top: 15px;
border-top: 1px solid $color-border;
padding: 10px 0;
}
@media print {
header,
nav {
display: none;
}
}

View File

@@ -1,208 +0,0 @@
table {
width: 100%;
margin-bottom: 15px;
border-collapse: separate;
th, td {
padding: 7px 5px;
border-right: 1px solid $color-border;
border-bottom: 1px solid $color-border;
vertical-align: middle;
text-overflow: ellipsis;
img {
border: 1px solid transparent;
}
&:first-child {
border-left: 1px solid $color-border;
}
a {
border-bottom: 1px dotted lighten($color-link, 10);
&:hover {
border-color: lighten($color-link-hover, 10);
}
}
.handle {
display: block !important;
text-align: center;
padding-right: 0;
}
&.actions {
background-color: transparent;
border: none !important;
text-align: center;
span.text {
font-size: $body-font-size;
}
[class*='icon-'].no-text {
font-size: 120%;
background-color: very-light($color-3);
border: 1px solid $color-border;
border-radius: 15px;
width: 29px;
height: 29px;
display: inline-block;
padding-top: 2px;
&:before {
text-align: center !important;
width: 27px;
display: inline-block;
}
&:hover {
border-color: transparent;
}
}
button[class*='icon-'] {
color: $color-link;
padding: 0 !important;
}
.icon-envelope-alt, .icon-eye-open {
color: $color-link;
padding-left: 0px;
&:hover {
background-color: $color-3;
color: $color-1;
}
}
.icon-trash:hover, .icon-void:hover {
background-color: $color-error;
color: $color-1;
}
.icon-cancel:hover {
background-color: $color-notice;
color: $color-1;
}
.icon-edit:hover, .icon-capture:hover, .icon-capture_and_complete_order:hover, .icon-ok:hover, .icon-plus:hover, .icon-road:hover {
background-color: $color-success;
color: $color-1;
}
.icon-copy:hover {
background-color: $color-notice;
color: $color-1;
}
}
input[type="number"],
input[type="text"] {
width: 100%;
}
&.no-border {
border-right: none;
}
.handle {
@extend .icon-reorder;
font-family: FontAwesome;
text-decoration: inherit;
display: inline-block;
speak: none;
cursor: move;
}
}
&.no-borders {
td, th {
border: none !important;
}
}
thead {
th {
padding: 10px;
border-top: 1px solid $color-border;
border-bottom: none;
background-color: $color-tbl-thead;
text-transform: uppercase;
font-size: 85%;
font-weight: $font-weight-bold;
}
}
tbody {
tr {
&:first-child th,
&:first-child td {
border-top: 1px solid $color-border;
}
&.even td {
background-color: $color-tbl-even;
img {
border: 1px solid very-light($color-3, 6);
}
}
&:hover td {
background-color: very-light($color-3, 5);
img {
border: 1px solid $color-border;
}
}
&.deleted td {
background-color: very-light($color-error, 6);
border-color: very-light($color-error, 15);
}
&.ui-sortable-placeholder td {
border: 1px solid $color-2 !important;
visibility: visible !important;
&.actions {
background-color: transparent;
border-right: none !important;
border-top: none !important;
border-bottom: none !important;
border-left: 1px solid $color-2 !important;
}
}
&.ui-sortable-helper {
width: 100%;
td {
background-color: lighten($color-3, 33);
border-bottom: 1px solid $color-border;
&.actions {
display: none;
}
}
}
}
&.no-border-top tr:first-child td {
border-top: none;
}
&.grand-total {
td {
border-color: $color-2 !important;
text-transform: uppercase;
font-size: 110%;
font-weight: 600;
background-color: lighten($color-2, 50);
}
.total {
background-color: $color-2;
color: $color-1;
}
}
}
}

View File

@@ -1,158 +0,0 @@
// Base
//--------------------------------------------------------------
body, div, dl, dt, dd, ul, ol, li, h1, h2, h3, h4, h5, h6, pre, form, p, blockquote, th, td { margin: 0; padding: 0; font-size: $body-font-size; }
body {
font-family: $base-font-family;
font-size: $body-font-size;
font-weight: 400;
color: $color-body-text;
text-rendering: optimizeLegibility;
}
hr {
border-top: 1px solid $color-border;
border-bottom: 1px solid white;
border-left: none;
}
strong, b {
font-weight: 600;
}
// links
//--------------------------------------------------------------
a {
color: $color-link;
text-decoration: none;
line-height: inherit;
&, &:hover, &:active, &:visited, &:focus {
outline: none;
}
&:visited {
color: $color-link-visited;
}
&:focus {
color: $color-link-focus;
}
&:active {
color: $color-link-active;
}
&:hover {
color: $color-link-hover;
}
}
// Headings
//--------------------------------------------------------------
h1,h2,h3,h4,h5,h6 {
font-weight: 600;
color: $color-headers;
line-height: 1.1;
}
h1 { font-size: $h1-size; line-height: $h1-size + 6 }
h2 { font-size: $h2-size; line-height: $h1-size + 4 }
h3 { font-size: $h3-size; line-height: $h1-size + 2 }
h4 { font-size: $h4-size; line-height: $h1-size }
h5 { font-size: $h5-size; line-height: $h1-size }
h6 { font-size: $h6-size; line-height: $h1-size }
// Lists
//--------------------------------------------------------------
ul {
&.inline-menu {
li {
display: inline-block;
}
}
&.fields {
list-style: none;
padding: 0;
margin: 0;
}
}
dl {
width: 100%;
overflow: hidden;
margin: 5px 0;
color: lighten($color-body-text, 15);
dt, dd {
float: left;
line-height: 16px;
padding: 5px;
text-align: justify;
}
dt {
width: 40%;
font-weight: 600;
padding-left: 0;
text-transform: uppercase;
font-size: 85%;
}
dd {
width: 60%;
padding-right: 0;
}
dd:after {
content: '';
clear: both;
}
}
// Helpers
.align-center { text-align: center }
.align-right { text-align: right }
.align-left { text-align: left }
.align-justify { text-align: justify }
.uppercase { text-transform: uppercase }
.green { color: $color-2 }
.blue { color: $color-3 }
.red { color: $color-5 }
.yellow { color: $color-6 }
.no-objects-found {
text-align: center;
font-size: 120%;
text-transform: uppercase;
padding: 40px 0px;
color: lighten($color-body-text, 15);
}
.text-normal {
font-size: 1rem;
font-weight: 300;
}
.text-big {
font-size: 1.2rem;
font-weight: 300;
}
.text-red {
color: $color-warning;
}
input.text-big {
font-size: 1.1rem;
}
.pad-top {
padding-top: 1em;
}
.white-space-nowrap {
white-space: nowrap;
}

View File

@@ -1,27 +0,0 @@
#banner-container {
position: fixed;
bottom: 0px;
left: 0;
width: 100%;
z-index: $tos-banner-z-index;
.terms-of-service-banner {
padding: 18px;
text-align: center;
font-size: 120%;
color: white;
font-weight: 600;
margin-top: 0;
background-color: rgba($color-notice, 0.8);
display: flex;
.column-left {
width: 70%;
}
.column-right {
width: 30%;
text-align: center;
}
}
}

View File

@@ -1,2 +0,0 @@
@import "../css/admin/all.scss";
@import "../../../node_modules/trix/dist/trix.css";

View File

@@ -73,17 +73,13 @@ Openfoodnetwork::Application.routes.draw do
post :import, on: :collection
end
constraints FeatureToggleConstraint.new(:admin_style_v3) do
# This might be easier to arrange once we rename the controller to plain old "products"
post '/products/bulk_update', to: 'products_v3#bulk_update'
get '/products', to: 'products_v3#index'
# we already have DELETE admin/products/:id here
delete 'products_v3/:id', to: 'products_v3#destroy', as: 'product_destroy'
delete 'products_v3/destroy_variant/:id', to: 'products_v3#destroy_variant', as: 'destroy_variant'
post 'clone/:id', to: 'products_v3#clone', as: 'clone_product'
resources :product_preview, only: [:show]
end
# This might be easier to arrange once we rename the controller to plain old "products"
post '/products/bulk_update', to: 'products_v3#bulk_update', as: 'products_bulk_update'
get '/products', to: 'products_v3#index', as: 'products'
delete 'products_v3/:id', to: 'products_v3#destroy', as: 'product_destroy'
delete 'products_v3/destroy_variant/:id', to: 'products_v3#destroy_variant', as: 'destroy_variant'
post 'clone/:id', to: 'products_v3#clone', as: 'clone_product'
resources :product_preview, only: [:show]
resources :variant_overrides do
post :bulk_update, on: :collection

View File

@@ -50,16 +50,8 @@ Spree::Core::Engine.routes.draw do
resources :users
constraints FeatureToggleConstraint.new(:admin_style_v3, negate: true) do
# Show old bulk products screen
resources :products, :index do
post :bulk_update, :on => :collection, :as => :bulk_update
end
end
resources :products, except: :index do
resources :products, except: [:index, :destroy] do
member do
get :clone
get :group_buy_options
get :seo
end
@@ -83,11 +75,6 @@ Spree::Core::Engine.routes.draw do
end
end
if Rails.env.development?
# duplicate old path for reference when admin_style_v3 enabled
resources :products_old, to: 'products#index', only: :index
end
get '/variants/search', :to => "variants#search", :as => :search_variants
resources :properties

View File

@@ -1,5 +0,0 @@
class ActivateAdminStyleV3ForNewUsers < ActiveRecord::Migration[7.0]
def up
Flipper.enable_group(:admin_style_v3, :new_2024_07_03)
end
end

View File

@@ -1,5 +0,0 @@
class EnableFeatureAdminStyleV3ForAdmins < ActiveRecord::Migration[7.0]
def up
Flipper.enable_group(:admin_style_v3, :admins)
end
end

View File

@@ -1,7 +0,0 @@
class EnableAdminStyleV3ByDefault < ActiveRecord::Migration[7.0]
def up
if Rails.env.development?
Flipper.enable(:admin_style_v3)
end
end
end

View File

@@ -1,5 +0,0 @@
class ActivateAdminStyleV3For25PCentUsers < ActiveRecord::Migration[7.0]
def up
Flipper.enable_percentage_of_actors(:admin_style_v3, 25)
end
end

View File

@@ -1,5 +0,0 @@
class ActivateAdminStyleV3For50PcUsers < ActiveRecord::Migration[7.0]
def up
Flipper.enable_percentage_of_actors(:admin_style_v3, 50)
end
end

View File

@@ -1,9 +0,0 @@
class ActivateAdminStyleV3For75PcUsers < ActiveRecord::Migration[7.0]
def up
Flipper.enable_percentage_of_actors(:admin_style_v3, 75)
end
def down
Flipper.enable_percentage_of_actors(:admin_style_v3, 50)
end
end

View File

@@ -1,5 +0,0 @@
class FullyEnableAdminStyleV3Flag < ActiveRecord::Migration[7.0]
def up
Flipper.enable(:admin_style_v3)
end
end

View File

@@ -8,16 +8,10 @@ module OpenFoodNetwork
#
module FeatureToggle
def self.conditional_features
features = {}
if Rails.env.development?
features.merge!({
"admin_style_v3" => <<~DESC,
Test the work-in-progress design updates.
DESC
});
end
# Returns environment-specific features that are conditionally available
# Currently empty but can be used to add features based on environment
features
{}
end
# Please add your new feature here to appear in the Flipper UI.
@@ -76,9 +70,6 @@ module OpenFoodNetwork
ACTIVE_BY_DEFAULT = {
# Copy features here that were activated in a migration so that new
# instances, development and test environments have the feature active.
"admin_style_v3" => <<~DESC,
Test the work-in-progress design updates.
DESC
}.freeze
def self.setup!
@@ -97,9 +88,6 @@ module OpenFoodNetwork
# Checks weather a feature is enabled for any of the given actors.
def self.enabled?(feature_name, *actors)
# TODO: Need to remove these checks when we fully remove the toggle from development as well
# need this check as Flipper won't recognize 'admin_style_v3' as it is removed for server envs
return true if !Rails.env.development? && feature_name == :admin_style_v3
return Flipper.enabled?(feature_name) if actors.empty?
actors.any? do |actor|

View File

@@ -3,130 +3,6 @@
require 'spec_helper'
RSpec.describe Spree::Admin::ProductsController do
describe 'bulk_update' do
context "updating a product we do not have access to" do
let(:s_managed) { create(:enterprise) }
let(:s_unmanaged) { create(:enterprise) }
let(:product) do
create(:simple_product, supplier_id: s_unmanaged.id, name: 'Peas')
end
before do
controller_login_as_enterprise_user [s_managed]
spree_post :bulk_update,
"products" => [{ "id" => product.id, "name" => "Pine nuts" }]
end
it "denies access" do
expect(response).to redirect_to unauthorized_path
end
it "does not update any product" do
expect(product.reload.name).not_to eq("Pine nuts")
end
end
context "when changing a product's variant_unit" do
let(:producer) { create(:enterprise) }
let!(:product) do
create(
:simple_product,
supplier_id: producer.id,
variant_unit: 'items',
variant_unit_scale: nil,
variant_unit_name: 'bunches',
unit_value: nil,
unit_description: 'some description'
)
end
before { controller_login_as_enterprise_user([producer]) }
it 'succeeds' do
spree_post :bulk_update,
"products" => [
{
"id" => product.id,
"variant_unit" => "weight",
"variant_unit_scale" => 1
}
]
expect(response).to have_http_status(:found)
end
it 'does not redirect to bulk_products' do
spree_post :bulk_update,
"products" => [
{
"id" => product.id,
"variant_unit" => "weight",
"variant_unit_scale" => 1
}
]
expect(response).to redirect_to(
'/api/v0/products/bulk_products'
)
end
end
context 'when passing empty variants_attributes' do
let(:producer) { create(:enterprise) }
let!(:product) do
create(
:simple_product,
supplier_id: producer.id,
variant_unit: 'items',
variant_unit_scale: nil,
variant_unit_name: 'bunches',
unit_value: nil,
unit_description: 'bunches'
)
end
let!(:another_product) do
create(
:simple_product,
supplier_id: producer.id,
variant_unit: 'weight',
variant_unit_scale: 1000,
variant_unit_name: nil
)
end
let!(:taxon) { create(:taxon) }
before { controller_login_as_enterprise_user([producer]) }
it 'does not fail' do
spree_post :bulk_update,
"products" => [
{
"id" => another_product.id,
"variants_attributes" => [{}]
},
{
"id" => product.id,
"variants_attributes" => [
{
"on_hand" => 2,
"price" => "5.0",
"unit_value" => 4,
"variant_unit" => "weight",
"variant_unit_scale" => "1",
"unit_description" => "",
"display_name" => "name",
"primary_taxon_id" => taxon.id,
"supplier_id" => producer.id
}
]
}
]
expect(response).to have_http_status(:found)
end
end
end
context "creating a new product" do
let(:supplier) { create(:supplier_enterprise) }
let(:taxon) { create(:taxon) }

File diff suppressed because it is too large Load Diff

View File

@@ -1,188 +0,0 @@
describe "BulkProducts service", ->
BulkProducts = $httpBackend = null
beforeEach ->
module "ofn.admin"
window.bigDecimal = jasmine.createSpyObj "bigDecimal", ["round"]
window.bigDecimal.round.and.callFake (a, b) -> a.toFixed(b)
beforeEach inject (_BulkProducts_, _$httpBackend_) ->
BulkProducts = _BulkProducts_
$httpBackend = _$httpBackend_
describe "cloning products", ->
it "clones products using a http post request to /api/products/(id)/clone", ->
BulkProducts.products = [
id: 13
]
$httpBackend.expectPOST("/api/v0/products/13/clone").respond 201,
id: 17
$httpBackend.expectGET("/api/v0/products/17?template=bulk_show").respond 200, [
id: 17
]
BulkProducts.cloneProduct BulkProducts.products[0]
$httpBackend.flush()
it "adds the product", ->
originalProduct =
id: 16
clonedProduct =
id: 17
spyOn(BulkProducts, "insertProductAfter")
spyOn(BulkProducts, "unpackProduct")
BulkProducts.products = [originalProduct]
$httpBackend.expectPOST("/api/v0/products/16/clone").respond 201, clonedProduct
$httpBackend.expectGET("/api/v0/products/17?template=bulk_show").respond 200, clonedProduct
BulkProducts.cloneProduct BulkProducts.products[0]
$httpBackend.flush()
expect(BulkProducts.unpackProduct).toHaveBeenCalledWith clonedProduct
BulkProducts.unpackProduct(clonedProduct)
expect(BulkProducts.insertProductAfter).toHaveBeenCalledWith originalProduct, clonedProduct
describe "preparing products", ->
beforeEach ->
spyOn BulkProducts, "loadVariantUnit"
it "calls loadVariantUnit for the product", ->
product = {id: 123}
BulkProducts.unpackProduct product
expect(BulkProducts.loadVariantUnit).toHaveBeenCalled()
describe "loading variant unit", ->
describe "setting product variant_unit_with_scale field", ->
it "HERE 2 sets by combining variant_unit and variant_unit_scale", ->
product =
variants:[
id: 10
variant_unit: "volume"
variant_unit_scale: .001
]
BulkProducts.loadVariantUnit product
expect(product.variants[0].variant_unit_with_scale).toEqual "volume_0.001"
it "sets to null when variant_unit is null", ->
product =
variants: [
{variant_unit: null, variant_unit_scale: 1000}
]
BulkProducts.loadVariantUnit product
expect(product.variants[0].variant_unit_with_scale).toBeNull()
it "sets to variant_unit when variant_unit_scale is null", ->
product =
variants: [
{variant_unit: 'items', variant_unit_scale: null, variant_unit_name: 'foo'}
]
BulkProducts.loadVariantUnit product
expect(product.variants[0].variant_unit_with_scale).toEqual "items"
it "sets to variant_unit when variant_unit is 'items'", ->
product =
variants: [
{variant_unit: 'items', variant_unit_scale: 1000, variant_unit_name: 'foo'}
]
BulkProducts.loadVariantUnit product
expect(product.variants[0].variant_unit_with_scale).toEqual "items"
it "loads data for variants (excl. master)", ->
spyOn BulkProducts, "loadVariantUnitValue"
product =
variants: [
{id: 2, variant_unit_scale: 1.0, unit_value: 2, unit_description: '(two)'}
]
BulkProducts.loadVariantUnitValues product.variants
expect(BulkProducts.loadVariantUnitValue).toHaveBeenCalledWith product.variants[0]
describe "setting variant unit_value_with_description", ->
it "sets by combining unit_value and unit_description", ->
product =
variants: [
{id: 1, variant_unit_scale: 1.0, unit_value: 1, unit_description: '(bottle)'}
]
BulkProducts.loadVariantUnitValues product.variants
expect(product.variants[0]).toEqual
id: 1
variant_unit_scale: 1.0,
variant_unit_with_scale: null,
unit_value: 1
unit_description: '(bottle)'
unit_value_with_description: '1 (bottle)'
it "uses unit_value when description is missing", ->
product =
variants: [
{id: 1, variant_unit_scale: 1.0, unit_value: 1}
]
BulkProducts.loadVariantUnitValues product.variants
expect(product.variants[0].unit_value_with_description).toEqual '1'
it "uses unit_description when value is missing", ->
product =
variants: [
{id: 1, variant_unit_scale: 1.0, unit_description: 'Small'}
]
BulkProducts.loadVariantUnitValues product.variants
expect(product.variants[0].unit_value_with_description).toEqual 'Small'
it "converts values from base value to chosen unit", ->
product =
variants: [
id: 1, variant_unit_scale: 1000.0, unit_value: 2500
]
BulkProducts.loadVariantUnitValues product.variants
expect(product.variants[0].unit_value_with_description).toEqual '2.5'
it "converts values from base value to chosen unit without breaking precision", ->
product =
variants: [
{id: 1,variant_unit_scale: 0.001, unit_value: 0.35}
]
BulkProducts.loadVariantUnitValues product.variants
expect(product.variants[0].unit_value_with_description).toEqual '350'
it "displays a unit_value of zero", ->
product =
variants: [
{id: 1, variant_unit_scale: 1.0, unit_value: 0}
]
BulkProducts.loadVariantUnitValues product.variants
expect(product.variants[0].unit_value_with_description).toEqual '0'
describe "calculating the scaled unit value for a variant", ->
it "returns the scaled value when variant has a unit_value", ->
variant = {variant_unit_scale: 0.001, unit_value: 5}
expect(BulkProducts.variantUnitValue(variant)).toEqual 5000
it "returns the scaled value rounded off upto 2 decimal points", ->
variant = {variant_unit_scale: 28.35, unit_value: 1234.5}
expect(BulkProducts.variantUnitValue(variant)).toEqual 43.54
it "returns the unscaled value when the product has no scale", ->
variant = {unit_value: 5}
expect(BulkProducts.variantUnitValue(variant)).toEqual 5
it "returns zero when the value is zero", ->
variant = {unit_value: 0}
expect(BulkProducts.variantUnitValue(variant)).toEqual 0
it "returns null when the variant has no unit_value", ->
variant = {}
expect(BulkProducts.variantUnitValue(variant)).toEqual null
describe "fetching a product by id", ->
it "returns the product when it is present", ->
product = {id: 123}
BulkProducts.products = [product]
expect(BulkProducts.find(123)).toEqual product
it "returns null when the product is not present", ->
BulkProducts.products = []
expect(BulkProducts.find(123)).toBeNull()