Merge pull request #10833 from Matt-Yorkley/order-angular

Remove Angular from admin orders index page
This commit is contained in:
Filipe
2023-06-01 13:57:39 +01:00
committed by GitHub
91 changed files with 1271 additions and 925 deletions

View File

@@ -1,6 +1,6 @@
# This configuration was generated by
# `rubocop --auto-gen-config --auto-gen-only-exclude --exclude-limit 1400 --no-auto-gen-timestamp`
# using RuboCop version 1.50.2.
# using RuboCop version 1.51.0.
# The point is for the user to remove these configuration records
# one by one as the offenses are removed from the code base.
# Note that changes in the inspected code, or installation of new
@@ -100,7 +100,7 @@ Layout/FirstHashElementIndentation:
Exclude:
- 'spec/services/products_renderer_spec.rb'
# Offense count: 11
# Offense count: 10
# This cop supports safe autocorrection (--autocorrect).
# Configuration parameters: AllowMultipleStyles, EnforcedHashRocketStyle, EnforcedColonStyle, EnforcedLastArgumentHashStyle.
# SupportedHashRocketStyles: key, separator, table
@@ -109,7 +109,6 @@ Layout/FirstHashElementIndentation:
Layout/HashAlignment:
Exclude:
- 'app/controllers/spree/users_controller.rb'
- 'app/models/spree/image.rb'
- 'spec/migrations/migrate_customer_names_spec.rb'
- 'spec/models/enterprise_spec.rb'
- 'spec/system/admin/customers_spec.rb'
@@ -142,7 +141,7 @@ Layout/LeadingCommentSpace:
Exclude:
- 'spec/system/admin/enterprises_spec.rb'
# Offense count: 114
# Offense count: 115
# This cop supports safe autocorrection (--autocorrect).
# Configuration parameters: EnforcedStyle.
# SupportedStyles: space, no_space
@@ -199,12 +198,13 @@ Layout/LineEndStringConcatenationIndentation:
- 'spec/system/consumer/cookies_spec.rb'
- 'spec/system/consumer/shopping/cart_spec.rb'
# Offense count: 615
# Offense count: 643
# This cop supports safe autocorrection (--autocorrect).
# Configuration parameters: Max, AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, AllowedPatterns.
# URISchemes: http, https
Layout/LineLength:
Exclude:
- 'app/components/confirm_modal_component.rb'
- 'app/controllers/admin/bulk_line_items_controller.rb'
- 'app/controllers/admin/enterprise_fees_controller.rb'
- 'app/controllers/admin/enterprise_relationships_controller.rb'
@@ -367,6 +367,8 @@ Layout/LineLength:
- 'spec/system/admin/adjustments_spec.rb'
- 'spec/system/admin/bulk_order_management_spec.rb'
- 'spec/system/admin/bulk_product_update_spec.rb'
- 'spec/system/admin/order_spec.rb'
- 'spec/system/admin/product_import_spec.rb'
# Offense count: 1
# This cop supports safe autocorrection (--autocorrect).
@@ -412,7 +414,7 @@ Layout/TrailingEmptyLines:
Exclude:
- 'Rakefile'
# Offense count: 70
# Offense count: 73
# This cop supports safe autocorrection (--autocorrect).
# Configuration parameters: AllowInHeredoc.
Layout/TrailingWhitespace:
@@ -420,7 +422,6 @@ Layout/TrailingWhitespace:
- 'app/controllers/spree/users_controller.rb'
- 'app/controllers/user_confirmations_controller.rb'
- 'app/models/enterprise.rb'
- 'app/models/spree/image.rb'
- 'spec/controllers/spree/credit_cards_controller_spec.rb'
- 'spec/controllers/user_confirmations_controller_spec.rb'
- 'spec/factories/order_factory.rb'
@@ -438,7 +439,6 @@ Layout/TrailingWhitespace:
- 'spec/system/admin/order_spec.rb'
- 'spec/system/admin/product_import_spec.rb'
- 'spec/system/admin/shipping_methods_spec.rb'
- 'spec/system/consumer/split_checkout_spec.rb'
# Offense count: 7
# This cop supports safe autocorrection (--autocorrect).
@@ -468,7 +468,7 @@ Lint/ConstantDefinitionInBlock:
- 'spec/validators/date_time_string_validator_spec.rb'
- 'spec/validators/integer_array_validator_spec.rb'
# Offense count: 8
# Offense count: 6
# Configuration parameters: IgnoreLiteralBranches, IgnoreConstantBranches.
Lint/DuplicateBranch:
Exclude:
@@ -581,6 +581,7 @@ Lint/UselessMethodDefinition:
- 'app/models/spree/gateway.rb'
# Offense count: 3
# This cop supports safe autocorrection (--autocorrect).
# Configuration parameters: CheckForMethodsWithNoSideEffects.
Lint/Void:
Exclude:
@@ -651,7 +652,7 @@ Metrics/BlockNesting:
Exclude:
- 'app/models/spree/payment/processing.rb'
# Offense count: 46
# Offense count: 47
# Configuration parameters: CountComments, Max, CountAsOne.
Metrics/ClassLength:
Exclude:
@@ -659,7 +660,6 @@ Metrics/ClassLength:
- 'app/controllers/admin/enterprise_fees_controller.rb'
- 'app/controllers/admin/enterprises_controller.rb'
- 'app/controllers/admin/order_cycles_controller.rb'
- 'app/controllers/admin/product_import_controller.rb'
- 'app/controllers/admin/resource_controller.rb'
- 'app/controllers/admin/schedules_controller.rb'
- 'app/controllers/admin/subscriptions_controller.rb'
@@ -704,7 +704,7 @@ Metrics/ClassLength:
- 'lib/reporting/reports/enterprise_fee_summary/scope.rb'
- 'lib/reporting/reports/xero_invoices/base.rb'
# Offense count: 35
# Offense count: 36
# Configuration parameters: AllowedMethods, AllowedPatterns, Max.
Metrics/CyclomaticComplexity:
Exclude:
@@ -759,7 +759,7 @@ Metrics/MethodLength:
- 'lib/reporting/reports/xero_invoices/base.rb'
- 'lib/tasks/sample_data/product_factory.rb'
# Offense count: 50
# Offense count: 49
# Configuration parameters: CountComments, Max, CountAsOne.
Metrics/ModuleLength:
Exclude:
@@ -808,16 +808,16 @@ Metrics/ModuleLength:
- 'spec/models/spree/product_spec.rb'
- 'spec/models/spree/shipping_method_spec.rb'
- 'spec/models/spree/tax_rate_spec.rb'
- 'spec/models/spree/variant_spec.rb'
- 'spec/services/permissions/order_spec.rb'
- 'spec/services/variant_units/option_value_namer_spec.rb'
- 'spec/support/request/shop_workflow.rb'
- 'spec/support/request/stripe_stubs.rb'
# Offense count: 7
# Offense count: 8
# Configuration parameters: Max, CountKeywordArgs, MaxOptionalParameters.
Metrics/ParameterLists:
Exclude:
- 'app/components/confirm_modal_component.rb'
- 'app/helpers/angular_form_builder.rb'
- 'app/models/product_import/entry_processor.rb'
- 'lib/reporting/reports/xero_invoices/base.rb'
@@ -852,6 +852,7 @@ Naming/HeredocDelimiterNaming:
- 'app/models/content_configuration.rb'
# Offense count: 5
# This cop supports unsafe autocorrection (--autocorrect-all).
# Configuration parameters: EnforcedStyleForLeadingUnderscores.
# SupportedStylesForLeadingUnderscores: disallowed, required, optional
Naming/MemoizedInstanceVariableName:
@@ -930,12 +931,6 @@ Rails/ApplicationController:
Exclude:
- 'engines/dfc_provider/app/controllers/dfc_provider/base_controller.rb'
# Offense count: 1
# This cop supports unsafe autocorrection (--autocorrect-all).
Rails/ApplicationJob:
Exclude:
- 'app/jobs/report_job.rb'
# Offense count: 5
# This cop supports unsafe autocorrection (--autocorrect-all).
# Configuration parameters: NilOrEmpty, NotPresent, UnlessPresent.
@@ -1083,7 +1078,6 @@ Rails/InverseOf:
Exclude:
- 'app/models/enterprise.rb'
- 'app/models/order_cycle.rb'
- 'app/models/spree/adjustment.rb'
- 'app/models/spree/country.rb'
- 'app/models/spree/inventory_unit.rb'
- 'app/models/spree/line_item.rb'
@@ -1122,7 +1116,7 @@ Rails/LexicallyScopedActionFilter:
- 'app/controllers/spree/admin/zones_controller.rb'
- 'app/controllers/spree/users_controller.rb'
# Offense count: 9
# Offense count: 8
# This cop supports unsafe autocorrection (--autocorrect-all).
Rails/NegateInclude:
Exclude:
@@ -1131,11 +1125,10 @@ Rails/NegateInclude:
- 'app/models/product_import/spreadsheet_entry.rb'
- 'app/models/spree/order/checkout.rb'
- 'app/services/order_cart_reset.rb'
- 'engines/order_management/app/services/order_management/stock/estimator.rb'
- 'lib/spree/localized_number.rb'
- 'spec/support/matchers/table_matchers.rb'
# Offense count: 18
# Offense count: 17
Rails/OutputSafety:
Exclude:
- 'app/helpers/angular_form_helper.rb'
@@ -1180,7 +1173,7 @@ Rails/PluckInWhere:
Exclude:
- 'app/models/spree/variant.rb'
# Offense count: 28
# Offense count: 30
# This cop supports unsafe autocorrection (--autocorrect-all).
Rails/RedundantPresenceValidationOnBelongsTo:
Exclude:
@@ -1690,7 +1683,7 @@ Style/RedundantStringEscape:
- 'spec/controllers/spree/admin/shipping_methods_controller_spec.rb'
- 'spec/system/admin/enterprise_fees_spec.rb'
# Offense count: 206
# Offense count: 208
Style/Send:
Exclude:
- 'app/controllers/split_checkout_controller.rb'
@@ -1740,7 +1733,7 @@ Style/SlicingWithRange:
- 'engines/order_management/app/services/order_management/subscriptions/validator.rb'
- 'lib/discourse/single_sign_on.rb'
# Offense count: 29
# Offense count: 28
# This cop supports unsafe autocorrection (--autocorrect-all).
# Configuration parameters: Mode.
Style/StringConcatenation:
@@ -1761,7 +1754,6 @@ Style/StringConcatenation:
- 'lib/spree/core/environment_extension.rb'
- 'spec/models/spree/line_item_spec.rb'
- 'spec/models/spree/product_spec.rb'
- 'spec/models/spree/variant_spec.rb'
- 'spec/services/embedded_page_service_spec.rb'
- 'spec/support/api_helper.rb'
- 'spec/support/features/datepicker_helper.rb'

View File

@@ -7,7 +7,6 @@
// jquery and angular
//= require jquery2
//= require jquery_ujs
//= require jquery.ui.all
//= require jquery.powertip
//= require jquery.cookie

View File

@@ -1,32 +0,0 @@
angular.module("admin.orders").controller "bulkInvoiceCtrl", ($scope, $http, $timeout) ->
$scope.createBulkInvoice = ->
$scope.invoice_id = null
$scope.poll = 1
$scope.loading = true
$scope.message = null
$scope.error = null
$scope.poll_wait = 5 # 5 Seconds between each check
$scope.poll_retries = 80 # Maximum checks before stopping
$http.post('/admin/orders/invoices', {order_ids: $scope.selected_orders}).then (response) ->
$scope.invoice_id = response.data
$scope.pollBulkInvoice()
$scope.pollBulkInvoice = ->
$timeout($scope.nextPoll, $scope.poll_wait * 1000)
$scope.nextPoll = ->
$http.get('/admin/orders/invoices/'+$scope.invoice_id+'/poll').then (response) ->
$scope.loading = false
$scope.message = t('js.admin.orders.index.bulk_invoice_created')
.catch (response) ->
$scope.poll++
if $scope.poll > $scope.poll_retries
$scope.loading = false
$scope.error = t('js.admin.orders.index.bulk_invoice_failed')
return
$scope.pollBulkInvoice()

View File

@@ -1,117 +0,0 @@
angular.module("admin.orders").controller "ordersCtrl", ($scope, $timeout, RequestMonitor, Orders, SortOptions, $window, $filter, $location, KeyValueMapStore) ->
$scope.RequestMonitor = RequestMonitor
$scope.pagination = Orders.pagination
$scope.orders = Orders.all
$scope.sortOptions = SortOptions
$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.selected_orders = []
$scope.checkboxes = {}
$scope.selected = false
$scope.select_all = false
$scope.poll = 0
$scope.rowStatus = {}
KeyValueMapStore.localStorageKey = 'ordersFilters'
KeyValueMapStore.storableKeys = ["q", "sorting", "page", "per_page"]
$scope.initialise = ->
unless KeyValueMapStore.restoreValues($scope)
$scope.setDefaults()
$scope.fetchResults()
$scope.setDefaults = ->
$scope.per_page = 15
$scope.q = {
completed_at_not_null: true
}
e = new CustomEvent("flatpickr_clear");
window.dispatchEvent(e)
$scope.clearFilters = () ->
KeyValueMapStore.clearKeyValueMap()
$scope.setDefaults()
$scope.fetchResults()
$scope.fetchResults = (page=1) ->
startDateWithTime = $scope.appendStringIfNotEmpty($scope.q?.completed_at_gteq, ' 00:00:00')
endDateWithTime = $scope.appendStringIfNotEmpty($scope.q?.completed_at_lteq, ' 23:59:59')
$scope.resetSelected()
params = {
'q[completed_at_gteq]': startDateWithTime,
'q[completed_at_lteq]': endDateWithTime,
'q[state_eq]': $scope.q?.state_eq,
'q[number_cont]': $scope.q?.number_cont,
'q[email_cont]': $scope.q?.email_cont,
'q[bill_address_firstname_start]': $scope.q?.bill_address_firstname_start,
'q[bill_address_lastname_start]': $scope.q?.bill_address_lastname_start,
# Set default checkbox values to null. See: https://github.com/openfoodfoundation/openfoodnetwork/pull/3076#issuecomment-440010498
'q[completed_at_not_null]': $scope.q?.completed_at_not_null || null,
'q[distributor_id_in][]': $scope.q?.distributor_id_in,
'q[order_cycle_id_in][]': $scope.q?.order_cycle_id_in,
'q[s]': $scope.sorting || 'completed_at desc',
shipping_method_id: $scope.q?.shipping_method_id,
per_page: $scope.per_page,
page: page
}
KeyValueMapStore.setStoredValues($scope)
RequestMonitor.load(Orders.index(params).$promise)
$scope.appendStringIfNotEmpty = (baseString, stringToAppend) ->
return baseString unless baseString
return baseString if baseString.endsWith(stringToAppend)
baseString + stringToAppend
$scope.resetSelected = ->
$scope.selected_orders.length = 0
$scope.selected = false
$scope.select_all = false
$scope.checkboxes = {}
$scope.toggleSelection = (id) ->
index = $scope.selected_orders.indexOf(id)
if index == -1
$scope.selected_orders.push(id)
else
$scope.selected_orders.splice(index, 1)
$scope.toggleAll = ->
$scope.selected_orders.length = 0
$scope.orders.forEach (order) ->
$scope.checkboxes[order.id] = $scope.select_all
$scope.selected_orders.push order.id if $scope.select_all
$scope.$watch 'sortOptions', (sort) ->
return unless sort && sort.predicate != ""
$scope.sorting = sort.getSortingExpr()
$scope.fetchResults()
, true
$scope.capturePayment = (order) ->
$scope.rowAction('capture', order)
$scope.shipOrder = (order) ->
$scope.rowAction('ship', order)
$scope.rowAction = (action, order) ->
$scope.rowStatus[order.id] = "loading"
Orders[action](order).$promise.then (data) ->
$scope.rowStatus[order.id] = "success"
$timeout(->
$scope.rowStatus[order.id] = null
, 1500)
, (error) ->
$scope.rowStatus[order.id] = "error"
$scope.changePage = (newPage) ->
$scope.page = newPage
$scope.fetchResults(newPage)

