diff --git a/.codeclimate.yml b/.codeclimate.yml
index d737c0b3c9..dc2ebef051 100644
--- a/.codeclimate.yml
+++ b/.codeclimate.yml
@@ -1,6 +1,9 @@
engines:
rubocop:
enabled: true
+ exclude_fingerprints:
+ - ac41db8d4ec4cbf508c353d9b65a024f
+ - 8e3b6322aef5be9f38700b3fd0cd347e
scss-lint:
enabled: true
ratings:
diff --git a/.gitignore b/.gitignore
index 42107a8e2e..13f5a4441c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -39,3 +39,4 @@ NERD_tree*
coverage
libpeerconnection.log
/config/application.yml
+node_modules
diff --git a/.travis.yml b/.travis.yml
index 56d94e5952..ef54e640c8 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -26,13 +26,16 @@ before_script:
- cp config/database.travis.yml config/database.yml
- cp config/application.yml.example config/application.yml
- RAILS_ENV=test bundle exec rake db:create db:schema:load
+
+ # Only install PhantomJS if it is not already present (ie. cached)
+ - npm list -g phantomjs-prebuilt@~2.1.7 --depth=0 || npm install -g phantomjs-prebuilt@~2.1.7
+ - export PATH=`npm bin -g`:$PATH
+
- >
if [ "$KARMA" = "true" ]; then
- npm install karma@0.12.31
- npm install karma-jasmine@0.1.5
- npm install karma-phantomjs-launcher@0.1.4
- npm install karma-coffee-preprocessor@0.2.1
- npm install -g karma-cli@0.0.4
+ npm install -g npm@'3.8.8'
+ npm install
+ npm install -g karma-cli@0.1.2
fi
script:
diff --git a/Gemfile b/Gemfile
index d581c8d10b..eb49f89c23 100644
--- a/Gemfile
+++ b/Gemfile
@@ -28,7 +28,7 @@ gem 'comfortable_mexican_sofa'
gem 'simple_form', :github => 'RohanM/simple_form'
gem 'unicorn'
-gem 'angularjs-rails', '1.2.13'
+gem 'angularjs-rails', '1.5.5'
gem 'bugsnag'
gem 'newrelic_rpm'
gem 'haml'
diff --git a/Gemfile.lock b/Gemfile.lock
index 0e31157d96..07931312ab 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -153,7 +153,7 @@ GEM
sprockets (~> 2)
tilt
angularjs-file-upload-rails (1.1.0)
- angularjs-rails (1.2.13)
+ angularjs-rails (1.5.5)
ansi (1.4.2)
arel (3.0.3)
atomic (1.1.99)
@@ -176,7 +176,8 @@ GEM
columnize (~> 0.3)
debugger-linecache (~> 1.2)
cancan (1.6.8)
- capybara (2.5.0)
+ capybara (2.7.1)
+ addressable
mime-types (>= 1.16)
nokogiri (>= 1.3.3)
rack (>= 1.0.0)
@@ -455,7 +456,7 @@ GEM
railties (>= 3.1)
money (5.1.1)
i18n (~> 0.6.0)
- multi_json (1.11.2)
+ multi_json (1.12.0)
multi_xml (0.5.5)
newrelic_rpm (3.12.0.288)
nokogiri (1.6.7.2)
@@ -479,7 +480,7 @@ GEM
paypal-sdk-merchant (1.106.1)
paypal-sdk-core (~> 0.2.3)
pg (0.13.2)
- poltergeist (1.7.0)
+ poltergeist (1.9.0)
capybara (~> 2.1)
cliver (~> 0.3.1)
multi_json (~> 1.0)
@@ -626,7 +627,7 @@ GEM
webmock (1.13.0)
addressable (>= 2.2.7)
crack (>= 0.3.2)
- websocket-driver (0.6.2)
+ websocket-driver (0.6.3)
websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.2)
whenever (0.9.2)
@@ -650,7 +651,7 @@ DEPENDENCIES
andand
angular-rails-templates (~> 0.2.0)
angularjs-file-upload-rails (~> 1.1.0)
- angularjs-rails (= 1.2.13)
+ angularjs-rails (= 1.5.5)
atomic
awesome_print
aws-sdk
@@ -734,4 +735,4 @@ DEPENDENCIES
wkhtmltopdf-binary
BUNDLED WITH
- 1.10.6
+ 1.11.2
diff --git a/app/assets/javascripts/admin/all.js b/app/assets/javascripts/admin/all.js
index 9f99dc1dcd..0923e5721c 100644
--- a/app/assets/javascripts/admin/all.js
+++ b/app/assets/javascripts/admin/all.js
@@ -43,9 +43,9 @@
//= require ./utils/utils
//= require ./users/users
//= require ./variant_overrides/variant_overrides
-//= require textAngular.min.js
+//= require textAngular-rangy.min.js
//= require textAngular-sanitize.min.js
-//= require ../shared/bindonce.min.js
+//= require textAngular.min.js
//= require darkswarm/i18n.js
//= require darkswarm/i18n.translate.js
diff --git a/app/assets/javascripts/admin/bulk_order_management.js.coffee b/app/assets/javascripts/admin/bulk_order_management.js.coffee
deleted file mode 100644
index b747d3699e..0000000000
--- a/app/assets/javascripts/admin/bulk_order_management.js.coffee
+++ /dev/null
@@ -1,222 +0,0 @@
-angular.module("ofn.admin").controller "AdminOrderMgmtCtrl", [
- "$scope", "$http", "$filter", "dataFetcher", "blankOption", "pendingChanges", "VariantUnitManager", "OptionValueNamer", "SpreeApiKey", "Columns"
- ($scope, $http, $filter, dataFetcher, blankOption, pendingChanges, VariantUnitManager, OptionValueNamer, SpreeApiKey, Columns) ->
- $scope.loading = true
-
- $scope.initialiseVariables = ->
- start = daysFromToday -7
- end = daysFromToday 1
- $scope.lineItems = []
- $scope.filteredLineItems = []
- $scope.confirmDelete = true
- $scope.startDate = formatDate start
- $scope.endDate = formatDate end
- $scope.quickSearch = ""
- $scope.bulkActions = [ { name: t("bom_actions_delete"), callback: $scope.deleteLineItems } ]
- $scope.selectedBulkAction = $scope.bulkActions[0]
- $scope.selectedUnitsProduct = {};
- $scope.selectedUnitsVariant = {};
- $scope.sharedResource = false
- $scope.columns = Columns.setColumns
- order_no: { name: t("bom_no"), visible: false }
- full_name: { name: t("name"), visible: true }
- email: { name: t("email"), visible: false }
- phone: { name: t("phone"), visible: false }
- order_date: { name: t("bom_date"), visible: true }
- producer: { name: t("producer"), visible: true }
- order_cycle: { name: t("bom_cycle"), visible: false }
- hub: { name: t("bom_hub"), visible: false }
- variant: { name: t("bom_variant"), visible: true }
- quantity: { name: t("bom_quantity"), visible: true }
- max: { name: t("bom_max"), visible: true }
- final_weight_volume: { name: t("bom_final_weigth_volume"), visible: false }
- price: { name: t("price"), visible: false }
- $scope.initialise = ->
- $scope.initialiseVariables()
- authorise_api_reponse = ""
- dataFetcher("/api/users/authorise_api?token=" + SpreeApiKey).then (data) ->
- authorise_api_reponse = data
- $scope.spree_api_key_ok = data.hasOwnProperty("success") and data["success"] == "Use of API Authorised"
- if $scope.spree_api_key_ok
- $http.defaults.headers.common["X-Spree-Token"] = SpreeApiKey
- dataFetcher("/api/enterprises/accessible?template=bulk_index&q[is_primary_producer_eq]=true").then (data) ->
- $scope.suppliers = $filter('orderBy')(data, 'name')
- $scope.suppliers.unshift blankOption()
- dataFetcher("/api/enterprises/accessible?template=bulk_index&q[sells_in][]=own&q[sells_in][]=any").then (data) ->
- $scope.distributors = $filter('orderBy')(data, 'name')
- $scope.distributors.unshift blankOption()
- ocFetcher = dataFetcher("/api/order_cycles/accessible?as=distributor&q[orders_close_at_gt]=#{formatDate(daysFromToday(-90))}").then (data) ->
- $scope.orderCycles = data
- $scope.orderCyclesByID = []
- $scope.orderCyclesByID[oc.id] = oc for oc in $scope.orderCycles
- $scope.orderCycles.unshift blankOption()
- $scope.fetchOrders()
- ocFetcher.then ->
- $scope.resetSelectFilters()
- else if authorise_api_reponse.hasOwnProperty("error")
- $scope.api_error_msg = authorise_api_reponse("error")
- else
- api_error_msg = "You don't have an API key yet. An attempt was made to generate one, but you are currently not authorised, please contact your site administrator for access."
-
- $scope.fetchOrders = ->
- $scope.loading = true
- dataFetcher("/admin/orders/managed?template=bulk_index;page=1;per_page=500;q[state_not_eq]=canceled;q[completed_at_not_null]=true;q[completed_at_gt]=#{$scope.startDate};q[completed_at_lt]=#{$scope.endDate}").then (data) ->
- $scope.resetOrders data
- $scope.loading = false
-
- $scope.resetOrders = (data) ->
- $scope.orders = data
- $scope.resetLineItems()
- pendingChanges.removeAll()
-
- $scope.resetLineItems = ->
- $scope.lineItems = $scope.orders.reduce (lineItems,order) ->
- orderWithoutLineItems = $scope.lineItemOrder order
- for i,line_item of order.line_items
- line_item.checked = false
- line_item.supplier = $scope.matchObject $scope.suppliers, line_item.supplier, null
- line_item.order = orderWithoutLineItems
- line_item.original_final_weight_volume = line_item.final_weight_volume
- line_item.original_quantity = line_item.quantity
- line_item.original_price = line_item.price
-
- lineItems.concat order.line_items
- , []
-
- $scope.lineItemOrder = (order) ->
- lineItemOrder = angular.copy(order)
- delete lineItemOrder.line_items
- lineItemOrder.distributor = $scope.matchObject $scope.distributors, order.distributor, null
- lineItemOrder.order_cycle = $scope.matchObject $scope.orderCycles, order.order_cycle, null
- lineItemOrder
-
- $scope.matchObject = (list, testObject, noMatch) ->
- for i, object of list
- if angular.equals(object, testObject)
- return object
- return noMatch
-
- $scope.deleteLineItem = (lineItem) ->
- if ($scope.confirmDelete && confirm("Are you sure?")) || !$scope.confirmDelete
- $http(
- method: "DELETE"
- url: "/api/orders/" + lineItem.order.number + "/line_items/" + lineItem.id
- ).success (data) ->
- $scope.lineItems.splice $scope.lineItems.indexOf(lineItem), 1
-
- $scope.deleteLineItems = (lineItems) ->
- existingState = $scope.confirmDelete
- $scope.confirmDelete = false
- $scope.deleteLineItem lineItem for lineItem in lineItems when lineItem.checked
- $scope.confirmDelete = existingState
-
- $scope.submit = ->
- if $scope.bulk_order_form.$valid
- pendingChanges.submitAll()
- else
- alert "Some errors must be resolved be before you can update orders.\nAny fields with red borders contain errors."
-
- $scope.allBoxesChecked = ->
- checkedCount = $scope.filteredLineItems.reduce (count,lineItem) ->
- count + (if lineItem.checked then 1 else 0 )
- , 0
- checkedCount == $scope.filteredLineItems.length
-
- $scope.toggleAllCheckboxes = ->
- changeTo = !$scope.allBoxesChecked()
- lineItem.checked = changeTo for lineItem in $scope.filteredLineItems
-
- $scope.setSelectedUnitsVariant = (unitsProduct,unitsVariant) ->
- $scope.selectedUnitsProduct = unitsProduct
- $scope.selectedUnitsVariant = unitsVariant
-
- $scope.sumUnitValues = ->
- sum = $scope.filteredLineItems.reduce (sum,lineItem) ->
- sum = sum + lineItem.final_weight_volume
- , 0
-
- $scope.sumMaxUnitValues = ->
- sum = $scope.filteredLineItems.reduce (sum,lineItem) ->
- sum = sum + Math.max(lineItem.max_quantity,lineItem.original_quantity) * lineItem.units_variant.unit_value
- , 0
-
- $scope.allFinalWeightVolumesPresent = ->
- for i,lineItem of $scope.filteredLineItems
- return false if !lineItem.hasOwnProperty('final_weight_volume') || !(lineItem.final_weight_volume > 0)
- true
-
- # How is this different to OptionValueNamer#name?
- # Should it be extracted to that class or VariantUnitManager?
- $scope.formattedValueWithUnitName = (value, unitsProduct, unitsVariant) ->
- # A Units Variant is an API object which holds unit properies of a variant
- if unitsProduct.hasOwnProperty("variant_unit") && (unitsProduct.variant_unit == "weight" || unitsProduct.variant_unit == "volume") && value > 0
- scale = VariantUnitManager.getScale(value, unitsProduct.variant_unit)
- Math.round(value/scale * 1000)/1000 + " " + VariantUnitManager.getUnitName(scale, unitsProduct.variant_unit)
- else
- ''
-
- $scope.fulfilled = (sumOfUnitValues) ->
- # A Units Variant is an API object which holds unit properies of a variant
- if $scope.selectedUnitsProduct.hasOwnProperty("group_buy_unit_size") && $scope.selectedUnitsProduct.group_buy_unit_size > 0 &&
- $scope.selectedUnitsProduct.hasOwnProperty("variant_unit") &&
- ( $scope.selectedUnitsProduct.variant_unit == "weight" || $scope.selectedUnitsProduct.variant_unit == "volume" )
- Math.round( sumOfUnitValues / $scope.selectedUnitsProduct.group_buy_unit_size * 1000)/1000
- else
- ''
-
- $scope.unitsVariantSelected = ->
- !angular.equals($scope.selectedUnitsVariant,{})
-
- $scope.resetSelectFilters = ->
- $scope.distributorFilter = $scope.distributors[0].id
- $scope.supplierFilter = $scope.suppliers[0].id
- $scope.orderCycleFilter = $scope.orderCycles[0].id
- $scope.quickSearch = ""
-
- $scope.weightAdjustedPrice = (lineItem) ->
- if lineItem.final_weight_volume > 0
- unit_value = lineItem.final_weight_volume / lineItem.quantity
- original_unit_value = lineItem.original_final_weight_volume / lineItem.original_quantity
- lineItem.price = lineItem.original_price * (unit_value / original_unit_value)
-
- $scope.unitValueLessThanZero = (lineItem) ->
- if lineItem.units_variant.unit_value <= 0
- true
- else
- false
-
- $scope.updateOnQuantity = (lineItem) ->
- if lineItem.quantity > 0
- lineItem.final_weight_volume = lineItem.original_final_weight_volume * lineItem.quantity / lineItem.original_quantity
- $scope.weightAdjustedPrice(lineItem)
-
- $scope.$watch "orderCycleFilter", (newVal, oldVal) ->
- unless $scope.orderCycleFilter == "0" || angular.equals(newVal, oldVal)
- $scope.startDate = $scope.orderCyclesByID[$scope.orderCycleFilter].first_order
- $scope.endDate = $scope.orderCyclesByID[$scope.orderCycleFilter].last_order
-]
-
-daysFromToday = (days) ->
- now = new Date
- now.setHours(0)
- now.setMinutes(0)
- now.setSeconds(0)
- now.setDate( now.getDate() + days )
- now
-
-formatDate = (date) ->
- year = date.getFullYear()
- month = twoDigitNumber date.getMonth() + 1
- day = twoDigitNumber date.getDate()
- return year + "-" + month + "-" + day
-
-formatTime = (date) ->
- hours = twoDigitNumber date.getHours()
- mins = twoDigitNumber date.getMinutes()
- secs = twoDigitNumber date.getSeconds()
- return hours + ":" + mins + ":" + secs
-
-twoDigitNumber = (number) ->
- twoDigits = "" + number
- twoDigits = ("0" + number) if number < 10
- twoDigits
diff --git a/app/assets/javascripts/admin/bulk_product_update.js.coffee b/app/assets/javascripts/admin/bulk_product_update.js.coffee
index 3d565f703a..751734a899 100644
--- a/app/assets/javascripts/admin/bulk_product_update.js.coffee
+++ b/app/assets/javascripts/admin/bulk_product_update.js.coffee
@@ -3,18 +3,7 @@ angular.module("ofn.admin").controller "AdminProductEditCtrl", ($scope, $timeout
$scope.StatusMessage = StatusMessage
- $scope.columns = Columns.setColumns
- producer: {name: t("products_producer"), visible: true}
- sku: {name: t("products_sku"), visible: false}
- name: {name: t("products_name"), visible: true}
- unit: {name: t("products_unit"), visible: true}
- price: {name: t("products_price"), visible: true}
- on_hand: {name: t("products_on_hand"), visible: true}
- on_demand: {name: t("products_on_demand"), visible: false}
- category: {name: t("products_category"), visible: false}
- tax_category: {name: t("products_tax_category"), visible: false}
- inherits_properties: {name: t("products_inherits_properties"), visible: false}
- available_on: {name: t("products_available_on"), visible: false}
+ $scope.columns = Columns.columns
$scope.variant_unit_options = VariantUnitManager.variantUnitOptions()
diff --git a/app/assets/javascripts/admin/customers/controllers/customers_controller.js.coffee b/app/assets/javascripts/admin/customers/controllers/customers_controller.js.coffee
index bfccfd3f4b..23bfb37738 100644
--- a/app/assets/javascripts/admin/customers/controllers/customers_controller.js.coffee
+++ b/app/assets/javascripts/admin/customers/controllers/customers_controller.js.coffee
@@ -1,43 +1,24 @@
-angular.module("admin.customers").controller "customersCtrl", ($scope, CustomerResource, TagsResource, $q, Columns, pendingChanges, shops) ->
- $scope.shop = {}
+angular.module("admin.customers").controller "customersCtrl", ($scope, $q, Customers, TagRuleResource, CurrentShop, RequestMonitor, Columns, pendingChanges, shops) ->
$scope.shops = shops
+ $scope.CurrentShop = CurrentShop
+ $scope.RequestMonitor = RequestMonitor
$scope.submitAll = pendingChanges.submitAll
+ $scope.add = Customers.add
+ $scope.deleteCustomer = Customers.remove
+ $scope.customerLimit = 20
+ $scope.columns = Columns.columns
- $scope.columns = Columns.setColumns
- email: { name: "Email", visible: true }
- code: { name: "Code", visible: true }
- tags: { name: "Tags", visible: true }
-
- $scope.$watch "shop.id", ->
- if $scope.shop.id?
- $scope.customers = index {enterprise_id: $scope.shop.id}
+ $scope.$watch "CurrentShop.shop", ->
+ if $scope.CurrentShop.shop.id?
+ Customers.index({enterprise_id: $scope.CurrentShop.shop.id}).then (data) ->
+ $scope.customers = data
$scope.findTags = (query) ->
defer = $q.defer()
params =
- enterprise_id: $scope.shop.id
- TagsResource.index params, (data) =>
+ enterprise_id: $scope.CurrentShop.shop.id
+ TagRuleResource.mapByTag params, (data) =>
filtered = data.filter (tag) ->
tag.text.toLowerCase().indexOf(query.toLowerCase()) != -1
defer.resolve filtered
defer.promise
-
- $scope.add = (email) ->
- params =
- enterprise_id: $scope.shop.id
- email: email
- CustomerResource.create params, (customer) =>
- if customer.id
- $scope.customers.push customer
- $scope.quickSearch = customer.email
-
- $scope.deleteCustomer = (customer) ->
- params = id: customer.id
- CustomerResource.destroy params, ->
- i = $scope.customers.indexOf customer
- $scope.customers.splice i, 1 unless i < 0
-
- index = (params) ->
- $scope.loaded = false
- CustomerResource.index params, =>
- $scope.loaded = true
diff --git a/app/assets/javascripts/admin/customers/customers.js.coffee b/app/assets/javascripts/admin/customers/customers.js.coffee
index 1e8ae9b988..fe8ae1de5b 100644
--- a/app/assets/javascripts/admin/customers/customers.js.coffee
+++ b/app/assets/javascripts/admin/customers/customers.js.coffee
@@ -1 +1 @@
-angular.module("admin.customers", ['ngResource', 'ngTagsInput', 'admin.indexUtils', 'admin.utils', 'admin.dropdown'])
\ No newline at end of file
+angular.module("admin.customers", ['ngResource', 'admin.tagRules', 'admin.indexUtils', 'admin.utils', 'admin.dropdown'])
diff --git a/app/assets/javascripts/admin/customers/directives/new_customer_dialog.js.coffee b/app/assets/javascripts/admin/customers/directives/new_customer_dialog.js.coffee
new file mode 100644
index 0000000000..46292ae85c
--- /dev/null
+++ b/app/assets/javascripts/admin/customers/directives/new_customer_dialog.js.coffee
@@ -0,0 +1,37 @@
+angular.module("admin.customers").directive 'newCustomerDialog', ($compile, $injector, $templateCache, DialogDefaults, CurrentShop, Customers) ->
+ restrict: 'A'
+ scope: true
+ link: (scope, element, attr) ->
+ scope.CurrentShop = CurrentShop
+ scope.submitted = null
+ scope.email = ""
+ scope.errors = []
+
+ scope.addCustomer = (valid) ->
+ scope.submitted = scope.email
+ scope.errors = []
+ if valid
+ Customers.add(scope.email).$promise.then (data) ->
+ if data.id
+ scope.email = ""
+ scope.submitted = null
+ template.dialog('close')
+ , (response) ->
+ if response.data.errors
+ scope.errors.push(error) for error in response.data.errors
+ else
+ scope.errors.push("Sorry! Could not create '#{scope.email}'")
+ return
+
+ # Compile modal template
+ template = $compile($templateCache.get('admin/new_customer_dialog.html'))(scope)
+
+ # Set Dialog options
+ template.dialog(DialogDefaults)
+
+ # Link opening of dialog to click event on element
+ element.bind 'click', (e) ->
+ if CurrentShop.shop.id
+ template.dialog('open')
+ else
+ alert('Please select a shop first')
diff --git a/app/assets/javascripts/admin/customers/services/current_enterprise.js.coffee b/app/assets/javascripts/admin/customers/services/current_enterprise.js.coffee
new file mode 100644
index 0000000000..9df7e09895
--- /dev/null
+++ b/app/assets/javascripts/admin/customers/services/current_enterprise.js.coffee
@@ -0,0 +1,3 @@
+angular.module("admin.customers").factory "CurrentShop", ->
+ new class CurrentShop
+ shop: {}
diff --git a/app/assets/javascripts/admin/customers/services/customers.js.coffee b/app/assets/javascripts/admin/customers/services/customers.js.coffee
new file mode 100644
index 0000000000..a9f4f0102f
--- /dev/null
+++ b/app/assets/javascripts/admin/customers/services/customers.js.coffee
@@ -0,0 +1,21 @@
+angular.module("admin.customers").factory "Customers", ($q, RequestMonitor, CustomerResource, CurrentShop) ->
+ new class Customers
+ customers: []
+
+ add: (email) ->
+ params =
+ enterprise_id: CurrentShop.shop.id
+ email: email
+ CustomerResource.create params, (customer) =>
+ @customers.unshift customer if customer.id
+
+ remove: (customer) ->
+ params = id: customer.id
+ CustomerResource.destroy params, =>
+ i = @customers.indexOf customer
+ @customers.splice i, 1 unless i < 0
+
+ index: (params) ->
+ request = CustomerResource.index(params, (data) => @customers = data)
+ RequestMonitor.load(request.$promise)
+ request.$promise
diff --git a/app/assets/javascripts/admin/customers/services/tags_resource.js.coffee b/app/assets/javascripts/admin/customers/services/tags_resource.js.coffee
deleted file mode 100644
index ad89511e32..0000000000
--- a/app/assets/javascripts/admin/customers/services/tags_resource.js.coffee
+++ /dev/null
@@ -1,9 +0,0 @@
-angular.module("admin.customers").factory 'TagsResource', ($resource) ->
- $resource('/admin/tags.json', {}, {
- 'index':
- method: 'GET'
- isArray: true
- cache: true
- params:
- enterprise_id: '@enterprise_id'
- })
diff --git a/app/assets/javascripts/admin/dropdown/controllers/columns_dropdown_controller.js.coffee b/app/assets/javascripts/admin/dropdown/controllers/columns_dropdown_controller.js.coffee
new file mode 100644
index 0000000000..a19b6aaed3
--- /dev/null
+++ b/app/assets/javascripts/admin/dropdown/controllers/columns_dropdown_controller.js.coffee
@@ -0,0 +1,10 @@
+angular.module("admin.dropdown").controller "ColumnsDropdownCtrl", ($scope, Columns) ->
+ $scope.columns = Columns.columns
+ $scope.toggle = Columns.toggleColumn
+ $scope.saved = Columns.preferencesSaved
+ $scope.saving = false
+
+ $scope.saveColumnPreferences = (action_name) ->
+ $scope.saving = true
+ Columns.savePreferences(action_name).then ->
+ $scope.saving = false
diff --git a/app/assets/javascripts/admin/dropdown/directives/columns_dropdown.js.coffee b/app/assets/javascripts/admin/dropdown/directives/columns_dropdown.js.coffee
new file mode 100644
index 0000000000..8248b626bf
--- /dev/null
+++ b/app/assets/javascripts/admin/dropdown/directives/columns_dropdown.js.coffee
@@ -0,0 +1,6 @@
+angular.module("admin.dropdown").directive 'columnsDropdown', ->
+ restrict: 'E'
+ templateUrl: 'admin/columns_dropdown.html'
+ controller: 'ColumnsDropdownCtrl'
+ scope:
+ action: '@'
diff --git a/app/assets/javascripts/admin/enterprise_groups/controllers/side_menu_controller.js.coffee b/app/assets/javascripts/admin/enterprise_groups/controllers/side_menu_controller.js.coffee
index 45ca04bfe7..f69c5365d5 100644
--- a/app/assets/javascripts/admin/enterprise_groups/controllers/side_menu_controller.js.coffee
+++ b/app/assets/javascripts/admin/enterprise_groups/controllers/side_menu_controller.js.coffee
@@ -4,12 +4,12 @@ angular.module("admin.enterprise_groups")
$scope.select = SideMenu.select
$scope.menu.setItems [
- { name: 'Primary Details', icon_class: "icon-user" }
- { name: (t('users')), icon_class: "icon-user" }
- { name: (t('about')), icon_class: "icon-pencil" }
- { name: (t('images')), icon_class: "icon-picture" }
- { name: (t('contact')), icon_class: "icon-phone" }
- { name: (t('web')), icon_class: "icon-globe" }
+ { name: 'primary_details', label: t('primary_details'), icon_class: "icon-user" }
+ { name: 'users', label: t('users'), icon_class: "icon-user" }
+ { name: 'about', label: t('about'), icon_class: "icon-pencil" }
+ { name: 'images', label: t('images'), icon_class: "icon-picture" }
+ { name: 'contact', label: t('admin_entreprise_groups_contact'), icon_class: "icon-phone" }
+ { name: 'web', label: t('admin_entreprise_groups_web'), icon_class: "icon-globe" }
]
$scope.select(0)
diff --git a/app/assets/javascripts/admin/enterprises/controllers/enterprise_index_row_controller.js.coffee b/app/assets/javascripts/admin/enterprises/controllers/enterprise_index_row_controller.js.coffee
deleted file mode 100644
index 63b8daf07c..0000000000
--- a/app/assets/javascripts/admin/enterprises/controllers/enterprise_index_row_controller.js.coffee
+++ /dev/null
@@ -1,49 +0,0 @@
-angular.module("admin.enterprises").controller "EnterpriseIndexRowCtrl", ($scope) ->
- $scope.status = ->
- if $scope.enterprise.issues.length > 0
- "issue"
- else if $scope.enterprise.warnings.length > 0
- "warning"
- else
- "ok"
-
-
- $scope.producerText = ->
- switch $scope.enterprise.is_primary_producer
- when true
- "Producer"
- else
- "Non-Producer"
-
- $scope.packageText = ->
- switch $scope.enterprise.is_primary_producer
- when true
- switch $scope.enterprise.sells
- when "none"
- "Profile"
- when "own"
- "Shop"
- when "any"
- "Hub"
- else
- "Choose"
- else
- switch $scope.enterprise.sells
- when "none"
- "Profile"
- when "any"
- "Hub"
- else
- "Choose"
-
- $scope.updateRowText = ->
- $scope.producer = $scope.producerText()
- $scope.package = $scope.packageText()
- $scope.producerError = ($scope.producer == "Choose")
- $scope.packageError = ($scope.package == "Choose")
-
-
- $scope.updateRowText()
-
- $scope.$on "enterprise:updated", ->
- $scope.updateRowText()
diff --git a/app/assets/javascripts/admin/enterprises/controllers/enterprises_controller.js.coffee b/app/assets/javascripts/admin/enterprises/controllers/enterprises_controller.js.coffee
index 3d8bfa6446..02553a822b 100644
--- a/app/assets/javascripts/admin/enterprises/controllers/enterprises_controller.js.coffee
+++ b/app/assets/javascripts/admin/enterprises/controllers/enterprises_controller.js.coffee
@@ -3,11 +3,54 @@ angular.module("admin.enterprises").controller 'enterprisesCtrl', ($scope, $q, E
requests.push ($scope.allEnterprises = Enterprises.index(ams_prefix: "index")).$promise
$q.all(requests).then ->
+ $scope.updateStaticFieldsFor(enterprise) for enterprise in $scope.allEnterprises
$scope.loaded = true
- $scope.columns = Columns.setColumns
- name: { name: "Name", visible: true }
- producer: { name: "Producer", visible: true }
- package: { name: "Package", visible: true }
- status: { name: "Status", visible: true }
- manage: { name: "Manage", visible: true }
+ $scope.columns = Columns.columns
+
+ $scope.updateStaticFieldsFor = (enterprise) ->
+ enterprise.producer = $scope.producerTextFor(enterprise)
+ enterprise.package = $scope.packageTextFor(enterprise)
+ enterprise.producerError = (enterprise.producer == "Choose")
+ enterprise.packageError = (enterprise.package == "Choose")
+ enterprise.status = $scope.statusFor(enterprise)
+
+ $scope.$on "enterprise:updated", (event, enterprise) ->
+ $scope.updateStaticFieldsFor(enterprise)
+
+ $scope.statusFor = (enterprise) ->
+ if enterprise.issues.length > 0
+ "issue"
+ else if enterprise.warnings.length > 0
+ "warning"
+ else
+ "ok"
+
+
+ $scope.producerTextFor = (enterprise) ->
+ switch enterprise.is_primary_producer
+ when true
+ "Producer"
+ else
+ "Non-Producer"
+
+ $scope.packageTextFor = (enterprise) ->
+ switch enterprise.is_primary_producer
+ when true
+ switch enterprise.sells
+ when "none"
+ "Profile"
+ when "own"
+ "Shop"
+ when "any"
+ "Hub"
+ else
+ "Choose"
+ else
+ switch enterprise.sells
+ when "none"
+ "Profile"
+ when "any"
+ "Hub"
+ else
+ "Choose"
diff --git a/app/assets/javascripts/admin/enterprises/controllers/index_panel_controller.js.coffee b/app/assets/javascripts/admin/enterprises/controllers/index_panel_controller.js.coffee
index 6f568ca5ea..1e207884d3 100644
--- a/app/assets/javascripts/admin/enterprises/controllers/index_panel_controller.js.coffee
+++ b/app/assets/javascripts/admin/enterprises/controllers/index_panel_controller.js.coffee
@@ -9,7 +9,7 @@ angular.module("admin.enterprises").controller 'indexPanelCtrl', ($scope, Enterp
unless $scope.saved()
$scope.saving = true
Enterprises.save($scope.enterprise).then (data) ->
- $scope.$emit("enterprise:updated")
+ $scope.$emit("enterprise:updated", $scope.enterprise)
$scope.saving = false
, (response) ->
$scope.saving = false
diff --git a/app/assets/javascripts/admin/enterprises/controllers/side_menu_controller.js.coffee b/app/assets/javascripts/admin/enterprises/controllers/side_menu_controller.js.coffee
index a0105fefa4..913ff59d3e 100644
--- a/app/assets/javascripts/admin/enterprises/controllers/side_menu_controller.js.coffee
+++ b/app/assets/javascripts/admin/enterprises/controllers/side_menu_controller.js.coffee
@@ -5,21 +5,21 @@ angular.module("admin.enterprises")
$scope.select = SideMenu.select
$scope.menu.setItems [
- { name: t('primary_details'), icon_class: "icon-home" }
- { name: t('users'), icon_class: "icon-user" }
- { name: t('address'), icon_class: "icon-map-marker" }
- { name: t('contact'), icon_class: "icon-phone" }
- { name: t('social'), icon_class: "icon-twitter" }
- { name: t('about'), icon_class: "icon-pencil" }
- { name: t('business_details'), icon_class: "icon-briefcase" }
- { name: t('images'), icon_class: "icon-picture" }
- { name: t('properties'), icon_class: "icon-tags", show: "showProperties()" }
- { name: t('shipping_methods'), icon_class: "icon-truck", show: "showShippingMethods()" }
- { name: t('payment_methods'), icon_class: "icon-money", show: "showPaymentMethods()" }
- { name: t('enterprise_fees'), icon_class: "icon-tasks", show: "showEnterpriseFees()" }
- { name: t('inventory_settings'), icon_class: "icon-list-ol", show: "enterpriseIsShop()" }
- { name: t('tag_rules'), icon_class: "icon-random", show: "enterpriseIsShop()" }
- { name: t('shop_preferences'), icon_class: "icon-shopping-cart", show: "enterpriseIsShop()" }
+ { name: 'primary_details', label: t('primary_details'), icon_class: "icon-home" }
+ { name: 'users', label: t('users'), icon_class: "icon-user" }
+ { name: 'address', label: t('address'), icon_class: "icon-map-marker" }
+ { name: 'contact', label: t('contact'), icon_class: "icon-phone" }
+ { name: 'social', label: t('social'), icon_class: "icon-twitter" }
+ { name: 'about', label: t('about'), icon_class: "icon-pencil" }
+ { name: 'business_details', label: t('business_details'), icon_class: "icon-briefcase" }
+ { name: 'images', label: t('images'), icon_class: "icon-picture" }
+ { name: 'properties', label: t('properties'), icon_class: "icon-tags", show: "showProperties()" }
+ { name: 'shipping_methods', label: t('shipping_methods'), icon_class: "icon-truck", show: "showShippingMethods()" }
+ { name: 'payment_methods', label: t('payment_methods'), icon_class: "icon-money", show: "showPaymentMethods()" }
+ { name: 'enterprise_fees', label: t('enterprise_fees'), icon_class: "icon-tasks", show: "showEnterpriseFees()" }
+ { name: 'inventory_settings', label: t('inventory_settings'), icon_class: "icon-list-ol", show: "enterpriseIsShop()" }
+ { name: 'tag_rules', label: t('tag_rules'), icon_class: "icon-random", show: "enterpriseIsShop()" }
+ { name: 'shop_preferences', label: t('shop_preferences'), icon_class: "icon-shopping-cart", show: "enterpriseIsShop()" }
]
$scope.select(0)
diff --git a/app/assets/javascripts/admin/enterprises/enterprises.js.coffee b/app/assets/javascripts/admin/enterprises/enterprises.js.coffee
index 2074a1ea05..da122761b8 100644
--- a/app/assets/javascripts/admin/enterprises/enterprises.js.coffee
+++ b/app/assets/javascripts/admin/enterprises/enterprises.js.coffee
@@ -1 +1 @@
-angular.module("admin.enterprises", [
"admin.paymentMethods",
"admin.utils",
"admin.shippingMethods",
"admin.users",
"textAngular",
"admin.side_menu",
"admin.taxons",
'admin.indexUtils',
'admin.tagRules',
'admin.dropdown',
'pasvaz.bindonce',
'ngSanitize']
)
\ No newline at end of file
+angular.module("admin.enterprises", [
"admin.paymentMethods",
"admin.utils",
"admin.shippingMethods",
"admin.users",
"textAngular",
"admin.side_menu",
"admin.taxons",
'admin.indexUtils',
'admin.tagRules',
'admin.dropdown',
'ngSanitize']
)
\ No newline at end of file
diff --git a/app/assets/javascripts/admin/enterprises/services/enterprise_resource.js.coffee b/app/assets/javascripts/admin/enterprises/services/enterprise_resource.js.coffee
index 023f33d86c..b522f5be3b 100644
--- a/app/assets/javascripts/admin/enterprises/services/enterprise_resource.js.coffee
+++ b/app/assets/javascripts/admin/enterprises/services/enterprise_resource.js.coffee
@@ -1,4 +1,7 @@
angular.module("admin.enterprises").factory 'EnterpriseResource', ($resource) ->
+ ignoredAttrs = ->
+ ["$$hashKey", "producer", "package", "producerError", "packageError", "status"]
+
$resource('/admin/enterprises/:id/:action.json', {}, {
'index':
method: 'GET'
diff --git a/app/assets/javascripts/admin/enterprises/services/enterprises.js.coffee b/app/assets/javascripts/admin/enterprises/services/enterprises.js.coffee
index b159816709..80db943689 100644
--- a/app/assets/javascripts/admin/enterprises/services/enterprises.js.coffee
+++ b/app/assets/javascripts/admin/enterprises/services/enterprises.js.coffee
@@ -33,8 +33,11 @@ angular.module("admin.enterprises").factory 'Enterprises', ($q, EnterpriseResour
diff: (enterprise) ->
changed = []
for attr, value of enterprise when not angular.equals(value, @pristineByID[enterprise.id][attr])
- changed.push attr unless attr is "$$hashKey"
+ changed.push attr unless attr in @ignoredAttrs()
changed
+ ignoredAttrs: ->
+ ["$$hashKey", "producer", "package", "producerError", "packageError", "status"]
+
resetAttribute: (enterprise, attribute) ->
enterprise[attribute] = @pristineByID[enterprise.id][attribute]
diff --git a/app/assets/javascripts/admin/index_utils/directives/ofn-select2.js.coffee b/app/assets/javascripts/admin/index_utils/directives/ofn-select2.js.coffee
index 132480d987..bbb5221682 100644
--- a/app/assets/javascripts/admin/index_utils/directives/ofn-select2.js.coffee
+++ b/app/assets/javascripts/admin/index_utils/directives/ofn-select2.js.coffee
@@ -1,4 +1,4 @@
-angular.module("admin.indexUtils").directive "ofnSelect2", ($sanitize, $timeout) ->
+angular.module("admin.indexUtils").directive "ofnSelect2", ($sanitize, $timeout, $filter) ->
require: 'ngModel'
restrict: 'C'
scope:
@@ -6,15 +6,19 @@ angular.module("admin.indexUtils").directive "ofnSelect2", ($sanitize, $timeout)
minSearch: "@?"
text: "@?"
blank: "=?"
+ filter: "=?"
link: (scope, element, attrs, ngModel) ->
$timeout ->
scope.text ||= 'name'
+ scope.filter ||= -> true
scope.data.unshift(scope.blank) if scope.blank? && typeof scope.blank is "object"
item.name = $sanitize(item.name) for item in scope.data
element.select2
minimumResultsForSearch: scope.minSearch || 0
- data: { results: scope.data, text: scope.text }
+ data: ->
+ filtered = $filter('filter')(scope.data,scope.filter)
+ { results: filtered, text: scope.text }
formatSelection: (item) ->
item[scope.text]
formatResult: (item) ->
diff --git a/app/assets/javascripts/admin/index_utils/directives/panel_ctrl.js.coffee b/app/assets/javascripts/admin/index_utils/directives/panel_ctrl.js.coffee
new file mode 100644
index 0000000000..4259ce63e2
--- /dev/null
+++ b/app/assets/javascripts/admin/index_utils/directives/panel_ctrl.js.coffee
@@ -0,0 +1,21 @@
+angular.module("admin.indexUtils").directive "panelCtrl", (Panels) ->
+ restrict: "C"
+ scope:
+ object: "="
+ selected: "@?"
+ controller: ($scope, $element) ->
+ this.toggle = (name) ->
+ Panels.toggle($scope.object, name)
+
+ this.select = (selection) ->
+ $scope.$broadcast("selection:changed", selection)
+ $element.toggleClass("expanded", selection?)
+
+ this.registerSelectionListener = (callback) ->
+ $scope.$on "selection:changed", (event, selection) ->
+ callback(selection)
+
+ this
+
+ link: (scope, element, attrs, ctrl) ->
+ Panels.register(ctrl, scope.object, scope.selected)
diff --git a/app/assets/javascripts/admin/index_utils/directives/panel_row.js.coffee b/app/assets/javascripts/admin/index_utils/directives/panel_row.js.coffee
index eb5a4171f4..8b1e8d4b24 100644
--- a/app/assets/javascripts/admin/index_utils/directives/panel_row.js.coffee
+++ b/app/assets/javascripts/admin/index_utils/directives/panel_row.js.coffee
@@ -1,37 +1,24 @@
angular.module("admin.indexUtils").directive "panelRow", (Panels, Columns) ->
restrict: "C"
+ require: "^^panelCtrl"
templateUrl: "admin/panel.html"
scope:
object: "="
panels: "="
- link: (scope, element, attrs) ->
- scope.template = ""
- selected = null
- scope.columnCount = Columns.visibleCount
+ colspan: "=?"
+ locals: '@?'
+ link: (scope, element, attrs, ctrl) ->
+ scope.template = null
+ scope.columnCount = (scope.colspan || Columns.visibleCount)
+
+ if scope.locals
+ scope[local] = scope.$parent.$eval(local.trim()) for local in scope.locals.split(',')
scope.$on "columnCount:changed", (event, count) ->
scope.columnCount = count
- setTemplate = ->
- if selected?
- scope.template = 'admin/panels/' + scope.panels[selected] + '.html'
+ ctrl.registerSelectionListener (selection) ->
+ if selection?
+ scope.template = "admin/panels/#{scope.panels[selection]}.html"
else
- scope.template = ""
-
- scope.getSelected = ->
- selected
-
- scope.setSelected = (name) ->
- scope.$apply ->
- selected = name
- setTemplate()
-
- scope.open = (name) ->
- element.show 0, ->
- scope.setSelected name
-
- scope.close = ->
- element.hide 0, ->
- scope.setSelected null
-
- Panels.register(scope.object.id, scope)
+ scope.template = null
diff --git a/app/assets/javascripts/admin/index_utils/directives/panel_toggle.js.coffee b/app/assets/javascripts/admin/index_utils/directives/panel_toggle.js.coffee
index df81328905..cdd15d1d62 100644
--- a/app/assets/javascripts/admin/index_utils/directives/panel_toggle.js.coffee
+++ b/app/assets/javascripts/admin/index_utils/directives/panel_toggle.js.coffee
@@ -2,11 +2,13 @@ angular.module("admin.indexUtils").directive "panelToggle", ->
restrict: "C"
transclude: true
template: '
'
- require: "^panelToggleRow"
+ require: "^^panelCtrl"
scope:
name: "@"
link: (scope, element, attrs, ctrl) ->
- scope.selected = ctrl.register(scope.name, element)
-
element.on "click", ->
- scope.selected = ctrl.select(scope.name)
+ scope.$apply ->
+ ctrl.toggle(scope.name)
+
+ ctrl.registerSelectionListener (selection) ->
+ element.toggleClass('selected', selection == scope.name)
diff --git a/app/assets/javascripts/admin/index_utils/directives/panel_toggle_row.js.coffee b/app/assets/javascripts/admin/index_utils/directives/panel_toggle_row.js.coffee
deleted file mode 100644
index d2d9c90ff8..0000000000
--- a/app/assets/javascripts/admin/index_utils/directives/panel_toggle_row.js.coffee
+++ /dev/null
@@ -1,29 +0,0 @@
-angular.module("admin.indexUtils").directive "panelToggleRow", (Panels) ->
- restrict: "C"
- scope:
- object: "="
- selected: "@?"
- controller: ($scope) ->
- panelToggles = {}
-
- this.register = (name, element) ->
- panelToggles[name] = element
- panelToggles[name].addClass("selected") if $scope.selected == name
- $scope.selected == name
-
- this.select = (name) ->
- panelToggle.removeClass("selected") for panelName, panelToggle of panelToggles
-
- switch $scope.selected = Panels.toggle($scope.object.id, name)
- when null
- panelToggles[name].parent(".panel-toggle-row").removeClass("expanded")
- else
- panelToggles[$scope.selected].addClass("selected")
- panelToggles[$scope.selected].parent(".panel-toggle-row").addClass("expanded")
-
- $scope.selected == name
-
- this
- #
- # link: (scope, element, attrs) ->
- # Panels.registerInitialSelection(scope.object.id, scope.selected)
diff --git a/app/assets/javascripts/admin/index_utils/directives/show_more.js.coffee b/app/assets/javascripts/admin/index_utils/directives/show_more.js.coffee
new file mode 100644
index 0000000000..a6e4efa333
--- /dev/null
+++ b/app/assets/javascripts/admin/index_utils/directives/show_more.js.coffee
@@ -0,0 +1,12 @@
+angular.module("admin.indexUtils").component 'showMore',
+ templateUrl: 'admin/show_more.html'
+ bindings:
+ data: "="
+ limit: "="
+ increment: "="
+
+# For now, this component is not being used.
+# Something about binding "data" to a variable on the parent scope that is continually refreshed by
+# being assigned within an ng-repeat means that we get $digest iteration errors. Seems to be solved
+# by using the new "as" syntax for ng-repeat to assign and alias the outcome of the filters, but this
+# has the limitation of not being able to be limited AFTER the assignment has been made, which we need
diff --git a/app/assets/javascripts/admin/index_utils/directives/toggle_column.js.coffee b/app/assets/javascripts/admin/index_utils/directives/toggle_column.js.coffee
deleted file mode 100644
index 614b8d9346..0000000000
--- a/app/assets/javascripts/admin/index_utils/directives/toggle_column.js.coffee
+++ /dev/null
@@ -1,8 +0,0 @@
-angular.module("admin.indexUtils").directive "toggleColumn", (Columns) ->
- link: (scope, element, attrs) ->
- element.addClass "selected" if scope.column.visible
-
- element.click "click", ->
- scope.$apply ->
- Columns.toggleColumn(scope.column)
- element.toggleClass "selected"
diff --git a/app/assets/javascripts/admin/index_utils/index_utils.js.coffee b/app/assets/javascripts/admin/index_utils/index_utils.js.coffee
index 5e5b5cadf2..1ea74e614b 100644
--- a/app/assets/javascripts/admin/index_utils/index_utils.js.coffee
+++ b/app/assets/javascripts/admin/index_utils/index_utils.js.coffee
@@ -1 +1 @@
-angular.module("admin.indexUtils", ['ngResource', 'ngSanitize', 'templates']).config ($httpProvider) ->
$httpProvider.defaults.headers.common["X-CSRF-Token"] = $("meta[name=csrf-token]").attr("content");
$httpProvider.defaults.headers.common["Accept"] = "application/json, text/javascript, */*";
\ No newline at end of file
+angular.module("admin.indexUtils", ['ngResource', 'ngSanitize', 'templates', 'admin.utils']).config ($httpProvider) ->
$httpProvider.defaults.headers.common["X-CSRF-Token"] = $("meta[name=csrf-token]").attr("content");
$httpProvider.defaults.headers.common["Accept"] = "application/json, text/javascript, */*";
\ No newline at end of file
diff --git a/app/assets/javascripts/admin/index_utils/services/columns.js.coffee b/app/assets/javascripts/admin/index_utils/services/columns.js.coffee
index 8bd99bf2f2..fbc5149a3f 100644
--- a/app/assets/javascripts/admin/index_utils/services/columns.js.coffee
+++ b/app/assets/javascripts/admin/index_utils/services/columns.js.coffee
@@ -1,13 +1,21 @@
-angular.module("admin.indexUtils").factory 'Columns', ($rootScope) ->
+angular.module("admin.indexUtils").factory 'Columns', ($rootScope, $http, $injector) ->
new class Columns
+ savedColumns: {}
columns: {}
visibleCount: 0
- setColumns: (columns) =>
+ constructor: ->
@columns = {}
- @columns[name] = column for name, column of columns
+ for column in @injectColumns()
+ @columns[column.column_name] = column
+ @savedColumns[column.column_name] = angular.copy(column)
@calculateVisibleCount()
- @columns
+
+ injectColumns: ->
+ if $injector.has('columns')
+ $injector.get('columns')
+ else
+ []
toggleColumn: (column) =>
column.visible = !column.visible
@@ -16,3 +24,18 @@ angular.module("admin.indexUtils").factory 'Columns', ($rootScope) ->
calculateVisibleCount: =>
@visibleCount = (column for name, column of @columns when column.visible).length
$rootScope.$broadcast "columnCount:changed", @visibleCount
+
+ preferencesSaved: =>
+ angular.equals(@columns, @savedColumns)
+
+ savePreferences: (action_name) =>
+ $http
+ method: "PUT"
+ url: "/admin/column_preferences/bulk_update"
+ data:
+ action_name: action_name
+ column_preferences: (preference for column_name, preference of @columns)
+ .success (data) =>
+ for column in data
+ angular.extend(@columns[column.column_name], column)
+ angular.extend(@savedColumns[column.column_name], column)
diff --git a/app/assets/javascripts/admin/index_utils/services/panels.js.coffee b/app/assets/javascripts/admin/index_utils/services/panels.js.coffee
index 27852bed12..d020b88264 100644
--- a/app/assets/javascripts/admin/index_utils/services/panels.js.coffee
+++ b/app/assets/javascripts/admin/index_utils/services/panels.js.coffee
@@ -1,19 +1,22 @@
angular.module("admin.indexUtils").factory 'Panels', ->
new class Panels
- panels: {}
+ panels: []
- register: (id, scope) ->
- if id? && scope?
- @panels[id] = scope
+ register: (ctrl, object, selected=null) ->
+ if ctrl? && object?
+ @panels.push { ctrl: ctrl, object: object, selected: selected }
+ ctrl.select(selected) if selected?
- toggle: (id, name) ->
- scope = @panels[id]
- selected = scope.getSelected()
- switch selected
- when name
- scope.close()
- when null
- scope.open(name)
- else
- scope.setSelected(name)
- scope.getSelected()
+ toggle: (object, name, state=null) ->
+ panel = @findPanelByObject(object)
+ if panel.selected == name
+ @select(panel, null) unless state == "open"
+ else
+ @select(panel, name) unless state == "closed"
+
+ select: (panel, name) ->
+ panel.selected = name
+ panel.ctrl.select(name)
+
+ findPanelByObject: (object) ->
+ (panel for panel in @panels when panel.object == object)[0]
diff --git a/app/assets/javascripts/admin/index_utils/services/pending_changes.js.coffee b/app/assets/javascripts/admin/index_utils/services/pending_changes.js.coffee
index 2f40a7faef..15626b1479 100644
--- a/app/assets/javascripts/admin/index_utils/services/pending_changes.js.coffee
+++ b/app/assets/javascripts/admin/index_utils/services/pending_changes.js.coffee
@@ -1,10 +1,12 @@
-angular.module("admin.indexUtils").factory "pendingChanges", (resources) ->
+angular.module("admin.indexUtils").factory "pendingChanges", ($q, resources, StatusMessage) ->
new class pendingChanges
pendingChanges: {}
+ errors: []
add: (id, attr, change) =>
@pendingChanges["#{id}"] = {} unless @pendingChanges.hasOwnProperty("#{id}")
@pendingChanges["#{id}"]["#{attr}"] = change
+ StatusMessage.display('notice', "You have made #{@changeCount(@pendingChanges)} unsaved changes")
removeAll: =>
@pendingChanges = {}
@@ -14,11 +16,19 @@ angular.module("admin.indexUtils").factory "pendingChanges", (resources) ->
delete @pendingChanges["#{id}"]["#{attr}"]
delete @pendingChanges["#{id}"] if @changeCount( @pendingChanges["#{id}"] ) < 1
- submitAll: =>
+ submitAll: (form=null) =>
all = []
+ @errors = []
+ StatusMessage.display('progress', "Saving...")
for id, objectChanges of @pendingChanges
for attrName, change of objectChanges
all.push @submit(change)
+ $q.all(all).then =>
+ if @errors.length == 0
+ StatusMessage.display('success', "All changes saved successfully")
+ form.$setPristine() if form?
+ else
+ StatusMessage.display('failure', "Oh no! I was unable to save your changes")
all
submit: (change) ->
@@ -26,7 +36,8 @@ angular.module("admin.indexUtils").factory "pendingChanges", (resources) ->
@remove change.object.id, change.attr
change.scope.reset( data["#{change.attr}"] )
change.scope.success()
- , (error) ->
+ , (error) =>
+ @errors.push error
change.scope.error()
changeCount: (objectChanges) ->
diff --git a/app/assets/javascripts/admin/line_items/controllers/line_items_controller.js.coffee b/app/assets/javascripts/admin/line_items/controllers/line_items_controller.js.coffee
index f905d33bfe..f105ebb0e5 100644
--- a/app/assets/javascripts/admin/line_items/controllers/line_items_controller.js.coffee
+++ b/app/assets/javascripts/admin/line_items/controllers/line_items_controller.js.coffee
@@ -5,27 +5,14 @@ angular.module("admin.lineItems").controller 'LineItemsCtrl', ($scope, $timeout,
$scope.confirmDelete = true
$scope.startDate = formatDate daysFromToday -7
$scope.endDate = formatDate daysFromToday 1
- $scope.bulkActions = [ { name: t("bom_actions_delete"), callback: 'deleteLineItems' } ]
+ $scope.bulkActions = [ { name: t("admin.orders.bulk_management.actions_delete"), callback: 'deleteLineItems' } ]
$scope.selectedUnitsProduct = {}
$scope.selectedUnitsVariant = {}
$scope.sharedResource = false
- $scope.columns = Columns.setColumns
- order_no: { name: t("bom_no"), visible: false }
- full_name: { name: t("name"), visible: true }
- email: { name: t("email"), visible: false }
- phone: { name: t("phone"), visible: false }
- order_date: { name: t("bom_date"), visible: true }
- producer: { name: t("producer"), visible: true }
- order_cycle: { name: t("bom_cycle"), visible: false }
- hub: { name: t("bom_hub"), visible: false }
- variant: { name: t("bom_variant"), visible: true }
- quantity: { name: t("bom_quantity"), visible: true }
- max: { name: t("bom_max"), visible: true }
- final_weight_volume: { name: t("bom_final_weigth_volume"), visible: false }
- price: { name: t("price"), visible: false }
+ $scope.columns = Columns.columns
$scope.confirmRefresh = ->
- LineItems.allSaved() || confirm(t "unsaved_changes_warning")
+ LineItems.allSaved() || confirm(t("unsaved_changes_warning"))
$scope.resetSelectFilters = ->
$scope.distributorFilter = blankOption().id
diff --git a/app/assets/javascripts/admin/order_cycles/controllers/create.js.coffee b/app/assets/javascripts/admin/order_cycles/controllers/create.js.coffee
index 2c98d60f0e..3f28b27f9f 100644
--- a/app/assets/javascripts/admin/order_cycles/controllers/create.js.coffee
+++ b/app/assets/javascripts/admin/order_cycles/controllers/create.js.coffee
@@ -12,7 +12,7 @@ angular.module('admin.orderCycles')
$scope.StatusMessage = StatusMessage
$scope.loaded = ->
- Enterprise.loaded && EnterpriseFee.loaded
+ Enterprise.loaded && EnterpriseFee.loaded && OrderCycle.loaded
$scope.suppliedVariants = (enterprise_id) ->
Enterprise.suppliedVariants(enterprise_id)
@@ -41,10 +41,6 @@ angular.module('admin.orderCycles')
$scope.enterprisesWithFees = ->
$scope.enterprises[id] for id in OrderCycle.participatingEnterpriseIds() when $scope.enterpriseFeesForEnterprise(id).length > 0
- $scope.toggleProducts = ($event, exchange) ->
- $event.preventDefault()
- OrderCycle.toggleProducts(exchange)
-
$scope.enterpriseFeesForEnterprise = (enterprise_id) ->
EnterpriseFee.forEnterprise(parseInt(enterprise_id))
@@ -79,5 +75,6 @@ angular.module('admin.orderCycles')
$scope.removeDistributionOfVariant = (variant_id) ->
OrderCycle.removeDistributionOfVariant(variant_id)
- $scope.submit = (destination) ->
+ $scope.submit = ($event, destination) ->
+ $event.preventDefault()
OrderCycle.create(destination)
diff --git a/app/assets/javascripts/admin/order_cycles/controllers/edit.js.coffee b/app/assets/javascripts/admin/order_cycles/controllers/edit.js.coffee
index 227c6a3045..c0818acafa 100644
--- a/app/assets/javascripts/admin/order_cycles/controllers/edit.js.coffee
+++ b/app/assets/javascripts/admin/order_cycles/controllers/edit.js.coffee
@@ -45,10 +45,6 @@ angular.module('admin.orderCycles')
$scope.enterprisesWithFees = ->
$scope.enterprises[id] for id in OrderCycle.participatingEnterpriseIds() when $scope.enterpriseFeesForEnterprise(id).length > 0
- $scope.toggleProducts = ($event, exchange) ->
- $event.preventDefault()
- OrderCycle.toggleProducts(exchange)
-
$scope.enterpriseFeesForEnterprise = (enterprise_id) ->
EnterpriseFee.forEnterprise(parseInt(enterprise_id))
@@ -85,9 +81,13 @@ angular.module('admin.orderCycles')
OrderCycle.removeDistributionOfVariant(variant_id)
$scope.submit = (destination) ->
+ $event.preventDefault()
StatusMessage.display 'progress', "Saving..."
- OrderCycle.update(destination)
- $scope.order_cycle_form.$setPristine()
+
+ $scope.submit = ($event, destination) ->
+ $event.preventDefault()
+ StatusMessage.display 'progress', "Saving..."
+ OrderCycle.update(destination, $scope.order_cycle_form)
$scope.cancel = (destination) ->
$window.location = destination
diff --git a/app/assets/javascripts/admin/order_cycles/controllers/simple_create.js.coffee b/app/assets/javascripts/admin/order_cycles/controllers/simple_create.js.coffee
index 53f00cdc36..dfa8011167 100644
--- a/app/assets/javascripts/admin/order_cycles/controllers/simple_create.js.coffee
+++ b/app/assets/javascripts/admin/order_cycles/controllers/simple_create.js.coffee
@@ -20,7 +20,7 @@ angular.module('admin.orderCycles').controller "AdminSimpleCreateOrderCycleCtrl"
OrderCycle.order_cycle.coordinator_id = enterprise.id
$scope.loaded = ->
- Enterprise.loaded && EnterpriseFee.loaded
+ Enterprise.loaded && EnterpriseFee.loaded && OrderCycle.loaded
$scope.removeDistributionOfVariant = angular.noop
@@ -41,6 +41,7 @@ angular.module('admin.orderCycles').controller "AdminSimpleCreateOrderCycleCtrl"
$scope.enterpriseFeesForEnterprise = (enterprise_id) ->
EnterpriseFee.forEnterprise(parseInt(enterprise_id))
- $scope.submit = (destination) ->
+ $scope.submit = ($event, destination) ->
+ $event.preventDefault()
OrderCycle.mirrorIncomingToOutgoingProducts()
OrderCycle.create(destination)
diff --git a/app/assets/javascripts/admin/order_cycles/controllers/simple_edit.js.coffee b/app/assets/javascripts/admin/order_cycles/controllers/simple_edit.js.coffee
index 99c2e2649c..f5bae5a4d8 100644
--- a/app/assets/javascripts/admin/order_cycles/controllers/simple_edit.js.coffee
+++ b/app/assets/javascripts/admin/order_cycles/controllers/simple_edit.js.coffee
@@ -37,7 +37,8 @@ angular.module('admin.orderCycles').controller "AdminSimpleEditOrderCycleCtrl",
$event.preventDefault()
OrderCycle.removeCoordinatorFee(index)
- $scope.submit = (destination) ->
+ $scope.submit = ($event, destination) ->
+ $event.preventDefault()
StatusMessage.display 'progress', "Saving..."
OrderCycle.mirrorIncomingToOutgoingProducts()
- OrderCycle.update(destination)
+ OrderCycle.update(destination, $scope.order_cycle_form)
diff --git a/app/assets/javascripts/admin/order_cycles/order_cycles.js.coffee b/app/assets/javascripts/admin/order_cycles/order_cycles.js.coffee
index a75fdad58c..6ea3f76984 100644
--- a/app/assets/javascripts/admin/order_cycles/order_cycles.js.coffee
+++ b/app/assets/javascripts/admin/order_cycles/order_cycles.js.coffee
@@ -1,4 +1,4 @@
-angular.module('admin.orderCycles', ['ngResource', 'admin.utils', 'admin.indexUtils'])
+angular.module('admin.orderCycles', ['ngResource', 'admin.utils', 'admin.indexUtils', 'ngTagsInput'])
.config ($httpProvider) ->
$httpProvider.defaults.headers.common['X-CSRF-Token'] = $('meta[name=csrf-token]').attr('content')
diff --git a/app/assets/javascripts/admin/order_cycles/services/order_cycle.js.coffee b/app/assets/javascripts/admin/order_cycles/services/order_cycle.js.coffee
index 9ca5f1028d..65f0f67b71 100644
--- a/app/assets/javascripts/admin/order_cycles/services/order_cycle.js.coffee
+++ b/app/assets/javascripts/admin/order_cycles/services/order_cycle.js.coffee
@@ -1,4 +1,4 @@
-angular.module('admin.orderCycles').factory 'OrderCycle', ($resource, $window, StatusMessage) ->
+angular.module('admin.orderCycles').factory 'OrderCycle', ($resource, $window, StatusMessage, Panels) ->
OrderCycleResource = $resource '/admin/order_cycles/:action_name/:order_cycle_id.json', {}, {
'index': { method: 'GET', isArray: true}
'new' : { method: 'GET', params: { action_name: "new" } }
@@ -30,17 +30,19 @@ angular.module('admin.orderCycles').factory 'OrderCycle', ($resource, $window, S
exchangeDirection: (exchange) ->
if this.order_cycle.incoming_exchanges.indexOf(exchange) == -1 then 'outgoing' else 'incoming'
- toggleProducts: (exchange) ->
- exchange.showProducts = !exchange.showProducts
-
toggleAllProducts: (direction) ->
this.showProducts[direction] = !this.showProducts[direction]
- exchange.showProducts = this.showProducts[direction] for exchange in this.exchangesByDirection(direction)
+ state = if this.showProducts[direction] then "open" else "closed"
+ exchanges = this.exchangesByDirection(direction)
+ Panels.toggle(exchange,'products',state) for exchange in exchanges
setExchangeVariants: (exchange, variants, selected) ->
direction = if exchange.incoming then "incoming" else "outgoing"
editable = @order_cycle["editable_variants_for_#{direction}_exchanges"][exchange.enterprise_id] || []
- exchange.variants[variant] = selected for variant in variants when variant in editable
+ for variant in variants when variant in editable
+ exchange.variants[variant] = selected
+ @removeDistributionOfVariant(variant.id) if exchange.incoming
+
addSupplier: (new_supplier_id) ->
this.order_cycle.incoming_exchanges.push({enterprise_id: new_supplier_id, incoming: true, active: true, variants: {}, enterprise_fees: []})
@@ -154,11 +156,12 @@ angular.module('admin.orderCycles').factory 'OrderCycle', ($resource, $window, S
else
console.log('Failed to create order cycle')
- update: (destination) ->
+ update: (destination, form) ->
return unless @confirmNoDistributors()
oc = new OrderCycleResource({order_cycle: this.dataForSubmit()})
oc.$update {order_cycle_id: this.order_cycle.id, reloading: (if destination? then 1 else 0)}, (data) =>
if data['success']
+ form.$setPristine() if form
if destination?
$window.location = destination
else
diff --git a/app/assets/javascripts/admin/orders/controllers/orders_controller.js.coffee b/app/assets/javascripts/admin/orders/controllers/orders_controller.js.coffee
index 6ad7f0bfb9..060b23bed9 100644
--- a/app/assets/javascripts/admin/orders/controllers/orders_controller.js.coffee
+++ b/app/assets/javascripts/admin/orders/controllers/orders_controller.js.coffee
@@ -2,13 +2,11 @@ angular.module("admin.orders").controller "ordersCtrl", ($scope, $compile, $attr
$scope.$compile = $compile
$scope.shops = shops
$scope.orderCycles = orderCycles
- for oc in $scope.orderCycles
- oc.name_and_status = "#{oc.name} (#{oc.status})"
- $scope.distributor_id = $attrs.ofnDistributorId
- $scope.order_cycle_id = $attrs.ofnOrderCycleId
+ $scope.distributor_id = parseInt($attrs.ofnDistributorId)
+ $scope.order_cycle_id = parseInt($attrs.ofnOrderCycleId)
- $scope.validOrderCycle = (oc, index, array) ->
+ $scope.validOrderCycle = (oc) ->
$scope.orderCycleHasDistributor oc, parseInt($scope.distributor_id)
$scope.distributorHasOrderCycles = (distributor) ->
@@ -20,3 +18,9 @@ angular.module("admin.orders").controller "ordersCtrl", ($scope, $compile, $attr
$scope.distributionChosen = ->
$scope.distributor_id && $scope.order_cycle_id
+
+ for oc in $scope.orderCycles
+ oc.name_and_status = "#{oc.name} (#{oc.status})"
+
+ for shop in $scope.shops
+ shop.disabled = !$scope.distributorHasOrderCycles(shop)
diff --git a/app/assets/javascripts/admin/orders/orders.js.coffee b/app/assets/javascripts/admin/orders/orders.js.coffee
index abfe576095..4e1ec754e3 100644
--- a/app/assets/javascripts/admin/orders/orders.js.coffee
+++ b/app/assets/javascripts/admin/orders/orders.js.coffee
@@ -1 +1 @@
-angular.module("admin.orders", ['ngResource'])
+angular.module("admin.orders", ['admin.indexUtils', 'ngResource'])
diff --git a/app/assets/javascripts/admin/payment_methods/controllers/payment_method_controller.js.coffee b/app/assets/javascripts/admin/payment_methods/controllers/payment_method_controller.js.coffee
index c2595faa6d..6e26b39d17 100644
--- a/app/assets/javascripts/admin/payment_methods/controllers/payment_method_controller.js.coffee
+++ b/app/assets/javascripts/admin/payment_methods/controllers/payment_method_controller.js.coffee
@@ -1,4 +1,2 @@
-angular.module("admin.paymentMethods")
- .controller "paymentMethodCtrl", ($scope, PaymentMethods) ->
- $scope.findPaymentMethodByID = (id) ->
- $scope.PaymentMethod = PaymentMethods.findByID(id)
+angular.module("admin.paymentMethods").controller "paymentMethodCtrl", ($scope, paymentMethod) ->
+ $scope.paymentMethod = paymentMethod
diff --git a/app/assets/javascripts/admin/payment_methods/controllers/payment_methods_controller.js.coffee b/app/assets/javascripts/admin/payment_methods/controllers/payment_methods_controller.js.coffee
new file mode 100644
index 0000000000..ddad6cd259
--- /dev/null
+++ b/app/assets/javascripts/admin/payment_methods/controllers/payment_methods_controller.js.coffee
@@ -0,0 +1,3 @@
+angular.module("admin.paymentMethods").controller "paymentMethodsCtrl", ($scope, PaymentMethods) ->
+ $scope.findPaymentMethodByID = (id) ->
+ $scope.PaymentMethod = PaymentMethods.findByID(id)
diff --git a/app/assets/javascripts/admin/controllers/providers_controller.js.coffee b/app/assets/javascripts/admin/payment_methods/controllers/providers_controller.js.coffee
similarity index 61%
rename from app/assets/javascripts/admin/controllers/providers_controller.js.coffee
rename to app/assets/javascripts/admin/payment_methods/controllers/providers_controller.js.coffee
index 3b3378d013..6b9669c9e7 100644
--- a/app/assets/javascripts/admin/controllers/providers_controller.js.coffee
+++ b/app/assets/javascripts/admin/payment_methods/controllers/providers_controller.js.coffee
@@ -1,7 +1,7 @@
-angular.module("ofn.admin").controller "ProvidersCtrl", ($scope, paymentMethod) ->
+angular.module("admin.paymentMethods").controller "ProvidersCtrl", ($scope, paymentMethod) ->
if paymentMethod.type
$scope.include_html = "/admin/payment_methods/show_provider_preferences?" +
"provider_type=#{paymentMethod.type};" +
"pm_id=#{paymentMethod.id};"
else
- $scope.include_html = ""
\ No newline at end of file
+ $scope.include_html = ""
diff --git a/app/assets/javascripts/admin/directives/provider_prefs_for.js.coffee b/app/assets/javascripts/admin/payment_methods/directives/provider_prefs_for.js.coffee
similarity index 64%
rename from app/assets/javascripts/admin/directives/provider_prefs_for.js.coffee
rename to app/assets/javascripts/admin/payment_methods/directives/provider_prefs_for.js.coffee
index 467bad4e5f..2a0fc50f6f 100644
--- a/app/assets/javascripts/admin/directives/provider_prefs_for.js.coffee
+++ b/app/assets/javascripts/admin/payment_methods/directives/provider_prefs_for.js.coffee
@@ -1,7 +1,7 @@
-angular.module("ofn.admin").directive "providerPrefsFor", ($http) ->
+angular.module("admin.paymentMethods").directive "providerPrefsFor", ($http) ->
link: (scope, element, attrs) ->
element.on "change blur load", ->
scope.$apply ->
scope.include_html = "/admin/payment_methods/show_provider_preferences?" +
"provider_type=#{element.val()};" +
- "pm_id=#{attrs.providerPrefsFor};"
\ No newline at end of file
+ "pm_id=#{attrs.providerPrefsFor};"
diff --git a/app/assets/javascripts/admin/payment_methods/payment_methods.js.coffee b/app/assets/javascripts/admin/payment_methods/payment_methods.js.coffee
index 01553647d4..fb4fec1ff9 100644
--- a/app/assets/javascripts/admin/payment_methods/payment_methods.js.coffee
+++ b/app/assets/javascripts/admin/payment_methods/payment_methods.js.coffee
@@ -1 +1 @@
-angular.module("admin.paymentMethods", [])
+angular.module("admin.paymentMethods", ['ngTagsInput', 'admin.utils', 'templates'])
diff --git a/app/assets/javascripts/admin/shipping_methods/controllers/shipping_method_controller.js.coffee b/app/assets/javascripts/admin/shipping_methods/controllers/shipping_method_controller.js.coffee
index cc7bd4ee3e..67c53d4b66 100644
--- a/app/assets/javascripts/admin/shipping_methods/controllers/shipping_method_controller.js.coffee
+++ b/app/assets/javascripts/admin/shipping_methods/controllers/shipping_method_controller.js.coffee
@@ -1,2 +1,10 @@
-angular.module("admin.shippingMethods").controller "shippingMethodCtrl", ($scope, shippingMethod) ->
+angular.module("admin.shippingMethods").controller "shippingMethodCtrl", ($scope, shippingMethod, TagRuleResource, $q) ->
$scope.shippingMethod = shippingMethod
+
+ $scope.findTags = (query) ->
+ defer = $q.defer()
+ TagRuleResource.mapByTag (data) =>
+ filtered = data.filter (tag) ->
+ tag.text.toLowerCase().indexOf(query.toLowerCase()) != -1
+ defer.resolve filtered
+ defer.promise
diff --git a/app/assets/javascripts/admin/shipping_methods/controllers/shipping_methods_controller.js.coffee b/app/assets/javascripts/admin/shipping_methods/controllers/shipping_methods_controller.js.coffee
index 91569b2256..9efd2a5fea 100644
--- a/app/assets/javascripts/admin/shipping_methods/controllers/shipping_methods_controller.js.coffee
+++ b/app/assets/javascripts/admin/shipping_methods/controllers/shipping_methods_controller.js.coffee
@@ -1,4 +1,3 @@
-angular.module("admin.shippingMethods")
- .controller "shippingMethodsCtrl", ($scope, ShippingMethods) ->
- $scope.findShippingMethodByID = (id) ->
- $scope.ShippingMethod = ShippingMethods.findByID(id)
+angular.module("admin.shippingMethods").controller "shippingMethodsCtrl", ($scope, ShippingMethods) ->
+ $scope.findShippingMethodByID = (id) ->
+ $scope.ShippingMethod = ShippingMethods.findByID(id)
diff --git a/app/assets/javascripts/admin/shipping_methods/shipping_methods.js.coffee b/app/assets/javascripts/admin/shipping_methods/shipping_methods.js.coffee
index 863163a9ef..46537b303e 100644
--- a/app/assets/javascripts/admin/shipping_methods/shipping_methods.js.coffee
+++ b/app/assets/javascripts/admin/shipping_methods/shipping_methods.js.coffee
@@ -1 +1 @@
-angular.module("admin.shippingMethods", ["ngTagsInput", 'admin.utils', 'templates'])
+angular.module("admin.shippingMethods", ["ngTagsInput", 'admin.utils', 'admin.tagRules', 'templates'])
diff --git a/app/assets/javascripts/admin/tag_rules/controllers/tag_rules_controller.js.coffee b/app/assets/javascripts/admin/tag_rules/controllers/tag_rules_controller.js.coffee
index 7209147b66..45f98a2b57 100644
--- a/app/assets/javascripts/admin/tag_rules/controllers/tag_rules_controller.js.coffee
+++ b/app/assets/javascripts/admin/tag_rules/controllers/tag_rules_controller.js.coffee
@@ -1,15 +1,16 @@
-angular.module("admin.tagRules").controller "TagRulesCtrl", ($scope, $http, enterprise) ->
+angular.module("admin.tagRules").controller "TagRulesCtrl", ($scope, $http, $filter, enterprise) ->
$scope.tagGroups = enterprise.tag_groups
+ $scope.defaultTagGroup = enterprise.default_tag_group
$scope.visibilityOptions = [ { id: "visible", name: "VISIBLE" }, { id: "hidden", name: "NOT VISIBLE" } ]
- updateRuleCounts = ->
- index = 0
- for tagGroup in $scope.tagGroups
+ $scope.updateRuleCounts = ->
+ index = $scope.defaultTagGroup.rules.length
+ for tagGroup in $filter('orderBy')($scope.tagGroups, 'position')
tagGroup.startIndex = index
index = index + tagGroup.rules.length
- updateRuleCounts()
+ $scope.updateRuleCounts()
$scope.updateTagsRulesFor = (tagGroup) ->
for tagRule in tagGroup.rules
@@ -18,6 +19,7 @@ angular.module("admin.tagRules").controller "TagRulesCtrl", ($scope, $http, ente
$scope.addNewRuleTo = (tagGroup, ruleType) ->
newRule =
id: null
+ is_default: tagGroup == $scope.defaultTagGroup
preferred_customer_tags: (tag.text for tag in tagGroup.tags).join(",")
type: "TagRule::#{ruleType}"
switch ruleType
@@ -26,18 +28,27 @@ angular.module("admin.tagRules").controller "TagRulesCtrl", ($scope, $http, ente
when "FilterShippingMethods"
newRule.peferred_shipping_method_tags = []
newRule.preferred_matched_shipping_methods_visibility = "visible"
+ when "FilterPaymentMethods"
+ newRule.peferred_payment_method_tags = []
+ newRule.preferred_matched_payment_methods_visibility = "visible"
+ when "FilterProducts"
+ newRule.peferred_variant_tags = []
+ newRule.preferred_matched_variants_visibility = "visible"
+ when "FilterOrderCycles"
+ newRule.peferred_exchange_tags = []
+ newRule.preferred_matched_order_cycles_visibility = "visible"
tagGroup.rules.push(newRule)
- updateRuleCounts()
+ $scope.updateRuleCounts()
$scope.addNewTag = ->
- $scope.tagGroups.push { tags: [], rules: [] }
+ $scope.tagGroups.push { tags: [], rules: [], position: $scope.tagGroups.length + 1 }
$scope.deleteTagRule = (tagGroup, tagRule) ->
index = tagGroup.rules.indexOf(tagRule)
return unless index >= 0
if tagRule.id is null
tagGroup.rules.splice(index, 1)
- updateRuleCounts()
+ $scope.updateRuleCounts()
else
if confirm("Are you sure?")
$http
@@ -45,4 +56,4 @@ angular.module("admin.tagRules").controller "TagRulesCtrl", ($scope, $http, ente
url: "/admin/enterprises/#{enterprise.id}/tag_rules/#{tagRule.id}.json"
.success ->
tagGroup.rules.splice(index, 1)
- updateRuleCounts()
+ $scope.updateRuleCounts()
diff --git a/app/assets/javascripts/admin/tag_rules/directives/new_rule_dialog.js.coffee b/app/assets/javascripts/admin/tag_rules/directives/new_rule_dialog.js.coffee
index 54e33006e4..1d3cb02f7a 100644
--- a/app/assets/javascripts/admin/tag_rules/directives/new_rule_dialog.js.coffee
+++ b/app/assets/javascripts/admin/tag_rules/directives/new_rule_dialog.js.coffee
@@ -1,28 +1,24 @@
-angular.module("admin.tagRules").directive 'newTagRuleDialog', ($compile, $templateCache, $window) ->
+angular.module("admin.tagRules").directive 'newTagRuleDialog', ($compile, $templateCache, DialogDefaults) ->
restrict: 'A'
- scope: true
+ scope:
+ tagGroup: '='
+ addNewRuleTo: '='
link: (scope, element, attr) ->
# Compile modal template
template = $compile($templateCache.get('admin/new_tag_rule_dialog.html'))(scope)
scope.ruleTypes = [
# { id: "DiscountOrder", name: 'Apply a discount to orders' }
- { id: "FilterShippingMethods", name: 'Show/Hide shipping methods' }
+ { id: "FilterProducts", name: 'Show or Hide variants in my shopfront' }
+ { id: "FilterShippingMethods", name: 'Show or Hide shipping methods at checkout' }
+ { id: "FilterPaymentMethods", name: 'Show or Hide payment methods at checkout' }
+ { id: "FilterOrderCycles", name: 'Show or Hide order cycles in my shopfront' }
]
- scope.ruleType = "DiscountOrder"
+ scope.ruleType = scope.ruleTypes[0].id
# Set Dialog options
- template.dialog
- show: { effect: "fade", duration: 400 }
- hide: { effect: "fade", duration: 300 }
- autoOpen: false
- resizable: false
- width: $window.innerWidth * 0.4;
- modal: true
- open: (event, ui) ->
- $('.ui-widget-overlay').bind 'click', ->
- $(this).siblings('.ui-dialog').find('.ui-dialog-content').dialog('close')
+ template.dialog(DialogDefaults)
# Link opening of dialog to click event on element
element.bind 'click', (e) ->
diff --git a/app/assets/javascripts/admin/tag_rules/directives/tag_rules/discount_order.js.coffee b/app/assets/javascripts/admin/tag_rules/directives/tag_rules/discount_order.js.coffee
deleted file mode 100644
index b374f88782..0000000000
--- a/app/assets/javascripts/admin/tag_rules/directives/tag_rules/discount_order.js.coffee
+++ /dev/null
@@ -1,4 +0,0 @@
-angular.module("admin.tagRules").directive "discountOrder", ->
- restrict: "E"
- replace: true
- templateUrl: "admin/tag_rules/discount_order.html"
diff --git a/app/assets/javascripts/admin/tag_rules/directives/tag_rules/filter_shipping_methods.js.coffee b/app/assets/javascripts/admin/tag_rules/directives/tag_rules/filter_shipping_methods.js.coffee
deleted file mode 100644
index 1a75cf8ff2..0000000000
--- a/app/assets/javascripts/admin/tag_rules/directives/tag_rules/filter_shipping_methods.js.coffee
+++ /dev/null
@@ -1,4 +0,0 @@
-angular.module("admin.tagRules").directive "filterShippingMethods", ->
- restrict: "E"
- replace: true
- templateUrl: "admin/tag_rules/filter_shipping_methods.html"
diff --git a/app/assets/javascripts/admin/tag_rules/directives/tag_rules/tag_rule.js.coffee b/app/assets/javascripts/admin/tag_rules/directives/tag_rules/tag_rule.js.coffee
new file mode 100644
index 0000000000..11c7ed014b
--- /dev/null
+++ b/app/assets/javascripts/admin/tag_rules/directives/tag_rules/tag_rule.js.coffee
@@ -0,0 +1,41 @@
+angular.module("admin.tagRules").directive "tagRule", ->
+ restrict: "C"
+ templateUrl: "admin/tag_rules/tag_rule.html"
+ link: (scope, element, attrs) ->
+ scope.opt =
+ "TagRule::FilterShippingMethods":
+ textTop: "Shipping methods tagged"
+ textBottom: "are:"
+ taggable: "shipping_method"
+ tagsAttr: "shipping_method_tags"
+ tagListAttr: "preferred_shipping_method_tags"
+ inputTemplate: "admin/tag_rules/filter_shipping_methods_input.html"
+ tagListFor: (rule) ->
+ rule.preferred_shipping_method_tags
+ "TagRule::FilterPaymentMethods":
+ textTop: "Payment methods tagged"
+ textBottom: "are:"
+ taggable: "payment_method"
+ tagsAttr: "payment_method_tags"
+ tagListAttr: "preferred_payment_method_tags"
+ inputTemplate: "admin/tag_rules/filter_payment_methods_input.html"
+ tagListFor: (rule) ->
+ rule.preferred_payment_method_tags
+ "TagRule::FilterOrderCycles":
+ textTop: "Order Cycles tagged"
+ textBottom: "are:"
+ taggable: "exchange"
+ tagsAttr: "exchange_tags"
+ tagListAttr: "preferred_exchange_tags"
+ inputTemplate: "admin/tag_rules/filter_order_cycles_input.html"
+ tagListFor: (rule) ->
+ rule.preferred_exchange_tags
+ "TagRule::FilterProducts":
+ textTop: "Inventory variants tagged"
+ textBottom: "are:"
+ taggable: "variant"
+ tagsAttr: "variant_tags"
+ tagListAttr: "preferred_variant_tags"
+ inputTemplate: "admin/tag_rules/filter_products_input.html"
+ tagListFor: (rule) ->
+ rule.preferred_variant_tags
diff --git a/app/assets/javascripts/admin/tag_rules/services/tag_rule_resource.js.coffee b/app/assets/javascripts/admin/tag_rules/services/tag_rule_resource.js.coffee
new file mode 100644
index 0000000000..d4a6c45ba4
--- /dev/null
+++ b/app/assets/javascripts/admin/tag_rules/services/tag_rule_resource.js.coffee
@@ -0,0 +1,10 @@
+angular.module("admin.tagRules").factory 'TagRuleResource', ($resource) ->
+ $resource('/admin/tag_rules/:action.json', {}, {
+ 'mapByTag':
+ method: 'GET'
+ isArray: true
+ cache: true
+ params:
+ action: 'map_by_tag'
+ enterprise_id: '@enterprise_id'
+ })
diff --git a/app/assets/javascripts/admin/tag_rules/tag_rules.js.coffee b/app/assets/javascripts/admin/tag_rules/tag_rules.js.coffee
index 88c7734c33..d9a4b2bbfd 100644
--- a/app/assets/javascripts/admin/tag_rules/tag_rules.js.coffee
+++ b/app/assets/javascripts/admin/tag_rules/tag_rules.js.coffee
@@ -1 +1 @@
-angular.module("admin.tagRules", ['ngTagsInput'])
+angular.module("admin.tagRules", ['ngResource', 'ngTagsInput'])
diff --git a/app/assets/javascripts/admin/utils/directives/help-modal.js.coffee b/app/assets/javascripts/admin/utils/directives/help-modal.js.coffee
new file mode 100644
index 0000000000..b458f85179
--- /dev/null
+++ b/app/assets/javascripts/admin/utils/directives/help-modal.js.coffee
@@ -0,0 +1,17 @@
+angular.module("admin.utils").directive 'helpModal', ($compile, $templateCache, $window, DialogDefaults) ->
+ restrict: 'C'
+ scope:
+ template: '@'
+ link: (scope, element, attr) ->
+ # Compile modal template
+ template = $compile($templateCache.get(scope.template))(scope)
+
+ # Load Dialog Options
+ template.dialog(DialogDefaults)
+
+ # Link opening of dialog to click event on element
+ element.bind 'click', (e) -> template.dialog('open')
+
+ scope.close = ->
+ template.dialog('close')
+ return
diff --git a/app/assets/javascripts/admin/utils/directives/save_bar.js.coffee b/app/assets/javascripts/admin/utils/directives/save_bar.js.coffee
index 0999679394..8bc6c98573 100644
--- a/app/assets/javascripts/admin/utils/directives/save_bar.js.coffee
+++ b/app/assets/javascripts/admin/utils/directives/save_bar.js.coffee
@@ -1,8 +1,9 @@
angular.module("admin.utils").directive "saveBar", (StatusMessage) ->
restrict: "E"
+ transclude: true
scope:
- form: "="
- buttons: "="
+ dirty: "="
+ persist: "=?"
templateUrl: "admin/save_bar.html"
link: (scope, element, attrs) ->
scope.StatusMessage = StatusMessage
diff --git a/app/assets/javascripts/admin/utils/directives/sortable.js.coffee b/app/assets/javascripts/admin/utils/directives/sortable.js.coffee
new file mode 100644
index 0000000000..2f2f100e31
--- /dev/null
+++ b/app/assets/javascripts/admin/utils/directives/sortable.js.coffee
@@ -0,0 +1,36 @@
+angular.module("admin.utils").directive "ofnSortable", ($timeout, $parse) ->
+ restrict: "E"
+ scope:
+ items: '@'
+ position: '@'
+ afterSort: '&'
+ handle: "@"
+ axis: "@"
+ link: (scope, element, attrs) ->
+ $timeout ->
+ scope.axis ||= "y"
+ scope.handle ||= ".handle"
+ getScopePos = $parse(scope.position)
+ setScopePos = getScopePos.assign
+
+ element.sortable
+ handle: scope.handle
+ helper: 'clone'
+ axis: scope.axis
+ items: scope.items
+ appendTo: element
+ update: (event, ui) ->
+ sortableSiblings = ($(ss) for ss in ui.item.siblings(scope.items))
+ offset = Math.min(ui.item.index(), sortableSiblings[0].index())
+ newPos = ui.item.index() - offset + 1
+ oldPos = getScopePos(ui.item.scope())
+ if newPos < oldPos
+ for sibScope in sortableSiblings.map((ss) -> ss.scope())
+ pos = getScopePos(sibScope)
+ setScopePos(sibScope, pos + 1) if pos >= newPos && pos < oldPos
+ else if newPos > oldPos
+ for sibScope in sortableSiblings.map((ss) -> ss.scope())
+ pos = getScopePos(sibScope)
+ setScopePos(sibScope, pos - 1) if pos > oldPos && pos <= newPos
+ setScopePos(ui.item.scope(), newPos)
+ scope.afterSort()
diff --git a/app/assets/javascripts/admin/utils/directives/tags_with_translation.js.coffee b/app/assets/javascripts/admin/utils/directives/tags_with_translation.js.coffee
index 7a6ae9afff..9722ab26af 100644
--- a/app/assets/javascripts/admin/utils/directives/tags_with_translation.js.coffee
+++ b/app/assets/javascripts/admin/utils/directives/tags_with_translation.js.coffee
@@ -6,11 +6,32 @@ angular.module("admin.utils").directive "tagsWithTranslation", ($timeout) ->
tagsAttr: "@?"
tagListAttr: "@?"
findTags: "&"
+ form: '=?'
+ onTagAdded: "&"
+ onTagRemoved: "&"
+ max: "="
link: (scope, element, attrs) ->
+ scope.findTags = undefined unless attrs.hasOwnProperty("findTags")
+ scope.limitReached = false
+
+ compileTagList = ->
+ scope.limitReached = scope.object[scope.tagsAttr].length >= scope.max if scope.max != undefined
+ scope.object[scope.tagListAttr] = (tag.text for tag in scope.object[scope.tagsAttr]).join(",")
+
$timeout ->
+ # Initialize properties if necessary
scope.tagsAttr ||= "tags"
scope.tagListAttr ||= "tag_list"
+ scope.object[scope.tagsAttr] ||= []
+ compileTagList()
- watchString = "object.#{scope.tagsAttr}"
- scope.$watchCollection watchString, ->
- scope.object[scope.tagListAttr] = (tag.text for tag in scope.object[scope.tagsAttr]).join(",")
+ scope.tagAdded = ->
+ scope.onTagAdded()
+ compileTagList()
+
+ scope.tagRemoved = ->
+ # For some reason the tags input doesn't mark the form
+ # as dirty when a tag is removed, which breaks the save bar
+ scope.form.$setDirty(true) if typeof scope.form isnt 'undefined'
+ scope.onTagRemoved()
+ compileTagList()
diff --git a/app/assets/javascripts/admin/utils/services/dialog_defaults.js.coffee b/app/assets/javascripts/admin/utils/services/dialog_defaults.js.coffee
new file mode 100644
index 0000000000..7afb65e82d
--- /dev/null
+++ b/app/assets/javascripts/admin/utils/services/dialog_defaults.js.coffee
@@ -0,0 +1,10 @@
+angular.module("admin.utils").factory "DialogDefaults", ($window) ->
+ show: { effect: "fade", duration: 400 }
+ hide: { effect: "fade", duration: 300 }
+ autoOpen: false
+ resizable: false
+ width: $window.innerWidth * 0.4;
+ modal: true
+ open: (event, ui) ->
+ $('.ui-widget-overlay').bind 'click', ->
+ $(this).siblings('.ui-dialog').find('.ui-dialog-content').dialog('close')
diff --git a/app/assets/javascripts/admin/utils/utils.js.coffee b/app/assets/javascripts/admin/utils/utils.js.coffee
index 094d3a5849..0b04480ff3 100644
--- a/app/assets/javascripts/admin/utils/utils.js.coffee
+++ b/app/assets/javascripts/admin/utils/utils.js.coffee
@@ -1 +1 @@
-angular.module("admin.utils", ["ngSanitize"])
\ No newline at end of file
+angular.module("admin.utils", ["templates", "ngSanitize"])
\ No newline at end of file
diff --git a/app/assets/javascripts/admin/variant_overrides/controllers/variant_overrides_controller.js.coffee b/app/assets/javascripts/admin/variant_overrides/controllers/variant_overrides_controller.js.coffee
index 26d1e05250..e72be8bf71 100644
--- a/app/assets/javascripts/admin/variant_overrides/controllers/variant_overrides_controller.js.coffee
+++ b/app/assets/javascripts/admin/variant_overrides/controllers/variant_overrides_controller.js.coffee
@@ -19,19 +19,10 @@ angular.module("admin.variantOverrides").controller "AdminVariantOverridesCtrl",
hidden: { name: "Hidden Products", visible: false }
new: { name: "New Products", visible: false }
- $scope.columns = Columns.setColumns
- producer: { name: "Producer", visible: true }
- product: { name: "Product", visible: true }
- sku: { name: "SKU", visible: false }
- price: { name: "Price", visible: true }
- on_hand: { name: "On Hand", visible: true }
- on_demand: { name: "On Demand", visible: false }
- reset: { name: "Reset Stock Level", visible: false }
- inheritance: { name: "Inheritance", visible: false }
- visibility: { name: "Hide", visible: false }
-
$scope.bulkActions = [ name: "Reset Stock Levels To Defaults", callback: 'resetStock' ]
+ $scope.columns = Columns.columns
+
$scope.resetSelectFilters = ->
$scope.producerFilter = 0
$scope.query = ''
diff --git a/app/assets/javascripts/admin/variant_overrides/directives/track_inheritance.js.coffee b/app/assets/javascripts/admin/variant_overrides/directives/track_inheritance.js.coffee
index e0a67eb7d0..ecc1cdac29 100644
--- a/app/assets/javascripts/admin/variant_overrides/directives/track_inheritance.js.coffee
+++ b/app/assets/javascripts/admin/variant_overrides/directives/track_inheritance.js.coffee
@@ -6,7 +6,6 @@ angular.module("admin.variantOverrides").directive "trackInheritance", (VariantO
ngModel.$parsers.push (viewValue) ->
if ngModel.$dirty && viewValue
- variantOverride = VariantOverrides.inherit(scope.hub_id, scope.variant.id)
- DirtyVariantOverrides.add variantOverride
+ DirtyVariantOverrides.inherit scope.hub_id, scope.variant.id, scope.variantOverrides[scope.hub_id][scope.variant.id].id
scope.displayDirty()
viewValue
diff --git a/app/assets/javascripts/admin/variant_overrides/directives/track_tag_list.js.coffee b/app/assets/javascripts/admin/variant_overrides/directives/track_tag_list.js.coffee
new file mode 100644
index 0000000000..096ffc24ff
--- /dev/null
+++ b/app/assets/javascripts/admin/variant_overrides/directives/track_tag_list.js.coffee
@@ -0,0 +1,9 @@
+angular.module("admin.variantOverrides").directive "trackTagList", (VariantOverrides, DirtyVariantOverrides) ->
+ link: (scope, element, attrs) ->
+ watchString = "variantOverrides[#{scope.hub_id}][#{scope.variant.id}].tag_list"
+ scope.$watch watchString, (newValue, oldValue) ->
+ if typeof newValue isnt 'undefined' && newValue != oldValue
+ scope.inherit = false
+ vo_id = scope.variantOverrides[scope.hub_id][scope.variant.id].id
+ DirtyVariantOverrides.set scope.hub_id, scope.variant.id, vo_id, 'tag_list', newValue
+ scope.displayDirty()
diff --git a/app/assets/javascripts/admin/variant_overrides/directives/track_variant_override.js.coffee b/app/assets/javascripts/admin/variant_overrides/directives/track_variant_override.js.coffee
index 184b7af232..db4c30dacb 100644
--- a/app/assets/javascripts/admin/variant_overrides/directives/track_variant_override.js.coffee
+++ b/app/assets/javascripts/admin/variant_overrides/directives/track_variant_override.js.coffee
@@ -3,8 +3,8 @@ angular.module("admin.variantOverrides").directive "ofnTrackVariantOverride", (D
link: (scope, element, attrs, ngModel) ->
ngModel.$parsers.push (viewValue) ->
if ngModel.$dirty
- variantOverride = scope.variantOverrides[scope.hub_id][scope.variant.id]
scope.inherit = false
- DirtyVariantOverrides.add variantOverride
+ vo_id = scope.variantOverrides[scope.hub_id][scope.variant.id].id
+ DirtyVariantOverrides.set scope.hub_id, scope.variant.id, vo_id, attrs.ofnTrackVariantOverride, viewValue
scope.displayDirty()
viewValue
diff --git a/app/assets/javascripts/admin/variant_overrides/services/dirty_variant_overrides.js.coffee b/app/assets/javascripts/admin/variant_overrides/services/dirty_variant_overrides.js.coffee
index 053c6cbfa1..537fb484dc 100644
--- a/app/assets/javascripts/admin/variant_overrides/services/dirty_variant_overrides.js.coffee
+++ b/app/assets/javascripts/admin/variant_overrides/services/dirty_variant_overrides.js.coffee
@@ -1,10 +1,22 @@
-angular.module("admin.variantOverrides").factory "DirtyVariantOverrides", ($http) ->
+angular.module("admin.variantOverrides").factory "DirtyVariantOverrides", ($http, VariantOverrides) ->
new class DirtyVariantOverrides
dirtyVariantOverrides: {}
- add: (vo) ->
- @dirtyVariantOverrides[vo.hub_id] ||= {}
- @dirtyVariantOverrides[vo.hub_id][vo.variant_id] = vo
+ add: (hub_id, variant_id, vo_id) ->
+ @dirtyVariantOverrides[hub_id] ||= {}
+ @dirtyVariantOverrides[hub_id][variant_id] ||=
+ { id: vo_id, variant_id: variant_id, hub_id: hub_id }
+
+ set: (hub_id, variant_id, vo_id, attr, value) ->
+ if attr in @requiredAttrs()
+ @add(hub_id, variant_id, vo_id)
+ @dirtyVariantOverrides[hub_id][variant_id][attr] = value
+
+ inherit: (hub_id, variant_id, vo_id) ->
+ @add(hub_id, variant_id, vo_id)
+ blankVo = angular.copy(VariantOverrides.inherit(hub_id, variant_id))
+ delete blankVo[attr] for attr, value of blankVo when attr not in @requiredAttrs()
+ @dirtyVariantOverrides[hub_id][variant_id] = blankVo
count: ->
count = 0
@@ -27,3 +39,6 @@ angular.module("admin.variantOverrides").factory "DirtyVariantOverrides", ($http
url: "/admin/variant_overrides/bulk_update"
data:
variant_overrides: @all()
+
+ requiredAttrs: ->
+ ['id','hub_id','variant_id','sku','price','count_on_hand','on_demand','default_stock','resettable','tag_list']
diff --git a/app/assets/javascripts/admin/variant_overrides/services/variant_overrides.js.coffee b/app/assets/javascripts/admin/variant_overrides/services/variant_overrides.js.coffee
index 4e1128572e..94a4d093eb 100644
--- a/app/assets/javascripts/admin/variant_overrides/services/variant_overrides.js.coffee
+++ b/app/assets/javascripts/admin/variant_overrides/services/variant_overrides.js.coffee
@@ -30,6 +30,8 @@ angular.module("admin.variantOverrides").factory "VariantOverrides", (variantOve
on_demand: null
default_stock: null
resettable: false
+ tag_list: ''
+ tags: []
updateIds: (updatedVos) ->
for vo in updatedVos
diff --git a/app/assets/javascripts/admin/variant_overrides/variant_overrides.js.coffee b/app/assets/javascripts/admin/variant_overrides/variant_overrides.js.coffee
index c302c0463e..4d875cbd33 100644
--- a/app/assets/javascripts/admin/variant_overrides/variant_overrides.js.coffee
+++ b/app/assets/javascripts/admin/variant_overrides/variant_overrides.js.coffee
@@ -1 +1 @@
-angular.module("admin.variantOverrides", ["pasvaz.bindonce", "admin.indexUtils", "admin.utils", "admin.dropdown", "admin.inventoryItems"])
+angular.module("admin.variantOverrides", ["admin.indexUtils", "admin.utils", "admin.dropdown", "admin.inventoryItems", 'ngTagsInput'])
diff --git a/app/assets/javascripts/darkswarm/all.js.coffee b/app/assets/javascripts/darkswarm/all.js.coffee
index f4303f8037..600044f09c 100644
--- a/app/assets/javascripts/darkswarm/all.js.coffee
+++ b/app/assets/javascripts/darkswarm/all.js.coffee
@@ -11,8 +11,7 @@
#= require lodash.underscore.js
#= require angular-scroll.min.js
#= require angular-google-maps.min.js
-#= require ../shared/mm-foundation-tpls-0.2.2.min.js
-#= require ../shared/bindonce.min.js
+#= require ../shared/mm-foundation-tpls-0.8.0.min.js
#= require ../shared/ng-infinite-scroll.min.js
#= require ../shared/angular-local-storage.js
#= require ../shared/angular-slideables.js
diff --git a/app/assets/javascripts/darkswarm/controllers/authentication/signup_controller.js.coffee b/app/assets/javascripts/darkswarm/controllers/authentication/signup_controller.js.coffee
index 123079b6b5..722414f9d4 100644
--- a/app/assets/javascripts/darkswarm/controllers/authentication/signup_controller.js.coffee
+++ b/app/assets/javascripts/darkswarm/controllers/authentication/signup_controller.js.coffee
@@ -1,5 +1,6 @@
Darkswarm.controller "SignupCtrl", ($scope, $http, $window, $location, Redirections, AuthenticationService) ->
$scope.path = "/signup"
+
$scope.errors =
email: null
password: null
diff --git a/app/assets/javascripts/darkswarm/controllers/authentication_controller.js.coffee b/app/assets/javascripts/darkswarm/controllers/authentication_controller.js.coffee
index 1a22f34b0c..135fe37b89 100644
--- a/app/assets/javascripts/darkswarm/controllers/authentication_controller.js.coffee
+++ b/app/assets/javascripts/darkswarm/controllers/authentication_controller.js.coffee
@@ -3,5 +3,10 @@ Darkswarm.controller "AuthenticationCtrl", ($scope, AuthenticationService, Spree
$scope.toggle = AuthenticationService.toggle
$scope.spree_user = SpreeUser.spree_user
- $scope.active = AuthenticationService.active
+ $scope.isActive = AuthenticationService.isActive
$scope.select = AuthenticationService.select
+
+ $scope.tabs =
+ login: { active: $scope.isActive('/login') }
+ signup: { active: $scope.isActive('/signup') }
+ forgot: { active: $scope.isActive('/forgot') }
diff --git a/app/assets/javascripts/darkswarm/controllers/group_page_controller.js.coffee b/app/assets/javascripts/darkswarm/controllers/group_page_controller.js.coffee
index ed52f46bb7..5d9bb0aa02 100644
--- a/app/assets/javascripts/darkswarm/controllers/group_page_controller.js.coffee
+++ b/app/assets/javascripts/darkswarm/controllers/group_page_controller.js.coffee
@@ -1,4 +1,4 @@
-Darkswarm.controller "GroupPageCtrl", ($scope, group_enterprises, Enterprises, MapConfiguration, OfnMap, visibleFilter) ->
+Darkswarm.controller "GroupPageCtrl", ($scope, group_enterprises, Enterprises, MapConfiguration, OfnMap, visibleFilter, Navigation) ->
$scope.Enterprises = Enterprises
all_enterprises_by_id = Enterprises.enterprises_by_id
@@ -19,4 +19,3 @@ Darkswarm.controller "GroupPageCtrl", ($scope, group_enterprises, Enterprises, M
$scope.map = angular.copy MapConfiguration.options
$scope.mapMarkers = OfnMap.enterprise_markers visible_enterprises
-
diff --git a/app/assets/javascripts/darkswarm/controllers/group_tabs_controller.js.coffee b/app/assets/javascripts/darkswarm/controllers/group_tabs_controller.js.coffee
new file mode 100644
index 0000000000..c9a8f88f58
--- /dev/null
+++ b/app/assets/javascripts/darkswarm/controllers/group_tabs_controller.js.coffee
@@ -0,0 +1,8 @@
+Darkswarm.controller "GroupTabsCtrl", ($scope, $controller, Navigation) ->
+ angular.extend this, $controller('TabsCtrl', {$scope: $scope})
+
+ $scope.tabs =
+ map: { active: Navigation.isActive('/map') }
+ about: { active: Navigation.isActive('/about') }
+ producers: { active: Navigation.isActive('/producers') }
+ hubs: { active: Navigation.isActive('/hubs') }
diff --git a/app/assets/javascripts/darkswarm/controllers/groups_controller.js.coffee b/app/assets/javascripts/darkswarm/controllers/groups_controller.js.coffee
index aafb7597d6..8fd47c49f8 100644
--- a/app/assets/javascripts/darkswarm/controllers/groups_controller.js.coffee
+++ b/app/assets/javascripts/darkswarm/controllers/groups_controller.js.coffee
@@ -1,3 +1,3 @@
-Darkswarm.controller "GroupsCtrl", ($scope, Groups, $anchorScroll, $rootScope) ->
+Darkswarm.controller "GroupsCtrl", ($scope, Groups) ->
$scope.Groups = Groups
$scope.order = 'position'
diff --git a/app/assets/javascripts/darkswarm/controllers/products/product_node_controller.js.coffee b/app/assets/javascripts/darkswarm/controllers/products/product_node_controller.js.coffee
index 07ddb4bc61..a692af3fb5 100644
--- a/app/assets/javascripts/darkswarm/controllers/products/product_node_controller.js.coffee
+++ b/app/assets/javascripts/darkswarm/controllers/products/product_node_controller.js.coffee
@@ -1,6 +1,7 @@
-Darkswarm.controller "ProductNodeCtrl", ($scope, $modal) ->
+Darkswarm.controller "ProductNodeCtrl", ($scope, $modal, FilterSelectorsService) ->
$scope.enterprise = $scope.product.supplier # For the modal, so it's consistent
$scope.triggerProductModal = ->
+ $scope.productTaxonSelectors = FilterSelectorsService.createSelectors()
+ $scope.productPropertySelectors = FilterSelectorsService.createSelectors()
$modal.open(templateUrl: "product_modal.html", scope: $scope)
-
diff --git a/app/assets/javascripts/darkswarm/controllers/shopping_tabs_controller.js.coffee b/app/assets/javascripts/darkswarm/controllers/shopping_tabs_controller.js.coffee
new file mode 100644
index 0000000000..8daac0212c
--- /dev/null
+++ b/app/assets/javascripts/darkswarm/controllers/shopping_tabs_controller.js.coffee
@@ -0,0 +1,8 @@
+Darkswarm.controller "ShoppingTabsCtrl", ($scope, $controller, Navigation) ->
+ angular.extend this, $controller('TabsCtrl', {$scope: $scope})
+
+ $scope.tabs =
+ about: { active: Navigation.isActive('/about') }
+ producers: { active: Navigation.isActive('/producers') }
+ contact: { active: Navigation.isActive('/contact') }
+ groups: { active: Navigation.isActive('/groups') }
diff --git a/app/assets/javascripts/darkswarm/controllers/tabs_controller.js.coffee b/app/assets/javascripts/darkswarm/controllers/tabs_controller.js.coffee
index fad13164ff..09095ebd42 100644
--- a/app/assets/javascripts/darkswarm/controllers/tabs_controller.js.coffee
+++ b/app/assets/javascripts/darkswarm/controllers/tabs_controller.js.coffee
@@ -1,15 +1,6 @@
-Darkswarm.controller "TabsCtrl", ($scope, $rootScope, $location) ->
- # Return active if supplied path matches url hash path.
- $scope.active = (path)->
- $location.hash() == path
+Darkswarm.controller "TabsCtrl", ($scope, Navigation) ->
+ $scope.isActive = Navigation.isActive
# Select tab by setting the url hash path.
- $scope.select = (path)->
- $location.hash path
-
- # Toggle tab selected status by setting the url hash path.
- $scope.toggle = (path)->
- if $scope.active(path)
- $location.hash ""
- else
- $location.hash path
+ $scope.select = (path) ->
+ Navigation.navigate path
diff --git a/app/assets/javascripts/darkswarm/darkswarm.js.coffee b/app/assets/javascripts/darkswarm/darkswarm.js.coffee
index a062ead058..ce5a73d69e 100644
--- a/app/assets/javascripts/darkswarm/darkswarm.js.coffee
+++ b/app/assets/javascripts/darkswarm/darkswarm.js.coffee
@@ -1,7 +1,6 @@
window.Darkswarm = angular.module("Darkswarm", ["ngResource",
'mm.foundation',
'angularLocalStorage',
- 'pasvaz.bindonce',
'infinite-scroll',
'angular-flash.service',
'templates',
diff --git a/app/assets/javascripts/darkswarm/services/authentication_service.js.coffee b/app/assets/javascripts/darkswarm/services/authentication_service.js.coffee
index 10f75675f8..478f5f28e1 100644
--- a/app/assets/javascripts/darkswarm/services/authentication_service.js.coffee
+++ b/app/assets/javascripts/darkswarm/services/authentication_service.js.coffee
@@ -22,7 +22,7 @@ Darkswarm.factory "AuthenticationService", (Navigation, $modal, $location, Redir
@selectedPath = path
Navigation.navigate @selectedPath
- active: Navigation.active
+ isActive: Navigation.isActive
close: ->
if location.pathname in ["/", "/checkout"]
diff --git a/app/assets/javascripts/darkswarm/services/checkout.js.coffee b/app/assets/javascripts/darkswarm/services/checkout.js.coffee
index 34cae22fcc..0ab94f61a4 100644
--- a/app/assets/javascripts/darkswarm/services/checkout.js.coffee
+++ b/app/assets/javascripts/darkswarm/services/checkout.js.coffee
@@ -62,8 +62,11 @@ Darkswarm.factory 'Checkout', (CurrentOrder, ShippingMethods, PaymentMethods, $h
shippingPrice: ->
@shippingMethod()?.price || 0.0
+ paymentPrice: ->
+ @paymentMethod()?.price || 0.0
+
paymentMethod: ->
PaymentMethods.payment_methods_by_id[@order.payment_method_id]
cartTotal: ->
- @shippingPrice() + @order.display_total
+ @order.display_total + @shippingPrice() + @paymentPrice()
diff --git a/app/assets/javascripts/darkswarm/services/enterprise_registration_service.js.coffee b/app/assets/javascripts/darkswarm/services/enterprise_registration_service.js.coffee
index a7d6a657fa..1434ffa44f 100644
--- a/app/assets/javascripts/darkswarm/services/enterprise_registration_service.js.coffee
+++ b/app/assets/javascripts/darkswarm/services/enterprise_registration_service.js.coffee
@@ -2,6 +2,7 @@ Darkswarm.factory "EnterpriseRegistrationService", ($http, RegistrationService,
new class EnterpriseRegistrationService
enterprise:
user_ids: [CurrentUser.id]
+ email: CurrentUser.email
email_address: CurrentUser.email
address: {}
country: availableCountries[0]
diff --git a/app/assets/javascripts/darkswarm/services/navigation.js.coffee b/app/assets/javascripts/darkswarm/services/navigation.js.coffee
index fd59d0f348..e2b04f3ad1 100644
--- a/app/assets/javascripts/darkswarm/services/navigation.js.coffee
+++ b/app/assets/javascripts/darkswarm/services/navigation.js.coffee
@@ -1,9 +1,9 @@
Darkswarm.factory 'Navigation', ($location, $window) ->
new class Navigation
- path: null
+ path: null
- active: (path)->
- $location.path() == path
+ isActive: (path)->
+ $location.path() == path
navigate: (path)=>
@path = path
diff --git a/app/assets/javascripts/darkswarm/services/payment_methods.js.coffee b/app/assets/javascripts/darkswarm/services/payment_methods.js.coffee
index 8b44d3e80a..423ceb57e2 100644
--- a/app/assets/javascripts/darkswarm/services/payment_methods.js.coffee
+++ b/app/assets/javascripts/darkswarm/services/payment_methods.js.coffee
@@ -4,5 +4,6 @@ Darkswarm.factory "PaymentMethods", (paymentMethods)->
payment_methods_by_id: {}
constructor: ->
for method in @payment_methods
+ method.price = parseFloat(method.price)
@payment_methods_by_id[method.id] = method
diff --git a/app/assets/javascripts/shared/bindonce.min.js b/app/assets/javascripts/shared/bindonce.min.js
deleted file mode 100644
index 0edd3d57d4..0000000000
--- a/app/assets/javascripts/shared/bindonce.min.js
+++ /dev/null
@@ -1 +0,0 @@
-!function(){"use strict";var e=angular.module("pasvaz.bindonce",[]);e.directive("bindonce",function(){var e=function(e){if(e&&0!==e.length){var t=angular.lowercase(""+e);e=!("f"===t||"0"===t||"false"===t||"no"===t||"n"===t||"[]"===t)}else e=!1;return e},t=parseInt((/msie (\d+)/.exec(angular.lowercase(navigator.userAgent))||[])[1],10);isNaN(t)&&(t=parseInt((/trident\/.*; rv:(\d+)/.exec(angular.lowercase(navigator.userAgent))||[])[1],10));var r={restrict:"AM",controller:["$scope","$element","$attrs","$interpolate",function(r,a,i,n){var c=function(t,r,a){var i="show"===r?"":"none",n="hide"===r?"":"none";t.css("display",e(a)?i:n)},o=function(e,t){if(angular.isObject(t)&&!angular.isArray(t)){var r=[];angular.forEach(t,function(e,t){e&&r.push(t)}),t=r}t&&e.addClass(angular.isArray(t)?t.join(" "):t)},s=function(e,t){e.transclude(t,function(t){var r=e.element.parent(),a=e.element&&e.element[e.element.length-1],i=r&&r[0]||a&&a.parentNode,n=a&&a.nextSibling||null;angular.forEach(t,function(e){i.insertBefore(e,n)})})},l={watcherRemover:void 0,binders:[],group:i.boName,element:a,ran:!1,addBinder:function(e){this.binders.push(e),this.ran&&this.runBinders()},setupWatcher:function(e){var t=this;this.watcherRemover=r.$watch(e,function(e){void 0!==e&&(t.removeWatcher(),t.checkBindonce(e))},!0)},checkBindonce:function(e){var t=this,r=e.$promise?e.$promise.then:e.then;"function"==typeof r?r(function(){t.runBinders()}):t.runBinders()},removeWatcher:function(){void 0!==this.watcherRemover&&(this.watcherRemover(),this.watcherRemover=void 0)},runBinders:function(){for(;this.binders.length>0;){var r=this.binders.shift();if(!this.group||this.group==r.group){var a=r.scope.$eval(r.interpolate?n(r.value):r.value);switch(r.attr){case"boIf":e(a)&&s(r,r.scope.$new());break;case"boSwitch":var i,l=r.controller[0];(i=l.cases["!"+a]||l.cases["?"])&&(r.scope.$eval(r.attrs.change),angular.forEach(i,function(e){s(e,r.scope.$new())}));break;case"boSwitchWhen":var u=r.controller[0];u.cases["!"+r.attrs.boSwitchWhen]=u.cases["!"+r.attrs.boSwitchWhen]||[],u.cases["!"+r.attrs.boSwitchWhen].push({transclude:r.transclude,element:r.element});break;case"boSwitchDefault":var u=r.controller[0];u.cases["?"]=u.cases["?"]||[],u.cases["?"].push({transclude:r.transclude,element:r.element});break;case"hide":case"show":c(r.element,r.attr,a);break;case"class":o(r.element,a);break;case"text":r.element.text(a);break;case"html":r.element.html(a);break;case"style":r.element.css(a);break;case"src":r.element.attr(r.attr,a),t&&r.element.prop("src",a);break;case"attr":angular.forEach(r.attrs,function(e,t){var a,i;t.match(/^boAttr./)&&r.attrs[t]&&(a=t.replace(/^boAttr/,"").replace(/([a-z])([A-Z])/g,"$1-$2").toLowerCase(),i=r.scope.$eval(r.attrs[t]),r.element.attr(a,i))});break;case"href":case"alt":case"title":case"id":case"value":r.element.attr(r.attr,a)}}}this.ran=!0}};return l}],link:function(e,t,r,a){var i=r.bindonce&&e.$eval(r.bindonce);void 0!==i?a.checkBindonce(i):(a.setupWatcher(r.bindonce),t.bind("$destroy",a.removeWatcher))}};return r}),angular.forEach([{directiveName:"boShow",attribute:"show"},{directiveName:"boHide",attribute:"hide"},{directiveName:"boClass",attribute:"class"},{directiveName:"boText",attribute:"text"},{directiveName:"boBind",attribute:"text"},{directiveName:"boHtml",attribute:"html"},{directiveName:"boSrcI",attribute:"src",interpolate:!0},{directiveName:"boSrc",attribute:"src"},{directiveName:"boHrefI",attribute:"href",interpolate:!0},{directiveName:"boHref",attribute:"href"},{directiveName:"boAlt",attribute:"alt"},{directiveName:"boTitle",attribute:"title"},{directiveName:"boId",attribute:"id"},{directiveName:"boStyle",attribute:"style"},{directiveName:"boValue",attribute:"value"},{directiveName:"boAttr",attribute:"attr"},{directiveName:"boIf",transclude:"element",terminal:!0,priority:1e3},{directiveName:"boSwitch",require:"boSwitch",controller:function(){this.cases={}}},{directiveName:"boSwitchWhen",transclude:"element",priority:800,require:"^boSwitch"},{directiveName:"boSwitchDefault",transclude:"element",priority:800,require:"^boSwitch"}],function(t){var r=200;return e.directive(t.directiveName,function(){var e={priority:t.priority||r,transclude:t.transclude||!1,terminal:t.terminal||!1,require:["^bindonce"].concat(t.require||[]),controller:t.controller,compile:function(e,r,a){return function(e,r,i,n){var c=n[0],o=i.boParent;if(o&&c.group!==o){var s=c.element.parent();c=void 0;for(var l;9!==s[0].nodeType&&s.length;){if((l=s.data("$bindonceController"))&&l.group===o){c=l;break}s=s.parent()}if(!c)throw new Error("No bindonce controller: "+o)}c.addBinder({element:r,attr:t.attribute||t.directiveName,attrs:i,value:i[t.directiveName],interpolate:t.interpolate,group:o,transclude:a,controller:n.slice(1),scope:e})}}};return e})})}();
\ No newline at end of file
diff --git a/app/assets/javascripts/shared/mm-foundation-tpls-0.2.2.min.js b/app/assets/javascripts/shared/mm-foundation-tpls-0.2.2.min.js
deleted file mode 100644
index be82613518..0000000000
--- a/app/assets/javascripts/shared/mm-foundation-tpls-0.2.2.min.js
+++ /dev/null
@@ -1,9 +0,0 @@
-/*
- * angular-mm-foundation
- * http://madmimi.github.io/angular-foundation/
-
- * Version: 0.2.2 - 2014-06-25
- * License: MIT
- */
-angular.module("mm.foundation",["mm.foundation.tpls","mm.foundation.accordion","mm.foundation.alert","mm.foundation.bindHtml","mm.foundation.buttons","mm.foundation.position","mm.foundation.dropdownToggle","mm.foundation.transition","mm.foundation.modal","mm.foundation.offcanvas","mm.foundation.pagination","mm.foundation.tooltip","mm.foundation.popover","mm.foundation.progressbar","mm.foundation.rating","mm.foundation.tabs","mm.foundation.tour","mm.foundation.typeahead"]),angular.module("mm.foundation.tpls",["template/accordion/accordion-group.html","template/accordion/accordion.html","template/alert/alert.html","template/modal/backdrop.html","template/modal/window.html","template/pagination/pager.html","template/pagination/pagination.html","template/tooltip/tooltip-html-unsafe-popup.html","template/tooltip/tooltip-popup.html","template/popover/popover.html","template/progressbar/bar.html","template/progressbar/progress.html","template/progressbar/progressbar.html","template/rating/rating.html","template/tabs/tab.html","template/tabs/tabset.html","template/tour/tour.html","template/typeahead/typeahead-match.html","template/typeahead/typeahead-popup.html"]),angular.module("mm.foundation.accordion",[]).constant("accordionConfig",{closeOthers:!0}).controller("AccordionController",["$scope","$attrs","accordionConfig",function(a,b,c){this.groups=[],this.closeOthers=function(d){var e=angular.isDefined(b.closeOthers)?a.$eval(b.closeOthers):c.closeOthers;e&&angular.forEach(this.groups,function(a){a!==d&&(a.isOpen=!1)})},this.addGroup=function(a){var b=this;this.groups.push(a),a.$on("$destroy",function(){b.removeGroup(a)})},this.removeGroup=function(a){var b=this.groups.indexOf(a);-1!==b&&this.groups.splice(this.groups.indexOf(a),1)}}]).directive("accordion",function(){return{restrict:"EA",controller:"AccordionController",transclude:!0,replace:!1,templateUrl:"template/accordion/accordion.html"}}).directive("accordionGroup",["$parse",function(a){return{require:"^accordion",restrict:"EA",transclude:!0,replace:!0,templateUrl:"template/accordion/accordion-group.html",scope:{heading:"@"},controller:function(){this.setHeading=function(a){this.heading=a}},link:function(b,c,d,e){var f,g;e.addGroup(b),b.isOpen=!1,d.isOpen&&(f=a(d.isOpen),g=f.assign,b.$parent.$watch(f,function(a){b.isOpen=!!a})),b.$watch("isOpen",function(a){a&&e.closeOthers(b),g&&g(b.$parent,a)})}}}]).directive("accordionHeading",function(){return{restrict:"EA",transclude:!0,template:"",replace:!0,require:"^accordionGroup",compile:function(a,b,c){return function(a,b,d,e){e.setHeading(c(a,function(){}))}}}}).directive("accordionTransclude",function(){return{require:"^accordionGroup",link:function(a,b,c,d){a.$watch(function(){return d[c.accordionTransclude]},function(a){a&&(b.html(""),b.append(a))})}}}),angular.module("mm.foundation.alert",[]).controller("AlertController",["$scope","$attrs",function(a,b){a.closeable="close"in b}]).directive("alert",function(){return{restrict:"EA",controller:"AlertController",templateUrl:"template/alert/alert.html",transclude:!0,replace:!0,scope:{type:"=",close:"&"}}}),angular.module("mm.foundation.bindHtml",[]).directive("bindHtmlUnsafe",function(){return function(a,b,c){b.addClass("ng-binding").data("$binding",c.bindHtmlUnsafe),a.$watch(c.bindHtmlUnsafe,function(a){b.html(a||"")})}}),angular.module("mm.foundation.buttons",[]).constant("buttonConfig",{activeClass:"active",toggleEvent:"click"}).controller("ButtonsController",["buttonConfig",function(a){this.activeClass=a.activeClass,this.toggleEvent=a.toggleEvent}]).directive("btnRadio",function(){return{require:["btnRadio","ngModel"],controller:"ButtonsController",link:function(a,b,c,d){var e=d[0],f=d[1];f.$render=function(){b.toggleClass(e.activeClass,angular.equals(f.$modelValue,a.$eval(c.btnRadio)))},b.bind(e.toggleEvent,function(){b.hasClass(e.activeClass)||a.$apply(function(){f.$setViewValue(a.$eval(c.btnRadio)),f.$render()})})}}}).directive("btnCheckbox",function(){return{require:["btnCheckbox","ngModel"],controller:"ButtonsController",link:function(a,b,c,d){function e(){return g(c.btnCheckboxTrue,!0)}function f(){return g(c.btnCheckboxFalse,!1)}function g(b,c){var d=a.$eval(b);return angular.isDefined(d)?d:c}var h=d[0],i=d[1];i.$render=function(){b.toggleClass(h.activeClass,angular.equals(i.$modelValue,e()))},b.bind(h.toggleEvent,function(){a.$apply(function(){i.$setViewValue(b.hasClass(h.activeClass)?f():e()),i.$render()})})}}}),angular.module("mm.foundation.position",[]).factory("$position",["$document","$window",function(a,b){function c(a,c){return a.currentStyle?a.currentStyle[c]:b.getComputedStyle?b.getComputedStyle(a)[c]:a.style[c]}function d(a){return"static"===(c(a,"position")||"static")}var e=function(b){for(var c=a[0],e=b.offsetParent||c;e&&e!==c&&d(e);)e=e.offsetParent;return e||c};return{position:function(b){var c=this.offset(b),d={top:0,left:0},f=e(b[0]);f!=a[0]&&(d=this.offset(angular.element(f)),d.top+=f.clientTop-f.scrollTop,d.left+=f.clientLeft-f.scrollLeft);var g=b[0].getBoundingClientRect();return{width:g.width||b.prop("offsetWidth"),height:g.height||b.prop("offsetHeight"),top:c.top-d.top,left:c.left-d.left}},offset:function(c){var d=c[0].getBoundingClientRect();return{width:d.width||c.prop("offsetWidth"),height:d.height||c.prop("offsetHeight"),top:d.top+(b.pageYOffset||a[0].body.scrollTop||a[0].documentElement.scrollTop),left:d.left+(b.pageXOffset||a[0].body.scrollLeft||a[0].documentElement.scrollLeft)}}}}]),angular.module("mm.foundation.dropdownToggle",["mm.foundation.position"]).directive("dropdownToggle",["$document","$location","$position",function(a,b,c){var d=null,e=angular.noop;return{restrict:"CA",scope:{dropdownToggle:"@"},link:function(b,f){var g=angular.element(a[0].querySelector(b.dropdownToggle));b.$watch("$location.path",function(){e()}),f.bind("click",function(h){g=angular.element(a[0].querySelector(b.dropdownToggle));var i=f===d;if(h.preventDefault(),h.stopPropagation(),d&&e(),!i&&!f.hasClass("disabled")&&!f.prop("disabled")){g.css("display","block");var j=c.offset(f),k=c.offset(angular.element(g[0].offsetParent));g.css({left:j.left-k.left+"px",top:j.top-k.top+j.height+"px"}),d=f,e=function(){a.unbind("click",e),g.css("display","none"),e=angular.noop,d=null},a.bind("click",e)}}),g&&g.css("display","none")}}}]),angular.module("mm.foundation.transition",[]).factory("$transition",["$q","$timeout","$rootScope",function(a,b,c){function d(a){for(var b in a)if(void 0!==f.style[b])return a[b]}var e=function(d,f,g){g=g||{};var h=a.defer(),i=e[g.animation?"animationEndEventName":"transitionEndEventName"],j=function(){c.$apply(function(){d.unbind(i,j),h.resolve(d)})};return i&&d.bind(i,j),b(function(){angular.isString(f)?d.addClass(f):angular.isFunction(f)?f(d):angular.isObject(f)&&d.css(f),i||h.resolve(d)}),h.promise.cancel=function(){i&&d.unbind(i,j),h.reject("Transition cancelled")},h.promise},f=document.createElement("trans"),g={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd",transition:"transitionend"},h={WebkitTransition:"webkitAnimationEnd",MozTransition:"animationend",OTransition:"oAnimationEnd",transition:"animationend"};return e.transitionEndEventName=d(g),e.animationEndEventName=d(h),e}]),angular.module("mm.foundation.modal",["mm.foundation.transition"]).factory("$$stackedMap",function(){return{createNew:function(){var a=[];return{add:function(b,c){a.push({key:b,value:c})},get:function(b){for(var c=0;c0)}function i(){if(k&&-1==g()){var a=l;j(k,l,150,function(){a.$destroy(),a=null}),k=void 0,l=void 0}}function j(c,d,e,f){function g(){g.done||(g.done=!0,c.remove(),f&&f())}d.animate=!1;var h=a.transitionEndEventName;if(h){var i=b(g,e);c.bind(h,function(){b.cancel(i),g(),d.$apply()})}else b(g,0)}var k,l,m="modal-open",n=f.createNew(),o={};return e.$watch(g,function(a){l&&(l.index=a)}),c.bind("keydown",function(a){var b;27===a.which&&(b=n.top(),b&&b.value.keyboard&&e.$apply(function(){o.dismiss(b.key)}))}),o.open=function(a,b){n.add(a,{deferred:b.deferred,modalScope:b.scope,backdrop:b.backdrop,keyboard:b.keyboard});var f=c.find("body").eq(0),h=g();h>=0&&!k&&(l=e.$new(!0),l.index=h,k=d("")(l),f.append(k));var i=angular.element("");i.attr("window-class",b.windowClass),i.attr("index",n.length()-1),i.attr("animate","animate"),i.html(b.content);var j=d(i)(b.scope);n.top().value.modalDomEl=j,f.append(j),f.addClass(m)},o.close=function(a,b){var c=n.get(a).value;c&&(c.deferred.resolve(b),h(a))},o.dismiss=function(a,b){var c=n.get(a).value;c&&(c.deferred.reject(b),h(a))},o.dismissAll=function(a){for(var b=this.getTop();b;)this.dismiss(b.key,a),b=this.getTop()},o.getTop=function(){return n.top()},o}]).provider("$modal",function(){var a={options:{backdrop:!0,keyboard:!0},$get:["$injector","$rootScope","$q","$http","$templateCache","$controller","$modalStack",function(b,c,d,e,f,g,h){function i(a){return a.template?d.when(a.template):e.get(a.templateUrl,{cache:f}).then(function(a){return a.data})}function j(a){var c=[];return angular.forEach(a,function(a){(angular.isFunction(a)||angular.isArray(a))&&c.push(d.when(b.invoke(a)))}),c}var k={};return k.open=function(b){var e=d.defer(),f=d.defer(),k={result:e.promise,opened:f.promise,close:function(a){h.close(k,a)},dismiss:function(a){h.dismiss(k,a)}};if(b=angular.extend({},a.options,b),b.resolve=b.resolve||{},!b.template&&!b.templateUrl)throw new Error("One of template or templateUrl options is required.");var l=d.all([i(b)].concat(j(b.resolve)));return l.then(function(a){var d=(b.scope||c).$new();d.$close=k.close,d.$dismiss=k.dismiss;var f,i={},j=1;b.controller&&(i.$scope=d,i.$modalInstance=k,angular.forEach(b.resolve,function(b,c){i[c]=a[j++]}),f=g(b.controller,i)),h.open(k,{scope:d,deferred:e,content:a[0],backdrop:b.backdrop,keyboard:b.keyboard,windowClass:b.windowClass})},function(a){e.reject(a)}),l.then(function(){f.resolve(!0)},function(){f.reject(!1)}),k},k}]};return a}),angular.module("mm.foundation.offcanvas",[]).directive("offCanvasWrap",["$window",function(a){return{scope:{},restrict:"C",link:function(b,c){var d=angular.element(a),e=b.sidebar=c;b.hide=function(){e.removeClass("move-left"),e.removeClass("move-right")},d.bind("resize.body",b.hide),b.$on("$destroy",function(){d.unbind("resize.body",b.hide)})},controller:["$scope",function(a){this.leftToggle=function(){a.sidebar.toggleClass("move-right")},this.rightToggle=function(){a.sidebar.toggleClass("move-left")},this.hide=function(){a.hide()}}]}}]).directive("leftOffCanvasToggle",[function(){return{require:"^offCanvasWrap",restrict:"C",link:function(a,b,c,d){b.on("click",function(){d.leftToggle()})}}}]).directive("rightOffCanvasToggle",[function(){return{require:"^offCanvasWrap",restrict:"C",link:function(a,b,c,d){b.on("click",function(){d.rightToggle()})}}}]).directive("exitOffCanvas",[function(){return{require:"^offCanvasWrap",restrict:"C",link:function(a,b,c,d){b.on("click",function(){d.hide()})}}}]).directive("offCanvasList",[function(){return{require:"^offCanvasWrap",restrict:"C",link:function(a,b,c,d){b.on("click",function(){d.hide()})}}}]),angular.module("mm.foundation.pagination",[]).controller("PaginationController",["$scope","$attrs","$parse","$interpolate",function(a,b,c,d){var e=this,f=b.numPages?c(b.numPages).assign:angular.noop;this.init=function(d){b.itemsPerPage?a.$parent.$watch(c(b.itemsPerPage),function(b){e.itemsPerPage=parseInt(b,10),a.totalPages=e.calculateTotalPages()}):this.itemsPerPage=d},this.noPrevious=function(){return 1===this.page},this.noNext=function(){return this.page===a.totalPages},this.isActive=function(a){return this.page===a},this.calculateTotalPages=function(){var b=this.itemsPerPage<1?1:Math.ceil(a.totalItems/this.itemsPerPage);return Math.max(b||0,1)},this.getAttributeValue=function(b,c,e){return angular.isDefined(b)?e?d(b)(a.$parent):a.$parent.$eval(b):c},this.render=function(){this.page=parseInt(a.page,10)||1,this.page>0&&this.page<=a.totalPages&&(a.pages=this.getPages(this.page,a.totalPages))},a.selectPage=function(b){!e.isActive(b)&&b>0&&b<=a.totalPages&&(a.page=b,a.onSelectPage({page:b}))},a.$watch("page",function(){e.render()}),a.$watch("totalItems",function(){a.totalPages=e.calculateTotalPages()}),a.$watch("totalPages",function(b){f(a.$parent,b),e.page>b?a.selectPage(b):e.render()})}]).constant("paginationConfig",{itemsPerPage:10,boundaryLinks:!1,directionLinks:!0,firstText:"First",previousText:"Previous",nextText:"Next",lastText:"Last",rotate:!0}).directive("pagination",["$parse","paginationConfig",function(a,b){return{restrict:"EA",scope:{page:"=",totalItems:"=",onSelectPage:" &"},controller:"PaginationController",templateUrl:"template/pagination/pagination.html",replace:!0,link:function(c,d,e,f){function g(a,b,c,d){return{number:a,text:b,active:c,disabled:d}}var h,i=f.getAttributeValue(e.boundaryLinks,b.boundaryLinks),j=f.getAttributeValue(e.directionLinks,b.directionLinks),k=f.getAttributeValue(e.firstText,b.firstText,!0),l=f.getAttributeValue(e.previousText,b.previousText,!0),m=f.getAttributeValue(e.nextText,b.nextText,!0),n=f.getAttributeValue(e.lastText,b.lastText,!0),o=f.getAttributeValue(e.rotate,b.rotate);f.init(b.itemsPerPage),e.maxSize&&c.$parent.$watch(a(e.maxSize),function(a){h=parseInt(a,10),f.render()}),f.getPages=function(a,b){var c=[],d=1,e=b,p=angular.isDefined(h)&&b>h;p&&(o?(d=Math.max(a-Math.floor(h/2),1),e=d+h-1,e>b&&(e=b,d=e-h+1)):(d=(Math.ceil(a/h)-1)*h+1,e=Math.min(d+h-1,b)));for(var q=d;e>=q;q++){var r=g(q,q,f.isActive(q),!1);c.push(r)}if(p&&!o){if(d>1){var s=g(d-1,"...",!1,!1);c.unshift(s)}if(b>e){var t=g(e+1,"...",!1,!1);c.push(t)}}if(j){var u=g(a-1,l,!1,f.noPrevious());c.unshift(u);var v=g(a+1,m,!1,f.noNext());c.push(v)}if(i){var w=g(1,k,!1,f.noPrevious());c.unshift(w);var x=g(b,n,!1,f.noNext());c.push(x)}return c}}}}]).constant("pagerConfig",{itemsPerPage:10,previousText:"« Previous",nextText:"Next »",align:!0}).directive("pager",["pagerConfig",function(a){return{restrict:"EA",scope:{page:"=",totalItems:"=",onSelectPage:" &"},controller:"PaginationController",templateUrl:"template/pagination/pager.html",replace:!0,link:function(b,c,d,e){function f(a,b,c,d,e){return{number:a,text:b,disabled:c,previous:i&&d,next:i&&e}}var g=e.getAttributeValue(d.previousText,a.previousText,!0),h=e.getAttributeValue(d.nextText,a.nextText,!0),i=e.getAttributeValue(d.align,a.align);e.init(a.itemsPerPage),e.getPages=function(a){return[f(a-1,g,e.noPrevious(),!0,!1),f(a+1,h,e.noNext(),!1,!0)]}}}}]),angular.module("mm.foundation.tooltip",["mm.foundation.position","mm.foundation.bindHtml"]).provider("$tooltip",function(){function a(a){var b=/[A-Z]/g,c="-";return a.replace(b,function(a,b){return(b?c:"")+a.toLowerCase()})}var b={placement:"top",animation:!0,popupDelay:0},c={mouseenter:"mouseleave",click:"click",focus:"blur"},d={};this.options=function(a){angular.extend(d,a)},this.setTriggers=function(a){angular.extend(c,a)},this.$get=["$window","$compile","$timeout","$parse","$document","$position","$interpolate",function(e,f,g,h,i,j,k){return function(e,l,m){function n(a){var b=a||o.trigger||m,d=c[b]||b;return{show:b,hide:d}}var o=angular.extend({},b,d),p=a(e),q=k.startSymbol(),r=k.endSymbol(),s="';return{restrict:"EA",scope:!0,compile:function(){var a=f(s);return function(b,c,d){function f(){b.tt_isOpen?m():k()}function k(){(!z||b.$eval(d[l+"Enable"]))&&(b.tt_popupDelay?(v=g(p,b.tt_popupDelay,!1),v.then(function(a){a()})):p()())}function m(){b.$apply(function(){q()})}function p(){return b.tt_content?(r(),u&&g.cancel(u),t.css({top:0,left:0,display:"block"}),w?i.find("body").append(t):c.after(t),A(),b.tt_isOpen=!0,b.$digest(),A):angular.noop}function q(){b.tt_isOpen=!1,g.cancel(v),b.tt_animation?u=g(s,500):s()}function r(){t&&s(),t=a(b,function(){}),b.$digest()}function s(){t&&(t.remove(),t=null)}var t,u,v,w=angular.isDefined(o.appendToBody)?o.appendToBody:!1,x=n(void 0),y=!1,z=angular.isDefined(d[l+"Enable"]),A=function(){var a,d,e,f;switch(a=w?j.offset(c):j.position(c),d=t.prop("offsetWidth"),e=t.prop("offsetHeight"),b.tt_placement){case"right":f={top:a.top+a.height/2-e/2,left:a.left+a.width+10};break;case"bottom":f={top:a.top+a.height+10,left:a.left};break;case"left":f={top:a.top+a.height/2-e/2,left:a.left-d-10};break;default:f={top:a.top-e-10,left:a.left}}f.top+="px",f.left+="px",t.css(f)};b.tt_isOpen=!1,d.$observe(e,function(a){b.tt_content=a,!a&&b.tt_isOpen&&q()}),d.$observe(l+"Title",function(a){b.tt_title=a}),d.$observe(l+"Placement",function(a){b.tt_placement=angular.isDefined(a)?a:o.placement}),d.$observe(l+"PopupDelay",function(a){var c=parseInt(a,10);b.tt_popupDelay=isNaN(c)?o.popupDelay:c});var B=function(){y&&(c.unbind(x.show,k),c.unbind(x.hide,m))},C=function(){};d.$observe(l+"Trigger",function(a){B(),C(),x=n(a),angular.isFunction(x.show)?C=b.$watch(function(){return x.show(b,c,d)},function(a){return g(a?p:q)}):x.show===x.hide?c.bind(x.show,f):(c.bind(x.show,k),c.bind(x.hide,m)),y=!0});var D=b.$eval(d[l+"Animation"]);b.tt_animation=angular.isDefined(D)?!!D:o.animation,d.$observe(l+"AppendToBody",function(a){w=angular.isDefined(a)?h(a)(b):w}),w&&b.$on("$locationChangeSuccess",function(){b.tt_isOpen&&q()}),b.$on("$destroy",function(){g.cancel(u),g.cancel(v),B(),C(),s()})}}}}}]}).directive("tooltipPopup",function(){return{restrict:"EA",replace:!0,scope:{content:"@",placement:"@",animation:"&",isOpen:"&"},templateUrl:"template/tooltip/tooltip-popup.html"}}).directive("tooltip",["$tooltip",function(a){return a("tooltip","tooltip","mouseenter")}]).directive("tooltipHtmlUnsafePopup",function(){return{restrict:"EA",replace:!0,scope:{content:"@",placement:"@",animation:"&",isOpen:"&"},templateUrl:"template/tooltip/tooltip-html-unsafe-popup.html"}}).directive("tooltipHtmlUnsafe",["$tooltip",function(a){return a("tooltipHtmlUnsafe","tooltip","mouseenter")}]),angular.module("mm.foundation.popover",["mm.foundation.tooltip"]).directive("popoverPopup",function(){return{restrict:"EA",replace:!0,scope:{title:"@",content:"@",placement:"@",animation:"&",isOpen:"&"},templateUrl:"template/popover/popover.html"}}).directive("popover",["$tooltip",function(a){return a("popover","popover","click")}]),angular.module("mm.foundation.progressbar",["mm.foundation.transition"]).constant("progressConfig",{animate:!0,max:100}).controller("ProgressController",["$scope","$attrs","progressConfig","$transition",function(a,b,c,d){var e=this,f=[],g=angular.isDefined(b.max)?a.$parent.$eval(b.max):c.max,h=angular.isDefined(b.animate)?a.$parent.$eval(b.animate):c.animate;this.addBar=function(a,b){var c=0,d=a.$parent.$index;angular.isDefined(d)&&f[d]&&(c=f[d].value),f.push(a),this.update(b,a.value,c),a.$watch("value",function(a,c){a!==c&&e.update(b,a,c)}),a.$on("$destroy",function(){e.removeBar(a)})},this.update=function(a,b,c){var e=this.getPercentage(b);h?(a.css("width",this.getPercentage(c)+"%"),d(a,{width:e+"%"})):a.css({transition:"none",width:e+"%"})},this.removeBar=function(a){f.splice(f.indexOf(a),1)},this.getPercentage=function(a){return Math.round(100*a/g)}}]).directive("progress",function(){return{restrict:"EA",replace:!0,transclude:!0,controller:"ProgressController",require:"progress",scope:{},template:''}}).directive("bar",function(){return{restrict:"EA",replace:!0,transclude:!0,require:"^progress",scope:{value:"=",type:"@"},templateUrl:"template/progressbar/bar.html",link:function(a,b,c,d){d.addBar(a,b)}}}).directive("progressbar",function(){return{restrict:"EA",replace:!0,transclude:!0,controller:"ProgressController",scope:{value:"=",type:"@"},templateUrl:"template/progressbar/progressbar.html",link:function(a,b,c,d){d.addBar(a,angular.element(b.children()[0]))}}}),angular.module("mm.foundation.rating",[]).constant("ratingConfig",{max:5,stateOn:null,stateOff:null}).controller("RatingController",["$scope","$attrs","$parse","ratingConfig",function(a,b,c,d){this.maxRange=angular.isDefined(b.max)?a.$parent.$eval(b.max):d.max,this.stateOn=angular.isDefined(b.stateOn)?a.$parent.$eval(b.stateOn):d.stateOn,this.stateOff=angular.isDefined(b.stateOff)?a.$parent.$eval(b.stateOff):d.stateOff,this.createRateObjects=function(a){for(var b={stateOn:this.stateOn,stateOff:this.stateOff},c=0,d=a.length;d>c;c++)a[c]=angular.extend({index:c},b,a[c]);return a},a.range=this.createRateObjects(angular.isDefined(b.ratingStates)?angular.copy(a.$parent.$eval(b.ratingStates)):new Array(this.maxRange)),a.rate=function(b){a.value===b||a.readonly||(a.value=b)},a.enter=function(b){a.readonly||(a.val=b),a.onHover({value:b})},a.reset=function(){a.val=angular.copy(a.value),a.onLeave()},a.$watch("value",function(b){a.val=b}),a.readonly=!1,b.readonly&&a.$parent.$watch(c(b.readonly),function(b){a.readonly=!!b})}]).directive("rating",function(){return{restrict:"EA",scope:{value:"=",onHover:"&",onLeave:"&"},controller:"RatingController",templateUrl:"template/rating/rating.html",replace:!0}}),angular.module("mm.foundation.tabs",[]).controller("TabsetController",["$scope",function(a){var b=this,c=b.tabs=a.tabs=[];b.select=function(a){a.selectExpression(a.$parent)},b.addTab=function(a){c.push(a)},b.removeTab=function(a){var d=c.indexOf(a);if(a.active&&c.length>1){var e=d==c.length-1?d-1:d+1;b.select(c[e])}c.splice(d,1)}}]).directive("tabset",function(){return{restrict:"EA",transclude:!0,replace:!0,scope:{},controller:"TabsetController",templateUrl:"template/tabs/tabset.html",link:function(a,b,c){a.vertical=angular.isDefined(c.vertical)?a.$parent.$eval(c.vertical):!1,a.justified=angular.isDefined(c.justified)?a.$parent.$eval(c.justified):!1,a.type=angular.isDefined(c.type)?a.$parent.$eval(c.type):"tabs"}}}).directive("tab",["$parse",function(a){return{require:"^tabset",restrict:"EA",replace:!0,templateUrl:"template/tabs/tab.html",transclude:!0,scope:{heading:"@",onSelect:"&select",onDeselect:"&deselect"},controller:function(){},compile:function(b,c,d){return function(b,c,e,f){var g,h;e.select&&(b.selectExpression=a(e.select)),e.active?(g=a(e.active),h=g.assign,b.$parent.$watch(g,function(a,c){a!==c&&(b.active=!!a)}),b.active=g(b.$parent)):h=g=angular.noop,b.disabled=!1,e.disabled&&b.$parent.$watch(a(e.disabled),function(a){b.disabled=!!a}),b.select=function(){b.disabled||b.selectExpression(b.$parent)},f.addTab(b),b.$on("$destroy",function(){f.removeTab(b)}),b.$transcludeFn=d}}}}]).directive("tabHeadingTransclude",[function(){return{restrict:"A",require:"^tab",link:function(a,b){a.$watch("headingElement",function(a){a&&(b.html(""),b.append(a))})}}}]).directive("tabContentTransclude",function(){function a(a){return a.tagName&&(a.hasAttribute("tab-heading")||a.hasAttribute("data-tab-heading")||"tab-heading"===a.tagName.toLowerCase()||"data-tab-heading"===a.tagName.toLowerCase())}return{restrict:"A",require:"^tabset",link:function(b,c,d){var e=b.$eval(d.tabContentTransclude);e.$transcludeFn(e.$parent,function(b){angular.forEach(b,function(b){a(b)?e.headingElement=b:c.append(b)})})}}}),angular.module("mm.foundation.tour",["mm.foundation.position","mm.foundation.tooltip"]).service("$tour",["$window",function(a){function b(){return parseInt(a.localStorage.getItem("mm.tour.step"),10)}function c(b){d=b,a.localStorage.setItem("mm.tour.step",b)}var d=b(),e={};this.add=function(a,b){e[a]=b},this.has=function(a){return!!e[a]},this.isActive=function(){return d>0},this.current=function(a){return a?void c(d):d},this.start=function(){c(1)},this.next=function(){c(d+1)},this.end=function(){c(0)}}]).directive("stepTextPopup",["$tour",function(a){return{restrict:"EA",replace:!0,scope:{title:"@",content:"@",placement:"@",animation:"&",isOpen:"&"},templateUrl:"template/tour/tour.html",link:function(b,c){b.isLastStep=function(){return!a.has(a.current()+1)},b.endTour=function(){c.remove(),a.end()},b.nextStep=function(){c.remove(),a.next()}}}}]).directive("stepText",["$position","$tooltip","$tour","$window",function(a,b,c,d){function e(a){var b=a[0].getBoundingClientRect();return b.top>=0&&b.left>=0&&b.bottom<=d.innerHeight-80&&b.right<=d.innerWidth}function f(b,f,g){var h=parseInt(g.stepIndex,10);if(c.isActive()&&h&&(c.add(h,g),h===c.current())){if(!e(f)){var i=a.offset(f);d.scrollTo(0,i.top-d.innerHeight/2)}return!0}return!1}return b("stepText","step",f)}]),angular.module("mm.foundation.typeahead",["mm.foundation.position","mm.foundation.bindHtml"]).factory("typeaheadParser",["$parse",function(a){var b=/^\s*(.*?)(?:\s+as\s+(.*?))?\s+for\s+(?:([\$\w][\$\w\d]*))\s+in\s+(.*)$/;return{parse:function(c){var d=c.match(b);if(!d)throw new Error("Expected typeahead specification in form of '_modelValue_ (as _label_)? for _item_ in _collection_' but got '"+c+"'.");return{itemName:d[3],source:a(d[4]),viewMapper:a(d[2]||d[1]),modelMapper:a(d[1])}}}}]).directive("typeahead",["$compile","$parse","$q","$timeout","$document","$position","typeaheadParser",function(a,b,c,d,e,f,g){var h=[9,13,27,38,40];return{require:"ngModel",link:function(i,j,k,l){var m,n=i.$eval(k.typeaheadMinLength)||1,o=i.$eval(k.typeaheadWaitMs)||0,p=i.$eval(k.typeaheadEditable)!==!1,q=b(k.typeaheadLoading).assign||angular.noop,r=b(k.typeaheadOnSelect),s=k.typeaheadInputFormatter?b(k.typeaheadInputFormatter):void 0,t=k.typeaheadAppendToBody?b(k.typeaheadAppendToBody):!1,u=b(k.ngModel).assign,v=g.parse(k.typeahead),w=angular.element("");w.attr({matches:"matches",active:"activeIdx",select:"select(activeIdx)",query:"query",position:"position"}),angular.isDefined(k.typeaheadTemplateUrl)&&w.attr("template-url",k.typeaheadTemplateUrl);var x=i.$new();i.$on("$destroy",function(){x.$destroy()});var y=function(){x.matches=[],x.activeIdx=-1},z=function(a){var b={$viewValue:a};q(i,!0),c.when(v.source(i,b)).then(function(c){if(a===l.$viewValue&&m){if(c.length>0){x.activeIdx=0,x.matches.length=0;for(var d=0;d=n?o>0?(A&&d.cancel(A),A=d(function(){z(a)},o)):z(a):(q(i,!1),y()),p?a:a?void l.$setValidity("editable",!1):(l.$setValidity("editable",!0),a)}),l.$formatters.push(function(a){var b,c,d={};return s?(d.$model=a,s(i,d)):(d[v.itemName]=a,b=v.viewMapper(i,d),d[v.itemName]=void 0,c=v.viewMapper(i,d),b!==c?b:a)}),x.select=function(a){var b,c,d={};d[v.itemName]=c=x.matches[a].model,b=v.modelMapper(i,d),u(i,b),l.$setValidity("editable",!0),r(i,{$item:c,$model:b,$label:v.viewMapper(i,d)}),y(),j[0].focus()},j.bind("keydown",function(a){0!==x.matches.length&&-1!==h.indexOf(a.which)&&(a.preventDefault(),40===a.which?(x.activeIdx=(x.activeIdx+1)%x.matches.length,x.$digest()):38===a.which?(x.activeIdx=(x.activeIdx?x.activeIdx:x.matches.length)-1,x.$digest()):13===a.which||9===a.which?x.$apply(function(){x.select(x.activeIdx)}):27===a.which&&(a.stopPropagation(),y(),x.$digest()))}),j.bind("blur",function(){m=!1});var B=function(a){j[0]!==a.target&&(y(),x.$digest())};e.bind("click",B),i.$on("$destroy",function(){e.unbind("click",B)});var C=a(w)(x);t?e.find("body").append(C):j.after(C)}}}]).directive("typeaheadPopup",function(){return{restrict:"EA",scope:{matches:"=",query:"=",active:"=",position:"=",select:"&"},replace:!0,templateUrl:"template/typeahead/typeahead-popup.html",link:function(a,b,c){a.templateUrl=c.templateUrl,a.isOpen=function(){return a.matches.length>0},a.isActive=function(b){return a.active==b},a.selectActive=function(b){a.active=b},a.selectMatch=function(b){a.select({activeIdx:b})}}}}).directive("typeaheadMatch",["$http","$templateCache","$compile","$parse",function(a,b,c,d){return{restrict:"EA",scope:{index:"=",match:"=",query:"="},link:function(e,f,g){var h=d(g.templateUrl)(e.$parent)||"template/typeahead/typeahead-match.html";a.get(h,{cache:b}).success(function(a){f.replaceWith(c(a.trim())(e))})}}}]).filter("typeaheadHighlight",function(){function a(a){return a.replace(/([.?*+^$[\]\\(){}|-])/g,"\\$1")}return function(b,c){return c?b.replace(new RegExp(a(c),"gi"),"$&"):b}}),angular.module("template/accordion/accordion-group.html",[]).run(["$templateCache",function(a){a.put("template/accordion/accordion-group.html",'\n {{heading}}\n \n\n')}]),angular.module("template/accordion/accordion.html",[]).run(["$templateCache",function(a){a.put("template/accordion/accordion.html",'
\n')}]),angular.module("template/alert/alert.html",[]).run(["$templateCache",function(a){a.put("template/alert/alert.html","\n")}]),angular.module("template/modal/backdrop.html",[]).run(["$templateCache",function(a){a.put("template/modal/backdrop.html",'\n')}]),angular.module("template/modal/window.html",[]).run(["$templateCache",function(a){a.put("template/modal/window.html",'\n')}]),angular.module("template/pagination/pager.html",[]).run(["$templateCache",function(a){a.put("template/pagination/pager.html",'\n')}]),angular.module("template/pagination/pagination.html",[]).run(["$templateCache",function(a){a.put("template/pagination/pagination.html",'\n')}]),angular.module("template/tooltip/tooltip-html-unsafe-popup.html",[]).run(["$templateCache",function(a){a.put("template/tooltip/tooltip-html-unsafe-popup.html",'\n \n \n\n')
-}]),angular.module("template/tooltip/tooltip-popup.html",[]).run(["$templateCache",function(a){a.put("template/tooltip/tooltip-popup.html",'\n \n \n\n')}]),angular.module("template/popover/popover.html",[]).run(["$templateCache",function(a){a.put("template/popover/popover.html",'\n')}]),angular.module("template/progressbar/bar.html",[]).run(["$templateCache",function(a){a.put("template/progressbar/bar.html",'\n')}]),angular.module("template/progressbar/progress.html",[]).run(["$templateCache",function(a){a.put("template/progressbar/progress.html",'\n')}]),angular.module("template/progressbar/progressbar.html",[]).run(["$templateCache",function(a){a.put("template/progressbar/progressbar.html",'\n \n
\n')}]),angular.module("template/rating/rating.html",[]).run(["$templateCache",function(a){a.put("template/rating/rating.html",'\n \n\n')}]),angular.module("template/tabs/tab.html",[]).run(["$templateCache",function(a){a.put("template/tabs/tab.html",'\n {{heading}}\n\n')}]),angular.module("template/tabs/tabset.html",[]).run(["$templateCache",function(a){a.put("template/tabs/tabset.html",'\n')}]),angular.module("template/tour/tour.html",[]).run(["$templateCache",function(a){a.put("template/tour/tour.html",'\n')}]),angular.module("template/typeahead/typeahead-match.html",[]).run(["$templateCache",function(a){a.put("template/typeahead/typeahead-match.html",'')}]),angular.module("template/typeahead/typeahead-popup.html",[]).run(["$templateCache",function(a){a.put("template/typeahead/typeahead-popup.html","\n')}]);
diff --git a/app/assets/javascripts/shared/mm-foundation-tpls-0.8.0.min.js b/app/assets/javascripts/shared/mm-foundation-tpls-0.8.0.min.js
new file mode 100644
index 0000000000..8f4e630b9f
--- /dev/null
+++ b/app/assets/javascripts/shared/mm-foundation-tpls-0.8.0.min.js
@@ -0,0 +1,10 @@
+/*
+ * angular-mm-foundation
+ * http://pineconellc.github.io/angular-foundation/
+
+ * Version: 0.8.0 - 2015-10-13
+ * License: MIT
+ * (c) Pinecone, LLC
+ */
+angular.module("mm.foundation",["mm.foundation.tpls","mm.foundation.accordion","mm.foundation.alert","mm.foundation.bindHtml","mm.foundation.buttons","mm.foundation.position","mm.foundation.mediaQueries","mm.foundation.dropdownToggle","mm.foundation.interchange","mm.foundation.transition","mm.foundation.modal","mm.foundation.offcanvas","mm.foundation.pagination","mm.foundation.tooltip","mm.foundation.popover","mm.foundation.progressbar","mm.foundation.rating","mm.foundation.tabs","mm.foundation.topbar","mm.foundation.tour","mm.foundation.typeahead"]),angular.module("mm.foundation.tpls",["template/accordion/accordion-group.html","template/accordion/accordion.html","template/alert/alert.html","template/modal/backdrop.html","template/modal/window.html","template/pagination/pager.html","template/pagination/pagination.html","template/tooltip/tooltip-html-unsafe-popup.html","template/tooltip/tooltip-popup.html","template/popover/popover.html","template/progressbar/bar.html","template/progressbar/progress.html","template/progressbar/progressbar.html","template/rating/rating.html","template/tabs/tab.html","template/tabs/tabset.html","template/topbar/has-dropdown.html","template/topbar/toggle-top-bar.html","template/topbar/top-bar-dropdown.html","template/topbar/top-bar-section.html","template/topbar/top-bar.html","template/tour/tour.html","template/typeahead/typeahead-match.html","template/typeahead/typeahead-popup.html"]),angular.module("mm.foundation.accordion",[]).constant("accordionConfig",{closeOthers:!0}).controller("AccordionController",["$scope","$attrs","accordionConfig",function(a,b,c){this.groups=[],this.closeOthers=function(d){var e=angular.isDefined(b.closeOthers)?a.$eval(b.closeOthers):c.closeOthers;e&&angular.forEach(this.groups,function(a){a!==d&&(a.isOpen=!1)})},this.addGroup=function(a){var b=this;this.groups.push(a),a.$on("$destroy",function(){b.removeGroup(a)})},this.removeGroup=function(a){var b=this.groups.indexOf(a);-1!==b&&this.groups.splice(b,1)}}]).directive("accordion",function(){return{restrict:"EA",controller:"AccordionController",transclude:!0,replace:!1,templateUrl:"template/accordion/accordion.html"}}).directive("accordionGroup",["$parse",function(a){return{require:"^accordion",restrict:"EA",transclude:!0,replace:!0,templateUrl:"template/accordion/accordion-group.html",scope:{heading:"@"},controller:function(){this.setHeading=function(a){this.heading=a}},link:function(b,c,d,e){var f,g;e.addGroup(b),b.isOpen=!1,d.isOpen&&(f=a(d.isOpen),g=f.assign,b.$parent.$watch(f,function(a){b.isOpen=!!a})),b.$watch("isOpen",function(a){a&&e.closeOthers(b),g&&g(b.$parent,a)})}}}]).directive("accordionHeading",function(){return{restrict:"EA",transclude:!0,template:"",replace:!0,require:"^accordionGroup",compile:function(a,b,c){return function(a,b,d,e){e.setHeading(c(a,function(){}))}}}}).directive("accordionTransclude",function(){return{require:"^accordionGroup",link:function(a,b,c,d){a.$watch(function(){return d[c.accordionTransclude]},function(a){a&&(b.html(""),b.append(a))})}}}),angular.module("mm.foundation.alert",[]).controller("AlertController",["$scope","$attrs",function(a,b){a.closeable="close"in b&&"undefined"!=typeof b.close}]).directive("alert",function(){return{restrict:"EA",controller:"AlertController",templateUrl:"template/alert/alert.html",transclude:!0,replace:!0,scope:{type:"=",close:"&"}}}),angular.module("mm.foundation.bindHtml",[]).directive("bindHtmlUnsafe",function(){return function(a,b,c){b.addClass("ng-binding").data("$binding",c.bindHtmlUnsafe),a.$watch(c.bindHtmlUnsafe,function(a){b.html(a||"")})}}),angular.module("mm.foundation.buttons",[]).constant("buttonConfig",{activeClass:"active",toggleEvent:"click"}).controller("ButtonsController",["buttonConfig",function(a){this.activeClass=a.activeClass,this.toggleEvent=a.toggleEvent}]).directive("btnRadio",function(){return{require:["btnRadio","ngModel"],controller:"ButtonsController",link:function(a,b,c,d){var e=d[0],f=d[1];f.$render=function(){b.toggleClass(e.activeClass,angular.equals(f.$modelValue,a.$eval(c.btnRadio)))},b.bind(e.toggleEvent,function(){b.hasClass(e.activeClass)||a.$apply(function(){f.$setViewValue(a.$eval(c.btnRadio)),f.$render()})})}}}).directive("btnCheckbox",function(){return{require:["btnCheckbox","ngModel"],controller:"ButtonsController",link:function(a,b,c,d){function e(){return g(c.btnCheckboxTrue,!0)}function f(){return g(c.btnCheckboxFalse,!1)}function g(b,c){var d=a.$eval(b);return angular.isDefined(d)?d:c}var h=d[0],i=d[1];i.$render=function(){b.toggleClass(h.activeClass,angular.equals(i.$modelValue,e()))},b.bind(h.toggleEvent,function(){a.$apply(function(){i.$setViewValue(b.hasClass(h.activeClass)?f():e()),i.$render()})})}}}),angular.module("mm.foundation.position",[]).factory("$position",["$document","$window",function(a,b){function c(a,c){return a.currentStyle?a.currentStyle[c]:b.getComputedStyle?b.getComputedStyle(a)[c]:a.style[c]}function d(a){return"static"===(c(a,"position")||"static")}var e=function(b){for(var c=a[0],e=b.offsetParent||c;e&&e!==c&&d(e);)e=e.offsetParent;return e||c};return{position:function(b){var c=this.offset(b),d={top:0,left:0},f=e(b[0]);f!=a[0]&&(d=this.offset(angular.element(f)),d.top+=f.clientTop-f.scrollTop,d.left+=f.clientLeft-f.scrollLeft);var g=b[0].getBoundingClientRect();return{width:g.width||b.prop("offsetWidth"),height:g.height||b.prop("offsetHeight"),top:c.top-d.top,left:c.left-d.left}},offset:function(c){var d=c[0].getBoundingClientRect();return{width:d.width||c.prop("offsetWidth"),height:d.height||c.prop("offsetHeight"),top:d.top+(b.pageYOffset||a[0].body.scrollTop||a[0].documentElement.scrollTop),left:d.left+(b.pageXOffset||a[0].body.scrollLeft||a[0].documentElement.scrollLeft)}}}}]),angular.module("mm.foundation.mediaQueries",[]).factory("matchMedia",["$document","$window",function(a,b){return b.matchMedia||function(a){var b,c=a.documentElement,d=c.firstElementChild||c.firstChild,e=a.createElement("body"),f=a.createElement("div");return f.id="mq-test-1",f.style.cssText="position:absolute;top:-100em",e.style.background="none",e.appendChild(f),function(a){return f.innerHTML='',c.insertBefore(e,d),b=42===f.offsetWidth,c.removeChild(e),{matches:b,media:a}}}(a[0])}]).factory("mediaQueries",["$document","matchMedia",function(a,b){var c=angular.element(a[0].querySelector("head"));c.append(''),c.append(''),c.append(''),c.append('');var d=/^[\/\\'"]+|(;\s?})+|[\/\\'"]+$/g,e={topbar:getComputedStyle(c[0].querySelector("meta.foundation-mq-topbar")).fontFamily.replace(d,""),small:getComputedStyle(c[0].querySelector("meta.foundation-mq-small")).fontFamily.replace(d,""),medium:getComputedStyle(c[0].querySelector("meta.foundation-mq-medium")).fontFamily.replace(d,""),large:getComputedStyle(c[0].querySelector("meta.foundation-mq-large")).fontFamily.replace(d,"")};return{topbarBreakpoint:function(){return!b(e.topbar).matches},small:function(){return b(e.small).matches},medium:function(){return b(e.medium).matches},large:function(){return b(e.large).matches}}}]),angular.module("mm.foundation.dropdownToggle",["mm.foundation.position","mm.foundation.mediaQueries"]).controller("DropdownToggleController",["$scope","$attrs","mediaQueries",function(a,b,c){this.small=function(){return c.small()&&!c.medium()}}]).directive("dropdownToggle",["$document","$window","$location","$position",function(a,b,c,d){var e=null,f=angular.noop;return{restrict:"CA",controller:"DropdownToggleController",link:function(c,g,h,i){var j=g.parent(),k=angular.element(a[0].querySelector(h.dropdownToggle)),l=function(){return j.hasClass("has-dropdown")},m=function(c){k=angular.element(a[0].querySelector(h.dropdownToggle));var m=g===e;if(c.preventDefault(),c.stopPropagation(),e&&f(),!m&&!g.hasClass("disabled")&&!g.prop("disabled")){k.css("display","block"),k.addClass("f-open-dropdown");var n=d.offset(g),o=d.offset(angular.element(k[0].offsetParent)),p=k.prop("offsetWidth"),q={top:n.top-o.top+n.height+"px"};if(i.small())q.left=Math.max((o.width-p)/2,8)+"px",q.position="absolute",q.width="95%",q["max-width"]="none";else{var r=Math.round(n.left-o.left),s=b.innerWidth-p-8;r>s&&(r=s,k.removeClass("left").addClass("right")),q.left=r+"px",q.position=null,q["max-width"]=null}k.css(q),g.addClass("expanded"),l()&&j.addClass("hover"),e=g,f=function(){a.off("click",f),k.css("display","none"),k.removeClass("f-open-dropdown"),g.removeClass("expanded"),f=angular.noop,e=null,j.hasClass("hover")&&j.removeClass("hover")},a.on("click",f)}};k&&k.css("display","none"),c.$watch("$location.path",function(){f()}),g.on("click",m),g.on("$destroy",function(){g.off("click",m)})}}}]),angular.module("mm.foundation.interchange",["mm.foundation.mediaQueries"]).factory("interchangeQueries",["$document",function(a){for(var b,c,d={"default":"only screen",landscape:"only screen and (orientation: landscape)",portrait:"only screen and (orientation: portrait)",retina:"only screen and (-webkit-min-device-pixel-ratio: 2),only screen and (min--moz-device-pixel-ratio: 2),only screen and (-o-min-device-pixel-ratio: 2/1),only screen and (min-device-pixel-ratio: 2),only screen and (min-resolution: 192dpi),only screen and (min-resolution: 2dppx)"},e="foundation-mq-",f=["small","medium","large","xlarge","xxlarge"],g=angular.element(a[0].querySelector("head")),h=0;h'),b=getComputedStyle(g[0].querySelector("meta."+e+f[h])),c=b.fontFamily.replace(/^[\/\\'"]+|(;\s?})+|[\/\\'"]+$/g,""),d[f[h]]=c;return d}]).factory("interchangeQueriesManager",["interchangeQueries",function(a){return{add:function(b,c){return b&&c&&angular.isString(b)&&angular.isString(c)&&!a[b]?(a[b]=c,!0):!1}}}]).factory("interchangeTools",["$window","matchMedia","interchangeQueries",function(a,b,c){var d=function(a){for(var b,c=a.split(/\[(.*?)\]/),d=c.length,e=/^(.+)\,\ \((.+)\)$/,f={};d--;)c[d].replace(/[\W\d]+/,"").length>4&&(b=e.exec(c[d]),b&&3===b.length&&(f[b[2]]=b[1]));return f},e=function(a){var d,e,f;for(d in a)if(e=c[d]||d,f=b(e),f.matches)return a[d]};return{parseAttribute:d,findCurrentMediaFile:e}}]).directive("interchange",["$window","$rootScope","interchangeTools",function(a,b,c){var d=/[A-Za-z0-9-_]+\.(jpg|jpeg|png|gif|bmp|tiff)\ *,/i;return{restrict:"A",scope:!0,priority:450,compile:function(e,f){return"DIV"!==e[0].nodeName||d.test(f.interchange)||e.html(''),{pre:function(){},post:function(d,e,f){var g,h;switch(h=e&&e[0]&&e[0].nodeName,d.fileMap=c.parseAttribute(f.interchange),h){case"DIV":g=c.findCurrentMediaFile(d.fileMap),d.type=/[A-Za-z0-9-_]+\.(jpg|jpeg|png|gif|bmp|tiff)$/i.test(g)?"background":"include";break;case"IMG":d.type="image";break;default:return}var i=function(a){var f=c.findCurrentMediaFile(d.fileMap);if(!d.currentFile||d.currentFile!==f){switch(d.currentFile=f,d.type){case"image":e.attr("src",d.currentFile);break;case"background":e.css("background-image","url("+d.currentFile+")")}b.$emit("replace",e,d),a&&d.$apply()}};i(),a.addEventListener("resize",i),d.$on("$destroy",function(){a.removeEventListener("resize",i)})}}}}}]),angular.module("mm.foundation.transition",[]).factory("$transition",["$q","$timeout","$rootScope",function(a,b,c){function d(a){for(var b in a)if(void 0!==f.style[b])return a[b]}var e=function(d,f,g){g=g||{};var h=a.defer(),i=e[g.animation?"animationEndEventName":"transitionEndEventName"],j=function(){c.$apply(function(){d.unbind(i,j),h.resolve(d)})};return i&&d.bind(i,j),b(function(){angular.isString(f)?d.addClass(f):angular.isFunction(f)?f(d):angular.isObject(f)&&d.css(f),i||h.resolve(d)}),h.promise.cancel=function(){i&&d.unbind(i,j),h.reject("Transition cancelled")},h.promise},f=document.createElement("trans"),g={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd",transition:"transitionend"},h={WebkitTransition:"webkitAnimationEnd",MozTransition:"animationend",OTransition:"oAnimationEnd",transition:"animationend"};return e.transitionEndEventName=d(g),e.animationEndEventName=d(h),e}]),angular.module("mm.foundation.modal",["mm.foundation.transition"]).factory("$$stackedMap",function(){return{createNew:function(){var a=[];return{add:function(b,c){a.push({key:b,value:c})},get:function(b){for(var c=0;c0?c[0].querySelectorAll("[autofocus]")[0].focus():c[0].focus()})}}}]).factory("$modalStack",["$window","$transition","$timeout","$document","$compile","$rootScope","$$stackedMap",function(a,b,c,d,e,f,g){function h(){for(var a=-1,b=q.keys(),c=0;c0),j()})}function j(){if(m&&-1==h()){var a=n;k(m,n,150,function(){a.$destroy(),a=null}),m=void 0,n=void 0}}function k(a,d,e,f){function g(){g.done||(g.done=!0,a.remove(),f&&f())}d.animate=!1;var h=b.transitionEndEventName;if(h){var i=c(g,e);a.bind(h,function(){c.cancel(i),g(),d.$apply()})}else c(g,0)}function l(b,c){angular.isUndefined(c)&&(c=0);var d=a.pageYOffset||0;return c+d}var m,n,o,p="modal-open",q=g.createNew(),r={};return f.$watch(h,function(a){n&&(n.index=a)}),d.bind("keydown",function(a){var b;27===a.which&&(b=q.top(),b&&b.value.keyboard&&f.$apply(function(){r.dismiss(b.key)}))}),r.open=function(b,c){b.options={deferred:c.deferred,modalScope:c.scope,backdrop:c.backdrop,keyboard:c.keyboard,parent:c.parent},q.add(b,b.options);var g=d.find(c.parent).eq(0),i=h();i>=0&&!m&&(n=f.$new(!0),n.index=i,m=e("")(n),g.append(m));var j=angular.element('');g.append(j[0]),o=parseInt(a.getComputedStyle(j[0]).top)||0;var k=l(j,o);j.remove();var r=angular.element('').attr({"window-class":c.windowClass,index:q.length()-1,animate:"animate"});r.html(c.content);var s=e(r)(c.scope);q.top().value.modalDomEl=s,g.append(s),g.addClass(p)},r.reposition=function(a){var b=q.get(a).value;if(b){var c=b.modalDomEl,d=l(c,o);c.css("top",d+"px")}},r.close=function(a,b){var c=q.get(a);c&&(c.value.deferred.resolve(b),i(a))},r.dismiss=function(a,b){var c=q.get(a);c&&(c.value.deferred.reject(b),i(a))},r.dismissAll=function(a){for(var b=this.getTop();b;)this.dismiss(b.key,a),b=this.getTop()},r.getTop=function(){return q.top()},r}]).provider("$modal",function(){var a={options:{backdrop:!0,keyboard:!0},$get:["$injector","$rootScope","$q","$http","$templateCache","$controller","$modalStack",function(b,c,d,e,f,g,h){function i(a){return a.template?d.when(a.template):e.get(a.templateUrl,{cache:f}).then(function(a){return a.data})}function j(a){var c=[];return angular.forEach(a,function(a){(angular.isFunction(a)||angular.isArray(a))&&c.push(d.when(b.invoke(a)))}),c}var k={};return k.open=function(b){var e=d.defer(),f=d.defer(),k={result:e.promise,opened:f.promise,close:function(a){h.close(k,a)},dismiss:function(a){h.dismiss(k,a)},reposition:function(){h.reposition(k)}};if(b=angular.extend({},a.options,b),b.resolve=b.resolve||{},!b.template&&!b.templateUrl)throw new Error("One of template or templateUrl options is required.");var l=d.all([i(b)].concat(j(b.resolve)));return l.then(function(a){var d=(b.scope||c).$new();d.$close=k.close,d.$dismiss=k.dismiss;var f,i={},j=1;b.controller&&(i.$scope=d,i.$modalInstance=k,angular.forEach(b.resolve,function(b,c){i[c]=a[j++]}),f=g(b.controller,i),b.controllerAs&&(d[b.controllerAs]=f)),h.open(k,{scope:d,deferred:e,content:a[0],backdrop:b.backdrop,keyboard:b.keyboard,windowClass:b.windowClass,parent:b.parent||"body"})},function(a){e.reject(a)}),l.then(function(){f.resolve(!0)},function(){f.reject(!1)}),k},k}]};return a}),angular.module("mm.foundation.offcanvas",[]).directive("offCanvasWrap",["$window",function(a){return{scope:{},restrict:"C",link:function(b,c){var d=angular.element(a),e=b.sidebar=c;b.hide=function(){e.removeClass("move-left"),e.removeClass("move-right")},d.bind("resize.body",b.hide),b.$on("$destroy",function(){d.unbind("resize.body",b.hide)})},controller:["$scope",function(a){this.leftToggle=function(){a.sidebar.toggleClass("move-right")},this.rightToggle=function(){a.sidebar.toggleClass("move-left")},this.hide=function(){a.hide()}}]}}]).directive("leftOffCanvasToggle",[function(){return{require:"^offCanvasWrap",restrict:"C",link:function(a,b,c,d){b.on("click",function(){d.leftToggle()})}}}]).directive("rightOffCanvasToggle",[function(){return{require:"^offCanvasWrap",restrict:"C",link:function(a,b,c,d){b.on("click",function(){d.rightToggle()})}}}]).directive("exitOffCanvas",[function(){return{require:"^offCanvasWrap",restrict:"C",link:function(a,b,c,d){b.on("click",function(){d.hide()})}}}]).directive("offCanvasList",[function(){return{require:"^offCanvasWrap",restrict:"C",link:function(a,b,c,d){b.on("click",function(){d.hide()})}}}]),angular.module("mm.foundation.pagination",[]).controller("PaginationController",["$scope","$attrs","$parse","$interpolate",function(a,b,c,d){var e=this,f=b.numPages?c(b.numPages).assign:angular.noop;this.init=function(d){b.itemsPerPage?a.$parent.$watch(c(b.itemsPerPage),function(b){e.itemsPerPage=parseInt(b,10),a.totalPages=e.calculateTotalPages()}):this.itemsPerPage=d},this.noPrevious=function(){return 1===this.page},this.noNext=function(){return this.page===a.totalPages},this.isActive=function(a){return this.page===a},this.calculateTotalPages=function(){var b=this.itemsPerPage<1?1:Math.ceil(a.totalItems/this.itemsPerPage);return Math.max(b||0,1)},this.getAttributeValue=function(b,c,e){return angular.isDefined(b)?e?d(b)(a.$parent):a.$parent.$eval(b):c},this.render=function(){this.page=parseInt(a.page,10)||1,this.page>0&&this.page<=a.totalPages&&(a.pages=this.getPages(this.page,a.totalPages))},a.selectPage=function(b){!e.isActive(b)&&b>0&&b<=a.totalPages&&(a.page=b,a.onSelectPage({page:b}))},a.$watch("page",function(){e.render()}),a.$watch("totalItems",function(){a.totalPages=e.calculateTotalPages()}),a.$watch("totalPages",function(b){f(a.$parent,b),e.page>b?a.selectPage(b):e.render()})}]).constant("paginationConfig",{itemsPerPage:10,boundaryLinks:!1,directionLinks:!0,firstText:"First",previousText:"Previous",nextText:"Next",lastText:"Last",rotate:!0}).directive("pagination",["$parse","paginationConfig",function(a,b){return{restrict:"EA",scope:{page:"=",totalItems:"=",onSelectPage:" &"},controller:"PaginationController",templateUrl:"template/pagination/pagination.html",replace:!0,link:function(c,d,e,f){function g(a,b,c,d){return{number:a,text:b,active:c,disabled:d}}var h,i=f.getAttributeValue(e.boundaryLinks,b.boundaryLinks),j=f.getAttributeValue(e.directionLinks,b.directionLinks),k=f.getAttributeValue(e.firstText,b.firstText,!0),l=f.getAttributeValue(e.previousText,b.previousText,!0),m=f.getAttributeValue(e.nextText,b.nextText,!0),n=f.getAttributeValue(e.lastText,b.lastText,!0),o=f.getAttributeValue(e.rotate,b.rotate);f.init(b.itemsPerPage),e.maxSize&&c.$parent.$watch(a(e.maxSize),function(a){h=parseInt(a,10),f.render()}),f.getPages=function(a,b){var c=[],d=1,e=b,p=angular.isDefined(h)&&b>h;p&&(o?(d=Math.max(a-Math.floor(h/2),1),e=d+h-1,e>b&&(e=b,d=e-h+1)):(d=(Math.ceil(a/h)-1)*h+1,e=Math.min(d+h-1,b)));for(var q=d;e>=q;q++){var r=g(q,q,f.isActive(q),!1);c.push(r)}if(p&&!o){if(d>1){var s=g(d-1,"...",!1,!1);c.unshift(s)}if(b>e){var t=g(e+1,"...",!1,!1);c.push(t)}}if(j){var u=g(a-1,l,!1,f.noPrevious());c.unshift(u);var v=g(a+1,m,!1,f.noNext());c.push(v)}if(i){var w=g(1,k,!1,f.noPrevious());c.unshift(w);var x=g(b,n,!1,f.noNext());c.push(x)}return c}}}}]).constant("pagerConfig",{itemsPerPage:10,previousText:"« Previous",nextText:"Next »",align:!0}).directive("pager",["pagerConfig",function(a){return{restrict:"EA",scope:{page:"=",totalItems:"=",onSelectPage:" &"},controller:"PaginationController",templateUrl:"template/pagination/pager.html",replace:!0,link:function(b,c,d,e){function f(a,b,c,d,e){return{number:a,text:b,disabled:c,previous:i&&d,next:i&&e}}var g=e.getAttributeValue(d.previousText,a.previousText,!0),h=e.getAttributeValue(d.nextText,a.nextText,!0),i=e.getAttributeValue(d.align,a.align);e.init(a.itemsPerPage),e.getPages=function(a){return[f(a-1,g,e.noPrevious(),!0,!1),f(a+1,h,e.noNext(),!1,!0)]}}}}]),angular.module("mm.foundation.tooltip",["mm.foundation.position","mm.foundation.bindHtml"]).provider("$tooltip",function(){function a(a){var b=/[A-Z]/g,c="-";return a.replace(b,function(a,b){return(b?c:"")+a.toLowerCase()})}var b={placement:"top",animation:!0,popupDelay:0},c={mouseenter:"mouseleave",click:"click",focus:"blur"},d={};this.options=function(a){angular.extend(d,a)},this.setTriggers=function(a){angular.extend(c,a)},this.$get=["$window","$compile","$timeout","$parse","$document","$position","$interpolate",function(e,f,g,h,i,j,k){return function(e,l,m){function n(a){var b=a||o.trigger||m,d=c[b]||b;return{show:b,hide:d}}var o=angular.extend({},b,d),p=a(e),q=k.startSymbol(),r=k.endSymbol(),s="';return{restrict:"EA",scope:!0,compile:function(){var a=f(s);return function(b,c,d){function f(){b.tt_isOpen?m():k()}function k(){(!z||b.$eval(d[l+"Enable"]))&&(b.tt_popupDelay?(v=g(p,b.tt_popupDelay,!1),v.then(function(a){a()})):p()())}function m(){b.$apply(function(){q()})}function p(){return b.tt_content?(r(),u&&g.cancel(u),t.css({top:0,left:0,display:"block"}),w?i.find("body").append(t):c.after(t),A(),b.tt_isOpen=!0,b.$digest(),A):angular.noop}function q(){b.tt_isOpen=!1,g.cancel(v),b.tt_animation?u=g(s,500):s()}function r(){t&&s(),t=a(b,function(){}),b.$digest()}function s(){t&&(t.remove(),t=null)}var t,u,v,w=angular.isDefined(o.appendToBody)?o.appendToBody:!1,x=n(void 0),y=!1,z=angular.isDefined(d[l+"Enable"]),A=function(){var a,d,e,f;switch(a=w?j.offset(c):j.position(c),d=t.prop("offsetWidth"),e=t.prop("offsetHeight"),b.tt_placement){case"right":f={top:a.top+a.height/2-e/2,left:a.left+a.width+10};break;case"bottom":f={top:a.top+a.height+10,left:a.left};break;case"left":f={top:a.top+a.height/2-e/2,left:a.left-d-10};break;default:f={top:a.top-e-10,left:a.left}}f.top+="px",f.left+="px",t.css(f)};b.tt_isOpen=!1,d.$observe(e,function(a){b.tt_content=a,!a&&b.tt_isOpen&&q()}),d.$observe(l+"Title",function(a){b.tt_title=a}),d[l+"Placement"]=d[l+"Placement"]||null,d.$observe(l+"Placement",function(a){b.tt_placement=angular.isDefined(a)&&a?a:o.placement}),d[l+"PopupDelay"]=d[l+"PopupDelay"]||null,d.$observe(l+"PopupDelay",function(a){var c=parseInt(a,10);b.tt_popupDelay=isNaN(c)?o.popupDelay:c});var B=function(){y&&(angular.isFunction(x.show)?C():(c.unbind(x.show,k),c.unbind(x.hide,m)))},C=function(){};d[l+"Trigger"]=d[l+"Trigger"]||null,d.$observe(l+"Trigger",function(a){B(),C(),x=n(a),angular.isFunction(x.show)?C=b.$watch(function(){return x.show(b,c,d)},function(a){return g(a?p:q)}):x.show===x.hide?c.bind(x.show,f):(c.bind(x.show,k),c.bind(x.hide,m)),y=!0});var D=b.$eval(d[l+"Animation"]);b.tt_animation=angular.isDefined(D)?!!D:o.animation,d.$observe(l+"AppendToBody",function(a){w=angular.isDefined(a)?h(a)(b):w}),w&&b.$on("$locationChangeSuccess",function(){b.tt_isOpen&&q()}),b.$on("$destroy",function(){g.cancel(u),g.cancel(v),B(),C(),s()})}}}}}]}).directive("tooltipPopup",function(){return{restrict:"EA",replace:!0,scope:{content:"@",placement:"@",animation:"&",isOpen:"&"},templateUrl:"template/tooltip/tooltip-popup.html"}}).directive("tooltip",["$tooltip",function(a){return a("tooltip","tooltip","mouseenter")}]).directive("tooltipHtmlUnsafePopup",function(){return{restrict:"EA",replace:!0,scope:{content:"@",placement:"@",animation:"&",isOpen:"&"},templateUrl:"template/tooltip/tooltip-html-unsafe-popup.html"}}).directive("tooltipHtmlUnsafe",["$tooltip",function(a){return a("tooltipHtmlUnsafe","tooltip","mouseenter")}]),angular.module("mm.foundation.popover",["mm.foundation.tooltip"]).directive("popoverPopup",function(){return{restrict:"EA",replace:!0,scope:{title:"@",content:"@",placement:"@",animation:"&",isOpen:"&"},templateUrl:"template/popover/popover.html"}}).directive("popover",["$tooltip",function(a){return a("popover","popover","click")}]),angular.module("mm.foundation.progressbar",["mm.foundation.transition"]).constant("progressConfig",{animate:!0,max:100}).controller("ProgressController",["$scope","$attrs","progressConfig","$transition",function(a,b,c,d){var e=this,f=[],g=angular.isDefined(b.max)?a.$parent.$eval(b.max):c.max,h=angular.isDefined(b.animate)?a.$parent.$eval(b.animate):c.animate;this.addBar=function(a,b){var c=0,d=a.$parent.$index;angular.isDefined(d)&&f[d]&&(c=f[d].value),f.push(a),this.update(b,a.value,c),a.$watch("value",function(a,c){a!==c&&e.update(b,a,c)}),a.$on("$destroy",function(){e.removeBar(a)})},this.update=function(a,b,c){var e=this.getPercentage(b);h?(a.css("width",this.getPercentage(c)+"%"),d(a,{width:e+"%"})):a.css({transition:"none",width:e+"%"})},this.removeBar=function(a){f.splice(f.indexOf(a),1)},this.getPercentage=function(a){return Math.round(100*a/g)}}]).directive("progress",function(){return{restrict:"EA",replace:!0,transclude:!0,controller:"ProgressController",require:"progress",scope:{},template:''}}).directive("bar",function(){return{restrict:"EA",replace:!0,transclude:!0,require:"^progress",scope:{value:"=",type:"@"},templateUrl:"template/progressbar/bar.html",link:function(a,b,c,d){d.addBar(a,b)}}}).directive("progressbar",function(){return{restrict:"EA",replace:!0,transclude:!0,controller:"ProgressController",scope:{value:"=",type:"@"},templateUrl:"template/progressbar/progressbar.html",link:function(a,b,c,d){d.addBar(a,angular.element(b.children()[0]))}}}),angular.module("mm.foundation.rating",[]).constant("ratingConfig",{max:5,stateOn:null,stateOff:null}).controller("RatingController",["$scope","$attrs","$parse","ratingConfig",function(a,b,c,d){this.maxRange=angular.isDefined(b.max)?a.$parent.$eval(b.max):d.max,this.stateOn=angular.isDefined(b.stateOn)?a.$parent.$eval(b.stateOn):d.stateOn,this.stateOff=angular.isDefined(b.stateOff)?a.$parent.$eval(b.stateOff):d.stateOff,this.createRateObjects=function(a){for(var b={stateOn:this.stateOn,stateOff:this.stateOff},c=0,d=a.length;d>c;c++)a[c]=angular.extend({index:c},b,a[c]);return a},a.range=this.createRateObjects(angular.isDefined(b.ratingStates)?angular.copy(a.$parent.$eval(b.ratingStates)):new Array(this.maxRange)),a.rate=function(b){a.value===b||a.readonly||(a.value=b)},a.enter=function(b){a.readonly||(a.val=b),a.onHover({value:b})},a.reset=function(){a.val=angular.copy(a.value),a.onLeave()},a.$watch("value",function(b){a.val=b}),a.readonly=!1,b.readonly&&a.$parent.$watch(c(b.readonly),function(b){a.readonly=!!b})}]).directive("rating",function(){return{restrict:"EA",scope:{value:"=",onHover:"&",onLeave:"&"},controller:"RatingController",templateUrl:"template/rating/rating.html",replace:!0}}),angular.module("mm.foundation.tabs",[]).controller("TabsetController",["$scope",function(a){var b=this,c=b.tabs=a.tabs=[];angular.isUndefined(a.openOnLoad)&&(a.openOnLoad=!0),b.select=function(a){angular.forEach(c,function(a){a.active=!1}),a.active=!0},b.addTab=function(d){c.push(d),a.openOnLoad&&(1===c.length||d.active)&&b.select(d)},b.removeTab=function(a){var d=c.indexOf(a);if(a.active&&c.length>1){var e=d==c.length-1?d-1:d+1;b.select(c[e])}c.splice(d,1)}}]).directive("tabset",function(){return{restrict:"EA",transclude:!0,replace:!0,scope:{openOnLoad:"=?"},controller:"TabsetController",templateUrl:"template/tabs/tabset.html",link:function(a,b,c){a.vertical=angular.isDefined(c.vertical)?a.$parent.$eval(c.vertical):!1,a.justified=angular.isDefined(c.justified)?a.$parent.$eval(c.justified):!1,a.type=angular.isDefined(c.type)?a.$parent.$eval(c.type):"tabs"}}}).directive("tab",["$parse",function(a){return{require:"^tabset",restrict:"EA",replace:!0,templateUrl:"template/tabs/tab.html",transclude:!0,scope:{heading:"@",onSelect:"&select",onDeselect:"&deselect"},controller:function(){},compile:function(b,c,d){return function(b,c,e,f){var g,h;e.active?(g=a(e.active),h=g.assign,b.$parent.$watch(g,function(a,c){a!==c&&(b.active=!!a)}),b.active=g(b.$parent)):h=g=angular.noop,b.$watch("active",function(a){angular.isFunction(h)&&(h(b.$parent,a),a?(f.select(b),b.onSelect()):b.onDeselect())}),b.disabled=!1,e.disabled&&b.$parent.$watch(a(e.disabled),function(a){b.disabled=!!a}),b.select=function(){b.disabled||(b.active=!0)},f.addTab(b),b.$on("$destroy",function(){f.removeTab(b)}),b.$transcludeFn=d}}}}]).directive("tabHeadingTransclude",[function(){return{restrict:"A",require:"^tab",link:function(a,b){a.$watch("headingElement",function(a){a&&(b.html(""),b.append(a))})}}}]).directive("tabContentTransclude",function(){function a(a){return a.tagName&&(a.hasAttribute("tab-heading")||a.hasAttribute("data-tab-heading")||"tab-heading"===a.tagName.toLowerCase()||"data-tab-heading"===a.tagName.toLowerCase())}return{restrict:"A",require:"^tabset",link:function(b,c,d){var e=b.$eval(d.tabContentTransclude);e.$transcludeFn(e.$parent,function(b){angular.forEach(b,function(b){a(b)?e.headingElement=b:c.append(b)})})}}}),angular.module("mm.foundation.topbar",["mm.foundation.mediaQueries"]).factory("closest",[function(){return function(a,b){for(var c=function(a,b){for(var c=(a.parentNode||a.document).querySelectorAll(b),d=-1;c[++d]&&c[d]!=a;);return!!c[d]},d=a[0];d;){if(c(d,b))return angular.element(d);d=d.parentElement}return!1}}]).directive("topBar",["$timeout","$compile","$window","$document","mediaQueries",function(a,b,c,d,e){return{scope:{stickyClass:"@",backText:"@",stickyOn:"=",customBackText:"=",isHover:"=",mobileShowParentLink:"=",scrolltop:"="},restrict:"EA",replace:!0,templateUrl:"template/topbar/top-bar.html",transclude:!0,controller:["$window","$scope","closest",function(b,c,f){c.settings={},c.settings.stickyClass=c.stickyClass||"sticky",c.settings.backText=c.backText||"Back",c.settings.stickyOn=c.stickyOn||"all",c.settings.customBackText=void 0===c.customBackText?!0:c.customBackText,c.settings.isHover=void 0===c.isHover?!0:c.isHover,c.settings.mobileShowParentLink=void 0===c.mobileShowParentLink?!0:c.mobileShowParentLink,c.settings.scrolltop=void 0===c.scrolltop?!0:c.scrolltop,this.settings=c.settings,c.index=0;var g=function(a){var b=a.offsetHeight,c=a.currentStyle||getComputedStyle(a);return b+=parseInt(c.marginTop,10)+parseInt(c.marginBottom,10)},h=[];this.addSection=function(a){h.push(a)},this.removeSection=function(a){var b=h.indexOf(a);b>-1&&h.splice(b,1)};var i=/rtl/i.test(d.find("html").attr("dir"))?"right":"left";c.$watch("index",function(a){for(var b=0;bb&&!g.hasClass("fixed")?(g.addClass("fixed"),h.css("padding-top",a.originalHeight+"px")):c.pageYOffset<=b&&g.hasClass("fixed")&&(g.removeClass("fixed"),h.css("padding-top",""))}},l=function(){var b=e.topbarBreakpoint();if(i!==b){i=e.topbarBreakpoint(),f.removeClass("expanded"),f.parent().removeClass("expanded"),a.height="";var c=angular.element(f[0].querySelectorAll("section"));angular.forEach(c,function(a){angular.element(a.querySelectorAll("li.moved")).removeClass("moved")}),a.$apply()}},m=function(){k(),a.$apply()};if(a.toggle=function(b){if(!e.topbarBreakpoint())return!1;var d=void 0===b?!f.hasClass("expanded"):b;d?f.addClass("expanded"):f.removeClass("expanded"),a.settings.scrolltop?!d&&f.hasClass("fixed")?(f.parent().addClass("fixed"),f.removeClass("fixed"),h.css("padding-top",a.originalHeight+"px")):d&&f.parent().hasClass("fixed")&&(f.parent().removeClass("fixed"),f.addClass("fixed"),h.css("padding-top",""),c.scrollTo(0,0)):(j()&&f.parent().addClass("fixed"),f.parent().hasClass("fixed")&&(d?(f.addClass("fixed"),f.parent().addClass("expanded"),h.css("padding-top",a.originalHeight+"px")):(f.removeClass("fixed"),f.parent().removeClass("expanded"),k())))},g.hasClass("fixed")||j()){a.stickyTopbar=!0,a.height=g[0].offsetHeight;var n=g[0].getBoundingClientRect().top+c.pageYOffset}else a.height=f[0].offsetHeight;a.originalHeight=a.height,a.$watch("height",function(a){a?f.css("height",a+"px"):f.css("height","")}),angular.element(c).bind("resize",l),angular.element(c).bind("scroll",m),a.$on("$destroy",function(){angular.element(c).unbind("scroll",l),angular.element(c).unbind("resize",m)}),g.hasClass("fixed")&&h.css("padding-top",a.originalHeight+"px")}}}]).directive("toggleTopBar",["closest",function(a){return{scope:{},require:"^topBar",restrict:"A",replace:!0,templateUrl:"template/topbar/toggle-top-bar.html",transclude:!0,link:function(b,c,d,e){c.bind("click",function(b){var c=a(angular.element(b.currentTarget),"li");c.hasClass("back")||c.hasClass("has-dropdown")||e.toggle()}),b.$on("$destroy",function(){c.unbind("click")})}}}]).directive("topBarSection",["$compile","closest",function(a,b){return{scope:{},require:"^topBar",restrict:"EA",replace:!0,templateUrl:"template/topbar/top-bar-section.html",transclude:!0,link:function(a,c,d,e){var f=c;a.reset=function(){angular.element(f[0].querySelectorAll("li.moved")).removeClass("moved")},a.move=function(a,b){f.css("left"===a?{left:-100*b+"%"}:{right:-100*b+"%"})},e.addSection(a),a.$on("$destroy",function(){e.removeSection(a)});var g=f[0].querySelectorAll("li>a");angular.forEach(g,function(c){var d=angular.element(c),f=b(d,"li");f.hasClass("has-dropdown")||f.hasClass("back")||f.hasClass("title")||(d.bind("click",function(){e.toggle(!1)}),a.$on("$destroy",function(){d.bind("click")}))})}}}]).directive("hasDropdown",["mediaQueries",function(a){return{scope:{},require:"^topBar",restrict:"A",templateUrl:"template/topbar/has-dropdown.html",replace:!0,transclude:!0,link:function(b,c,d,e){b.triggerLink=c.children("a")[0];var f=angular.element(b.triggerLink);f.bind("click",function(a){e.forward(a)}),b.$on("$destroy",function(){f.unbind("click")}),c.bind("mouseenter",function(){e.settings.isHover&&!a.topbarBreakpoint()&&c.addClass("not-click")}),c.bind("click",function(){e.settings.isHover||a.topbarBreakpoint()||c.toggleClass("not-click")}),c.bind("mouseleave",function(){c.removeClass("not-click")}),b.$on("$destroy",function(){c.unbind("click"),c.unbind("mouseenter"),c.unbind("mouseleave")})},controller:["$window","$scope",function(a,b){this.triggerLink=b.triggerLink}]}}]).directive("topBarDropdown",["$compile",function(a){return{scope:{},require:["^topBar","^hasDropdown"],restrict:"A",replace:!0,templateUrl:"template/topbar/top-bar-dropdown.html",transclude:!0,link:function(b,c,d,e){var f,g=e[0],h=e[1],i=angular.element(h.triggerLink),j=i.attr("href");b.linkText=i.text(),b.back=function(a){g.back(a)},b.backText=g.settings.customBackText?g.settings.backText:"« "+i.html(),f=angular.element(g.settings.mobileShowParentLink&&j&&j.length>1?'{{linkText}}':''),a(f)(b),c.prepend(f)}}}]),angular.module("mm.foundation.tour",["mm.foundation.position","mm.foundation.tooltip"]).service("$tour",["$window",function(a){function b(){try{return parseInt(a.localStorage.getItem("mm.tour.step"),10)}catch(b){if("SecurityError"!==b.name)throw b}}function c(){try{a.localStorage.setItem("mm.tour.step",e)}catch(b){if("SecurityError"!==b.name)throw b}}function d(a){e=a,c()}var e=b(),f={};this.add=function(a,b){f[a]=b},this.has=function(a){return!!f[a]},this.isActive=function(){return e>0},this.current=function(a){return a?void d(e):e},this.start=function(){d(1)},this.next=function(){d(e+1)},this.end=function(){d(0)}}]).directive("stepTextPopup",["$tour",function(a){return{restrict:"EA",replace:!0,scope:{title:"@",content:"@",placement:"@",animation:"&",isOpen:"&"},templateUrl:"template/tour/tour.html",link:function(b,c){b.isLastStep=function(){return!a.has(a.current()+1)},b.endTour=function(){c.remove(),a.end()},b.nextStep=function(){c.remove(),a.next()},b.$on("$locationChangeSuccess",b.endTour)}}}]).directive("stepText",["$position","$tooltip","$tour","$window",function(a,b,c,d){function e(a){var b=a[0].getBoundingClientRect();return b.top>=0&&b.left>=0&&b.bottom<=d.innerHeight-80&&b.right<=d.innerWidth}function f(b,f,g){var h=parseInt(g.stepIndex,10);if(c.isActive()&&h&&(c.add(h,g),h===c.current())){if(!e(f)){var i=a.offset(f);d.scrollTo(0,i.top-d.innerHeight/2)}return!0}return!1}return b("stepText","step",f)}]),angular.module("mm.foundation.typeahead",["mm.foundation.position","mm.foundation.bindHtml"]).factory("typeaheadParser",["$parse",function(a){var b=/^\s*(.*?)(?:\s+as\s+(.*?))?\s+for\s+(?:([\$\w][\$\w\d]*))\s+in\s+(.*)$/;return{parse:function(c){var d=c.match(b);if(!d)throw new Error("Expected typeahead specification in form of '_modelValue_ (as _label_)? for _item_ in _collection_' but got '"+c+"'.");return{itemName:d[3],source:a(d[4]),viewMapper:a(d[2]||d[1]),modelMapper:a(d[1])}}}}]).directive("typeahead",["$compile","$parse","$q","$timeout","$document","$position","typeaheadParser",function(a,b,c,d,e,f,g){var h=[9,13,27,38,40];return{require:"ngModel",link:function(i,j,k,l){var m,n=i.$eval(k.typeaheadMinLength)||1,o=i.$eval(k.typeaheadWaitMs)||0,p=i.$eval(k.typeaheadEditable)!==!1,q=b(k.typeaheadLoading).assign||angular.noop,r=b(k.typeaheadOnSelect),s=k.typeaheadInputFormatter?b(k.typeaheadInputFormatter):void 0,t=k.typeaheadAppendToBody?b(k.typeaheadAppendToBody):!1,u=b(k.ngModel).assign,v=g.parse(k.typeahead),w=angular.element("");w.attr({matches:"matches",active:"activeIdx",select:"select(activeIdx)",query:"query",position:"position"}),angular.isDefined(k.typeaheadTemplateUrl)&&w.attr("template-url",k.typeaheadTemplateUrl);var x=i.$new();i.$on("$destroy",function(){x.$destroy()});var y=function(){x.matches=[],x.activeIdx=-1},z=function(a){var b={$viewValue:a};q(i,!0),c.when(v.source(i,b)).then(function(c){if(a===l.$viewValue&&m)if(c.length>0){x.activeIdx=0,x.matches.length=0;for(var d=0;d=n?o>0?(A&&d.cancel(A),A=d(function(){z(a)},o)):z(a):(q(i,!1),y()),p?a:a?void l.$setValidity("editable",!1):(l.$setValidity("editable",!0),a)}),l.$formatters.push(function(a){var b,c,d={};return s?(d.$model=a,s(i,d)):(d[v.itemName]=a,b=v.viewMapper(i,d),d[v.itemName]=void 0,c=v.viewMapper(i,d),b!==c?b:a)}),x.select=function(a){var b,c,d={};d[v.itemName]=c=x.matches[a].model,b=v.modelMapper(i,d),u(i,b),l.$setValidity("editable",!0),r(i,{$item:c,$model:b,$label:v.viewMapper(i,d)}),y(),j[0].focus()},j.bind("keydown",function(a){0!==x.matches.length&&-1!==h.indexOf(a.which)&&(a.preventDefault(),40===a.which?(x.activeIdx=(x.activeIdx+1)%x.matches.length,x.$digest()):38===a.which?(x.activeIdx=(x.activeIdx?x.activeIdx:x.matches.length)-1,x.$digest()):13===a.which||9===a.which?x.$apply(function(){x.select(x.activeIdx)}):27===a.which&&(a.stopPropagation(),y(),x.$digest()))}),j.bind("blur",function(){m=!1}),j.bind("focus",function(){m=!0});var B=function(a){j[0]!==a.target&&(y(),x.$digest())};e.bind("click",B),i.$on("$destroy",function(){e.unbind("click",B)});var C=a(w)(x);t?e.find("body").append(C):j.after(C)}}}]).directive("typeaheadPopup",function(){return{restrict:"EA",scope:{matches:"=",query:"=",active:"=",position:"=",select:"&"},replace:!0,templateUrl:"template/typeahead/typeahead-popup.html",link:function(a,b,c){a.templateUrl=c.templateUrl,a.isOpen=function(){return a.matches.length>0},a.isActive=function(b){return a.active==b},a.selectActive=function(b){a.active=b},a.selectMatch=function(b){a.select({activeIdx:b})}}}}).directive("typeaheadMatch",["$http","$templateCache","$compile","$parse",function(a,b,c,d){return{restrict:"EA",scope:{index:"=",match:"=",query:"="},link:function(e,f,g){var h=d(g.templateUrl)(e.$parent)||"template/typeahead/typeahead-match.html";a.get(h,{cache:b}).success(function(a){f.replaceWith(c(a.trim())(e))})}}}]).filter("typeaheadHighlight",function(){function a(a){return a.replace(/([.?*+^$[\]\\(){}|-])/g,"\\$1")}return function(b,c){return c?b.replace(new RegExp(a(c),"gi"),"$&"):b}}),angular.module("template/accordion/accordion-group.html",[]).run(["$templateCache",function(a){a.put("template/accordion/accordion-group.html",'\n {{heading}}\n \n\n')}]),angular.module("template/accordion/accordion.html",[]).run(["$templateCache",function(a){a.put("template/accordion/accordion.html",'
\n')}]),angular.module("template/alert/alert.html",[]).run(["$templateCache",function(a){a.put("template/alert/alert.html","\n")}]),angular.module("template/modal/backdrop.html",[]).run(["$templateCache",function(a){a.put("template/modal/backdrop.html",'\n')}]),angular.module("template/modal/window.html",[]).run(["$templateCache",function(a){a.put("template/modal/window.html",'\n')}]),angular.module("template/pagination/pager.html",[]).run(["$templateCache",function(a){a.put("template/pagination/pager.html",'\n')}]),angular.module("template/pagination/pagination.html",[]).run(["$templateCache",function(a){a.put("template/pagination/pagination.html",'\n')}]),angular.module("template/tooltip/tooltip-html-unsafe-popup.html",[]).run(["$templateCache",function(a){a.put("template/tooltip/tooltip-html-unsafe-popup.html",'\n \n \n\n')}]),angular.module("template/tooltip/tooltip-popup.html",[]).run(["$templateCache",function(a){a.put("template/tooltip/tooltip-popup.html",'\n \n \n\n')}]),angular.module("template/popover/popover.html",[]).run(["$templateCache",function(a){a.put("template/popover/popover.html",'\n')}]),angular.module("template/progressbar/bar.html",[]).run(["$templateCache",function(a){a.put("template/progressbar/bar.html",'\n')}]),angular.module("template/progressbar/progress.html",[]).run(["$templateCache",function(a){a.put("template/progressbar/progress.html",'\n')}]),angular.module("template/progressbar/progressbar.html",[]).run(["$templateCache",function(a){a.put("template/progressbar/progressbar.html",'\n \n
\n')}]),angular.module("template/rating/rating.html",[]).run(["$templateCache",function(a){a.put("template/rating/rating.html",'\n \n\n')}]),angular.module("template/tabs/tab.html",[]).run(["$templateCache",function(a){a.put("template/tabs/tab.html",'\n {{heading}}\n\n')}]),angular.module("template/tabs/tabset.html",[]).run(["$templateCache",function(a){a.put("template/tabs/tabset.html",'\n')}]),angular.module("template/topbar/has-dropdown.html",[]).run(["$templateCache",function(a){a.put("template/topbar/has-dropdown.html",'')}]),angular.module("template/topbar/toggle-top-bar.html",[]).run(["$templateCache",function(a){a.put("template/topbar/toggle-top-bar.html",'')}]),angular.module("template/topbar/top-bar-dropdown.html",[]).run(["$templateCache",function(a){a.put("template/topbar/top-bar-dropdown.html",'')}]),angular.module("template/topbar/top-bar-section.html",[]).run(["$templateCache",function(a){a.put("template/topbar/top-bar-section.html",'')}]),angular.module("template/topbar/top-bar.html",[]).run(["$templateCache",function(a){a.put("template/topbar/top-bar.html",'')}]),angular.module("template/tour/tour.html",[]).run(["$templateCache",function(a){a.put("template/tour/tour.html",'\n')}]),angular.module("template/typeahead/typeahead-match.html",[]).run(["$templateCache",function(a){a.put("template/typeahead/typeahead-match.html",'')}]),angular.module("template/typeahead/typeahead-popup.html",[]).run(["$templateCache",function(a){a.put("template/typeahead/typeahead-popup.html","\n')}]);
diff --git a/app/assets/javascripts/shared/ng-tags-input.min.js b/app/assets/javascripts/shared/ng-tags-input.min.js
index 9a1acd6e0d..9af98627db 100644
--- a/app/assets/javascripts/shared/ng-tags-input.min.js
+++ b/app/assets/javascripts/shared/ng-tags-input.min.js
@@ -1 +1 @@
-/*! ngTagsInput v2.3.0 License: MIT */!function(){"use strict";var a={backspace:8,tab:9,enter:13,escape:27,space:32,up:38,down:40,left:37,right:39,"delete":46,comma:188},b=9007199254740991,c=["text","email","url"],d=angular.module("ngTagsInput",[]);d.directive("tagsInput",["$timeout","$document","$window","tagsInputConfig","tiUtil",function(d,e,f,g,h){function i(a,b,c,d){var e,f,g,i={};return e=function(b){return h.safeToString(b[a.displayProperty])},f=function(b,c){b[a.displayProperty]=c},g=function(b){var d=e(b);return d&&d.length>=a.minLength&&d.length<=a.maxLength&&a.allowedTagsPattern.test(d)&&!h.findInObjectArray(i.items,b,a.keyProperty||a.displayProperty)&&c({$tag:b})},i.items=[],i.addText=function(a){var b={};return f(b,a),i.add(b)},i.add=function(c){var d=e(c);return a.replaceSpacesWithDashes&&(d=h.replaceSpacesWithDashes(d)),f(c,d),g(c)?(i.items.push(c),b.trigger("tag-added",{$tag:c})):d&&b.trigger("invalid-tag",{$tag:c}),c},i.remove=function(a){var c=i.items[a];return d({$tag:c})?(i.items.splice(a,1),i.clearSelection(),b.trigger("tag-removed",{$tag:c}),c):void 0},i.select=function(a){0>a?a=i.items.length-1:a>=i.items.length&&(a=0),i.index=a,i.selected=i.items[a]},i.selectPrior=function(){i.select(--i.index)},i.selectNext=function(){i.select(++i.index)},i.removeSelected=function(){return i.remove(i.index)},i.clearSelection=function(){i.selected=null,i.index=-1},i.clearSelection(),i}function j(a){return-1!==c.indexOf(a)}return{restrict:"E",require:"ngModel",scope:{tags:"=ngModel",onTagAdding:"&",onTagAdded:"&",onInvalidTag:"&",onTagRemoving:"&",onTagRemoved:"&"},replace:!1,transclude:!0,templateUrl:"ngTagsInput/tags-input.html",controller:["$scope","$attrs","$element",function(a,c,d){a.events=h.simplePubSub(),g.load("tagsInput",a,c,{template:[String,"ngTagsInput/tag-item.html"],type:[String,"text",j],placeholder:[String,"Add a tag"],tabindex:[Number,null],removeTagSymbol:[String,String.fromCharCode(215)],replaceSpacesWithDashes:[Boolean,!0],minLength:[Number,3],maxLength:[Number,b],addOnEnter:[Boolean,!0],addOnSpace:[Boolean,!1],addOnComma:[Boolean,!0],addOnBlur:[Boolean,!0],addOnPaste:[Boolean,!1],pasteSplitPattern:[RegExp,/,/],allowedTagsPattern:[RegExp,/.+/],enableEditingLastTag:[Boolean,!1],minTags:[Number,0],maxTags:[Number,b],displayProperty:[String,"text"],keyProperty:[String,""],allowLeftoverText:[Boolean,!1],addFromAutocompleteOnly:[Boolean,!1],spellcheck:[Boolean,!0]}),a.tagList=new i(a.options,a.events,h.handleUndefinedResult(a.onTagAdding,!0),h.handleUndefinedResult(a.onTagRemoving,!0)),this.registerAutocomplete=function(){var b=d.find("input");return{addTag:function(b){return a.tagList.add(b)},focusInput:function(){b[0].focus()},getTags:function(){return a.tags},getCurrentTagText:function(){return a.newTag.text},getOptions:function(){return a.options},on:function(b,c){return a.events.on(b,c),this}}},this.registerTagItem=function(){return{getOptions:function(){return a.options},removeTag:function(b){a.disabled||a.tagList.remove(b)}}}}],link:function(b,c,g,i){var j,k=[a.enter,a.comma,a.space,a.backspace,a["delete"],a.left,a.right],l=b.tagList,m=b.events,n=b.options,o=c.find("input"),p=["minTags","maxTags","allowLeftoverText"];j=function(){i.$setValidity("maxTags",b.tags.length<=n.maxTags),i.$setValidity("minTags",b.tags.length>=n.minTags),i.$setValidity("leftoverText",b.hasFocus||n.allowLeftoverText?!0:!b.newTag.text)},i.$isEmpty=function(a){return!a||!a.length},b.newTag={text:"",invalid:null,setText:function(a){this.text=a,m.trigger("input-change",a)}},b.track=function(a){return a[n.keyProperty||n.displayProperty]},b.$watch("tags",function(a){b.tags=h.makeObjectArray(a,n.displayProperty),l.items=b.tags}),b.$watch("tags.length",function(){j()}),g.$observe("disabled",function(a){b.disabled=a}),b.eventHandlers={input:{change:function(a){m.trigger("input-change",a)},keydown:function(a){m.trigger("input-keydown",a)},focus:function(){b.hasFocus||(b.hasFocus=!0,m.trigger("input-focus"))},blur:function(){d(function(){var a=e.prop("activeElement"),d=a===o[0],f=c[0].contains(a);(d||!f)&&(b.hasFocus=!1,m.trigger("input-blur"))})},paste:function(a){a.getTextData=function(){var b=a.clipboardData||a.originalEvent&&a.originalEvent.clipboardData;return b?b.getData("text/plain"):f.clipboardData.getData("Text")},m.trigger("input-paste",a)}},host:{click:function(){b.disabled||o[0].focus()}}},m.on("tag-added",b.onTagAdded).on("invalid-tag",b.onInvalidTag).on("tag-removed",b.onTagRemoved).on("tag-added",function(){b.newTag.setText("")}).on("tag-added tag-removed",function(){i.$setViewValue(b.tags)}).on("invalid-tag",function(){b.newTag.invalid=!0}).on("option-change",function(a){-1!==p.indexOf(a.name)&&j()}).on("input-change",function(){l.clearSelection(),b.newTag.invalid=null}).on("input-focus",function(){c.triggerHandler("focus"),i.$setValidity("leftoverText",!0)}).on("input-blur",function(){n.addOnBlur&&!n.addFromAutocompleteOnly&&l.addText(b.newTag.text),c.triggerHandler("blur"),j()}).on("input-keydown",function(c){var d,e,f,g,h=c.keyCode,i=c.shiftKey||c.altKey||c.ctrlKey||c.metaKey,j={};if(!i&&-1!==k.indexOf(h)){if(j[a.enter]=n.addOnEnter,j[a.comma]=n.addOnComma,j[a.space]=n.addOnSpace,d=!n.addFromAutocompleteOnly&&j[h],e=(h===a.backspace||h===a["delete"])&&l.selected,g=h===a.backspace&&0===b.newTag.text.length&&n.enableEditingLastTag,f=(h===a.backspace||h===a.left||h===a.right)&&0===b.newTag.text.length&&!n.enableEditingLastTag,d)l.addText(b.newTag.text);else if(g){var m;l.selectPrior(),m=l.removeSelected(),m&&b.newTag.setText(m[n.displayProperty])}else e?l.removeSelected():f&&(h===a.left||h===a.backspace?l.selectPrior():h===a.right&&l.selectNext());(d||f||e||g)&&c.preventDefault()}}).on("input-paste",function(a){if(n.addOnPaste){var b=a.getTextData(),c=b.split(n.pasteSplitPattern);c.length>1&&(c.forEach(function(a){l.addText(a)}),a.preventDefault())}})}}}]),d.directive("tiTagItem",["tiUtil",function(a){return{restrict:"E",require:"^tagsInput",template:'',scope:{data:"="},link:function(b,c,d,e){var f=e.registerTagItem(),g=f.getOptions();b.$$template=g.template,b.$$removeTagSymbol=g.removeTagSymbol,b.$getDisplayText=function(){return a.safeToString(b.data[g.displayProperty])},b.$removeTag=function(){f.removeTag(b.$index)},b.$watch("$parent.$index",function(a){b.$index=a})}}}]),d.directive("autoComplete",["$document","$timeout","$sce","$q","tagsInputConfig","tiUtil",function(b,c,d,e,f,g){function h(a,b,c){var d,f,h,i={};return h=function(){return b.tagsInput.keyProperty||b.tagsInput.displayProperty},d=function(a,c){return a.filter(function(a){return!g.findInObjectArray(c,a,h(),function(a,c){return b.tagsInput.replaceSpacesWithDashes&&(a=g.replaceSpacesWithDashes(a),c=g.replaceSpacesWithDashes(c)),g.defaultComparer(a,c)})})},i.reset=function(){f=null,i.items=[],i.visible=!1,i.index=-1,i.selected=null,i.query=null},i.show=function(){b.selectFirstMatch?i.select(0):i.selected=null,i.visible=!0},i.load=g.debounce(function(c,j){i.query=c;var k=e.when(a({$query:c}));f=k,k.then(function(a){k===f&&(a=g.makeObjectArray(a.data||a,h()),a=d(a,j),i.items=a.slice(0,b.maxResultsToShow),i.items.length>0?i.show():i.reset())})},b.debounceDelay),i.selectNext=function(){i.select(++i.index)},i.selectPrior=function(){i.select(--i.index)},i.select=function(a){0>a?a=i.items.length-1:a>=i.items.length&&(a=0),i.index=a,i.selected=i.items[a],c.trigger("suggestion-selected",a)},i.reset(),i}function i(a,b){var c=a.find("li").eq(b),d=c.parent(),e=c.prop("offsetTop"),f=c.prop("offsetHeight"),g=d.prop("clientHeight"),h=d.prop("scrollTop");h>e?d.prop("scrollTop",e):e+f>g+h&&d.prop("scrollTop",e+f-g)}return{restrict:"E",require:"^tagsInput",scope:{source:"&"},templateUrl:"ngTagsInput/auto-complete.html",controller:["$scope","$element","$attrs",function(a,b,c){a.events=g.simplePubSub(),f.load("autoComplete",a,c,{template:[String,"ngTagsInput/auto-complete-match.html"],debounceDelay:[Number,100],minLength:[Number,3],highlightMatchedText:[Boolean,!0],maxResultsToShow:[Number,10],loadOnDownArrow:[Boolean,!1],loadOnEmpty:[Boolean,!1],loadOnFocus:[Boolean,!1],selectFirstMatch:[Boolean,!0],displayProperty:[String,""]}),a.suggestionList=new h(a.source,a.options,a.events),this.registerAutocompleteMatch=function(){return{getOptions:function(){return a.options},getQuery:function(){return a.suggestionList.query}}}}],link:function(b,c,d,e){var f,g=[a.enter,a.tab,a.escape,a.up,a.down],h=b.suggestionList,j=e.registerAutocomplete(),k=b.options,l=b.events;k.tagsInput=j.getOptions(),f=function(a){return a&&a.length>=k.minLength||!a&&k.loadOnEmpty},b.addSuggestionByIndex=function(a){h.select(a),b.addSuggestion()},b.addSuggestion=function(){var a=!1;return h.selected&&(j.addTag(angular.copy(h.selected)),h.reset(),j.focusInput(),a=!0),a},b.track=function(a){return a[k.tagsInput.keyProperty||k.tagsInput.displayProperty]},j.on("tag-added invalid-tag input-blur",function(){h.reset()}).on("input-change",function(a){f(a)?h.load(a,j.getTags()):h.reset()}).on("input-focus",function(){var a=j.getCurrentTagText();k.loadOnFocus&&f(a)&&h.load(a,j.getTags())}).on("input-keydown",function(c){var d=c.keyCode,e=!1;if(-1!==g.indexOf(d))return h.visible?d===a.down?(h.selectNext(),e=!0):d===a.up?(h.selectPrior(),e=!0):d===a.escape?(h.reset(),e=!0):(d===a.enter||d===a.tab)&&(e=b.addSuggestion()):d===a.down&&b.options.loadOnDownArrow&&(h.load(j.getCurrentTagText(),j.getTags()),e=!0),e?(c.preventDefault(),c.stopImmediatePropagation(),!1):void 0}),l.on("suggestion-selected",function(a){i(c,a)})}}}]),d.directive("tiAutocompleteMatch",["$sce","tiUtil",function(a,b){return{restrict:"E",require:"^autoComplete",template:'',scope:{data:"="},link:function(c,d,e,f){var g=f.registerAutocompleteMatch(),h=g.getOptions();c.$$template=h.template,c.$index=c.$parent.$index,c.$highlight=function(c){return h.highlightMatchedText&&(c=b.safeHighlight(c,g.getQuery())),a.trustAsHtml(c)},c.$getDisplayText=function(){return b.safeToString(c.data[h.displayProperty||h.tagsInput.displayProperty])}}}}]),d.directive("tiTranscludeAppend",function(){return function(a,b,c,d,e){e(function(a){b.append(a)})}}),d.directive("tiAutosize",["tagsInputConfig",function(a){return{restrict:"A",require:"ngModel",link:function(b,c,d,e){var f,g,h=a.getTextAutosizeThreshold();f=angular.element(''),f.css("display","none").css("visibility","hidden").css("width","auto").css("white-space","pre"),c.parent().append(f),g=function(a){var b,e=a;return angular.isString(e)&&0===e.length&&(e=d.placeholder),e&&(f.text(e),f.css("display",""),b=f.prop("offsetWidth"),f.css("display","none")),c.css("width",b?b+h+"px":""),a},e.$parsers.unshift(g),e.$formatters.unshift(g),d.$observe("placeholder",function(a){e.$modelValue||g(a)})}}}]),d.directive("tiBindAttrs",function(){return function(a,b,c){a.$watch(c.tiBindAttrs,function(a){angular.forEach(a,function(a,b){c.$set(b,a)})},!0)}}),d.provider("tagsInputConfig",function(){var a={},b={},c=3;this.setDefaults=function(b,c){return a[b]=c,this},this.setActiveInterpolation=function(a,c){return b[a]=c,this},this.setTextAutosizeThreshold=function(a){return c=a,this},this.$get=["$interpolate",function(d){var e={};return e[String]=function(a){return a},e[Number]=function(a){return parseInt(a,10)},e[Boolean]=function(a){return"true"===a.toLowerCase()},e[RegExp]=function(a){return new RegExp(a)},{load:function(c,f,g,h){var i=function(){return!0};f.options={},angular.forEach(h,function(h,j){var k,l,m,n,o,p;k=h[0],l=h[1],m=h[2]||i,n=e[k],o=function(){var b=a[c]&&a[c][j];return angular.isDefined(b)?b:l},p=function(a){f.options[j]=a&&m(a)?n(a):o()},b[c]&&b[c][j]?g.$observe(j,function(a){p(a),f.events.trigger("option-change",{name:j,newValue:a})}):p(g[j]&&d(g[j])(f.$parent))})},getTextAutosizeThreshold:function(){return c}}}]}),d.factory("tiUtil",["$timeout",function(a){var b={};return b.debounce=function(b,c){var d;return function(){var e=arguments;a.cancel(d),d=a(function(){b.apply(null,e)},c)}},b.makeObjectArray=function(a,b){return a=a||[],a.length>0&&!angular.isObject(a[0])&&a.forEach(function(c,d){a[d]={},a[d][b]=c}),a},b.findInObjectArray=function(a,c,d,e){var f=null;return e=e||b.defaultComparer,a.some(function(a){return e(a[d],c[d])?(f=a,!0):void 0}),f},b.defaultComparer=function(a,c){return b.safeToString(a).toLowerCase()===b.safeToString(c).toLowerCase()},b.safeHighlight=function(a,c){function d(a){return a.replace(/([.?*+^$[\]\\(){}|-])/g,"\\$1")}if(!c)return a;a=b.encodeHTML(a),c=b.encodeHTML(c);var e=new RegExp("&[^;]+;|"+d(c),"gi");return a.replace(e,function(a){return a.toLowerCase()===c.toLowerCase()?""+a+"":a})},b.safeToString=function(a){return angular.isUndefined(a)||null==a?"":a.toString().trim()},b.encodeHTML=function(a){return b.safeToString(a).replace(/&/g,"&").replace(//g,">")},b.handleUndefinedResult=function(a,b){return function(){var c=a.apply(null,arguments);return angular.isUndefined(c)?b:c}},b.replaceSpacesWithDashes=function(a){return b.safeToString(a).replace(/\s/g,"-")},b.simplePubSub=function(){var a={};return{on:function(b,c){return b.split(" ").forEach(function(b){a[b]||(a[b]=[]),a[b].push(c)}),this},trigger:function(c,d){var e=a[c]||[];return e.every(function(a){return b.handleUndefinedResult(a,!0)(d)}),this}}},b}]),d.run(["$templateCache",function(a){a.put("ngTagsInput/tags-input.html",''),a.put("ngTagsInput/tag-item.html",' '),a.put("ngTagsInput/auto-complete.html",''),a.put("ngTagsInput/auto-complete-match.html",'')}])}();
\ No newline at end of file
+/*! ngTagsInput v3.0.0 License: MIT */!function(){"use strict";var a={backspace:8,tab:9,enter:13,escape:27,space:32,up:38,down:40,left:37,right:39,"delete":46,comma:188},b=9007199254740991,c=["text","email","url"],d=angular.module("ngTagsInput",[]);d.directive("tagsInput",["$timeout","$document","$window","tagsInputConfig","tiUtil",function(d,e,f,g,h){function i(a,b,c,d){var e,f,g,i={};return e=function(b){return h.safeToString(b[a.displayProperty])},f=function(b,c){b[a.displayProperty]=c},g=function(b){var d=e(b);return d&&d.length>=a.minLength&&d.length<=a.maxLength&&a.allowedTagsPattern.test(d)&&!h.findInObjectArray(i.items,b,a.keyProperty||a.displayProperty)&&c({$tag:b})},i.items=[],i.addText=function(a){var b={};return f(b,a),i.add(b)},i.add=function(c){var d=e(c);return a.replaceSpacesWithDashes&&(d=h.replaceSpacesWithDashes(d)),f(c,d),g(c)?(i.items.push(c),b.trigger("tag-added",{$tag:c})):d&&b.trigger("invalid-tag",{$tag:c}),c},i.remove=function(a){var c=i.items[a];return d({$tag:c})?(i.items.splice(a,1),i.clearSelection(),b.trigger("tag-removed",{$tag:c}),c):void 0},i.select=function(a){0>a?a=i.items.length-1:a>=i.items.length&&(a=0),i.index=a,i.selected=i.items[a]},i.selectPrior=function(){i.select(--i.index)},i.selectNext=function(){i.select(++i.index)},i.removeSelected=function(){return i.remove(i.index)},i.clearSelection=function(){i.selected=null,i.index=-1},i.clearSelection(),i}function j(a){return-1!==c.indexOf(a)}return{restrict:"E",require:"ngModel",scope:{tags:"=ngModel",text:"=?",onTagAdding:"&",onTagAdded:"&",onInvalidTag:"&",onTagRemoving:"&",onTagRemoved:"&",onTagClicked:"&"},replace:!1,transclude:!0,templateUrl:"ngTagsInput/tags-input.html",controller:["$scope","$attrs","$element",function(a,c,d){a.events=h.simplePubSub(),g.load("tagsInput",a,c,{template:[String,"ngTagsInput/tag-item.html"],type:[String,"text",j],placeholder:[String,"Add a tag"],tabindex:[Number,null],removeTagSymbol:[String,String.fromCharCode(215)],replaceSpacesWithDashes:[Boolean,!0],minLength:[Number,3],maxLength:[Number,b],addOnEnter:[Boolean,!0],addOnSpace:[Boolean,!1],addOnComma:[Boolean,!0],addOnBlur:[Boolean,!0],addOnPaste:[Boolean,!1],pasteSplitPattern:[RegExp,/,/],allowedTagsPattern:[RegExp,/.+/],enableEditingLastTag:[Boolean,!1],minTags:[Number,0],maxTags:[Number,b],displayProperty:[String,"text"],keyProperty:[String,""],allowLeftoverText:[Boolean,!1],addFromAutocompleteOnly:[Boolean,!1],spellcheck:[Boolean,!0]}),a.tagList=new i(a.options,a.events,h.handleUndefinedResult(a.onTagAdding,!0),h.handleUndefinedResult(a.onTagRemoving,!0)),this.registerAutocomplete=function(){var b=d.find("input");return{addTag:function(b){return a.tagList.add(b)},focusInput:function(){b[0].focus()},getTags:function(){return a.tagList.items},getCurrentTagText:function(){return a.newTag.text()},getOptions:function(){return a.options},on:function(b,c){return a.events.on(b,c),this}}},this.registerTagItem=function(){return{getOptions:function(){return a.options},removeTag:function(b){a.disabled||a.tagList.remove(b)}}}}],link:function(b,c,g,i){var j,k=[a.enter,a.comma,a.space,a.backspace,a["delete"],a.left,a.right],l=b.tagList,m=b.events,n=b.options,o=c.find("input"),p=["minTags","maxTags","allowLeftoverText"];j=function(){i.$setValidity("maxTags",l.items.length<=n.maxTags),i.$setValidity("minTags",l.items.length>=n.minTags),i.$setValidity("leftoverText",b.hasFocus||n.allowLeftoverText?!0:!b.newTag.text())},i.$isEmpty=function(a){return!a||!a.length},b.newTag={text:function(a){return angular.isDefined(a)?(b.text=a,void m.trigger("input-change",a)):b.text||""},invalid:null},b.track=function(a){return a[n.keyProperty||n.displayProperty]},b.$watch("tags",function(a){a?(l.items=h.makeObjectArray(a,n.displayProperty),b.tags=l.items):l.items=[]}),b.$watch("tags.length",function(){j(),i.$validate()}),g.$observe("disabled",function(a){b.disabled=a}),b.eventHandlers={input:{keydown:function(a){m.trigger("input-keydown",a)},focus:function(){b.hasFocus||(b.hasFocus=!0,m.trigger("input-focus"))},blur:function(){d(function(){var a=e.prop("activeElement"),d=a===o[0],f=c[0].contains(a);(d||!f)&&(b.hasFocus=!1,m.trigger("input-blur"))})},paste:function(a){a.getTextData=function(){var b=a.clipboardData||a.originalEvent&&a.originalEvent.clipboardData;return b?b.getData("text/plain"):f.clipboardData.getData("Text")},m.trigger("input-paste",a)}},host:{click:function(){b.disabled||o[0].focus()}},tag:{click:function(a){m.trigger("tag-clicked",{$tag:a})}}},m.on("tag-added",b.onTagAdded).on("invalid-tag",b.onInvalidTag).on("tag-removed",b.onTagRemoved).on("tag-clicked",b.onTagClicked).on("tag-added",function(){b.newTag.text("")}).on("tag-added tag-removed",function(){b.tags=l.items,i.$setDirty()}).on("invalid-tag",function(){b.newTag.invalid=!0}).on("option-change",function(a){-1!==p.indexOf(a.name)&&j()}).on("input-change",function(){l.clearSelection(),b.newTag.invalid=null}).on("input-focus",function(){c.triggerHandler("focus"),i.$setValidity("leftoverText",!0)}).on("input-blur",function(){n.addOnBlur&&!n.addFromAutocompleteOnly&&l.addText(b.newTag.text()),c.triggerHandler("blur"),j()}).on("input-keydown",function(c){var d,e,f,g,i=c.keyCode,j={};if(!h.isModifierOn(c)&&-1!==k.indexOf(i)){if(j[a.enter]=n.addOnEnter,j[a.comma]=n.addOnComma,j[a.space]=n.addOnSpace,d=!n.addFromAutocompleteOnly&&j[i],e=(i===a.backspace||i===a["delete"])&&l.selected,g=i===a.backspace&&0===b.newTag.text().length&&n.enableEditingLastTag,f=(i===a.backspace||i===a.left||i===a.right)&&0===b.newTag.text().length&&!n.enableEditingLastTag,d)l.addText(b.newTag.text());else if(g){var m;l.selectPrior(),m=l.removeSelected(),m&&b.newTag.text(m[n.displayProperty])}else e?l.removeSelected():f&&(i===a.left||i===a.backspace?l.selectPrior():i===a.right&&l.selectNext());(d||f||e||g)&&c.preventDefault()}}).on("input-paste",function(a){if(n.addOnPaste){var b=a.getTextData(),c=b.split(n.pasteSplitPattern);c.length>1&&(c.forEach(function(a){l.addText(a)}),a.preventDefault())}})}}}]),d.directive("tiTagItem",["tiUtil",function(a){return{restrict:"E",require:"^tagsInput",template:'',scope:{data:"="},link:function(b,c,d,e){var f=e.registerTagItem(),g=f.getOptions();b.$$template=g.template,b.$$removeTagSymbol=g.removeTagSymbol,b.$getDisplayText=function(){return a.safeToString(b.data[g.displayProperty])},b.$removeTag=function(){f.removeTag(b.$index)},b.$watch("$parent.$index",function(a){b.$index=a})}}}]),d.directive("autoComplete",["$document","$timeout","$sce","$q","tagsInputConfig","tiUtil",function(b,c,d,e,f,g){function h(a,b,c){var d,f,h,i={};return h=function(){return b.tagsInput.keyProperty||b.tagsInput.displayProperty},d=function(a,c){return a.filter(function(a){return!g.findInObjectArray(c,a,h(),function(a,c){return b.tagsInput.replaceSpacesWithDashes&&(a=g.replaceSpacesWithDashes(a),c=g.replaceSpacesWithDashes(c)),g.defaultComparer(a,c)})})},i.reset=function(){f=null,i.items=[],i.visible=!1,i.index=-1,i.selected=null,i.query=null},i.show=function(){b.selectFirstMatch?i.select(0):i.selected=null,i.visible=!0},i.load=g.debounce(function(c,j){i.query=c;var k=e.when(a({$query:c}));f=k,k.then(function(a){k===f&&(a=g.makeObjectArray(a.data||a,h()),a=d(a,j),i.items=a.slice(0,b.maxResultsToShow),i.items.length>0?i.show():i.reset())})},b.debounceDelay),i.selectNext=function(){i.select(++i.index)},i.selectPrior=function(){i.select(--i.index)},i.select=function(a){0>a?a=i.items.length-1:a>=i.items.length&&(a=0),i.index=a,i.selected=i.items[a],c.trigger("suggestion-selected",a)},i.reset(),i}function i(a,b){var c=a.find("li").eq(b),d=c.parent(),e=c.prop("offsetTop"),f=c.prop("offsetHeight"),g=d.prop("clientHeight"),h=d.prop("scrollTop");h>e?d.prop("scrollTop",e):e+f>g+h&&d.prop("scrollTop",e+f-g)}return{restrict:"E",require:"^tagsInput",scope:{source:"&"},templateUrl:"ngTagsInput/auto-complete.html",controller:["$scope","$element","$attrs",function(a,b,c){a.events=g.simplePubSub(),f.load("autoComplete",a,c,{template:[String,"ngTagsInput/auto-complete-match.html"],debounceDelay:[Number,100],minLength:[Number,3],highlightMatchedText:[Boolean,!0],maxResultsToShow:[Number,10],loadOnDownArrow:[Boolean,!1],loadOnEmpty:[Boolean,!1],loadOnFocus:[Boolean,!1],selectFirstMatch:[Boolean,!0],displayProperty:[String,""]}),a.suggestionList=new h(a.source,a.options,a.events),this.registerAutocompleteMatch=function(){return{getOptions:function(){return a.options},getQuery:function(){return a.suggestionList.query}}}}],link:function(b,c,d,e){var f,h=[a.enter,a.tab,a.escape,a.up,a.down],j=b.suggestionList,k=e.registerAutocomplete(),l=b.options,m=b.events;l.tagsInput=k.getOptions(),f=function(a){return a&&a.length>=l.minLength||!a&&l.loadOnEmpty},b.addSuggestionByIndex=function(a){j.select(a),b.addSuggestion()},b.addSuggestion=function(){var a=!1;return j.selected&&(k.addTag(angular.copy(j.selected)),j.reset(),k.focusInput(),a=!0),a},b.track=function(a){return a[l.tagsInput.keyProperty||l.tagsInput.displayProperty]},k.on("tag-added tag-removed invalid-tag input-blur",function(){j.reset()}).on("input-change",function(a){f(a)?j.load(a,k.getTags()):j.reset()}).on("input-focus",function(){var a=k.getCurrentTagText();l.loadOnFocus&&f(a)&&j.load(a,k.getTags())}).on("input-keydown",function(c){var d=c.keyCode,e=!1;if(!g.isModifierOn(c)&&-1!==h.indexOf(d))return j.visible?d===a.down?(j.selectNext(),e=!0):d===a.up?(j.selectPrior(),e=!0):d===a.escape?(j.reset(),e=!0):(d===a.enter||d===a.tab)&&(e=b.addSuggestion()):d===a.down&&b.options.loadOnDownArrow&&(j.load(k.getCurrentTagText(),k.getTags()),e=!0),e?(c.preventDefault(),c.stopImmediatePropagation(),!1):void 0}),m.on("suggestion-selected",function(a){i(c,a)})}}}]),d.directive("tiAutocompleteMatch",["$sce","tiUtil",function(a,b){return{restrict:"E",require:"^autoComplete",template:'',scope:{data:"="},link:function(c,d,e,f){var g=f.registerAutocompleteMatch(),h=g.getOptions();c.$$template=h.template,c.$index=c.$parent.$index,c.$highlight=function(c){return h.highlightMatchedText&&(c=b.safeHighlight(c,g.getQuery())),a.trustAsHtml(c)},c.$getDisplayText=function(){return b.safeToString(c.data[h.displayProperty||h.tagsInput.displayProperty])}}}}]),d.directive("tiTranscludeAppend",function(){return function(a,b,c,d,e){e(function(a){b.append(a)})}}),d.directive("tiAutosize",["tagsInputConfig",function(a){return{restrict:"A",require:"ngModel",link:function(b,c,d,e){var f,g,h=a.getTextAutosizeThreshold();f=angular.element(''),f.css("display","none").css("visibility","hidden").css("width","auto").css("white-space","pre"),c.parent().append(f),g=function(a){var b,e=a;return angular.isString(e)&&0===e.length&&(e=d.placeholder),e&&(f.text(e),f.css("display",""),b=f.prop("offsetWidth"),f.css("display","none")),c.css("width",b?b+h+"px":""),a},e.$parsers.unshift(g),e.$formatters.unshift(g),d.$observe("placeholder",function(a){e.$modelValue||g(a)})}}}]),d.directive("tiBindAttrs",function(){return function(a,b,c){a.$watch(c.tiBindAttrs,function(a){angular.forEach(a,function(a,b){c.$set(b,a)})},!0)}}),d.provider("tagsInputConfig",function(){var a={},b={},c=3;this.setDefaults=function(b,c){return a[b]=c,this},this.setActiveInterpolation=function(a,c){return b[a]=c,this},this.setTextAutosizeThreshold=function(a){return c=a,this},this.$get=["$interpolate",function(d){var e={};return e[String]=function(a){return a},e[Number]=function(a){return parseInt(a,10)},e[Boolean]=function(a){return"true"===a.toLowerCase()},e[RegExp]=function(a){return new RegExp(a)},{load:function(c,f,g,h){var i=function(){return!0};f.options={},angular.forEach(h,function(h,j){var k,l,m,n,o,p;k=h[0],l=h[1],m=h[2]||i,n=e[k],o=function(){var b=a[c]&&a[c][j];return angular.isDefined(b)?b:l},p=function(a){f.options[j]=a&&m(a)?n(a):o()},b[c]&&b[c][j]?g.$observe(j,function(a){p(a),f.events.trigger("option-change",{name:j,newValue:a})}):p(g[j]&&d(g[j])(f.$parent))})},getTextAutosizeThreshold:function(){return c}}}]}),d.factory("tiUtil",["$timeout",function(a){var b={};return b.debounce=function(b,c){var d;return function(){var e=arguments;a.cancel(d),d=a(function(){b.apply(null,e)},c)}},b.makeObjectArray=function(a,b){if(!angular.isArray(a)||0===a.length||angular.isObject(a[0]))return a;var c=[];return a.forEach(function(a){var d={};d[b]=a,c.push(d)}),c},b.findInObjectArray=function(a,c,d,e){var f=null;return e=e||b.defaultComparer,a.some(function(a){return e(a[d],c[d])?(f=a,!0):void 0}),f},b.defaultComparer=function(a,c){return b.safeToString(a).toLowerCase()===b.safeToString(c).toLowerCase()},b.safeHighlight=function(a,c){function d(a){return a.replace(/([.?*+^$[\]\\(){}|-])/g,"\\$1")}if(!c)return a;a=b.encodeHTML(a),c=b.encodeHTML(c);var e=new RegExp("&[^;]+;|"+d(c),"gi");return a.replace(e,function(a){return a.toLowerCase()===c.toLowerCase()?""+a+"":a})},b.safeToString=function(a){return angular.isUndefined(a)||null==a?"":a.toString().trim()},b.encodeHTML=function(a){return b.safeToString(a).replace(/&/g,"&").replace(//g,">")},b.handleUndefinedResult=function(a,b){return function(){var c=a.apply(null,arguments);return angular.isUndefined(c)?b:c}},b.replaceSpacesWithDashes=function(a){return b.safeToString(a).replace(/\s/g,"-")},b.isModifierOn=function(a){return a.shiftKey||a.ctrlKey||a.altKey||a.metaKey},b.simplePubSub=function(){var a={};return{on:function(b,c){return b.split(" ").forEach(function(b){a[b]||(a[b]=[]),a[b].push(c)}),this},trigger:function(c,d){var e=a[c]||[];return e.every(function(a){return b.handleUndefinedResult(a,!0)(d)}),this}}},b}]),d.run(["$templateCache",function(a){a.put("ngTagsInput/tags-input.html",''),a.put("ngTagsInput/tag-item.html",' '),a.put("ngTagsInput/auto-complete.html",''),a.put("ngTagsInput/auto-complete-match.html",'')}])}();
diff --git a/app/assets/javascripts/templates/admin/columns_dropdown.html.haml b/app/assets/javascripts/templates/admin/columns_dropdown.html.haml
new file mode 100644
index 0000000000..37398b200a
--- /dev/null
+++ b/app/assets/javascripts/templates/admin/columns_dropdown.html.haml
@@ -0,0 +1,11 @@
+.ofn-drop-down.right#columns-dropdown{ ng: { controller: 'ColumnsDropdownCtrl' } }
+ %span{ :class => 'icon-reorder' }= " #{t('admin.columns')}".html_safe
+ %span{ 'ng-class' => "expanded && 'icon-caret-up' || !expanded && 'icon-caret-down'" }
+ %div.menu{ 'ng-show' => "expanded" }
+ %div.menu_item{ ng: { repeat: "column in columns", click: "toggle(column)", class: "{selected: column.visible}" } }
+ %span.check
+ %span.name {{column.name }}
+ %hr
+ %div.menu_item.text-center
+ %input.fullwidth.orange{ type: "button", ng: { value: "saved() ? 'Saved': 'Saving'", show: "saved() || saving", disabled: "saved()" } }
+ %input.fullwidth.red{ type: "button", value: 'Save As Default', ng: { show: "!saved() && !saving", click: "saveColumnPreferences(action)"} }
diff --git a/app/assets/javascripts/templates/admin/modals/tag_rule_help.html.haml b/app/assets/javascripts/templates/admin/modals/tag_rule_help.html.haml
new file mode 100644
index 0000000000..6a0d720a5f
--- /dev/null
+++ b/app/assets/javascripts/templates/admin/modals/tag_rule_help.html.haml
@@ -0,0 +1,18 @@
+#tag-rule-help
+ .margin-bottom-30.text-center
+ .text-big Tag Rules
+
+ .margin-bottom-30
+ .text-normal Overview
+ %p Tag rules provide a way to describe which items are visible or otherwise to which customers. Items can be Shipping Methods, Payment Methods, Products and Order Cycles.
+
+ .margin-bottom-30
+ .text-normal 'By Default...' Rules
+ %p Default rules allow you to hide items so that they are not visible by default. This behaviour can then be overriden by non-default rules for customers with particular tags.
+
+ .margin-bottom-30
+ .text-normal 'Customers Tagged...' Rules
+ %p By creating rules related to a specific customer tag, you can override the default behaviour (whether it be to show or to hide items) for customers with the specified tag.
+
+ .text-center
+ %input.button.red.icon-plus{ type: 'button', value: 'Got it', ng: { click: 'close()' } }
diff --git a/app/assets/javascripts/templates/admin/new_customer_dialog.html.haml b/app/assets/javascripts/templates/admin/new_customer_dialog.html.haml
new file mode 100644
index 0000000000..e30cfcd607
--- /dev/null
+++ b/app/assets/javascripts/templates/admin/new_customer_dialog.html.haml
@@ -0,0 +1,15 @@
+#new-customer-dialog
+ .text-normal.margin-bottom-30.text-center
+ = t('admin.customers.index.add_a_new_customer_for', shop_name: "{{ CurrentShop.shop.name }}:")
+
+ %form{ name: 'new_customer_form', novalidate: true }
+
+ .text-center.margin-bottom-30
+ %input.fullwidth{ type: 'email', name: 'email', required: true, placeholder: t('admin.customers.index.customer_placeholder'), ng: { model: "email" } }
+ %div{ ng: { show: "email == submitted" } }
+ .error{ ng: { show: "(new_customer_form.email.$error.email || new_customer_form.email.$error.required)" } }
+ = t('admin.customers.index.valid_email_error')
+ .error{ ng: { repeat: "error in errors", bind: "error" } }
+
+ .text-center
+ %input.button.red.icon-plus{ type: 'submit', value: t('admin.customers.index.add_customer'), ng: { click: 'addCustomer(new_customer_form.email.$valid)' } }
diff --git a/app/assets/javascripts/templates/admin/panel.html.haml b/app/assets/javascripts/templates/admin/panel.html.haml
index be0c98109f..4d3780aed0 100644
--- a/app/assets/javascripts/templates/admin/panel.html.haml
+++ b/app/assets/javascripts/templates/admin/panel.html.haml
@@ -1,2 +1,2 @@
-%td{ colspan: "{{columnCount}}" }
+%td{ colspan: "{{columnCount}}", ng: { if: "template" } }
.panel{ ng: { include: "template" } }
diff --git a/app/assets/javascripts/templates/admin/panels/enterprise_status.html.haml b/app/assets/javascripts/templates/admin/panels/enterprise_status.html.haml
index 39392a497d..518122774d 100644
--- a/app/assets/javascripts/templates/admin/panels/enterprise_status.html.haml
+++ b/app/assets/javascripts/templates/admin/panels/enterprise_status.html.haml
@@ -17,13 +17,13 @@
%td.severity
%i.icon-warning-sign.issue
%td.description
- %span{ bo: { bind: "issue.description" } }
+ %span{ ng: { bind: "::issue.description" } }
%td.resolve
%div{ ng: { bind: { html: "issue.link" } } }
%tr{ ng: { repeat: "warning in warnings"} }
%td.severity
%i.icon-warning-sign.warning
%td.description
- %span{ bo: { bind: "warning.description" } }
+ %span{ ng: { bind: "::warning.description" } }
%td.resolve
%div{ ng: { bind: { html: "warning.link" } } }
diff --git a/app/assets/javascripts/templates/admin/panels/exchange_distributed_products.html.haml b/app/assets/javascripts/templates/admin/panels/exchange_distributed_products.html.haml
new file mode 100644
index 0000000000..4c6a86e926
--- /dev/null
+++ b/app/assets/javascripts/templates/admin/panels/exchange_distributed_products.html.haml
@@ -0,0 +1,31 @@
+.row.exchange-distributed-products
+ .sixteen.columns.alpha.omega
+ .exchange-select-all-variants
+ %label
+ %input{ type: 'checkbox', name: 'order_cycle_outgoing_exchange_{{ $parent.$index }}_select_all_variants',
+ value: 1,
+ 'ng-model' => 'exchange.select_all_variants',
+ 'ng-change' => 'setExchangeVariants(exchange, incomingExchangeVariantsFor(exchange.enterprise_id), exchange.select_all_variants)',
+ 'id' => 'order_cycle_outgoing_exchange_{{ $parent.$index }}_select_all_variants' }
+ Select all
+
+ .exchange-products
+ -# Scope product list based on permissions the current user has to view variants in this exchange
+ .exchange-product{'ng-repeat' => 'product in supplied_products | filter:productSuppliedToOrderCycle | visibleProducts:exchange:order_cycle.visible_variants_for_outgoing_exchanges | orderBy:"name"' }
+ .exchange-product-details
+ %label
+ -# MASTER_VARIANTS: No longer required
+ -# = check_box_tag 'order_cycle_outgoing_exchange_{{ $parent.$index }}_variants_{{ product.master_id }}', 1, 1, 'ng-hide' => 'product.variants.length > 0', 'ng-model' => 'exchange.variants[product.master_id]', 'id' => 'order_cycle_outgoing_exchange_{{ $parent.$index }}_variants_{{ product.master_id }}',
+ -# 'ng-disabled' => 'product.variants.length > 0 || !order_cycle.editable_variants_for_outgoing_exchanges.hasOwnProperty(exchange.enterprise_id) || order_cycle.editable_variants_for_outgoing_exchanges[exchange.enterprise_id].indexOf(product.master_id) < 0'
+ %img{'ng-src' => '{{ product.image_url }}'}
+ .name {{ product.name }}
+ .supplier {{ product.supplier_name }}
+
+ .exchange-product-variant{'ng-repeat' => 'variant in product.variants | visibleVariants:exchange:order_cycle.visible_variants_for_outgoing_exchanges | filter:variantSuppliedToOrderCycle'}
+ %label
+ %input{ type: 'checkbox', name: 'order_cycle_outgoing_exchange_{{ $parent.$parent.$index }}_variants_{{ variant.id }}',
+ value: 1,
+ 'ng-model' => 'exchange.variants[variant.id]',
+ 'id' => 'order_cycle_outgoing_exchange_{{ $parent.$parent.$index }}_variants_{{ variant.id }}',
+ 'ng-disabled' => '!order_cycle.editable_variants_for_outgoing_exchanges.hasOwnProperty(exchange.enterprise_id) || order_cycle.editable_variants_for_outgoing_exchanges[exchange.enterprise_id].indexOf(variant.id) < 0' }
+ {{ variant.label }}
diff --git a/app/assets/javascripts/templates/admin/panels/exchange_supplied_products.html.haml b/app/assets/javascripts/templates/admin/panels/exchange_supplied_products.html.haml
new file mode 100644
index 0000000000..04e191fcf0
--- /dev/null
+++ b/app/assets/javascripts/templates/admin/panels/exchange_supplied_products.html.haml
@@ -0,0 +1,49 @@
+.row.exchange-supplied-products
+ .sixteen.columns.alpha.omega
+ .exchange-select-all-variants
+ %label
+ %input{ type: 'checkbox', name: 'order_cycle_incoming_exchange_{{ $index }}_select_all_variants',
+ value: 1,
+ 'ng-model' => 'exchange.select_all_variants',
+ 'ng-change' => 'setExchangeVariants(exchange, suppliedVariants(exchange.enterprise_id), exchange.select_all_variants)',
+ 'id' => 'order_cycle_incoming_exchange_{{ $index }}_select_all_variants' }
+ Select all
+
+ .exchange-products
+ -# No need to scope product list based on permissions, because if an incoming exchange is visible,
+ -# then all of the variants within it should be visible. May change in the future?
+ .exchange-product{'ng-repeat' => 'product in enterprises[exchange.enterprise_id].supplied_products'}
+
+ .exchange-product-details
+ %label
+ %input{ type: 'checkbox', name: 'order_cycle_incoming_exchange_{{ $parent.$index }}_variants_{{ product.master_id }}',
+ value: 1,
+ 'ng-hide' => 'product.variants.length > 0',
+ 'ng-model' => 'exchange.variants[product.master_id]',
+ 'ofn-sync-distributions' => '{{ product.master_id }}',
+ 'id' => 'order_cycle_incoming_exchange_{{ $parent.$index }}_variants_{{ product.master_id }}',
+ 'ng-disabled' => 'product.variants.length > 0 || !order_cycle.editable_variants_for_incoming_exchanges.hasOwnProperty(exchange.enterprise_id) || order_cycle.editable_variants_for_incoming_exchanges[exchange.enterprise_id].indexOf(product.master_id) < 0' }
+ %img{'ng-src' => '{{ product.image_url }}'}
+ {{ product.name }}
+
+ -# When the master variant is in the order cycle but the product has variants, we want to
+ -# be able to remove the master variant, since it serves no purpose. Display a checkbox to do so.
+ .exchange-product-variant{'ng-show' => 'exchange.variants[product.master_id] && product.variants.length > 0'}
+ %label
+ %input{ type: 'checkbox', name: 'order_cycle_incoming_exchange_{{ $parent.$index }}_variants_{{ product.master_id }}',
+ value: 1,
+ 'ng-model' => 'exchange.variants[product.master_id]',
+ 'ofn-sync-distributions' => '{{ product.master_id }}',
+ 'id' => 'order_cycle_incoming_exchange_{{ $parent.$index }}_variants_{{ product.master_id }}',
+ 'ng-disabled' => '!order_cycle.editable_variants_for_incoming_exchanges.hasOwnProperty(exchange.enterprise_id) || order_cycle.editable_variants_for_incoming_exchanges[exchange.enterprise_id].indexOf(product.master_id) < 0' }
+ Obsolete master
+
+ .exchange-product-variant{'ng-repeat' => 'variant in product.variants'}
+ %label
+ %input{ type: 'checkbox', name: 'order_cycle_incoming_exchange_{{ $parent.$parent.$index }}_variants_{{ variant.id }}',
+ value: 1,
+ 'ng-model' => 'exchange.variants[variant.id]',
+ 'ofn-sync-distributions' => '{{ variant.id }}',
+ 'id' => 'order_cycle_incoming_exchange_{{ $parent.$parent.$index }}_variants_{{ variant.id }}',
+ 'ng-disabled' => '!order_cycle.editable_variants_for_incoming_exchanges.hasOwnProperty(exchange.enterprise_id) || order_cycle.editable_variants_for_incoming_exchanges[exchange.enterprise_id].indexOf(variant.id) < 0' }
+ {{ variant.label }}
diff --git a/app/assets/javascripts/templates/admin/panels/exchange_tags.html.haml b/app/assets/javascripts/templates/admin/panels/exchange_tags.html.haml
new file mode 100644
index 0000000000..0963c480c0
--- /dev/null
+++ b/app/assets/javascripts/templates/admin/panels/exchange_tags.html.haml
@@ -0,0 +1,5 @@
+.row.exchange-tags
+ .sixteen.columns.alpha.omega
+ %span.text-normal Tags
+ %br
+ %tags-with-translation.fullwidth{ object: 'object' }
diff --git a/app/assets/javascripts/templates/admin/save_bar.html.haml b/app/assets/javascripts/templates/admin/save_bar.html.haml
index 08b7cbdf3d..12ccd4537c 100644
--- a/app/assets/javascripts/templates/admin/save_bar.html.haml
+++ b/app/assets/javascripts/templates/admin/save_bar.html.haml
@@ -1,7 +1,6 @@
-#save-bar.animate-show{ ng: { show: 'form.$dirty || StatusMessage.active()' } }
+#save-bar.animate-show{ ng: { show: 'dirty || persist || StatusMessage.active()' } }
.container
.eight.columns.alpha
%h5#status-message{ ng: { style: 'StatusMessage.statusMessage.style' } }
{{ StatusMessage.statusMessage.text || " " }}
- .eight.columns.omega.text-right
- %input{"ng-repeat" => "button in buttons", type: "button", value: "{{button.text}}", ng: { class: "button.class", click: "button.action(button.param)" } }
+ .eight.columns.omega.text-right{ ng: { transclude: true } }
diff --git a/app/assets/javascripts/templates/admin/show_more.html.haml b/app/assets/javascripts/templates/admin/show_more.html.haml
new file mode 100644
index 0000000000..f9a008993c
--- /dev/null
+++ b/app/assets/javascripts/templates/admin/show_more.html.haml
@@ -0,0 +1,4 @@
+%div{ ng: { show: "data.length > limit" } }
+ %input{ type: 'button', value: 'Show More', ng: { click: 'limit = limit + increment' } }
+ or
+ %input{ type: 'button', value: "Show All ({{ data.length - limit }} More)", ng: { click: 'limit = data.length' } }
diff --git a/app/assets/javascripts/templates/admin/tag_rules/discount_order.html.haml b/app/assets/javascripts/templates/admin/tag_rules/discount_order.html.haml
deleted file mode 100644
index 358d9ce1a6..0000000000
--- a/app/assets/javascripts/templates/admin/tag_rules/discount_order.html.haml
+++ /dev/null
@@ -1,37 +0,0 @@
-%div
- %input{ type: "hidden",
- id: "enterprise_tag_rules_attributes_{{tagGroup.startIndex + $index}}_id",
- name: "enterprise[tag_rules_attributes][{{tagGroup.startIndex + $index}}][id]",
- ng: { value: "rule.id" } }
-
- %input{ type: "hidden",
- id: "enterprise_tag_rules_attributes_{{tagGroup.startIndex + $index}}_type",
- name: "enterprise[tag_rules_attributes][{{tagGroup.startIndex + $index}}][type]",
- value: "TagRule::DiscountOrder" }
-
- %input{ type: "hidden",
- id: "enterprise_tag_rules_attributes_{{tagGroup.startIndex + $index}}_preferred_customer_tags",
- name: "enterprise[tag_rules_attributes][{{tagGroup.startIndex + $index}}][preferred_customer_tags]",
- ng: { value: "rule.preferred_customer_tags" } }
-
- %input{ type: "hidden",
- id: "enterprise_tag_rules_attributes_{{tagGroup.startIndex + $index}}_calculator_type",
- name: "enterprise[tag_rules_attributes][{{tagGroup.startIndex + $index}}][calculator_type]",
- value: "Spree::Calculator::FlatPercentItemTotal" }
-
- %input{ type: "hidden",
- id: "enterprise_tag_rules_attributes_{{tagGroup.startIndex + $index}}_calculator_attributes_id",
- name: "enterprise[tag_rules_attributes][{{tagGroup.startIndex + $index}}][calculator_attributes][id]",
- ng: { value: "rule.calculator.id" } }
-
- %input{ type: "hidden",
- name: "enterprise[tag_rules_attributes][{{tagGroup.startIndex + $index}}][calculator_attributes][preferred_flat_percent]",
- ng: { value: "rule.calculator.preferred_flat_percent" } }
-
- %span.text-normal {{ $index + 1 }}. Orders are discounted by
- %input{ type: "number",
- id: "enterprise_tag_rules_attributes_{{tagGroup.startIndex + $index}}_calculator_attributes_preferred_flat_percent",
- min: -100,
- max: 100,
- ng: { model: "rule.calculator.preferred_flat_percent" }, 'invert-number' => true }
- %span.text-normal %
diff --git a/app/assets/javascripts/templates/admin/tag_rules/discount_order_input.html.haml b/app/assets/javascripts/templates/admin/tag_rules/discount_order_input.html.haml
new file mode 100644
index 0000000000..abad41ea62
--- /dev/null
+++ b/app/assets/javascripts/templates/admin/tag_rules/discount_order_input.html.haml
@@ -0,0 +1,7 @@
+%div
+ %input{ type: "number",
+ id: "enterprise_tag_rules_attributes_{{tagGroup.startIndex + $index}}_calculator_attributes_preferred_flat_percent",
+ min: -100,
+ max: 100,
+ ng: { model: "rule.calculator.preferred_flat_percent" }, 'invert-number' => true }
+ %span.text-normal %
diff --git a/app/assets/javascripts/templates/admin/tag_rules/filter_order_cycles_input.html.haml b/app/assets/javascripts/templates/admin/tag_rules/filter_order_cycles_input.html.haml
new file mode 100644
index 0000000000..fd8dac63c7
--- /dev/null
+++ b/app/assets/javascripts/templates/admin/tag_rules/filter_order_cycles_input.html.haml
@@ -0,0 +1,10 @@
+%div
+ %input.fullwidth.light.ofn-select2{ id: "enterprise_tag_rules_attributes_{{tagGroup.startIndex + $index}}_preferred_matched_order_cycles_visibility",
+ name: "enterprise[tag_rules_attributes][{{tagGroup.startIndex + $index}}][preferred_matched_order_cycles_visibility]",
+ ng: { model: "rule.preferred_matched_order_cycles_visibility", if: "!rule.is_default" },
+ data: 'visibilityOptions', "min-search" => 5 }
+ %input{ type: "hidden",
+ id: "enterprise_tag_rules_attributes_{{tagGroup.startIndex + $index}}_preferred_matched_order_cycles_visibility",
+ name: "enterprise[tag_rules_attributes][{{tagGroup.startIndex + $index}}][preferred_matched_order_cycles_visibility]",
+ ng: { value: "'hidden'", if: "rule.is_default" } }
+ %span.text-normal{ ng: { if: "rule.is_default" } } not visible
diff --git a/app/assets/javascripts/templates/admin/tag_rules/filter_payment_methods_input.html.haml b/app/assets/javascripts/templates/admin/tag_rules/filter_payment_methods_input.html.haml
new file mode 100644
index 0000000000..0dd533235d
--- /dev/null
+++ b/app/assets/javascripts/templates/admin/tag_rules/filter_payment_methods_input.html.haml
@@ -0,0 +1,10 @@
+%div
+ %input.fullwidth.light.ofn-select2{ id: "enterprise_tag_rules_attributes_{{tagGroup.startIndex + $index}}_preferred_matched_payment_methods_visibility",
+ name: "enterprise[tag_rules_attributes][{{tagGroup.startIndex + $index}}][preferred_matched_payment_methods_visibility]",
+ ng: { model: "rule.preferred_matched_payment_methods_visibility", if: "!rule.is_default" },
+ data: 'visibilityOptions', "min-search" => 5 }
+ %input{ type: "hidden",
+ id: "enterprise_tag_rules_attributes_{{tagGroup.startIndex + $index}}_preferred_matched_payment_methods_visibility",
+ name: "enterprise[tag_rules_attributes][{{tagGroup.startIndex + $index}}][preferred_matched_payment_methods_visibility]",
+ ng: { value: "'hidden'", if: "rule.is_default" } }
+ %span.text-normal{ ng: { if: "rule.is_default" } } not visible
diff --git a/app/assets/javascripts/templates/admin/tag_rules/filter_products_input.html.haml b/app/assets/javascripts/templates/admin/tag_rules/filter_products_input.html.haml
new file mode 100644
index 0000000000..da112c9db2
--- /dev/null
+++ b/app/assets/javascripts/templates/admin/tag_rules/filter_products_input.html.haml
@@ -0,0 +1,10 @@
+%div
+ %input.fullwidth.light.ofn-select2{ id: "enterprise_tag_rules_attributes_{{tagGroup.startIndex + $index}}_preferred_matched_variants_visibility",
+ name: "enterprise[tag_rules_attributes][{{tagGroup.startIndex + $index}}][preferred_matched_variants_visibility]",
+ ng: { model: "rule.preferred_matched_variants_visibility", if: "!rule.is_default" },
+ data: 'visibilityOptions', "min-search" => 5 }
+ %input{ type: "hidden",
+ id: "enterprise_tag_rules_attributes_{{tagGroup.startIndex + $index}}_preferred_matched_variants_visibility",
+ name: "enterprise[tag_rules_attributes][{{tagGroup.startIndex + $index}}][preferred_matched_variants_visibility]",
+ ng: { value: "'hidden'", if: "rule.is_default" } }
+ %span.text-normal{ ng: { if: "rule.is_default" } } not visible
diff --git a/app/assets/javascripts/templates/admin/tag_rules/filter_shipping_methods.html.haml b/app/assets/javascripts/templates/admin/tag_rules/filter_shipping_methods.html.haml
deleted file mode 100644
index 6552d834b5..0000000000
--- a/app/assets/javascripts/templates/admin/tag_rules/filter_shipping_methods.html.haml
+++ /dev/null
@@ -1,27 +0,0 @@
-%div
- %input{ type: "hidden",
- id: "enterprise_tag_rules_attributes_{{tagGroup.startIndex + $index}}_id",
- name: "enterprise[tag_rules_attributes][{{tagGroup.startIndex + $index}}][id]",
- ng: { value: "rule.id" } }
-
- %input{ type: "hidden",
- id: "enterprise_tag_rules_attributes_{{tagGroup.startIndex + $index}}_type",
- name: "enterprise[tag_rules_attributes][{{tagGroup.startIndex + $index}}][type]",
- value: "TagRule::FilterShippingMethods" }
-
- %input{ type: "hidden",
- id: "enterprise_tag_rules_attributes_{{tagGroup.startIndex + $index}}_preferred_customer_tags",
- name: "enterprise[tag_rules_attributes][{{tagGroup.startIndex + $index}}][preferred_customer_tags]",
- ng: { value: "rule.preferred_customer_tags" } }
-
- %input{ type: "hidden",
- id: "enterprise_tag_rules_attributes_{{tagGroup.startIndex + $index}}_preferred_shipping_method_tags",
- name: "enterprise[tag_rules_attributes][{{tagGroup.startIndex + $index}}][preferred_shipping_method_tags]",
- ng: { value: "rule.preferred_customer_tags" } }
-
- %span.text-normal {{ $index + 1 }}. Shipping methods with matching tags are
- %input.light.ofn-select2{ id: "enterprise_tag_rules_attributes_{{tagGroup.startIndex + $index}}_preferred_matched_shipping_methods_visibility",
- name: "enterprise[tag_rules_attributes][{{tagGroup.startIndex + $index}}][preferred_matched_shipping_methods_visibility]",
- ng: { model: "rule.preferred_matched_shipping_methods_visibility"},
- data: 'visibilityOptions', "min-search" => 5 }
- -# %tags-with-translation{ object: "rule", "tags-attr" => "shipping_method_tags", "tag-list-attr" => "preferred_shipping_method_tags" }
diff --git a/app/assets/javascripts/templates/admin/tag_rules/filter_shipping_methods_input.html.haml b/app/assets/javascripts/templates/admin/tag_rules/filter_shipping_methods_input.html.haml
new file mode 100644
index 0000000000..a46b9abd14
--- /dev/null
+++ b/app/assets/javascripts/templates/admin/tag_rules/filter_shipping_methods_input.html.haml
@@ -0,0 +1,10 @@
+%div
+ %input.fullwidth.light.ofn-select2{ id: "enterprise_tag_rules_attributes_{{tagGroup.startIndex + $index}}_preferred_matched_shipping_methods_visibility",
+ name: "enterprise[tag_rules_attributes][{{tagGroup.startIndex + $index}}][preferred_matched_shipping_methods_visibility]",
+ ng: { model: "rule.preferred_matched_shipping_methods_visibility", if: "!rule.is_default" },
+ data: 'visibilityOptions', "min-search" => 5 }
+ %input{ type: "hidden",
+ id: "enterprise_tag_rules_attributes_{{tagGroup.startIndex + $index}}_preferred_matched_shipping_methods_visibility",
+ name: "enterprise[tag_rules_attributes][{{tagGroup.startIndex + $index}}][preferred_matched_shipping_methods_visibility]",
+ ng: { value: "'hidden'", if: "rule.is_default" } }
+ %span.text-normal{ ng: { if: "rule.is_default" } } not visible
diff --git a/app/assets/javascripts/templates/admin/tag_rules/tag_rule.html.haml b/app/assets/javascripts/templates/admin/tag_rules/tag_rule.html.haml
new file mode 100644
index 0000000000..7907b9dfda
--- /dev/null
+++ b/app/assets/javascripts/templates/admin/tag_rules/tag_rule.html.haml
@@ -0,0 +1,50 @@
+%div{ id: "tr_{{tagGroup.startIndex + $index}}" }
+ %table
+ %colgroup
+ %col.text{ width: "35%" }
+ %col.inputs{ width: "55%" }
+ %col.actions{ width: "10%" }
+ %tr
+ %td
+ %input{ type: "hidden",
+ id: "enterprise_tag_rules_attributes_{{tagGroup.startIndex + $index}}_id",
+ name: "enterprise[tag_rules_attributes][{{tagGroup.startIndex + $index}}][id]",
+ ng: { value: "rule.id" } }
+
+ %input{ type: "hidden",
+ id: "enterprise_tag_rules_attributes_{{tagGroup.startIndex + $index}}_type",
+ name: "enterprise[tag_rules_attributes][{{tagGroup.startIndex + $index}}][type]",
+ ng: { value: "rule.type" } }
+
+ %input{ type: "hidden",
+ id: "enterprise_tag_rules_attributes_{{tagGroup.startIndex + $index}}_priority",
+ name: "enterprise[tag_rules_attributes][{{tagGroup.startIndex + $index}}][priority]",
+ ng: { value: "tagGroup.startIndex + $index" } }
+
+ %input{ type: "hidden",
+ id: "enterprise_tag_rules_attributes_{{tagGroup.startIndex + $index}}_is_default",
+ name: "enterprise[tag_rules_attributes][{{tagGroup.startIndex + $index}}][is_default]",
+ ng: { value: "rule.is_default" } }
+
+ %input{ type: "hidden",
+ id: "enterprise_tag_rules_attributes_{{tagGroup.startIndex + $index}}_preferred_customer_tags",
+ name: "enterprise[tag_rules_attributes][{{tagGroup.startIndex + $index}}][preferred_customer_tags]",
+ ng: { value: "rule.preferred_customer_tags" } }
+
+ %input{ type: "hidden",
+ id: "enterprise_tag_rules_attributes_{{tagGroup.startIndex + $index}}_preferred_{{opt[rule.type].taggable}}_tags",
+ name: "enterprise[tag_rules_attributes][{{tagGroup.startIndex + $index}}][preferred_{{opt[rule.type].taggable}}_tags]",
+ ng: { value: "opt[rule.type].tagListFor(rule)" } }
+
+ %span.text-normal {{ opt[rule.type].textTop }}
+ %td
+ %tags-with-translation{ object: "rule", max: 1, "tags-attr" => "{{opt[rule.type].tagsAttr}}", "tag-list-attr" => "{{opt[rule.type].tagListAttr}}" }
+ %td.actions{ rowspan: 2 }
+ %a{ ng: { click: "deleteTagRule(tagGroup || defaultTagGroup, rule)" }, :class => "delete-tag-rule icon-trash no-text" }
+ %tr
+ %td
+ %span.text-normal {{ opt[rule.type].textBottom }}
+ %td
+ %div{ ng: { include: "opt[rule.type].inputTemplate"} }
+
+ %hr
diff --git a/app/assets/javascripts/templates/admin/tags_input.html.haml b/app/assets/javascripts/templates/admin/tags_input.html.haml
index 2dd75ce5ac..3c604b4b15 100644
--- a/app/assets/javascripts/templates/admin/tags_input.html.haml
+++ b/app/assets/javascripts/templates/admin/tags_input.html.haml
@@ -1,5 +1,7 @@
-%tags-input{ template: 'admin/tag.html', ng: { model: 'object[tagsAttr]' } }
- %auto-complete{source: "findTags({query: $query})",
+%tags-input{ template: 'admin/tag.html',
+ ng: { model: 'object[tagsAttr]', class: "{'limit-reached': limitReached}"},
+ on: { tag: { added: 'tagAdded()', removed:'tagRemoved()' } } }
+ %auto-complete{ ng: { if: "findTags" }, source: "findTags({query: $query})",
template: "admin/tag_autocomplete.html",
"min-length" => "0",
"load-on-focus" => "true",
diff --git a/app/assets/javascripts/templates/filter_selector.html.haml b/app/assets/javascripts/templates/filter_selector.html.haml
index a53c4f44db..cd4135ebee 100644
--- a/app/assets/javascripts/templates/filter_selector.html.haml
+++ b/app/assets/javascripts/templates/filter_selector.html.haml
@@ -1,4 +1,4 @@
-%ul{ bindonce: true }
+%ul
%active-selector{ ng: { repeat: "selector in allSelectors", show: "ifDefined(selector.fits, true)" } }
%render-svg{path: "{{selector.object.icon}}", ng: { if: "selector.object.icon"} }
- %span{"bo-text" => "selector.object.name"}
+ %span{"ng-bind" => "::selector.object.name"}
diff --git a/app/assets/javascripts/templates/forgot.html.haml b/app/assets/javascripts/templates/forgot.html.haml
index c1990d778a..958f1daa39 100644
--- a/app/assets/javascripts/templates/forgot.html.haml
+++ b/app/assets/javascripts/templates/forgot.html.haml
@@ -1,10 +1,5 @@
-%tab#forgot{"ng-controller" => "ForgotCtrl",
- heading: "{{'forgot_password' | t}}",
- active: "active(path)",
- select: "select(path)"}
-
- %form{"ng-submit" => "submit()"}
-
+%tab#forgot{ heading: "{{'forgot_password' | t}}", active: "tabs.forgot.active", select: "select(path)"}
+ %form{ ng: { controller: "ForgotCtrl", submit: "submit()" } }
.row
.large-12.columns
.alert-box.success.radius{"ng-show" => "sent"}
@@ -13,18 +8,18 @@
%div{"ng-show" => "!sent"}
.alert-box.alert{"ng-show" => "errors != null"}
{{ errors }}
-
+
.row
.large-12.columns
%label{for: "email"} {{'signup_email' | t}}
- %input.title.input-text{name: "email",
+ %input.title.input-text{name: "email",
type: "email",
id: "email",
tabindex: 1,
"ng-model" => "spree_user.email"}
.row
.large-12.columns
- %input.button.primary{name: "commit",
- tabindex: "3",
- type: "submit",
+ %input.button.primary{name: "commit",
+ tabindex: "3",
+ type: "submit",
value: "{{'reset_password' | t}}"}
diff --git a/app/assets/javascripts/templates/login.html.haml b/app/assets/javascripts/templates/login.html.haml
index 44d2e08ced..79511d8968 100644
--- a/app/assets/javascripts/templates/login.html.haml
+++ b/app/assets/javascripts/templates/login.html.haml
@@ -1,8 +1,5 @@
-%tab#login-content{"ng-controller" => "LoginCtrl",
- heading: "{{'label_login' | t}}",
- active: "active(path)",
- select: "select(path)"}
- %form{"ng-submit" => "submit()"}
+%tab#login-content{ heading: "{{'label_login' | t}}", active: "tabs.login.active", select: "select(path)"}
+ %form{ ng: { controller: "LoginCtrl", submit: "submit()" } }
.row
.large-12.columns
.alert-box.alert{"ng-show" => "errors != null"}
@@ -10,7 +7,7 @@
.row
.large-12.columns
%label{for: "email"} {{'email' | t}}
- %input.title.input-text{name: "email",
+ %input.title.input-text{name: "email",
type: "email",
id: "email",
tabindex: 1,
@@ -18,7 +15,7 @@
.row
.large-12.columns
%label{for: "password"} {{'password' | t}}
- %input.title.input-text{name: "password",
+ %input.title.input-text{name: "password",
type: "password",
id: "password",
autocomplete: "off",
@@ -26,15 +23,15 @@
"ng-model" => "spree_user.password"}
.row
.large-12.columns
- %input{name: "remember_me",
- type: "checkbox",
- id: "remember_me",
+ %input{name: "remember_me",
+ type: "checkbox",
+ id: "remember_me",
value: "1",
"ng-model" => "spree_user.remember_me"}
%label{for: "remember_me"} {{'remember_me' | t}}
.row
.large-12.columns
- %input.button.primary{name: "commit",
- tabindex: "3",
- type: "submit",
+ %input.button.primary{name: "commit",
+ tabindex: "3",
+ type: "submit",
value: "{{'label_login' | t}}"}
diff --git a/app/assets/javascripts/templates/partials/contact.html.haml b/app/assets/javascripts/templates/partials/contact.html.haml
index 12aa5ff061..d173bcf366 100644
--- a/app/assets/javascripts/templates/partials/contact.html.haml
+++ b/app/assets/javascripts/templates/partials/contact.html.haml
@@ -1,11 +1,11 @@
-%div.contact-container{bindonce: true}
- %div.modal-centered{"bo-if" => "enterprise.email_address || enterprise.website || enterprise.phone"}
+%div.contact-container
+ %div.modal-centered{"ng-if" => "::enterprise.email_address || enterprise.website || enterprise.phone"}
%p.modal-header {{'contact' | t}}
- %p{"bo-if" => "enterprise.phone", "bo-text" => "enterprise.phone"}
+ %p{"ng-if" => "::enterprise.phone", "ng-bind" => "::enterprise.phone"}
- %p.word-wrap{"ng-if" => "enterprise.email_address"}
- %a{"bo-href" => "enterprise.email_address | stripUrl", target: "_blank", mailto: true}
- %span.email{"bo-bind" => "enterprise.email_address | stripUrl"}
+ %p.word-wrap{"ng-if" => "::enterprise.email_address"}
+ %a{"ng-href" => "{{::enterprise.email_address | stripUrl}}", target: "_blank", mailto: true}
+ %span.email{"ng-bind" => "::enterprise.email_address | stripUrl"}
%p.word-wrap{"ng-if" => "enterprise.website"}
- %a{"bo-href-i" => "http://{{enterprise.website | stripUrl}}", target: "_blank", "bo-bind" => "enterprise.website | stripUrl"}
+ %a{"ng-href" => "http://{{::enterprise.website | stripUrl}}", target: "_blank", "ng-bind" => "::enterprise.website | stripUrl"}
diff --git a/app/assets/javascripts/templates/partials/enterprise_details.html.haml b/app/assets/javascripts/templates/partials/enterprise_details.html.haml
index fc0a638c8d..d1d208c5c4 100644
--- a/app/assets/javascripts/templates/partials/enterprise_details.html.haml
+++ b/app/assets/javascripts/templates/partials/enterprise_details.html.haml
@@ -1,4 +1,4 @@
-.row{bindonce: true}
+.row
.small-12.large-8.columns
/ TODO: Rob add logic for taxons and properties too:
/ %div{"ng-if" => "enterprise.long_description.length > 0 || enterprise.logo"}
@@ -22,8 +22,8 @@
-# / TODO: Rob - need popover, use will's directive or this? http://pineconellc.github.io/angular-foundation/
-#
.about-container.pad-top
- %img.enterprise-logo{"bo-src" => "enterprise.logo", "bo-if" => "enterprise.logo"}
- %p.text-small{"ng-bind-html" => "enterprise.long_description"}
+ %img.enterprise-logo{"ng-src" => "{{::enterprise.logo}}", "ng-if" => "::enterprise.logo"}
+ %p.text-small{"ng-bind-html" => "::enterprise.long_description"}
.small-12.large-4.columns
%ng-include{src: "'partials/contact.html'"}
%ng-include{src: "'partials/follow.html'"}
diff --git a/app/assets/javascripts/templates/partials/enterprise_header.html.haml b/app/assets/javascripts/templates/partials/enterprise_header.html.haml
index 44175a57fe..066ebdd957 100644
--- a/app/assets/javascripts/templates/partials/enterprise_header.html.haml
+++ b/app/assets/javascripts/templates/partials/enterprise_header.html.haml
@@ -1,13 +1,13 @@
-.highlight{bindonce: true, "ng-class" => "{'is_distributor' : enterprise.is_distributor}"}
+.highlight{"ng-class" => "::{'is_distributor' : enterprise.is_distributor}"}
.highlight-top.row
.small-12.medium-7.large-8.columns
- %h3{"ng-if" => "enterprise.is_distributor"}
- %a{"bo-href" => "enterprise.path", "ofn-change-hub" => "enterprise"}
- %i{"ng-class" => "enterprise.icon_font"}
- %span{"bo-text" => "enterprise.name"}
- %h3{"ng-if" => "!enterprise.is_distributor", "ng-class" => "{'is_producer' : enterprise.is_primary_producer}"}
- %i{"ng-class" => "enterprise.icon_font"}
- %span{"bo-text" => "enterprise.name"}
+ %h3{"ng-if" => "::enterprise.is_distributor"}
+ %a{"ng-href" => "{{::enterprise.path}}", "ofn-change-hub" => "enterprise"}
+ %i{"ng-class" => "::enterprise.icon_font"}
+ %span{"ng-bind" => "::enterprise.name"}
+ %h3{"ng-if" => "::!enterprise.is_distributor", "ng-class" => "::{'is_producer' : enterprise.is_primary_producer}"}
+ %i{"ng-class" => "::enterprise.icon_font"}
+ %span{"ng-bind" => "::enterprise.name"}
.small-12.medium-5.large-4.columns.text-right.small-only-text-left
- %p{"bo-bind" => "[enterprise.address.city, enterprise.address.state_name] | printArray"}
- %img.hero-img{"bo-src" => "enterprise.promo_image"}
+ %p{"ng-bind" => "::[enterprise.address.city, enterprise.address.state_name] | printArray"}
+ %img.hero-img{"ng-src" => "{{::enterprise.promo_image}}"}
diff --git a/app/assets/javascripts/templates/partials/follow.html.haml b/app/assets/javascripts/templates/partials/follow.html.haml
index c04567a357..a47054d1f3 100644
--- a/app/assets/javascripts/templates/partials/follow.html.haml
+++ b/app/assets/javascripts/templates/partials/follow.html.haml
@@ -1,19 +1,18 @@
-%div.modal-centered{bindonce: true, "bo-if" => "enterprise.twitter || enterprise.facebook || enterprise.linkedin || enterprise.instagram"}
+%div.modal-centered{ "ng-if" => "::enterprise.twitter || enterprise.facebook || enterprise.linkedin || enterprise.instagram"}
%p.modal-header {{'follow' | t}}
.follow-icons
- %span{"bo-if" => "enterprise.twitter"}
- %a{"bo-href-i" => "http://twitter.com/{{enterprise.twitter}}", target: "_blank"}
+ %span{"ng-if" => "::enterprise.twitter"}
+ %a{"ng-href" => "http://twitter.com/{{::enterprise.twitter}}", target: "_blank"}
%i.ofn-i_041-twitter
-
- %span{"bo-if" => "enterprise.facebook"}
- %a{"bo-href-i" => "http://{{enterprise.facebook | stripUrl}}", target: "_blank"}
- %i.ofn-i_044-facebook
-
- %span{"bo-if" => "enterprise.linkedin"}
- %a{"bo-href-i" => "http://{{enterprise.linkedin | stripUrl}}", target: "_blank"}
- %i.ofn-i_042-linkedin
-
- %span{"bo-if" => "enterprise.instagram"}
- %a{"bo-href-i" => "http://instagram.com/{{enterprise.instagram}}", target: "_blank"}
- %i.ofn-i_043-instagram
+ %span{"ng-if" => "::enterprise.facebook"}
+ %a{"ng-href" => "http://{{::enterprise.facebook | stripUrl}}", target: "_blank"}
+ %i.ofn-i_044-facebook
+
+ %span{"ng-if" => "::enterprise.linkedin"}
+ %a{"ng-href" => "http://{{::enterprise.linkedin | stripUrl}}", target: "_blank"}
+ %i.ofn-i_042-linkedin
+
+ %span{"ng-if" => "::enterprise.instagram"}
+ %a{"ng-href" => "http://instagram.com/{{::enterprise.instagram}}", target: "_blank"}
+ %i.ofn-i_043-instagram
diff --git a/app/assets/javascripts/templates/partials/hub_details.html.haml b/app/assets/javascripts/templates/partials/hub_details.html.haml
index fa75f72253..e3a0234df1 100644
--- a/app/assets/javascripts/templates/partials/hub_details.html.haml
+++ b/app/assets/javascripts/templates/partials/hub_details.html.haml
@@ -1,24 +1,24 @@
-.row.pad-top{bindonce: true, ng: { if: 'enterprise.is_distributor' } }
+.row.pad-top{ng: { if: 'enterprise.is_distributor' } }
.cta-container.small-12.columns
.row
.small-4.columns
%label{"active-table-hub-link" => "enterprise", change: "{{'change_shop' | t}}", shop: "{{'shop_at' | t}}"}
.small-8.columns.right
- %label.right{"bo-if" => "enterprise.pickup || enterprise.delivery"}
+ %label.right{"ng-if" => "::enterprise.pickup || enterprise.delivery"}
{{'hubs_delivery_options' | t}}:
- %span{"bo-if" => "enterprise.pickup"}
+ %span{"ng-if" => "::enterprise.pickup"}
%i.ofn-i_038-takeaway
{{'hubs_pickup' | t}}
- %span{"bo-if" => "enterprise.delivery"}
+ %span{"ng-if" => "::enterprise.delivery"}
%i.ofn-i_039-delivery
{{'hubs_delivery' | t}}
.row
.columns.small-12
- %a.cta-hub{"bo-href" => "enterprise.path",
+ %a.cta-hub{"ng-href" => "{{::enterprise.path}}",
"ng-class" => "{primary: enterprise.active, secondary: !enterprise.active}",
"ofn-change-hub" => "enterprise"}
- %i.ofn-i_033-open-sign{"bo-if" => "enterprise.active"}
- %i.ofn-i_032-closed-sign{"bo-if" => "!enterprise.active"}
- .hub-name{"bo-text" => "enterprise.name"}
- .button-address{"bo-bind" => "[enterprise.address.city, enterprise.address.state_name] | printArray"}
+ %i.ofn-i_033-open-sign{"ng-if" => "::enterprise.active"}
+ %i.ofn-i_032-closed-sign{"ng-if" => "::!enterprise.active"}
+ .hub-name{"ng-bind" => "::enterprise.name"}
+ .button-address{"ng-bind" => "::[enterprise.address.city, enterprise.address.state_name] | printArray"}
/ %i.ofn-i_007-caret-right
diff --git a/app/assets/javascripts/templates/partials/producer_details.html.haml b/app/assets/javascripts/templates/partials/producer_details.html.haml
index 599b977dfc..cb7ee70482 100644
--- a/app/assets/javascripts/templates/partials/producer_details.html.haml
+++ b/app/assets/javascripts/templates/partials/producer_details.html.haml
@@ -1,20 +1,20 @@
-# Show places to buy products from this producer, when there are any
-# Do not show this for producer shops selling only their own produce,
-# Since a shopping link will already have been displayed in hub_details.html.haml
-.row.active_table_row.pad-top{bindonce: true, "ng-if" => "enterprise.is_primary_producer && enterprise.hubs.length > 0 && !(enterprise.hubs.length == 1 && enterprise.hubs[0] == enterprise)"}
+.row.active_table_row.pad-top{ "ng-if" => "enterprise.is_primary_producer && enterprise.hubs.length > 0 && !(enterprise.hubs.length == 1 && enterprise.hubs[0] == enterprise)"}
.columns.small-12
.row
.columns.small-12.fat
- %div{"bo-if" => "enterprise.name"}
- %label{"bo-html" => "'shop_for_products_html' | t:{enterprise: enterprise.name}"}
- %div.show-for-medium-up{"bo-if" => "!enterprise.name"}
+ %div{"ng-if" => "::enterprise.name"}
+ %label{"ng-html" => "::'shop_for_products_html' | t:{enterprise: enterprise.name}"}
+ %div.show-for-medium-up{"ng-if" => "::!enterprise.name"}
.row.cta-container
.columns.small-12
%a.cta-hub{"ng-repeat" => "hub in enterprise.hubs | filter:{id: '!'+enterprise.id} | orderBy:'-active'",
- "bo-href" => "hub.path", "ofn-empties-cart" => "hub",
- "bo-class" => "{primary: hub.active, secondary: !hub.active}"}
- %i.ofn-i_033-open-sign{"bo-if" => "hub.active"}
- %i.ofn-i_032-closed-sign{"bo-if" => "!hub.active"}
- .hub-name{"bo-text" => "hub.name"}
- .button-address{"bo-bind" => "[hub.address.city, hub.address.state_name] | printArray"}
+ "ng-href" => "{{::hub.path}}", "ofn-empties-cart" => "hub",
+ "ng-class" => "::{primary: hub.active, secondary: !hub.active}"}
+ %i.ofn-i_033-open-sign{"ng-if" => "::hub.active"}
+ %i.ofn-i_032-closed-sign{"ng-if" => "::!hub.active"}
+ .hub-name{"ng-bind" => "::hub.name"}
+ .button-address{"ng-bind" => "::[hub.address.city, hub.address.state_name] | printArray"}
diff --git a/app/assets/javascripts/templates/partials/shop_variant_no_group_buy.html.haml b/app/assets/javascripts/templates/partials/shop_variant_no_group_buy.html.haml
index 1153d93b0f..3a52ba9d07 100644
--- a/app/assets/javascripts/templates/partials/shop_variant_no_group_buy.html.haml
+++ b/app/assets/javascripts/templates/partials/shop_variant_no_group_buy.html.haml
@@ -1,4 +1,4 @@
-.small-5.medium-3.large-3.columns.text-right{"bo-if" => "!variant.product.group_buy"}
+.small-5.medium-3.large-3.columns.text-right{"ng-if" => "::!variant.product.group_buy"}
%input{type: :number,
integer: true,
diff --git a/app/assets/javascripts/templates/partials/shop_variant_with_group_buy.html.haml b/app/assets/javascripts/templates/partials/shop_variant_with_group_buy.html.haml
index 6601f0c019..d4c88092b6 100644
--- a/app/assets/javascripts/templates/partials/shop_variant_with_group_buy.html.haml
+++ b/app/assets/javascripts/templates/partials/shop_variant_with_group_buy.html.haml
@@ -1,4 +1,4 @@
-.small-5.medium-3.large-3.columns.text-right{"bo-if" => "variant.product.group_buy"}
+.small-5.medium-3.large-3.columns.text-right{"ng-if" => "::variant.product.group_buy"}
%span.bulk-input-container
%span.bulk-input
%input.bulk.first{type: :number,
diff --git a/app/assets/javascripts/templates/price_breakdown.html.haml b/app/assets/javascripts/templates/price_breakdown.html.haml
index 993c20bc9b..fa884cd4f0 100644
--- a/app/assets/javascripts/templates/price_breakdown.html.haml
+++ b/app/assets/javascripts/templates/price_breakdown.html.haml
@@ -1,38 +1,37 @@
-.joyride-tip-guide.price_breakdown{bindonce: true, "ng-class" => "{ in: tt_isOpen, fade: tt_animation }"}
+.joyride-tip-guide.price_breakdown{"ng-class" => "{ in: tt_isOpen, fade: tt_animation }"}
%span.joyride-nub.right
.joyride-content-wrapper
.collapsed{"ng-show" => "!expanded"}
%price-percentage{percentage: 'variant.basePricePercentage'}
- %a{"ng-click" => "expanded = !expanded"}
- %span{"bo-text" => "'price_breakdown' | t"}
+ %a{"ng-click" => "expanded = !expanded"}
+ %span{"ng-bind" => "::'price_breakdown' | t"}
%i.ofn-i_005-caret-down
.expanded{"ng-show" => "expanded"}
%ul
%li.cost
.right {{ variant.price | localizeCurrency }}
- %span{"bo-text" => "'item_cost' | t"}
- %li.admin-fee{"bo-if" => "variant.fees.admin"}
+ %span{"ng-bind" => "::'item_cost' | t"}
+ %li.admin-fee{"ng-if" => "::variant.fees.admin"}
.right {{ variant.fees.admin | localizeCurrency }}
- %span{"bo-text" => "'admin_fee' | t"}
- %li.sales-fee{"bo-if" => "variant.fees.sales"}
+ %span{"ng-bind" => "::'admin_fee' | t"}
+ %li.sales-fee{"ng-if" => "::variant.fees.sales"}
.right {{ variant.fees.sales | localizeCurrency }}
- %span{"bo-text" => "'sales_fee' | t"}
- %li.packing-fee{"bo-if" => "variant.fees.packing"}
+ %span{"ng-bind" => "::'sales_fee' | t"}
+ %li.packing-fee{"ng-if" => "::variant.fees.packing"}
.right {{ variant.fees.packing | localizeCurrency }}
- %span{"bo-text" => "'packing_fee' | t"}
- %li.transport-fee{"bo-if" => "variant.fees.transport"}
+ %span{"ng-bind" => "::'packing_fee' | t"}
+ %li.transport-fee{"ng-if" => "::variant.fees.transport"}
.right {{ variant.fees.transport | localizeCurrency }}
- %span{"bo-text" => "'transport_fee' | t"}
- %li.fundraising-fee{"bo-if" => "variant.fees.fundraising"}
+ %span{"ng-bind" => "::'transport_fee' | t"}
+ %li.fundraising-fee{"ng-if" => "::variant.fees.fundraising"}
.right {{ variant.fees.fundraising | localizeCurrency }}
- %span{"bo-text" => "'fundraising_fee' | t"}
+ %span{"ng-bind" => "::'fundraising_fee' | t"}
%li.total
%strong
.right = {{ variant.price_with_fees | localizeCurrency }}
- %a{"ng-click" => "expanded = !expanded"}
- %span{"bo-text" => "'price_graph' | t"}
+ %a{"ng-click" => "expanded = !expanded"}
+ %span{"ng-bind" => "::'price_graph' | t"}
%i.ofn-i_006-caret-up
-
diff --git a/app/assets/javascripts/templates/product_modal.html.haml b/app/assets/javascripts/templates/product_modal.html.haml
index e13f6df60d..4b21c054a4 100644
--- a/app/assets/javascripts/templates/product_modal.html.haml
+++ b/app/assets/javascripts/templates/product_modal.html.haml
@@ -1,26 +1,26 @@
-.row{bindonce: true}
+.row
.columns.small-12.large-6.product-header
- %h3{"bo-text" => "product.name"}
+ %h3{"ng-bind" => "::product.name"}
%span
%em {{'products_from' | t}}
- %span{"bo-text" => "enterprise.name"}
+ %span{"ng-bind" => "::enterprise.name"}
%br
.filter-shopfront.taxon-selectors.inline-block
- %filter-selector{ objects: "[product] | taxonsOf" }
+ %filter-selector{ 'selector-set' => "productTaxonSelectors", objects: "[product] | taxonsOf" }
.filter-shopfront.property-selectors.inline-block
- %filter-selector{ objects: "[product] | propertiesWithValuesOf" }
+ %filter-selector{ 'selector-set' => "productPropertySelectors", objects: "[product] | propertiesWithValuesOf" }
%div{"ng-if" => "product.description"}
%hr
- %p.text-small{"bo-text" => "product.description"}
+ %p.text-small{"ng-bind" => "::product.description"}
%hr
.columns.small-12.large-6
- %img.product-img{"bo-src" => "product.largeImage", "bo-if" => "product.largeImage"}
- %img.product-img.placeholder{"bo-src" => "'/assets/noimage/large.png'", "bo-if" => "!product.largeImage"}
+ %img.product-img{"ng-src" => "{{::product.largeImage}}", "ng-if" => "::product.largeImage"}
+ %img.product-img.placeholder{ src: "/assets/noimage/large.png", "ng-if" => "::!product.largeImage"}
%ng-include{src: "'partials/close.html'"}
diff --git a/app/assets/javascripts/templates/registration/details.html.haml b/app/assets/javascripts/templates/registration/details.html.haml
index f03a48ec59..6bd3fc02ba 100644
--- a/app/assets/javascripts/templates/registration/details.html.haml
+++ b/app/assets/javascripts/templates/registration/details.html.haml
@@ -1,19 +1,19 @@
-.container#registration-details{bindonce: true}
+.container#registration-details
%ng-include{ src: "'registration/steps.html'" }
.row
.small-12.columns
%header
%h2 {{'registration_detail_headline' | t}}
- %h5{ bo: { if: "enterprise.type != 'own'" } } {{'registration_detail_enterprise' | t}}
- %h5{ bo: { if: "enterprise.type == 'own'" } } {{'registration_detail_producer' | t}}
+ %h5{ ng: { if: "::enterprise.type != 'own'" } } {{'registration_detail_enterprise' | t}}
+ %h5{ ng: { if: "::enterprise.type == 'own'" } } {{'registration_detail_producer' | t}}
%form{ name: 'details', novalidate: true, ng: { controller: "RegistrationFormCtrl", submit: "selectIfValid('contact',details)" } }
.row
.small-12.medium-9.large-12.columns.end
.field
- %label{ for: 'enterprise_name', bo: { if: "enterprise.type != 'own'" } } {{'registration_detail_name_enterprise' | t}}
- %label{ for: 'enterprise_name', bo: { if: "enterprise.type == 'own'" } } {{'registration_detail_name_producer' | t}}
+ %label{ for: 'enterprise_name', ng: { if: "::enterprise.type != 'own'" } } {{'registration_detail_name_enterprise' | t}}
+ %label{ for: 'enterprise_name', ng: { if: "::enterprise.type == 'own'" } } {{'registration_detail_name_producer' | t}}
%input.chunky{ id: 'enterprise_name', name: 'name', placeholder: "{{'registration_detail_name_placeholder' | t}}", required: true, ng: { model: 'enterprise.name' } }
%span.error{ ng: { show: "details.name.$error.required && submitted" } }
{{'registration_detail_name_error' | t}}
diff --git a/app/assets/javascripts/templates/registration/finished.html.haml b/app/assets/javascripts/templates/registration/finished.html.haml
index 5c7b3b6ad0..76403cb1c8 100644
--- a/app/assets/javascripts/templates/registration/finished.html.haml
+++ b/app/assets/javascripts/templates/registration/finished.html.haml
@@ -10,6 +10,6 @@
.small-12.columns.text-center
%h4{ "ng-bind" => "'registration_finished_activate' | t:{enterprise: enterprise.name}" }
- %p{ "ng-bind-html" => "t('registration_finished_activate_instruction_html', {email: enterprise.email})"}
+ %p{ "ng-bind-html" => "'registration_finished_activate_instruction_html' | t:{email: enterprise.email}"}
%a.button.primary{ type: "button", href: "/" } {{'registration_finished_action' | t}} >
diff --git a/app/assets/javascripts/templates/registration/type.html.haml b/app/assets/javascripts/templates/registration/type.html.haml
index c63b40239e..85a81398d7 100644
--- a/app/assets/javascripts/templates/registration/type.html.haml
+++ b/app/assets/javascripts/templates/registration/type.html.haml
@@ -1,4 +1,4 @@
-.container#registration-type{bindonce: true}
+.container#registration-type
%ng-include{ src: "'registration/steps.html'" }
@@ -10,7 +10,7 @@
{{'registration_type_question' | t}}
%form{ name: 'type', novalidate: true, ng: { controller: "RegistrationFormCtrl", submit: "create(type)" } }
- .row#enterprise-types{ 'data-equalizer' => true, bo: { if: "enterprise.type != 'own'" } }
+ .row#enterprise-types{ 'data-equalizer' => true, ng: { if: "::enterprise.type != 'own'" } }
.small-12.columns.field
.row
.small-12.medium-6.large-6.columns{ 'data-equalizer-watch' => true }
diff --git a/app/assets/javascripts/templates/shop_variant.html.haml b/app/assets/javascripts/templates/shop_variant.html.haml
index 2c9eb932d5..1d06ffbb39 100644
--- a/app/assets/javascripts/templates/shop_variant.html.haml
+++ b/app/assets/javascripts/templates/shop_variant.html.haml
@@ -2,16 +2,14 @@
.small-12.medium-4.large-4.columns.variant-name
.table-cell
.inline {{ variant.name_to_display }}
- .bulk-buy.inline{"bo-if" => "variant.product.group_buy"}
+ .bulk-buy.inline{"ng-if" => "::variant.product.group_buy"}
%i.ofn-i_056-bulk><
%em><
\ {{'bulk' | t}}
-
%ng-include{src: "'partials/shop_variant_no_group_buy.html'"}
%ng-include{src: "'partials/shop_variant_with_group_buy.html'"}
-
.small-3.medium-1.large-1.columns.variant-unit
.table-cell
%em {{ variant.unit_to_display }}
diff --git a/app/assets/javascripts/templates/signup.html.haml b/app/assets/javascripts/templates/signup.html.haml
index 2e780aabb5..23eab8e38c 100644
--- a/app/assets/javascripts/templates/signup.html.haml
+++ b/app/assets/javascripts/templates/signup.html.haml
@@ -1,8 +1,5 @@
-%tab#sign-up-content{"ng-controller" => "SignupCtrl",
- heading: "{{'label_signup' | t}}",
- active: "active(path)",
- select: "select(path)"}
- %form{"ng-submit" => "submit()"}
+%tab#sign-up-content{ heading: "{{'label_signup' | t}}", active: 'tabs.signup.active', select: "select(path)"}
+ %form{ ng: { controller: "SignupCtrl", submit: "submit()" } }
.row
.large-12.columns
%label{for: "email"} {{'signup_email' | t}}
diff --git a/app/assets/stylesheets/admin/all.css b/app/assets/stylesheets/admin/all.css
index e0d668b95b..e913e9e710 100644
--- a/app/assets/stylesheets/admin/all.css
+++ b/app/assets/stylesheets/admin/all.css
@@ -9,7 +9,7 @@
*= require admin/spree_promo
*= require shared/jquery-ui-timepicker-addon
- *= require shared/textAngular.min
+ *= require shared/textAngular
*= require shared/ng-tags-input.min
*= require_self
diff --git a/app/assets/stylesheets/admin/components/jquery_dialog.scss b/app/assets/stylesheets/admin/components/jquery_dialog.scss
index 2e36db6e33..f3fe08da60 100644
--- a/app/assets/stylesheets/admin/components/jquery_dialog.scss
+++ b/app/assets/stylesheets/admin/components/jquery_dialog.scss
@@ -4,8 +4,8 @@ dark: #545454
light: #ccc
*/
.ui-dialog {
- border: 2px solid #4a4a4a;
- border-radius:3px;
+ border: 0px solid #4a4a4a;
+ border-radius:5px;
padding:0px;
-moz-box-shadow: 3px 3px 4px #797979;
-webkit-box-shadow: 3px 3px 4px #797979;
@@ -18,10 +18,6 @@ light: #ccc
filter: progid:DXImageTransform.Microsoft.Shadow(Strength=4, Direction=135, Color='#545454');
}
-.ui-dialog .ui-dialog-titlebar{
- border-radius: 3px;
-}
-
.ui-dialog .ui-state-hover {
&.ui-dialog-titlebar-close{
@@ -34,17 +30,17 @@ light: #ccc
background-image: none;
background-color: #ffffff;
border:0px;
- border-radius: 3px;
+ border-radius: 8px;
padding: 0px 5px 0px 5px;
}
.ui-dialog .ui-widget-content{
border: none;
- border-radius: 3px;
+ border-radius: 8px;
padding: 0px 50px 30px 50px;
}
.ui-dialog .ui-corner-all{
- border-radius:0px;
+ border-radius: 8px;
}
.ui-dialog {
.ui-state-hover, .ui-state-focus{
@@ -83,6 +79,6 @@ light: #ccc
}
.ui-widget-overlay {
- background: #e9e9e9;
- opacity: 0.6;
+ background: #000000;
+ opacity: 0.5;
}
diff --git a/app/assets/stylesheets/admin/components/save_bar.sass b/app/assets/stylesheets/admin/components/save_bar.sass
index 87dcce82f9..f453c5ddfc 100644
--- a/app/assets/stylesheets/admin/components/save_bar.sass
+++ b/app/assets/stylesheets/admin/components/save_bar.sass
@@ -1,6 +1,7 @@
#save-bar
position: fixed
width: 100%
+ z-index: 100
bottom: 0px
left: 0
padding: 8px 8px
diff --git a/app/assets/stylesheets/admin/dialog.css.scss b/app/assets/stylesheets/admin/dialog.css.scss
new file mode 100644
index 0000000000..832270d07d
--- /dev/null
+++ b/app/assets/stylesheets/admin/dialog.css.scss
@@ -0,0 +1,5 @@
+.ui-dialog {
+ p {
+ line-height: 2;
+ }
+}
diff --git a/app/assets/stylesheets/admin/index_panels.css.scss b/app/assets/stylesheets/admin/index_panels.css.scss
index a910e41d14..b622212446 100644
--- a/app/assets/stylesheets/admin/index_panels.css.scss
+++ b/app/assets/stylesheets/admin/index_panels.css.scss
@@ -1,4 +1,4 @@
-tr.panel-toggle-row {
+tbody.panel-ctrl {
td.panel-toggle{
-webkit-touch-callout: none;
-webkit-user-select: none;
@@ -65,64 +65,60 @@ tr.panel-toggle-row {
}
&.expanded{
- td {
- border-bottom: 2px solid #444444;
+ >tr:not(.panel-row) {
+ >td {
+ border-bottom: 1px solid #6788a2;
- &.selected {
- background-color: #ffffff;
- border-left: 2px solid #444444;
- border-right: 2px solid #444444;
- border-top: 2px solid #444444;
- border-bottom: none;
-
- &:hover {
+ &.selected {
background-color: #ffffff;
- }
+ border-left: 1px solid #6788a2;
+ border-right: 1px solid #6788a2;
+ border-top: 1px solid #6788a2;
+ border-bottom: none;
- * {
- color: #1b3c56;
- }
+ &:hover {
+ background-color: #ffffff;
+ }
- i.icon-status::before {
- opacity: 1.0;
- }
+ * {
+ color: #1b3c56;
+ }
- i.icon-chevron::before {
- content: "\f077";
- }
- }
- }
- }
-}
-
-tr.panel-row {
- display: none;
-
- &:hover {
- td {
- background-color: #ffffff;
- }
- }
-
- >td {
- border-color: #444444;
- padding: 0;
- .panel {
- border-left: 1px solid #444444;
- border-right: 1px solid #444444;
- border-bottom: 1px solid #444444;
-
- .row{
- margin: 0px -4px;
-
- padding: 20px 0px;
-
- .column.alpha, .columns.alpha {
- padding-left: 20px;
- }
-
- .column.omega, .columns.omega {
- padding-right: 20px;
+ i.icon-status::before {
+ opacity: 1.0;
+ }
+
+ i.icon-chevron::before {
+ content: "\f077";
+ }
+ }
+ }
+ }
+ }
+
+ tr.panel-row {
+ &:hover {
+ td {
+ background-color: #ffffff;
+ }
+ }
+
+ >td {
+ border-color: #6788a2;
+ padding: 0;
+ .panel {
+ .row{
+ margin: 0px -4px;
+
+ padding: 20px 0px;
+
+ .column.alpha, .columns.alpha {
+ padding-left: 20px;
+ }
+
+ .column.omega, .columns.omega {
+ padding-right: 20px;
+ }
}
}
}
diff --git a/app/assets/stylesheets/admin/openfoodnetwork.css.scss b/app/assets/stylesheets/admin/openfoodnetwork.css.scss
index 1fc95face8..1532ea8520 100644
--- a/app/assets/stylesheets/admin/openfoodnetwork.css.scss
+++ b/app/assets/stylesheets/admin/openfoodnetwork.css.scss
@@ -28,6 +28,9 @@ text-angular .ta-editor {
left: 275px;
}
+span.error, div.error {
+ color: #DA5354;
+}
/* Fix conflict between Spree and elRTE's styles */
.el-rte .toolbar {
@@ -40,6 +43,11 @@ input.red {
margin-right: 5px;
}
+input.orange {
+ background-color: #FF9848;
+ margin-right: 5px;
+}
+
input.search {
margin-bottom: 1em;
}
@@ -71,10 +79,6 @@ form.order_cycle {
width: 20px;
}
- tr.supplier td {
- border-bottom: 2px solid #C3D9FF;
- }
-
.exchange-select-all-variants {
clear: both;
margin: 5px;
diff --git a/app/assets/stylesheets/admin/orders.css.scss b/app/assets/stylesheets/admin/orders.css.scss
index 761ccbc014..23cb9d8fff 100644
--- a/app/assets/stylesheets/admin/orders.css.scss
+++ b/app/assets/stylesheets/admin/orders.css.scss
@@ -13,10 +13,6 @@ input.show-dirty {
}
}
-span.error {
- color: #DA5354;
-}
-
input, div {
&.update-error {
border: solid 1px #DA5354;
diff --git a/app/assets/stylesheets/admin/tag_rules.css.scss b/app/assets/stylesheets/admin/tag_rules.css.scss
index 028ad6ce1c..05decd6027 100644
--- a/app/assets/stylesheets/admin/tag_rules.css.scss
+++ b/app/assets/stylesheets/admin/tag_rules.css.scss
@@ -1,3 +1,11 @@
+tags-input {
+ &.limit-reached {
+ input, span.input {
+ display: none;
+ }
+ }
+}
+
.no_tags {
margin-bottom: 40px;
color: #aeaeae;
@@ -6,6 +14,13 @@
}
.customer_tag {
+ .header {
+ cursor: move;
+ }
+}
+
+.customer_tag, .default_rules {
+ background-color: #ffffff;
border: 1px solid #cee1f4;
margin-bottom: 40px;
@@ -20,6 +35,9 @@
tr {
td {
border: none;
+ h5 {
+ display: inline-block;
+ }
}
}
}
@@ -33,22 +51,30 @@
font-weight: bold;
}
- table {
- padding: 0px;
- margin: 0px 0px 10px 0px;
-
- tr.tag_rule {
- border: none;
+ .tag_rule {
+ table {
padding: 0px;
- margin: 0px;
+ margin: 0px 0px 10px 0px;
- td {
+ &:hover {
+ td {
+ background-color: #ebf3fb;
+ }
+ }
+
+ tr {
border: none;
- padding: 4px 10px 10px 10px;
+ padding: 0px;
margin: 0px;
- input {
- width: auto;
+ td {
+ border: none;
+ padding: 2px 10px 2px 10px;
+ margin: 0px;
+
+ input {
+ width: auto;
+ }
}
}
}
diff --git a/app/assets/stylesheets/darkswarm/shop.css.sass b/app/assets/stylesheets/darkswarm/shop.css.sass
index eab074369e..5cfcef46b8 100644
--- a/app/assets/stylesheets/darkswarm/shop.css.sass
+++ b/app/assets/stylesheets/darkswarm/shop.css.sass
@@ -84,16 +84,25 @@
padding-right: 0rem
font-size: 0.8rem
- .shopfront_message, .shopfront_closed_message, .shopfront_hidden_message
+ .alert-box.shopfront-message
+ border: 2px solid $clr-turquoise
+ border-radius: 5px
+ background-color: $clr-turquoise-light
+ color: $clr-turquoise
+ a
+ color: #0096ad
+ &:hover, &:focus, &:active
+ text-decoration: none
+ color: #4aadbd
+
+
+ .shopfront_closed_message, .shopfront_hidden_message
padding: 15px
border-radius: 5px
- .shopfront_message, .shopfront_closed_message
+ .shopfront_closed_message
border: 2px solid #eb4c46
- .shopfront_message
- margin-top: 2em
-
.shopfront_closed_message
margin: 2em 0em
diff --git a/app/assets/stylesheets/shared/ng-tags-input.min.css b/app/assets/stylesheets/shared/ng-tags-input.min.css
index ee4a4a98d5..22dcf47d66 100755
--- a/app/assets/stylesheets/shared/ng-tags-input.min.css
+++ b/app/assets/stylesheets/shared/ng-tags-input.min.css
@@ -1 +1 @@
-tags-input{display:block}tags-input *,tags-input :after,tags-input :before{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}tags-input .host{position:relative;margin-top:5px;margin-bottom:5px;height:100%}tags-input .host:active{outline:0}tags-input .tags{-moz-appearance:textfield;-webkit-appearance:textfield;padding:1px;overflow:hidden;word-wrap:break-word;cursor:text;background-color:#fff;border:1px solid #a9a9a9;box-shadow:1px 1px 1px 0 #d3d3d3 inset;height:100%}tags-input .tags.focused{outline:0;-webkit-box-shadow:0 0 3px 1px rgba(5,139,242,.6);-moz-box-shadow:0 0 3px 1px rgba(5,139,242,.6);box-shadow:0 0 3px 1px rgba(5,139,242,.6)}tags-input .tags .tag-list{margin:0;padding:0;list-style-type:none}tags-input .tags .tag-item{margin:2px;padding:0 5px;display:inline-block;float:left;font:14px "Helvetica Neue",Helvetica,Arial,sans-serif;height:26px;line-height:25px;border:1px solid #acacac;border-radius:3px;background:-webkit-linear-gradient(top,#f0f9ff 0,#cbebff 47%,#a1dbff 100%);background:linear-gradient(to bottom,#f0f9ff 0,#cbebff 47%,#a1dbff 100%)}tags-input .tags .tag-item.selected{background:-webkit-linear-gradient(top,#febbbb 0,#fe9090 45%,#ff5c5c 100%);background:linear-gradient(to bottom,#febbbb 0,#fe9090 45%,#ff5c5c 100%)}tags-input .tags .tag-item .remove-button{margin:0 0 0 5px;padding:0;border:none;background:0 0;cursor:pointer;vertical-align:middle;font:700 16px Arial,sans-serif;color:#585858}tags-input .tags .tag-item .remove-button:active{color:red}tags-input .tags .input{border:0;outline:0;margin:2px;padding:0;padding-left:5px;float:left;height:26px;font:14px "Helvetica Neue",Helvetica,Arial,sans-serif}tags-input .tags .input.invalid-tag{color:red}tags-input .tags .input::-ms-clear{display:none}tags-input.ng-invalid .tags{-webkit-box-shadow:0 0 3px 1px rgba(255,0,0,.6);-moz-box-shadow:0 0 3px 1px rgba(255,0,0,.6);box-shadow:0 0 3px 1px rgba(255,0,0,.6)}tags-input[disabled] .host:focus{outline:0}tags-input[disabled] .tags{background-color:#eee;cursor:default}tags-input[disabled] .tags .tag-item{opacity:.65;background:-webkit-linear-gradient(top,#f0f9ff 0,rgba(203,235,255,.75)47%,rgba(161,219,255,.62)100%);background:linear-gradient(to bottom,#f0f9ff 0,rgba(203,235,255,.75)47%,rgba(161,219,255,.62)100%)}tags-input[disabled] .tags .tag-item .remove-button{cursor:default}tags-input[disabled] .tags .tag-item .remove-button:active{color:#585858}tags-input[disabled] .tags .input{background-color:#eee;cursor:default}tags-input .autocomplete{margin-top:5px;position:absolute;padding:5px 0;z-index:999;width:100%;background-color:#fff;border:1px solid rgba(0,0,0,.2);-webkit-box-shadow:0 5px 10px rgba(0,0,0,.2);-moz-box-shadow:0 5px 10px rgba(0,0,0,.2);box-shadow:0 5px 10px rgba(0,0,0,.2)}tags-input .autocomplete .suggestion-list{margin:0;padding:0;list-style-type:none;max-height:280px;overflow-y:auto;position:relative}tags-input .autocomplete .suggestion-item{padding:5px 10px;cursor:pointer;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font:16px "Helvetica Neue",Helvetica,Arial,sans-serif;color:#000;background-color:#fff}tags-input .autocomplete .suggestion-item.selected,tags-input .autocomplete .suggestion-item.selected em{color:#fff;background-color:#0097cf}tags-input .autocomplete .suggestion-item em{font:normal bold 16px "Helvetica Neue",Helvetica,Arial,sans-serif;color:#000;background-color:#fff}
\ No newline at end of file
+tags-input{display:block}tags-input *,tags-input :after,tags-input :before{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}tags-input .host{position:relative;margin-top:5px;margin-bottom:5px;height:100%}tags-input .host:active{outline:0}tags-input .tags{-moz-appearance:textfield;-webkit-appearance:textfield;padding:1px;overflow:hidden;word-wrap:break-word;cursor:text;background-color:#fff;border:1px solid #a9a9a9;box-shadow:1px 1px 1px 0 #d3d3d3 inset;height:100%}tags-input .tags.focused{outline:0;-webkit-box-shadow:0 0 3px 1px rgba(5,139,242,.6);-moz-box-shadow:0 0 3px 1px rgba(5,139,242,.6);box-shadow:0 0 3px 1px rgba(5,139,242,.6)}tags-input .tags .tag-list{margin:0;padding:0;list-style-type:none}tags-input .tags .tag-item{margin:2px;padding:0 5px;display:inline-block;float:left;font:14px "Helvetica Neue",Helvetica,Arial,sans-serif;height:26px;line-height:25px;border:1px solid #acacac;border-radius:3px;background:-webkit-linear-gradient(top,#f0f9ff 0,#cbebff 47%,#a1dbff 100%);background:linear-gradient(to bottom,#f0f9ff 0,#cbebff 47%,#a1dbff 100%)}tags-input .tags .tag-item.selected{background:-webkit-linear-gradient(top,#febbbb 0,#fe9090 45%,#ff5c5c 100%);background:linear-gradient(to bottom,#febbbb 0,#fe9090 45%,#ff5c5c 100%)}tags-input .tags .tag-item .remove-button{margin:0 0 0 5px;padding:0;border:none;background:0 0;cursor:pointer;vertical-align:middle;font:700 16px Arial,sans-serif;color:#585858}tags-input .tags .input.invalid-tag,tags-input .tags .tag-item .remove-button:active{color:red}tags-input .tags .input{border:0;outline:0;margin:2px;padding:0 0 0 5px;float:left;height:26px;font:14px "Helvetica Neue",Helvetica,Arial,sans-serif}tags-input .tags .input::-ms-clear{display:none}tags-input.ng-invalid .tags{-webkit-box-shadow:0 0 3px 1px rgba(255,0,0,.6);-moz-box-shadow:0 0 3px 1px rgba(255,0,0,.6);box-shadow:0 0 3px 1px rgba(255,0,0,.6)}tags-input[disabled] .host:focus{outline:0}tags-input[disabled] .tags{background-color:#eee;cursor:default}tags-input[disabled] .tags .tag-item{opacity:.65;background:-webkit-linear-gradient(top,#f0f9ff 0,rgba(203,235,255,.75)47%,rgba(161,219,255,.62)100%);background:linear-gradient(to bottom,#f0f9ff 0,rgba(203,235,255,.75)47%,rgba(161,219,255,.62)100%)}tags-input[disabled] .tags .tag-item .remove-button{cursor:default}tags-input[disabled] .tags .tag-item .remove-button:active{color:#585858}tags-input[disabled] .tags .input{background-color:#eee;cursor:default}tags-input .autocomplete{margin-top:5px;position:absolute;padding:5px 0;z-index:999;width:100%;background-color:#fff;border:1px solid rgba(0,0,0,.2);-webkit-box-shadow:0 5px 10px rgba(0,0,0,.2);-moz-box-shadow:0 5px 10px rgba(0,0,0,.2);box-shadow:0 5px 10px rgba(0,0,0,.2)}tags-input .autocomplete .suggestion-list{margin:0;padding:0;list-style-type:none;max-height:280px;overflow-y:auto;position:relative}tags-input .autocomplete .suggestion-item{padding:5px 10px;cursor:pointer;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font:16px "Helvetica Neue",Helvetica,Arial,sans-serif;color:#000;background-color:#fff}tags-input .autocomplete .suggestion-item.selected,tags-input .autocomplete .suggestion-item.selected em{color:#fff;background-color:#0097cf}tags-input .autocomplete .suggestion-item em{font:normal bold 16px "Helvetica Neue",Helvetica,Arial,sans-serif;color:#000;background-color:#fff}
diff --git a/app/assets/stylesheets/shared/textAngular.css b/app/assets/stylesheets/shared/textAngular.css
new file mode 100644
index 0000000000..a2f76234dc
--- /dev/null
+++ b/app/assets/stylesheets/shared/textAngular.css
@@ -0,0 +1,193 @@
+.ta-hidden-input {
+ width: 1px;
+ height: 1px;
+ border: none;
+ margin: 0;
+ padding: 0;
+ position: absolute;
+ top: -10000px;
+ left: -10000px;
+ opacity: 0;
+ overflow: hidden;
+}
+
+/* add generic styling for the editor */
+.ta-root.focussed > .ta-scroll-window.form-control {
+ border-color: #66afe9;
+ outline: 0;
+ -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(102, 175, 233, 0.6);
+ -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(102, 175, 233, 0.6);
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(102, 175, 233, 0.6);
+}
+
+.ta-editor.ta-html, .ta-scroll-window.form-control {
+ min-height: 300px;
+ height: auto;
+ overflow: auto;
+ font-family: inherit;
+ font-size: 100%;
+}
+
+.ta-scroll-window.form-control {
+ position: relative;
+ padding: 0;
+}
+
+.ta-scroll-window > .ta-bind {
+ height: auto;
+ min-height: 300px;
+ padding: 6px 12px;
+}
+
+.ta-editor:focus {
+ user-select: text;
+}
+
+/* add the styling for the awesomness of the resizer */
+.ta-resizer-handle-overlay {
+ z-index: 100;
+ position: absolute;
+ display: none;
+}
+
+.ta-resizer-handle-overlay > .ta-resizer-handle-info {
+ position: absolute;
+ bottom: 16px;
+ right: 16px;
+ border: 1px solid black;
+ background-color: #FFF;
+ padding: 0 4px;
+ opacity: 0.7;
+}
+
+.ta-resizer-handle-overlay > .ta-resizer-handle-background {
+ position: absolute;
+ bottom: 5px;
+ right: 5px;
+ left: 5px;
+ top: 5px;
+ border: 1px solid black;
+ background-color: rgba(0, 0, 0, 0.2);
+}
+
+.ta-resizer-handle-overlay > .ta-resizer-handle-corner {
+ width: 10px;
+ height: 10px;
+ position: absolute;
+}
+
+.ta-resizer-handle-overlay > .ta-resizer-handle-corner-tl{
+ top: 0;
+ left: 0;
+ border-left: 1px solid black;
+ border-top: 1px solid black;
+}
+
+.ta-resizer-handle-overlay > .ta-resizer-handle-corner-tr{
+ top: 0;
+ right: 0;
+ border-right: 1px solid black;
+ border-top: 1px solid black;
+}
+
+.ta-resizer-handle-overlay > .ta-resizer-handle-corner-bl{
+ bottom: 0;
+ left: 0;
+ border-left: 1px solid black;
+ border-bottom: 1px solid black;
+}
+
+.ta-resizer-handle-overlay > .ta-resizer-handle-corner-br{
+ bottom: 0;
+ right: 0;
+ border: 1px solid black;
+ cursor: se-resize;
+ background-color: white;
+}
+
+/* copy the popover code from bootstrap so this will work even without it */
+.popover {
+ position: absolute;
+ top: 0;
+ left: 0;
+ z-index: 1060;
+ display: none;
+ max-width: 276px;
+ padding: 1px;
+ font-size: 14px;
+ font-weight: normal;
+ line-height: 1.42857143;
+ text-align: left;
+ white-space: normal;
+ background-color: #fff;
+ -webkit-background-clip: padding-box;
+ background-clip: padding-box;
+ border: 1px solid #ccc;
+ border: 1px solid rgba(0, 0, 0, .2);
+ border-radius: 6px;
+ -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, .2);
+ box-shadow: 0 5px 10px rgba(0, 0, 0, .2);
+}
+.popover.top {
+ margin-top: -10px;
+}
+.popover.bottom {
+ margin-top: 10px;
+}
+.popover-title {
+ padding: 8px 14px;
+ margin: 0;
+ font-size: 14px;
+ background-color: #f7f7f7;
+ border-bottom: 1px solid #ebebeb;
+ border-radius: 5px 5px 0 0;
+}
+.popover-content {
+ padding: 9px 14px;
+}
+.popover > .arrow,
+.popover > .arrow:after {
+ position: absolute;
+ display: block;
+ width: 0;
+ height: 0;
+ border-color: transparent;
+ border-style: solid;
+}
+.popover > .arrow {
+ border-width: 11px;
+}
+.popover > .arrow:after {
+ content: "";
+ border-width: 10px;
+}
+.popover.top > .arrow {
+ bottom: -11px;
+ left: 50%;
+ margin-left: -11px;
+ border-top-color: #999;
+ border-top-color: rgba(0, 0, 0, .25);
+ border-bottom-width: 0;
+}
+.popover.top > .arrow:after {
+ bottom: 1px;
+ margin-left: -10px;
+ content: " ";
+ border-top-color: #fff;
+ border-bottom-width: 0;
+}
+.popover.bottom > .arrow {
+ top: -11px;
+ left: 50%;
+ margin-left: -11px;
+ border-top-width: 0;
+ border-bottom-color: #999;
+ border-bottom-color: rgba(0, 0, 0, .25);
+}
+.popover.bottom > .arrow:after {
+ top: 1px;
+ margin-left: -10px;
+ content: " ";
+ border-top-width: 0;
+ border-bottom-color: #fff;
+}
diff --git a/app/assets/stylesheets/shared/textAngular.min.css b/app/assets/stylesheets/shared/textAngular.min.css
deleted file mode 100644
index 6f132953bb..0000000000
--- a/app/assets/stylesheets/shared/textAngular.min.css
+++ /dev/null
@@ -1 +0,0 @@
-.ta-scroll-window.form-control{height:auto;min-height:300px;overflow:auto;font-family:inherit;font-size:100%;position:relative;padding:0}.ta-root.focussed .ta-scroll-window.form-control{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6)}.ta-editor.ta-html{min-height:300px;height:auto;overflow:auto;font-family:inherit;font-size:100%}.ta-scroll-window>.ta-bind{height:auto;min-height:300px;padding:6px 12px}.ta-root .ta-resizer-handle-overlay{z-index:100;position:absolute;display:none}.ta-root .ta-resizer-handle-overlay>.ta-resizer-handle-info{position:absolute;bottom:16px;right:16px;border:1px solid #000;background-color:#FFF;padding:0 4px;opacity:.7}.ta-root .ta-resizer-handle-overlay>.ta-resizer-handle-background{position:absolute;bottom:5px;right:5px;left:5px;top:5px;border:1px solid #000;background-color:rgba(0,0,0,.2)}.ta-root .ta-resizer-handle-overlay>.ta-resizer-handle-corner{width:10px;height:10px;position:absolute}.ta-root .ta-resizer-handle-overlay>.ta-resizer-handle-corner-tl{top:0;left:0;border-left:1px solid #000;border-top:1px solid #000}.ta-root .ta-resizer-handle-overlay>.ta-resizer-handle-corner-tr{top:0;right:0;border-right:1px solid #000;border-top:1px solid #000}.ta-root .ta-resizer-handle-overlay>.ta-resizer-handle-corner-bl{bottom:0;left:0;border-left:1px solid #000;border-bottom:1px solid #000}.ta-root .ta-resizer-handle-overlay>.ta-resizer-handle-corner-br{bottom:0;right:0;border:1px solid #000;cursor:se-resize;background-color:#fff}
\ No newline at end of file
diff --git a/app/controllers/admin/column_preferences_controller.rb b/app/controllers/admin/column_preferences_controller.rb
new file mode 100644
index 0000000000..0a9300d71c
--- /dev/null
+++ b/app/controllers/admin/column_preferences_controller.rb
@@ -0,0 +1,38 @@
+module Admin
+ class ColumnPreferencesController < ResourceController
+ before_filter :load_collection, only: [:bulk_update]
+
+ respond_to :json
+
+ def bulk_update
+ @cp_set.collection.each { |cp| authorize! :bulk_update, cp }
+
+ if @cp_set.save
+ # Return saved VOs with IDs
+ render json: @cp_set.collection, each_serializer: Api::Admin::ColumnPreferenceSerializer
+ else
+ if @cp_set.errors.present?
+ render json: { errors: @cp_set.errors }, status: 400
+ else
+ render nothing: true, status: 500
+ end
+ end
+ end
+
+ private
+
+ def load_collection
+ collection_hash = Hash[params[:column_preferences].each_with_index.map { |cp, i| [i, cp] }]
+ collection_hash.reject!{ |i, cp| cp[:action_name] != params[:action_name] }
+ @cp_set = ColumnPreferenceSet.new @column_preferences, collection_attributes: collection_hash
+ end
+
+ def collection
+ ColumnPreference.where(user_id: spree_current_user, action_name: params[:action_name])
+ end
+
+ def collection_actions
+ [:bulk_update]
+ end
+ end
+end
diff --git a/app/controllers/admin/customers_controller.rb b/app/controllers/admin/customers_controller.rb
index 24c26661e5..fe975aa038 100644
--- a/app/controllers/admin/customers_controller.rb
+++ b/app/controllers/admin/customers_controller.rb
@@ -7,11 +7,8 @@ module Admin
respond_to do |format|
format.html
format.json do
- serialised = ActiveModel::ArraySerializer.new(
- @collection,
- each_serializer: Api::Admin::CustomerSerializer,
- spree_current_user: spree_current_user)
- render json: serialised.to_json
+ tag_rule_mapping = TagRule.mapping_for(Enterprise.where(id: params[:enterprise_id]))
+ render_as_json @collection, tag_rule_mapping: tag_rule_mapping
end
end
end
@@ -19,8 +16,12 @@ module Admin
def create
@customer = Customer.new(params[:customer])
if user_can_create_customer?
- @customer.save
- render json: Api::Admin::CustomerSerializer.new(@customer).to_json
+ if @customer.save
+ tag_rule_mapping = TagRule.mapping_for(Enterprise.where(id: @customer.enterprise))
+ render_as_json @customer, tag_rule_mapping: tag_rule_mapping
+ else
+ render json: { errors: @customer.errors.full_messages }, status: 400
+ end
else
redirect_to '/unauthorized'
end
diff --git a/app/controllers/admin/tag_rules_controller.rb b/app/controllers/admin/tag_rules_controller.rb
index 7d60cb4888..25f5b177d6 100644
--- a/app/controllers/admin/tag_rules_controller.rb
+++ b/app/controllers/admin/tag_rules_controller.rb
@@ -6,5 +6,38 @@ module Admin
respond_override destroy: { json: {
success: lambda { render nothing: true, :status => 204 }
} }
+
+ def map_by_tag
+ respond_to do |format|
+ format.json do
+ serialiser = ActiveModel::ArraySerializer.new(collection)
+ render json: serialiser.to_json
+ end
+ end
+ end
+
+
+ private
+
+ def collection_actions
+ [:index, :map_by_tag]
+ end
+
+ def collection
+ case action
+ when :map_by_tag
+ TagRule.mapping_for(enterprises).values
+ else
+ TagRule.for(enterprises.pluck(&:id))
+ end
+ end
+
+ def enterprises
+ if params[:enterprise_id]
+ Enterprise.managed_by(spree_current_user).where(id: params[:enterprise_id])
+ else
+ Enterprise.managed_by(spree_current_user)
+ end
+ end
end
end
diff --git a/app/controllers/admin/tags_controller.rb b/app/controllers/admin/tags_controller.rb
deleted file mode 100644
index 3ab5685ffe..0000000000
--- a/app/controllers/admin/tags_controller.rb
+++ /dev/null
@@ -1,28 +0,0 @@
-module Admin
- class TagsController < Spree::Admin::BaseController
- respond_to :json
-
- def index
- respond_to do |format|
- format.json do
- serialiser = ActiveModel::ArraySerializer.new(tags_of_enterprise)
- render json: serialiser.to_json
- end
- end
- end
-
- private
-
- def enterprise
- Enterprise.managed_by(spree_current_user).find_by_id(params[:enterprise_id])
- end
-
- def tags_of_enterprise
- return [] unless enterprise
- tag_rule_map = enterprise.rules_per_tag
- tag_rule_map.keys.map do |tag|
- { text: tag, rules: tag_rule_map[tag] }
- end
- end
- end
-end
diff --git a/app/controllers/base_controller.rb b/app/controllers/base_controller.rb
index 9d2c3cff85..58bf679dc3 100644
--- a/app/controllers/base_controller.rb
+++ b/app/controllers/base_controller.rb
@@ -1,3 +1,5 @@
+require 'open_food_network/tag_rule_applicator'
+
class BaseController < ApplicationController
include Spree::Core::ControllerHelpers
include Spree::Core::ControllerHelpers::RespondWith
@@ -19,6 +21,9 @@ class BaseController < ApplicationController
@order_cycles = OrderCycle.with_distributor(@distributor).active
.order(@distributor.preferred_shopfront_order_cycle_order)
+ applicator = OpenFoodNetwork::TagRuleApplicator.new(@distributor, "FilterOrderCycles", current_customer.andand.tag_list)
+ applicator.filter!(@order_cycles)
+
# And default to the only order cycle if there's only the one
if @order_cycles.count == 1
current_order(true).set_order_cycle! @order_cycles.first
diff --git a/app/controllers/enterprises_controller.rb b/app/controllers/enterprises_controller.rb
index fe2eda8bfd..7f627ec73a 100644
--- a/app/controllers/enterprises_controller.rb
+++ b/app/controllers/enterprises_controller.rb
@@ -40,15 +40,30 @@ class EnterprisesController < BaseController
distributor = Enterprise.is_distributor.find_by_permalink(params[:id]) || Enterprise.is_distributor.find(params[:id])
order = current_order(true)
+ reset_distributor(order, distributor)
+
+ reset_user_and_customer(order) if try_spree_current_user
+
+ reset_order_cycle(order, distributor)
+
+ order.save!
+ end
+
+ def reset_distributor(order, distributor)
if order.distributor && order.distributor != distributor
order.empty!
order.set_order_cycle! nil
end
-
order.distributor = distributor
+ end
+ def reset_user_and_customer(order)
+ order.associate_user!(spree_current_user) if order.user.blank? || order.email.blank?
+ order.send(:associate_customer) if order.customer.nil? # Only associates existing customers
+ end
+
+ def reset_order_cycle(order, distributor)
order_cycle_options = OrderCycle.active.with_distributor(distributor)
order.order_cycle = order_cycle_options.first if order_cycle_options.count == 1
- order.save!
end
end
diff --git a/app/controllers/shop_controller.rb b/app/controllers/shop_controller.rb
index 55b6bcc02b..091a501ca5 100644
--- a/app/controllers/shop_controller.rb
+++ b/app/controllers/shop_controller.rb
@@ -11,7 +11,11 @@ class ShopController < BaseController
def products
begin
- products_json = OpenFoodNetwork::CachedProductsRenderer.new(current_distributor, current_order_cycle).products_json
+ renderer = OpenFoodNetwork::CachedProductsRenderer.new(current_distributor, current_order_cycle)
+
+ # If we add any more filtering logic, we should probably
+ # move it all to a lib class like 'CachedProductsFilterer'
+ products_json = filter(renderer.products_json)
render json: products_json
@@ -33,4 +37,24 @@ class ShopController < BaseController
end
end
+ private
+
+ def filtered_json(products_json)
+ if applicator.send(:rules).any?
+ filter(products_json)
+ else
+ products_json
+ end
+ end
+
+ def filter(products_json)
+ products_hash = JSON.parse(products_json)
+ applicator.filter!(products_hash)
+ JSON.unparse(products_hash)
+ end
+
+ def applicator
+ return @applicator unless @applicator.nil?
+ @applicator = OpenFoodNetwork::TagRuleApplicator.new(current_distributor, "FilterProducts", current_customer.andand.tag_list)
+ end
end
diff --git a/app/controllers/spree/admin/payment_methods_controller_decorator.rb b/app/controllers/spree/admin/payment_methods_controller_decorator.rb
index 963b61fa81..fc15324d0b 100644
--- a/app/controllers/spree/admin/payment_methods_controller_decorator.rb
+++ b/app/controllers/spree/admin/payment_methods_controller_decorator.rb
@@ -57,6 +57,7 @@ module Spree
else
@providers = Gateway.providers.reject{ |p| p.name.include? "Bogus" }.sort{|p1, p2| p1.name <=> p2.name }
end
+ @calculators = PaymentMethod.calculators.sort_by(&:name)
end
def load_hubs
diff --git a/app/helpers/admin/injection_helper.rb b/app/helpers/admin/injection_helper.rb
index 0c032eaa9e..67105b6b34 100644
--- a/app/helpers/admin/injection_helper.rb
+++ b/app/helpers/admin/injection_helper.rb
@@ -23,6 +23,10 @@ module Admin
admin_inject_json_ams_array "admin.paymentMethods", "paymentMethods", @payment_methods, Api::Admin::IdNameSerializer
end
+ def admin_inject_payment_method
+ admin_inject_json_ams "admin.paymentMethods", "paymentMethod", @payment_method, Api::Admin::PaymentMethodSerializer
+ end
+
def admin_inject_shipping_methods
admin_inject_json_ams_array "admin.shippingMethods", "shippingMethods", @shipping_methods, Api::Admin::IdNameSerializer
end
@@ -47,13 +51,19 @@ module Admin
admin_inject_json_ams_array opts[:module], "inventoryItems", @inventory_items, Api::Admin::InventoryItemSerializer
end
+ def admin_inject_column_preferences(opts={})
+ opts.reverse_merge!(module: 'ofn.admin', action: "#{controller_name}_#{action_name}")
+ column_preferences = ColumnPreference.for(spree_current_user, opts[:action])
+ admin_inject_json_ams_array opts[:module], "columns", column_preferences, Api::Admin::ColumnPreferenceSerializer
+ end
+
def admin_inject_enterprise_permissions
permissions =
{can_manage_shipping_methods: can?(:manage_shipping_methods, @enterprise),
can_manage_payment_methods: can?(:manage_payment_methods, @enterprise),
can_manage_enterprise_fees: can?(:manage_enterprise_fees, @enterprise)}
- render partial: "admin/json/injection_ams", locals: {ngModule: "admin.enterprises", name: "enterprisePermissions", json: permissions.to_json}
+ admin_inject_json "admin.enterprises", "enterprisePermissions", permissions
end
def admin_inject_hub_permissions
@@ -96,6 +106,11 @@ module Admin
render partial: "admin/json/injection_ams", locals: {ngModule: 'admin.indexUtils', name: 'SpreeApiKey', json: "'#{@spree_api_key.to_s}'"}
end
+ def admin_inject_json(ngModule, name, data)
+ json = data.to_json
+ render partial: "admin/json/injection_ams", locals: {ngModule: ngModule, name: name, json: json}
+ end
+
def admin_inject_json_ams(ngModule, name, data, serializer, opts = {})
json = serializer.new(data, scope: spree_current_user).to_json
render partial: "admin/json/injection_ams", locals: {ngModule: ngModule, name: name, json: json}
diff --git a/app/helpers/checkout_helper.rb b/app/helpers/checkout_helper.rb
index 080db9c0e8..66d98d7862 100644
--- a/app/helpers/checkout_helper.rb
+++ b/app/helpers/checkout_helper.rb
@@ -94,4 +94,13 @@ module CheckoutHelper
current_order.tokenized_permission.save!
session[:access_token] = token
end
+
+ def payment_method_price(method, order)
+ price = method.compute_amount(order)
+ if price == 0
+ t('checkout_method_free')
+ else
+ "{{ #{price} | localizeCurrency }}"
+ end
+ end
end
diff --git a/app/helpers/enterprises_helper.rb b/app/helpers/enterprises_helper.rb
index 93b7c96f95..cc2b966b01 100644
--- a/app/helpers/enterprises_helper.rb
+++ b/app/helpers/enterprises_helper.rb
@@ -3,14 +3,31 @@ module EnterprisesHelper
@current_distributor ||= current_order(false).andand.distributor
end
+ def current_customer
+ return nil unless spree_current_user && current_distributor
+ @current_customer ||= spree_current_user.customer_of(current_distributor)
+ end
+
def available_shipping_methods
+ return [] unless current_distributor.present?
shipping_methods = current_distributor.shipping_methods
- if current_distributor.present?
- current_distributor.apply_tag_rules_to(shipping_methods, customer: current_order.customer)
- end
+
+ applicator = OpenFoodNetwork::TagRuleApplicator.new(current_distributor, "FilterShippingMethods", current_customer.andand.tag_list)
+ applicator.filter!(shipping_methods)
+
shipping_methods.uniq
end
+ def available_payment_methods
+ return [] unless current_distributor.present?
+ payment_methods = current_distributor.payment_methods.available(:front_end).all
+
+ applicator = OpenFoodNetwork::TagRuleApplicator.new(current_distributor, "FilterPaymentMethods", current_customer.andand.tag_list)
+ applicator.filter!(payment_methods)
+
+ payment_methods
+ end
+
def managed_enterprises
Enterprise.managed_by(spree_current_user)
end
diff --git a/app/helpers/injection_helper.rb b/app/helpers/injection_helper.rb
index c6be5dc63e..50b9c93fca 100644
--- a/app/helpers/injection_helper.rb
+++ b/app/helpers/injection_helper.rb
@@ -23,8 +23,8 @@ module InjectionHelper
end
def inject_available_payment_methods
- inject_json_ams "paymentMethods", current_order.available_payment_methods,
- Api::PaymentMethodSerializer
+ inject_json_ams "paymentMethods", available_payment_methods,
+ Api::PaymentMethodSerializer, current_order: current_order
end
def inject_taxons
diff --git a/app/mailers/producer_mailer.rb b/app/mailers/producer_mailer.rb
index 30fad48f58..102ac60417 100644
--- a/app/mailers/producer_mailer.rb
+++ b/app/mailers/producer_mailer.rb
@@ -33,7 +33,7 @@ class ProducerMailer < Spree::BaseMailer
joins(:order => :order_cycle, :variant => :product).
where('order_cycles.id = ?', order_cycle).
merge(Spree::Product.in_supplier(producer)).
- merge(Spree::Order.complete)
+ merge(Spree::Order.by_state('complete'))
end
def total_from_line_items(line_items)
diff --git a/app/models/column_preference.rb b/app/models/column_preference.rb
new file mode 100644
index 0000000000..09ad0e55a4
--- /dev/null
+++ b/app/models/column_preference.rb
@@ -0,0 +1,46 @@
+require 'open_food_network/column_preference_defaults'
+
+class ColumnPreference < ActiveRecord::Base
+ extend OpenFoodNetwork::ColumnPreferenceDefaults
+
+ # These are the attributes used to identify a preference
+ attr_accessible :user_id, :action_name, :column_name
+
+ # These are attributes that need to be mass assignable
+ attr_accessible :name, :visible
+
+ # Non-persisted attributes that only have one
+ # setting (ie. the default) for a given column
+ attr_accessor :name
+
+ belongs_to :user, class_name: "Spree::User"
+
+ validates :action_name, presence: true, inclusion: { in: proc { known_actions } }
+ validates :column_name, presence: true, inclusion: { in: proc { |p| valid_columns_for(p.action_name) } }
+
+ def self.for(user, action_name)
+ stored_preferences = where(user_id: user.id, action_name: action_name)
+ default_preferences = send("#{action_name}_columns")
+ default_preferences.each_with_object([]) do |(column_name, default_attributes), preferences|
+ stored_preference = stored_preferences.find_by_column_name(column_name)
+ if stored_preference
+ stored_preference.assign_attributes(default_attributes.select{ |k,v| stored_preference[k].nil? })
+ preferences << stored_preference
+ else
+ attributes = default_attributes.merge(user_id: user.id, action_name: action_name, column_name: column_name)
+ preferences << ColumnPreference.new(attributes)
+ end
+ end
+ end
+
+ private
+
+ def self.valid_columns_for(action_name)
+ send("#{action_name}_columns").keys.map(&:to_s)
+ end
+
+ def self.known_actions
+ OpenFoodNetwork::ColumnPreferenceDefaults.private_instance_methods
+ .select{|m| m.to_s.end_with?("_columns")}.map{ |m| m.to_s.sub /_columns$/, ''}
+ end
+end
diff --git a/app/models/column_preference_set.rb b/app/models/column_preference_set.rb
new file mode 100644
index 0000000000..32b9b85a1b
--- /dev/null
+++ b/app/models/column_preference_set.rb
@@ -0,0 +1,5 @@
+class ColumnPreferenceSet < ModelSet
+ def initialize(collection, attributes={})
+ super(ColumnPreference, collection, attributes, nil, nil )
+ end
+end
diff --git a/app/models/enterprise.rb b/app/models/enterprise.rb
index b7a5e3189a..554529d7f2 100644
--- a/app/models/enterprise.rb
+++ b/app/models/enterprise.rb
@@ -344,27 +344,6 @@ class Enterprise < ActiveRecord::Base
abn.present?
end
- def apply_tag_rules_to(subject, context)
- tag_rules.each do |rule|
- rule.set_context(subject,context)
- rule.apply
- end
- end
-
- def rules_per_tag
- tag_rule_map = {}
- tag_rules.each do |rule|
- rule.preferred_customer_tags.split(",").each do |tag|
- if tag_rule_map[tag]
- tag_rule_map[tag] += 1
- else
- tag_rule_map[tag] = 1
- end
- end
- end
- tag_rule_map
- end
-
protected
def devise_mailer
diff --git a/app/models/exchange.rb b/app/models/exchange.rb
index ad290f6c26..3d0e6417e6 100644
--- a/app/models/exchange.rb
+++ b/app/models/exchange.rb
@@ -1,4 +1,6 @@
class Exchange < ActiveRecord::Base
+ acts_as_taggable
+
belongs_to :order_cycle
belongs_to :sender, :class_name => 'Enterprise'
belongs_to :receiver, :class_name => 'Enterprise'
@@ -58,6 +60,7 @@ class Exchange < ActiveRecord::Base
exchange.order_cycle = new_order_cycle
exchange.enterprise_fee_ids = self.enterprise_fee_ids
exchange.variant_ids = self.variant_ids
+ exchange.tag_ids = self.tag_ids
exchange.save!
exchange
end
diff --git a/app/models/spree/ability_decorator.rb b/app/models/spree/ability_decorator.rb
index e250d1341d..2936a47d41 100644
--- a/app/models/spree/ability_decorator.rb
+++ b/app/models/spree/ability_decorator.rb
@@ -72,7 +72,7 @@ class AbilityDecorator
can [:admin, :index, :read, :create, :edit, :update_positions, :destroy], ProducerProperty
- can [:admin, :destroy], TagRule do |tag_rule|
+ can [:admin, :map_by_tag, :destroy], TagRule do |tag_rule|
user.enterprises.include? tag_rule.enterprise
end
@@ -101,6 +101,10 @@ class AbilityDecorator
can [:print], Spree::Order do |order|
order.user == user
end
+
+ can [:admin, :bulk_update], ColumnPreference do |column_preference|
+ column_preference.user == user
+ end
end
def add_product_management_abilities(user)
@@ -218,7 +222,6 @@ class AbilityDecorator
can [:create], Customer
can [:admin, :index, :update, :destroy], Customer, enterprise_id: Enterprise.managed_by(user).pluck(:id)
- can [:admin, :index], :tag
end
diff --git a/app/models/spree/adjustment_decorator.rb b/app/models/spree/adjustment_decorator.rb
index 0d1bc941f6..c8529fc577 100644
--- a/app/models/spree/adjustment_decorator.rb
+++ b/app/models/spree/adjustment_decorator.rb
@@ -12,6 +12,7 @@ module Spree
scope :with_tax, where('spree_adjustments.included_tax > 0')
scope :without_tax, where('spree_adjustments.included_tax = 0')
+ scope :payment_fee, where(originator_type: 'Spree::PaymentMethod')
attr_accessible :included_tax
diff --git a/app/models/spree/gateway/pay_pal_express_decorator.rb b/app/models/spree/gateway/pay_pal_express_decorator.rb
new file mode 100644
index 0000000000..dc8239a107
--- /dev/null
+++ b/app/models/spree/gateway/pay_pal_express_decorator.rb
@@ -0,0 +1,7 @@
+module Spree
+ class Gateway::PayPalExpress < Gateway
+ # Something odd is happening with class inheritance here, this class (defined in spree_paypal_express gem)
+ # doesn't seem to pick up attr_accessible from the Gateway class, so we redefine the attrs we need here
+ attr_accessible :tag_list
+ end
+end
diff --git a/app/models/spree/gateway_decorator.rb b/app/models/spree/gateway_decorator.rb
index 3c6bb2638d..9125ef1d2d 100644
--- a/app/models/spree/gateway_decorator.rb
+++ b/app/models/spree/gateway_decorator.rb
@@ -1,4 +1,5 @@
Spree::Gateway.class_eval do
+ acts_as_taggable
# Due to class load order, when config.cache_classes is enabled (ie. staging and production
# environments), this association isn't inherited from PaymentMethod. As a result, creating
@@ -12,4 +13,6 @@ Spree::Gateway.class_eval do
# Default to live
preference :server, :string, :default => 'live'
preference :test_mode, :boolean, :default => false
+
+ attr_accessible :tag_list
end
diff --git a/app/models/spree/order_decorator.rb b/app/models/spree/order_decorator.rb
index a06a34bcd0..a4995274b3 100644
--- a/app/models/spree/order_decorator.rb
+++ b/app/models/spree/order_decorator.rb
@@ -1,6 +1,7 @@
require 'open_food_network/enterprise_fee_calculator'
require 'open_food_network/distribution_change_validator'
require 'open_food_network/feature_toggle'
+require 'open_food_network/tag_rule_applicator'
ActiveSupport::Notifications.subscribe('spree.order.contents_changed') do |name, start, finish, id, payload|
payload[:order].reload.update_distribution_charge!
@@ -178,10 +179,6 @@ Spree::Order.class_eval do
if order_cycle
OpenFoodNetwork::EnterpriseFeeCalculator.new.create_order_adjustments_for self
end
-
- if distributor.present? && customer.present?
- distributor.apply_tag_rules_to(self, customer: customer)
- end
end
end
@@ -206,11 +203,8 @@ Spree::Order.class_eval do
adjustments.eligible.where("originator_type = ? AND source_type != ?", 'EnterpriseFee', 'Spree::LineItem').sum(&:amount)
end
- # Show payment methods for this distributor
- def available_payment_methods
- @available_payment_methods ||= Spree::PaymentMethod.available(:front_end).select do |pm|
- (self.distributor && (pm.distributors.include? self.distributor))
- end
+ def payment_fee
+ adjustments.payment_fee.map(&:amount).sum
end
# Does this order have shipments that can be shipped?
@@ -225,10 +219,6 @@ Spree::Order.class_eval do
end
end
- def available_shipping_methods(display_on = nil)
- Spree::ShippingMethod.all_available(self, display_on)
- end
-
def shipping_tax
adjustments(:reload).shipping.sum &:included_tax
end
diff --git a/app/models/spree/payment_decorator.rb b/app/models/spree/payment_decorator.rb
index 1a80f0269f..d178e1de54 100644
--- a/app/models/spree/payment_decorator.rb
+++ b/app/models/spree/payment_decorator.rb
@@ -1,5 +1,33 @@
module Spree
Payment.class_eval do
+ has_one :adjustment, as: :source, dependent: :destroy
+
+ after_save :ensure_correct_adjustment, :update_order
+
+ def ensure_correct_adjustment
+ if adjustment
+ adjustment.originator = payment_method
+ adjustment.label = adjustment_label
+ adjustment.save
+ else
+ payment_method.create_adjustment(adjustment_label, order, self, true)
+ reload
+ end
+ end
+
+ def adjustment_label
+ I18n.t('payment_method_fee')
+ end
+
+ # This is called by the calculator of a payment method
+ def line_items
+ if order.complete? && Spree::Config[:track_inventory_levels]
+ order.line_items.select { |li| inventory_units.pluck(:variant_id).include?(li.variant_id) }
+ else
+ order.line_items
+ end
+ end
+
# Pin payments lacks void and credit methods, but it does have refund
# Here we swap credit out for refund and remove void as a possible action
def actions_with_pin_payment_adaptations
diff --git a/app/models/spree/payment_method_decorator.rb b/app/models/spree/payment_method_decorator.rb
index 915eb65ae8..77e22c591f 100644
--- a/app/models/spree/payment_method_decorator.rb
+++ b/app/models/spree/payment_method_decorator.rb
@@ -1,8 +1,14 @@
Spree::PaymentMethod.class_eval do
+ acts_as_taggable
+
# See gateway_decorator.rb when modifying this association
has_and_belongs_to_many :distributors, join_table: 'distributors_payment_methods', :class_name => 'Enterprise', association_foreign_key: 'distributor_id'
- attr_accessible :distributor_ids
+ attr_accessible :distributor_ids, :tag_list
+
+ calculated_adjustments
+
+ after_initialize :init
validates :distributors, presence: { message: "^At least one hub must be selected" }
@@ -31,6 +37,11 @@ Spree::PaymentMethod.class_eval do
where('spree_payment_methods.environment=? OR spree_payment_methods.environment=? OR spree_payment_methods.environment IS NULL', Rails.env, '')
}
+ def init
+ self.class.calculated_adjustments unless reflections.keys.include? :calculator
+ self.calculator ||= Spree::Calculator::FlatRate.new(preferred_amount: 0)
+ end
+
def has_distributor?(distributor)
self.distributors.include?(distributor)
end
diff --git a/app/models/spree/taxon_decorator.rb b/app/models/spree/taxon_decorator.rb
index a1e07cc53e..a051de98fa 100644
--- a/app/models/spree/taxon_decorator.rb
+++ b/app/models/spree/taxon_decorator.rb
@@ -24,10 +24,9 @@ Spree::Taxon.class_eval do
joins(:products => :supplier).
select('spree_taxons.*, enterprises.id AS enterprise_id').
each do |t|
-
- taxons[t.enterprise_id.to_i] ||= Set.new
- taxons[t.enterprise_id.to_i] << t.id
- end
+ taxons[t.enterprise_id.to_i] ||= Set.new
+ taxons[t.enterprise_id.to_i] << t.id
+ end
taxons
end
@@ -43,10 +42,9 @@ Spree::Taxon.class_eval do
where('o_exchanges.incoming = ?', false).
select('spree_taxons.*, o_exchanges.receiver_id AS enterprise_id').
each do |t|
-
- taxons[t.enterprise_id.to_i] ||= Set.new
- taxons[t.enterprise_id.to_i] << t.id
- end
+ taxons[t.enterprise_id.to_i] ||= Set.new
+ taxons[t.enterprise_id.to_i] << t.id
+ end
taxons
end
diff --git a/app/models/spree/user_decorator.rb b/app/models/spree/user_decorator.rb
index afd32bd294..562b0d10ca 100644
--- a/app/models/spree/user_decorator.rb
+++ b/app/models/spree/user_decorator.rb
@@ -38,7 +38,8 @@ Spree.user_class.class_eval do
end
def customer_of(enterprise)
- customers.of(enterprise).first
+ return nil unless enterprise
+ customers.find_by_enterprise_id(enterprise)
end
def send_signup_confirmation
diff --git a/app/models/tag_rule.rb b/app/models/tag_rule.rb
index 6e6405a589..a5f2d93dee 100644
--- a/app/models/tag_rule.rb
+++ b/app/models/tag_rule.rb
@@ -1,42 +1,26 @@
class TagRule < ActiveRecord::Base
- attr_accessor :subject, :context
-
belongs_to :enterprise
preference :customer_tags, :string, default: ""
validates :enterprise, presence: true
- attr_accessible :enterprise, :enterprise_id, :preferred_customer_tags
+ attr_accessible :enterprise, :enterprise_id, :is_default, :priority
+ attr_accessible :preferred_customer_tags
- def set_context(subject, context)
- @subject = subject
- @context = context
- end
+ scope :for, ->(enterprise) { where(enterprise_id: enterprise) }
+ scope :prioritised, -> { order('priority ASC') }
- def apply
- if relevant?
- if customer_tags_match?
- apply!
- else
- apply_default! if respond_to?(:apply_default!,true)
+ def self.mapping_for(enterprises)
+ self.for(enterprises).inject({}) do |mapping, rule|
+ rule.preferred_customer_tags.split(",").each do |tag|
+ if mapping[tag]
+ mapping[tag][:rules] += 1
+ else
+ mapping[tag] = { text: tag, rules: 1 }
+ end
end
+ mapping
end
end
-
- private
-
- def relevant?
- return false unless subject_class_matches?
- if respond_to?(:additional_requirements_met?, true)
- return false unless additional_requirements_met?
- end
- true
- end
-
- def customer_tags_match?
- context_customer_tags = context.andand[:customer].andand.tag_list || []
- preferred_tags = preferred_customer_tags.split(",")
- ( context_customer_tags & preferred_tags ).any?
- end
end
diff --git a/app/models/tag_rule/filter_order_cycles.rb b/app/models/tag_rule/filter_order_cycles.rb
new file mode 100644
index 0000000000..de626ac70a
--- /dev/null
+++ b/app/models/tag_rule/filter_order_cycles.rb
@@ -0,0 +1,22 @@
+class TagRule::FilterOrderCycles < TagRule
+ preference :matched_order_cycles_visibility, :string, default: "visible"
+ preference :exchange_tags, :string, default: ""
+
+ attr_accessible :preferred_matched_order_cycles_visibility, :preferred_exchange_tags
+
+ def tags_match?(order_cycle)
+ exchange_tags = exchange_for(order_cycle).andand.tag_list || []
+ preferred_tags = preferred_exchange_tags.split(",")
+ ( exchange_tags & preferred_tags ).any?
+ end
+
+ def reject_matched?
+ preferred_matched_order_cycles_visibility != "visible"
+ end
+
+ private
+
+ def exchange_for(order_cycle)
+ order_cycle.exchanges.outgoing.to_enterprise(enterprise).first
+ end
+end
diff --git a/app/models/tag_rule/filter_payment_methods.rb b/app/models/tag_rule/filter_payment_methods.rb
new file mode 100644
index 0000000000..04a10889e0
--- /dev/null
+++ b/app/models/tag_rule/filter_payment_methods.rb
@@ -0,0 +1,16 @@
+class TagRule::FilterPaymentMethods < TagRule
+ preference :matched_payment_methods_visibility, :string, default: "visible"
+ preference :payment_method_tags, :string, default: ""
+
+ attr_accessible :preferred_matched_payment_methods_visibility, :preferred_payment_method_tags
+
+ def tags_match?(payment_method)
+ payment_method_tags = payment_method.andand.tag_list || []
+ preferred_tags = preferred_payment_method_tags.split(",")
+ ( payment_method_tags & preferred_tags ).any?
+ end
+
+ def reject_matched?
+ preferred_matched_payment_methods_visibility != "visible"
+ end
+end
diff --git a/app/models/tag_rule/filter_products.rb b/app/models/tag_rule/filter_products.rb
new file mode 100644
index 0000000000..e1d3865036
--- /dev/null
+++ b/app/models/tag_rule/filter_products.rb
@@ -0,0 +1,22 @@
+class TagRule
+ class FilterProducts < TagRule
+ preference :matched_variants_visibility, :string, default: "visible"
+ preference :variant_tags, :string, default: ""
+
+ attr_accessible :preferred_matched_variants_visibility, :preferred_variant_tags
+
+ def self.tagged_children_for(product)
+ product["variants"]
+ end
+
+ def tags_match?(variant)
+ variant_tags = variant.andand["tag_list"] || []
+ preferred_tags = preferred_variant_tags.split(",")
+ (variant_tags & preferred_tags).any?
+ end
+
+ def reject_matched?
+ preferred_matched_variants_visibility != "visible"
+ end
+ end
+end
diff --git a/app/models/tag_rule/filter_shipping_methods.rb b/app/models/tag_rule/filter_shipping_methods.rb
index 74438e560e..2091a14a63 100644
--- a/app/models/tag_rule/filter_shipping_methods.rb
+++ b/app/models/tag_rule/filter_shipping_methods.rb
@@ -4,19 +4,8 @@ class TagRule::FilterShippingMethods < TagRule
attr_accessible :preferred_matched_shipping_methods_visibility, :preferred_shipping_method_tags
- private
-
- # Warning: this should only EVER be called via TagRule#apply
- def apply!
- unless preferred_matched_shipping_methods_visibility == "visible"
- subject.reject!{ |sm| tags_match?(sm) }
- end
- end
-
- def apply_default!
- if preferred_matched_shipping_methods_visibility == "visible"
- subject.reject!{ |sm| tags_match?(sm) }
- end
+ def reject_matched?
+ preferred_matched_shipping_methods_visibility != "visible"
end
def tags_match?(shipping_method)
@@ -24,9 +13,4 @@ class TagRule::FilterShippingMethods < TagRule
preferred_tags = preferred_shipping_method_tags.split(",")
( shipping_method_tags & preferred_tags ).any?
end
-
- def subject_class_matches?
- subject.class == Array &&
- subject.all? { |i| i.class == Spree::ShippingMethod }
- end
end
diff --git a/app/models/variant_override.rb b/app/models/variant_override.rb
index b373900e0c..e96b56db56 100644
--- a/app/models/variant_override.rb
+++ b/app/models/variant_override.rb
@@ -1,4 +1,6 @@
class VariantOverride < ActiveRecord::Base
+ acts_as_taggable
+
belongs_to :hub, class_name: 'Enterprise'
belongs_to :variant, class_name: 'Spree::Variant'
diff --git a/app/models/variant_override_set.rb b/app/models/variant_override_set.rb
index 730246cebe..2d39d452fd 100644
--- a/app/models/variant_override_set.rb
+++ b/app/models/variant_override_set.rb
@@ -1,16 +1,28 @@
class VariantOverrideSet < ModelSet
def initialize(collection, attributes={})
- super(VariantOverride, collection, attributes, nil, proc { |attrs| deletable?(attrs) } )
+ super(VariantOverride, collection, attributes, nil, proc { |attrs, tag_list| deletable?(attrs, tag_list) } )
end
private
- def deletable?(attrs)
+ def deletable?(attrs, tag_list)
attrs['price'].blank? &&
attrs['count_on_hand'].blank? &&
attrs['default_stock'].blank? &&
attrs['resettable'].blank? &&
attrs['sku'].nil? &&
- attrs['on_demand'].nil?
+ attrs['on_demand'].nil? &&
+ tag_list.empty?
+ end
+
+ def collection_to_delete
+ # Override of ModelSet method to allow us to check presence of a tag_list (which is not an attribute)
+ deleted = []
+ collection.delete_if { |e| deleted << e if @delete_if.andand.call(e.attributes, e.tag_list) }
+ deleted
+ end
+
+ def collection_to_keep
+ collection.reject { |e| @delete_if.andand.call(e.attributes, e.tag_list) }
end
end
diff --git a/app/overrides/spree/admin/orders/_form/add_distribution_fields.html.haml.deface b/app/overrides/spree/admin/orders/_form/add_distribution_fields.html.haml.deface
index 3fa7d3ea83..a6c51d517c 100644
--- a/app/overrides/spree/admin/orders/_form/add_distribution_fields.html.haml.deface
+++ b/app/overrides/spree/admin/orders/_form/add_distribution_fields.html.haml.deface
@@ -19,11 +19,20 @@
.alpha.six.columns
.field
%label{for: "order_distributor_id"} Distributor
- %select.fullwidth{id: "order_distributor_id", name: "order[distributor_id]", 'ng-model' => 'distributor_id'}
- %option{"ng-repeat" => "shop in shops", "ng-value" => "shop.id", "ng-selected" => "distributor_id == shop.id", "ng-disabled" => "!distributorHasOrderCycles(shop)", "ng-bind" => "shop.name"}
+ %input.ofn-select2.fullwidth{id: "order_distributor_id",
+ type: 'number',
+ name: "order[distributor_id]",
+ "ng-model" => 'distributor_id',
+ data: "shops" }
.omega.six.columns
- .field{"ng-show" => "distributor_id"}
- %label{for: "order_order_cycle_id"} Order Cycle
- %select.select2.fullwidth{id: "order_order_cycle_id", name: "order[order_cycle_id]", 'ng-model' => 'order_cycle_id'}
- %option{"ng-repeat" => "oc in orderCycles | filter:validOrderCycle", "ng-value" => "oc.id", "ng-selected" => "order_cycle_id == oc.id", "ng-bind" => "oc.name_and_status"}
+ .field
+ %label{ for: "order_order_cycle_id"} Order Cycle
+ %input.ofn-select2.fullwidth{id: "order_order_cycle_id",
+ type: 'number',
+ name: "order[order_cycle_id]",
+ "ng-model" => 'order_cycle_id',
+ "ng-disabled" => "!distributor_id",
+ data: "orderCycles",
+ text: "name_and_status",
+ filter: "validOrderCycle" }
diff --git a/app/overrides/spree/admin/payment_methods/_form/replace_form_fields.html.haml.deface b/app/overrides/spree/admin/payment_methods/_form/replace_form_fields.html.haml.deface
index 3b588a98c1..aa9ead7a0f 100644
--- a/app/overrides/spree/admin/payment_methods/_form/replace_form_fields.html.haml.deface
+++ b/app/overrides/spree/admin/payment_methods/_form/replace_form_fields.html.haml.deface
@@ -1,6 +1,7 @@
/ replace "div[data-hook='admin_payment_method_form_fields']"
-%div.alpha.eleven.columns
+= admin_inject_payment_method
+%div.alpha.eleven.columns{ "ng-app" => "admin.paymentMethods", "ng-controller" => "paymentMethodCtrl" }
.row
.alpha.three.columns
= label_tag nil, t(:name)
@@ -33,4 +34,16 @@
= radio_button :payment_method, :active, false
= label_tag nil, t(:say_no)
- = render 'providers'
\ No newline at end of file
+
+ .row
+ .alpha.three.columns
+ = label(:payment_method, :tags, t(:tags))
+ .omega.eight.columns
+ = hidden_field(:payment_method, :tag_list, "ng-value" => "paymentMethod.tag_list")
+ %tags-with-translation#something{ object: "paymentMethod" }
+
+ = render 'providers'
+
+ .row
+ .omega.eleven.columns
+ = render partial: 'spree/admin/shared/calculator_fields', :locals => { :f => f }
diff --git a/app/overrides/spree/admin/shipping_methods/_form/replace_form_fields.html.haml.deface b/app/overrides/spree/admin/shipping_methods/_form/replace_form_fields.html.haml.deface
index a2f9ed766a..3cffbbabcb 100644
--- a/app/overrides/spree/admin/shipping_methods/_form/replace_form_fields.html.haml.deface
+++ b/app/overrides/spree/admin/shipping_methods/_form/replace_form_fields.html.haml.deface
@@ -54,7 +54,7 @@
= f.label :tags, t(:tags)
.omega.eight.columns
= f.hidden_field :tag_list, "ng-value" => "shippingMethod.tag_list"
- %tags-with-translation#something{ object: "shippingMethod" }
+ %tags-with-translation#something{ object: "shippingMethod", 'find-tags' => 'findTags(query)' }
.row
.alpha.eleven.columns
diff --git a/app/overrides/spree/layouts/admin/add_analytics.html.haml.deface b/app/overrides/spree/layouts/admin/add_analytics.html.haml.deface
index 548439b60f..ced2d1b7b6 100644
--- a/app/overrides/spree/layouts/admin/add_analytics.html.haml.deface
+++ b/app/overrides/spree/layouts/admin/add_analytics.html.haml.deface
@@ -1,3 +1,3 @@
/ insert_bottom "[data-hook='admin_footer_scripts']"
-= render 'shared/analytics'
+= render 'spree/shared/google_analytics'
diff --git a/app/serializers/api/admin/column_preference_serializer.rb b/app/serializers/api/admin/column_preference_serializer.rb
new file mode 100644
index 0000000000..8b8eeb1b3a
--- /dev/null
+++ b/app/serializers/api/admin/column_preference_serializer.rb
@@ -0,0 +1,3 @@
+class Api::Admin::ColumnPreferenceSerializer < ActiveModel::Serializer
+ attributes :id, :user_id, :action_name, :column_name, :name, :visible
+end
diff --git a/app/serializers/api/admin/customer_serializer.rb b/app/serializers/api/admin/customer_serializer.rb
index 052f49a917..4634625c11 100644
--- a/app/serializers/api/admin/customer_serializer.rb
+++ b/app/serializers/api/admin/customer_serializer.rb
@@ -6,10 +6,9 @@ class Api::Admin::CustomerSerializer < ActiveModel::Serializer
end
def tags
- tag_rule_map = object.enterprise.rules_per_tag
object.tag_list.map do |tag|
- { text: tag, rules: tag_rule_map[tag] }
+ tag_rule_map = options[:tag_rule_mapping][tag]
+ tag_rule_map || { text: tag, rules: nil }
end
end
-
end
diff --git a/app/serializers/api/admin/enterprise_serializer.rb b/app/serializers/api/admin/enterprise_serializer.rb
index 8f70da1a0d..49a49b3250 100644
--- a/app/serializers/api/admin/enterprise_serializer.rb
+++ b/app/serializers/api/admin/enterprise_serializer.rb
@@ -3,19 +3,26 @@ class Api::Admin::EnterpriseSerializer < ActiveModel::Serializer
attributes :producer_profile_only, :email, :long_description, :permalink
attributes :preferred_shopfront_message, :preferred_shopfront_closed_message, :preferred_shopfront_taxon_order, :preferred_shopfront_order_cycle_order
attributes :preferred_product_selection_from_inventory_only
- attributes :owner, :users, :tag_groups
+ attributes :owner, :users, :tag_groups, :default_tag_group
has_one :owner, serializer: Api::Admin::UserSerializer
has_many :users, serializer: Api::Admin::UserSerializer
def tag_groups
- tag_groups = []
- object.tag_rules.each do |tag_rule|
+ object.tag_rules.prioritised.reject(&:is_default).each_with_object([]) do |tag_rule, tag_groups|
tag_group = find_match(tag_groups, tag_rule.preferred_customer_tags.split(",").map{ |t| { text: t } })
- tag_groups << tag_group if tag_group[:rules].empty?
+ if tag_group[:rules].empty?
+ tag_groups << tag_group
+ tag_group[:position] = tag_groups.count
+ end
tag_group[:rules] << Api::Admin::TagRuleSerializer.new(tag_rule).serializable_hash
end
- tag_groups
+ end
+
+ def default_tag_group
+ default_rules = object.tag_rules.select(&:is_default)
+ serialized_rules = ActiveModel::ArraySerializer.new(default_rules, each_serializer: Api::Admin::TagRuleSerializer)
+ { tags: [], rules: serialized_rules }
end
def find_match(tag_groups, tags)
diff --git a/app/serializers/api/admin/exchange_serializer.rb b/app/serializers/api/admin/exchange_serializer.rb
index 615d49f695..e1c15c5210 100644
--- a/app/serializers/api/admin/exchange_serializer.rb
+++ b/app/serializers/api/admin/exchange_serializer.rb
@@ -1,5 +1,6 @@
class Api::Admin::ExchangeSerializer < ActiveModel::Serializer
attributes :id, :sender_id, :receiver_id, :incoming, :variants, :receival_instructions, :pickup_time, :pickup_instructions
+ attributes :tags, :tag_list
has_many :enterprise_fees, serializer: Api::Admin::BasicEnterpriseFeeSerializer
@@ -35,4 +36,12 @@ class Api::Admin::ExchangeSerializer < ActiveModel::Serializer
OpenFoodNetwork::OrderCyclePermissions.new(options[:current_user], object.order_cycle)
.visible_variants_for_outgoing_exchanges_to(object.receiver)
end
+
+ def tag_list
+ object.tag_list.join(",")
+ end
+
+ def tags
+ object.tag_list.map{ |t| { text: t } }
+ end
end
diff --git a/app/serializers/api/admin/payment_method_serializer.rb b/app/serializers/api/admin/payment_method_serializer.rb
new file mode 100644
index 0000000000..42c11a6953
--- /dev/null
+++ b/app/serializers/api/admin/payment_method_serializer.rb
@@ -0,0 +1,11 @@
+class Api::Admin::PaymentMethodSerializer < ActiveModel::Serializer
+ attributes :id, :name, :type, :tag_list, :tags
+
+ def tag_list
+ object.tag_list.join(",")
+ end
+
+ def tags
+ object.tag_list.map{ |t| { text: t } }
+ end
+end
diff --git a/app/serializers/api/admin/shipping_method_serializer.rb b/app/serializers/api/admin/shipping_method_serializer.rb
index 9fbb864d09..e160d97fdf 100644
--- a/app/serializers/api/admin/shipping_method_serializer.rb
+++ b/app/serializers/api/admin/shipping_method_serializer.rb
@@ -1,5 +1,9 @@
class Api::Admin::ShippingMethodSerializer < ActiveModel::Serializer
- attributes :id, :name, :tags
+ attributes :id, :name, :tag_list, :tags
+
+ def tag_list
+ object.tag_list.join(",")
+ end
def tags
object.tag_list.map{ |t| { text: t } }
diff --git a/app/serializers/api/admin/tag_rule_serializer.rb b/app/serializers/api/admin/tag_rule_serializer.rb
index ac333c23c6..ca24a02638 100644
--- a/app/serializers/api/admin/tag_rule_serializer.rb
+++ b/app/serializers/api/admin/tag_rule_serializer.rb
@@ -10,7 +10,7 @@ end
module Api::Admin::TagRule
class BaseSerializer < ActiveModel::Serializer
- attributes :id, :enterprise_id, :type, :preferred_customer_tags
+ attributes :id, :enterprise_id, :type, :is_default, :preferred_customer_tags
end
class DiscountOrderSerializer < BaseSerializer
@@ -18,10 +18,34 @@ module Api::Admin::TagRule
end
class FilterShippingMethodsSerializer < BaseSerializer
- attributes :preferred_matched_shipping_methods_visibility, :shipping_method_tags
+ attributes :preferred_matched_shipping_methods_visibility, :preferred_shipping_method_tags, :shipping_method_tags
def shipping_method_tags
object.preferred_shipping_method_tags.split(",")
end
end
+
+ class FilterPaymentMethodsSerializer < BaseSerializer
+ attributes :preferred_matched_payment_methods_visibility, :preferred_payment_method_tags, :payment_method_tags
+
+ def payment_method_tags
+ object.preferred_payment_method_tags.split(",")
+ end
+ end
+
+ class FilterProductsSerializer < BaseSerializer
+ attributes :preferred_matched_variants_visibility, :preferred_variant_tags, :variant_tags
+
+ def variant_tags
+ object.preferred_variant_tags.split(",")
+ end
+ end
+
+ class FilterOrderCyclesSerializer < BaseSerializer
+ attributes :preferred_matched_order_cycles_visibility, :preferred_exchange_tags, :exchange_tags
+
+ def exchange_tags
+ object.preferred_exchange_tags.split(",")
+ end
+ end
end
diff --git a/app/serializers/api/admin/variant_override_serializer.rb b/app/serializers/api/admin/variant_override_serializer.rb
index c1e6e0038c..68b86818a1 100644
--- a/app/serializers/api/admin/variant_override_serializer.rb
+++ b/app/serializers/api/admin/variant_override_serializer.rb
@@ -1,3 +1,12 @@
class Api::Admin::VariantOverrideSerializer < ActiveModel::Serializer
attributes :id, :hub_id, :variant_id, :sku, :price, :count_on_hand, :on_demand, :default_stock, :resettable
+ attributes :tag_list, :tags
+
+ def tag_list
+ object.tag_list.join(",")
+ end
+
+ def tags
+ object.tag_list.map { |t| { text: t } }
+ end
end
diff --git a/app/serializers/api/payment_method_serializer.rb b/app/serializers/api/payment_method_serializer.rb
index d62bc18154..a135f5d792 100644
--- a/app/serializers/api/payment_method_serializer.rb
+++ b/app/serializers/api/payment_method_serializer.rb
@@ -1,3 +1,8 @@
class Api::PaymentMethodSerializer < ActiveModel::Serializer
- attributes :name, :description, :id, :method_type
+ attributes :name, :description, :id, :method_type,
+ :price
+
+ def price
+ object.compute_amount(options[:current_order])
+ end
end
diff --git a/app/serializers/api/variant_serializer.rb b/app/serializers/api/variant_serializer.rb
index 6908eaf84f..6a38fe9b93 100644
--- a/app/serializers/api/variant_serializer.rb
+++ b/app/serializers/api/variant_serializer.rb
@@ -1,6 +1,7 @@
class Api::VariantSerializer < ActiveModel::Serializer
- attributes :id, :is_master, :count_on_hand, :name_to_display, :unit_to_display,
- :options_text, :on_demand, :price, :fees, :price_with_fees, :product_name
+ attributes :id, :is_master, :count_on_hand, :name_to_display, :unit_to_display
+ attributes :options_text, :on_demand, :price, :fees, :price_with_fees, :product_name
+ attributes :tag_list
def price
object.price
diff --git a/app/views/admin/customers/index.html.haml b/app/views/admin/customers/index.html.haml
index 0a348d4a0a..c570632c64 100644
--- a/app/views/admin/customers/index.html.haml
+++ b/app/views/admin/customers/index.html.haml
@@ -2,39 +2,50 @@
%h1.page-title
=t :customers
+- content_for :app_wrapper_attrs do
+ = "ng-app='admin.customers'"
+
+- content_for :page_actions do
+ %li
+ %a.button.icon-plus#new-customer{ href: "#", "new-customer-dialog" => true }
+ = t('admin.customers.index.new_customer')
+
+= admin_inject_column_preferences module: 'admin.customers'
= admin_inject_shops
-%div{ ng: { app: 'admin.customers', controller: 'customersCtrl' } }
- .row{ ng: { hide: "loaded && customers.length > 0" } }
+%div{ ng: { controller: 'customersCtrl' } }
+ .row{ ng: { hide: "!RequestMonitor.loading && customers.length > 0" } }
.five.columns.alpha
%h3
=t :please_select_hub
.four.columns
- %select.select2.fullwidth#shop_id{ 'ng-model' => 'shop.id', name: 'shop_id', 'ng-options' => 'shop.id as shop.name for shop in shops' }
+ %select.select2.fullwidth#shop_id{ 'ng-model' => 'CurrentShop.shop', name: 'shop_id', 'ng-options' => 'shop as shop.name for shop in shops' }
.seven.columns.omega
- .row{ 'ng-hide' => '!loaded || customers.length == 0' }
+ .row{ 'ng-hide' => 'RequestMonitor.loading || !CurrentShop.shop.id || customers.length == 0' }
.controls.sixteen.columns.alpha.omega
.five.columns.alpha
%input.fullwidth{ :type => "text", :id => 'quick_search', 'ng-model' => 'quickSearch', :placeholder => 'Quick Search' }
.eight.columns
- = render 'admin/shared/columns_dropdown'
- .row{ 'ng-if' => 'shop.id && !loaded' }
+ %columns-dropdown{ action: "#{controller_name}_#{action_name}" }
+ .row{ 'ng-if' => 'CurrentShop.shop.id && RequestMonitor.loading' }
.sixteen.columns.alpha#loading
%img.spinner{ src: "/assets/spinning-circles.svg" }
%h1
=t :loading_customers
- .row{ :class => "sixteen columns alpha", 'ng-show' => 'loaded && filteredCustomers.length == 0'}
+ .row{ :class => "sixteen columns alpha", 'ng-show' => '!RequestMonitor.loading && filteredCustomers.length == 0'}
%h1#no_results
=t :no_customers_found
+ .row.margin-bottom-50{ ng: { show: "!RequestMonitor.loading && filteredCustomers.length > 0" } }
+ %form{ name: "customers_form" }
+ %save-bar{ dirty: "customers_form.$dirty", persist: "false" }
+ %input.red{ type: "button", value: "Save Changes", ng: { click: "submitAll(customers_form)", disabled: "!customers_form.$dirty" } }
- .row{ ng: { show: "loaded && filteredCustomers.length > 0" } }
- %form{ name: "customers" }
%table.index#customers
- %col.email{ width: "20%"}
- %col.code{ width: "20%"}
- %col.tags{ width: "50%"}
+ %col.email{ width: "20%", 'ng-show' => 'columns.email.visible' }
+ %col.code{ width: "20%", 'ng-show' => 'columns.code.visible' }
+ %col.tags{ width: "50%", 'ng-show' => 'columns.tags.visible' }
%col.actions{ width: "10%"}
%thead
%tr{ ng: { controller: "ColumnsCtrl" } }
@@ -48,10 +59,10 @@
%th.actions
Ask?
%input{ :type => 'checkbox', 'ng-model' => "confirmDelete" }
- %tr.customer{ 'ng-repeat' => "customer in filteredCustomers = ( customers | filter:quickSearch | orderBy:predicate:reverse )", 'ng-class-even' => "'even'", 'ng-class-odd' => "'odd'", :id => "c_{{customer.id}}" }
+ %tr.customer{ 'ng-repeat' => "customer in filteredCustomers = ( customers | filter:quickSearch | orderBy:predicate:reverse ) | limitTo:customerLimit track by customer.id", 'ng-class-even' => "'even'", 'ng-class-odd' => "'odd'", :id => "c_{{customer.id}}" }
-# %td.bulk
-# %input{ :type => "checkbox", :name => 'bulk', 'ng-model' => 'customer.checked' }
- %td.email{ 'ng-show' => 'columns.email.visible' } {{ customer.email }}
+ %td.email{ 'ng-show' => 'columns.email.visible', "ng-bind" => '::customer.email' }
%td.code{ 'ng-show' => 'columns.code.visible' }
%input{ :type => 'text', :name => 'code', :id => 'code', 'ng-model' => 'customer.code', 'obj-for-update' => "customer", "attr-for-update" => "code" }
%td.tags{ 'ng-show' => 'columns.tags.visible' }
@@ -59,12 +70,9 @@
%tags_with_translation{ object: 'customer', 'find-tags' => 'findTags(query)' }
%td.actions
%a{ 'ng-click' => "deleteCustomer(customer)", :class => "delete-customer icon-trash no-text" }
- %input{ :type => "button", 'value' => 'Update', 'ng-click' => 'submitAll()' }
- %form{ng: {show: "loaded", submit: 'add(newCustomerEmail)'}}
- %h2= t '.add_new_customer'
- .row
- .five.columns.alpha
- %input.fullwidth{type: "text", placeholder: t('.customer_placeholder'), ng: {model: 'newCustomerEmail'}}
- .eleven.columns.omega
- %input{type: "submit", value: t('.add_customer')}
+ -# %show-more.text-center{ data: "filteredCustomers", limit: "customerLimit", increment: "20" }
+ %div.text-center{ ng: { show: "filteredCustomers.length > customerLimit" } }
+ %input{ type: 'button', value: 'Show More', ng: { click: 'customerLimit = customerLimit + 20' } }
+ or
+ %input{ type: 'button', value: "Show All ({{ filteredCustomers.length - customerLimit }} More)", ng: { click: 'customerLimit = filteredCustomers.length' } }
diff --git a/app/views/admin/enterprise_groups/_form_about.html.haml b/app/views/admin/enterprise_groups/_form_about.html.haml
index 1e97697a4e..e7932bc272 100644
--- a/app/views/admin/enterprise_groups/_form_about.html.haml
+++ b/app/views/admin/enterprise_groups/_form_about.html.haml
@@ -1,6 +1,5 @@
-%fieldset.alpha.no-border-bottom{ ng: { show: "menu.selected.name=='About'" } }
- %legend
- = t 'admin_entreprise_groups_about'
+%fieldset.alpha.no-border-bottom{ ng: { show: "menu.selected.name=='about'" } }
+ %legend {{menu.selected.label}}
= f.field_container :long_description do
%text-angular{'id' => 'enterprise_group_long_description', 'name' => 'enterprise_group[long_description]', 'class' => 'text-angular',
'ta-toolbar' => "[['h1','h2','h3','h4','p'],['bold','italics','underline','clear'],['insertLink']]"}
diff --git a/app/views/admin/enterprise_groups/_form_address.html.haml b/app/views/admin/enterprise_groups/_form_address.html.haml
index fa7b1e7e75..2b8add5392 100644
--- a/app/views/admin/enterprise_groups/_form_address.html.haml
+++ b/app/views/admin/enterprise_groups/_form_address.html.haml
@@ -1,7 +1,6 @@
= f.fields_for :address do |af|
- %fieldset.alpha.no-border-bottom{ ng: { show: "menu.selected.name=='Contact'" } }
- %legend
- = t 'admin_entreprise_groups_contact'
+ %fieldset.alpha.no-border-bottom{ ng: { show: "menu.selected.name=='contact'" } }
+ %legend {{menu.selected.label}}
.row
.alpha.three.columns
= af.label :phone
diff --git a/app/views/admin/enterprise_groups/_form_images.html.haml b/app/views/admin/enterprise_groups/_form_images.html.haml
index 24bfad3bf0..7add7c914a 100644
--- a/app/views/admin/enterprise_groups/_form_images.html.haml
+++ b/app/views/admin/enterprise_groups/_form_images.html.haml
@@ -1,6 +1,5 @@
-%fieldset.alpha.no-border-bottom{ ng: { show: "menu.selected.name=='Images'" } }
- %legend
- = t 'admin_entreprise_groups_images'
+%fieldset.alpha.no-border-bottom{ ng: { show: "menu.selected.name=='images'" } }
+ %legend {{menu.selected.label}}
.row
.alpha.three.columns
= f.label :logo, 'ofn-with-tip' => t('admin_entreprise_groups_data_powertip_logo')
diff --git a/app/views/admin/enterprise_groups/_form_primary_details.html.haml b/app/views/admin/enterprise_groups/_form_primary_details.html.haml
index 56771c458c..f003963b4e 100644
--- a/app/views/admin/enterprise_groups/_form_primary_details.html.haml
+++ b/app/views/admin/enterprise_groups/_form_primary_details.html.haml
@@ -1,6 +1,5 @@
-%fieldset.alpha.no-border-bottom{ ng: { show: "menu.selected.name=='Primary Details'" } }
- %legend
- = t "admin_entreprise_groups_primary_details"
+%fieldset.alpha.no-border-bottom{ ng: { show: "menu.selected.name=='primary_details'" } }
+ %legend {{menu.selected.label}}
= f.field_container :name do
= f.label :name
%br/
diff --git a/app/views/admin/enterprise_groups/_form_users.html.haml b/app/views/admin/enterprise_groups/_form_users.html.haml
index 6bd6eaa4a3..22ac7921ca 100644
--- a/app/views/admin/enterprise_groups/_form_users.html.haml
+++ b/app/views/admin/enterprise_groups/_form_users.html.haml
@@ -1,6 +1,5 @@
-%fieldset.alpha.no-border-bottom{ ng: { show: "menu.selected.name=='Users'" } }
- %legend
- = t(:users)
+%fieldset.alpha.no-border-bottom{ ng: { show: "menu.selected.name=='users'" } }
+ %legend {{menu.selected.label}}
.row
.three.columns.alpha
=f.label :owner_id, t(:admin_entreprise_groups_owner)
diff --git a/app/views/admin/enterprise_groups/_form_web.html.haml b/app/views/admin/enterprise_groups/_form_web.html.haml
index 5d982f1bbb..e0de2f7159 100644
--- a/app/views/admin/enterprise_groups/_form_web.html.haml
+++ b/app/views/admin/enterprise_groups/_form_web.html.haml
@@ -1,6 +1,5 @@
-%fieldset.alpha.no-border-bottom{ ng: { show: "menu.selected.name=='Web'" } }
- %legend
- = t 'admin_entreprise_groups_web'
+%fieldset.alpha.no-border-bottom{ ng: { show: "menu.selected.name=='web'" } }
+ %legend {{menu.selected.label}}
.row
.alpha.three.columns
= f.label :website
diff --git a/app/views/admin/enterprises/_enterprise_user_index.html.haml b/app/views/admin/enterprises/_enterprise_user_index.html.haml
index 1fb35e595c..d89970946d 100644
--- a/app/views/admin/enterprises/_enterprise_user_index.html.haml
+++ b/app/views/admin/enterprises/_enterprise_user_index.html.haml
@@ -6,7 +6,7 @@
.six.columns
-# = render 'admin/shared/bulk_actions_dropdown'
.three.columns
- = render 'admin/shared/columns_dropdown'
+ %columns-dropdown{ action: "#{controller_name}_#{action_name}" }
.row{ 'ng-if' => '!loaded' }
.sixteen.columns.alpha#loading
%img.spinner{ src: "/assets/spinning-circles.svg" }
@@ -15,7 +15,7 @@
%h1#no_results No enterprises found.
- .row{ ng: { show: "loaded && filteredEnterprises.length > 0" }, bindonce: true }
+ .row{ ng: { show: "loaded && filteredEnterprises.length > 0" } }
%table.index#enterprises
%col.name{ width: "28%", ng: { show: 'columns.name.visible' } }
%col.producer{ width: "18%", ng: { show: 'columns.producer.visible' }}
@@ -24,24 +24,23 @@
%col.manage{ width: "18%", ng: { show: 'columns.manage.visible' }}
%thead
%tr{ ng: { controller: "ColumnsCtrl" } }
- %th.name{ ng: { show: 'columns.name.visible' } }
- Name
- %th.producer{ ng: { show: 'columns.producer.visible' } } Producer?
- %th.package{ ng: { show: 'columns.package.visible' } } Package
- %th.status{ ng: { show: 'columns.status.visible' } } Status
- %th.manage{ ng: { show: 'columns.manage.visible' } } Manage
- %tbody{ :id => "e_{{enterprise.id}}", ng: { repeat: "enterprise in filteredEnterprises = ( allEnterprises | filter:{ name: quickSearch } )", controller: 'EnterpriseIndexRowCtrl' } }
- %tr.enterprise.panel-toggle-row{ object: "enterprise", ng: { class: { even: "'even'", odd: "'odd'"} } }
+ %th.name{ ng: { show: 'columns.name.visible' } }=t('admin.name')
+ %th.producer{ ng: { show: 'columns.producer.visible' } }=t('.producer?')
+ %th.package{ ng: { show: 'columns.package.visible' } }=t('.package')
+ %th.status{ ng: { show: 'columns.status.visible' } }=t('.status')
+ %th.manage{ ng: { show: 'columns.manage.visible' } }=t('.manage')
+ %tbody.panel-ctrl{ :id => "e_{{enterprise.id}}", object: "enterprise", ng: { repeat: "enterprise in filteredEnterprises = ( allEnterprises | filter:{ name: quickSearch } )" } }
+ %tr.enterprise{ ng: { class: { even: "'even'", odd: "'odd'"} } }
%td.name{ ng: { show: 'columns.name.visible' } }
- %span{ bo: { bind: "enterprise.name" } }
- %td.producer.panel-toggle.text-center{ ng: { show: 'columns.producer.visible', class: "{error: producerError}" }, name: "producer" }
- %h5{ ng: { bind: "producer" } }
- %td.package.panel-toggle.text-center{ ng: { show: 'columns.package.visible', class: "{error: packageError}" }, name: "package" }
- %h5{ ng: { bind: "package" } }
+ %span{ ng: { bind: "::enterprise.name" } }
+ %td.producer.panel-toggle.text-center{ ng: { show: 'columns.producer.visible', class: "{error: enterprise.producerError}" }, name: "producer" }
+ %h5{ ng: { bind: "enterprise.producer" } }
+ %td.package.panel-toggle.text-center{ ng: { show: 'columns.package.visible', class: "{error: enterprise.packageError}" }, name: "package" }
+ %h5{ ng: { bind: "enterprise.package" } }
%td.status.panel-toggle.text-center{ ng: { show: 'columns.status.visible' }, name: "status" }
- %i.icon-status{ bo: { class: "status" } }
+ %i.icon-status{ ng: { class: "enterprise.status" } }
%td.manage{ ng: { show: 'columns.manage.visible' } }
- %a.button.fullwidth{ bo: { href: 'enterprise.edit_path' } }
+ %a.button.fullwidth{ ng: { href: '{{::enterprise.edit_path}}' } }
Manage
%i.icon-arrow-right
diff --git a/app/views/admin/enterprises/_form.html.haml b/app/views/admin/enterprises/_form.html.haml
index e363078733..ee3dfa2183 100644
--- a/app/views/admin/enterprises/_form.html.haml
+++ b/app/views/admin/enterprises/_form.html.haml
@@ -1,60 +1,60 @@
-%fieldset.alpha.no-border-bottom{ ng: { show: "menu.selected.name=='Primary Details'" } }
- %legend Primary Details
+%fieldset.alpha.no-border-bottom{ ng: { show: "menu.selected.name=='primary_details'" } }
+ %legend {{menu.selected.label}}
= render 'admin/enterprises/form/primary_details', f: f
-%fieldset.alpha.no-border-bottom{ ng: { show: "menu.selected.name=='Users'" } }
- %legend Users
+%fieldset.alpha.no-border-bottom{ ng: { show: "menu.selected.name=='users'" } }
+ %legend {{menu.selected.label}}
= render 'admin/enterprises/form/users', f: f
= f.fields_for :address do |af|
- %fieldset.alpha.no-border-bottom{ ng: { show: "menu.selected.name=='Address'" } }
- %legend Address
+ %fieldset.alpha.no-border-bottom{ ng: { show: "menu.selected.name=='address'" } }
+ %legend {{menu.selected.label}}
= render 'admin/enterprises/form/address', af: af
-%fieldset.alpha.no-border-bottom{ ng: { show: "menu.selected.name=='Contact'" } }
- %legend Contact
+%fieldset.alpha.no-border-bottom{ ng: { show: "menu.selected.name=='contact'" } }
+ %legend {{menu.selected.label}}
= render 'admin/enterprises/form/contact', f: f
-%fieldset.alpha.no-border-bottom{ ng: { show: "menu.selected.name=='Social'" } }
- %legend Social
+%fieldset.alpha.no-border-bottom{ ng: { show: "menu.selected.name=='social'" } }
+ %legend {{menu.selected.label}}
= render 'admin/enterprises/form/social', f: f
-%fieldset.alpha.no-border-bottom{ ng: { show: "menu.selected.name=='Business Details'" } }
- %legend Business Details
+%fieldset.alpha.no-border-bottom{ ng: { show: "menu.selected.name=='business_details'" } }
+ %legend {{menu.selected.label}}
= render 'admin/enterprises/form/business_details', f: f
-%fieldset.alpha.no-border-bottom{ ng: { show: "menu.selected.name=='About'" } }
- %legend About Us
+%fieldset.alpha.no-border-bottom{ ng: { show: "menu.selected.name=='about'" } }
+ %legend {{menu.selected.label}}
= render 'admin/enterprises/form/about_us', f: f
-%fieldset.alpha.no-border-bottom{ ng: { show: "menu.selected.name=='Images'" } }
- %legend Images
+%fieldset.alpha.no-border-bottom{ ng: { show: "menu.selected.name=='images'" } }
+ %legend {{menu.selected.label}}
= render 'admin/enterprises/form/images', f: f
-%fieldset.alpha.no-border-bottom{ ng: { show: "menu.selected.name=='Properties'" } }
- %legend Properties
+%fieldset.alpha.no-border-bottom{ ng: { show: "menu.selected.name=='properties'" } }
+ %legend {{menu.selected.label}}
= render 'admin/enterprises/form/properties', f: f
-%fieldset.alpha.no-border-bottom{ ng: { show: "menu.selected.name=='Shipping Methods'" } }
- %legend Shipping Methods
+%fieldset.alpha.no-border-bottom{ ng: { show: "menu.selected.name=='shipping_methods'" } }
+ %legend {{menu.selected.label}}
= render 'admin/enterprises/form/shipping_methods', f: f
-%fieldset.alpha.no-border-bottom{ ng: { show: "menu.selected.name=='Payment Methods'" } }
- %legend Payment Methods
+%fieldset.alpha.no-border-bottom{ ng: { show: "menu.selected.name=='payment_methods'" } }
+ %legend {{menu.selected.label}}
= render 'admin/enterprises/form/payment_methods', f: f
-%fieldset.alpha.no-border-bottom{ ng: { show: "menu.selected.name=='Enterprise Fees'" } }
- %legend Enterprise Fees
+%fieldset.alpha.no-border-bottom{ ng: { show: "menu.selected.name=='enterprise_fees'" } }
+ %legend {{menu.selected.label}}
= render 'admin/enterprises/form/enterprise_fees', f: f
-%fieldset.alpha.no-border-bottom{ ng: { show: "menu.selected.name=='Inventory Settings'" } }
- %legend Inventory Settings
+%fieldset.alpha.no-border-bottom{ ng: { show: "menu.selected.name=='inventory_settings'" } }
+ %legend {{menu.selected.label}}
= render 'admin/enterprises/form/inventory_settings', f: f
-%fieldset.alpha.no-border-bottom{ ng: { show: "menu.selected.name=='Shop Preferences'" } }
- %legend Shop Preferences
+%fieldset.alpha.no-border-bottom{ ng: { show: "menu.selected.name=='shop_preferences'" } }
+ %legend {{menu.selected.label}}
= render 'admin/enterprises/form/shop_preferences', f: f
-%fieldset.alpha.no-border-bottom{ ng: { if: "menu.selected.name=='Tag Rules'" } }
- %legend Tag Rules
+%fieldset.alpha.no-border-bottom{ ng: { if: "menu.selected.name=='tag_rules'" } }
+ %legend {{menu.selected.label}}
= render 'admin/enterprises/form/tag_rules', f: f
diff --git a/app/views/admin/enterprises/form/_payment_methods.html.haml b/app/views/admin/enterprises/form/_payment_methods.html.haml
index 8857980bd9..57d9426d03 100644
--- a/app/views/admin/enterprises/form/_payment_methods.html.haml
+++ b/app/views/admin/enterprises/form/_payment_methods.html.haml
@@ -7,7 +7,7 @@
%th
%tbody
- @payment_methods.each do |payment_method|
- %tr{ ng: { controller: 'paymentMethodCtrl', init: "findPaymentMethodByID(#{payment_method.id})" } }
+ %tr{ ng: { controller: 'paymentMethodsCtrl', init: "findPaymentMethodByID(#{payment_method.id})" } }
%td= payment_method.name
%td= f.check_box :payment_method_ids, { multiple: true, 'ng-model' => 'PaymentMethod.selected' }, payment_method.id, nil
%td= link_to "Edit", edit_admin_payment_method_path(payment_method)
@@ -30,4 +30,4 @@
.text-center
%a.button{ href: "#{new_admin_payment_method_path}"}
Create One Now
- %i.icon-arrow-right
\ No newline at end of file
+ %i.icon-arrow-right
diff --git a/app/views/admin/enterprises/form/_tag_rules.html.haml b/app/views/admin/enterprises/form/_tag_rules.html.haml
index 1a50e4f353..e58f7c5a62 100644
--- a/app/views/admin/enterprises/form/_tag_rules.html.haml
+++ b/app/views/admin/enterprises/form/_tag_rules.html.haml
@@ -1,9 +1,11 @@
.row{ ng: { controller: "TagRulesCtrl" } }
.eleven.columns.alpha.omega
- .eleven.columns.alpha.omega
+ %ofn-sortable{ axis: "y", handle: ".header", items: '.customer_tag', position: "tagGroup.position", after: { sort: "updateRuleCounts()" } }
.no_tags{ ng: { show: "tagGroups.length == 0" } }
No tags apply to this enterprise yet
- .customer_tag{ ng: { repeat: "tagGroup in tagGroups" }, bindonce: true }
+ = render 'admin/enterprises/form/tag_rules/default_rules'
+ -# = render 'customer_tags'
+ .customer_tag{ id: "tg_{{tagGroup.position}}", ng: { repeat: "tagGroup in tagGroups" } }
.header
%table
%colgroup
@@ -14,20 +16,12 @@
%h5
For customers tagged:
%td
- %tags-input{ ng: { model: 'tagGroup.tags'},
- min: { tags: "1" },
- on: { tag: { added: "updateTagsRulesFor(tagGroup)", removed: "updateTagsRulesFor(tagGroup)" } } }
+ %tags-with-translation{ object: "tagGroup", max: 1, on: { tag: { added: "updateTagsRulesFor(tagGroup)", removed: "updateTagsRulesFor(tagGroup)" } } }
.no_rules{ ng: { show: "tagGroup.rules.length == 0" } }
No rules apply to this tag yet
- %table
- %tr.tag_rule{ id: "tr_{{rule.id}}", ng: { repeat: "rule in tagGroup.rules" } }
- %td
- %discount-order{ bo: { if: "rule.type == 'TagRule::DiscountOrder'" } }
- %filter-shipping-methods{ bo: { if: "rule.type == 'TagRule::FilterShippingMethods'" } }
- %td.actions
- %a{ ng: { click: "deleteTagRule(tagGroup, rule)" }, :class => "delete-tag-rule icon-trash no-text" }
+ .tag_rule{ ng: { repeat: "rule in tagGroup.rules" } }
.add_rule.text-center
- %input.button.icon-plus{ type: 'button', value: "+ Add A New Rule", "new-tag-rule-dialog" => true }
- .add_tage
+ %input.button.icon-plus{ type: 'button', value: "+ Add A New Rule", "add-new-rule-to" => "addNewRuleTo", "tag-group" => "tagGroup", "new-tag-rule-dialog" => true }
+ .add_tag
%input.button.red.icon-plus{ type: 'button', value: "+ Add A New Tag", ng: { click: 'addNewTag()' } }
diff --git a/app/views/admin/enterprises/form/tag_rules/_default_rules.html.haml b/app/views/admin/enterprises/form/tag_rules/_default_rules.html.haml
new file mode 100644
index 0000000000..09c78f9c8c
--- /dev/null
+++ b/app/views/admin/enterprises/form/tag_rules/_default_rules.html.haml
@@ -0,0 +1,15 @@
+.default_rules
+ .header
+ %table
+ %colgroup
+ %col{width: '100%'}
+ %tr
+ %td
+ %h5
+ By Default
+ %i.text-big.icon-question-sign.help-modal{ template: 'admin/modals/tag_rule_help.html' }
+ .no_rules{ ng: { show: "defaultTagGroup.rules.length == 0" } }
+ No default rules apply yet
+ .tag_rule{ ng: { repeat: "rule in defaultTagGroup.rules" } }
+ .add_rule.text-center
+ %input.button.icon-plus{ type: 'button', value: "+ Add A New Default Rule", "add-new-rule-to" => "addNewRuleTo", "tag-group" => "defaultTagGroup", "new-tag-rule-dialog" => true }
diff --git a/app/views/admin/enterprises/index.html.haml b/app/views/admin/enterprises/index.html.haml
index 62bc881728..b84659738c 100644
--- a/app/views/admin/enterprises/index.html.haml
+++ b/app/views/admin/enterprises/index.html.haml
@@ -9,6 +9,8 @@
= button_link_to "New Enterprise", main_app.new_admin_enterprise_path, :icon => 'icon-plus', :id => 'admin_new_enterprise_link'
= admin_inject_monthly_bill_description
+= admin_inject_column_preferences module: 'admin.enterprises', action: "enterprises_index"
+
= render 'admin/shared/enterprises_sub_menu'
= render :partial => 'spree/shared/error_messages', :locals => { :target => @enterprise_set }
diff --git a/app/views/admin/order_cycles/_advanced_settings.html.haml b/app/views/admin/order_cycles/_advanced_settings.html.haml
index 27b347880b..f8f71d0b93 100644
--- a/app/views/admin/order_cycles/_advanced_settings.html.haml
+++ b/app/views/admin/order_cycles/_advanced_settings.html.haml
@@ -5,7 +5,7 @@
= form_for [main_app, :admin, @order_cycle] do |f|
.row
.six.columns.alpha
- = f.label "enterprise_preferred_product_selection_from_coordinator_inventory_only", t('admin.order_cycle.choose_products_from')
+ = f.label "enterprise_preferred_product_selection_from_coordinator_inventory_only", t('admin.order_cycles.edit.choose_products_from')
.with-tip{'data-powertip' => "You can opt to restrict all available products (both incoming and outgoing), to only those in #{@order_cycle.coordinator.name}'s inventory."}
%a What's this?
.four.columns
diff --git a/app/views/admin/order_cycles/_exchange_distributed_products_form.html.haml b/app/views/admin/order_cycles/_exchange_distributed_products_form.html.haml
deleted file mode 100644
index e2218599ce..0000000000
--- a/app/views/admin/order_cycles/_exchange_distributed_products_form.html.haml
+++ /dev/null
@@ -1,23 +0,0 @@
-%td{:colspan => 4}
- .exchange-select-all-variants
- %label
- = check_box_tag 'order_cycle_outgoing_exchange_{{ $parent.$index }}_select_all_variants', 1, 1, 'ng-model' => 'exchange.select_all_variants', 'ng-change' => 'setExchangeVariants(exchange, incomingExchangeVariantsFor(exchange.enterprise_id), exchange.select_all_variants)', 'id' => 'order_cycle_outgoing_exchange_{{ $parent.$index }}_select_all_variants'
- Select all
-
- .exchange-products
- -# Scope product list based on permissions the current user has to view variants in this exchange
- .exchange-product{'ng-repeat' => 'product in supplied_products | filter:productSuppliedToOrderCycle | visibleProducts:exchange:order_cycle.visible_variants_for_outgoing_exchanges | orderBy:"name"' }
- .exchange-product-details
- %label
- -# MASTER_VARIANTS: No longer required
- -# = check_box_tag 'order_cycle_outgoing_exchange_{{ $parent.$index }}_variants_{{ product.master_id }}', 1, 1, 'ng-hide' => 'product.variants.length > 0', 'ng-model' => 'exchange.variants[product.master_id]', 'id' => 'order_cycle_outgoing_exchange_{{ $parent.$index }}_variants_{{ product.master_id }}',
- -# 'ng-disabled' => 'product.variants.length > 0 || !order_cycle.editable_variants_for_outgoing_exchanges.hasOwnProperty(exchange.enterprise_id) || order_cycle.editable_variants_for_outgoing_exchanges[exchange.enterprise_id].indexOf(product.master_id) < 0'
- %img{'ng-src' => '{{ product.image_url }}'}
- .name {{ product.name }}
- .supplier {{ product.supplier_name }}
-
- .exchange-product-variant{'ng-repeat' => 'variant in product.variants | visibleVariants:exchange:order_cycle.visible_variants_for_outgoing_exchanges | filter:variantSuppliedToOrderCycle'}
- %label
- = check_box_tag 'order_cycle_outgoing_exchange_{{ $parent.$parent.$index }}_variants_{{ variant.id }}', 1, 1, 'ng-model' => 'exchange.variants[variant.id]', 'id' => 'order_cycle_outgoing_exchange_{{ $parent.$parent.$index }}_variants_{{ variant.id }}',
- 'ng-disabled' => '!order_cycle.editable_variants_for_outgoing_exchanges.hasOwnProperty(exchange.enterprise_id) || order_cycle.editable_variants_for_outgoing_exchanges[exchange.enterprise_id].indexOf(variant.id) < 0'
- {{ variant.label }}
diff --git a/app/views/admin/order_cycles/_exchange_form.html.haml b/app/views/admin/order_cycles/_exchange_form.html.haml
index ed3e43fd6b..0e3dbb4340 100644
--- a/app/views/admin/order_cycles/_exchange_form.html.haml
+++ b/app/views/admin/order_cycles/_exchange_form.html.haml
@@ -1,29 +1,42 @@
-%td{:class => "#{type}_name"} {{ enterprises[exchange.enterprise_id].name }}
-%td.products
- = f.submit 'Products', 'ng-click' => 'toggleProducts($event, exchange)'
- {{ exchangeSelectedVariants(exchange) }} /
+%tr{ ng: { class: "'#{type} #{type}-{{ exchange.enterprise_id }}'" } }
+ %td{:class => "#{type}_name"} {{ enterprises[exchange.enterprise_id].name }}
+ %td.products.panel-toggle.text-center{ name: "products" }
+ {{ exchangeSelectedVariants(exchange) }} /
+ - if type == 'supplier'
+ {{ enterpriseTotalVariants(enterprises[exchange.enterprise_id]) }}
+ - else
+ {{ (incomingExchangeVariantsFor(exchange.enterprise_id)).length }}
+ selected
- if type == 'supplier'
- {{ enterpriseTotalVariants(enterprises[exchange.enterprise_id]) }}
- - else
- {{ (incomingExchangeVariantsFor(exchange.enterprise_id)).length }}
- selected
+ %td.receival-details
+ = text_field_tag 'order_cycle_incoming_exchange_{{ $index }}_receival_instructions', '', 'id' => 'order_cycle_incoming_exchange_{{ $index }}_receival_instructions', 'placeholder' => 'Receival instructions', 'ng-model' => 'exchange.receival_instructions'
+ - if type == 'distributor'
+ %td.tags.panel-toggle.text-center{ name: "tags", ng: { if: 'enterprises[exchange.enterprise_id].managed || order_cycle.viewing_as_coordinator' } }
+ {{ exchange.tags.length }}
+ %td.collection-details
+ = text_field_tag 'order_cycle_outgoing_exchange_{{ $index }}_pickup_time', '', 'id' => 'order_cycle_outgoing_exchange_{{ $index }}_pickup_time', 'placeholder' => 'Ready for (ie. Date / Time)', 'ng-model' => 'exchange.pickup_time', 'ng-disabled' => '!enterprises[exchange.enterprise_id].managed && !order_cycle.viewing_as_coordinator'
+ %br/
+ = text_field_tag 'order_cycle_outgoing_exchange_{{ $index }}_pickup_instructions', '', 'id' => 'order_cycle_outgoing_exchange_{{ $index }}_pickup_instructions', 'placeholder' => 'Pick-up instructions', 'ng-model' => 'exchange.pickup_instructions', 'ng-disabled' => '!enterprises[exchange.enterprise_id].managed && !order_cycle.viewing_as_coordinator'
+ %td.fees
+ %ol{ ng: { show: 'enterprises[exchange.enterprise_id].managed || order_cycle.viewing_as_coordinator' } }
+ %li{'ng-repeat' => 'enterprise_fee in exchange.enterprise_fees'}
+ = select_tag 'order_cycle_{{ exchangeDirection(exchange) }}_exchange_{{ $parent.$index }}_enterprise_fees_{{ $index }}_enterprise_id', nil, {'id' => 'order_cycle_{{ exchangeDirection(exchange) }}_exchange_{{ $parent.$index }}_enterprise_fees_{{ $index }}_enterprise_id', 'ng-model' => 'enterprise_fee.enterprise_id', 'ng-options' => 'enterprise.id as enterprise.name for enterprise in enterprisesWithFees()'}
+
+ = select_tag 'order_cycle_{{ exchangeDirection(exchange) }}_exchange_{{ $parent.$index }}_enterprise_fees_{{ $index }}_enterprise_fee_id', nil, {'id' => 'order_cycle_{{ exchangeDirection(exchange) }}_exchange_{{ $parent.$index }}_enterprise_fees_{{ $index }}_enterprise_fee_id', 'ng-model' => 'enterprise_fee.id', 'ng-options' => 'enterprise_fee.id as enterprise_fee.name for enterprise_fee in enterpriseFeesForEnterprise(enterprise_fee.enterprise_id)'}
+
+ = link_to 'Remove', '#', {'id' => 'order_cycle_{{ exchangeDirection(exchange) }}_exchange_{{ $parent.$index }}_enterprise_fees_{{ $index }}_remove', 'ng-click' => 'removeExchangeFee($event, exchange, $index)'}
+
+ = f.submit 'Add fee', 'ng-click' => 'addExchangeFee($event, exchange)', 'ng-hide' => '!enterprises[exchange.enterprise_id].managed && !order_cycle.viewing_as_coordinator'
+ %td.actions
+ %a{'ng-click' => 'removeExchange($event, exchange)', :class => "icon-trash no-text remove-exchange"}
+
- if type == 'supplier'
- %td.receival-details
- = text_field_tag 'order_cycle_incoming_exchange_{{ $index }}_receival_instructions', '', 'id' => 'order_cycle_incoming_exchange_{{ $index }}_receival_instructions', 'placeholder' => 'Receival instructions', 'ng-model' => 'exchange.receival_instructions'
+ %tr.panel-row{ object: "exchange",
+ panels: "{products: 'exchange_supplied_products'}",
+ locals: "$index,order_cycle,exchange,enterprises,setExchangeVariants,suppliedVariants,removeDistributionOfVariant",
+ colspan: 4 }
- if type == 'distributor'
- %td.collection-details
- = text_field_tag 'order_cycle_outgoing_exchange_{{ $index }}_pickup_time', '', 'id' => 'order_cycle_outgoing_exchange_{{ $index }}_pickup_time', 'placeholder' => 'Ready for (ie. Date / Time)', 'ng-model' => 'exchange.pickup_time', 'ng-disabled' => '!enterprises[exchange.enterprise_id].managed && !order_cycle.viewing_as_coordinator'
- %br/
- = text_field_tag 'order_cycle_outgoing_exchange_{{ $index }}_pickup_instructions', '', 'id' => 'order_cycle_outgoing_exchange_{{ $index }}_pickup_instructions', 'placeholder' => 'Pick-up instructions', 'ng-model' => 'exchange.pickup_instructions', 'ng-disabled' => '!enterprises[exchange.enterprise_id].managed && !order_cycle.viewing_as_coordinator'
-%td.fees
- %ol{ ng: { show: 'enterprises[exchange.enterprise_id].managed || order_cycle.viewing_as_coordinator' } }
- %li{'ng-repeat' => 'enterprise_fee in exchange.enterprise_fees'}
- = select_tag 'order_cycle_{{ exchangeDirection(exchange) }}_exchange_{{ $parent.$index }}_enterprise_fees_{{ $index }}_enterprise_id', nil, {'id' => 'order_cycle_{{ exchangeDirection(exchange) }}_exchange_{{ $parent.$index }}_enterprise_fees_{{ $index }}_enterprise_id', 'ng-model' => 'enterprise_fee.enterprise_id', 'ng-options' => 'enterprise.id as enterprise.name for enterprise in enterprisesWithFees()'}
-
- = select_tag 'order_cycle_{{ exchangeDirection(exchange) }}_exchange_{{ $parent.$index }}_enterprise_fees_{{ $index }}_enterprise_fee_id', nil, {'id' => 'order_cycle_{{ exchangeDirection(exchange) }}_exchange_{{ $parent.$index }}_enterprise_fees_{{ $index }}_enterprise_fee_id', 'ng-model' => 'enterprise_fee.id', 'ng-options' => 'enterprise_fee.id as enterprise_fee.name for enterprise_fee in enterpriseFeesForEnterprise(enterprise_fee.enterprise_id)'}
-
- = link_to 'Remove', '#', {'id' => 'order_cycle_{{ exchangeDirection(exchange) }}_exchange_{{ $parent.$index }}_enterprise_fees_{{ $index }}_remove', 'ng-click' => 'removeExchangeFee($event, exchange, $index)'}
-
- = f.submit 'Add fee', 'ng-click' => 'addExchangeFee($event, exchange)', 'ng-hide' => '!enterprises[exchange.enterprise_id].managed && !order_cycle.viewing_as_coordinator'
-%td.actions
- %a{'ng-click' => 'removeExchange($event, exchange)', :class => "icon-trash no-text remove-exchange"}
+ %tr.panel-row{ object: "exchange",
+ panels: "{products: 'exchange_distributed_products', tags: 'exchange_tags'}",
+ locals: "$index,order_cycle,exchange,supplied_products,setExchangeVariants,incomingExchangeVariantsFor,productSuppliedToOrderCycle,variantSuppliedToOrderCycle",
+ colspan: 5 }
diff --git a/app/views/admin/order_cycles/_form.html.haml b/app/views/admin/order_cycles/_form.html.haml
index ed2d3acbc2..a0f0a178c8 100644
--- a/app/views/admin/order_cycles/_form.html.haml
+++ b/app/views/admin/order_cycles/_form.html.haml
@@ -17,11 +17,8 @@
%th Receival details
%th Fees
%th.actions
- %tbody{'ng-repeat' => 'exchange in order_cycle.incoming_exchanges'}
- %tr{'class' => "supplier supplier-{{ exchange.enterprise_id }}"}
- = render 'exchange_form', :f => f, :type => 'supplier'
- %tr.products{'ng-show' => 'exchange.showProducts'}
- = render 'exchange_supplied_products_form'
+ %tbody.panel-ctrl{ object: 'exchange', 'ng-repeat' => 'exchange in order_cycle.incoming_exchanges'}
+ = render 'exchange_form', :f => f, :type => 'supplier'
- if Enterprise.managed_by(spree_current_user).include? @order_cycle.coordinator
= render 'add_exchange_form', f: f, type: 'supplier'
@@ -37,24 +34,21 @@
%a{href: '#', 'ng-click' => "OrderCycle.toggleAllProducts('outgoing')"}
%span{'ng-show' => "OrderCycle.showProducts['outgoing']"} Collapse all
%span{'ng-hide' => "OrderCycle.showProducts['outgoing']"} Expand all
+ %th{ ng: { if: 'enterprises[exchange.enterprise_id].managed || order_cycle.viewing_as_coordinator' } } Tags
%th Pickup / Delivery details
%th Fees
%th.actions
- %tbody{'ng-repeat' => 'exchange in order_cycle.outgoing_exchanges'}
- %tr{'class' => "distributor distributor-{{ exchange.enterprise_id }}"}
- = render 'exchange_form', :f => f, :type => 'distributor'
- %tr.products{'ng-show' => 'exchange.showProducts'}
- = render 'exchange_distributed_products_form'
+ %tbody.panel-ctrl{ object: 'exchange', 'ng-repeat' => 'exchange in order_cycle.outgoing_exchanges'}
+ = render 'exchange_form', :f => f, :type => 'distributor'
+
- if Enterprise.managed_by(spree_current_user).include? @order_cycle.coordinator
= render 'add_exchange_form', f: f, type: 'distributor'
.actions
- if @order_cycle.new_record?
- = f.submit 'Create', 'ng-click' => "submit('#{main_app.admin_order_cycles_path}')", 'ng-disabled' => '!loaded()'
+ = f.submit 'Create', 'ng-click' => "submit($event, '#{main_app.admin_order_cycles_path}')", 'ng-disabled' => '!loaded()'
- %span{'ng-show' => 'loaded()'}
- = link_to 'Cancel', main_app.admin_order_cycles_path
%span{'ng-hide' => 'loaded()'} Loading...
diff --git a/app/views/admin/order_cycles/_name_and_timing_form.html.haml b/app/views/admin/order_cycles/_name_and_timing_form.html.haml
index 703d8094b9..39fa19f287 100644
--- a/app/views/admin/order_cycles/_name_and_timing_form.html.haml
+++ b/app/views/admin/order_cycles/_name_and_timing_form.html.haml
@@ -3,14 +3,14 @@
= f.label :name
.six.columns.omega
- if viewing_as_coordinator_of?(@order_cycle)
- = f.text_field :name, 'ng-model' => 'order_cycle.name', 'required' => true
+ = f.text_field :name, 'ng-model' => 'order_cycle.name', 'required' => true, 'ng-disabled' => '!loaded()'
- else
{{ order_cycle.name }}
.two.columns
= f.label :orders_open_at, 'Orders open'
.omega.six.columns
- if viewing_as_coordinator_of?(@order_cycle)
- = f.text_field :orders_open_at, 'datetimepicker' => 'order_cycle.orders_open_at', 'ng-model' => 'order_cycle.orders_open_at'
+ = f.text_field :orders_open_at, 'datetimepicker' => 'order_cycle.orders_open_at', 'ng-model' => 'order_cycle.orders_open_at', 'ng-disabled' => '!loaded()'
- else
{{ order_cycle.orders_open_at }}
@@ -23,6 +23,6 @@
= f.label :orders_close_at, 'Orders close'
.six.columns.omega
- if viewing_as_coordinator_of?(@order_cycle)
- = f.text_field :orders_close_at, 'datetimepicker' => 'order_cycle.orders_close_at', 'ng-model' => 'order_cycle.orders_close_at'
+ = f.text_field :orders_close_at, 'datetimepicker' => 'order_cycle.orders_close_at', 'ng-model' => 'order_cycle.orders_close_at', 'ng-disabled' => '!loaded()'
- else
{{ order_cycle.orders_close_at }}
diff --git a/app/views/admin/order_cycles/_simple_form.html.haml b/app/views/admin/order_cycles/_simple_form.html.haml
index 9364d080ff..6bb0034c73 100644
--- a/app/views/admin/order_cycles/_simple_form.html.haml
+++ b/app/views/admin/order_cycles/_simple_form.html.haml
@@ -14,7 +14,7 @@
%table.exchanges
%tbody{ng: {repeat: "exchange in order_cycle.incoming_exchanges"}}
%tr.products
- = render 'exchange_supplied_products_form'
+ %td{ ng: { include: "'admin/panels/exchange_supplied_products.html'" } }
%br/
= label_tag 'Fees'
@@ -22,8 +22,6 @@
.actions
- if @order_cycle.new_record?
- = f.submit 'Create', 'ng-click' => "submit('#{main_app.admin_order_cycles_path}')", 'ng-disabled' => '!loaded()'
+ = f.submit 'Create', 'ng-click' => "submit($event, '#{main_app.admin_order_cycles_path}')", 'ng-disabled' => '!loaded()'
- %span{'ng-show' => 'loaded()'}
- = link_to 'Cancel', main_app.admin_order_cycles_path
%span{'ng-hide' => 'loaded()'} Loading...
diff --git a/app/views/admin/order_cycles/edit.html.haml b/app/views/admin/order_cycles/edit.html.haml
index 9f85261224..4712566dfd 100644
--- a/app/views/admin/order_cycles/edit.html.haml
+++ b/app/views/admin/order_cycles/edit.html.haml
@@ -29,7 +29,10 @@
- ng_controller = order_cycles_simple_form ? 'AdminSimpleEditOrderCycleCtrl' : 'AdminEditOrderCycleCtrl'
= form_for [main_app, :admin, @order_cycle], :url => '', :html => {:class => 'ng order_cycle', 'ng-app' => 'admin.orderCycles', 'ng-controller' => ng_controller, name: 'order_cycle_form'} do |f|
- %save-bar{ buttons: "[{ text: 'Update', action: submit, param: null, class: 'red' }, { text: 'Update and Close', action: submit, param: '#{main_app.admin_order_cycles_path}', class: 'red' }, { text: 'Cancel', action: cancel, param: '#{main_app.admin_order_cycles_path}', class: '' }]", form: "order_cycle_form" }
+ %save-bar{ dirty: "order_cycle_form.$dirty", persist: "true" }
+ %input.red{ type: "button", value: "Update", ng: { click: "submit($event, null)", disabled: "!order_cycle_form.$dirty" } }
+ %input.red{ type: "button", value: "Update and Close", ng: { click: "submit($event, '#{main_app.admin_order_cycles_path}')", disabled: "!order_cycle_form.$dirty" } }
+ %input{ type: "button", ng: { value: "order_cycle_form.$dirty ? 'Cancel' : 'Close'", click: "cancel('#{main_app.admin_order_cycles_path}')" } }
- if order_cycles_simple_form
= render 'simple_form', f: f
diff --git a/app/views/admin/order_cycles/set_coordinator.html.haml b/app/views/admin/order_cycles/set_coordinator.html.haml
index db9bbb926c..f4c06e0a98 100644
--- a/app/views/admin/order_cycles/set_coordinator.html.haml
+++ b/app/views/admin/order_cycles/set_coordinator.html.haml
@@ -1,5 +1,5 @@
%h4.text-center
- =t'select_a_coordinator_for_your_order_cycle'
+ =t 'select_a_coordinator_for_your_order_cycle'
%br
diff --git a/app/views/admin/shared/_columns_dropdown.html.haml b/app/views/admin/shared/_columns_dropdown.html.haml
deleted file mode 100644
index 4663443321..0000000000
--- a/app/views/admin/shared/_columns_dropdown.html.haml
+++ /dev/null
@@ -1,7 +0,0 @@
-.ofn-drop-down.right#columns-dropdown
- %span{ :class => 'icon-reorder' }= " #{t('admin.columns')}".html_safe
- %span{ 'ng-class' => "expanded && 'icon-caret-up' || !expanded && 'icon-caret-down'" }
- %div.menu{ 'ng-show' => "expanded" }
- %div.menu_item{ ng: { repeat: "column in columns" }, toggle: { column: true } }
- %span.check
- %span.name {{column.name }}
diff --git a/app/views/admin/shared/_side_menu.html.haml b/app/views/admin/shared/_side_menu.html.haml
index cf1d3d42f7..267a341991 100644
--- a/app/views/admin/shared/_side_menu.html.haml
+++ b/app/views/admin/shared/_side_menu.html.haml
@@ -5,4 +5,4 @@
show: '!showItem || showItem(item)',
class: '{ selected: item.selected }' } }
%i{ class: "{{item.icon_class}}" }
- %span {{ item.name }}
+ %span {{ item.label }}
diff --git a/app/views/admin/variant_overrides/_controls.html.haml b/app/views/admin/variant_overrides/_controls.html.haml
index 4a25677be3..900f46e430 100644
--- a/app/views/admin/variant_overrides/_controls.html.haml
+++ b/app/views/admin/variant_overrides/_controls.html.haml
@@ -3,13 +3,13 @@
.eight.columns.alpha
= render 'admin/shared/bulk_actions_dropdown'
= render 'admin/shared/views_dropdown'
- %span.text-big.with-tip.icon-question-sign{ ng: { show: 'views.inventory.visible' } , data: { powertip: "#{t('admin.inventory.inventory_powertip')}" } }
- %span.text-big.with-tip.icon-question-sign{ ng: { show: 'views.hidden.visible' } , data: { powertip: "#{t('admin.inventory.hidden_powertip')}" } }
- %span.text-big.with-tip.icon-question-sign{ ng: { show: 'views.new.visible' } , data: { powertip: "#{t('admin.inventory.new_powertip')}" } }
+ %span.text-big.with-tip.icon-question-sign{ ng: { show: 'views.inventory.visible' } , data: { powertip: "#{t('admin.variant_overrides.index.inventory_powertip')}" } }
+ %span.text-big.with-tip.icon-question-sign{ ng: { show: 'views.hidden.visible' } , data: { powertip: "#{t('admin.variant_overrides.index.hidden_powertip')}" } }
+ %span.text-big.with-tip.icon-question-sign{ ng: { show: 'views.new.visible' } , data: { powertip: "#{t('admin.variant_overrides.index.new_powertip')}" } }
.four.columns
.four.columns.omega{ ng: { show: 'views.new.visible' } }
%button.fullwidth{ type: 'button', ng: { click: "selectView('inventory')" } }
%i.icon-chevron-left
Back to my inventory
.four.columns.omega{ng: { show: 'views.inventory.visible' } }
- = render 'admin/shared/columns_dropdown'
+ %columns-dropdown{ action: "#{controller_name}_#{action_name}" }
diff --git a/app/views/admin/variant_overrides/_data.html.haml b/app/views/admin/variant_overrides/_data.html.haml
index 9d371415fe..02c52d747d 100644
--- a/app/views/admin/variant_overrides/_data.html.haml
+++ b/app/views/admin/variant_overrides/_data.html.haml
@@ -2,5 +2,6 @@
= admin_inject_hubs module: 'admin.variantOverrides'
= admin_inject_hub_permissions
= admin_inject_producers module: 'admin.variantOverrides'
+= admin_inject_column_preferences module: 'admin.variantOverrides'
= admin_inject_variant_overrides
= admin_inject_inventory_items module: 'admin.variantOverrides'
diff --git a/app/views/admin/variant_overrides/_filters.html.haml b/app/views/admin/variant_overrides/_filters.html.haml
index c9f3a27b32..a1772ed222 100644
--- a/app/views/admin/variant_overrides/_filters.html.haml
+++ b/app/views/admin/variant_overrides/_filters.html.haml
@@ -5,7 +5,7 @@
%input.fullwidth{ :type => "text", :id => 'query', ng: { model: 'query', disabled: '!hub_id'} }
.two.columns
.filter_select.four.columns
- %label{ :for => 'hub_id', ng: { bind: "hub_id ? '#{t('admin.shop')}' : '#{t('admin.inventory.select_a_shop')}'" } }
+ %label{ :for => 'hub_id', ng: { bind: "hub_id ? '#{t('admin.shop')}' : '#{t('admin.variant_overrides.index.select_a_shop')}'" } }
%br
%select.select2.fullwidth#hub_id{ 'ng-model' => 'hub_id', name: 'hub_id', ng: { options: 'hub.id as hub.name for (id, hub) in hubs' } }
.filter_select.four.columns
diff --git a/app/views/admin/variant_overrides/_header.html.haml b/app/views/admin/variant_overrides/_header.html.haml
index e4a8e98420..64a6bfb201 100644
--- a/app/views/admin/variant_overrides/_header.html.haml
+++ b/app/views/admin/variant_overrides/_header.html.haml
@@ -1,8 +1,8 @@
- content_for :html_title do
- = t("admin.inventory.title")
+ = t("admin.variant_overrides.index.title")
- content_for :page_title do
- %h1.page-title= t("admin.inventory.title")
- %a.with-tip{ 'data-powertip' => "#{t("admin.inventory.description")}" }=t('admin.whats_this')
+ %h1.page-title= t("admin.variant_overrides.index.title")
+ %a.with-tip{ 'data-powertip' => "#{t("admin.variant_overrides.index.description")}" }=t('admin.whats_this')
= render :partial => 'spree/admin/shared/product_sub_menu'
diff --git a/app/views/admin/variant_overrides/_hidden_products.html.haml b/app/views/admin/variant_overrides/_hidden_products.html.haml
index 902e24a232..ed3e2555c8 100644
--- a/app/views/admin/variant_overrides/_hidden_products.html.haml
+++ b/app/views/admin/variant_overrides/_hidden_products.html.haml
@@ -9,14 +9,14 @@
%th.producer=t('admin.producer')
%th.product=t('admin.product')
%th.variant=t('(admin.variant')
- %th.add=t('admin.inventory.add')
- %tbody{ bindonce: true, ng: { repeat: 'product in filteredProducts | limitTo:productLimit' } }
+ %th.add=t('admin.variant_overrides.index.add')
+ %tbody{ ng: { repeat: 'product in filteredProducts | limitTo:productLimit' } }
%tr{ id: "v_{{variant.id}}", ng: { repeat: 'variant in product.variants | inventoryVariants:hub_id:views' } }
- %td.producer{ bo: { bind: 'producersByID[product.producer_id].name'} }
- %td.product{ bo: { bind: 'product.name'} }
+ %td.producer{ ng: { bind: '::producersByID[product.producer_id].name'} }
+ %td.product{ ng: { bind: '::product.name'} }
%td.variant
- %span{ bo: { bind: 'variant.display_name || ""'} }
- .variant-override-unit{ bo: { bind: 'variant.unit_to_display'} }
+ %span{ ng: { bind: '::variant.display_name || ""'} }
+ .variant-override-unit{ ng: { bind: '::variant.unit_to_display'} }
%td.add
%button.fullwidth.icon-plus{ ng: { click: "setVisibility(hub_id,variant.id,true)" } }
- = t('admin.inventory.add')
+ = t('admin.variant_overrides.index.add')
diff --git a/app/views/admin/variant_overrides/_new_products.html.haml b/app/views/admin/variant_overrides/_new_products.html.haml
index 86ca180b8f..99827717f5 100644
--- a/app/views/admin/variant_overrides/_new_products.html.haml
+++ b/app/views/admin/variant_overrides/_new_products.html.haml
@@ -8,19 +8,19 @@
%tr
%th.producer=t('admin.producer')
%th.product=t('admin.product')
- %th.variant=t('(admin.variant')
- %th.add=t('admin.inventory.add')
- %th.hide=t('admin.inventory.hide')
- %tbody{ bindonce: true, ng: { repeat: 'product in filteredProducts | limitTo:productLimit' } }
+ %th.variant=t('admin.variant')
+ %th.add=t('admin.variant_overrides.index.add')
+ %th.hide=t('admin.variant_overrides.index.hide')
+ %tbody{ ng: { repeat: 'product in filteredProducts | limitTo:productLimit' } }
%tr{ id: "v_{{variant.id}}", ng: { repeat: 'variant in product.variants | inventoryVariants:hub_id:views' } }
- %td.producer{ bo: { bind: 'producersByID[product.producer_id].name'} }
- %td.product{ bo: { bind: 'product.name'} }
+ %td.producer{ ng: { bind: '::producersByID[product.producer_id].name'} }
+ %td.product{ ng: { bind: '::product.name'} }
%td.variant
- %span{ bo: { bind: 'variant.display_name || ""'} }
- .variant-override-unit{ bo: { bind: 'variant.unit_to_display'} }
+ %span{ ng: { bind: '::variant.display_name || ""'} }
+ .variant-override-unit{ ng: { bind: '::variant.unit_to_display'} }
%td.add
%button.fullwidth.icon-plus{ ng: { click: "setVisibility(hub_id,variant.id,true)" } }
- = t('admin.inventory.add')
+ = t('admin.variant_overrides.index.add')
%td.hide
%button.fullwidth.hide.icon-remove{ ng: { click: "setVisibility(hub_id,variant.id,false)" } }
- = t('admin.inventory.hide')
+ = t('admin.variant_overrides.index.hide')
diff --git a/app/views/admin/variant_overrides/_new_products_alert.html.haml b/app/views/admin/variant_overrides/_new_products_alert.html.haml
index 29ec4c9623..385616d817 100644
--- a/app/views/admin/variant_overrides/_new_products_alert.html.haml
+++ b/app/views/admin/variant_overrides/_new_products_alert.html.haml
@@ -1,5 +1,5 @@
%div{ ng: { show: '(newProductCount = (products | hubPermissions:hubPermissions:hub_id | newInventoryProducts:hub_id).length) > 0 && !views.new.visible && !alertDismissed' } }
%hr.divider.sixteen.columns.alpha.omega
- %alert-row{ message: "#{t('admin.inventory.new_products_alert_message', new_product_count: '{{ newProductCount }}')}",
+ %alert-row{ message: "#{t('admin.variant_overrides.index.new_products_alert_message', new_product_count: '{{ newProductCount }}')}",
dismissed: "alertDismissed",
- button: { text: "#{t('admin.inventory.review_now')}", action: "selectView('new')" } }
+ button: { text: "#{t('admin.variant_overrides.index.review_now')}", action: "selectView('new')" } }
diff --git a/app/views/admin/variant_overrides/_no_results.html.haml b/app/views/admin/variant_overrides/_no_results.html.haml
index cdec6ab8c5..7ec4e68f0e 100644
--- a/app/views/admin/variant_overrides/_no_results.html.haml
+++ b/app/views/admin/variant_overrides/_no_results.html.haml
@@ -1,7 +1,7 @@
%div.text-big.no-results{ ng: { show: 'hub_id && products.length > 0 && filteredProducts.length == 0' } }
- %span{ ng: { show: 'views.inventory.visible && !filtersApplied()' } }=t('admin.inventory.currently_empty')
- %span{ ng: { show: 'views.inventory.visible && filtersApplied()' } }=t('admin.inventory.no_matching_products')
- %span{ ng: { show: 'views.hidden.visible && !filtersApplied()' } }=t('admin.inventory.no_hidden_products')
- %span{ ng: { show: 'views.hidden.visible && filtersApplied()' } }=t('admin.inventory.no_matching_hidden_products')
- %span{ ng: { show: 'views.new.visible && !filtersApplied()' } }=t('admin.inventory.no_new_products')
- %span{ ng: { show: 'views.new.visible && filtersApplied()' } }=t('admin.inventory.no_matching_new_products')
+ %span{ ng: { show: 'views.inventory.visible && !filtersApplied()' } }=t('admin.variant_overrides.index.currently_empty')
+ %span{ ng: { show: 'views.inventory.visible && filtersApplied()' } }=t('admin.variant_overrides.index.no_matching_products')
+ %span{ ng: { show: 'views.hidden.visible && !filtersApplied()' } }=t('admin.variant_overrides.index.no_hidden_products')
+ %span{ ng: { show: 'views.hidden.visible && filtersApplied()' } }=t('admin.variant_overrides.index.no_matching_hidden_products')
+ %span{ ng: { show: 'views.new.visible && !filtersApplied()' } }=t('admin.variant_overrides.index.no_new_products')
+ %span{ ng: { show: 'views.new.visible && filtersApplied()' } }=t('admin.variant_overrides.index.no_matching_new_products')
diff --git a/app/views/admin/variant_overrides/_products.html.haml b/app/views/admin/variant_overrides/_products.html.haml
index f255d86e0b..711c52f6cf 100644
--- a/app/views/admin/variant_overrides/_products.html.haml
+++ b/app/views/admin/variant_overrides/_products.html.haml
@@ -1,5 +1,6 @@
%form{ name: 'variant_overrides_form', ng: { show: "views.inventory.visible" } }
- %save-bar{ form: "variant_overrides_form", buttons: "[{ text: 'Save Changes', action: update, class: 'red' }]" }
+ %save-bar{ dirty: "customers_form.$dirty", persist: "false" }
+ %input.red{ type: "button", value: "Save Changes", ng: { click: "update()", disabled: "!variant_overrides_form.$dirty" } }
%table.index.bulk#variant-overrides
%col.producer{ width: "20%", ng: { show: 'columns.producer.visible' } }
%col.product{ width: "20%", ng: { show: 'columns.product.visible' } }
@@ -10,18 +11,20 @@
%col.reset{ width: "1%", ng: { show: 'columns.reset.visible' } }
%col.reset{ width: "15%", ng: { show: 'columns.reset.visible' } }
%col.inheritance{ width: "5%", ng: { show: 'columns.inheritance.visible' } }
+ %col.tags{ width: "30%", ng: { show: 'columns.tags.visible' } }
%col.visibility{ width: "10%", ng: { show: 'columns.visibility.visible' } }
%thead
%tr{ ng: { controller: "ColumnsCtrl" } }
%th.producer{ ng: { show: 'columns.producer.visible' } }=t('admin.producer')
%th.product{ ng: { show: 'columns.product.visible' } }=t('admin.product')
- %th.sku{ ng: { show: 'columns.sku.visible' } }=t('admin.inventory.sku')
- %th.price{ ng: { show: 'columns.price.visible' } }=t('admin.inventory.price')
- %th.on_hand{ ng: { show: 'columns.on_hand.visible' } }=t('admin.inventory.on_hand')
- %th.on_demand{ ng: { show: 'columns.on_demand.visible' } }=t('admin.inventory.on_demand')
- %th.reset{ colspan: 2, ng: { show: 'columns.reset.visible' } }=t('admin.inventory.enable_reset')
- %th.inheritance{ ng: { show: 'columns.inheritance.visible' } }=t('admin.inventory.inherit')
- %th.visibility{ ng: { show: 'columns.visibility.visible' } }=t('admin.inventory.hide')
- %tbody{bindonce: true, ng: {repeat: 'product in filteredProducts = (products | hubPermissions:hubPermissions:hub_id | inventoryProducts:hub_id:views | attrFilter:{producer_id:producerFilter} | filter:query) | limitTo:productLimit' } }
+ %th.sku{ ng: { show: 'columns.sku.visible' } }=t('admin.sku')
+ %th.price{ ng: { show: 'columns.price.visible' } }=t('admin.price')
+ %th.on_hand{ ng: { show: 'columns.on_hand.visible' } }=t('admin.on_hand')
+ %th.on_demand{ ng: { show: 'columns.on_demand.visible' } }=t('admin.on_demand?')
+ %th.reset{ colspan: 2, ng: { show: 'columns.reset.visible' } }=t('admin.variant_overrides.index.enable_reset?')
+ %th.inheritance{ ng: { show: 'columns.inheritance.visible' } }=t('admin.variant_overrides.index.inherit?')
+ %th.tags{ ng: { show: 'columns.tags.visible' } }=t('admin.tags')
+ %th.visibility{ ng: { show: 'columns.visibility.visible' } }=t('admin.variant_overrides.index.hide')
+ %tbody{ ng: {repeat: 'product in filteredProducts = (products | hubPermissions:hubPermissions:hub_id | inventoryProducts:hub_id:views | attrFilter:{producer_id:producerFilter} | filter:query) | limitTo:productLimit' } }
= render 'admin/variant_overrides/products_product'
= render 'admin/variant_overrides/products_variants'
diff --git a/app/views/admin/variant_overrides/_products_product.html.haml b/app/views/admin/variant_overrides/_products_product.html.haml
index 70b48e3909..0427333790 100644
--- a/app/views/admin/variant_overrides/_products_product.html.haml
+++ b/app/views/admin/variant_overrides/_products_product.html.haml
@@ -1,10 +1,11 @@
%tr.product.even
- %td.producer{ ng: { show: 'columns.producer.visible' }, bo: { bind: 'producersByID[product.producer_id].name'} }
- %td.product{ ng: { show: 'columns.product.visible' }, bo: { bind: 'product.name'} }
+ %td.producer{ ng: { show: 'columns.producer.visible', bind: '::producersByID[product.producer_id].name'} }
+ %td.product{ ng: { show: 'columns.product.visible', bind: '::product.name'} }
%td.sku{ ng: { show: 'columns.sku.visible' } }
%td.price{ ng: { show: 'columns.price.visible' } }
%td.on_hand{ ng: { show: 'columns.on_hand.visible' } }
%td.on_demand{ ng: { show: 'columns.on_demand.visible' } }
%td.reset{ colspan: 2, ng: { show: 'columns.reset.visible' } }
%td.inheritance{ ng: { show: 'columns.inheritance.visible' } }
+ %td.tags{ ng: { show: 'columns.tags.visible' } }
%td.visibility{ ng: { show: 'columns.visibility.visible' } }
diff --git a/app/views/admin/variant_overrides/_products_variants.html.haml b/app/views/admin/variant_overrides/_products_variants.html.haml
index c26be92697..94fc309798 100644
--- a/app/views/admin/variant_overrides/_products_variants.html.haml
+++ b/app/views/admin/variant_overrides/_products_variants.html.haml
@@ -1,8 +1,8 @@
-%tr.variant{ id: "v_{{variant.id}}", ng: {repeat: 'variant in product.variants | inventoryVariants:hub_id:views'}}
+%tr.variant{ id: "v_{{variant.id}}", ng: {repeat: 'variant in product.variants | inventoryVariants:hub_id:views'} }
%td.producer{ ng: { show: 'columns.producer.visible' } }
%td.product{ ng: { show: 'columns.product.visible' } }
- %span{ bo: { bind: 'variant.display_name || ""'} }
- .variant-override-unit{ bo: { bind: 'variant.unit_to_display'} }
+ %span{ ng: { bind: '::variant.display_name || ""'} }
+ .variant-override-unit{ ng: { bind: '::variant.unit_to_display'} }
%td.sku{ ng: { show: 'columns.sku.visible' } }
%input{name: 'variant-overrides-{{ variant.id }}-sku', type: 'text', ng: {model: 'variantOverrides[hub_id][variant.id].sku'}, placeholder: '{{ variant.sku }}', 'ofn-track-variant-override' => 'sku'}
%td.price{ ng: { show: 'columns.price.visible' } }
@@ -17,6 +17,9 @@
%input{name: 'variant-overrides-{{ variant.id }}-default_stock', type: 'text', ng: {model: 'variantOverrides[hub_id][variant.id].default_stock'}, placeholder: '{{ variant.default_stock ? variant.default_stock : "Default stock"}}', 'ofn-track-variant-override' => 'default_stock'}
%td.inheritance{ ng: { show: 'columns.inheritance.visible' } }
%input.field{ :type => 'checkbox', name: 'variant-overrides-{{ variant.id }}-inherit', ng: { model: 'inherit' }, 'track-inheritance' => true }
+ %td.tags{ ng: { show: 'columns.tags.visible' } }
+ .tag_watcher{ 'track-tag-list' => true }
+ %tags_with_translation{ object: 'variantOverrides[hub_id][variant.id]', form: 'variant_overrides_form' }
%td.visibility{ ng: { show: 'columns.visibility.visible' } }
%button.icon-remove.hide.fullwidth{ :type => 'button', ng: { click: "setVisibility(hub_id,variant.id,false)" } }
- = t('admin.inventory.hide')
+ = t('admin.variant_overrides.index.hide')
diff --git a/app/views/admin/variant_overrides/_show_more.html.haml b/app/views/admin/variant_overrides/_show_more.html.haml
index 8d60593ddc..21e927e443 100644
--- a/app/views/admin/variant_overrides/_show_more.html.haml
+++ b/app/views/admin/variant_overrides/_show_more.html.haml
@@ -1,4 +1,5 @@
-.text-center
+-# %show-more.text-center{ data: "filteredProducts", limit: "productLimit", increment: "10" }
+.text-center{ ng: { show: "filteredProducts.length > productLimit" } }
%input{ type: 'button', value: 'Show More', ng: { click: 'productLimit = productLimit + 10' } }
or
%input{ type: 'button', value: "Show All ({{ filteredProducts.length - productLimit }} More)", ng: { click: 'productLimit = filteredProducts.length' } }
diff --git a/app/views/checkout/_order.rabl b/app/views/checkout/_order.rabl
deleted file mode 100644
index 778f4428d0..0000000000
--- a/app/views/checkout/_order.rabl
+++ /dev/null
@@ -1,44 +0,0 @@
-#NOTE: when adding new fields for user input, it may want to be cached in localStorage
-# If so, make sure to add it to controller attribute caching
-
-object current_order
-attributes :id, :email, :shipping_method_id, :user_id
-
-node :display_total do
- current_order.display_total.money.to_f
-end
-
-node :payment_method_id do
- current_order.payments.first.andand.payment_method_id
-end
-
-child current_order.bill_address => :bill_address do
- attributes :phone, :firstname, :lastname, :address1, :address2, :city, :country_id, :state_id, :zipcode
-end
-
-child current_order.ship_address => :ship_address do
- attributes :phone, :firstname, :lastname, :address1, :address2, :city, :country_id, :state_id, :zipcode
-end
-
-# This is actually totally decoupled data and should be injected separately into their
-# own services
-
-node :shipping_methods do
- Hash[current_distributor.shipping_methods.uniq.collect { |method|
- [method.id, {
- require_ship_address: method.require_ship_address,
- price: method.compute_amount(current_order).to_f,
- name: method.name,
- description: method.description
- }]
- }]
-end
-
-node :payment_methods do
- Hash[current_order.available_payment_methods.collect {
- |method| [method.id, {
- name: method.name,
- method_type: method.method_type
- }]
- }]
-end
diff --git a/app/views/checkout/_payment.html.haml b/app/views/checkout/_payment.html.haml
index c11d7cd45e..8d387145d5 100644
--- a/app/views/checkout/_payment.html.haml
+++ b/app/views/checkout/_payment.html.haml
@@ -17,7 +17,7 @@
-# The problem being how to render the partials
.row
.small-6.columns
- - current_order.available_payment_methods.each do |method|
+ - available_payment_methods.each do |method|
.row
.small-12.columns
%label
@@ -26,6 +26,7 @@
name: "order.payment_method_id",
"ng-model" => "order.payment_method_id"
= method.name
+ = "(#{payment_method_price(method, @order)})"
%small.error.medium.input-text{"ng-show" => "!fieldValid('order.payment_method_id')"}
= "{{ fieldErrors('order.payment_method_id') }}"
diff --git a/app/views/checkout/_summary.html.haml b/app/views/checkout/_summary.html.haml
index 3a4f6a4ea1..a3b61c5ded 100644
--- a/app/views/checkout/_summary.html.haml
+++ b/app/views/checkout/_summary.html.haml
@@ -19,6 +19,11 @@
= t :checkout_shipping_price
%td.shipping.text-right {{ Checkout.shippingPrice() | localizeCurrency }}
+ %tr
+ %th
+ = t :payment_method_fee
+ %td.text-right {{ Checkout.paymentPrice() | localizeCurrency }}
+
%tr
%th
= t :checkout_total_price
diff --git a/app/views/groups/_contact.html.haml b/app/views/groups/_contact.html.haml
index 320da464bd..5c1e3aef5e 100644
--- a/app/views/groups/_contact.html.haml
+++ b/app/views/groups/_contact.html.haml
@@ -1,4 +1,4 @@
-%div.contact-container{bindonce: true}
+%div.contact-container
- if @group.email.present? || @group.website.present? || @group.phone.present?
%div.modal-centered
%p.modal-header
@@ -16,12 +16,12 @@
=link_to_service "http://", @group.website do
= t :groups_contact_website
-%div{bindonce: true}
+%div
- if @group.facebook.present? || @group.twitter.present? || @group.linkedin.present? || @group.instagram.present?
%div.modal-centered.pad-top
%p.modal-header
= t :groups_contact_web
- .follow-icons{bindonce: true}
+ .follow-icons
=link_to_service "http://twitter.com/", @group.twitter do
%i.ofn-i_041-twitter
=link_to_service "https://www.facebook.com/", @group.facebook do
@@ -30,8 +30,8 @@
%i.ofn-i_042-linkedin
=link_to_service "http://instagram.com/", @group.instagram do
%i.ofn-i_043-instagram
-
-%div{bindonce: true}
+
+%div
- if @group.address1.present? || @group.city.present?
%div.modal-centered.pad-top
%p.modal-header
@@ -46,5 +46,3 @@
= @group.city
= @group.state
= @group.zipcode
-
-
diff --git a/app/views/groups/index.html.haml b/app/views/groups/index.html.haml
index afb19e1b91..ad93d4f366 100644
--- a/app/views/groups/index.html.haml
+++ b/app/views/groups/index.html.haml
@@ -25,14 +25,14 @@
.group.animate-repeat{"ng-repeat" => "group in groups = (Groups.groups | groups:query | orderBy:order)",
name: "group{{group.id}}",
id: "group{{group.id}}"}
- .row.pad-top{bindonce: true}
+ .row.pad-top
.small-12.medium-6.columns
.groups-header
- %a{"bo-href-i" => "/groups/{{group.permalink}}"}
+ %a{"ng-href" => "/groups/{{::group.permalink}}"}
%i.ofn-i_035-groups
- %span.group-name{"bo-text" => "group.name"}
+ %span.group-name{"ng-bind" => "::group.name"}
.small-3.medium-2.columns
- %p{"bo-text" => "group.state"}
+ %p{"ng-bind" => "::group.state"}
.small-9.medium-4.columns.groups-icons
%p
%link-to-service.ofn-i_050-mail-circle{service: '""', ref: 'group.email.split("").reverse().join("")', mailto: true}
diff --git a/app/views/groups/show.html.haml b/app/views/groups/show.html.haml
index b5222e5f86..af497a4195 100644
--- a/app/views/groups/show.html.haml
+++ b/app/views/groups/show.html.haml
@@ -32,27 +32,27 @@
.small-12.columns.pad-top
.row
.small-12.medium-12.large-9.columns
- %div{"ng-controller" => "TabsCtrl"}
+ %div{"ng-controller" => "GroupTabsCtrl"}
%tabset
%tab{heading: t(:label_map),
- active: "active(\'\')",
- select: "select(\'\')"}
+ active: "tabs.map.active",
+ select: "select(\'map\')"}
.map-container
- %map{"ng-if" => "(active(\'\') && (mapShowed = true)) || mapShowed"}
+ %map{"ng-if" => "(isActive(\'/map\') && (mapShowed = true)) || mapShowed"}
%google-map{options: "map.additional_options", center: "map.center", zoom: "map.zoom", styles: "map.styles", draggable: "true"}
%map-search
%markers{models: "mapMarkers", fit: "true",
coords: "'self'", icon: "'icon'", click: "'reveal'"}
%tab{heading: t(:groups_about),
- active: "active(\'about\')",
+ active: "tabs.about.active",
select: "select(\'about\')"}
%h1
= t :groups_about
%p!= @group.long_description
%tab{heading: t(:groups_producers),
- active: "active(\'producers\')",
+ active: "tabs.producers.active",
select: "select(\'producers\')"}
.producers{"ng-controller" => "GroupEnterprisesCtrl"}
.row
@@ -62,7 +62,7 @@
= render partial: "shared/components/enterprise_search"
= render partial: "producers/filters"
- .row{bindonce: true}
+ .row
.small-12.columns
.active_table
%producer.active_table_node.row.animate-repeat{id: "{{producer.path}}",
@@ -78,7 +78,7 @@
= render partial: 'shared/components/enterprise_no_results'
%tab{heading: t(:groups_hubs),
- active: "active(\'hubs\')",
+ active: "tabs.hubs.active",
select: "select(\'hubs\')"}
.hubs{"ng-controller" => "GroupEnterprisesCtrl"}
.row
@@ -89,7 +89,7 @@
= render partial: "shared/components/enterprise_search"
= render partial: "hub_filters"
- .row{bindonce: true}
+ .row
.small-12.columns
.active_table
%hub.active_table_node.row.animate-repeat{id: "{{hub.hash}}",
diff --git a/app/views/home/_fat.html.haml b/app/views/home/_fat.html.haml
index b96b13885e..4e90771cc1 100644
--- a/app/views/home/_fat.html.haml
+++ b/app/views/home/_fat.html.haml
@@ -1,45 +1,45 @@
-.row.active_table_row{"ng-show" => "open()", "ng-click" => "toggle($event)", "ng-class" => "{'open' : !ofn-i_032-closed-sign()}", bindonce: true}
+.row.active_table_row{"ng-show" => "open()", "ng-click" => "toggle($event)", "ng-class" => "{'open' : !ofn-i_032-closed-sign()}"}
.columns.small-12.medium-6.large-5.fat
- %div{"bo-if" => "hub.taxons"}
+ %div{"ng-if" => "::hub.taxons"}
%label
= t :hubs_buy
.trans-sentence
%span.fat-taxons{"ng-repeat" => "taxon in hub.taxons"}
%render-svg{path: "{{taxon.icon}}"}
- %span{"bo-text" => "taxon.name"}
- %div.show-for-medium-up{"bo-if" => "hub.taxons.length==0"}
+ %span{"ng-bind" => "::taxon.name"}
+ %div.show-for-medium-up{"ng-if" => "::hub.taxons.length==0"}
.columns.small-12.medium-3.large-2.fat
- %div{"bo-if" => "hub.pickup || hub.delivery"}
+ %div{"ng-if" => "::(hub.pickup || hub.delivery)"}
%label
= t :hubs_delivery_options
%ul.small-block-grid-2.medium-block-grid-1.large-block-grid-1
- %li.pickup{"bo-if" => "hub.pickup"}
+ %li.pickup{"ng-if" => "::hub.pickup"}
%i.ofn-i_038-takeaway
= t :hubs_pickup
- %li.delivery{"bo-if" => "hub.delivery"}
+ %li.delivery{"ng-if" => "::hub.delivery"}
%i.ofn-i_039-delivery
= t :hubs_delivery
.columns.small-12.medium-3.large-5.fat
- %div{"bo-if" => "hub.producers"}
+ %div{"ng-if" => "::hub.producers"}
%label
= t :hubs_producers
%ul.small-block-grid-2.medium-block-grid-1.large-block-grid-2{"ng-class" => "{'show-more-producers' : toggleMoreProducers}", "class" => "producers-list"}
%li{"ng-repeat" => "enterprise in hub.producers | limitTo:7"}
%enterprise-modal
%i.ofn-i_036-producers
- %span{"bo-text" => "enterprise.name"}
- %li{"data-is-link" => "true", "class" => "more-producers-link", "bo-show" => "hub.producers.length>7"}
+ %span{"ng-bind" => "::enterprise.name"}
+ %li{"data-is-link" => "true", "class" => "more-producers-link", "ng-show" => "::hub.producers.length>7"}
%a{"ng-click" => "toggleMoreProducers=!toggleMoreProducers"}
.more
+
- %span{"bo-text" => "hub.producers.length-7"}
+ %span{"ng-bind" => "::hub.producers.length-7"}
= t :label_more
.less
= t :label_less
%li{"ng-repeat" => "enterprise in hub.producers.slice(7,hub.producers.length)", "class" => "additional-producer"}
%enterprise-modal
%i.ofn-i_036-producers
- %span{"bo-text" => "enterprise.name"}
- %div.show-for-medium-up{"bo-if" => "hub.producers.length==0"}
+ %span{"ng-bind" => "::enterprise.name"}
+ %div.show-for-medium-up{"ng-if" => "::hub.producers.length==0"}
diff --git a/app/views/home/_skinny.html.haml b/app/views/home/_skinny.html.haml
index 85e200fb94..fbc008a5c6 100644
--- a/app/views/home/_skinny.html.haml
+++ b/app/views/home/_skinny.html.haml
@@ -1,46 +1,46 @@
-.row.active_table_row{"ng-if" => "hub.is_distributor", "ng-click" => "toggle($event)", "ng-class" => "{'closed' : !open(), 'is_distributor' : producer.is_distributor}", bindonce: true}
+.row.active_table_row{"ng-if" => "hub.is_distributor", "ng-click" => "toggle($event)", "ng-class" => "{'closed' : !open(), 'is_distributor' : producer.is_distributor}"}
.columns.small-12.medium-5.large-5.skinny-head
- %a.hub{"bo-href" => "hub.path", "ng-class" => "{primary: hub.active, secondary: !hub.active}", "ofn-change-hub" => "hub", "data-is-link" => "true"}
- %i{bo: {class: "hub.icon_font"}}
- %span.margin-top.hub-name-listing{"bo-bind" => "hub.name | truncate:40"}
+ %a.hub{"ng-href" => "{{::hub.path}}", "ng-class" => "{primary: hub.active, secondary: !hub.active}", "ofn-change-hub" => "hub", "data-is-link" => "true"}
+ %i{ng: {class: "::hub.icon_font"}}
+ %span.margin-top.hub-name-listing{"ng-bind" => "::hub.name | truncate:40"}
.columns.small-4.medium-2.large-2
- %span.margin-top{"bo-text" => "hub.address.city"}
+ %span.margin-top{"ng-bind" => "::hub.address.city"}
.columns.small-2.medium-1.large-1
- %span.margin-top{"bo-bind" => "hub.address.state_name | uppercase"}
+ %span.margin-top{"ng-bind" => "::hub.address.state_name | uppercase"}
%span.margin-top{"ng-if" => "hub.distance != null && hub.distance > 0"} ({{ hub.distance / 1000 | number:0 }} km)
- .columns.small-4.medium-3.large-3.text-right{"bo-if" => "hub.active"}
- %a.hub.open_closed{"bo-href" => "hub.path", "ng-class" => "{primary: hub.active, secondary: !hub.active}", "ofn-change-hub" => "hub"}
+ .columns.small-4.medium-3.large-3.text-right{"ng-if" => "::hub.active"}
+ %a.hub.open_closed{"ng-href" => "{{::hub.path}}", "ng-class" => "{primary: hub.active, secondary: !hub.active}", "ofn-change-hub" => "hub"}
%i.ofn-i_033-open-sign
- %span.margin-top{ bo: { if: "current()" } }
+ %span.margin-top{ ng: { if: "::current()" } }
%em= t :hubs_shopping_here
- %span.margin-top{ bo: { if: "!current()" } }
- %span{"bo-bind" => "hub.orders_close_at | sensible_timeframe"}
+ %span.margin-top{ ng: { if: "::!current()" } }
+ %span{"ng-bind" => "::hub.orders_close_at | sensible_timeframe"}
- .columns.small-4.medium-3.large-3.text-right{"bo-if" => "!hub.active"}
- %a.hub.open_closed{"bo-href" => "hub.path", "ng-class" => "{primary: hub.active, secondary: !hub.active}", "ofn-change-hub" => "hub"}
+ .columns.small-4.medium-3.large-3.text-right{"ng-if" => "::!hub.active"}
+ %a.hub.open_closed{"ng-href" => "{{::hub.path}}", "ng-class" => "{primary: hub.active, secondary: !hub.active}", "ofn-change-hub" => "hub"}
%i.ofn-i_032-closed-sign
- %span.margin-top{ bo: { if: "current()" } }
+ %span.margin-top{ ng: { if: "::current()" } }
%em= t :hubs_shopping_here
- %span.margin-top{ bo: { if: "!current()" } }
+ %span.margin-top{ ng: { if: "::!current()" } }
= t :hubs_orders_closed
.columns.small-2.medium-1.large-1.text-right
%span.margin-top
%i{"ng-class" => "{'ofn-i_005-caret-down' : !open(), 'ofn-i_006-caret-up' : open()}"}
-.row.active_table_row{"ng-if" => "!hub.is_distributor", "ng-class" => "closed", bindonce: true}
+.row.active_table_row{"ng-if" => "!hub.is_distributor", "ng-class" => "closed"}
.columns.small-12.medium-6.large-5.skinny-head
%a.hub{"ng-click" => "openModal(hub)", "ng-class" => "{primary: hub.active, secondary: !hub.active}"}
%i{ng: {class: "hub.icon_font"}}
- %span.margin-top.hub-name-listing{"bo-bind" => "hub.name | truncate:40"}
+ %span.margin-top.hub-name-listing{"ng-bind" => "::hub.name | truncate:40"}
.columns.small-4.medium-2.large-2
- %span.margin-top{"bo-text" => "hub.address.city"}
+ %span.margin-top{"ng-bind" => "::hub.address.city"}
.columns.small-2.medium-1.large-1
- %span.margin-top{"bo-bind" => "hub.address.state_name | uppercase"}
+ %span.margin-top{"ng-bind" => "::hub.address.state_name | uppercase"}
.columns.small-6.medium-3.large-4.text-right
- %span.margin-top{ bo: { if: "!current()" } }
+ %span.margin-top{ ng: { if: "::!current()" } }
%em= t :hubs_profile_only
diff --git a/app/views/layouts/darkswarm.html.haml b/app/views/layouts/darkswarm.html.haml
index c758e4836e..d3f0a78087 100644
--- a/app/views/layouts/darkswarm.html.haml
+++ b/app/views/layouts/darkswarm.html.haml
@@ -43,4 +43,4 @@
#footer
%loading
- = render 'shared/analytics'
+ = render 'spree/shared/google_analytics'
diff --git a/app/views/producers/_fat.html.haml b/app/views/producers/_fat.html.haml
index 9faa239c02..6b665b4f34 100644
--- a/app/views/producers/_fat.html.haml
+++ b/app/views/producers/_fat.html.haml
@@ -2,78 +2,78 @@
.columns.small-12.medium-7.large-7.fat
/ Will add in long description available once clean up HTML formatting producer.long_description
- %div{"bo-if" => "producer.description"}
+ %div{"ng-if" => "::producer.description"}
%label
= t :producers_about
- %img.right.show-for-medium-up{"bo-src" => "producer.logo" }
- %p.text-small{ "bo-text" => "producer.description"}
- %div.show-for-medium-up{"bo-if" => "producer.description.length==0"}
+ %img.right.show-for-medium-up{"ng-src" => "{{::producer.logo}}" }
+ %p.text-small{ "ng-bind" => "::producer.description"}
+ %div.show-for-medium-up{"ng-if" => "::producer.description.length==0"}
%label
.columns.small-12.medium-5.large-5.fat
- %div{"bo-if" => "producer.supplied_taxons"}
+ %div{"ng-if" => "::producer.supplied_taxons"}
%label
= t :producers_buy
%p.trans-sentence
%span.fat-taxons{"ng-repeat" => "taxon in producer.supplied_taxons"}
%render-svg{path: "{{taxon.icon}}"}
- %span{"bo-text" => "taxon.name"}
+ %span{"ng-bind" => "::taxon.name"}
%div.show-for-medium-up{"ng-if" => "producer.supplied_taxons.length==0"}
- %div{"bo-if" => "producer.email_address || producer.website || producer.phone"}
+ %div{"ng-if" => "::producer.email_address || producer.website || producer.phone"}
%label
= t :producers_contact
- %p.word-wrap{"bo-if" => "producer.phone"}
+ %p.word-wrap{"ng-if" => "::producer.phone"}
= t :producers_contact_phone
- %span{"bo-text" => "producer.phone"}
+ %span{"ng-bind" => "::producer.phone"}
- %p.word-wrap{"bo-if" => "producer.email_address"}
- %a{"bo-href" => "producer.email_address | stripUrl", target: "_blank", mailto: true}
- %span.email{"bo-bind" => "producer.email_address | stripUrl"}
+ %p.word-wrap{"ng-if" => "::producer.email_address"}
+ %a{"ng-href" => "{{::producer.email_address | stripUrl}}", target: "_blank", mailto: true}
+ %span.email{"ng-bind" => "::producer.email_address | stripUrl"}
- %p.word-wrap{"bo-if" => "producer.website"}
- %a{"bo-href-i" => "http://{{producer.website | stripUrl}}", target: "_blank" }
- %span{"bo-bind" => "producer.website | stripUrl"}
+ %p.word-wrap{"ng-if" => "::producer.website"}
+ %a{"ng-href" => "http://{{::producer.website | stripUrl}}", target: "_blank" }
+ %span{"ng-bind" => "::producer.website | stripUrl"}
- %div{"bo-if" => "producer.twitter || producer.facebook || producer.linkedin || producer.instagram"}
+ %div{"ng-if" => "::producer.twitter || producer.facebook || producer.linkedin || producer.instagram"}
%label
= t :producers_social
- .follow-icons{bindonce: true}
- %span{"bo-if" => "producer.twitter"}
- %a{"bo-href-i" => "http://twitter.com/{{producer.twitter}}", target: "_blank"}
+ .follow-icons
+ %span{"ng-if" => "::producer.twitter"}
+ %a{"ng-href" => "http://twitter.com/{{::producer.twitter}}", target: "_blank"}
%i.ofn-i_041-twitter
- %span{"bo-if" => "producer.facebook"}
- %a{"bo-href-i" => "http://{{producer.facebook | stripUrl}}", target: "_blank"}
+ %span{"ng-if" => "::producer.facebook"}
+ %a{"ng-href" => "http://{{::producer.facebook | stripUrl}}", target: "_blank"}
%i.ofn-i_044-facebook
- %span{"bo-if" => "producer.linkedin"}
- %a{"bo-href-i" => "http://{{producer.linkedin | stripUrl}}", target: "_blank"}
+ %span{"ng-if" => "::producer.linkedin"}
+ %a{"ng-href" => "http://{{::producer.linkedin | stripUrl}}", target: "_blank"}
%i.ofn-i_042-linkedin
- %span{"bo-if" => "producer.instagram"}
- %a{"bo-href-i" => "http://instagram.com/{{producer.instagram}}", target: "_blank"}
+ %span{"ng-if" => "::producer.instagram"}
+ %a{"ng-href" => "http://instagram.com/{{::producer.instagram}}", target: "_blank"}
%i.ofn-i_043-instagram
-.row.active_table_row.pad-top{"ng-if" => "open()", "bo-if" => "producer.hubs"}
+.row.active_table_row.pad-top{"ng-if" => "open() && producer.hubs"}
.columns.small-12
.row
.columns.small-12.fat
- %div{"bo-if" => "producer.name"}
+ %div{"ng-if" => "::producer.name"}
%label
- = t :producers_buy_at_html, {enterprise: ''.html_safe}
- %div.show-for-medium-up{"bo-if" => "!producer.name"}
+ = t :producers_buy_at_html, {enterprise: ''.html_safe}
+ %div.show-for-medium-up{"ng-if" => "::!producer.name"}
.row.cta-container
.columns.small-12
%a.cta-hub{"ng-repeat" => "hub in producer.hubs | visible | orderBy:'-active'",
- "bo-href" => "hub.path", "ofn-change-hub" => "hub",
- "bo-class" => "{primary: hub.active, secondary: !hub.active}"}
- %i.ofn-i_033-open-sign{"bo-if" => "hub.active"}
- %i.ofn-i_032-closed-sign{"bo-if" => "!hub.active"}
- .hub-name{"bo-text" => "hub.name"}
- .button-address{"bo-bind" => "[hub.address.city, hub.address.state_name] | printArray"}
+ "ng-href" => "{{::hub.path}}", "ofn-change-hub" => "hub",
+ "ng-class" => "::{primary: hub.active, secondary: !hub.active}"}
+ %i.ofn-i_033-open-sign{"ng-if" => "::hub.active"}
+ %i.ofn-i_032-closed-sign{"ng-if" => "::!hub.active"}
+ .hub-name{"ng-bind" => "::hub.name"}
+ .button-address{"ng-bind" => "::[hub.address.city, hub.address.state_name] | printArray"}
diff --git a/app/views/producers/_skinny.html.haml b/app/views/producers/_skinny.html.haml
index cf066be05a..311de9daf5 100644
--- a/app/views/producers/_skinny.html.haml
+++ b/app/views/producers/_skinny.html.haml
@@ -1,20 +1,20 @@
.row.active_table_row{"ng-click" => "toggle($event)", "ng-class" => "{'closed' : !open(), 'is_distributor' : producer.is_distributor}"}
.columns.small-12.medium-4.large-4.skinny-head
- %span{"bo-if" => "producer.is_distributor" }
- %a.is_distributor{"bo-href" => "producer.path" }
- %i{bo: {class: "producer.producer_icon_font"}}
+ %span{"ng-if" => "::producer.is_distributor" }
+ %a.is_distributor{"ng-href" => "{{::producer.path}}" }
+ %i{ng: {class: "::producer.producer_icon_font"}}
%span.margin-top
- %strong{"bo-text" => "producer.name"}
- %span.producer-name{"bo-if" => "!producer.is_distributor" }
- %i{bo: {class: "producer.producer_icon_font"}}
+ %strong{"ng-bind" => "::producer.name"}
+ %span.producer-name{"ng-if" => "::!producer.is_distributor" }
+ %i{ng: {class: "::producer.producer_icon_font"}}
%span.margin-top
- %strong{"bo-text" => "producer.name"}
+ %strong{"ng-bind" => "::producer.name"}
.columns.small-6.medium-3.large-3
- %span.margin-top{"bo-text" => "producer.address.city"}
+ %span.margin-top{"ng-bind" => "::producer.address.city"}
.columns.small-4.medium-3.large-4
- %span.margin-top{"bo-bind" => "producer.address.state_name | uppercase"}
+ %span.margin-top{"ng-bind" => "::producer.address.state_name | uppercase"}
.columns.small-2.medium-2.large-1.text-right
%span.margin-top
%i{"ng-class" => "{'ofn-i_005-caret-down' : !open(), 'ofn-i_006-caret-up' : open()}"}
diff --git a/app/views/producers/index.html.haml b/app/views/producers/index.html.haml
index e842568cb3..8b7fd5695e 100644
--- a/app/views/producers/index.html.haml
+++ b/app/views/producers/index.html.haml
@@ -12,7 +12,7 @@
= render partial: "shared/components/enterprise_search"
= render partial: "producers/filters"
- .row{bindonce: true}
+ .row
.small-12.columns
.active_table
%producer.active_table_node.row.animate-repeat{id: "{{producer.path}}",
diff --git a/app/views/shared/_analytics.html.haml b/app/views/shared/_analytics.html.haml
deleted file mode 100644
index 16ad08ff5f..0000000000
--- a/app/views/shared/_analytics.html.haml
+++ /dev/null
@@ -1,9 +0,0 @@
-- if Rails.env.production?
- :javascript
- (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
- (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
- m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
- })(window,document,'script','//www.google-analytics.com/analytics.js','ga');
-
- ga('create', 'UA-62912229-1', 'auto');
- ga('send', 'pageview');
diff --git a/app/views/shop/_messages.html.haml b/app/views/shop/_messages.html.haml
index ba35354bf3..9e5d3b8e82 100644
--- a/app/views/shop/_messages.html.haml
+++ b/app/views/shop/_messages.html.haml
@@ -24,6 +24,6 @@
.row
.small-12.columns
- .alert-box{ "ofn-inline-alert" => true, ng: { show: "visible" } }
+ .alert-box.shopfront-message{ "ofn-inline-alert" => true, ng: { show: "visible" } }
= current_distributor.preferred_shopfront_message.html_safe
%a.close{ ng: { click: "close()" } } ×
diff --git a/app/views/shop/products/_form.html.haml b/app/views/shop/products/_form.html.haml
index f2be498451..1decc0a43c 100644
--- a/app/views/shop/products/_form.html.haml
+++ b/app/views/shop/products/_form.html.haml
@@ -29,11 +29,10 @@
.small-12.medium-6.large-6.large-offset-1.columns
= render partial: "shop/products/filters"
- %div.pad-top{bindonce: true}
- %product.animate-repeat{"ng-controller" => "ProductNodeCtrl",
- "ng-repeat" => "product in filteredProducts = (Products.products | products:query | taxons:activeTaxons | properties: activeProperties) track by product.id ", "id" => "product-{{ product.id }}"}
+ %div.pad-top
+ %product.animate-repeat{"ng-controller" => "ProductNodeCtrl", "ng-repeat" => "product in filteredProducts = (Products.products | products:query | taxons:activeTaxons | properties: activeProperties) track by product.id ", "id" => "product-{{ product.id }}"}
= render "shop/products/summary"
- %shop-variant{variant: 'product.master', "bo-if" => "!product.hasVariants", "id" => "variant-{{ product.master.id }}"}
+ %shop-variant{variant: 'product.master', "ng-if" => "::!product.hasVariants", "id" => "variant-{{ product.master.id }}"}
%shop-variant{variant: 'variant', "ng-repeat" => "variant in product.variants track by variant.id", "id" => "variant-{{ variant.id }}", "ng-class" => "{'out-of-stock': !variant.on_demand && variant.count_on_hand == 0}"}
%product{"ng-show" => "Products.loading"}
diff --git a/app/views/shop/products/_summary.html.haml b/app/views/shop/products/_summary.html.haml
index 8671b00cbf..4dd5b3f224 100644
--- a/app/views/shop/products/_summary.html.haml
+++ b/app/views/shop/products/_summary.html.haml
@@ -1,13 +1,13 @@
.product-thumb
%a{"ng-click" => "triggerProductModal()"}
%i.ofn-i_057-expand
- %img{"bo-src" => "product.primaryImageOrMissing", "ng-click" => "triggerProductModal()"}
+ %img{"ng-src" => "{{::product.primaryImageOrMissing}}", "ng-click" => "triggerProductModal()"}
.row.summary
.small-10.medium-10.large-11.columns.summary-header
%h3
%a{"ng-click" => "triggerProductModal()"}
- %span{"bo-text" => "product.name"}
+ %span{"ng-bind" => "::product.name"}
%i.ofn-i_057-expand
%small
%em
@@ -15,7 +15,7 @@
%span
%enterprise-modal
%i.ofn-i_036-producers
- %span{"bo-bind" => "enterprise.name"}
+ %span{"ng-bind" => "::enterprise.name"}
.small-2.medium-2.large-1.columns.text-center
.taxon-flag
%render-svg{path: "{{product.primary_taxon.icon}}"}
diff --git a/app/views/shopping_shared/_about.html.haml b/app/views/shopping_shared/_about.html.haml
index ffe66c6c82..eac2fe5658 100644
--- a/app/views/shopping_shared/_about.html.haml
+++ b/app/views/shopping_shared/_about.html.haml
@@ -1,8 +1,8 @@
-.content#about{"ng-controller" => "AboutUsCtrl", bindonce: true}
+.content#about{"ng-controller" => "AboutUsCtrl"}
.panel
.row
.small-12.large-8.columns
- %img.hero-img-small{"bo-src" => "CurrentHub.hub.promo_image", "bo-if" => "CurrentHub.hub.promo_image"}
- %p{"bo-html" => "CurrentHub.hub.long_description"}
+ %img.hero-img-small{"ng-src" => "{{::CurrentHub.hub.promo_image}}", "ng-if" => "::CurrentHub.hub.promo_image"}
+ %p{"ng-bind-html" => "::CurrentHub.hub.long_description"}
.small-12.large-4.columns
diff --git a/app/views/shopping_shared/_contact.html.haml b/app/views/shopping_shared/_contact.html.haml
index 100d64d958..405c958768 100644
--- a/app/views/shopping_shared/_contact.html.haml
+++ b/app/views/shopping_shared/_contact.html.haml
@@ -57,6 +57,6 @@
- unless current_distributor.instagram.blank?
%span
- %a{href: "http://instagram.com.#{current_distributor.instagram}", target: "_blank" }
+ %a{href: "http://instagram.com/#{current_distributor.instagram}", target: "_blank" }
%i.ofn-i_043-instagram
/ = current_distributor.instagram
diff --git a/app/views/shopping_shared/_tabs.html.haml b/app/views/shopping_shared/_tabs.html.haml
index 8d0d9e021d..3bcd2aa37e 100644
--- a/app/views/shopping_shared/_tabs.html.haml
+++ b/app/views/shopping_shared/_tabs.html.haml
@@ -1,16 +1,15 @@
-#tabs{"ng-controller" => "TabsCtrl", "ng-cloak" => true}
+#tabs{"ng-controller" => "ShoppingTabsCtrl", "ng-cloak" => true}
.row
- %tabset
+ %tabset{ 'open-on-load' => 'false' }
-# Build all tabs.
- for name, heading_cols in { about: [t(:shopping_tabs_about, distributor: current_distributor.name), 6],
producers: [t(:label_producers),2],
contact: [t(:shopping_tabs_contact),2],
groups: [t(:label_groups),2]}
- -# tabs take tab path in 'active' and 'select' functions defined in TabsCtrl.
- heading, cols = heading_cols
%tab.columns{heading: heading,
id: "tab_#{name}",
- active: "active(\'#{name}\')",
- select: "toggle(\'#{name}\')",
+ active: "tabs.#{name}.active",
+ select: "select(\'#{name}\')",
class: "small-12 medium-#{cols}" }
= render "shopping_shared/#{name}"
diff --git a/app/views/spree/admin/orders/bulk_management.html.haml b/app/views/spree/admin/orders/bulk_management.html.haml
index d8228b5c83..520d2376a4 100644
--- a/app/views/spree/admin/orders/bulk_management.html.haml
+++ b/app/views/spree/admin/orders/bulk_management.html.haml
@@ -3,39 +3,43 @@
- content_for :page_title do
%h1.page-title
- = t "bom_page_title"
+ = t("admin.orders.bulk_management.page_title")
%a{ 'ofn-with-tip' => t("bom_tip") }
- = t "admin.whats_this"
+ = t("admin.whats_this")
= render :partial => 'spree/admin/shared/order_sub_menu'
+= admin_inject_column_preferences module: 'admin.lineItems'
+
%div{ ng: { controller: 'LineItemsCtrl' } }
- %save-bar{ form: "bulk_order_form", buttons: "[{ text: 'Save Changes', action: submit, class: 'red' }]" }
+ %save-bar{ dirty: "bulk_order_form.$dirty", persist: "false" }
+ %input.red{ type: "button", value: "Save Changes", ng: { click: "submit()", disabled: "!bulk_order_form.$dirty" } }
+
.filters{ :class => "sixteen columns alpha" }
.date_filter{ :class => "two columns alpha" }
%label{ :for => 'start_date_filter' }
- = t "start_date"
+ = t("admin.start_date")
%br
%input{ :class => "two columns alpha", :type => "text", :id => 'start_date_filter', 'ng-model' => 'startDate', 'datepicker' => "startDate", 'confirm-change' => "confirmRefresh()", 'ng-change' => 'refreshData()' }
.date_filter{ :class => "two columns" }
%label{ :for => 'end_date_filter' }
- = t "end_date"
+ = t("admin.end_date")
%br
%input{ :class => "two columns alpha", :type => "text", :id => 'end_date_filter', 'ng-model' => 'endDate', 'datepicker' => "endDate", 'confirm-change' => "confirmRefresh()", 'ng-change' => 'refreshData()' }
.one.column
.filter_select{ :class => "three columns" }
%label{ :for => 'supplier_filter' }
- = t "producer"
+ = t("admin.producer")
%br
%select{ :class => "three columns alpha", :id => 'supplier_filter', 'select2-min-search' => 5, 'ng-model' => 'supplierFilter', 'ng-options' => 's.id as s.name for s in suppliers' }
.filter_select{ :class => "three columns" }
%label{ :for => 'distributor_filter' }
- = t "bom_hub"
+ = t("admin.shop")
%br
%select{ :class => "three columns alpha", :id => 'distributor_filter', 'select2-min-search' => 5, 'ng-model' => 'distributorFilter', 'ng-options' => 'd.id as d.name for d in distributors'}
.filter_select{ :class => "three columns" }
%label{ :for => 'order_cycle_filter' }
- = t "order_cycle"
+ = t("admin.order_cycle")
%br
%select{ :class => "three columns alpha", :id => 'order_cycle_filter', 'select2-min-search' => 5, 'ng-model' => 'orderCycleFilter', 'ng-options' => 'oc.id as oc.name for oc in orderCycles', 'confirm-change' => "confirmRefresh()", 'ng-change' => 'refreshData()'}
.filter_clear{ :class => "two columns omega" }
@@ -49,7 +53,7 @@
%div.shared_resource{ :class => "four columns alpha" }
%span{ :class => 'three columns alpha' }
%input{ type: 'checkbox', :id => 'shared_resource', 'ng-model' => 'sharedResource'}
- = t "bom_shared"
+ = t("admin.orders.bulk_management.shared")
%div{ :class => "eight columns" }
%h6{ :class => "eight columns alpha", 'ng-show' => 'sharedResource', style: 'text-align: center;' } {{ selectedUnitsProduct.name + ": ALL" }}
%h6{ :class => "eight columns alpha", 'ng-hide' => 'sharedResource', style: 'text-align: center;' } {{ selectedUnitsVariant.full_name }}
@@ -61,32 +65,32 @@
.one.column.alpha
.two.columns
%span.two.columns
- = t "group_buy_unit_size"
+ = t("admin.orders.bulk_management.group_buy_unit_size")
%span.two.columns {{ formattedValueWithUnitName( selectedUnitsProduct.group_buy_unit_size, selectedUnitsProduct, selectedUnitsVariant ) }}
.one.column
.two.columns
%span.two.columns
- = t "total_qtt_ordered"
+ = t("admin.orders.bulk_management.total_qtt_ordered")
%span.two.columns {{ formattedValueWithUnitName( sumUnitValues(), selectedUnitsProduct, selectedUnitsVariant ) }}
.one.column
.two.columns
%span.two.columns
- = t "max_qtt_ordered"
+ = t("admin.orders.bulk_management.max_qtt_ordered")
%span.two.columns {{ formattedValueWithUnitName( sumMaxUnitValues(), selectedUnitsProduct, selectedUnitsVariant ) }}
.one.column
.two.columns
%span.two.columns
- = t "current_fulfilled_units"
+ = t("admin.orders.bulk_management.current_fulfilled_units")
%span.two.columns {{ fulfilled(sumUnitValues()) }}
.one.column
.two.columns
%span.two.columns
- = t "max_fulfilled_units"
+ = t("admin.orders.bulk_management.max_fulfilled_units")
%span.two.columns {{ fulfilled(sumMaxUnitValues()) }}
.one.column.omega
%div{ :class => "eight columns alpha", 'ng-hide' => 'allFinalWeightVolumesPresent()' }
%span{ :class => "eight columns alpha", style: 'color:red' }
- = t "bulk_management_warning"
+ = t("admin.orders.bulk_management.variants_without_unit_value")
%hr.divider.sixteen.columns.alpha.omega
@@ -95,16 +99,16 @@
%input.fullwidth{ :type => "text", :id => 'quick_search', 'ng-model' => 'quickSearch', :placeholder => 'Quick Search' }
= render 'admin/shared/bulk_actions_dropdown'
%div.seven.columns
- = render 'admin/shared/columns_dropdown'
+ %columns-dropdown{ action: "#{controller_name}_#{action_name}" }
%div.sixteen.columns.alpha#loading{ 'ng-if' => 'RequestMonitor.loading' }
%img.spinner{ src: "/assets/spinning-circles.svg" }
%h1
- =t "bom_loading"
+ = t("admin.orders.bulk_management.loading")
%div{ :class => "sixteen columns alpha", 'ng-show' => '!RequestMonitor.loading && filteredLineItems.length == 0'}
%h1#no_results
- = t "bom_no_results"
+ = t("admin.orders.bulk_management.no_results")
.margin-bottom-50{ 'ng-hide' => 'RequestMonitor.loading || filteredLineItems.length == 0' }
%form{ name: 'bulk_order_form' }
@@ -115,42 +119,42 @@
%input{ :type => "checkbox", :name => 'toggle_bulk', 'ng-click' => 'toggleAllCheckboxes()', 'ng-checked' => "allBoxesChecked()" }
%th.order_no{ 'ng-show' => 'columns.order_no.visible' }
%a{ :href => '', 'ng-click' => "predicate = 'order.number'; reverse = !reverse" }
- = t "order_no"
+ = t("admin.orders.bulk_management.order_no")
%th.full_name{ 'ng-show' => 'columns.full_name.visible' }
%a{ :href => '', 'ng-click' => "predicate = 'order.full_name'; reverse = !reverse" }
- = t "name"
+ = t("admin.name")
%th.email{ 'ng-show' => 'columns.email.visible' }
%a{ :href => '', 'ng-click' => "predicate = 'order.email'; reverse = !reverse" }
- = t "email"
+ = t("admin.email")
%th.phone{ 'ng-show' => 'columns.phone.visible' }
%a{ :href => '', 'ng-click' => "predicate = 'order.phone'; reverse = !reverse" }
- = t "phone"
+ = t("admin.phone")
%th.date{ 'ng-show' => 'columns.order_date.visible' }
%a{ :href => '', 'ng-click' => "predicate = 'order.completed_at'; reverse = !reverse" }
- =t "bom_date"
+ = t("admin.orders.bulk_management.order_date")
%th.producer{ 'ng-show' => 'columns.producer.visible' }
%a{ :href => '', 'ng-click' => "predicate = 'supplier.name'; reverse = !reverse" }
- = t "producer"
+ = t("admin.producer")
%th.order_cycle{ 'ng-show' => 'columns.order_cycle.visible' }
%a{ :href => '', 'ng-click' => "predicate = 'order.order_cycle.name'; reverse = !reverse" }
- = t "bom_cycle"
+ = t("admin.order_cycle")
%th.hub{ 'ng-show' => 'columns.hub.visible' }
%a{ :href => '', 'ng-click' => "predicate = 'order.distributor.name'; reverse = !reverse" }
- = t "bom_hub"
+ = t("admin.shop")
%th.variant{ 'ng-show' => 'columns.variant.visible' }
%a{ :href => '', 'ng-click' => "predicate = 'units_variant.full_name'; reverse = !reverse" }
- = t "bom_variant"
+ = t("admin.orders.bulk_management.product_unit")
%th.quantity{ 'ng-show' => 'columns.quantity.visible' }
- = t "products_quantity"
+ = t("admin.quantity")
%th.max{ 'ng-show' => 'columns.max.visible' }
- = t "shop_variant_quantity_max"
+ = t("admin.orders.bulk_management.max")
%th.final_weight_volume{ 'ng-show' => 'columns.final_weight_volume.visible' }
- = t "weight_volume"
+ = t("admin.orders.bulk_management.weight_volume")
%th.price{ 'ng-show' => 'columns.price.visible' }
- = t "products_price"
+ = t("admin.price")
%th.actions
%th.actions
- = t "ask"
+ = t("admin.orders.bulk_management.ask")
%input{ :type => 'checkbox', 'ng-model' => "confirmDelete" }
%tr.line_item{ 'ng-repeat' => "line_item in filteredLineItems = ( lineItems | filter:quickSearch | selectFilter:supplierFilter:distributorFilter:orderCycleFilter | variantFilter:selectedUnitsProduct:selectedUnitsVariant:sharedResource | orderBy:predicate:reverse )", 'ng-class-even' => "'even'", 'ng-class-odd' => "'odd'", :id => "li_{{line_item.id}}" }
diff --git a/app/views/spree/admin/payment_methods/_providers.html.haml b/app/views/spree/admin/payment_methods/_providers.html.haml
index 77b11fb473..657e21029e 100644
--- a/app/views/spree/admin/payment_methods/_providers.html.haml
+++ b/app/views/spree/admin/payment_methods/_providers.html.haml
@@ -1,10 +1,8 @@
-:javascript
- angular.module('ofn.admin').value('paymentMethod', #{ { id: @payment_method.id, type: @payment_method.type }.to_json })
-#provider-settings{ ng: { app: "ofn.admin", controller: "ProvidersCtrl" } }
+#provider-settings{ ng: { controller: "ProvidersCtrl" } }
.row
.alpha.three.columns
= label :payment_method, :type, t(:provider)
.omega.eight.columns
= collection_select(:payment_method, :type, @providers, :to_s, :clean_name, (!@object.persisted? ? { :selected => "Spree::PaymentMethod::Check"} : {}), { class: 'select2 fullwidth', 'provider-prefs-for' => "#{@object.id}"})
- %div{"ng-include" => "include_html" }
\ No newline at end of file
+ %div{"ng-include" => "include_html" }
diff --git a/app/views/spree/admin/products/bulk_edit/_actions.html.haml b/app/views/spree/admin/products/bulk_edit/_actions.html.haml
index 7ea29bdb4c..40f135d62e 100644
--- a/app/views/spree/admin/products/bulk_edit/_actions.html.haml
+++ b/app/views/spree/admin/products/bulk_edit/_actions.html.haml
@@ -3,4 +3,4 @@
%input.four.columns.alpha{ :type => 'button', :value => 'Save Changes', 'ng-click' => 'submitProducts()'}
.nine.columns
= render 'spree/admin/shared/status_message'
- = render 'admin/shared/columns_dropdown'
+ %columns-dropdown{ action: "#{controller_name}_#{action_name}" }
diff --git a/app/views/spree/admin/products/bulk_edit/_data.html.haml b/app/views/spree/admin/products/bulk_edit/_data.html.haml
index 3624421870..a17f725b11 100644
--- a/app/views/spree/admin/products/bulk_edit/_data.html.haml
+++ b/app/views/spree/admin/products/bulk_edit/_data.html.haml
@@ -2,3 +2,4 @@
= admin_inject_taxons
= admin_inject_tax_categories
= admin_inject_spree_api_key
+= admin_inject_column_preferences
diff --git a/app/views/spree/admin/products/bulk_edit/_products_head.html.haml b/app/views/spree/admin/products/bulk_edit/_products_head.html.haml
index 7767b8de89..50789a2f82 100644
--- a/app/views/spree/admin/products/bulk_edit/_products_head.html.haml
+++ b/app/views/spree/admin/products/bulk_edit/_products_head.html.haml
@@ -21,18 +21,18 @@
%th.left-actions
%a{ 'ng-click' => 'toggleShowAllVariants()', :style => 'color: red' }
Expand All
- %th.producer{ 'ng-show' => 'columns.producer.visible' } Producer
- %th.sku{ 'ng-show' => 'columns.sku.visible' } SKU
- %th.name{ 'ng-show' => 'columns.name.visible' } Name
- %th.unit{ 'ng-show' => 'columns.unit.visible' } Unit / Value
- %th.display_as{ 'ng-show' => 'columns.unit.visible' } Display As
- %th.price{ 'ng-show' => 'columns.price.visible' } Price
- %th.on_hand{ 'ng-show' => 'columns.on_hand.visible' } On Hand
- %th.on_demand{ 'ng-show' => 'columns.on_demand.visible' } On Demand
- %th.category{ 'ng-show' => 'columns.category.visible' } Category
- %th.tax_category{ 'ng-show' => 'columns.tax_category.visible' } Tax Category
- %th.inherits_properties{ 'ng-show' => 'columns.inherits_properties.visible' } Inherits Properties?
- %th.available_on{ 'ng-show' => 'columns.available_on.visible' } Av. On
+ %th.producer{ 'ng-show' => 'columns.producer.visible' }=t('admin.producer')
+ %th.sku{ 'ng-show' => 'columns.sku.visible' }=t('admin.sku')
+ %th.name{ 'ng-show' => 'columns.name.visible' }=t('admin.name')
+ %th.unit{ 'ng-show' => 'columns.unit.visible' }=t('admin.products.bulk_edit.unit')
+ %th.display_as{ 'ng-show' => 'columns.unit.visible' }=t('admin.products.bulk_edit.display_as')
+ %th.price{ 'ng-show' => 'columns.price.visible' }=t('admin.price')
+ %th.on_hand{ 'ng-show' => 'columns.on_hand.visible' }=t('admin.on_hand')
+ %th.on_demand{ 'ng-show' => 'columns.on_demand.visible' }=t('admin.on_demand?')
+ %th.category{ 'ng-show' => 'columns.category.visible' }=t('admin.products.bulk_edit.category')
+ %th.tax_category{ 'ng-show' => 'columns.tax_category.visible' }=t('admin.products.bulk_edit.tax_category')
+ %th.inherits_properties{ 'ng-show' => 'columns.inherits_properties.visible' }=t('admin.products.bulk_edit.inherits_proerties?')
+ %th.available_on{ 'ng-show' => 'columns.available_on.visible' }=t('admin.products.bulk_edit.av_on')
%th.actions
%th.actions
%th.actions
diff --git a/app/views/spree/admin/products/bulk_edit/_products_product.html.haml b/app/views/spree/admin/products/bulk_edit/_products_product.html.haml
index 6ac25ae286..01af8014d1 100644
--- a/app/views/spree/admin/products/bulk_edit/_products_product.html.haml
+++ b/app/views/spree/admin/products/bulk_edit/_products_product.html.haml
@@ -18,8 +18,8 @@
%td.price{ 'ng-show' => 'columns.price.visible' }
%input{ 'ng-model' => 'product.price', 'ofn-decimal' => :true, :name => 'price', 'ofn-track-product' => 'price', :type => 'text', 'ng-hide' => 'hasVariants(product)' }
%td.on_hand{ 'ng-show' => 'columns.on_hand.visible' }
- %span{ 'ng-bind' => 'product.on_hand', :name => 'on_hand', 'ng-show' => '!hasOnDemandVariants(product) && (hasVariants(product) || product.on_demand)' }
- %input.field{ 'ng-model' => 'product.on_hand', :name => 'on_hand', 'ofn-track-product' => 'on_hand', 'ng-hide' => 'hasVariants(product) || product.on_demand', :type => 'number' }
+ %span{ 'ng-bind' => 'product.on_hand', :name => 'on_hand', 'ng-if' => '!hasOnDemandVariants(product) && (hasVariants(product) || product.on_demand)' }
+ %input.field{ 'ng-model' => 'product.on_hand', :name => 'on_hand', 'ofn-track-product' => 'on_hand', 'ng-if' => '!(hasVariants(product) || product.on_demand)', :type => 'number' }
%td.on_demand{ 'ng-show' => 'columns.on_demand.visible' }
%input.field{ 'ng-model' => 'product.on_demand', :name => 'on_demand', 'ofn-track-product' => 'on_demand', :type => 'checkbox', 'ng-hide' => 'hasVariants(product)' }
%td.category{ 'ng-if' => 'columns.category.visible' }
diff --git a/app/views/spree/admin/products/bulk_edit/_products_variant.html.haml b/app/views/spree/admin/products/bulk_edit/_products_variant.html.haml
index fb68704b79..f2e65d6c88 100644
--- a/app/views/spree/admin/products/bulk_edit/_products_variant.html.haml
+++ b/app/views/spree/admin/products/bulk_edit/_products_variant.html.haml
@@ -14,8 +14,8 @@
%td{ 'ng-show' => 'columns.price.visible' }
%input{ 'ng-model' => 'variant.price', 'ofn-decimal' => :true, :name => 'variant_price', 'ofn-track-variant' => 'price', :type => 'text' }
%td{ 'ng-show' => 'columns.on_hand.visible' }
- %input.field{ 'ng-model' => 'variant.on_hand', 'ng-change' => 'updateOnHand(product)', :name => 'variant_on_hand', 'ng-hide' => 'variant.on_demand', 'ofn-track-variant' => 'on_hand', :type => 'number' }
- %span{ 'ng-bind' => 'variant.on_hand', :name => 'variant_on_hand', 'ng-show' => 'variant.on_demand' }
+ %input.field{ 'ng-model' => 'variant.on_hand', 'ng-change' => 'updateOnHand(product)', :name => 'variant_on_hand', 'ng-if' => '!variant.on_demand', 'ofn-track-variant' => 'on_hand', :type => 'number' }
+ %span{ 'ng-bind' => 'variant.on_hand', :name => 'variant_on_hand', 'ng-if' => 'variant.on_demand' }
%td{ 'ng-show' => 'columns.on_demand.visible' }
%input.field{ 'ng-model' => 'variant.on_demand', :name => 'variant_on_demand', 'ofn-track-variant' => 'on_demand', :type => 'checkbox' }
%td{ 'ng-show' => 'columns.category.visible' }
diff --git a/app/views/spree/users/_fat.html.haml b/app/views/spree/users/_fat.html.haml
index 5c87077b4c..4ce051e5b8 100644
--- a/app/views/spree/users/_fat.html.haml
+++ b/app/views/spree/users/_fat.html.haml
@@ -12,20 +12,20 @@
%tbody.transaction-group{"ng-repeat" => "order in distributor.distributed_orders", "ng-class-odd"=>"'odd'", "ng-class-even"=>"'even'"}
%tr.order-row
%td.order1
- %a{"bo-href" => "order.path", "bo-text" => "('order' | t )+ ' ' + order.number"}
- %td.order2{"bo-text" => "order.completed_at"}
- %td.order3.show-for-large-up{"bo-text" => "'spree.payment_states.' + order.payment_state | t | capitalize"}
- %td.order4.show-for-large-up{"bo-text" => "'spree.shipment_states.' + order.shipment_state | t | capitalize"}
- %td.order5.text-right{"ng-class" => "{'credit' : order.total < 0, 'debit' : order.total > 0, 'paid' : order.total == 0}","bo-text" => "order.total | localizeCurrency"}
- %td.order6.text-right.show-for-large-up{"ng-class" => "{'credit' : order.outstanding_balance < 0, 'debit' : order.outstanding_balance > 0, 'paid' : order.outstanding_balance == 0}", "bo-text" => "order.outstanding_balance | localizeCurrency"}
- %td.order7.text-right{"ng-class" => "{'credit' : order.running_balance < 0, 'debit' : order.running_balance > 0, 'paid' : order.running_balance == 0}", "bo-text" => "order.running_balance | localizeCurrency"}
- %tr.payment-row{"ng-repeat" => "payment in order.payments", "ng-class" => "{'invalid': payment.state != 'completed'}"}
- %td.order1{"bo-text" => "payment.payment_method"}
- %td.order2{"bo-text" => "payment.updated_at"}
- %td.order3.show-for-large-up
- %i{"ng-class" => "{'ofn-i_012-warning': payment.state == 'invalid' || payment.state == 'void' || payment.state == 'failed'}"}
- %span{"bo-text" => "'spree.payment_states.' + payment.state | t | capitalize"}
- %td.order4.show-for-large-up
- %td.order5.text-right{"ng-class" => "{'credit' : payment.amount > 0, 'debit' : payment.amount < 0, 'paid' : payment.amount == 0}","bo-text" => "payment.amount | localizeCurrency"}
- %td.order6.show-for-large-up
- %td.order7
+ %a{"ng-href" => "{{::order.path}}", "ng-bind" => "::('order' | t )+ ' ' + order.number"}
+ %td.order2{"ng-bind" => "::order.completed_at"}
+ %td.order3.show-for-large-up{"ng-bind" => "::'spree.payment_states.' + order.payment_state | t | capitalize"}
+ %td.order4.show-for-large-up{"ng-bind" => "::'spree.shipment_states.' + order.shipment_state | t | capitalize"}
+ %td.order5.text-right{"ng-class" => "{'credit' : order.total < 0, 'debit' : order.total > 0, 'paid' : order.total == 0}","ng-bind" => "::order.total | localizeCurrency"}
+ %td.order6.text-right.show-for-large-up{"ng-class" => "{'credit' : order.outstanding_balance < 0, 'debit' : order.outstanding_balance > 0, 'paid' : order.outstanding_balance == 0}", "ng-bind" => "::order.outstanding_balance | localizeCurrency"}
+ %td.order7.text-right{"ng-class" => "{'credit' : order.running_balance < 0, 'debit' : order.running_balance > 0, 'paid' : order.running_balance == 0}", "ng-bind" => "::order.running_balance | localizeCurrency"}
+ %tr.payment-row{"ng-repeat" => "payment in order.payments", "ng-class" => "{'invalid': payment.state != 'completed'}"}
+ %td.order1{"ng-bind" => "::payment.payment_method"}
+ %td.order2{"ng-bind" => "::payment.updated_at"}
+ %td.order3.show-for-large-up
+ %i{"ng-class" => "{'ofn-i_012-warning': payment.state == 'invalid' || payment.state == 'void' || payment.state == 'failed'}"}
+ %span{"ng-bind" => "::'spree.payment_states.' + payment.state | t | capitalize"}
+ %td.order4.show-for-large-up
+ %td.order5.text-right{"ng-class" => "{'credit' : payment.amount > 0, 'debit' : payment.amount < 0, 'paid' : payment.amount == 0}","ng-bind" => "::payment.amount | localizeCurrency"}
+ %td.order6.show-for-large-up
+ %td.order7
diff --git a/app/views/spree/users/_skinny.html.haml b/app/views/spree/users/_skinny.html.haml
index 14c04f024a..4894824296 100644
--- a/app/views/spree/users/_skinny.html.haml
+++ b/app/views/spree/users/_skinny.html.haml
@@ -4,9 +4,9 @@
%img.account-logo{"logo-fallback" => true, "ng-src" => "{{distributor.logo}}"}
.columns.small-10.medium-5
%span.margin-top
- %strong{"bo-text" => "distributor.name"}
+ %strong{"ng-bind" => "::distributor.name"}
.columns.small-8.small-offset-2.medium-3.text-right
- %span.margin-top.distributor-balance{"bo-text" => "distributor.balance | formatBalance", "ng-class" => "{'credit' : distributor.balance < 0, 'debit' : distributor.balance > 0, 'paid' : distributor.balance == 0}" }
+ %span.margin-top.distributor-balance{"ng-bind" => "::distributor.balance | formatBalance", "ng-class" => "{'credit' : distributor.balance < 0, 'debit' : distributor.balance > 0, 'paid' : distributor.balance == 0}" }
.columns.small-2.medium-2.text-right
%span.margin-top
%i{"ng-class" => "{'ofn-i_005-caret-down' : !open(), 'ofn-i_006-caret-up' : open()}"}
diff --git a/app/views/spree/users/show.html.haml b/app/views/spree/users/show.html.haml
index 83171daf0d..0b4800f79e 100644
--- a/app/views/spree/users/show.html.haml
+++ b/app/views/spree/users/show.html.haml
@@ -9,7 +9,7 @@
(#{link_to t(:edit), spree.edit_account_path})
%h3= t(:my_orders)
.orders{"ng-controller" => "OrdersCtrl", "ng-cloak" => true}
- .row{bindonce: true}
+ .row
.small-12.columns
.active_table
%distributor.active_table_node.row.animate-repeat{"ng-if" => "Orders.orders_by_distributor.length > 0", "ng-repeat" => "(key, distributor) in Orders.orders_by_distributor",
@@ -19,6 +19,6 @@
.small-12.columns
= render partial: "spree/users/skinny"
= render partial: "spree/users/fat"
- .message{"ng-if" => "Orders.orders_by_distributor.length == 0", "bo-text" => "'you_have_no_orders_yet' | t"}
+ .message{"ng-if" => "Orders.orders_by_distributor.length == 0", "ng-bind" => "::'you_have_no_orders_yet' | t"}
= render partial: "shared/footer"
diff --git a/config/application.rb b/config/application.rb
index 1fb6c88bbf..6ffee13160 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -34,6 +34,11 @@ module Openfoodnetwork
Spree::Calculator::PerItem,
Spree::Calculator::PriceSack,
OpenFoodNetwork::Calculator::Weight]
+ app.config.spree.calculators.payment_methods = [Spree::Calculator::FlatPercentItemTotal,
+ Spree::Calculator::FlatRate,
+ Spree::Calculator::FlexiRate,
+ Spree::Calculator::PerItem,
+ Spree::Calculator::PriceSack]
end
# Register Spree payment methods
diff --git a/config/initializers/spree.rb b/config/initializers/spree.rb
index a6a045222d..92ba3db249 100644
--- a/config/initializers/spree.rb
+++ b/config/initializers/spree.rb
@@ -40,9 +40,7 @@ module Spree
module Core
class Environment
class Calculators
- include EnvironmentExtension
-
- attr_accessor :enterprise_fees
+ attr_accessor :enterprise_fees, :payment_methods
end
end
end
diff --git a/config/locales/en-US.yml b/config/locales/en-US.yml
new file mode 100644
index 0000000000..e86100ba93
--- /dev/null
+++ b/config/locales/en-US.yml
@@ -0,0 +1,1048 @@
+# English language file
+# ---------------------
+#
+# Originally adapted from source language file maintained by the Australian OFN team.
+# Visit Transifex to translate this file into other languages:
+#
+# https://www.transifex.com/open-food-foundation/open-food-network/
+#
+# If you translate this file in a text editor, please share your results with us by
+#
+# - uploading the file to Transifex or
+# - opening a pull request at GitHub.
+#
+#
+# See http://community.openfoodnetwork.org/t/localisation-ofn-in-your-language/397
+
+en-US:
+ activerecord:
+ # Overridden here due to a bug in spree i18n (Issue #870)
+ weight_measurement_unit: "Weight (per lb)"
+ attributes:
+ spree/order:
+ payment_state: Payment State
+ shipment_state: Shipment State
+ devise:
+ failure:
+ invalid: |
+ Invalid email or password.
+ Were you a guest last time? Perhaps you need to create an account or reset your password.
+ enterprise_confirmations:
+ enterprise:
+ confirmed: Thankyou, your email address has been confirmed.
+ not_confirmed: Your email address could not be confirmed. Perhaps you have already completed this step?
+ confirmation_sent: "Confirmation email sent!"
+ confirmation_not_sent: "Could not send a confirmation email."
+ home: "OFN"
+ title: Open Food Network
+ welcome_to: 'Welcome to '
+ site_meta_description: "We begin from the ground up. With farmers and growers ready to tell their stories proudly and truly. With distributors ready to connect people with products fairly and honestly. With buyers who believe that better weekly shopping decisions can…"
+ search_by_name: Search by name or city...
+ producers: 'US Producers'
+ producers_join: US producers are now welcome to join the Open Food Network. #FIXME
+ charges_sales_tax: Charges GST?
+ print_invoice: "Print Invoice"
+ send_invoice: "Send Invoice"
+ resend_confirmation: "Resend Confirmation"
+ view_order: "View Order"
+ edit_order: "Edit Order"
+ ship_order: "Ship Order"
+ cancel_order: "Cancel Order"
+ confirm_send_invoice: "An invoice for this order will be sent to the customer. Are you sure you want to continue?"
+ confirm_resend_order_confirmation: "Are you sure you want to resend the order confirmation email?"
+ must_have_valid_business_number: "%{enterprise_name} must have a valid ABN before invoices can be sent."
+ invoice: "Invoice"
+ percentage_of_sales: "%{percentage} of sales"
+ percentage_of_turnover: "Percentage of turnover"
+ monthly_cap_excl_tax: "monthly cap (excl. GST)"
+ capped_at_cap: "capped at %{cap}"
+ per_month: "per month"
+ free: "free"
+ plus_tax: "plus GST"
+ total_monthly_bill_incl_tax: "Total Monthly Bill (Incl. Tax)"
+ say_no: "No"
+ say_yes: "Yes"
+
+ sort_order_cycles_on_shopfront_by: "Sort Order Cycles On Shopfront By"
+
+
+ admin:
+ # General form elements
+ quick_search: Quick Search
+ clear_all: Clear All
+ producer: Producer
+ shop: Shop
+ product: Product
+ variant: Variant
+
+ columns: Columns
+ actions: Actions
+ viewing: "Viewing: %{current_view_name}"
+
+ whats_this: What's this?
+
+ tag_has_rules: "Existing rules for this tag: %{num}"
+ has_one_rule: "has one rule"
+ has_n_rules: "has %{num} rules"
+
+ customers:
+ index:
+ add_customer: "Add customer"
+ customer_placeholder: "customer@example.org"
+ inventory:
+ title: Inventory
+ description: Use this page to manage inventories for your enterprises. Any product details set here will override those set on the 'Products' page
+ sku: SKU
+ price: Price
+ on_hand: On Hand
+ on_demand: On Demand?
+ enable_reset: Enable Stock Level Reset?
+ inherit: Inherit?
+ add: Add
+ hide: Hide
+ select_a_shop: Select A Shop
+ review_now: Review Now
+ new_products_alert_message: There are %{new_product_count} new products available to add to your inventory.
+ currently_empty: Your inventory is currently empty
+ no_matching_products: No matching products found in your inventory
+ no_hidden_products: No products have been hidden from this inventory
+ no_matching_hidden_products: No hidden products match your search criteria
+ no_new_products: No new products are available to add to this inventory
+ no_matching_new_products: No new products match your search criteria
+ inventory_powertip: This is your inventory of products. To add products to your inventory, select 'New Products' from the Viewing dropdown.
+ hidden_powertip: These products have been hidden from your inventory and will not be available to add to your shop. You can click 'Add' to add a product to you inventory.
+ new_powertip: These products are available to be added to your inventory. Click 'Add' to add a product to your inventory, or 'Hide' to hide it from view. You can always change your mind later!
+
+
+ order_cycle:
+ choose_products_from: "Choose Products From:"
+
+ enterprise:
+ select_outgoing_oc_products_from: Select outgoing OC products from
+
+ enterprises:
+ form:
+ primary_details:
+ shopfront_requires_login: "Shopfront requires login?"
+ shopfront_requires_login_tip: "Choose whether customers must login to view the shopfront."
+ shopfront_requires_login_false: "Public"
+ shopfront_requires_login_true: "Require customers to login"
+
+ home:
+ hubs:
+ show_closed_shops: "Show closed shops"
+ hide_closed_shops: "Hide closed shops"
+ show_on_map: "Show all on the map"
+ shared:
+ register_call:
+ selling_on_ofn: "Interested in getting on the Open Food Network?"
+ register: "Register here"
+ shop:
+ messages:
+ login: "login"
+ register: "register"
+ contact: "contact"
+ require_customer_login: "This shop is for customers only."
+ require_login_html: "Please %{login} if you have an account already. Otherwise, %{register} to become a customer."
+ require_customer_html: "Please %{contact} %{enterprise} to become a customer."
+
+ # Printable Invoice Columns
+ invoice_column_item: "Item"
+ invoice_column_qty: "Qty"
+ invoice_column_tax: "GST"
+ invoice_column_price: "Price"
+
+ logo: "Logo (640x130)" #FIXME
+ logo_mobile: "Mobile logo (75x26)" #FIXME
+ logo_mobile_svg: "Mobile logo (SVG)" #FIXME
+ home_hero: "Hero image"
+ home_show_stats: "Show statistics"
+ footer_logo: "Logo (220x76)" #FIXME
+ footer_facebook_url: "Facebook URL"
+ footer_twitter_url: "Twitter URL"
+ footer_instagram_url: "Instagram URL"
+ footer_linkedin_url: "LinkedIn URL"
+ footer_googleplus_url: "Google Plus URL"
+ footer_pinterest_url: "Pinterest URL"
+ footer_email: "Email"
+ footer_links_md: "Links"
+ footer_about_url: "About URL"
+ footer_tos_url: "Terms of Service URL"
+
+ name: Name
+ first_name: First Name
+ last_name: Last Name
+ email: Email
+ phone: Phone
+ next: Next
+ address: Address
+ address2: Address (contd.)
+ city: City
+ state: State
+ postcode: Postcode
+ country: Country
+ unauthorized: Unauthorized
+ terms_of_service: "Terms of service"
+ on_demand: On demand
+ none: None
+
+ label_shops: "Shops"
+ label_map: "Map"
+ label_producers: "Producers"
+ label_groups: "Groups"
+ label_about: "About"
+ label_shopping: "Shopping"
+ label_login: "Login"
+ label_logout: "Logout"
+ label_signup: "Sign up"
+ label_administration: "Administration"
+ label_admin: "Admin"
+ label_account: "Account"
+ label_more: "Show more"
+ label_less: "Show less"
+ label_notices: "Notices"
+
+ items: "items"
+ cart_headline: "Your shopping cart"
+ total: "Total"
+ checkout: "Checkout now"
+ cart_updating: "Updating cart..."
+ cart_empty: "Cart empty"
+ cart_edit: "Edit your cart"
+
+ card_number: Card Number
+ card_securitycode: "Security Code"
+ card_expiry_date: Expiry Date
+
+ ofn_cart_headline: "Current cart for:"
+ ofn_cart_distributor: "Distributor:"
+ ofn_cart_oc: "Order cycle:"
+ ofn_cart_from: "From:"
+ ofn_cart_to: "To:"
+ ofn_cart_product: "Product:"
+ ofn_cart_quantitiy: "Quantity:"
+ ofn_cart_send: "Buy me"
+
+ ie_warning_headline: "Your browser is out of date :-("
+ ie_warning_text: "For the best Open Food Network experience, we strongly recommend upgrading your browser:"
+ ie_warning_chrome: Download Chrome
+ ie_warning_firefox: Download Firefox
+ ie_warning_ie: Upgrade Internet Explorer
+ ie_warning_other: "Can't upgrade your browser? Try Open Food Network on your smartphone :-)"
+
+ footer_global_headline: "OFN Global"
+ footer_global_home: "Home"
+ footer_global_news: "News"
+ footer_global_about: "About"
+ footer_global_contact: "Contact"
+
+ footer_sites_headline: "OFN Sites"
+ footer_sites_developer: "Developer"
+ footer_sites_community: "Community"
+ footer_sites_userguide: "User Guide"
+
+ footer_secure: "Secure and trusted."
+ footer_secure_text: "Open Food Network uses SSL encryption (2048 bit RSA) everywhere to keep your shopping and payment information private. Our servers do not store your credit card details and payments are processed by PCI-compliant services."
+
+ footer_contact_headline: "Keep in touch"
+ footer_contact_email: "Email us"
+
+ footer_nav_headline: "Navigate"
+ footer_join_headline: "Join us"
+ footer_join_producers: "Producers sign-up"
+ footer_join_hubs: "Hubs sign-up"
+ footer_join_groups: "Groups sign-up"
+ footer_join_partners: "Food systems partners"
+
+ footer_legal_call: "Read our"
+ footer_legal_tos: "Terms and conditions"
+ footer_legal_visit: "Find us on"
+ footer_legal_text_html: "Open Food Network is a free and open source software platform. Our content is licensed with %{content_license} and our code with %{code_license}."
+
+ home_shop: Shop Now
+
+ brandstory_headline: "Food, unincorporated."
+ brandstory_intro: "Sometimes the best way to fix the system is to start a new one…"
+ brandstory_part1: "We begin from the ground up. With farmers and growers ready to tell their stories proudly and truly. With distributors ready to connect people with products fairly and honestly. With buyers who believe that better weekly shopping decisions can seriously change the world."
+ brandstory_part2: "Then we need a way to make it real. A way to empower everyone who grows, sells and buys food. A way to tell all the stories, to handle all the logistics. A way to turn transaction into transformation every day."
+ brandstory_part3: "So we build an online marketplace that levels the playing field. It’s transparent, so it creates real relationships. It’s open source, so it’s owned by everyone. It scales to regions and nations, so people start versions across the world."
+ brandstory_part4: "It works everywhere. It changes everything."
+ brandstory_part5_strong: "We call it Open Food Network."
+ brandstory_part6: "We all love food. Now we can love our food system too."
+
+ system_headline: "Here's how it works."
+ system_step1: "1. Search"
+ system_step1_text: "Search our diverse, independent shops for seasonal local food. Search by neighbourhood and food category, or whether you prefer delivery or pickup."
+ system_step2: "2. Shop"
+ system_step2_text: "Transform your transactions with affordable local food from diverse producers and hubs. Know the stories behind your food and the people who make it!"
+ system_step3: "3. Pick-up / Delivery"
+ system_step3_text: "Hang on for your delivery, or visit your producer or hub for a more personal connection with your food. Food shopping as diverse as nature intended it."
+
+ cta_headline: "Shopping that makes the world a better place."
+ cta_label: "I'm Ready"
+
+ stats_headline: "We're creating a new food system."
+ stats_producers: "food producers"
+ stats_shops: "food shops"
+ stats_shoppers: "food shoppers"
+ stats_orders: "food orders"
+
+ checkout_title: Checkout
+ checkout_now: Checkout now
+ checkout_order_ready: Order ready for
+ checkout_hide: Hide
+ checkout_expand: Expand
+ checkout_headline: "Ok, ready to checkout?"
+ checkout_as_guest: "Checkout as guest"
+ checkout_details: "Your details"
+ checkout_billing: "Billing info"
+ checkout_shipping: Shipping info
+ checkout_method_free: Free
+ checkout_address_same: Shipping address same as billing address?
+ checkout_ready_for: "Ready for:"
+ checkout_instructions: "Any comments or special instructions?"
+ checkout_payment: Payment
+ checkout_send: Place order now
+ checkout_your_order: Your order
+ checkout_cart_total: Cart total
+ checkout_shipping_price: Shipping
+ checkout_total_price: Total
+ checkout_back_to_cart: "Back to Cart"
+
+ order_paid: PAID
+ order_not_paid: NOT PAID
+ order_total: Total order
+ order_payment: "Paying via:"
+ order_billing_address: Billing address
+ order_delivery_on: Delivery on
+ order_delivery_address: Delivery address
+ order_special_instructions: "Your notes:"
+ order_pickup_time: Ready for collection
+ order_pickup_instructions: Collection Instructions
+ order_produce: Produce
+ order_total_price: Total
+ order_includes_tax: (includes tax)
+ order_payment_paypal_successful: Your payment via PayPal has been processed successfully.
+ order_hub_info: Hub Info
+
+ bom_tip: "Use this page to alter product quantities across multiple orders. Products may also be removed from orders entirely, if required."
+ bom_shared: "Shared Resource?"
+ bom_page_title: "Bulk Order Management"
+ bom_no: "Order no."
+ bom_date: "Order date"
+ bom_cycle: "Order cycle"
+ bom_max: "Max"
+ bom_hub: "Hub"
+ bom_variant: "Product: Unit"
+ bom_final_weigth_volume: "Weight/Volume"
+ bom_quantity: "Quantity"
+ bom_actions_delete: "Delete Selected"
+ bom_loading: "Loading orders"
+ bom_no_results: "No orders found."
+ bom_order_error: "Some errors must be resolved before you can update orders.\nAny fields with red borders contain errors."
+
+ unsaved_changes_warning: "Unsaved changes exist and will be lost if you continue."
+ unsaved_changes_error: "Fields with red borders contain errors."
+
+ products: "Products"
+ products_in: "in %{oc}"
+ products_at: "at %{distributor}"
+ products_elsewhere: "Products found elsewhere"
+
+ email_welcome: "Welcome"
+ email_confirmed: "Thank you for confirming your email address."
+ email_registered: "is now part of"
+ email_userguide_html: "The User Guide with detailed support for setting up your Producer or Hub is here:
+%{link}"
+ email_admin_html: "You can manage your account by logging into the %{link} or by clicking on the cog in the top right hand side of the homepage, and selecting Administration."
+ email_community_html: "We also have an online forum for community discussion related to OFN software and the unique challenges of running a food enterprise. You are encouraged to join in. We are constantly evolving and your input into this forum will shape what happens next.
+%{link}"
+ email_help: "If you have any difficulties, check out our FAQs, browse the forum or post a 'Support' topic and someone will help you out!"
+ email_confirmation_greeting: "Hi, %{contact}!"
+ email_confirmation_profile_created: "A profile for %{name} has been successfully created!
+To activate your Profile we need to confirm this email address."
+ email_confirmation_click_link: "Please click the link below to confirm your email and to continue setting up your profile."
+ email_confirmation_link_label: "Confirm this email address »"
+ email_confirmation_help_html: "After confirming your email you can access your administration account for this enterprise.
+See the %{link} to find out more about %{sitename}'s features and to start using your profile or online store."
+ email_confirmation_userguide: "User Guide"
+ email_social: "Connect with Us:"
+ email_contact: "Email us:"
+ email_signoff: "Cheers,"
+ email_signature: "%{sitename} Team"
+
+ email_confirm_customer_greeting: "Hi %{name},"
+ email_confirm_customer_intro_html: "Thanks for shopping at %{distributor}!"
+ email_confirm_customer_number_html: "Order confirmation #%{number}"
+ email_confirm_customer_details_html: "Here are your order details from %{distributor}:"
+ email_confirm_customer_signoff: "Kind regards,"
+ email_confirm_shop_greeting: "Hi %{name},"
+ email_confirm_shop_order_html: "Well done! You have a new order for %{distributor}!"
+ email_confirm_shop_number_html: "Order confirmation #%{number}"
+ email_order_summary_item: "Item"
+ email_order_summary_quantity: "Qty"
+ email_order_summary_price: "Price"
+ email_order_summary_subtotal: "Subtotal:"
+ email_order_summary_total: "Total:"
+ email_payment_paid: PAID
+ email_payment_not_paid: NOT PAID
+ email_payment_summary: Payment summary
+ email_payment_method: "Paying via:"
+ email_shipping_delivery_details: Delivery details
+ email_shipping_delivery_time: "Delivery on:"
+ email_shipping_delivery_address: "Delivery address:"
+ email_shipping_collection_details: Collection details
+ email_shipping_collection_time: "Ready for collection:"
+ email_shipping_collection_instructions: "Collection instructions:"
+ email_special_instructions: "Your notes:"
+
+ email_signup_greeting: Hello!
+ email_signup_welcome: "Welcome to %{sitename}!"
+ email_signup_login: Your login
+ email_signup_email: Your login email is
+ email_signup_shop_html: "You can start shopping online now at %{link}."
+ email_signup_text: "Thanks for joining the network.
+ If you are a customer, we look forward to introducing you to many fantastic farmers, wonderful food hubs and delicious food!
+ If you are a producer or food enterprise, we are excited to have you as a part of the network."
+ email_signup_help_html: "We welcome all your questions and feedback; you can use the Send Feedback button on the site or email us at"
+
+ shopping_oc_closed: Orders are closed
+ shopping_oc_closed_description: "Please wait until the next cycle opens (or contact us directly to see if we can accept any late orders)"
+ shopping_oc_last_closed: "The last cycle closed %{distance_of_time} ago"
+ shopping_oc_next_open: "The next cycle opens in %{distance_of_time}"
+ shopping_tabs_about: "About %{distributor}"
+ shopping_tabs_contact: "Contact"
+ shopping_contact_address: "Address"
+ shopping_contact_web: "Contact"
+ shopping_contact_social: "Follow"
+ shopping_groups_part_of: "is part of:"
+ shopping_producers_of_hub: "%{hub}'s producers:"
+
+ enterprises_next_closing: "Next order closing"
+ enterprises_ready_for: "Ready for"
+ enterprises_choose: "Choose when you want your order:"
+
+ hubs_buy: "Shop for:"
+ hubs_shopping_here: "Shopping here"
+ hubs_orders_closed: "Orders closed"
+ hubs_profile_only: "Profile only"
+ hubs_delivery_options: "Delivery options"
+ hubs_pickup: "Pickup"
+ hubs_delivery: "Delivery"
+ hubs_producers: "Our producers"
+ hubs_filter_by: "Filter by"
+ hubs_filter_type: "Type"
+ hubs_filter_delivery: "Delivery"
+ hubs_matches: "Did you mean?"
+ hubs_intro: Shop in your local area
+ hubs_distance: Closest to
+ hubs_distance_filter: "Show me shops near %{location}"
+
+ products_clear_all: Clear all
+ products_showing: "Showing:"
+ products_with: with
+ products_search: "Search by product or producer"
+ products_loading: "Loading products..."
+ products_updating_cart: "Updating cart..."
+ products_cart_empty: "Cart empty"
+ products_edit_cart: "Edit your cart"
+ products_from: from
+ products_change: "No changes to save."
+ products_update_error: "Saving failed with the following error(s):"
+ products_update_error_msg: "Saving failed."
+ products_update_error_data: "Save failed due to invalid data:"
+ products_changes_saved: "Changes saved."
+
+ search_no_results_html: "Sorry, no results found for %{query}. Try another search?"
+
+ components_profiles_popover: "Profiles do not have a shopfront on the Open Food Network, but may have their own physical or online shop elsewhere"
+ components_profiles_show: "Show profiles"
+ components_filters_nofilters: "No filters"
+ components_filters_clearfilters: "Clear all filters"
+
+ groups_title: Groups
+ groups_headline: Groups / regions
+ groups_text: "Every producer is unique. Every business has something different to offer. Our groups are collectives of producers, hubs and distributors who share something in common like location, farmers market or philosophy. This makes your shopping experience easier. So explore our groups and have the curating done for you."
+ groups_search: "Search name or keyword"
+ groups_no_groups: "No groups found"
+ groups_about: "About Us"
+
+ groups_producers: "Our producers"
+ groups_hubs: "Our hubs"
+ groups_contact_web: Contact
+ groups_contact_social: Follow
+ groups_contact_address: Address
+ groups_contact_email: Email us
+ groups_contact_website: Visit our website
+ groups_contact_facebook: Follow us on Facebook
+ groups_signup_title: Sign up as a group
+ groups_signup_headline: Groups sign up
+ groups_signup_intro: "We're an amazing platform for collaborative marketing, the easiest way for your members and stakeholders to reach new markets. We're non-profit, affordable, and simple."
+ groups_signup_email: Email us
+ groups_signup_motivation1: We transform food systems fairly.
+ groups_signup_motivation2: It's why we get out of bed every day. We're a global non-profit, based on open source code. We play fair. You can always trust us.
+ groups_signup_motivation3: We know you have big ideas, and we want to help. We'll share our knowledge, networks and resources. We know that isolation doesn't create change, so we'll partner with you.
+ groups_signup_motivation4: We meet you where you are.
+ groups_signup_motivation5: You might be an alliance of food hubs, producers, or distributors, and an industry body, or a local government.
+ groups_signup_motivation6: Whatever your role in your local food movement, we're ready to help. However you come to wonder what Open Food Network would look like or is doing in your part of the world, let's start the conversation.
+ groups_signup_motivation7: We make food movements make more sense.
+ groups_signup_motivation8: You need to activate and enable your networks, we offer a platform for conversation and action. You need real engagement. We’ll help reach all the players, all the stakeholders, all the sectors.
+ groups_signup_motivation9: You need resourcing. We’ll bring all our experience to bear. You need cooperation. We’ll better connect you to a global network of peers.
+ groups_signup_pricing: Group Account
+ groups_signup_studies: Case Studies
+ groups_signup_contact: Ready to discuss?
+ groups_signup_contact_text: "Get in touch to discover what OFN can do for you:"
+ groups_signup_detail: "Here's the detail."
+
+ login_invalid: "Invalid email or password"
+
+ modal_hubs: "Food Hubs"
+ modal_hubs_abstract: Our food hubs are the point of contact between you and the people who make your food!
+ modal_hubs_content1: You can search for a convenient hub by location or name. Some hubs have multiple points where you can pick-up your purchases, and some will also provide delivery options. Each food hub is a sales point with independent business operations and logistics - so variations between hubs are to be expected.
+ modal_hubs_content2: You can only shop at one food hub at a time.
+
+ modal_groups: "Groups / Regions"
+ modal_groups_content1: These are the organisations and relationships between hubs which make up the Open Food Network.
+ modal_groups_content2: Some groups are clustered by location or council, others by non-geographic similarities.
+
+ modal_how: "How it works"
+ modal_how_shop: Shop the Open Food Network
+ modal_how_shop_explained: Search for a food hub near you to start shopping! You can expand each food hub to see what kinds of goodies are available, and click through to start shopping. (You can only shop one food hub at a time.)
+ modal_how_pickup: Pick-ups, delivery and shipping costs
+ modal_how_pickup_explained: Some food hubs deliver to your door, while others require you to pick-up your purchases. You can see which options are available on the homepage, and select which you'd like at the shopping and check-out pages. Delivery will cost more, and pricing differs from hub-to-hub. Each food hub is a sales point with independent business operations and logisitics - so variations between hubs are to be expected.
+ modal_how_more: Learn more
+ modal_how_more_explained: "If you want to learn more about the Open Food Network, how it works, and get involved, check out:"
+
+ modal_producers: "Producers"
+ modal_producers_explained: "Our producers make all the delicious food you can shop for on the Open Food Network."
+
+ ocs_choice_hub: "Hub:"
+ ocs_choice_oc: "Order Cycle:"
+ ocs_choice_text: "You have not yet picked where you will get your order from."
+ ocs_closed_headline: Orders are currently closed for this hub
+ ocs_closed_time: "The last cycle closed %{time} ago."
+ ocs_closed_contact: "Please contact your hub directly to see if they accept late orders, or wait until the next cycle opens."
+ ocs_closed_opens: "The next order cycle opens in %{time}"
+ ocs_closed_email: "Email: %{email}"
+ ocs_closed_phone: "Phone: %{phone}"
+ ocs_pickup_time: "Your order will be ready on %{pickup_time}"
+ ocs_change_date: "Change Collection Date"
+ ocs_change_date_notice: "(This will reset your cart)"
+ ocs_close_time: "ORDERS CLOSE"
+ ocs_when_headline: When do you want your order?
+ ocs_when_text: No products are displayed until you select a date.
+ ocs_when_closing: "Closing On"
+ ocs_when_choose: "Choose Order Cycle"
+ ocs_list: "List View"
+
+ producers_about: About us
+ producers_buy: Shop for
+ producers_contact: Contact
+ producers_contact_phone: Call
+ producers_contact_social: Follow
+ producers_buy_at_html: "Shop for %{enterprise} products at:"
+ producers_filter: Filter by
+ producers_filter_type: Type
+ producers_title: Producers
+ producers_headline: Find local producers
+ producers_signup_title: Sign up as a producer
+ producers_signup_headline: Food producers, empowered.
+ producers_signup_motivation: Sell your food and tell your stories to diverse new markets. Save time and money on every overhead. We support innovation without the risk. We've levelled the playing field.
+ producers_signup_send: Join now
+ producers_signup_enterprise: Enterprise Accounts
+ producers_signup_studies: Stories from our producers.
+ producers_signup_cta_headline: Join now!
+ producers_signup_cta_action: Join now
+ producers_signup_detail: Here's the detail.
+ producer: Producer
+
+ products_item: Item
+ products_description: Description
+ products_variant: Variant
+ products_quantity: Quantity
+ products_availabel: Available?
+ products_producer: "Producer"
+ products_price: "Price"
+ products_sku: "SKU"
+ products_name: "name"
+ products_unit: "unit"
+ products_on_hand: "on hand"
+ products_on_demand: "On demand?"
+ products_category: "Category"
+ products_tax_category: "tax category"
+ products_available_on: "Available On"
+ products_inherit: "Inherit?"
+ products_inherits_properties: "Inherits Properties?"
+ products_stock_level_reset: "Enable Stock Level Reset?"
+
+ register_title: Register
+
+ shops_title: Shops
+ shops_headline: Shopping, transformed.
+ shops_text: Food grows in cycles, farmers harvest in cycles, and we order food in cycles. If you find an order cycle closed, check back soon.
+ shops_signup_title: Sign up as a hub
+ shops_signup_headline: Food hubs, unlimited.
+ shops_signup_motivation: Whatever your model, we support you. However you change, we're with you. We're non-profit, independent, and open-sourced. We're the software partners you've dreamed of.
+ shops_signup_action: Join now
+ shops_signup_pricing: Enterprise Accounts
+ shops_signup_stories: Stories from our hubs.
+ shops_signup_help: We're ready to help.
+ shops_signup_help_text: You need a better return. You need new buyers and logistics partners. You need your story told across wholesale, retail, and the kitchen table.
+ shops_signup_detail: Here's the detail.
+
+ orders_fees: Fees...
+ orders_edit_title: Shopping Cart
+ orders_edit_headline: Your shopping cart
+ orders_edit_time: Order ready for
+ orders_edit_continue: Continue shopping
+ orders_edit_checkout: Checkout
+ orders_form_empty_cart: "Empty cart"
+ orders_form_subtotal: Produce subtotal
+ orders_form_admin: Admin and handling
+ orders_form_total: Total
+ orders_oc_expired_headline: Orders have closed for this order cycle
+ orders_oc_expired_text: "Sorry, orders for this order cycle closed %{time} ago! Please contact your hub directly to see if they can accept late orders."
+ orders_oc_expired_text_others_html: "Sorry, orders for this order cycle closed %{time} ago! Please contact your hub directly to see if they can accept late orders %{link}."
+ orders_oc_expired_text_link: "or see the other order cycles available at this hub"
+ orders_oc_expired_email: "Email:"
+ orders_oc_expired_phone: "Phone:"
+ orders_show_title: Order Confirmation
+ orders_show_time: Order ready on
+ orders_show_number: Order confirmation
+
+ products_cart_distributor_choice: "Distributor for your order:"
+ products_cart_distributor_change: "Your distributor for this order will be changed to %{name} if you add this product to your cart."
+ products_cart_distributor_is: "Your distributor for this order is %{name}."
+ products_distributor_error: "Please complete your order at %{link} before shopping with another distributor."
+ products_oc: "Order cycle for your order:"
+ products_oc_change: "Your order cycle for this order will be changed to %{name} if you add this product to your cart."
+ products_oc_is: "Your order cycle for this order is %{name}."
+ products_oc_error: "Please complete your order from %{link} before shopping in a different order cycle."
+ products_oc_current: "your current order cycle"
+ products_max_quantity: Max quantity
+ products_distributor: Distributor
+ products_distributor_info: When you select a distributor for your order, their address and pickup times will be displayed here.
+
+ # keys used in javascript
+ password: Password
+ remember_me: Remember Me
+ are_you_sure: "Are you sure?"
+ orders_open: Orders open
+ closing: "Closing "
+ going_back_to_home_page: "Taking you back to the home page"
+ creating: Creating
+ updating: Updating
+ failed_to_create_enterprise: "Failed to create your enterprise."
+ failed_to_create_enterprise_unknown: "Failed to create your enterprise.\nPlease ensure all fields are completely filled out."
+ failed_to_update_enterprise_unknown: "Failed to update your enterprise.\nPlease ensure all fields are completely filled out."
+ order_not_saved_yet: "Your order hasn't been saved yet. Give us a few seconds to finish!"
+ filter_by: "Filter by"
+ hide_filters: "Hide filters"
+ one_filter_applied: "1 filter applied"
+ x_filters_applied: " filters applied"
+ submitting_order: "Submitting your order: please wait"
+ confirm_hub_change: "Are you sure? This will change your selected hub and remove any items in your shopping cart."
+ confirm_oc_change: "Are you sure? This will change your selected order cycle and remove any items in your shopping cart."
+ location_placeholder: "Type in a location..."
+ error_required: "can't be blank"
+ error_number: "must be number"
+ error_email: "must be email address"
+ item_handling_fees: "Item Handling Fees (included in item totals)"
+ january: "January"
+ february: "February"
+ march: "March"
+ april: "April"
+ may: "May"
+ june: "June"
+ july: "July"
+ august: "August"
+ september: "September"
+ october: "October"
+ november: "November"
+ december: "December"
+ email_not_found: "Email address not found"
+ email_required: "You must provide an email address"
+ logging_in: "Hold on a moment, we're logging you in"
+ signup_email: "Your email"
+ choose_password: "Choose a password"
+ confirm_password: "Confirm password"
+ action_signup: "Sign up now"
+ welcome_to_ofn: "Welcome to the Open Food Network!"
+ signup_or_login: "Start By Signing Up (or logging in)"
+ have_an_account: "Already have an account?"
+ action_login: "Log in now."
+ forgot_password: "Forgot Password?"
+ password_reset_sent: "An email with instructions on resetting your password has been sent!"
+ reset_password: "Reset password"
+ registration_greeting: "Greetings!"
+ who_is_managing_enterprise: "Who is responsible for managing %{enterprise}?"
+ enterprise_contact: "Primary Contact"
+ enterprise_contact_required: "You need to enter a primary contact."
+ enterprise_email_address: "Email address"
+ enterprise_phone: "Phone number"
+ back: "Back"
+ continue: "Continue"
+ limit_reached_headline: "Oh no!"
+ limit_reached_message: "You have reached the limit!"
+ limit_reached_text: "You have reached the limit for the number of enterprises you are allowed to own on the"
+ limit_reached_action: "Return to the homepage"
+ select_promo_image: "Step 3. Select Promo Image"
+ promo_image_tip: "Tip: Shown as a banner, preferred size is 1200×260px"
+ promo_image_label: "Choose a promo image"
+ action_or: "OR"
+ promo_image_drag: "Drag and drop your promo here"
+ review_promo_image: "Step 4. Review Your Promo Banner"
+ review_promo_image_tip: "Tip: for best results, your promo image should fill the available space"
+ promo_image_placeholder: "Your logo will appear here for review once uploaded"
+ uploading: "Uploading..."
+ select_logo: "Step 1. Select Logo Image"
+ logo_tip: "Tip: Square images will work best, preferably at least 300×300px"
+ logo_label: "Choose a logo image"
+ logo_drag: "Drag and drop your logo here"
+ review_logo: "Step 2. Review Your Logo"
+ review_logo_tip: "Tip: for best results, your logo should fill the available space"
+ logo_placeholder: "Your logo will appear here for review once uploaded"
+ enterprise_about_headline: "Nice one!"
+ enterprise_about_message: "Now let's flesh out the details about"
+ enterprise_success: "Success! %{enterprise} added to the Open Food Network "
+ enterprise_registration_exit_message: "If you exit this wizard at any stage, you need to click the confirmation link in the email you have received. This will take you to your admin interface where you can continue setting up your profile."
+ enterprise_description: "Short Description"
+ enterprise_description_placeholder: "A short sentence describing your enterprise"
+ enterprise_long_desc: "Long Description"
+ enterprise_long_desc_placeholder: "This is your opportunity to tell the story of your enterprise - what makes you different and wonderful? We'd suggest keeping your description to under 600 characters or 150 words."
+ enterprise_long_desc_length: "%{num} characters / up to 600 recommended"
+ enterprise_abn: "ABN"
+ enterprise_abn_placeholder: "eg. 99 123 456 789"
+ enterprise_acn: "ACN"
+ enterprise_acn_placeholder: "eg. 123 456 789"
+ enterprise_tax_required: "You need to make a selection."
+ enterprise_final_step: "Final step!"
+ enterprise_social_text: "How can people find %{enterprise} online?"
+ website: "Website"
+ website_placeholder: "eg. openfoodnetwork.org.au"
+ facebook: "Facebook"
+ facebook_placeholder: "eg. www.facebook.com/PageNameHere"
+ linkedin: "LinkedIn"
+ linkedin_placeholder: "eg. www.linkedin.com/YourNameHere"
+ twitter: "Twitter"
+ twitter_placeholder: "eg. @twitter_handle"
+ instagram: "Instagram"
+ instagram_placeholder: "eg. @instagram_handle"
+ registration_greeting: "Hi there!"
+ registration_intro: "You can now create a profile for your Producer or Hub"
+ registration_action: "Let's get started!"
+ registration_checklist: "You'll need"
+ registration_time: "5-10 minutes"
+ registration_enterprise_address: "Enterprise address"
+ registration_contact_details: "Primary contact details"
+ registration_logo: "Your logo image"
+ registration_promo_image: "Landscape image for your profile"
+ registration_about_us: "'About Us' text"
+ registration_outcome_headline: "What do I get?"
+ registration_outcome1_html: "Your profile helps people find and contact you on the Open Food Network."
+ registration_outcome2: "Use this space to tell the story of your enterprise, to help drive connections to your social and online presence. "
+ registration_outcome3: "It's also the first step towards trading on the Open Food Network, or opening an online store."
+ registration_finished_headline: "Finished!"
+ registration_finished_thanks: "Thanks for filling out the details for %{enterprise}."
+ registration_finished_login: "You can change or update your enterprise at any stage by logging into Open Food Network and going to Admin."
+ registration_finished_activate: "Activate %{enterprise}."
+ registration_finished_activate_instruction_html: "We've sent a confirmation email to %{email} if it hasn't been activated before.
+Please follow the instructions there to make your enterprise visible on the Open Food Network."
+ registration_finished_action: "Open Food Network home"
+ registration_type_headline: "Last step to add %{enterprise}!"
+ registration_type_question: "Are you a producer?"
+ registration_type_producer: "Yes, I'm a producer"
+ registration_type_no_producer: "No, I'm not a producer"
+ registration_type_error: "Please choose one. Are you are producer?"
+ registration_type_producer_help: "Producers make yummy things to eat and/or drink. You're a producer if you grow it, raise it, brew it, bake it, ferment it, milk it or mould it."
+ registration_type_no_producer_help: "If you’re not a producer, you’re probably someone who sells and distributes food. You might be a hub, coop, buying group, retailer, wholesaler or other."
+ create_profile: "Create Profile"
+ registration_images_headline: "Thanks!"
+ registration_images_description: "Let's upload some pretty pictures so your profile looks great! :)"
+ registration_detail_headline: "Let's Get Started"
+ registration_detail_enterprise: "Woot! First we need to know a little bit about your enterprise:"
+ registration_detail_producer: "Woot! First we need to know a little bit about your farm:"
+ registration_detail_name_enterprise: "Enterprise Name:"
+ registration_detail_name_producer: "Farm Name:"
+ registration_detail_name_placeholder: "e.g. Charlie's Awesome Farm"
+ registration_detail_name_error: "Please choose a unique name for your enterprise"
+ registration_detail_address1: "Address line 1:"
+ registration_detail_address1_placeholder: "e.g. 123 Cranberry Drive"
+ registration_detail_address1_error: "Please enter an address"
+ registration_detail_address2: "Address line 2:"
+ registration_detail_suburb: "City:"
+ registration_detail_suburb_placeholder: "e.g. New York City"
+ registration_detail_suburb_error: "Please enter a city"
+ registration_detail_postcode: "Postcode:"
+ registration_detail_postcode_placeholder: "e.g. 10019"
+ registration_detail_postcode_error: "Postcode required"
+ registration_detail_state: "State:"
+ registration_detail_state_error: "State required"
+ registration_detail_country: "Country:"
+ registration_detail_country_error: "Please select a country"
+ fees: "Fees"
+ item_cost: "Item cost"
+ bulk: "Bulk"
+ shop_variant_quantity_min: "min"
+ shop_variant_quantity_max: "max"
+ contact: "Contact"
+ follow: "Follow"
+ shop_for_products_html: "Shop for %{enterprise} products at:"
+ change_shop: "Change shop to:"
+ shop_at: "Shop now at:"
+ price_breakdown: "Full price breakdown"
+ admin_fee: "Admin fee"
+ sales_fee: "Sales fee"
+ packing_fee: "Packing fee"
+ transport_fee: "Transport fee"
+ fundraising_fee: "Fundraising fee"
+ price_graph: "Price graph"
+ included_tax: "Included tax"
+ remove_tax: "Remove tax"
+ balance: "Balance"
+ transaction: "Transaction"
+ transaction_date: "Date" #Transaction is only in key to avoid conflict with :date
+ payment_state: "Payment status"
+ shipping_state: "Shipping status"
+ value: "Value"
+ balance_due: "Balance due"
+ credit: "Credit"
+ Paid: "Paid"
+ Ready: "Ready"
+ you_have_no_orders_yet: "You have no orders yet"
+ running_balance: "Running balance"
+ outstanding_balance: "Outstanding balance"
+ admin_entreprise_relationships: "Enterprise Relationships"
+ admin_entreprise_relationships_everything: "Everything"
+ admin_entreprise_relationships_permits: "permits"
+ admin_entreprise_relationships_seach_placeholder: "Search"
+ admin_entreprise_relationships_button_create: "Create"
+ admin_entreprise_groups: "Enterprise Groups"
+ admin_entreprise_groups_name: "Name"
+ admin_entreprise_groups_owner: "Owner"
+ admin_entreprise_groups_on_front_page: "On front page ?"
+ admin_entreprise_groups_entreprise: "Enterprises"
+ admin_entreprise_groups_primary_details: "Primary Details"
+ admin_entreprise_groups_data_powertip: "The primary user responsible for this group."
+ admin_entreprise_groups_data_powertip_logo: "This is the logo for the group"
+ admin_entreprise_groups_data_powertip_promo_image: "This image is displayed at the top of the Group profile"
+ admin_entreprise_groups_about: "About"
+ admin_entreprise_groups_images: "Images"
+ admin_entreprise_groups_contact: "Contact"
+ admin_entreprise_groups_contact_phone_placeholder: "eg. 212 765 3210"
+ admin_entreprise_groups_contact_address1_placeholder: "eg. 100 Martin Luther King Drive"
+ admin_entreprise_groups_contact_city: "City"
+ admin_entreprise_groups_contact_city_placeholder: "eg. NYC, NY"
+ admin_entreprise_groups_contact_zipcode: "Postcode"
+ admin_entreprise_groups_contact_zipcode_placeholder: "eg. 10019"
+ admin_entreprise_groups_contact_state_id: "State"
+ admin_entreprise_groups_contact_country_id: "Country"
+ admin_entreprise_groups_web: "Web Resources"
+ admin_entreprise_groups_web_twitter: "eg. @the_prof"
+ admin_entreprise_groups_web_website_placeholder: "eg. www.truffles.com"
+ admin_order_cycles: "Admin Order Cycles"
+ open: "Open"
+ close: "Close"
+ supplier: "Supplier"
+ coordinator: "Coordinator"
+ distributor: "Distributor"
+ product: "Products"
+ enterprise_fees: "Enterprise Fees"
+ fee_type: "Fee Type"
+ tax_category: "Tax Category"
+ calculator: "Calculator"
+ calculator_values: "Calculator values"
+ new_order_cycles: "New Order Cycles"
+ select_a_coordinator_for_your_order_cycle: "select a coordinator for your order cycle"
+ edit_order_cycle: "Edit Order Cycle"
+ roles: "Roles"
+ update: "Update"
+ add_producer_property: "Add producer property"
+ admin_settings: "Settings"
+ update_invoice: "Update Invoices"
+ finalise_invoice: "Finalise Invoices"
+ finalise_user_invoices: "Finalise User Invoices"
+ finalise_user_invoice_explained: "Use this button to finalize all invoices in the system for the previous calendar month. This task can be set up to run automatically once a month."
+ manually_run_task: "Manually Run Task "
+ update_user_invoices: "Update User Invoices"
+ update_user_invoice_explained: "Use this button to immediately update invoices for the month to date for each enterprise user in the system. This task can be set up to run automatically every night."
+ auto_finalise_invoices: "Auto-finalise invoices monthly on the 2nd at 1:30am"
+ auto_update_invoices: "Auto-update invoices nightly at 1:00am"
+ in_progress: "In Progress"
+ started_at: "Started at"
+ queued: "Queued"
+ scheduled_for: "Scheduled for"
+ customers: "Customers"
+ please_select_hub: "Please select a Hub"
+ loading_customers: "Loading Customers"
+ no_customers_found: "No customers found"
+ go: "Go"
+ hub: "Hub"
+ accounts_administration_distributor: "accounts administration distributor"
+ accounts_and_billing: "Accounts & Billing"
+ producer: "Producer"
+ product: "Product"
+ price: "Price"
+ on_hand: "On hand"
+ save_changes: "Save Changes"
+ spree_admin_overview_enterprises_header: "My Enterprises"
+ spree_admin_overview_enterprises_footer: "MANAGE MY ENTERPRISES"
+ spree_admin_enterprises_hubs_name: "Name"
+ spree_admin_enterprises_create_new: "CREATE NEW"
+ spree_admin_enterprises_shipping_methods: "Shipping Methods"
+ spree_admin_enterprises_fees: "Enterprise Fees"
+ spree_admin_enterprises_none_create_a_new_enterprise: "CREATE A NEW ENTERPRISE"
+ spree_admin_enterprises_none_text: "You don't have any enterprises yet"
+ spree_admin_enterprises_producers_name: "Name"
+ spree_admin_enterprises_producers_total_products: "Total Products"
+ spree_admin_enterprises_producers_active_products: "Active Products"
+ spree_admin_enterprises_producers_order_cycles: "Products in OCs"
+ spree_admin_enterprises_producers_order_cycles_title: ""
+ spree_admin_enterprises_tabs_hubs: "HUBS"
+ spree_admin_enterprises_tabs_producers: "PRODUCERS"
+ spree_admin_enterprises_producers_manage_order_cycles: "MANAGE ORDER CYCLES"
+ spree_admin_enterprises_producers_manage_products: "MANAGE PRODUCTS"
+ spree_admin_enterprises_producers_orders_cycle_text: "You don't have any active order cycles."
+ spree_admin_enterprises_any_active_products_text: "You don't have any active products."
+ spree_admin_enterprises_create_new_product: "CREATE A NEW PRODUCT"
+ spree_admin_order_cycles: "Order Cycles"
+ spree_admin_order_cycles_tip: "Order cycles determine when and where your products are available to customers."
+ dashbord: "Dashboard"
+ spree_admin_single_enterprise_alert_mail_confirmation: "Please confirm the email address for"
+ spree_admin_single_enterprise_alert_mail_sent: "We've sent an email to"
+ spree_admin_overview_action_required: "Action Required"
+ spree_admin_overview_check_your_inbox: "Please check you inbox for furher instructions. Thanks!"
+ change_package: "Change Package"
+ spree_admin_single_enterprise_hint: "Hint: To allow people to find you, turn on your visibility under"
+ your_profil_live: "Your profile live"
+ on_ofn_map: "on the Open Food Network map"
+ see: "See"
+ live: "live"
+ manage: "Manage"
+ resend: "Resend"
+ add_and_manage_products: "Add & manage products"
+ add_and_manage_order_cycles: "Add & manage order cycles"
+ manage_order_cycles: "Manage order cycles"
+ manage_products: "Manage products"
+ edit_profile_details: "Edit profile details"
+ edit_profile_details_etc: "Change your profile description, images, etc."
+ start_date: "Start Date"
+ end_date: "End Date"
+ order_cycle: "Order Cycle"
+ group_buy_unit_size: "Group Buy Unit Size"
+ total_qtt_ordered: "Total Quantity Ordered"
+ max_qtt_ordered: "Max Quantity Ordered"
+ current_fulfilled_units: "Current Fulfilled Units"
+ max_fulfilled_units: "Max Fulfilled Units"
+ bulk_management_warning: "WARNING: Some variants do not have a unit value"
+ ask: "Ask?"
+ no_orders_found: "No orders found."
+ order_no: "Order No."
+ weight_volume: "Weight/Volume"
+ remove_tax: "Remove tax"
+ tax_settings: "Tax Settings"
+ products_require_tax_category: "products require tax category"
+ admin_shared_address_1: "Address"
+ admin_shared_address_2: "Address (cont.)"
+ admin_share_city: "City"
+ admin_share_zipcode: "Postcode"
+ admin_share_country: "Country"
+ admin_share_state: "State"
+ hub_sidebar_hubs: "Hubs"
+ hub_sidebar_none_available: "None Available"
+ hub_sidebar_manage: "Manage"
+ hub_sidebar_at_least: "At least one hub must be selected"
+ hub_sidebar_blue: "blue"
+ hub_sidebar_red: "red"
+ shop_trial_in_progress: "Your shopfront trial expires in %{days}."
+ shop_trial_expired: "Good news! We have decided to extend shopfront trials until further notice (probably around March 2015)." #FIXME
+ report_customers_distributor: "Distributor"
+ report_customers_supplier: "Supplier"
+ report_customers_cycle: "Order Cycle"
+ report_customers_type: "Report Type"
+ report_customers_csv: "Download as csv"
+ report_producers: "Producers: "
+ report_type: "Report Type: "
+ report_hubs: "Hubs: "
+ report_payment: "Payment Methods: "
+ report_distributor: "Distributor: "
+ report_payment_by: 'Payments By Type'
+ report_itemised_payment: 'Itemised Payment Totals'
+ report_payment_totals: 'Payment Totals'
+ report_all: 'all'
+ report_order_cycle: "Order Cycle: "
+ report_entreprises: "Enterprises: "
+ report_users: "Users: "
+ initial_invoice_number: "Initial invoice number:"
+ invoice_date: "Invoice date:"
+ due_date: "Due date:"
+ account_code: "Account code:"
+ equals: "Equals"
+ contains: "contains"
+ discount: "Discount"
+ filter_products: "Filter Products"
+ delete_product_variant: "The last variant cannot be deleted!"
+ progress: "progress"
+ saving: "Saving.."
+ success: "success"
+ failure: "failure"
+ unsaved_changes_confirmation: "Unsaved changes will be lost. Continue anyway?"
+ one_product_unsaved: "Changes to one product remain unsaved."
+ products_unsaved: "Changes to %{n} products remain unsaved."
+ add_manager: "Add a manager"
+ is_already_manager: "is already a manager!"
+ no_change_to_save: " No change to save"
+ add_manager: "Add a manager"
+ users: "Users"
+ about: "About"
+ images: "Images"
+ contact: "Contact"
+ web: "Web"
+ primary_details: "Primary Details"
+ adrdress: "Address"
+ contact: "Contact"
+ social: "Social"
+ business_details: "Business Details"
+ properties: "Properties"
+ shipping_methods: "Shipping Methods"
+ payment_methods: "Payment Methods"
+ enterprise_fees: "Enterprise Fees"
+ inventory_settings: "Inventory Settings"
+ tag_rules: "Tag Rules"
+ shop_preferences: "Shop Preferences"
+ validation_msg_relationship_already_established: "^That relationship is already established."
+ validation_msg_at_least_one_hub: "^At least one hub must be selected"
+ validation_msg_product_category_cant_be_blank: "^Product Category cant be blank"
+ validation_msg_tax_category_cant_be_blank: "^Tax Category can't be blank"
+ validation_msg_is_associated_with_an_exising_customer: "is associated with an existing customer"
+
+ spree:
+ shipment_states:
+ backorder: backorder
+ partial: partial
+ pending: pending
+ ready: ready
+ shipped: shipped
+ payment_states:
+ balance_due: balance due
+ completed: completed
+ checkout: checkout
+ credit_owed: credit owed
+ failed: failed
+ paid: paid
+ pending: pending
+ processing: processing
+ void: void
+ order_state:
+ address: address
+ adjustments: adjustments
+ awaiting_return: awaiting return
+ canceled: canceled
+ cart: cart
+ complete: complete
+ confirm: confirm
+ delivery: delivery
+ payment: payment
+ resumed: resumed
+ returned: returned
+ skrill: skrill
diff --git a/config/locales/en.yml b/config/locales/en.yml
index e895522903..7fd49fc2fe 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -71,13 +71,29 @@ en:
admin:
+ # Common properties / models
+ date: Date
+ email: Email
+ name: Name
+ on_hand: On Hand
+ on_demand: On Demand
+ on_demand?: On Demand?
+ order_cycle: Order Cycle
+ phone: Phone
+ price: Price
+ producer: Producer
+ product: Product
+ quantity: Quantity
+ shop: Shop
+ sku: SKU
+ tags: Tags
+ variant: Variant
+
# General form elements
quick_search: Quick Search
clear_all: Clear All
- producer: Producer
- shop: Shop
- product: Product
- variant: Variant
+ start_date: "Start Date"
+ end_date: "End Date"
columns: Columns
actions: Actions
@@ -91,40 +107,79 @@ en:
customers:
index:
- add_customer: "Add customer"
+ add_customer: "Add Customer"
+ new_customer: "New Customer"
customer_placeholder: "customer@example.org"
- inventory:
- title: Inventory
- description: Use this page to manage inventories for your enterprises. Any product details set here will override those set on the 'Products' page
- sku: SKU
- price: Price
- on_hand: On Hand
- on_demand: On Demand?
- enable_reset: Enable Stock Level Reset?
- inherit: Inherit?
- add: Add
- hide: Hide
- select_a_shop: Select A Shop
- review_now: Review Now
- new_products_alert_message: There are %{new_product_count} new products available to add to your inventory.
- currently_empty: Your inventory is currently empty
- no_matching_products: No matching products found in your inventory
- no_hidden_products: No products have been hidden from this inventory
- no_matching_hidden_products: No hidden products match your search criteria
- no_new_products: No new products are available to add to this inventory
- no_matching_new_products: No new products match your search criteria
- inventory_powertip: This is your inventory of products. To add products to your inventory, select 'New Products' from the Viewing dropdown.
- hidden_powertip: These products have been hidden from your inventory and will not be available to add to your shop. You can click 'Add' to add a product to you inventory.
- new_powertip: These products are available to be added to your inventory. Click 'Add' to add a product to your inventory, or 'Hide' to hide it from view. You can always change your mind later!
+ valid_email_error: Please enter a valid email address
+ add_a_new_customer_for: Add a new customer for %{shop_name}
+ code: Code
+ products:
+ bulk_edit:
+ unit: Unit
+ display_as: Display As
+ category: Category
+ tax_category: Tax Category
+ inherits_properties?: Inherits Properties?
+ available_on: Available On
+ av_on: "Av. On"
- order_cycle:
- choose_products_from: "Choose Products From:"
+ variant_overrides:
+ index:
+ title: Inventory
+ description: Use this page to manage inventories for your enterprises. Any product details set here will override those set on the 'Products' page
+ enable_reset?: Enable Stock Reset?
+ inherit?: Inherit?
+ add: Add
+ hide: Hide
+ select_a_shop: Select A Shop
+ review_now: Review Now
+ new_products_alert_message: There are %{new_product_count} new products available to add to your inventory.
+ currently_empty: Your inventory is currently empty
+ no_matching_products: No matching products found in your inventory
+ no_hidden_products: No products have been hidden from this inventory
+ no_matching_hidden_products: No hidden products match your search criteria
+ no_new_products: No new products are available to add to this inventory
+ no_matching_new_products: No new products match your search criteria
+ inventory_powertip: This is your inventory of products. To add products to your inventory, select 'New Products' from the Viewing dropdown.
+ hidden_powertip: These products have been hidden from your inventory and will not be available to add to your shop. You can click 'Add' to add a product to you inventory.
+ new_powertip: These products are available to be added to your inventory. Click 'Add' to add a product to your inventory, or 'Hide' to hide it from view. You can always change your mind later!
+
+ orders:
+ bulk_management:
+ tip: "Use this page to alter product quantities across multiple orders. Products may also be removed from orders entirely, if required."
+ shared: "Shared Resource?"
+ order_no: "Order No."
+ order_date: "Order Date"
+ max: "Max"
+ product_unit: "Product: Unit"
+ weight_volume: "Weight/Volume"
+ ask: "Ask?"
+ page_title: "Bulk Order Management"
+ actions_delete: "Delete Selected"
+ loading: "Loading orders"
+ no_results: "No orders found."
+ group_buy_unit_size: "Group Buy Unit Size"
+ total_qtt_ordered: "Total Quantity Ordered"
+ max_qtt_ordered: "Max Quantity Ordered"
+ current_fulfilled_units: "Current Fulfilled Units"
+ max_fulfilled_units: "Max Fulfilled Units"
+ order_error: "Some errors must be resolved before you can update orders.\nAny fields with red borders contain errors."
+ variants_without_unit_value: "WARNING: Some variants do not have a unit value"
+
+ order_cycles:
+ edit:
+ choose_products_from: "Choose Products From:"
enterprise:
select_outgoing_oc_products_from: Select outgoing OC products from
enterprises:
+ index:
+ producer?: Producer?
+ package: Package
+ status: Status
+ manage: Manage
form:
primary_details:
shopfront_requires_login: "Shopfront requires login?"
@@ -329,22 +384,6 @@ en:
order_payment_paypal_successful: Your payment via PayPal has been processed successfully.
order_hub_info: Hub Info
- bom_tip: "Use this page to alter product quantities across multiple orders. Products may also be removed from orders entirely, if required."
- bom_shared: "Shared Resource?"
- bom_page_title: "Bulk Order Management"
- bom_no: "Order no."
- bom_date: "Order date"
- bom_cycle: "Order cycle"
- bom_max: "Max"
- bom_hub: "Hub"
- bom_variant: "Product: Unit"
- bom_final_weigth_volume: "Weight/Volume"
- bom_quantity: "Quantity"
- bom_actions_delete: "Delete Selected"
- bom_loading: "Loading orders"
- bom_no_results: "No orders found."
- bom_order_error: "Some errors must be resolved before you can update orders.\nAny fields with red borders contain errors."
-
unsaved_changes_warning: "Unsaved changes exist and will be lost if you continue."
unsaved_changes_error: "Fields with red borders contain errors."
@@ -571,20 +610,9 @@ See the %{link} to find out more about %{sitename}'s features and to start using
products_description: Description
products_variant: Variant
products_quantity: Quantity
- products_availabel: Available?
+ products_available: Available?
products_producer: "Producer"
products_price: "Price"
- products_sku: "SKU"
- products_name: "name"
- products_unit: "unit"
- products_on_hand: "on hand"
- products_on_demand: "On demand?"
- products_category: "Category"
- products_tax_category: "tax category"
- products_available_on: "Available On"
- products_inherit: "Inherit?"
- products_inherits_properties: "Inherits Properties?"
- products_stock_level_reset: "Enable Stock Level Reset?"
register_title: Register
@@ -838,12 +866,9 @@ Please follow the instructions there to make your enterprise visible on the Open
admin_entreprise_groups_owner: "Owner"
admin_entreprise_groups_on_front_page: "On front page ?"
admin_entreprise_groups_entreprise: "Enterprises"
- admin_entreprise_groups_primary_details: "Primary Details"
admin_entreprise_groups_data_powertip: "The primary user responsible for this group."
admin_entreprise_groups_data_powertip_logo: "This is the logo for the group"
admin_entreprise_groups_data_powertip_promo_image: "This image is displayed at the top of the Group profile"
- admin_entreprise_groups_about: "About"
- admin_entreprise_groups_images: "Images"
admin_entreprise_groups_contact: "Contact"
admin_entreprise_groups_contact_phone_placeholder: "eg. 98 7654 3210"
admin_entreprise_groups_contact_address1_placeholder: "eg. 123 High Street"
@@ -869,7 +894,7 @@ Please follow the instructions there to make your enterprise visible on the Open
calculator: "Calculator"
calculator_values: "Calculator values"
new_order_cycles: "New Order Cycles"
- select_a_coordinator_for_your_order_cycle: "select a coordinator for your order cycle"
+ select_a_coordinator_for_your_order_cycle: "Select a coordinator for your order cycle"
edit_order_cycle: "Edit Order Cycle"
roles: "Roles"
update: "Update"
@@ -942,19 +967,7 @@ Please follow the instructions there to make your enterprise visible on the Open
manage_products: "Manage products"
edit_profile_details: "Edit profile details"
edit_profile_details_etc: "Change your profile description, images, etc."
- start_date: "Start Date"
- end_date: "End Date"
order_cycle: "Order Cycle"
- group_buy_unit_size: "Group Buy Unit Size"
- total_qtt_ordered: "Total Quantity Ordered"
- max_qtt_ordered: "Max Quantity Ordered"
- current_fulfilled_units: "Current Fulfilled Units"
- max_fulfilled_units: "Max Fulfilled Units"
- bulk_management_warning: "WARNING: Some variants do not have a unit value"
- ask: "Ask?"
- no_orders_found: "No orders found."
- order_no: "Order No."
- weight_volume: "Weight/Volume"
remove_tax: "Remove tax"
tax_settings: "Tax Settings"
products_require_tax_category: "products require tax category"
@@ -1022,6 +1035,7 @@ Please follow the instructions there to make your enterprise visible on the Open
properties: "Properties"
shipping_methods: "Shipping Methods"
payment_methods: "Payment Methods"
+ payment_method_fee: "Transaction fee"
enterprise_fees: "Enterprise Fees"
inventory_settings: "Inventory Settings"
tag_rules: "Tag Rules"
diff --git a/config/ng-test.conf.js b/config/ng-test.conf.js
index 0cc4fc385e..20ec36622c 100644
--- a/config/ng-test.conf.js
+++ b/config/ng-test.conf.js
@@ -9,7 +9,6 @@ module.exports = function(config) {
'app/assets/javascripts/shared/jquery-1.8.0.js', // TODO: Can we link to Rails' jquery?
'app/assets/javascripts/shared/jquery.timeago.js',
'app/assets/javascripts/shared/angular-local-storage.js',
- 'app/assets/javascripts/shared/bindonce.min.js',
'app/assets/javascripts/shared/ng-infinite-scroll.min.js',
'app/assets/javascripts/shared/angular-slideables.js',
diff --git a/config/routes.rb b/config/routes.rb
index e240f901a8..f1b96de181 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -117,7 +117,9 @@ Openfoodnetwork::Application.routes.draw do
resources :customers, only: [:index, :create, :update, :destroy]
- resources :tags, only: [:index], format: :json
+ resources :tag_rules, only: [], format: :json do
+ get :map_by_tag, on: :collection
+ end
resource :content
@@ -133,6 +135,10 @@ Openfoodnetwork::Application.routes.draw do
resource :cache_settings
resource :account, only: [:show], controller: 'account'
+
+ resources :column_preferences, only: [], format: :json do
+ put :bulk_update, on: :collection
+ end
end
namespace :api do
diff --git a/db/migrate/20160116024333_create_column_preferences.rb b/db/migrate/20160116024333_create_column_preferences.rb
new file mode 100644
index 0000000000..e38c51d858
--- /dev/null
+++ b/db/migrate/20160116024333_create_column_preferences.rb
@@ -0,0 +1,13 @@
+class CreateColumnPreferences < ActiveRecord::Migration
+ def change
+ create_table :column_preferences do |t|
+ t.references :user, null: false, index: true
+ t.string :action_name, null: false, index: true
+ t.string :column_name, null: false
+ t.boolean :visible, null: false
+
+ t.timestamps
+ end
+ add_index :column_preferences, [:user_id, :action_name, :column_name], unique: true, name: 'index_column_prefs_on_user_id_and_action_name_and_column_name'
+ end
+end
diff --git a/db/migrate/20160520065217_add_is_default_to_tag_rule.rb b/db/migrate/20160520065217_add_is_default_to_tag_rule.rb
new file mode 100644
index 0000000000..209520b693
--- /dev/null
+++ b/db/migrate/20160520065217_add_is_default_to_tag_rule.rb
@@ -0,0 +1,5 @@
+class AddIsDefaultToTagRule < ActiveRecord::Migration
+ def change
+ add_column :tag_rules, :is_default, :boolean, default: false, null: false
+ end
+end
diff --git a/db/migrate/20160527012603_add_priority_to_tag_rule.rb b/db/migrate/20160527012603_add_priority_to_tag_rule.rb
new file mode 100644
index 0000000000..7080d7a9ac
--- /dev/null
+++ b/db/migrate/20160527012603_add_priority_to_tag_rule.rb
@@ -0,0 +1,5 @@
+class AddPriorityToTagRule < ActiveRecord::Migration
+ def change
+ add_column :tag_rules, :priority, :integer, default: 99, null: false
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index dc54bd8f3d..002e367dd6 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -11,7 +11,7 @@
#
# It's strongly recommended to check this file into your version control system.
-ActiveRecord::Schema.define(:version => 20160401043927) do
+ActiveRecord::Schema.define(:version => 20160527012603) do
create_table "account_invoices", :force => true do |t|
t.integer "user_id", :null => false
@@ -176,6 +176,17 @@ ActiveRecord::Schema.define(:version => 20160401043927) do
add_index "cms_snippets", ["site_id", "identifier"], :name => "index_cms_snippets_on_site_id_and_identifier", :unique => true
add_index "cms_snippets", ["site_id", "position"], :name => "index_cms_snippets_on_site_id_and_position"
+ create_table "column_preferences", :force => true do |t|
+ t.integer "user_id", :null => false
+ t.string "action_name", :null => false
+ t.string "column_name", :null => false
+ t.boolean "visible", :null => false
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ end
+
+ add_index "column_preferences", ["user_id", "action_name", "column_name"], :name => "index_column_prefs_on_user_id_and_action_name_and_column_name", :unique => true
+
create_table "coordinator_fees", :force => true do |t|
t.integer "order_cycle_id"
t.integer "enterprise_fee_id"
@@ -683,9 +694,9 @@ ActiveRecord::Schema.define(:version => 20160401043927) do
t.string "email"
t.text "special_instructions"
t.integer "distributor_id"
+ t.integer "order_cycle_id"
t.string "currency"
t.string "last_ip_address"
- t.integer "order_cycle_id"
t.integer "cart_id"
t.integer "customer_id"
end
@@ -1148,10 +1159,12 @@ ActiveRecord::Schema.define(:version => 20160401043927) do
end
create_table "tag_rules", :force => true do |t|
- t.integer "enterprise_id", :null => false
- t.string "type", :null => false
- t.datetime "created_at", :null => false
- t.datetime "updated_at", :null => false
+ t.integer "enterprise_id", :null => false
+ t.string "type", :null => false
+ t.datetime "created_at", :null => false
+ t.datetime "updated_at", :null => false
+ t.boolean "is_default", :default => false, :null => false
+ t.integer "priority", :default => 99, :null => false
end
create_table "taggings", :force => true do |t|
diff --git a/lib/open_food_network/cached_products_renderer.rb b/lib/open_food_network/cached_products_renderer.rb
index 2174bf4094..eada6a7c9e 100644
--- a/lib/open_food_network/cached_products_renderer.rb
+++ b/lib/open_food_network/cached_products_renderer.rb
@@ -14,19 +14,11 @@ module OpenFoodNetwork
end
def products_json
- raise NoProducts.new if @distributor.nil? || @order_cycle.nil?
+ raise NoProducts.new("No Products") if @distributor.nil? || @order_cycle.nil?
- products_json = Rails.cache.fetch("products-json-#{@distributor.id}-#{@order_cycle.id}") do
- log_warning
+ products_json = cached_products_json
- begin
- uncached_products_json
- rescue ProductsRenderer::NoProducts
- nil
- end
- end
-
- raise NoProducts.new if products_json.nil?
+ raise NoProducts.new("No Products") if products_json.nil?
products_json
end
@@ -40,6 +32,22 @@ module OpenFoodNetwork
end
end
+ def cached_products_json
+ if Rails.env.production? || Rails.env.staging?
+ Rails.cache.fetch("products-json-#{@distributor.id}-#{@order_cycle.id}") do
+ log_warning
+
+ begin
+ uncached_products_json
+ rescue ProductsRenderer::NoProducts
+ nil
+ end
+ end
+ else
+ uncached_products_json
+ end
+ end
+
def uncached_products_json
ProductsRenderer.new(@distributor, @order_cycle).products_json
end
diff --git a/lib/open_food_network/column_preference_defaults.rb b/lib/open_food_network/column_preference_defaults.rb
new file mode 100644
index 0000000000..09dc197832
--- /dev/null
+++ b/lib/open_food_network/column_preference_defaults.rb
@@ -0,0 +1,83 @@
+module OpenFoodNetwork
+ module ColumnPreferenceDefaults
+
+ private
+
+ # NOTE: These methods define valid column names (via hash keys)
+ # as well as default values for column attributes (eg. visiblity)
+ # Default values can be overridden by storing a different value
+ # for a given user, action_name and column_name
+
+ def variant_overrides_index_columns
+ node = 'admin.variant_overrides.index'
+ {
+ producer: { name: I18n.t("admin.producer"), visible: true },
+ product: { name: I18n.t("admin.product"), visible: true },
+ sku: { name: I18n.t("admin.sku"), visible: false },
+ price: { name: I18n.t("admin.price"), visible: true },
+ on_hand: { name: I18n.t("admin.on_hand"), visible: true },
+ on_demand: { name: I18n.t("admin.on_demand?"), visible: false },
+ reset: { name: I18n.t("#{node}.enable_reset?"), visible: false },
+ inheritance: { name: I18n.t("#{node}.inherit?"), visible: false },
+ tags: { name: I18n.t("admin.tags"), visible: false },
+ visibility: { name: I18n.t("#{node}.hide"), visible: false }
+ }
+ end
+
+ def customers_index_columns
+ node = 'admin.customers.index'
+ {
+ email: { name: I18n.t("admin.email"), visible: true },
+ code: { name: I18n.t("#{node}.code"), visible: true },
+ tags: { name: I18n.t("admin.tags"), visible: true }
+ }
+ end
+
+ def orders_bulk_management_columns
+ node = "admin.orders.bulk_management"
+ {
+ order_no: { name: I18n.t("#{node}.order_no"), visible: false },
+ full_name: { name: I18n.t("admin.name"), visible: true },
+ email: { name: I18n.t("admin.email"), visible: false },
+ phone: { name: I18n.t("admin.phone"), visible: false },
+ order_date: { name: I18n.t("#{node}.order_date"), visible: true },
+ producer: { name: I18n.t("admin.producer"), visible: true },
+ order_cycle: { name: I18n.t("admin.order_cycle"), visible: false },
+ hub: { name: I18n.t("admin.shop"), visible: false },
+ variant: { name: I18n.t("#{node}.product_unit"), visible: true },
+ quantity: { name: I18n.t("admin.quantity"), visible: true },
+ max: { name: I18n.t("#{node}.max"), visible: true },
+ final_weight_volume: { name: I18n.t("#{node}.weight_volume"), visible: false },
+ price: { name: I18n.t("admin.price"), visible: false }
+ }
+ end
+
+ def products_bulk_edit_columns
+ node = "admin.products.bulk_edit"
+ {
+ producer: { name: I18n.t("admin.producer"), visible: true },
+ sku: { name: I18n.t("admin.sku"), visible: false },
+ name: { name: I18n.t("admin.name"), visible: true },
+ unit: { name: I18n.t("#{node}.unit"), visible: true },
+ price: { name: I18n.t("admin.price"), visible: true },
+ on_hand: { name: I18n.t("admin.on_hand"), visible: true },
+ on_demand: { name: I18n.t("admin.on_demand"), visible: false },
+ category: { name: I18n.t("#{node}.category"), visible: false },
+ tax_category: { name: I18n.t("#{node}.tax_category"), visible: false },
+ inherits_properties: { name: I18n.t("#{node}.inherits_properties?"), visible: false },
+ available_on: { name: I18n.t("#{node}.available_on"), visible: false }
+ }
+ end
+
+ def enterprises_index_columns
+ node = "admin.enterprises.index"
+ {
+ name: { name: I18n.t("admin.name"), visible: true },
+ producer: { name: I18n.t("#{node}.producer?"), visible: true },
+ package: { name: I18n.t("#{node}.package"), visible: true },
+ status: { name: I18n.t("#{node}.status"), visible: true },
+ manage: { name: I18n.t("#{node}.manage"), visible: true }
+ }
+ end
+ end
+end
diff --git a/lib/open_food_network/order_cycle_form_applicator.rb b/lib/open_food_network/order_cycle_form_applicator.rb
index 6d2e3e3e53..2cccc4a4fa 100644
--- a/lib/open_food_network/order_cycle_form_applicator.rb
+++ b/lib/open_food_network/order_cycle_form_applicator.rb
@@ -42,13 +42,15 @@ module OpenFoodNetwork
{variant_ids: variant_ids,
enterprise_fee_ids: enterprise_fee_ids,
pickup_time: exchange[:pickup_time],
- pickup_instructions: exchange[:pickup_instructions]})
+ pickup_instructions: exchange[:pickup_instructions],
+ tag_list: exchange[:tag_list]})
else
add_exchange(@order_cycle.coordinator_id, exchange[:enterprise_id], false,
{variant_ids: variant_ids,
enterprise_fee_ids: enterprise_fee_ids,
pickup_time: exchange[:pickup_time],
- pickup_instructions: exchange[:pickup_instructions]})
+ pickup_instructions: exchange[:pickup_instructions],
+ tag_list: exchange[:tag_list]})
end
end
@@ -81,6 +83,7 @@ module OpenFoodNetwork
attrs.delete :enterprise_fee_ids
attrs.delete :pickup_time
attrs.delete :pickup_instructions
+ attrs.delete :tag_list
end
if permission_for exchange
diff --git a/lib/open_food_network/orders_and_fulfillments_report.rb b/lib/open_food_network/orders_and_fulfillments_report.rb
index 52d07771a2..089838a1b1 100644
--- a/lib/open_food_network/orders_and_fulfillments_report.rb
+++ b/lib/open_food_network/orders_and_fulfillments_report.rb
@@ -18,7 +18,14 @@ module OpenFoodNetwork
["Hub", "Producer", "Product", "Variant", "Amount", "Curr. Cost per Unit", "Total Cost", "Total Shipping Cost", "Shipping Method"]
when "order_cycle_customer_totals"
["Hub", "Customer", "Email", "Phone", "Producer", "Product", "Variant",
- "Amount", "Item (#{currency_symbol})", "Item + Fees (#{currency_symbol})", "Admin & Handling (#{currency_symbol})", "Ship (#{currency_symbol})", "Total (#{currency_symbol})", "Paid?",
+ "Amount",
+ "Item (#{currency_symbol})",
+ "Item + Fees (#{currency_symbol})",
+ "Admin & Handling (#{currency_symbol})",
+ "Ship (#{currency_symbol})",
+ "Pay fee (#{currency_symbol})",
+ "Total (#{currency_symbol})",
+ "Paid?",
"Shipping", "Delivery?",
"Ship Street", "Ship Street 2", "Ship City", "Ship Postcode", "Ship State",
"Comments", "SKU",
@@ -118,6 +125,7 @@ module OpenFoodNetwork
proc { |line_items| line_items.sum { |li| li.amount_with_adjustments } },
proc { |line_items| line_items.map { |li| li.order }.uniq.sum { |o| o.admin_and_handling_total } },
proc { |line_items| line_items.map { |li| li.order }.uniq.sum { |o| o.ship_total } },
+ proc { |line_items| line_items.map { |li| li.order }.uniq.sum { |o| o.payment_fee } },
proc { |line_items| line_items.map { |li| li.order }.uniq.sum { |o| o.total } },
proc { |line_items| line_items.all? { |li| li.order.paid? } ? "Yes" : "No" },
diff --git a/lib/open_food_network/scope_product_to_hub.rb b/lib/open_food_network/scope_product_to_hub.rb
index d3e018f295..98ffbd84ff 100644
--- a/lib/open_food_network/scope_product_to_hub.rb
+++ b/lib/open_food_network/scope_product_to_hub.rb
@@ -4,7 +4,7 @@ module OpenFoodNetwork
class ScopeProductToHub
def initialize(hub)
@hub = hub
- @variant_overrides = VariantOverride.indexed @hub
+ @variant_overrides = VariantOverride.indexed(@hub)
end
def scope(product)
diff --git a/lib/open_food_network/scope_variant_to_hub.rb b/lib/open_food_network/scope_variant_to_hub.rb
index 37a2455616..dadcfbd9a8 100644
--- a/lib/open_food_network/scope_variant_to_hub.rb
+++ b/lib/open_food_network/scope_variant_to_hub.rb
@@ -50,6 +50,10 @@ module OpenFoodNetwork
def sku
@variant_override.andand.sku || super
end
+
+ def tag_list
+ @variant_override.andand.tag_list || []
+ end
end
end
end
diff --git a/lib/open_food_network/tag_rule_applicator.rb b/lib/open_food_network/tag_rule_applicator.rb
new file mode 100644
index 0000000000..5225dfdd86
--- /dev/null
+++ b/lib/open_food_network/tag_rule_applicator.rb
@@ -0,0 +1,61 @@
+module OpenFoodNetwork
+ class TagRuleApplicator
+ attr_reader :enterprise, :rule_class, :customer_tags
+
+ def initialize(enterprise, rule_type, customer_tags=[])
+ raise "Enterprise cannot be nil" if enterprise.nil?
+ raise "Rule Type cannot be nil" if rule_type.nil?
+
+ @enterprise = enterprise
+ @rule_class = "TagRule::#{rule_type}".constantize
+ @customer_tags = customer_tags || []
+ end
+
+ def filter!(subject)
+ return unless subject.respond_to?(:any?) && subject.any?
+ subject.reject! do |element|
+ if rule_class.respond_to?(:tagged_children_for)
+ children = rule_class.tagged_children_for(element)
+ children.reject! { |child| reject?(child) }
+ children.empty?
+ else
+ reject?(element)
+ end
+ end
+ end
+
+ private
+
+ def reject?(element)
+ customer_rules.each do |rule|
+ return rule.reject_matched? if rule.tags_match?(element)
+ end
+
+ default_rules.each do |rule|
+ return rule.reject_matched? if rule.tags_match?(element)
+ end
+
+ false
+ end
+
+ def rules
+ return @rules unless @rules.nil?
+ @rules = rule_class.prioritised.for(enterprise)
+ end
+
+ def customer_rules
+ return @customer_matched_rules unless @customer_matched_rules.nil?
+ @customer_matched_rules = rules.select{ |rule| customer_tags_match?(rule) }
+ end
+
+ def default_rules
+ return @default_rules unless @default_rules.nil?
+ @default_rules = rules.select(&:is_default?)
+ end
+
+ def customer_tags_match?(rule)
+ preferred_tags = rule.preferred_customer_tags.split(",")
+ (customer_tags & preferred_tags).any?
+ end
+ end
+end
diff --git a/lib/open_food_network/xero_invoices_report.rb b/lib/open_food_network/xero_invoices_report.rb
index 331c8b204c..cab44aa55e 100644
--- a/lib/open_food_network/xero_invoices_report.rb
+++ b/lib/open_food_network/xero_invoices_report.rb
@@ -88,6 +88,7 @@ module OpenFoodNetwork
rows += produce_summary_rows(order, invoice_number, opts) unless detail?
rows += fee_summary_rows(order, invoice_number, opts) unless detail? && order.account_invoice?
rows += shipping_summary_rows(order, invoice_number, opts)
+ rows += payment_summary_rows(order, invoice_number, opts)
rows += admin_adjustment_summary_rows(order, invoice_number, opts) unless detail?
rows
@@ -107,6 +108,10 @@ module OpenFoodNetwork
[summary_row(order, 'Delivery Shipping Cost (tax inclusive)', total_shipping(order), invoice_number, tax_on_shipping_s(order), opts)]
end
+ def payment_summary_rows(order, invoice_number, opts)
+ [summary_row(order, 'Transaction Fee (no tax)', total_transaction(order), invoice_number, 'GST Free Income', opts)]
+ end
+
def admin_adjustment_summary_rows(order, invoice_number, opts)
[summary_row(order, 'Total untaxable admin adjustments (no tax)', total_untaxable_admin_adjustments(order), invoice_number, 'GST Free Income', opts),
summary_row(order, 'Total taxable admin adjustments (tax inclusive)', total_taxable_admin_adjustments(order), invoice_number, 'GST on Income', opts)]
@@ -189,6 +194,10 @@ module OpenFoodNetwork
order.adjustments.shipping.sum &:amount
end
+ def total_transaction(order)
+ order.adjustments.payment_fee.sum &:amount
+ end
+
def tax_on_shipping_s(order)
tax_on_shipping = order.adjustments.shipping.sum(&:included_tax) > 0
tax_on_shipping ? 'GST on Income' : 'GST Free Income'
diff --git a/package.json b/package.json
new file mode 100644
index 0000000000..bb174e883d
--- /dev/null
+++ b/package.json
@@ -0,0 +1,17 @@
+{
+ "name": "openfoodnetwork",
+ "version": "1.7.1",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/openfoodfoundation/openfoodnetwork"
+ },
+ "devDependencies": {
+ "phantomjs-prebuilt": "~2.1.7",
+ "karma": "~0.13.22",
+ "karma-jasmine": "~0.3.8",
+ "jasmine-core": "~2.4.1",
+ "karma-phantomjs-launcher": "~1.0.0",
+ "karma-coffee-preprocessor": "~0.3.0"
+ },
+ "license": "AGPL-1.0"
+}
diff --git a/public/404.html b/public/404.html
index b650949066..4e42e63b73 100644
--- a/public/404.html
+++ b/public/404.html
@@ -3,24 +3,25 @@
The page you were looking for doesn't exist (404)
@@ -28,10 +29,13 @@
-

-
It seems the page you're looking for is in a grump.
-
-
+

+
Oops! Page not found.
+
Please try again
+
This might be a temporary problem. Please click the back button to return to the previous screen or go back to Home and try again.
+
Contact support
+
If the problem persists or is urgent, please tell us about it. Find our contact details from the global Open Food Network Local page.
+
It really helps us if you can give as much detail as possible about what the missing page is about.