View File

@@ -1,5 +0,0 @@
angular.module("admin.orders").directive "invoicesModal", ($modal) ->
restrict: 'C'
link: (scope, elem, attrs, ctrl) ->
elem.on "click", (ev) =>
scope.uploadModal = $modal.open(templateUrl: 'admin/modals/bulk_invoice.html', controller: ctrl, scope: scope, windowClass: 'simple-modal')

View File

@@ -0,0 +1,34 @@
// This is a manifest file that'll be compiled into including all the files listed below.
// Add new JavaScript/Coffee code in separate files in this directory and they'll automatically
// be included in the compiled file accessible from http://example.com/assets/application.js
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
// the compiled file.
//
//= require jquery2
//= require admin/spree/spree-select2
//= require admin/spree/handlebar_extensions
//= require i18n/translations
//= require darkswarm/i18n.translate.js
//= require moment/min/moment.min.js
//= require moment/locale/ar.js
//= require moment/locale/ca.js
//= require moment/locale/de.js
//= require moment/locale/en-gb.js
//= require moment/locale/es.js
//= require moment/locale/fil.js
//= require moment/locale/fr.js
//= require moment/locale/it.js
//= require moment/locale/nb.js
//= require moment/locale/nl-be.js
//= require moment/locale/pt-br.js
//= require moment/locale/pt.js
//= require moment/locale/ru.js
//= require moment/locale/sv.js
//= require moment/locale/tr.js
//= require moment/locale/pl.js
//= require js-big-decimal/dist/web/js-big-decimal.min.js
window.angular = { module: function(noop){ return { value: function(){} } } }

View File

@@ -67,11 +67,3 @@ document.addEventListener "turbo:before-render", ->
rootscope = null
window.injector = null
true
document.addEventListener "ajax:beforeSend", (event) =>
window.Turbo.navigator.adapter.progressBar.setValue(0)
window.Turbo.navigator.adapter.progressBar.show()
document.addEventListener "ajax:complete", (event) =>
window.Turbo.navigator.adapter.progressBar.setValue(100)
window.Turbo.navigator.adapter.progressBar.hide()

View File

@@ -4,7 +4,16 @@
{{ 'admin.actions' | t }}
%i{ 'ng-class' => "expanded && 'icon-caret-up' || !expanded && 'icon-caret-down'" }
%div.menu{ 'ng-show' => "expanded" }
%a.menu_item{ 'ng-repeat' => "link in links", href: '{{link.url}}', target: "{{link.target || '_self'}}", data: { method: "{{ link.method || 'get' }}", confirm: "{{link.confirm}}" } }
%span
%i{ ng: { class: "link.icon" } }
%span {{ link.name }}
%div{ 'ng-repeat' => "link in links" }
%a.menu_item{ 'ng-if': "link.method", href: '{{link.url}}', target: "{{link.target || '_self'}}", data: { method: "{{ link.method }}", "ujs-navigate": "false", confirm: "{{link.confirm}}" } }
%span
%i{ ng: { class: "link.icon" } }
%span {{ link.name }}
%a.menu_item{ 'ng-if': "link.confirm && !link.method", href: '{{link.url}}', target: "{{link.target || '_self'}}", "data-confirm": "{{link.confirm}}" }
%span
%i{ ng: { class: "link.icon" } }
%span {{ link.name }}
%a.menu_item{ 'ng-if': "!link.confirm && !link.method", href: '{{link.url}}', target: "{{link.target || '_self'}}" }
%span
%i{ ng: { class: "link.icon" } }
%span {{ link.name }}

View File

@@ -0,0 +1,13 @@
# frozen_string_literal: true
class SessionChannel < ApplicationCable::Channel
def self.for_request(request)
"SessionChannel:#{request.session.id}"
end
def subscribed
return reject if current_user.nil?
stream_from "SessionChannel:#{session_id}"
end
end

View File

@@ -1,11 +1,12 @@
# frozen_string_literal: true
class ConfirmModalComponent < ModalComponent
def initialize(id:, confirm_actions: nil, controllers: nil, message: nil, confirm_reflexes: nil)
def initialize(id:, confirm_actions: nil, reflex: nil, controller: nil, message: nil, confirm_reflexes: nil)
super(id: id, close_button: true)
@confirm_actions = confirm_actions
@reflex = reflex
@confirm_reflexes = confirm_reflexes
@controllers = controllers
@controller = controller
@message = message
end

View File

@@ -1,4 +1,4 @@
%div{ id: @id, "data-controller": "modal #{@controllers}", "data-action": "keyup@document->modal#closeIfEscapeKey" }
%div{ id: @id, "data-controller": "modal #{@controller}", "data-action": "keyup@document->modal#closeIfEscapeKey", "data-#{@controller}-reflex-value": @reflex }
.reveal-modal-bg.fade{ "data-modal-target": "background", "data-action": "click->modal#close" }
.reveal-modal.fade.tiny.help-modal{ "data-modal-target": "modal" }
= content

View File

@@ -1,6 +1,6 @@
%tr
- @columns.each do |column|
%td.products_column{class: column[:id]}
- if column[:id] == "name" && @image
- if column[:id] == "name" && @image&.attachment.present?
= image_tag @image.url(:mini)
= column[:value]

View File

@@ -47,17 +47,15 @@ module Api
def capture
authorize! :admin, order
pending_payment = order.pending_payments.first
payment_capture = OrderCaptureService.new(order)
return payment_capture_failed unless order.payment_required? && pending_payment
if pending_payment.capture!
if payment_capture.call
render json: order.reload, serializer: Api::Admin::OrderSerializer, status: :ok
elsif payment_capture.gateway_error.present?
error_during_processing(payment_capture.gateway_error)
else
payment_capture_failed
end
rescue Spree::Core::GatewayError => e
error_during_processing(e)
end
private

View File

@@ -9,6 +9,7 @@ require 'spree/core/controller_helpers/common'
require 'open_food_network/referer_parser'
class ApplicationController < ActionController::Base
include CablecarResponses
include Pagy::Backend
include RequestTimeouts

View File

@@ -11,11 +11,18 @@ module Spree
before_action :load_order, only: [:edit, :update, :fire, :resend,
:invoice, :print, :distribution]
before_action :load_distribution_choices, only: [:new, :edit, :update, :distribution]
before_action :require_distributor_abn, only: :invoice
before_action :restore_saved_query!, only: :index
respond_to :html, :json
def index
orders = SearchOrders.new(search_params, spree_current_user).orders
@pagy, @orders = pagy(orders, items: params[:per_page] || 15)
update_search_results if searching?
end
def new
@order = Order.create
@order.created_by = spree_current_user
@@ -110,6 +117,36 @@ module Spree
private
def update_search_results
session[:admin_orders_search] = search_params
render cable_ready: cable_car.inner_html(
"#orders-index",
partial("spree/admin/orders/table", locals: { pagy: @pagy, orders: @orders })
)
end
def searching?
params[:q].present? && request.format.symbol == :cable_ready
end
def search_params
default_filters.deep_merge(
params.permit(:page, :per_page, :shipping_method_id, q: {})
).to_h.with_indifferent_access
end
def default_filters
{ q: { completed_at_not_null: 1, s: "completed_at desc" } }
end
def restore_saved_query!
return unless request.format.html?
@_params = ActionController::Parameters.new(session[:admin_orders_search] || {})
@stored_query = search_params.to_query
end
def on_update
@order.recreate_all_fees!

View File

@@ -62,7 +62,6 @@ module Spree
{ name: t(:resend_confirmation),
url: spree.resend_admin_order_path(@order),
icon: 'icon-email',
method: 'post',
confirm: t(:confirm_resend_order_confirmation) }
end

View File

@@ -1,7 +1,10 @@
# frozen_string_literal: true
class BulkInvoiceJob < ApplicationJob
def perform(order_ids, filepath)
include CableReady::Broadcaster
delegate :render, to: ActionController::Base
def perform(order_ids, filepath, options = {})
pdf = CombinePDF.new
sorted_orders(order_ids).each do |order|
@@ -11,6 +14,8 @@ class BulkInvoiceJob < ApplicationJob
end
pdf.save filepath
broadcast(filepath, options[:channel]) if options[:channel]
end
private
@@ -18,10 +23,22 @@ class BulkInvoiceJob < ApplicationJob
# Ensures the records are returned in the same order the ids were originally given in
def sorted_orders(order_ids)
orders_by_id = Spree::Order.where(id: order_ids).to_a.index_by(&:id)
order_ids.map { |id| orders_by_id[id] }
order_ids.map { |id| orders_by_id[id.to_i] }
end
def renderer
@renderer ||= InvoiceRenderer.new
end
def broadcast(filepath, channel)
file_id = filepath.split("/").last.split(".").first
cable_ready[channel].
inner_html(
selector: "#bulk_invoices_modal .modal-content",
html: render(partial: "spree/admin/orders/bulk/invoice_link",
locals: { invoice_url: "/admin/orders/invoices/#{file_id}" })
).
broadcast
end
end

View File

@@ -0,0 +1,101 @@
# frozen_string_literal: true
module Admin
class OrdersReflex < ApplicationReflex
before_reflex :authorize_order, only: [:capture, :ship]
def capture
payment_capture = OrderCaptureService.new(@order)
if payment_capture.call
morph dom_id(@order), render(partial: "spree/admin/orders/table_row",
locals: { order: @order.reload, success: true })
else
flash[:error] = with_locale{
payment_capture.gateway_error || I18n.t(:payment_processing_failed)
}
morph_admin_flashes
end
end
def ship
if @order.ship
morph dom_id(@order), render(partial: "spree/admin/orders/table_row",
locals: { order: @order.reload, success: true })
else
flash[:error] = with_locale{ I18n.t("api.orders.failed_to_update") }
morph_admin_flashes
end
end
def bulk_invoice(params)
cable_ready.append(
selector: "#orders-index",
html: render(partial: "spree/admin/orders/bulk/invoice_modal")
).broadcast
BulkInvoiceJob.perform_later(
params[:bulk_ids],
"tmp/invoices/#{Time.zone.now.to_i}-#{SecureRandom.hex(2)}.pdf",
channel: SessionChannel.for_request(request)
)
morph :nothing
end
def cancel_orders(params)
cancelled_orders = OrdersBulkCancelService.new(params, current_user).call
cable_ready.dispatch_event(name: "modal:close")
cancelled_orders.each do |order|
cable_ready.replace(
selector: dom_id(order),
html: render(partial: "spree/admin/orders/table_row", locals: { order: order })
)
end
cable_ready.broadcast
morph :nothing
end
def resend_confirmation_emails(params)
editable_orders.where(id: params[:bulk_ids]).find_each do |order|
next unless can? :resend, order
Spree::OrderMailer.confirm_email_for_customer(order.id, true).deliver_later
end
success("admin.resend_confirmation_emails_feedback", params[:bulk_ids].count)
end
def send_invoices(params)
count = 0
editable_orders.where(id: params[:bulk_ids]).find_each do |o|
next unless o.distributor.can_invoice? && (o.resumed? || o.complete?)
Spree::OrderMailer.invoice_email(o.id).deliver_later
count += 1
end
success("admin.send_invoice_feedback", count)
end
private
def authorize_order
@order = Spree::Order.find_by(id: element.dataset[:id])
authorize! :admin, @order
end
def success(i18n_key, count)
flash[:success] = with_locale { I18n.t(i18n_key, count: count) }
cable_ready.dispatch_event(name: "modal:close")
morph_admin_flashes
end
def editable_orders
Permissions::Order.new(current_user).editable_orders
end
end
end

View File

@@ -20,6 +20,8 @@ class ApplicationReflex < StimulusReflex::Reflex
delegate :current_user, to: :connection
private
def current_ability
Spree::Ability.new(current_user)
end
@@ -27,4 +29,8 @@ class ApplicationReflex < StimulusReflex::Reflex
def with_locale(&block)
I18n.with_locale(current_user.locale, &block)
end
def morph_admin_flashes
morph "#flashes", render(partial: "admin/shared/flashes", locals: { flashes: flash })
end
end

View File

@@ -1,35 +0,0 @@
# frozen_string_literal: true
class BulkActionsInOrdersListReflex < ApplicationReflex
def resend_confirmation_email(order_ids)
editable_orders.where(id: order_ids).find_each do |o|
Spree::OrderMailer.confirm_email_for_customer(o.id, true).deliver_later if can? :resend, o
end
success("admin.resend_confirmation_emails_feedback", order_ids.count)
end
def send_invoice(order_ids)
count = 0
editable_orders.where(id: order_ids).find_each do |o|
next unless o.distributor.can_invoice? && (o.resumed? || o.complete?)
Spree::OrderMailer.invoice_email(o.id).deliver_later
count += 1
end
success("admin.send_invoice_feedback", count)
end
private
def success(i18n_key, count)
flash[:success] = I18n.t(i18n_key, count: count)
cable_ready.dispatch_event(name: "modal:close")
morph "#flashes", render(partial: "shared/flashes", locals: { flashes: flash })
end
def editable_orders
Permissions::Order.new(current_user).editable_orders
end
end

View File

@@ -1,9 +0,0 @@
# frozen_string_literal: true
class CancelOrdersReflex < ApplicationReflex
def confirm(params)
OrdersBulkCancelService.new(params, current_user).call
cable_ready.dispatch_event(name: "modal:close")
# flash[:success] = Spree.t(:order_updated)
end
end

View File

@@ -21,6 +21,6 @@ class WhiteLabelReflex < ApplicationReflex
I18n.t("admin.enterprises.form.white_label.remove_logo_success")
}
cable_ready.dispatch_event(name: "modal:close")
morph "#flashes", render(partial: "shared/flashes", locals: { flashes: flash })
morph_admin_flashes
end
end

View File

@@ -0,0 +1,22 @@
# frozen_string_literal: true
# Use `authorize! :admin order` before calling this service
class OrderCaptureService
attr_reader :gateway_error
def initialize(order)
@order = order
@gateway_error = nil
end
def call
return false unless @order.payment_required?
return false unless (pending_payment = @order.pending_payments.first)
pending_payment.capture!
rescue Spree::Core::GatewayError => e
@gateway_error = e
false
end
end

View File

@@ -2,14 +2,14 @@
class OrdersBulkCancelService
def initialize(params, current_user)
@order_ids = params[:order_ids]
@order_ids = params[:bulk_ids]
@current_user = current_user
@send_cancellation_email = params[:send_cancellation_email]
@restock_items = params[:restock_items]
end
def call
editable_orders.where(id: @order_ids).find_each do |order|
editable_orders.where(id: @order_ids).each do |order|
order.send_cancellation_email = @send_cancellation_email
order.restock_items = @restock_items
order.cancel

View File

@@ -26,7 +26,7 @@ class SearchOrders
base_query = ::Permissions::Order.new(current_user).editable_orders.not_empty
.or(::Permissions::Order.new(current_user).editable_orders.finalized)
return base_query unless params[:shipping_method_id]
return base_query if params[:shipping_method_id].blank?
base_query
.joins(shipments: :shipping_rates)

View File

@@ -4,7 +4,8 @@
.row
= t('.stripe_account_connected')
.row
=link_to t('.disconnect'), main_app.admin_stripe_account_path(@stripe_account), method: :delete, class: 'button'
=link_to t('.disconnect'), main_app.admin_stripe_account_path(@stripe_account), data: { method: :delete, "ujs-navigate": "false"}, class: 'button'
- else
.row.stripe-info
.six.columns.alpha

View File

@@ -22,4 +22,4 @@
%br
= button_to t(".link_account_button"),
Spree::Core::Engine.routes.url_helpers.spree_user_openid_connect_omniauth_authorize_path(auth_type: "login"),
method: :post
data: { method: :post, "ujs-navigate": "false" }

View File

@@ -36,4 +36,4 @@
%td.actions{ ng: { if: 'orderCycle.viewing_as_coordinator' } }
%a.clone-order-cycle.icon-copy.no-text{ ng: { href: '{{orderCycle.clone_path}}'}, 'ofn-with-tip' => t(:clone) }
%td.actions{ ng: { if: 'orderCycle.deletable && orderCycle.viewing_as_coordinator' }}
%a.delete-order-cycle.icon-trash.no-text{ ng: { href: '{{orderCycle.delete_path}}'}, data: { method: 'delete', confirm: t(:are_you_sure) }, 'ofn-with-tip' => t(:remove) }
%a.delete-order-cycle.icon-trash.no-text{ ng: { href: '{{orderCycle.delete_path}}'}, data: { method: 'delete', confirm: t(:are_you_sure), "ujs-navigate": "false" }, 'ofn-with-tip' => t(:remove) }

View File

@@ -21,11 +21,11 @@
= hidden_field_tag "order_cycle[selected_distributor_shipping_method_ids][]", ""
- @order_cycle.distributors.each do |distributor|
- distributor_shipping_methods = @order_cycle.attachable_distributor_shipping_methods.where("distributor_id = ?", distributor.id).includes(:shipping_method)
%tr{ class: "distributor-#{distributor.id}-shipping-methods", "data-controller": "select-all" }
%tr{ class: "distributor-#{distributor.id}-shipping-methods", "data-controller": "checked" }
%td.text-center
- if distributor_shipping_methods.many?
%label
= check_box_tag nil, nil, nil, { "data-action": "change->select-all#toggleAll", "data-select-all-target": "all" }
= check_box_tag nil, nil, nil, { "data-action": "change->checked#toggleAll", "data-checked-target": "all" }
= t(".select_all")
%td
%em= distributor.name
@@ -36,7 +36,7 @@
distributor_shipping_method.id,
@order_cycle.distributor_shipping_methods.include?(distributor_shipping_method),
id: "order_cycle_selected_distributor_shipping_method_ids_#{distributor_shipping_method.id}",
data: ({ "action" => "change->select-all#toggleCheckbox", "select-all-target" => "checkbox" } if distributor_shipping_method.shipping_method.frontend?)
data: ({ "action" => "change->checked#toggleCheckbox", "checked-target" => "checkbox" } if distributor_shipping_method.shipping_method.frontend?)
= distributor_shipping_method.shipping_method.name
- distributor.shipping_methods.backend.each do |shipping_method|
%label.disabled
@@ -52,11 +52,11 @@
= hidden_field_tag "order_cycle[selected_distributor_payment_method_ids][]", ""
- @order_cycle.distributors.each do |distributor|
- distributor_payment_methods = @order_cycle.attachable_distributor_payment_methods.where("distributor_id = ?", distributor.id).includes(:payment_method)
%tr{ class: "distributor-#{distributor.id}-payment-methods", "data-controller": "select-all" }
%tr{ class: "distributor-#{distributor.id}-payment-methods", "data-controller": "checked" }
%td.text-center
- if distributor_payment_methods.many?
%label
= check_box_tag nil, nil, nil, { "data-action": "change->select-all#toggleAll", "data-select-all-target": "all" }
= check_box_tag nil, nil, nil, { "data-action": "change->checked#toggleAll", "data-checked-target": "all" }
= t(".select_all")
%td
%em= distributor.name
@@ -67,7 +67,7 @@
distributor_payment_method.id,
@order_cycle.distributor_payment_methods.include?(distributor_payment_method),
id: "order_cycle_selected_distributor_payment_method_ids_#{distributor_payment_method.id}",
data: ({ "action" => "change->select-all#toggleCheckbox", "select-all-target" => "checkbox" } if distributor_payment_method.payment_method.frontend?)
data: ({ "action" => "change->checked#toggleCheckbox", "checked-target" => "checkbox" } if distributor_payment_method.payment_method.frontend?)
= distributor_payment_method.payment_method.name
- distributor.payment_methods.inactive_or_backend.each do |payment_method|
%label.disabled

View File

@@ -5,7 +5,8 @@
- mails_sent = @order_cycle.mails_sent?
- url = main_app.notify_producers_admin_order_cycle_path
- confirm_msg = "#{t('.notify_producers_tip')} #{t(:are_you_sure)}"
%a.button.icon-email.with-tip{ href: url, data: { method: 'post', confirm: confirm_msg }, 'data-powertip': t('.notify_producers_tip') }
%a.button.icon-email.with-tip{ href: url, data: { method: :post, "ujs-navigate": "false", confirm: confirm_msg }, 'data-powertip': t('.notify_producers_tip') }
= mails_sent ? t('.re_notify_producers') : t(:notify_producers)
- if mails_sent
.badge.icon-ok.success

View File

@@ -0,0 +1,6 @@
#flashes
- if defined? flashes
- flashes.each do |type, msg|
.animate-show{"data-controller": "flash"}
.flash{type: "#{type}", class: "#{type}"}
%span= msg

View File

@@ -0,0 +1,8 @@
= select_tag :per_page,
options_for_select([15, 50, 100].collect{|num| [t('js.admin.orders.index.per_page', results: num), num] }, params[:per_page]),
{ class: "no-search primary per-page-dropdown", data: { controller: "tom-select search", action: "change->search#changePerPage" } }
- if pagy
%span.per-page-feedback
= t("spree.admin.orders.index.results_found", number: pagy.count)
= t("spree.admin.orders.index.viewing", start: pagy.from, end: pagy.to )

View File

@@ -0,0 +1,22 @@
- link = pagy_link_proc(pagy)
.pagination{ "data-controller": "search" }
- if pagy.prev
%button{ data: { action: 'click->search#changePage', page: pagy.prev } }!= pagy_t('pagy.nav.prev')
- else
%button.disabled{disabled: "disabled"}!= pagy_t('pagy.nav.prev')
- pagy.series.each do |item| # series example: [1, :gap, 7, 8, "9", 10, 11, :gap, 36]
- if item.is_a?(Integer) # page link
%button{ data: { action: 'click->search#changePage', page: item } }= item
- elsif item.is_a?(String) # current page
%button.active= item
- elsif item == :gap # page gap
%span.pagination-ellipsis!= pagy_t('pagy.nav.gap')
- if pagy.next
%button{ data: { action: 'click->search#changePage', page: pagy.next } }!= pagy_t('pagy.nav.next')
- else
%button.disabled.pagination-next{disabled: "disabled"}!= pagy_t('pagy.nav.next')

View File

@@ -1,5 +1,6 @@
%div{"data-controller": "tooltip"}
%a{"data-tooltip-target": "element", "data-action": "mouseenter->tooltip#showTooltip mouseleave->tooltip#hideTooltip"}= t('admin.whats_this')
%a{"data-tooltip-target": "element"}
= t('admin.whats_this')
.tooltip-container
.tooltip{"data-tooltip-target": "tooltip"}
= sanitize tooltip_text

View File

@@ -16,5 +16,5 @@
%input#add_quantity.fullwidth{ type: 'number', min: 1, ng: { model: 'newItem.quantity' } }
%td
.actions
%a.icon-plus.button.fullwidth{ href: 'javascript:void(0)', method: :post, ng: { click: 'addSubscriptionLineItem()' } }
%a.icon-plus.button.fullwidth{ href: 'javascript:void(0)', ng: { click: 'addSubscriptionLineItem()' } }
= t('.add')

View File

@@ -7,7 +7,7 @@
.admin-current-terms-of-service
- if @current_file
%p= t(".current_terms_html", tos_link: link_to(t(".terms_of_service"), @current_file.attachment), datetime: @current_file.updated_at)
%p= link_to t(".delete"), main_app.admin_terms_of_service_files_path, method: "delete", data: { confirm: t(".confirm_delete") }
%p= link_to t(".delete"), main_app.admin_terms_of_service_files_path, data: { method: :delete, "ujs-navigate": "false", confirm: t(".confirm_delete") }
- else
%p
= t(".no_files")

View File

@@ -5,7 +5,8 @@
- content_for :page_actions do
%li
= link_to_with_icon 'icon-envelope-alt', t("spree.admin.mail_methods.send_testmail"), testmail_admin_mail_methods_path, method: :post, title: t("spree.admin.mail_methods.send_testmail"), class: 'send_mail button no-text'
= link_to_with_icon 'icon-envelope-alt', t("spree.admin.mail_methods.send_testmail"), testmail_admin_mail_methods_path,
data: { method: :post, "ujs-navigate": "false" }, title: t("spree.admin.mail_methods.send_testmail"), class: 'send_mail button no-text'
= render partial: 'spree/shared/error_messages', locals: { target: @mail_method }

View File

@@ -0,0 +1,25 @@
.ofn-drop-down-with-prepend
.ofn-drop-down-prepend.disabled{ "data-checked-target": "disable" }
%span{ "data-controller": "checked-feedback", "data-checked-feedback-translation-value": "spree.admin.orders.index.selected" }
= t("spree.admin.orders.index.selected", count: 0)
%button.plain.ofn-drop-down.disabled{ "data-checked-target": "disable" }
%span{ class: 'icon-reorder' }
="#{t('admin.actions')}".html_safe
%span.toggle-off.icon-caret-up
%span.toggle-on.icon-caret-down
%div.menu.dropdown-content
%div.menu_item
%span.name{ "data-controller": "modal-link", "data-action": "click->modal-link#open", "data-modal-link-target-value": "resend_confirmation" }
= t('spree.admin.orders.index.resend_confirmation')
- if Spree::Config[:enable_invoices?]
%div.menu_item
%span.name{ "data-controller": "modal-link", "data-action": "click->modal-link#open", "data-modal-link-target-value": "send_invoice" }
= t('spree.admin.orders.index.send_invoice')
%div.menu_item
%span.name{ "data-controller": "bulk-actions", "data-action": "click->bulk-actions#perform", "data-bulk-actions-reflex-value": "Admin::Orders#bulk_invoice" }
= t('spree.admin.orders.index.print_invoices')
%div.menu_item
%span.name{ "data-controller": "modal-link", "data-action": "click->modal-link#open", "data-modal-link-target-value": "cancel_orders" }
= t('spree.admin.orders.index.cancel_orders')

View File

@@ -1,59 +1,61 @@
%div.admin-orders-index-search
= form_tag spree.admin_orders_url, {name: "orders_form", "ng-submit" => "fetchResults()"} do
%div.admin-orders-index-search{ "data-controller": "search", "data-search-restore-value": @stored_query }
= form_with url: spree.admin_orders_url, id: "orders_form", method: :get, data: { remote: true, "search-target": "form" } do
= hidden_field_tag :page, 1, class: "page"
= hidden_field_tag :per_page, 15, class: "per-page"
= hidden_field_tag "[q][s]", params.dig(:q, :s) || "completed_at desc", class: "sort", "data-default": "completed_at desc"
.field-block.alpha.four.columns
.date-range-filter.field
= label_tag nil, t(:date_range)
.date-range-fields{ data: { controller: "flatpickr", "flatpickr-mode-value": "range", "flatpickr-default-date": "{{ [q.completed_at_gteq, q.completed_at_lteq] }}" } }
.date-range-fields{ data: { controller: "flatpickr", "flatpickr-mode-value": "range" } }
= text_field_tag nil, nil, class: "datepicker", data: { "flatpickr-target": "instance", action: "flatpickr_clear@window->flatpickr#clear" }
= text_field_tag "q[completed_at_gteq]", nil, "ng-model": "q.completed_at_gteq", data: { "flatpickr-target": "start" }, style: "display: none"
= text_field_tag "q[completed_at_lteq]", nil, "ng-model": "q.completed_at_lteq", data: { "flatpickr-target": "end" }, style: "display: none"
.field
= label_tag nil, t(:status)
%select2-watch-ng-model{'ng-model': 'q.state_eq'}
= select_tag("q[state_eq]",
options_for_select(Spree::Order.state_machines[:state].states.collect {|s| [t("spree.order_state.#{s.name}"), s.value]}),
{include_blank: true, class: 'select2', 'ng-model' => 'q.state_eq'})
= select_tag("q[state_eq]",
options_for_select(Spree::Order.state_machines[:state].states.collect {|s| [t("spree.order_state.#{s.name}"), s.value]}),
{ include_blank: true, class: "primary", "data-controller": "tom-select" })
.four.columns
.field
= label_tag "q_number_cont", t(:order_number)
= text_field_tag "q[number_cont]", nil, "ng-model" => "q.number_cont", "ng-keypress" => "$event.keyCode === 13 && fetchResults()"
= text_field_tag "q[number_cont]", nil
.field
= label_tag "q_email_cont", t(:email)
= email_field_tag "q[email_cont]", nil, "ng-model" => "q.email_cont", "ng-keypress" => "$event.keyCode === 13 && fetchResults()"
= email_field_tag "q[email_cont]", nil
.four.columns
.field
= label_tag "q_bill_address_firstname_start", t(:first_name_begins_with)
= text_field_tag "q[bill_address_firstname_start]", nil, size: 25, "ng-model" => "q.bill_address_firstname_start", "ng-keypress" => "$event.keyCode === 13 && fetchResults()"
= text_field_tag "q[bill_address_firstname_start]", nil, size: 25
.field
= label_tag "q_bill_address_lastname_start", t(:last_name_begins_with)
= text_field_tag "q[bill_address_lastname_start]", nil, size: 25, "ng-model" => "q.bill_address_lastname_start", "ng-keypress" => "$event.keyCode === 13 && fetchResults()"
= text_field_tag "q[bill_address_lastname_start]", nil, size: 25
.omega.four.columns
.field.checkbox
.field.checkbox.inline-checkbox
%label
= check_box_tag "q[completed_at_not_null]", 1, true, {'ng-model' => 'q.completed_at_not_null'}
= check_box_tag "q[completed_at_not_null]", 1, true
= t(:show_only_complete_orders)
.field
= label_tag nil, t(:shipping_method)
%select2-watch-ng-model{'ng-model': 'q.shipping_method_id'}
= select_tag("q[shipping_method_id]",
options_for_select(Spree::ShippingMethod.managed_by(spree_current_user).collect {|s| [t("spree.shipping_method_names.#{s.name}"), s.id]}),
{include_blank: true, class: 'select2', 'ng-model': 'q.shipping_method_id'})
= select_tag(:shipping_method_id,
options_for_select(Spree::ShippingMethod.managed_by(spree_current_user).collect {|s| [t("spree.shipping_method_names.#{s.name}"), s.id]}),
{ include_blank: true, class: "primary", "data-controller": "tom-select" })
.field-block.alpha.eight.columns
= label_tag nil, t(:distributors)
%select2-watch-ng-model{'ng-model': 'q.distributor_id_in'}
= select_tag("q[distributor_id_in]",
options_for_select(Enterprise.is_distributor.managed_by(spree_current_user).map {|e| [e.name, e.id]}, params[:distributor_ids]),
{class: "select2 fullwidth", multiple: true, 'ng-model' => 'q.distributor_id_in'})
= select_tag("q[distributor_id_in]",
options_for_select(Enterprise.is_distributor.managed_by(spree_current_user).map {|e| [e.name, e.id]}, params[:distributor_ids]),
{ class: "fullwidth", multiple: true, data: { controller: "tom-select", "tom-select-options-value": { plugins: ['remove_button'], maxItems: nil } }})
.field-block.omega.eight.columns
= label_tag nil, t(:order_cycles)
%select2-watch-ng-model{'ng-model': 'q.order_cycle_id_in'}
= select_tag("q[order_cycle_id_in]",
options_for_select(OrderCycle.managed_by(spree_current_user).where('order_cycles.orders_close_at is not null').order('order_cycles.orders_close_at DESC').map {|oc| [oc.name, oc.id]}, params[:order_cycle_ids]),
{class: "select2 fullwidth", multiple: true, 'ng-model' => 'q.order_cycle_id_in'})
= select_tag("q[order_cycle_id_in]",
options_for_select(OrderCycle.managed_by(spree_current_user).where('order_cycles.orders_close_at is not null').order('order_cycles.orders_close_at DESC').map {|oc| [oc.name, oc.id]}, params[:order_cycle_ids]),
{ class: "fullwidth", multiple: true, data: { controller: "tom-select", "tom-select-options-value": { plugins: ['remove_button'], maxItems: nil } }})
.clearfix
.actions.filter-actions
%a.button.icon-search{'ng-click' => 'fetchResults()'}
= t(:filter_results)
%a.button{'ng-click' => 'clearFilters()', "id": "clear_filters_button"}
= t(:clear_filters)
.actions.filter-actions{ style: "column-gap: 0" }
.eight.columns.alpha
%button.float-right.mr-0{type: "submit", class: "button"}
%i.icon-search
= t(:filter_results)
.eight.columns.omega
%button.float-left{"id": "clear_filters_button", type: "button", "data-controller": "search", "data-action": "click->search#reset" }
= t(:clear_filters)

View File

@@ -0,0 +1,30 @@
.row.index-controls
%div{ style: "display: flex; justify-content: space-between;" }
= render partial: "bulk_actions"
.per-page.right
= render partial: 'admin/shared/stimulus_page_controls', locals: { pagy: pagy }
%table#listing_orders.index.responsive{width: "100%" }
%colgroup
%col{style: "width: 3%"}
%thead
%tr
%th
%input#selectAll{ type: 'checkbox', data: { "checked-target": "all", action: "change->checked#toggleAll" } }
%th
= t(:products_distributor)
- columns = ['completed_at', 'number', 'state', 'payment_state', 'shipment_state', 'email', 'bill_address_lastname', 'total']
= render partial: "spree/admin/shared/stimulus_sortable_header", collection: columns, as: :column,
locals: { sorted: params.dig(:q, :s), default: "completed_at desc" }
%th.actions
%tbody
= render partial: "table_row", collection: orders, as: :order
- if pagy&.count&.positive?
= render partial: "admin/shared/stimulus_pagination", locals: { pagy: pagy }
- else
.no-objects-found= t('spree.admin.orders.index.no_orders_found')

View File

@@ -0,0 +1,53 @@
%tr{ id: dom_id(order), class: "state-#{order.state}" }
%td.align-center
%input{type: 'checkbox', value: order.id, name: 'bulk_ids[]', "data-checked-target": "checkbox", "data-action": "change->checked#toggleCheckbox" }
%td.align-center
= order.distributor.name
%td.align-center
= I18n.l(order.completed_at, format: '%B %d, %Y') if order.completed_at
%td
%a{ href: edit_admin_order_path(order) }
= order.number
- if order.special_instructions.present?
%div
%br
%div{ "data-controller": "tooltip", "data-tooltip-tip-value": order.special_instructions.to_s }
%span.icon-warning-sign{ "data-tooltip-target": "element" }
= t('spree.admin.orders.index.note')
%td.align-center
%span.state{ class: order.state.to_s }
= t('js.admin.orders.order_state.' + order.state.to_s)
%td.align-center
- if order.payment_state
%span.state{class: 'order.payment_state'}
%a{href: spree.admin_order_payments_path(order) }
= t('js.admin.orders.payment_states.' + order.payment_state.to_s)
- if order.display_outstanding_balance
%span
= "(#{order.display_outstanding_balance})"
%td.align-center
- if order.shipment_state
%span.state{class: order.shipment_state.to_s}
= t('js.admin.orders.shipment_states.' + order.shipment_state.to_s)
%td
%a{ href: "mailto:#{order.email}", target: "_blank" }
= order.email
%td
= order.bill_address.full_name
%td.align-center
%span
= order.display_total
%td.actions
%div.row-loading-icons
- if local_assigns[:success]
%i.success.icon-ok-sign{"data-controller": "ephemeral"}
%a.icon_link.with-tip.icon-edit.no-text{href: edit_admin_order_path(order), 'ofn-with-tip' => t('spree.admin.orders.index.edit')}
- if order.ready_to_ship?
%div{ "data-controller": "tooltip", "data-tooltip-tip-value": t('spree.admin.orders.index.ship') }
%button.icon-road.icon_link.with-tip.no-text{"data-reflex": "click->Admin::OrdersReflex#ship", "data-id": order.id.to_s,
"data-tooltip-target": "element" }
- if order.payment_required? && order.pending_payments.reject(&:requires_authorization?).any?
%div{ "data-controller": "tooltip", "data-tooltip-tip-value": t('spree.admin.orders.index.capture') }
%button.icon-capture.icon_link.no-text{"data-reflex": "click->Admin::OrdersReflex#capture", "data-id": order.id.to_s,
"data-tooltip-target": "element" }

View File

@@ -0,0 +1,7 @@
%p.message
= t('js.admin.orders.index.bulk_invoice_created')
%br
%a.button.primary{ target: '_blank', href: invoice_url }
= t('js.admin.orders.index.view_file')

View File

@@ -0,0 +1,15 @@
%div{ id: "bulk_invoices_modal", "data-controller": "modal", "data-modal-instant-value": true, "data-action": "keyup@document->modal#closeIfEscapeKey" }
.reveal-modal-bg.fade{ "data-modal-target": "background", "data-action": "click->modal#remove" }
.reveal-modal.fade.tiny.help-modal{ "data-modal-target": "modal" }
%div.fullwidth.align-center
%h4.modal-title
= t('js.admin.orders.index.compiling_invoices')
%br
%br
.modal-content
.modal-loading
%img.spinner{ src: image_path("/spinning-circles.svg") }
%br
%br
%p= t('js.admin.orders.index.please_wait')
%br

View File

@@ -8,8 +8,8 @@
%li= event_links
= render partial: 'spree/admin/shared/order_links'
- if can?(:admin, Spree::Order)
%li{"ng-controller" => "ordersCtrl"}
%a.button.icon-arrow-left{icon: 'icon-arrow-left', ng: { href: admin_orders_path }}
%li
%a.button.icon-arrow-left{icon: 'icon-arrow-left', href: admin_orders_path }
= t(:back_to_orders_list)
= render partial: "spree/admin/shared/order_page_title"

View File

@@ -1,137 +1,33 @@
- content_for :page_title do
= t('.listing_orders')
- content_for :minimal_js, true
- content_for :page_actions do
%li
= button_link_to t('.new_order'), spree.new_admin_order_url, icon: 'icon-plus', id: 'admin_new_order'
= render partial: 'spree/admin/shared/order_sub_menu'
- content_for :main_ng_app_name do
= "ofn.admin"
- content_for :main_ng_ctrl_name do
= "ordersCtrl"
- content_for :table_filter_title do
= t(:search)
- content_for :table_filter do
= render partial: 'filters'
.row.index-controls{'ng-show' => '!RequestMonitor.loading && orders.length > 0'}
%div{style: "display: flex; justify-content: space-between;"}
.ofn-drop-down-with-prepend
.ofn-drop-down-prepend{"ng-class": "selected_orders.length == 0 ? 'disabled' : ''"}
{{ "spree.admin.orders.index.selected" | t:{count: selected_orders.length} }}
.ofn-drop-down{"ng-class": "selected_orders.length == 0 ? 'disabled' : ''"}
%span{ :class => 'icon-reorder' }
="#{t('admin.actions')}".html_safe
%span{ 'ng-class' => "expanded && 'icon-caret-up' || !expanded && 'icon-caret-down'" }
%div.menu{ 'ng-show' => "expanded" }
%div.menu_item
%span.name{ "data-controller": "modal-link", "data-action": "click->modal-link#open", "data-modal-link-target-value": "resend_confirmation" }
= t('.resend_confirmation')
- if Spree::Config[:enable_invoices?]
%div.menu_item
%span.name{ "data-controller": "modal-link", "data-action": "click->modal-link#open", "data-modal-link-target-value": "send_invoice" }
= t('.send_invoice')
%div.menu_item
%span.name.invoices-modal{'ng-controller' => 'bulkInvoiceCtrl', 'ng-click' => 'createBulkInvoice()' }
= t('.print_invoices')
%div.menu_item
%span.name{ "data-controller": "modal-link", "data-action": "click->modal-link#open", "data-modal-link-target-value": "cancel_orders" }
= t('.cancel_orders')
= render partial: 'admin/shared/angular_per_page_controls', locals: { position: "right", model: "orders" }
%table#listing_orders.index.responsive{width: "100%", 'ng-init' => 'initialise()', 'ng-show' => "!RequestMonitor.loading && orders.length > 0" }
%colgroup
%col{style: "width: 3%"}
%thead
%tr
%th
%input#selectAll{type: 'checkbox', 'ng-change' => 'toggleAll()', 'ng-model' => 'select_all'}
%th
= t(:products_distributor)
%th
%a{'ng-click' => "sortOptions.toggle('completed_at')"}
= t(:completed_at, scope: 'activerecord.attributes.spree/order')
%span{'ng-show' => "sorting == 'completed_at asc'"}= "&#x25B2;".html_safe
%span{'ng-show' => "sorting == 'completed_at desc' || sorting === undefined"}= "&#x25BC;".html_safe
- ['number', 'state', 'payment_state', 'shipment_state', 'email', 'bill_address_lastname', 'total'].each do |column_name|
%th
= render partial: 'spree/admin/shared/sortable_header', locals: {column_name: column_name}
%th.actions
%tbody
%tr{ng: {repeat: 'order in orders track by order.id', class: {even: "'even'", odd: "'odd'"}}, 'ng-class' => "{'state-{{order.state}}': true, 'row-loading': rowStatus[order.id] == 'loading'}"}
%td.align-center
%input{type: 'checkbox', 'ng-model' => 'checkboxes[order.id]', 'ng-change' => 'toggleSelection(order.id)', value: '{{order.id}}', name: 'order_ids[]'}
%td.align-center
{{order.distributor_name}}
%td.align-center
{{order.completed_at}}
%td
%a{'ng-href' => '{{order.edit_path}}'}
{{order.number}}
%div{'ng-if' => 'order.special_instructions'}
%br
%span.icon-warning-sign{'ofn-with-tip' => "{{order.special_instructions}}"}
= t('.note')
%td.align-center
%span.state{'ng-class' => 'order.state'}
{{'js.admin.orders.order_state.' + order.state | t}}
%td.align-center
%span.state{'ng-class' => 'order.payment_state', 'ng-if' => 'order.payment_state'}
%a{'ng-href' => '{{order.payments_path}}' }
{{'js.admin.orders.payment_states.' + order.payment_state | t}}
%span{'ng-if' => 'order.display_outstanding_balance'}
({{order.display_outstanding_balance}})
%td.align-center
%span.state{'ng-class' => 'order.shipment_state', 'ng-if' => 'order.shipment_state'}
{{'js.admin.orders.shipment_states.' + order.shipment_state | t}}
%td
%a{ ng: { href: "mailto:{{order.email}}" } }
{{order.email}}
%td
{{order.full_name}}
%td.align-center
%span{'ng-bind-html' => 'order.display_total'}
%td.actions
%div.row-loading-icons
%div{ng: {show: 'rowStatus[order.id] == "loading"', cloak: true}, style: "width: 30px; height: 30px;"}
= render partial: "components/spinner"
%i.success.icon-ok-sign{ng: {show: 'rowStatus[order.id] == "success"'} }
%i.error.icon-remove-sign.with-tip{ng: {show: 'rowStatus[order.id] == "error"'}, 'ofn-with-tip' => t('.order_not_updated')}
%a.icon_link.with-tip.icon-edit.no-text{'ng-href' => '{{order.edit_path}}', 'data-action' => 'edit', 'ofn-with-tip' => t('.edit')}
%div{'ng-if' => 'order.ready_to_ship'}
%button.icon-road.icon_link.with-tip.no-text{'ng-click' => 'shipOrder(order)', rel: 'nofollow', 'ofn-with-tip' => t('.ship')}
%div{'ng-if' => 'order.ready_to_capture'}
%button.icon-capture.icon_link.no-text{'ng-click' => 'capturePayment(order)', rel: 'nofollow', 'ofn-with-tip' => t('.capture')}
.sixteen.columns.alpha#loading{ 'ng-show' => 'RequestMonitor.loading' }
= render partial: "components/admin_spinner"
%h1
= t('.loading')
%div{'ng-show' => "!RequestMonitor.loading && orders.length > 0" }
= render partial: 'admin/shared/angular_pagination'
.no-objects-found{'ng-show' => "!RequestMonitor.loading && orders.length == 0"}
= t('.no_orders_found')
#orders-index{"data-controller": "search checked"}
= render partial: "table", locals: { pagy: @pagy, orders: @orders }
= render 'spree/admin/shared/custom-confirm'
= render ConfirmModalComponent.new(id: "resend_confirmation", confirm_actions: "click->resend-confirmation-email#confirm", controllers: "resend-confirmation-email") do
= render ConfirmModalComponent.new(id: "resend_confirmation", confirm_actions: "click->bulk-actions#perform", controller: "bulk-actions", reflex: "Admin::Orders#resend_confirmation_emails") do
.margin-bottom-30
= t('.resend_confirmation_confirm_html')
= render ConfirmModalComponent.new(id: "send_invoice", confirm_actions: "click->send-invoice#confirm", controllers: "send-invoice") do
= render ConfirmModalComponent.new(id: "send_invoice", confirm_actions: "click->bulk-actions#perform", controller: "bulk-actions", reflex: "Admin::Orders#send_invoices") do
.margin-bottom-30
= t('.send_invoice_confirm_html')
= render ConfirmModalComponent.new(id: "cancel_orders", confirm_actions: "click->cancel-orders#confirm", controllers: "cancel-orders", message: "spree/admin/orders/messages/cancel_orders") do
= render ConfirmModalComponent.new(id: "cancel_orders", confirm_actions: "click->bulk-actions#perform", controller: "bulk-actions", reflex: "Admin::Orders#cancel_orders", message: "spree/admin/orders/messages/cancel_orders") do
.margin-bottom-30
= t("js.admin.orders.cancel_the_order_html")

View File

@@ -1,10 +1,10 @@
.modal-message
.form
%form{ "data-bulk-actions-target": "extraParams" }
%input{ type: "checkbox", name: "send_cancellation_email", value: "1", id: "send_cancellation_email", checked: "true" }
%label{ for: "send_cancellation_email" }
= t("js.admin.orders.cancel_the_order_send_cancelation_email")
%br
%input{ type: "checkbox", name: "restock_items", id: "restock_items", checked: "true" }
%input{ type: "checkbox", name: "restock_items", value: "1", id: "restock_items", checked: "true" }
%label{ for: "restock_items" }
= t("js.admin.orders.restock_items")
.margin-bottom-30

View File

@@ -16,4 +16,5 @@
%span{class: "state #{payment.state}"}= t(payment.state, scope: "spree.payment_states", default: payment.state.capitalize)
%td.actions
- payment.actions.each do |action|
= link_to_with_icon "icon-#{action}", Spree.t(action), fire_admin_order_payment_path(@order, payment, e: action), method: :put, no_text: true, data: {action: action, disable_with: ""}
= link_to_with_icon "icon-#{action}", Spree.t(action), fire_admin_order_payment_path(@order, payment, e: action),
no_text: true, data: { method: :put, "ujs-navigate": "false", action: action, disable_with: "" }

View File

@@ -1,10 +1,12 @@
- content_for :page_actions do
%li
- if @return_authorization.can_receive?
= button_link_to t('.receive'), fire_admin_order_return_authorization_url(@order, @return_authorization, e: 'receive'), method: :put, data: { confirm: t('.are_you_sure') }, icon: 'icon-download-alt'
= button_link_to t('.receive'), fire_admin_order_return_authorization_url(@order, @return_authorization, e: 'receive'),
data: { method: :put, "ujs-navigate": "false", confirm: t('.are_you_sure') }, icon: 'icon-download-alt'
%li
- if @return_authorization.can_cancel?
= button_link_to t('actions.cancel'), fire_admin_order_return_authorization_url(@order, @return_authorization, e: 'cancel'), method: :put, data: { confirm: t('.are_you_sure') }, icon: 'icon-remove'
= button_link_to t('actions.cancel'), fire_admin_order_return_authorization_url(@order, @return_authorization, e: 'cancel'),
data: { method: :put, "ujs-navigate": "false", confirm: t('.are_you_sure') }, icon: 'icon-remove'
= render partial: 'spree/admin/shared/order_page_title'
= render partial: 'spree/admin/shared/order_tabs', locals: { current: 'Return Authorizations' }

View File

@@ -3,8 +3,6 @@
= csrf_meta_tags
= action_cable_meta_tag
= action_cable_meta_tag
%title
- if content_for? :html_title
= yield :html_title
@@ -20,7 +18,11 @@
= stylesheet_pack_tag 'admin-styles', media: "screen, print"
= render "layouts/bugsnag_js"
= javascript_include_tag 'admin/all'
- if content_for? :minimal_js
= javascript_include_tag 'admin_minimal'
- else
= javascript_include_tag 'admin/all'
= render "spree/admin/shared/translations"
= render "spree/admin/shared/routes"

View File

@@ -0,0 +1,8 @@
%th
%a{ "data-action": "click->search#changeSorting", "data-column": "#{column}", "data-current": sorted.to_s }
= t("spree.admin.shared.sortable_header.#{column.to_s}")
- if sorted == "#{column} asc" || sorted.blank? && local_assigns[:default] == "#{column} asc"
= "&#x25B2;".html_safe
- if sorted == "#{column} desc" || sorted.blank? && local_assigns[:default] == "#{column} desc"
= "&#x25BC;".html_safe

View File

@@ -1,7 +1,7 @@
= 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, 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('q[s]' => 'completed_at desc'), icon: 'icon-shopping-cart'
= 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, :mail_methods, :tax_categories, :tax_rates, :tax_settings, :zones, :countries, :states, :payment_methods, :taxonomies, :shipping_methods, :shipping_categories, :enterprise_fees, :contents, :invoice_settings, :matomo_settings, :stripe_connect_settings, label: 'configuration', icon: 'icon-wrench', url: edit_admin_general_settings_path
= tab :enterprises, :enterprise_relationships, :vouchers, :oidc_settings, url: main_app.admin_enterprises_path

View File

@@ -1,4 +1,7 @@
<script>
if (Spree === undefined) {
var Spree = {}
}
Spree.translations = <%==
{:flatpickr_date_format => Spree.t(:flatpickr_date_format,
:scope => 'date_picker',

View File

@@ -4,14 +4,7 @@
#wrapper
.flash-container
- if flash[:error]
.flash.error= flash[:error]
- if notice
.flash.notice= notice
- if flash[:success]
.flash.success= flash[:success]
= render partial: "shared/flashes"
= render partial: "admin/shared/flashes", locals: { flashes: flash }
= render partial: "spree/layouts/admin/progress_spinner"

View File

@@ -3,7 +3,7 @@
%head
= render :partial => 'spree/admin/shared/head'
%body.admin
%body.admin{ "data-turbo": "false" }
- if content_for?(:main_ng_app_name)
- if content_for?(:main_ng_ctrl_name)
%div{ "ng-app" => yield(:main_ng_app_name).strip.html_safe, "ng-controller" => yield(:main_ng_ctrl_name).strip.html_safe }

View File

@@ -1,13 +1,9 @@
%html{ lang: "en", "ng-csp": "no-unsafe-eval" }
%head= render :partial => 'spree/admin/shared/head'
%body.admin{"data-ajax-root-path" => main_app.root_path}
%body.admin{"data-turbo": "false", "data-ajax-root-path": main_app.root_path}
#wrapper
- if flash[:error]
.flash.error= flash[:error]
- if notice
.flash.notice= notice
- if flash[:success]
.flash.success= flash[:success]
= render partial: "admin/shared/flashes", locals: { flashes: flash }
= render partial: "spree/layouts/admin/progress_spinner"
%header#header

View File

@@ -0,0 +1,8 @@
import consumer from './consumer'
import CableReady from 'cable_ready'
consumer.subscriptions.create("SessionChannel", {
received(data) {
if (data.cableReady) CableReady.perform(data.operations)
}
});

View File

@@ -1,20 +1,35 @@
import ApplicationController from "./application_controller";
export default class extends ApplicationController {
static targets = ["extraParams"]
static values = { reflex: String }
connect() {
super.connect();
}
// abstract
confirm(action) {
this.stimulate(action, this.getOrdersIds());
perform() {
let params = { bulk_ids: this.getSelectedIds() };
if (this.hasExtraParamsTarget) {
Object.assign(params, this.extraFormData())
}
this.stimulate(this.reflexValue, params);
}
// private
getOrdersIds() {
getSelectedIds() {
const checkboxes = document.querySelectorAll(
"#listing_orders input[name='order_ids[]']:checked"
"table input[name='bulk_ids[]']:checked"
);
return Array.from(checkboxes).map((checkbox) => checkbox.value);
}
extraFormData() {
if (this.extraParamsTarget.constructor.name !== "HTMLFormElement") { return {} }
return Object.fromEntries(new FormData(this.extraParamsTarget).entries())
}
}

View File

@@ -1,30 +0,0 @@
import ApplicationController from "./application_controller";
export default class extends ApplicationController {
connect() {
super.connect();
}
confirm() {
const send_cancellation_email = document.querySelector(
"#send_cancellation_email"
).checked;
const restock_items = document.querySelector("#restock_items").checked;
const order_ids = [];
document
.querySelectorAll("#listing_orders input[name='order_ids[]']:checked")
.forEach((checkbox) => {
order_ids.push(checkbox.value);
});
const params = {
order_ids: order_ids,
send_cancellation_email: send_cancellation_email,
restock_items: restock_items,
};
this.stimulate("CancelOrdersReflex#confirm", params).then(() =>
window.location.reload()
);
}
}

View File

@@ -0,0 +1,55 @@
import { Controller } from "stimulus";
export default class extends Controller {
static targets = ["all", "checkbox", "disable"];
static values = { count: Number };
connect() {
this.toggleCheckbox();
}
toggleAll() {
this.checkboxTargets.forEach((checkbox) => {
checkbox.checked = this.allTarget.checked;
});
this.countValue = this.allTarget.checked ? this.checkboxTargets.length : 0;
this.#toggleDisabled();
}
toggleCheckbox() {
this.countValue = this.#checkedCount();
this.allTarget.checked = this.#allChecked();
this.#toggleDisabled();
}
countValueChanged() {
window.dispatchEvent(
new CustomEvent("checked:updated", { detail: { count: this.countValue } })
);
}
// private
#checkedCount() {
return this.checkboxTargets.filter((checkbox) => checkbox.checked).length;
}
#allChecked() {
return this.countValue === this.checkboxTargets.length;
}
#toggleDisabled() {
if (!this.hasDisableTarget) {
return;
}
if (this.#checkedCount() === 0) {
this.disableTargets.forEach((element) => element.classList.add("disabled"));
} else {
this.disableTargets.forEach((element) => element.classList.remove("disabled"));
}
}
}

View File

@@ -0,0 +1,19 @@
import { Controller } from "stimulus";
export default class extends Controller {
static values = { translation: String };
connect() {
window.addEventListener("checked:updated", this.updateFeedback);
}
disconnect() {
window.removeEventListener("checked:updated", this.updateFeedback);
}
updateFeedback = (event) => {
this.element.textContent = I18n.t(this.translationValue, {
count: event?.detail?.count ? event.detail.count : 0,
});
};
}

View File

@@ -0,0 +1,16 @@
import { Controller } from "stimulus";
export default class extends Controller {
connect() {
setTimeout(this.fadeout, 1500);
}
fadeout = () => {
this.element.classList.add("animate-hide-500");
setTimeout(this.remove, 500);
};
remove = () => {
this.element.remove();
};
}

View File

@@ -39,8 +39,8 @@ export default class extends Flatpickr {
};
initialize() {
const datetimepicker = this.enableTimeValue == true;
const mode = this.modeValue == "range" ? "range" : "single";
const datetimepicker = this.enableTimeValue === true;
const mode = this.modeValue === "range" ? "range" : "single";
// sets your language (you can also set some global setting for all time pickers)
this.config = {
altInput: true,
@@ -54,40 +54,47 @@ export default class extends Flatpickr {
plugins: this.plugins(mode, datetimepicker),
mode,
};
window.addEventListener("flatpickr:change", this.onChangeEvent.bind(this));
window.addEventListener("flatpickr:clear", this.clear.bind(this));
}
clear(e) {
this.fp.setDate(null);
connect() {
super.connect();
window.addEventListener("flatpickr:change", this.onChangeEvent);
window.addEventListener("flatpickr:clear", this.clear);
}
disconnect() {
super.disconnect();
window.removeEventListener("flatpickr:change", this.onChangeEvent);
window.removeEventListener("flatpickr:clear", this.clear);
}
clear = (e) => {
this.fp.setDate(null);
};
open() {
this.fp.element.dispatchEvent(new Event("focus"));
if (!this.fp.selectedDates.length) {
this.setDefaultDateValue();
}
}
onChangeEvent(e) {
if (
this.modeValue == "range" &&
this.hasStartTarget &&
this.hasEndTarget &&
e.detail.startDate &&
e.detail.endDate
) {
onChangeEvent = (e) => {
if (this.modeValue === "range" && this.hasStartTarget && this.hasEndTarget) {
// date range mode
this.startTarget.value = e.detail.startDate;
this.endTarget.value = e.detail.endDate;
this.fp.setDate([e.detail.startDate, e.detail.endDate]);
if (e.detail) {
this.startTarget.value = e.detail.startDate;
this.endTarget.value = e.detail.endDate;
}
this.fp.setDate([this.startTarget.value, this.endTarget.value]);
} else if (e.detail.date) {
// single date mode
this.fp.setDate(e.detail.date);
}
}
};
change(selectedDates, dateStr, instance) {
if (this.hasStartTarget && this.hasEndTarget && this.modeValue == "range") {
if (this.hasStartTarget && this.hasEndTarget && this.modeValue === "range") {
this.startTarget.value = selectedDates[0]
? this.fp.formatDate(selectedDates[0], this.config.dateFormat)
: "";
@@ -112,11 +119,9 @@ export default class extends Flatpickr {
plugins = (mode, datetimepicker) => {
const buttons = [{ label: Spree.translations.close }];
if (mode == "single") {
if (mode === "single") {
buttons.unshift({
label: datetimepicker
? Spree.translations.now
: Spree.translations.today,
label: datetimepicker ? Spree.translations.now : Spree.translations.today,
});
}
return [
@@ -132,8 +137,8 @@ export default class extends Flatpickr {
// Memorize index used for the 'Close' and 'Today|Now' buttons
// it has index of 1 in case of single mode (ie. can set Today or Now date)
// it has index of 0 in case of range mode (no Today or Now button)
const closeButtonIndex = this.modeValue == "range" ? 0 : 1;
const todayButtonIndex = this.modeValue == "range" ? null : 0;
const closeButtonIndex = this.modeValue === "range" ? 0 : 1;
const todayButtonIndex = this.modeValue === "range" ? null : 0;
switch (index) {
case todayButtonIndex:
fp.setDate(new Date(), true);

View File

@@ -11,7 +11,7 @@ export const useOpenAndCloseAsAModal = (controller) => {
});
}.bind(controller),
close: function () {
close: function (_event, remove = false) {
this.modalTarget.classList.remove("in");
this.backgroundTarget.classList.remove("in");
document.querySelector("body").classList.remove("modal-open");
@@ -19,6 +19,7 @@ export const useOpenAndCloseAsAModal = (controller) => {
setTimeout(() => {
this.backgroundTarget.style.display = "none";
this.modalTarget.style.display = "none";
if (remove) { this.element.remove() }
}, 200);
}.bind(controller),

View File

@@ -3,13 +3,20 @@ import { useOpenAndCloseAsAModal } from "./mixins/useOpenAndCloseAsAModal";
export default class extends Controller {
static targets = ["background", "modal"];
static values = { instant: { type: Boolean, default: false } }
connect() {
useOpenAndCloseAsAModal(this);
window.addEventListener("modal:close", this.close.bind(this));
if (this.instantValue) { this.open() }
}
disconnect() {
window.removeEventListener("modal:close", this.close);
}
remove(event) {
this.close(event, true)
}
}

View File

@@ -1,11 +0,0 @@
import BulkActionsController from "./bulk_actions_controller";
export default class extends BulkActionsController {
connect() {
super.connect();
}
confirm() {
super.confirm("BulkActionsInOrdersList#resend_confirmation_email");
}
}

View File

@@ -0,0 +1,89 @@
import { Controller } from "stimulus";
export default class extends Controller {
static targets = ["form"];
static values = { restore: String }; // Query string, eg: "?color=red&size=small"
connect() {
this.#setup();
}
changePage(event) {
this.page.value = event.target.dataset.page;
this.submitSearch();
this.page.value = 1;
}
changePerPage(event) {
this.per_page.value = parseInt(event.target.value);
this.submitSearch();
}
changeSorting(event) {
let current = event.target.dataset.current;
let column = event.target.dataset.column;
this.sort.value = current === `${column} asc` ? `${column} desc` : `${column} asc`;
this.submitSearch();
}
submitSearch() {
this.form.requestSubmit();
}
reset() {
this.clearForm();
this.submitSearch();
}
clearForm() {
this.form.reset();
this.#clearCustomElements();
if (this.page) this.page.value = 1;
if (this.sort) this.sort.value = this.sort.dataset.default;
}
// private
#setup() {
if (this.hasFormTarget) {
this.form = this.formTarget;
this.form.controller = this;
if (this.restoreValue) this.#restoreFormState(this.form, this.restoreValue);
} else {
this.form = document.querySelector("form[data-search-target=form]");
}
this.page = this.form.querySelector(".page");
this.per_page = this.form.querySelector(".per-page");
this.sort = this.form.querySelector(".sort");
}
#clearCustomElements() {
window.dispatchEvent(new CustomEvent("flatpickr:clear"));
this.form.querySelectorAll(".tomselected").forEach((select) => {
select.tomselect?.clear();
});
}
#restoreFormState(form, queryString) {
const params = new URLSearchParams(queryString);
// Apply non-checkbox values
for (const [key, value] of params.entries()) {
const input = form.elements[key];
if (input && input.type !== "checkbox") input.value = value;
}
// Deal with checkbox values
form.querySelectorAll("[type=checkbox]").forEach((checkbox) => {
checkbox.checked = !!params.get(checkbox.name);
});
setTimeout(() => {
window.dispatchEvent(new CustomEvent("flatpickr:change"));
});
}
}

View File

@@ -1,19 +0,0 @@
import { Controller } from "stimulus";
export default class extends Controller {
static targets = ["all", "checkbox"];
connect() {
this.toggleCheckbox()
}
toggleAll() {
this.checkboxTargets.forEach(checkbox => {
checkbox.checked = this.allTarget.checked;
});
}
toggleCheckbox() {
this.allTarget.checked = this.checkboxTargets.every(checkbox => checkbox.checked);
}
}

View File

@@ -1,11 +0,0 @@
import BulkActionsController from "./bulk_actions_controller";
export default class extends BulkActionsController {
connect() {
super.connect();
}
confirm() {
super.confirm("BulkActionsInOrdersList#send_invoice");
}
}

View File

@@ -8,6 +8,7 @@ export default class extends Controller {
maxOptions: null,
plugins: ["dropdown_input"],
allowEmptyOption: true,
closeAfterSelect: true,
onItemAdd: function () {
this.setTextboxValue("");
},
@@ -34,7 +35,6 @@ export default class extends Controller {
#placeholder() {
const optionsArray = [...this.element.options];
return optionsArray.find((option) => [null, ""].includes(option.value))
?.text;
return optionsArray.find((option) => [null, ""].includes(option.value))?.text;
}
}

View File

@@ -4,12 +4,22 @@ import { computePosition, offset, arrow } from "@floating-ui/dom";
export default class extends Controller {
static targets = ["element", "tooltip", "arrow"];
static values = {
placement: {
type: String,
default: "top",
},
tip: String,
placement: { type: String, default: "top" },
};
connect() {
if (this.hasTipValue) { this.insertToolTipMarkup() }
this.elementTarget.addEventListener("mouseenter", this.showTooltip);
this.elementTarget.addEventListener("mouseleave", this.hideTooltip);
}
disconnect() {
this.elementTarget.removeEventListener("mouseenter", this.showTooltip);
this.elementTarget.removeEventListener("mouseleave", this.hideTooltip);
}
update() {
computePosition(this.elementTarget, this.tooltipTarget, {
placement: this.placementValue,
@@ -38,12 +48,31 @@ export default class extends Controller {
});
}
showTooltip() {
showTooltip = () => {
this.tooltipTarget.style.display = "block";
this.update();
}
};
hideTooltip() {
hideTooltip = () => {
this.tooltipTarget.style.display = "";
};
insertToolTipMarkup() {
let container = document.createElement("div");
let tooltip = document.createElement("div");
let arrow = document.createElement("div");
let text = document.createTextNode(this.tipValue);
container.classList.add("tooltip-container");
tooltip.classList.add("tooltip");
tooltip.setAttribute("data-tooltip-target", "tooltip");
arrow.classList.add("arrow");
arrow.setAttribute("data-tooltip-target", "arrow");
container.appendChild(tooltip);
tooltip.appendChild(text);
tooltip.appendChild(arrow);
this.elementTarget.appendChild(container);
}
}

View File

@@ -10,6 +10,16 @@
}
}
@keyframes fade-out-hide {
0% {opacity: 1; visibility: visible;}
99% {opacity: 0; visibility: visible;}
100% {opacity: 0; visibility: hidden;}
}
.animate-hide-500 {
animation: fade-out-hide 0.5s;
}
// @-webkit-keyframes slideOutDown
// 0%
// -webkit-transform: translateY(0)

View File

@@ -1,6 +1,6 @@
input[type="submit"],
input[type="button"],
button,
button:not(.plain),
.button {
position: relative;
cursor: pointer;

View File

@@ -10,6 +10,10 @@
margin-right: 1em;
margin-left: 0;
}
.ts-control > * {
padding-right: 2.75em;
}
}
.per-page-feedback {

View File

@@ -3,14 +3,9 @@
}
.row-loading-icons {
margin-left: 3em;
margin-left: 2.5em;
position: absolute;
.spinner {
border: 0;
width: 2.3em;
}
i {
font-size: 2.3em;
opacity: 0.75;

View File

@@ -1,5 +1,42 @@
.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,
@@ -45,6 +82,10 @@
}
}
.ts-wrapper .select-multiple {
cursor: pointer;
}
.ts-wrapper.dropdown-active.primary .ts-control {
background-color: $spree-green;
border-color: $spree-green;

View File

@@ -28,5 +28,4 @@
.tooltip-container {
position: relative;
width: 240px;
}

View File

@@ -6,130 +6,156 @@
margin-left: 3px;
}
.ofn-drop-down:hover, .ofn-drop-down.expanded {
border: 1px solid #adadad;
color: #575757;
.ofn-drop-down {
.dropdown-content {
display: none;
}
.toggle-off {
display: none;
}
&:active:not(.disabled),
&:focus:not(.disabled) {
.dropdown-content {
display: inline-block;
}
.toggle-off {
display: inline-block;
}
.toggle-on {
display: none;
}
}
}
.ofn-drop-down:hover,
.ofn-drop-down.expanded {
border: 1px solid #adadad;
color: #575757;
}
@mixin ofn-drop-down-style {
padding: 7px 15px;
border-radius: 3px;
border: 1px solid #d4d4d4;
background-color: #f5f5f5;
display: block;
color: #828282;
cursor: pointer;
-moz-user-select: none;
-khtml-user-select: none;
-webkit-user-select: none;
-ms-user-select: none;
user-select: none;
text-align: center;
padding: 7px 15px;
border-radius: 3px;
border: 1px solid #d4d4d4;
background-color: #f5f5f5;
display: block;
color: #828282;
cursor: pointer;
-moz-user-select: none;
-khtml-user-select: none;
-webkit-user-select: none;
-ms-user-select: none;
user-select: none;
text-align: center;
margin-right: 10px;
&.disabled {
opacity: 0.5;
&:hover {
cursor: default;
border-color: #d4d4d4;
color: #828282;
}
}
&.disabled {
opacity: 0.5;
&:hover {
cursor: default;
border-color: #d4d4d4;
color: #828282;
}
}
}
.ofn-drop-down-with-prepend {
display: flex;
display: flex;
&.right {
float: right;
&.right {
float: right;
}
}
.ofn-drop-down {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
.ofn-drop-down {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
.ofn-drop-down-prepend {
@include ofn-drop-down-style;
.ofn-drop-down-prepend {
@include ofn-drop-down-style;
border-right: none;
margin-left: 0;
margin-right: 0;
border-top-right-radius: 0;
border-bottom-right-radius: 0;
cursor: default;
}
border-right: none;
margin-left: 0;
margin-right: 0;
border-top-right-radius: 0;
border-bottom-right-radius: 0;
cursor: default;
}
}
.ofn-drop-down {
@include ofn-drop-down-style;
@include ofn-drop-down-style;
position: relative;
float: left;
position: relative;
float: left;
&.right {
float: right;
&.right {
float: right;
margin-right: 0px;
margin-left: 10px;
}
}
&:hover, &.expanded {
&:hover,
&.expanded {
border: 1px solid #adadad;
color: #575757;
}
> span {
width: auto;
text-transform: uppercase;
font-size: 85%;
font-weight: 600;
}
> span {
width: auto;
text-transform: uppercase;
font-size: 85%;
font-weight: 600;
}
.menu {
margin-top: 1px;
position: absolute;
float: none;
top:100%;
left: 0px;
padding: 5px 0px;
border: 1px solid #adadad;
background-color: #ffffff;
box-shadow: 1px 3px 10px #888888;
z-index: 100;
.menu {
margin-top: 1px;
position: absolute;
float: none;
top: 100%;
left: 0px;
padding: 5px 0px;
border: 1px solid #adadad;
background-color: #ffffff;
box-shadow: 1px 3px 10px #888888;
z-index: 100;
white-space: nowrap;
.filter {
padding-left: 5px;
padding-right: 5px;
position: relative;
> input[type="text"] {
border: 1px solid rgba(18, 18, 18, 0.1);
width: 100%;
padding-left: 30px;
padding-top: 10px;
padding-bottom: 10px;
font-size: 13px;
color:#454545;
}
.filter {
padding-left: 5px;
padding-right: 5px;
position: relative;
&:after {
content: "\f002";
font-family: FontAwesome;
position: absolute;
left: 15px;
top: 13px;
color:#454545;
}
}
> input[type="text"] {
border: 1px solid rgba(18, 18, 18, 0.1);
width: 100%;
padding-left: 30px;
padding-top: 10px;
padding-bottom: 10px;
font-size: 13px;
color: #454545;
}
.menu_item {
margin: 0px;
padding: 2px 10px;
color: #454545;
text-align: left;
&:after {
content: "\f002";
font-family: FontAwesome;
position: absolute;
left: 15px;
top: 13px;
color: #454545;
}
}
.menu_item {
margin: 0px;
padding: 2px 10px;
color: #454545;
text-align: left;
display: block;
.check {
@@ -146,78 +172,79 @@
padding: 0px 15px 0px 0px;
}
&.selected{
&.selected {
.check:before {
content: "\2713";
}
}
&.hidden {
display: none;
}
}
&.hidden {
display: none;
}
}
.menu_item:hover {
background-color: #ededed;
}
}
.menu_item:hover {
background-color: #ededed;
}
}
}
.ofn-drop-down-v2 {
border: 1px solid $pale-blue;
background-color: white;
padding: 0px;
&:hover {
border-color: $spree-blue;
}
border: 1px solid $pale-blue;
background-color: white;
padding: 0px;
.ofn-drop-down-label {
color: $color-3;
padding: 10px;
width: 235px;
display: flex;
justify-content: space-between;
&:hover {
border-color: $spree-blue;
}
&:hover {
color: $color-3;
}
.ofn-drop-down-label {
color: $color-3;
padding: 10px;
width: 235px;
display: flex;
justify-content: space-between;
.label {
padding-right: 10px;
}
&:hover {
color: $color-3;
}
.icon-caret-down, .icon-caret-up {
padding-right: 0px;
}
}
.label {
padding-right: 10px;
}
.menu {
width: 100%;
}
.icon-caret-down,
.icon-caret-up {
padding-right: 0px;
}
}
.menu_items {
max-height: 200px;
overflow-y: scroll;
.menu {
width: 100%;
}
.menu_item {
margin-bottom: 5px;
color: #454545;
font-weight: 400;
cursor: pointer;
padding-top: 4px;
padding-bottom: 5px;
text-transform: uppercase;
font-size: 85%;
}
}
.menu_items {
max-height: 200px;
overflow-y: scroll;
.menu_item {
margin-bottom: 5px;
color: #454545;
font-weight: 400;
cursor: pointer;
padding-top: 4px;
padding-bottom: 5px;
text-transform: uppercase;
font-size: 85%;
}
}
}
.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;
}
// 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;
}
}

View File

@@ -1,12 +1,12 @@
$text-inputs:
"input[type=text], input[type=password], input[type=email], input[type=url], input[type=tel]";
$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 {
textarea,
fieldset {
@include border-radius($border-radius);
padding: 7px 10px;
border: 1px solid $color-txt-brd;
@@ -48,7 +48,9 @@ label {
}
}
.label-block label { display: block }
.label-block label {
display: block;
}
span.info {
font-style: italic;
@@ -63,7 +65,7 @@ span.info {
padding: 10px 0;
&.checkbox {
min-height: 73px;
min-height: 70px;
input[type="checkbox"] {
display: inline-block;
@@ -85,7 +87,6 @@ span.info {
display: inline-block;
padding-right: 10px;
label {
font-weight: normal;
text-transform: none;
@@ -171,14 +172,18 @@ fieldset {
display: inline-block;
}
button, .button, input[type="submit"], input[type="button"], span.or {
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;
-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;
@@ -197,10 +202,10 @@ fieldset {
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;
-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;
}
}
@@ -210,7 +215,8 @@ fieldset {
display: table;
width: 100%;
label, input {
label,
input {
display: table-cell !important;
}
input {
@@ -219,7 +225,7 @@ fieldset {
&.checkbox {
input {
width: auto !important
width: auto !important;
}
}
}
@@ -247,11 +253,12 @@ select {
align-items: center;
margin-top: 3px;
input, label {
input,
label {
cursor: pointer;
}
label {
margin: 0;
padding-left: .4rem;
padding-left: 0.4rem;
}
}

View File

@@ -2,8 +2,8 @@
//---------------------------------------------------
* {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
.admin {
@@ -68,6 +68,23 @@
.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;
@@ -81,7 +98,9 @@
padding: 5px 0;
}
#logo { height: 40px }
#logo {
height: 40px;
}
.page-title {
i {
@@ -108,7 +127,8 @@
}
@media print {
header, nav {
display:none;
header,
nav {
display: none;
}
}
}

28
app/webpacker/js/mrujs.js Normal file
View File

@@ -0,0 +1,28 @@
import CableReady from "cable_ready";
import mrujs from "mrujs";
import { CableCar } from "mrujs/plugins";
mrujs.start({
plugins: [new CableCar(CableReady, { mimeType: "text/vnd.cable-ready.json" })],
});
// Handle legacy jquery ujs buttons
document.addEventListener("ajax:beforeNavigation", (event) => {
if (event.detail.element.dataset.ujsNavigate !== "false") return;
event.preventDefault();
if (event.detail.fetchResponse.response.redirected) {
document.location.href = event.detail.fetchResponse.response.url;
}
});
document.addEventListener("ajax:beforeSend", (event) => {
window.Turbo.navigator.adapter.progressBar.setValue(0);
window.Turbo.navigator.adapter.progressBar.show();
});
document.addEventListener("ajax:complete", (event) => {
window.Turbo.navigator.adapter.progressBar.setValue(100);
window.Turbo.navigator.adapter.progressBar.hide();
});

View File

@@ -1,18 +1,7 @@
import { Application } from "stimulus";
import { definitionsFromContext } from "stimulus/webpack-helpers";
const application = Application.start();
const context = require.context("controllers", true, /.js$/);
application.load(definitionsFromContext(context));
import StimulusReflex from "stimulus_reflex";
import consumer from "../channels/consumer";
import controller from "../controllers/application_controller";
application.consumer = consumer;
StimulusReflex.initialize(application, { controller, isolate: true });
StimulusReflex.debug = process.env.RAILS_ENV === "development";
CableReady.initialize({ consumer });
import "controllers";
import "channels";
import "@hotwired/turbo";
import "../js/mrujs";
import debounced from "debounced";
debounced.initialize({ input: { wait: 300 } });

View File

@@ -1,19 +1,7 @@
/* eslint no-console:0 */
import CableReady from "cable_ready";
import mrujs from "mrujs";
import { CableCar } from "mrujs/plugins";
import * as Turbo from "@hotwired/turbo";
window.Turbo = Turbo;
window.CableReady = CableReady;
mrujs.start({
plugins: [
new CableCar(CableReady, { mimeType: "text/vnd.cable-ready.json" }),
],
});
import "controllers";
import "@hotwired/turbo";
import "../js/mrujs";
require.context("../fonts", true);
const images = require.context("../images", true);
const imagePath = (name) => images(name, true);
import "controllers";

View File

@@ -209,7 +209,7 @@ module Openfoodnetwork
# Instead, they must be explicitly included below
# http://stackoverflow.com/questions/8012434/what-is-the-purpose-of-config-assets-precompile
config.assets.initialize_on_precompile = true
config.assets.precompile += ['admin/*.js', 'admin/**/*.js']
config.assets.precompile += ['admin/*.js', 'admin/**/*.js', 'admin_minimal.js']
config.assets.precompile += ['web/all.js']
config.assets.precompile += ['darkswarm/all.js']
config.assets.precompile += ['shared/*']

View File

@@ -4289,6 +4289,7 @@ See the %{link} to find out more about %{sitename}'s features and to start using
sortable_header:
name: "Name"
number: "Number"
completed_at: "Completed At"
state: "State"
payment_state: "Payment State"
shipment_state: "Shipment State"

View File

@@ -86,7 +86,7 @@ Spree::Core::Engine.routes.draw do
member do
put :fire
get :fire
post :resend
get :resend
get :invoice
get :print
get :distribution

View File

@@ -3,32 +3,32 @@
*/
import { Application } from "stimulus";
import select_all_controller from "../../../app/webpacker/controllers/select_all_controller";
import checked_controller from "../../../app/webpacker/controllers/checked_controller";
describe("SelectAllController", () => {
describe("CheckedController", () => {
beforeAll(() => {
const application = Application.start();
application.register("select-all", select_all_controller);
application.register("checked", checked_controller);
});
beforeEach(() => {
document.body.innerHTML = `
<div data-controller="select-all">
<div data-controller="checked">
<input
id="selectAllCheckbox"
type="checkbox"
data-action="change->select-all#toggleAll"
data-select-all-target="all">
data-action="change->checked#toggleAll"
data-checked-target="all">
<input
id="checkboxA"
type="checkbox"
data-action="change->select-all#toggleCheckbox"
data-select-all-target="checkbox">
data-action="change->checked#toggleCheckbox"
data-checked-target="checkbox">
<input
id="checkboxB"
type="checkbox"
data-action="change->select-all#toggleCheckbox"
data-select-all-target="checkbox">
data-action="change->checked#toggleCheckbox"
data-checked-target="checkbox">
</div>
`;
});
@@ -83,23 +83,23 @@ describe("SelectAllController", () => {
describe("#connect", () => {
beforeEach(() => {
document.body.innerHTML = `
<div data-controller="select-all">
<div data-controller="checked">
<input
id="selectAllCheckbox"
type="checkbox"
data-action="change->select-all#toggleAll"
data-select-all-target="all">
data-action="change->checked#toggleAll"
data-checked-target="all">
<input
id="checkboxA"
type="checkbox"
data-action="change->select-all#toggleCheckbox"
data-select-all-target="checkbox"
data-action="change->checked#toggleCheckbox"
data-checked-target="checkbox"
checked="checked">
<input
id="checkboxB"
type="checkbox"
data-action="change->select-all#toggleCheckbox"
data-select-all-target="checkbox"
data-action="change->checked#toggleCheckbox"
data-checked-target="checkbox"
checked="checked">
</div>
`;

View File

@@ -1,76 +0,0 @@
describe "ordersCtrl", ->
ctrl = null
Orders = null
$scope = null
orders = [
{ id: 8, order_cycle: { id: 4 }, distributor: { id: 5 }, number: "R123456" }
{ id: 9, order_cycle: { id: 5 }, distributor: { id: 7 }, number: "R213776" }
{ id: 10, order_cycle: { id: 6 }, distributor: { id: 8 }, number: "R213777" }
]
form = {
q: {
created_at_lt: ''
created_at_gt: ''
completed_at_not_null: true
}
}
beforeEach ->
module 'admin.orders'
inject ($controller, $rootScope, RequestMonitor, SortOptions) ->
$scope = $rootScope.$new()
Orders =
index: jasmine.createSpy('index').and.returnValue(orders)
all: orders
ctrl = $controller 'ordersCtrl', { $scope: $scope, RequestMonitor: RequestMonitor, SortOptions: SortOptions, Orders: Orders }
$scope.q = form.q
describe "initialising the controller", ->
it "fetches orders", ->
$scope.initialise()
expect(Orders.index).toHaveBeenCalled()
expect($scope.orders).toEqual orders
it "fetches them sorted by completed_at by default", ->
$scope.initialise()
expect(Orders.index).toHaveBeenCalledWith(jasmine.objectContaining({
'q[s]': 'completed_at desc'
}))
describe "using pagination", ->
it "changes the page", ->
$scope.changePage(2)
expect($scope.page).toEqual 2
expect(Orders.index).toHaveBeenCalled()
describe "sorting orders", ->
it "sorts orders", ->
spyOn $scope, "fetchResults"
$scope.sortOptions.toggle('number')
$scope.$apply()
expect($scope.sorting).toEqual 'number asc'
expect($scope.fetchResults).toHaveBeenCalled()
describe "filtering orders", ->
it "filters orders by all selected order cycles", ->
$scope['q']['order_cycle_id_in'] = ['4', '5']
$scope.fetchResults()
# Fetched with correct square brackets in field name for array value
expect(Orders.index).toHaveBeenCalledWith(jasmine.objectContaining({
'q[order_cycle_id_in][]': ['4', '5']
}))
it "filters orders on inclusive dates", ->
$scope['q']['completed_at_gteq'] = '2020-06-08'
$scope['q']['completed_at_lteq'] = '2020-06-09'
$scope.fetchResults()
expect(Orders.index).toHaveBeenCalledWith(jasmine.objectContaining({
'q[completed_at_gteq]': '2020-06-08 00:00:00'
'q[completed_at_lteq]': '2020-06-09 23:59:59'
}))

View File

@@ -84,6 +84,16 @@ module WebHelper
find(:css, ".select2-result-label", text: options[:select_text] || value).click
end
def tomselect_open(field_name)
page.find("##{field_name}-ts-control").click
end
def tomselect_multiselect(value, options)
tomselect_wrapper = page.find("[name='#{options[:from]}']").sibling(".ts-wrapper")
tomselect_wrapper.find(".ts-control").click
tomselect_wrapper.find(:css, '.ts-dropdown.multi .ts-dropdown-content .option', text: value).click
end
def tomselect_search_and_select(value, options)
tomselect_wrapper = page.find("[name='#{options[:from]}']").sibling(".ts-wrapper")
tomselect_wrapper.find(".ts-control").click

View File

@@ -29,9 +29,10 @@ describe '
# Verify that the orders have a STATE of COMPLETE
expect(page).to have_selector('span', text: 'COMPLETE', count: 2)
page.check('selectAll')
page.find('#selectAll').trigger('click')
page.find("span.icon-reorder", text: "ACTIONS").click
within ".ofn-drop-down-with-prepend .menu" do
within ".ofn-drop-down .menu" do
expect(page).to have_selector("span", text: "Cancel Orders")
page.find("span", text: "Cancel Orders").click
end

View File

@@ -611,6 +611,24 @@ describe '
href: spree.fire_admin_order_path(order, e: 'cancel')
end
end
context "Resending confirmation email" do
before do
visit spree.edit_admin_order_path(order)
find("#links-dropdown .ofn-drop-down").click
end
it "shows the link" do
expect(page).to have_link "Resend Confirmation", href: spree.resend_admin_order_path(order)
end
it "resends the confirmation email" do
accept_alert "Are you sure you want to resend the order confirmation email?" do
click_link "Resend Confirmation"
end
expect(page).to have_content "Order email has been resent"
end
end
context "Check send/print invoice links" do

View File

@@ -99,17 +99,17 @@ distributors: [distributor4, distributor5]) }
end
it "order cycles appear in descending order by close date on orders page" do
open_select2('#s2id_q_order_cycle_id_in')
tomselect_open('q_order_cycle_id_in').click
expect(find('#q_order_cycle_id_in',
visible: :all)[:innerHTML]).to have_content(/.*Four.*Three.*Two.*Five/m)
end
it "filter by multiple order cycles" do
select2_select 'Two', from: 'q_order_cycle_id_in'
select2_select 'Three', from: 'q_order_cycle_id_in'
tomselect_multiselect 'Two', from: 'q[order_cycle_id_in][]'
tomselect_multiselect 'Three', from: 'q[order_cycle_id_in][]'
page.find('.filter-actions .button.icon-search').click
page.find('.filter-actions .button[type=submit]').click
# Order 2 and 3 should show, but not 4
expect(page).to have_content order2.number
@@ -118,10 +118,10 @@ distributors: [distributor4, distributor5]) }
end
it "filter by distributors" do
select2_select distributor2.name.to_s, from: 'q_distributor_id_in'
select2_select distributor4.name.to_s, from: 'q_distributor_id_in'
tomselect_multiselect distributor2.name.to_s, from: 'q[distributor_id_in][]'
tomselect_multiselect distributor4.name.to_s, from: 'q[distributor_id_in][]'
page.find('.filter-actions .button.icon-search').click
page.find('.filter-actions .button[type=submit]').click
# Order 2 and 4 should show, but not 3
expect(page).to have_content order2.number
@@ -134,7 +134,7 @@ distributors: [distributor4, distributor5]) }
select_dates_from_daterangepicker(order3.completed_at.yesterday,
order4.completed_at.tomorrow)
page.find('.filter-actions .button.icon-search').click
page.find('.filter-actions .button[type=submit]').click
# Order 3 and 4 should show, but not 2
expect(page).to_not have_content order2.number
@@ -145,7 +145,7 @@ distributors: [distributor4, distributor5]) }
it "filter by email" do
fill_in "Email", with: customer3.email
page.find('.filter-actions .button.icon-search').click
page.find('.filter-actions .button[type=submit]').click
# Order 3 should show, but not 2 and 4
expect(page).to_not have_content order2.number
@@ -157,28 +157,28 @@ distributors: [distributor4, distributor5]) }
# NOTE: this field refers to the name given in billing addresses and not to customer name
# filtering by first name
fill_in "First name begins with", with: billing_address2.firstname
page.find('.filter-actions .button.icon-search').click
page.find('.filter-actions .button[type=submit]').click
# Order 2 should show, but not 3 and 4
expect(page).to have_content order2.number
expect(page).to_not have_content order3.number
expect(page).to_not have_content order4.number
find("a#clear_filters_button").click
find("#clear_filters_button").click
# filtering by last name
fill_in "Last name begins with", with: billing_address4.lastname
page.find('.filter-actions .button.icon-search').click
page.find('.filter-actions .button[type=submit]').click
# Order 4 should show, but not 2 and 3
expect(page).to_not have_content order2.number
expect(page).to_not have_content order3.number
expect(page).to have_content order4.number
find("a#clear_filters_button").click
find("#clear_filters_button").click
# filtering by first and last name together
fill_in "First name begins with", with: billing_address3.firstname
fill_in "Last name begins with", with: billing_address3.lastname
page.find('.filter-actions .button.icon-search').click
page.find('.filter-actions .button[type=submit]').click
# Order 3 should show, but not 2 and 4
expect(page).not_to have_content order2.number
expect(page).to have_content order3.number
@@ -189,17 +189,17 @@ distributors: [distributor4, distributor5]) }
order2.select_shipping_method(shipping_method.id)
order4.select_shipping_method(shipping_method2.id)
select2_select "Pick-up at the farm", from: 'q_shipping_method_id'
page.find('.filter-actions .button.icon-search').click
tomselect_search_and_select "Pick-up at the farm", from: 'shipping_method_id'
page.find('.filter-actions .button[type=submit]').click
# Order 2 should show, but not 3 and 5
expect(page).to have_content order2.number
expect(page).to_not have_content order3.number
expect(page).to_not have_content order4.number
find("a#clear_filters_button").click
find("#clear_filters_button").click
select2_select "Signed, sealed, delivered", from: 'q_shipping_method_id'
page.find('.filter-actions .button.icon-search').click
tomselect_search_and_select "Signed, sealed, delivered", from: 'shipping_method_id'
page.find('.filter-actions .button[type=submit]').click
# Order 4 should show, but not 2 and 3
expect(page).to_not have_content order2.number
expect(page).to_not have_content order3.number
@@ -209,7 +209,7 @@ distributors: [distributor4, distributor5]) }
it "filter by invoice number" do
fill_in "Invoice number:", with: order2.number
page.find('.filter-actions .button.icon-search').click
page.find('.filter-actions .button[type=submit]').click
# Order 2 should show, but not 3 and 4
expect(page).to have_content order2.number
@@ -221,7 +221,7 @@ distributors: [distributor4, distributor5]) }
order.update(state: "payment")
uncheck 'Only show complete orders'
page.find('.filter-actions .button.icon-search').click
page.find('.filter-actions .button[type=submit]').click
expect(page).to have_content order.number
expect(page).to have_content order2.number
@@ -229,9 +229,9 @@ distributors: [distributor4, distributor5]) }
expect(page).to have_content order4.number
expect(page).to have_content order5.number
select2_select "payment", from: 'q_state_eq'
tomselect_search_and_select "payment", from: 'q[state_eq]'
page.find('.filter-actions .button.icon-search').click
page.find('.filter-actions .button[type=submit]').click
# Order 2 should show, but not 3 and 4
expect(page).to have_content order.number
@@ -295,7 +295,7 @@ distributors: [distributor4, distributor5]) }
login_as_admin
visit spree.admin_orders_path
uncheck 'Only show complete orders'
page.find('.filter-actions .button.icon-search').click
page.find('.filter-actions .button[type=submit]').click
end
it "orders by order state" do
@@ -389,6 +389,21 @@ distributors: [distributor4, distributor5]) }
end
end
context "displaying order special instructions" do
before do
order3.update(special_instructions: "Leave it next to the porch. Thanks!")
login_as_admin
visit spree.admin_orders_path
end
it "displays a note with order instructions" do
within "tr#order_#{order3.id}" do
expect(page).to have_content I18n.t('spree.admin.orders.index.note')
expect(page).to have_css "[data-tooltip-tip-value='#{order3.special_instructions}']"
end
end
end
context "orders with different order totals" do
before do
Spree::LineItem.where(order_id: order2.id).first.update!(quantity: 5)
@@ -432,7 +447,7 @@ distributors: [distributor4, distributor5]) }
page.find("span.icon-reorder", text: "ACTIONS").click
expect(page).to have_content "Print Invoices"
# unselect all orders
page.find("#listing_orders thead th:first-child input[type=checkbox]").click
page.find("#listing_orders thead th:first-child input[type=checkbox]").trigger("click")
expect(page.find(
"#listing_orders tbody tr td:first-child input[type=checkbox]")
).to_not be_checked
@@ -465,14 +480,14 @@ distributors: [distributor4, distributor5]) }
it "can bulk print invoices but only for the 'complete' or 'resumed' ones" do
within "#listing_orders" do
page.find("input[name='order_ids[]'][value='#{order2.id}']").click
page.find("input[name='order_ids[]'][value='#{order3.id}']").click
page.find("input[name='order_ids[]'][value='#{order4.id}']").click
page.find("input[name='order_ids[]'][value='#{order5.id}']").click
page.find("input[name='bulk_ids[]'][value='#{order2.id}']").click
page.find("input[name='bulk_ids[]'][value='#{order3.id}']").click
page.find("input[name='bulk_ids[]'][value='#{order4.id}']").click
page.find("input[name='bulk_ids[]'][value='#{order5.id}']").click
end
page.find("span.icon-reorder", text: "ACTIONS").click
within ".ofn-drop-down-with-prepend .menu" do
within ".ofn-drop-down .menu" do
page.find("span", text: "Send Invoices").click
end
@@ -492,11 +507,11 @@ distributors: [distributor4, distributor5]) }
end
it "can bulk send email to 2 orders" do
page.find("#listing_orders tbody tr:nth-child(1) input[name='order_ids[]']").click
page.find("#listing_orders tbody tr:nth-child(2) input[name='order_ids[]']").click
page.find("#listing_orders tbody tr:nth-child(1) input[name='bulk_ids[]']").click
page.find("#listing_orders tbody tr:nth-child(2) input[name='bulk_ids[]']").click
page.find("span.icon-reorder", text: "ACTIONS").click
within ".ofn-drop-down-with-prepend .menu" do
within ".ofn-drop-down .menu" do
page.find("span", text: "Resend Confirmation").click
end
@@ -512,11 +527,11 @@ distributors: [distributor4, distributor5]) }
end
it "can bulk print invoices from 2 orders" do
page.find("#listing_orders tbody tr:nth-child(1) input[name='order_ids[]']").click
page.find("#listing_orders tbody tr:nth-child(2) input[name='order_ids[]']").click
page.find("#listing_orders tbody tr:nth-child(1) input[name='bulk_ids[]']").click
page.find("#listing_orders tbody tr:nth-child(2) input[name='bulk_ids[]']").click
page.find("span.icon-reorder", text: "ACTIONS").click
within ".ofn-drop-down-with-prepend .menu" do
within ".ofn-drop-down .menu" do
page.find("span", text: "Print Invoices").click
end
@@ -527,11 +542,11 @@ distributors: [distributor4, distributor5]) }
end
it "can bulk cancel 2 orders" do
page.find("#listing_orders tbody tr:nth-child(1) input[name='order_ids[]']").click
page.find("#listing_orders tbody tr:nth-child(2) input[name='order_ids[]']").click
page.find("#listing_orders tbody tr:nth-child(1) input[name='bulk_ids[]']").click
page.find("#listing_orders tbody tr:nth-child(2) input[name='bulk_ids[]']").click
page.find("span.icon-reorder", text: "ACTIONS").click
within ".ofn-drop-down-with-prepend .menu" do
within ".ofn-drop-down .menu" do
page.find("span", text: "Cancel Orders").click
end
@@ -545,7 +560,7 @@ distributors: [distributor4, distributor5]) }
end
page.find("span.icon-reorder", text: "ACTIONS").click
within ".ofn-drop-down-with-prepend .menu" do
within ".ofn-drop-down .menu" do
page.find("span", text: "Cancel Orders").click
end
@@ -574,17 +589,17 @@ distributors: [distributor4, distributor5]) }
end
it "cannot send emails to orders if permission have been revoked in the meantime" do
page.find("#listing_orders tbody tr:nth-child(1) input[name='order_ids[]']").click
page.find("#listing_orders tbody tr:nth-child(1) input[name='bulk_ids[]']").click
# Find the clicked order
order = Spree::Order.find_by(
id: page.find("#listing_orders tbody tr:nth-child(1) input[name='order_ids[]']").value
id: page.find("#listing_orders tbody tr:nth-child(1) input[name='bulk_ids[]']").value
)
# Revoke permission for the current user on that specific order by changing its owners
order.update_attribute(:distributor, distributor)
order.update_attribute(:order_cycle, order_cycle)
page.find("span.icon-reorder", text: "ACTIONS").click
within ".ofn-drop-down-with-prepend .menu" do
within ".ofn-drop-down .menu" do
page.find("span", text: "Resend Confirmation").click
end
@@ -608,11 +623,11 @@ distributors: [distributor4, distributor5]) }
it "displays pagination options" do
# displaying 4 orders (one order per table row)
within('tbody') do
expect(page).to have_css('tr.ng-scope', count: 4)
expect(page).to have_css('tr', count: 4)
end
# pagination options also refer 4 order
expect(page).to have_content "4 Results found. Viewing 1 to 4."
page.find(".per-page-select").click # toggling the pagination dropdown
page.find(".per-page-dropdown .ts-control .item").click # toggling the pagination dropdown
expect(page).to have_content "15 per page"
expect(page).to have_content "50 per page"
expect(page).to have_content "100 per page"
@@ -631,7 +646,7 @@ distributors: [distributor4, distributor5]) }
expect(page).to have_current_path spree.admin_orders_path
# click the 'capture' link for the order
page.find("[data-powertip=Capture]").click
page.find("button.icon-capture").click
expect(page).to have_css "i.success"
expect(page).to have_css "button.icon-road"
@@ -648,7 +663,7 @@ distributors: [distributor4, distributor5]) }
login_as_admin
visit spree.admin_orders_path
page.find("[data-powertip=Ship]").click
page.find("button.icon-road").click
expect(page).to have_css "i.success"
expect(order.reload.shipments.any?(&:shipped?)).to be true
@@ -666,7 +681,7 @@ distributors: [distributor4, distributor5]) }
login_as_admin
visit spree.admin_orders_path
uncheck 'Only show complete orders'
page.find('a.icon-search').click
page.find('button[type=submit]').click
find(".icon-edit").click
@@ -711,7 +726,7 @@ distributors: [distributor4, distributor5]) }
expect(page).to have_no_content empty_order.number
uncheck 'Only show complete orders'
page.find('a.icon-search').click
page.find('button[type=submit]').click
expect(page).to have_content complete_order.number
expect(page).to have_content incomplete_order.number
@@ -741,17 +756,17 @@ distributors: [distributor4, distributor5]) }
# Specify each filters
uncheck 'Only show complete orders'
fill_in "Invoice number", with: "R123456"
select2_select order_cycle.name, from: 'q_order_cycle_id_in'
select2_select distributor.name, from: 'q_distributor_id_in'
select2_select shipping_method.name, from: 'q_shipping_method_id'
select2_select "complete", from: 'q_state_eq'
tomselect_multiselect order_cycle.name, from: 'q[order_cycle_id_in][]'
tomselect_multiselect distributor.name, from: 'q[distributor_id_in][]'
tomselect_search_and_select shipping_method.name, from: 'shipping_method_id'
tomselect_search_and_select "complete", from: 'q[state_eq]'
fill_in "Email", with: user.email
fill_in "First name begins with", with: "J"
fill_in "Last name begins with", with: "D"
find("input.datepicker").click
select_dates_from_daterangepicker(Time.zone.at(1.week.ago), Time.zone.now.tomorrow)
page.find('a.icon-search').click
page.find('.button[type=submit]').click
end
it "when reloading the page" do
@@ -760,10 +775,10 @@ distributors: [distributor4, distributor5]) }
# Check every filters to be equal
expect(find_field("Only show complete orders")).not_to be_checked
expect(find_field("Invoice number").value).to eq "R123456"
expect(find("#s2id_q_shipping_method_id").text).to eq shipping_method.name
expect(find("#s2id_q_state_eq").text).to eq "complete"
expect(find("#s2id_q_distributor_id_in").text).to eq distributor.name
expect(find("#s2id_q_order_cycle_id_in").text).to eq order_cycle.name
expect(find("#shipping_method_id-ts-control .item").text).to eq shipping_method.name
expect(find("#q_state_eq-ts-control .item").text).to eq "complete"
expect(find("#q_distributor_id_in").value).to eq [distributor.id.to_s]
expect(find("#q_order_cycle_id_in").value).to eq [order_cycle.id.to_s]
expect(find_field("Email").value).to eq user.email
expect(find_field("First name begins with").value).to eq "J"
expect(find_field("Last name begins with").value).to eq "D"
@@ -773,13 +788,13 @@ distributors: [distributor4, distributor5]) }
end
it "and clear filters" do
find("a#clear_filters_button").click
find("#clear_filters_button").click
expect(find_field("Only show complete orders")).to be_checked
expect(find_field("Invoice number").value).to eq ""
expect(find("#s2id_q_shipping_method_id").text).to be_empty
expect(find("#s2id_q_state_eq").text).to be_empty
expect(find("#s2id_q_distributor_id_in").text).to be_empty
expect(find("#s2id_q_order_cycle_id_in").text).to be_empty
expect(find("#shipping_method_id").value).to be_empty
expect(find("#q_state_eq").value).to be_empty
expect(find("#q_distributor_id_in").value).to be_empty
expect(find("#q_order_cycle_id_in").value).to be_empty
expect(find_field("Email").value).to be_empty
expect(find_field("First name begins with").value).to be_empty
expect(find_field("Last name begins with").value).to be_empty