mirror of
https://github.com/openfoodfoundation/openfoodnetwork
synced 2026-02-27 01:43:22 +00:00
Merge branch 'master' into enterprise-fee-naming-example
This commit is contained in:
6
Gemfile
6
Gemfile
@@ -7,7 +7,11 @@ gem 'pg'
|
||||
gem 'spree', :github => 'openfoodfoundation/spree', :branch => '1-3-stable'
|
||||
gem 'spree_i18n', :github => 'spree/spree_i18n'
|
||||
gem 'spree_auth_devise', :github => 'spree/spree_auth_devise', :branch => '1-3-stable'
|
||||
gem 'spree_paypal_express', :github => "spree-contrib/better_spree_paypal_express", :branch => "1-3-stable"
|
||||
|
||||
# Waiting on merge of PR #117
|
||||
# https://github.com/spree-contrib/better_spree_paypal_express/pull/117
|
||||
gem 'spree_paypal_express', :github => "openfoodfoundation/better_spree_paypal_express", :branch => "1-3-stable"
|
||||
#gem 'spree_paypal_express', :github => "spree-contrib/better_spree_paypal_express", :branch => "1-3-stable"
|
||||
|
||||
gem 'comfortable_mexican_sofa'
|
||||
|
||||
|
||||
18
Gemfile.lock
18
Gemfile.lock
@@ -12,6 +12,15 @@ GIT
|
||||
specs:
|
||||
custom_error_message (1.1.1)
|
||||
|
||||
GIT
|
||||
remote: git://github.com/openfoodfoundation/better_spree_paypal_express.git
|
||||
revision: cdd61161ccd27cd8d183f9321422c7be113796b8
|
||||
branch: 1-3-stable
|
||||
specs:
|
||||
spree_paypal_express (2.0.3)
|
||||
paypal-sdk-merchant (= 1.106.1)
|
||||
spree_core (~> 1.3.4)
|
||||
|
||||
GIT
|
||||
remote: git://github.com/openfoodfoundation/spree.git
|
||||
revision: bbe5e779bcb883a1726ad4006d7c06b06c3f5372
|
||||
@@ -54,15 +63,6 @@ GIT
|
||||
spree_sample (1.3.6.beta)
|
||||
spree_core (= 1.3.6.beta)
|
||||
|
||||
GIT
|
||||
remote: git://github.com/spree-contrib/better_spree_paypal_express.git
|
||||
revision: db135b89a289aaab951c1228bcc55871de0cbba7
|
||||
branch: 1-3-stable
|
||||
specs:
|
||||
spree_paypal_express (2.0.3)
|
||||
paypal-sdk-merchant (= 1.106.1)
|
||||
spree_core (~> 1.3.4)
|
||||
|
||||
GIT
|
||||
remote: git://github.com/spree/deface.git
|
||||
revision: 1110a1336252109bce7f98f9182042e0bc2930ae
|
||||
|
||||
@@ -9,7 +9,6 @@ Supported by the Open Food Foundation, we are proudly open source and not-for-pr
|
||||
|
||||
We're part of global movement - get involved!
|
||||
|
||||
* We're crowd-funding RIGHT NOW - please help out at http://startsomegood.com/openfoodnetwork
|
||||
* Fill in this short survey to tell us who you are and what you want to do with OFN: https://docs.google.com/a/eaterprises.com.au/forms/d/1zxR5vSiU9CigJ9cEaC8-eJLgYid8CR8er7PPH9Mc-30/edit#
|
||||
* Find out more and join in the conversation - http://openfoodnetwork.org
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 17.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="32px" y="32px" height="32px" width="32px"
|
||||
viewBox="0 0 30.2 40" enable-background="new 0 0 30.2 40" xml:space="preserve">
|
||||
<g id="Layer_1_1_">
|
||||
<g>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.6 KiB |
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 17.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="32px" y="32px" height="32px" width="32px"
|
||||
viewBox="0 0 30.2 40" enable-background="new 0 0 30.2 40" xml:space="preserve">
|
||||
<g id="Layer_1_1_">
|
||||
<g>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 17.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="32px" y="32px" height="32px" width="32px"
|
||||
viewBox="0 0 30.2 40" enable-background="new 0 0 30.2 40" xml:space="preserve">
|
||||
<g id="Layer_1_1_">
|
||||
<g>
|
||||
|
||||
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.0 KiB |
@@ -1,3 +1,3 @@
|
||||
angular.module("ofn.admin", ["ngResource", "ngAnimate", "ofn.dropdown", "admin.products"]).config ($httpProvider) ->
|
||||
angular.module("ofn.admin", ["ngResource", "ngAnimate", "ofn.dropdown", "admin.products", "infinite-scroll"]).config ($httpProvider) ->
|
||||
$httpProvider.defaults.headers.common["X-CSRF-Token"] = $("meta[name=csrf-token]").attr("content")
|
||||
$httpProvider.defaults.headers.common["Accept"] = "application/json, text/javascript, */*"
|
||||
@@ -16,6 +16,7 @@
|
||||
//= require admin/spree_auth
|
||||
//= require admin/spree_promo
|
||||
//= require admin/spree_paypal_express
|
||||
//= require ../shared/ng-infinite-scroll.min.js
|
||||
//= require ./admin
|
||||
//= require ./enterprises/enterprises
|
||||
//= require ./payment_methods/payment_methods
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
angular.module("ofn.admin").controller "AdminProductEditCtrl", [
|
||||
"$scope", "$timeout", "$http", "dataFetcher", "DirtyProducts", "VariantUnitManager",
|
||||
($scope, $timeout, $http, dataFetcher, DirtyProducts, VariantUnitManager) ->
|
||||
"$scope", "$timeout", "$http", "dataFetcher", "DirtyProducts", "VariantUnitManager", "producers", "Taxons",
|
||||
($scope, $timeout, $http, dataFetcher, DirtyProducts, VariantUnitManager, producers, Taxons) ->
|
||||
$scope.updateStatusMessage =
|
||||
text: ""
|
||||
style: {}
|
||||
|
||||
$scope.columns =
|
||||
supplier: {name: "Supplier", visible: true}
|
||||
producer: {name: "Producer", visible: true}
|
||||
name: {name: "Name", visible: true}
|
||||
unit: {name: "Unit", visible: true}
|
||||
price: {name: "Price", visible: true}
|
||||
on_hand: {name: "On Hand", visible: true}
|
||||
taxons: {name: "Taxons", visible: false}
|
||||
category: {name: "Category", visible: false}
|
||||
available_on: {name: "Available On", visible: false}
|
||||
|
||||
$scope.variant_unit_options = VariantUnitManager.variantUnitOptions()
|
||||
|
||||
$scope.filterableColumns = [
|
||||
{ name: "Supplier", db_column: "supplier_name" },
|
||||
{ name: "Producer", db_column: "producer_name" },
|
||||
{ name: "Name", db_column: "name" }
|
||||
]
|
||||
|
||||
@@ -28,25 +28,20 @@ angular.module("ofn.admin").controller "AdminProductEditCtrl", [
|
||||
|
||||
$scope.optionTabs =
|
||||
filters: { title: "Filter Products", visible: false }
|
||||
column_toggle: { title: "Toggle Columns", visible: false }
|
||||
|
||||
$scope.perPage = 25
|
||||
$scope.currentPage = 1
|
||||
|
||||
$scope.producers = producers
|
||||
$scope.taxons = Taxons.taxons
|
||||
$scope.filterProducers = [{id: "0", name: ""}].concat $scope.producers
|
||||
$scope.filterTaxons = [{id: "0", name: ""}].concat $scope.taxons
|
||||
$scope.producerFilter = "0"
|
||||
$scope.categoryFilter = "0"
|
||||
$scope.products = []
|
||||
$scope.filteredProducts = []
|
||||
$scope.currentFilters = []
|
||||
$scope.totalCount = -> $scope.filteredProducts.length
|
||||
$scope.totalPages = -> Math.ceil($scope.totalCount()/$scope.perPage)
|
||||
$scope.firstVisibleProduct = -> ($scope.currentPage-1)*$scope.perPage+1
|
||||
$scope.lastVisibleProduct = -> Math.min($scope.totalCount(),$scope.currentPage*$scope.perPage)
|
||||
$scope.setPage = (page) -> $scope.currentPage = page
|
||||
$scope.minPage = -> Math.max(1,Math.min($scope.totalPages()-4,$scope.currentPage-2))
|
||||
$scope.maxPage = -> Math.min($scope.totalPages(),Math.max(5,$scope.currentPage+2))
|
||||
$scope.limit = 15
|
||||
$scope.productsWithUnsavedVariants = []
|
||||
|
||||
$scope.$watch ->
|
||||
$scope.totalPages()
|
||||
, (newVal, oldVal) ->
|
||||
$scope.currentPage = Math.max $scope.totalPages(), 1 if newVal != oldVal && $scope.totalPages() < $scope.currentPage
|
||||
|
||||
$scope.initialise = (spree_api_key) ->
|
||||
authorise_api_reponse = ""
|
||||
@@ -55,24 +50,29 @@ angular.module("ofn.admin").controller "AdminProductEditCtrl", [
|
||||
$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"] = spree_api_key
|
||||
dataFetcher("/api/enterprises/managed?template=bulk_index&q[is_primary_producer_eq]=true").then (data) ->
|
||||
$scope.suppliers = data
|
||||
# Need to have suppliers before we get products so we can match suppliers to product.supplier
|
||||
$scope.fetchProducts()
|
||||
$scope.fetchProducts()
|
||||
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.$watchCollection '[query, producerFilter, categoryFilter]', ->
|
||||
$scope.limit = 15 # Reset limit whenever searching
|
||||
|
||||
$scope.fetchProducts = -> # WARNING: returns a promise
|
||||
$scope.loading = true
|
||||
queryString = $scope.currentFilters.reduce (qs,f) ->
|
||||
return qs + "q[#{f.property.db_column}_#{f.predicate.predicate}]=#{f.value};"
|
||||
, ""
|
||||
return dataFetcher("/api/products/managed?template=bulk_index;page=1;per_page=500;#{queryString}").then (data) ->
|
||||
$scope.resetProducts data
|
||||
return dataFetcher("/api/products/bulk_products?page=1;per_page=20;#{queryString}").then (data) ->
|
||||
$scope.resetProducts data.products
|
||||
$scope.loading = false
|
||||
if data.pages > 1
|
||||
for page in [2..data.pages]
|
||||
dataFetcher("/api/products/bulk_products?page=#{page};per_page=20;#{queryString}").then (data) ->
|
||||
for product in data.products
|
||||
$scope.unpackProduct product
|
||||
$scope.products.push product
|
||||
|
||||
|
||||
$scope.resetProducts = (data) ->
|
||||
@@ -87,17 +87,15 @@ angular.module("ofn.admin").controller "AdminProductEditCtrl", [
|
||||
$scope.unpackProduct = (product) ->
|
||||
$scope.displayProperties ||= {}
|
||||
$scope.displayProperties[product.id] ||= showVariants: false
|
||||
$scope.matchSupplier product
|
||||
#$scope.matchProducer product
|
||||
$scope.loadVariantUnit product
|
||||
|
||||
|
||||
$scope.matchSupplier = (product) ->
|
||||
for i of $scope.suppliers
|
||||
supplier = $scope.suppliers[i]
|
||||
if angular.equals(supplier, product.supplier)
|
||||
product.supplier = supplier
|
||||
break
|
||||
|
||||
# $scope.matchProducer = (product) ->
|
||||
# for producer in $scope.producers
|
||||
# if angular.equals(producer.id, product.producer)
|
||||
# product.producer = producer
|
||||
# break
|
||||
|
||||
$scope.loadVariantUnit = (product) ->
|
||||
product.variant_unit_with_scale =
|
||||
@@ -108,13 +106,14 @@ angular.module("ofn.admin").controller "AdminProductEditCtrl", [
|
||||
else
|
||||
null
|
||||
|
||||
if product.variants
|
||||
for variant in product.variants
|
||||
$scope.loadVariantVariantUnit product, variant
|
||||
$scope.loadVariantVariantUnit product, product.master if product.master
|
||||
$scope.loadVariantUnitValues product if product.variants
|
||||
$scope.loadVariantUnitValue product, product.master if product.master
|
||||
|
||||
$scope.loadVariantUnitValues = (product) ->
|
||||
for variant in product.variants
|
||||
$scope.loadVariantUnitValue product, variant
|
||||
|
||||
$scope.loadVariantVariantUnit = (product, variant) ->
|
||||
$scope.loadVariantUnitValue = (product, variant) ->
|
||||
unit_value = $scope.variantUnitValue product, variant
|
||||
unit_value = if unit_value? then unit_value else ''
|
||||
variant.unit_value_with_description = "#{unit_value} #{variant.unit_description || ''}".trim()
|
||||
@@ -153,29 +152,10 @@ angular.module("ofn.admin").controller "AdminProductEditCtrl", [
|
||||
tab.visible = !tab.visible
|
||||
$scope.visibleTab = tab
|
||||
|
||||
$scope.addFilter = (filter) ->
|
||||
existingfilterIndex = $scope.indexOfFilter filter
|
||||
if $scope.filterableColumns.indexOf(filter.property) >= 0 && $scope.filterTypes.indexOf(filter.predicate) >= 0 && filter.value != "" && filter.value != undefined
|
||||
if (DirtyProducts.count() > 0 and confirm("Unsaved changes will be lost. Continue anyway?")) or (DirtyProducts.count() == 0)
|
||||
if existingfilterIndex == -1
|
||||
$scope.currentFilters.push filter
|
||||
$scope.fetchProducts()
|
||||
else if confirm("'#{filter.predicate.name}' filter already exists on column '#{filter.property.name}'. Replace it?")
|
||||
$scope.currentFilters[existingfilterIndex] = filter
|
||||
$scope.fetchProducts()
|
||||
else
|
||||
alert("Please ensure all filter fields are filled in before adding a filter.")
|
||||
|
||||
$scope.removeFilter = (filter) ->
|
||||
index = $scope.currentFilters.indexOf(filter)
|
||||
if index != -1
|
||||
$scope.currentFilters.splice index, 1
|
||||
$scope.fetchProducts()
|
||||
|
||||
$scope.indexOfFilter = (filter) ->
|
||||
for existingFilter, i in $scope.currentFilters
|
||||
return i if filter.property == existingFilter.property && filter.predicate == existingFilter.predicate
|
||||
return -1
|
||||
$scope.resetSelectFilters = ->
|
||||
$scope.query = ""
|
||||
$scope.producerFilter = "0"
|
||||
$scope.categoryFilter = "0"
|
||||
|
||||
$scope.editWarn = (product, variant) ->
|
||||
if (DirtyProducts.count() > 0 and confirm("Unsaved changes will be lost. Continue anyway?")) or (DirtyProducts.count() == 0)
|
||||
@@ -192,6 +172,7 @@ angular.module("ofn.admin").controller "AdminProductEditCtrl", [
|
||||
display_name: null
|
||||
on_hand: null
|
||||
price: null
|
||||
$scope.productsWithUnsavedVariants.push product
|
||||
$scope.displayProperties[product.id].showVariants = true
|
||||
|
||||
|
||||
@@ -200,6 +181,11 @@ angular.module("ofn.admin").controller "AdminProductEditCtrl", [
|
||||
$scope.variantIdCounter -= 1
|
||||
$scope.variantIdCounter
|
||||
|
||||
$scope.updateVariantLists = (server_products) ->
|
||||
for product in $scope.productsWithUnsavedVariants
|
||||
server_product = $scope.findProduct(product.id, server_products)
|
||||
product.variants = server_product.variants
|
||||
$scope.loadVariantUnitValues product
|
||||
|
||||
$scope.deleteProduct = (product) ->
|
||||
if confirm("Are you sure?")
|
||||
@@ -244,7 +230,7 @@ angular.module("ofn.admin").controller "AdminProductEditCtrl", [
|
||||
|
||||
|
||||
$scope.hasVariants = (product) ->
|
||||
Object.keys(product.variants).length > 0
|
||||
product.variants.length > 0
|
||||
|
||||
|
||||
$scope.hasUnit = (product) ->
|
||||
@@ -269,7 +255,7 @@ angular.module("ofn.admin").controller "AdminProductEditCtrl", [
|
||||
if productsToSubmit.length > 0
|
||||
$scope.updateProducts productsToSubmit # Don't submit an empty list
|
||||
else
|
||||
$scope.setMessage $scope.updateStatusMessage, "No changes to update.", color: "grey", 3000
|
||||
$scope.setMessage $scope.updateStatusMessage, "No changes to save.", color: "grey", 3000
|
||||
|
||||
|
||||
$scope.updateProducts = (productsToSubmit) ->
|
||||
@@ -281,22 +267,16 @@ angular.module("ofn.admin").controller "AdminProductEditCtrl", [
|
||||
products: productsToSubmit
|
||||
filters: $scope.currentFilters
|
||||
).success((data) ->
|
||||
# TODO: remove this check altogether, need to write controller tests if we want to test this behaviour properly
|
||||
# Note: Rob implemented subset(), which is a simpler alternative to productsWithoutDerivedAttributes(). However, it
|
||||
# conflicted with some changes I made before merging my work, so for now I've reverted to the old way of
|
||||
# doing things. TODO: Review together and decide on strategy here. -- Rohan, 14-1-2014
|
||||
#if subset($scope.productsWithoutDerivedAttributes(), data)
|
||||
if $scope.productListsMatch $scope.products, data
|
||||
$scope.resetProducts data
|
||||
$timeout -> $scope.displaySuccess()
|
||||
else
|
||||
# console.log angular.toJson($scope.productsWithoutDerivedAttributes($scope.products))
|
||||
# console.log "---"
|
||||
# console.log angular.toJson($scope.productsWithoutDerivedAttributes(data))
|
||||
# console.log "---"
|
||||
$scope.displayFailure "Product lists do not match."
|
||||
DirtyProducts.clear()
|
||||
$scope.updateVariantLists(data.products)
|
||||
$timeout -> $scope.displaySuccess()
|
||||
).error (data, status) ->
|
||||
$scope.displayFailure "Server returned with error status: " + status
|
||||
if status == 400 && data.errors? && data.errors.length > 0
|
||||
errors = error + "\n" for error in data.errors
|
||||
alert "Saving failed with the following error(s):\n" + errors
|
||||
$scope.displayFailure "Save failed due to invalid data"
|
||||
else
|
||||
$scope.displayFailure "Server returned with error status: " + status
|
||||
|
||||
|
||||
$scope.packProduct = (product) ->
|
||||
@@ -322,58 +302,19 @@ angular.module("ofn.admin").controller "AdminProductEditCtrl", [
|
||||
if variant.hasOwnProperty("unit_value_with_description")
|
||||
match = variant.unit_value_with_description.match(/^([\d\.]+(?= |$)|)( |)(.*)$/)
|
||||
if match
|
||||
product = $scope.findProduct(product.id)
|
||||
product = $scope.findProduct(product.id, $scope.products)
|
||||
variant.unit_value = parseFloat(match[1])
|
||||
variant.unit_value = null if isNaN(variant.unit_value)
|
||||
variant.unit_value *= product.variant_unit_scale if variant.unit_value && product.variant_unit_scale
|
||||
variant.unit_description = match[3]
|
||||
|
||||
|
||||
$scope.productListsMatch = (clientProducts, serverProducts) ->
|
||||
$scope.copyNewVariantIds clientProducts, serverProducts
|
||||
angular.toJson($scope.productsWithoutDerivedAttributes(clientProducts)) == angular.toJson($scope.productsWithoutDerivedAttributes(serverProducts))
|
||||
|
||||
|
||||
# When variants are created clientside, they are given a negative id. The server
|
||||
# responds with a real id, which would cause the productListsMatch() check to fail.
|
||||
# To avoid that false negative, we copy the server variant id to the client for any
|
||||
# negative ids.
|
||||
$scope.copyNewVariantIds = (clientProducts, serverProducts) ->
|
||||
if clientProducts?
|
||||
for product, i in clientProducts
|
||||
if product.variants?
|
||||
for variant, j in product.variants
|
||||
if variant.id < 0
|
||||
variant.id = serverProducts[i].variants[j].id
|
||||
|
||||
|
||||
$scope.productsWithoutDerivedAttributes = (products) ->
|
||||
products_filtered = []
|
||||
if products
|
||||
products_filtered = $scope.deepCopyProducts products
|
||||
for product in products_filtered
|
||||
delete product.variant_unit_with_scale
|
||||
if product.variants
|
||||
for variant in product.variants
|
||||
delete variant.unit_value_with_description
|
||||
# If we end up live-updating this field, we might want to reinstate its verification here
|
||||
delete variant.options_text
|
||||
delete product.master
|
||||
products_filtered
|
||||
|
||||
|
||||
$scope.deepCopyProducts = (products) ->
|
||||
copied_products = (angular.extend {}, product for product in products)
|
||||
for product in copied_products
|
||||
if product.variants
|
||||
product.variants = (angular.extend {}, variant for variant in product.variants)
|
||||
copied_products
|
||||
|
||||
|
||||
$scope.findProduct = (id) ->
|
||||
products = (product for product in $scope.products when product.id == id)
|
||||
$scope.findProduct = (id, product_list) ->
|
||||
products = (product for product in product_list when product.id == id)
|
||||
if products.length == 0 then null else products[0]
|
||||
|
||||
$scope.incrementLimit = ->
|
||||
if $scope.limit < $scope.products.length
|
||||
$scope.limit = $scope.limit + 5
|
||||
|
||||
$scope.setMessage = (model, text, style, timeout) ->
|
||||
model.text = text
|
||||
@@ -386,26 +327,27 @@ angular.module("ofn.admin").controller "AdminProductEditCtrl", [
|
||||
|
||||
|
||||
$scope.displayUpdating = ->
|
||||
$scope.setMessage $scope.updateStatusMessage, "Updating...",
|
||||
color: "orange"
|
||||
$scope.setMessage $scope.updateStatusMessage, "Saving...",
|
||||
color: "#FF9906"
|
||||
, false
|
||||
|
||||
|
||||
$scope.displaySuccess = ->
|
||||
$scope.setMessage $scope.updateStatusMessage, "Update complete",
|
||||
color: "green"
|
||||
$scope.setMessage $scope.updateStatusMessage, "Changes saved.",
|
||||
color: "#9fc820"
|
||||
, 3000
|
||||
|
||||
|
||||
$scope.displayFailure = (failMessage) ->
|
||||
$scope.setMessage $scope.updateStatusMessage, "Updating failed. " + failMessage,
|
||||
color: "red"
|
||||
, 10000
|
||||
$scope.setMessage $scope.updateStatusMessage, "Saving failed. " + failMessage,
|
||||
color: "#DA5354"
|
||||
, false
|
||||
|
||||
|
||||
$scope.displayDirtyProducts = ->
|
||||
if DirtyProducts.count() > 0
|
||||
$scope.setMessage $scope.updateStatusMessage, "Changes to " + DirtyProducts.count() + " products remain unsaved.",
|
||||
message = if DirtyProducts.count() == 1 then "one product" else DirtyProducts.count() + " products"
|
||||
$scope.setMessage $scope.updateStatusMessage, "Changes to " + message + " remain unsaved.",
|
||||
color: "gray"
|
||||
, false
|
||||
else
|
||||
@@ -442,8 +384,8 @@ filterSubmitProducts = (productsToFilter) ->
|
||||
if product.hasOwnProperty("name")
|
||||
filteredProduct.name = product.name
|
||||
hasUpdatableProperty = true
|
||||
if product.hasOwnProperty("supplier")
|
||||
filteredProduct.supplier_id = product.supplier.id
|
||||
if product.hasOwnProperty("producer")
|
||||
filteredProduct.supplier_id = product.producer
|
||||
hasUpdatableProperty = true
|
||||
if product.hasOwnProperty("price")
|
||||
filteredProduct.price = product.price
|
||||
@@ -458,8 +400,8 @@ filterSubmitProducts = (productsToFilter) ->
|
||||
if product.hasOwnProperty("on_hand") and filteredVariants.length == 0 #only update if no variants present
|
||||
filteredProduct.on_hand = product.on_hand
|
||||
hasUpdatableProperty = true
|
||||
if product.hasOwnProperty("taxon_ids")
|
||||
filteredProduct.taxon_ids = product.taxon_ids
|
||||
if product.hasOwnProperty("category")
|
||||
filteredProduct.primary_taxon_id = product.category
|
||||
hasUpdatableProperty = true
|
||||
if product.hasOwnProperty("available_on")
|
||||
filteredProduct.available_on = product.available_on
|
||||
@@ -510,11 +452,3 @@ toObjectWithIDKeys = (array) ->
|
||||
object[array[i].id].variants = toObjectWithIDKeys(array[i].variants) if array[i].hasOwnProperty("variants") and array[i].variants instanceof Array
|
||||
|
||||
object
|
||||
|
||||
subset = (bigArray,smallArray) ->
|
||||
if smallArray instanceof Array && bigArray instanceof Array && smallArray.length > 0
|
||||
for item in smallArray
|
||||
return false if angular.toJson(bigArray).indexOf(angular.toJson(item)) == -1
|
||||
return true
|
||||
else
|
||||
return false
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
angular.module("ofn.admin").controller "AdminEnterpriseRelationshipsCtrl", ($scope, EnterpriseRelationships, Enterprises) ->
|
||||
$scope.EnterpriseRelationships = EnterpriseRelationships
|
||||
$scope.Enterprises = Enterprises
|
||||
$scope.permissions = {}
|
||||
|
||||
$scope.create = ->
|
||||
$scope.EnterpriseRelationships.create($scope.parent_id, $scope.child_id)
|
||||
$scope.EnterpriseRelationships.create($scope.parent_id, $scope.child_id, $scope.permissions)
|
||||
|
||||
$scope.delete = (enterprise_relationship) ->
|
||||
if confirm("Are you sure?")
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
angular.module("ofn.admin").controller "AdminEnterpriseRolesCtrl", ($scope, EnterpriseRoles, Users, Enterprises) ->
|
||||
$scope.EnterpriseRoles = EnterpriseRoles
|
||||
$scope.Users = Users
|
||||
$scope.Enterprises = Enterprises
|
||||
|
||||
$scope.create = ->
|
||||
$scope.EnterpriseRoles.create($scope.user_id, $scope.enterprise_id)
|
||||
|
||||
$scope.delete = (enterprise_role) ->
|
||||
if confirm("Are you sure?")
|
||||
$scope.EnterpriseRoles.delete enterprise_role
|
||||
@@ -0,0 +1,7 @@
|
||||
angular.module("ofn.admin").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 = ""
|
||||
@@ -0,0 +1,7 @@
|
||||
angular.module("ofn.admin").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};"
|
||||
@@ -4,18 +4,16 @@ angular.module("ofn.admin").directive "ofnTaxonAutocomplete", (Taxons) ->
|
||||
link: (scope,element,attrs,ngModel) ->
|
||||
setTimeout ->
|
||||
element.select2
|
||||
placeholder: Spree.translations.taxon_placeholder
|
||||
multiple: true
|
||||
placeholder: "Category"
|
||||
multiple: false
|
||||
initSelection: (element, callback) ->
|
||||
Taxons.findByIDs(element.val()).$promise.then (result) ->
|
||||
callback Taxons.cleanTaxons(result)
|
||||
callback Taxons.findByID(scope.product.category)
|
||||
query: (query) ->
|
||||
Taxons.findByTerm(query.term).$promise.then (result) ->
|
||||
query.callback { results: Taxons.cleanTaxons(result) }
|
||||
query.callback { results: Taxons.findByTerm(query.term) }
|
||||
formatResult: (taxon) ->
|
||||
taxon.pretty_name
|
||||
taxon.name
|
||||
formatSelection: (taxon) ->
|
||||
taxon.pretty_name
|
||||
taxon.name
|
||||
element.on "change", ->
|
||||
scope.$apply ->
|
||||
ngModel.$setViewValue element.val()
|
||||
@@ -4,8 +4,8 @@ angular.module("admin.enterprises")
|
||||
$scope.PaymentMethods = PaymentMethods.paymentMethods
|
||||
$scope.ShippingMethods = ShippingMethods.shippingMethods
|
||||
|
||||
for PaymentMethod in $scope.PaymentMethods
|
||||
PaymentMethod.selected = if PaymentMethod.id in $scope.Enterprise.payment_method_ids then true else false
|
||||
for payment_method in $scope.PaymentMethods
|
||||
payment_method.selected = payment_method.id in $scope.Enterprise.payment_method_ids
|
||||
|
||||
$scope.paymentMethodsColor = ->
|
||||
if $scope.PaymentMethods.length > 0
|
||||
@@ -14,13 +14,13 @@ angular.module("admin.enterprises")
|
||||
"red"
|
||||
|
||||
$scope.selectedPaymentMethodsCount = ->
|
||||
$scope.PaymentMethods.reduce (count, PaymentMethod) ->
|
||||
count++ if PaymentMethod.selected
|
||||
$scope.PaymentMethods.reduce (count, payment_method) ->
|
||||
count++ if payment_method.selected
|
||||
count
|
||||
, 0
|
||||
|
||||
for ShippingMethod in $scope.ShippingMethods
|
||||
ShippingMethod.selected = if ShippingMethod.id in $scope.Enterprise.shipping_method_ids then true else false
|
||||
for shipping_method in $scope.ShippingMethods
|
||||
shipping_method.selected = shipping_method.id in $scope.Enterprise.shipping_method_ids
|
||||
|
||||
$scope.shippingMethodsColor = ->
|
||||
if $scope.ShippingMethods.length > 0
|
||||
@@ -29,7 +29,7 @@ angular.module("admin.enterprises")
|
||||
"red"
|
||||
|
||||
$scope.selectedShippingMethodsCount = ->
|
||||
$scope.ShippingMethods.reduce (count, ShippingMethod) ->
|
||||
count++ if ShippingMethod.selected
|
||||
$scope.ShippingMethods.reduce (count, shipping_method) ->
|
||||
count++ if shipping_method.selected
|
||||
count
|
||||
, 0
|
||||
@@ -0,0 +1,4 @@
|
||||
angular.module("ofn.admin").filter "category", ($filter) ->
|
||||
return (products, taxonID) ->
|
||||
return products if taxonID == "0"
|
||||
return $filter('filter')( products, { category: taxonID }, true )
|
||||
@@ -0,0 +1,4 @@
|
||||
angular.module("ofn.admin").filter "producer", ($filter) ->
|
||||
return (products, producerID) ->
|
||||
return products if producerID == "0"
|
||||
$filter('filter')( products, { producer: producerID }, true )
|
||||
@@ -0,0 +1,7 @@
|
||||
angular.module("ofn.admin").filter "taxonsTermFilter", ->
|
||||
return (lineItems,selectedSupplier,selectedDistributor,selectedOrderCycle) ->
|
||||
filtered = []
|
||||
filtered.push lineItem for lineItem in lineItems when (angular.equals(selectedSupplier,"0") || lineItem.supplier.id == selectedSupplier) &&
|
||||
(angular.equals(selectedDistributor,"0") || lineItem.order.distributor.id == selectedDistributor) &&
|
||||
(angular.equals(selectedOrderCycle,"0") || lineItem.order.order_cycle.id == selectedOrderCycle)
|
||||
filtered
|
||||
@@ -330,7 +330,7 @@ angular.module('order_cycle', ['ngResource'])
|
||||
}])
|
||||
|
||||
.factory('Enterprise', ['$resource', ($resource) ->
|
||||
Enterprise = $resource('/admin/enterprises/:enterprise_id.json', {}, {'index': {method: 'GET', isArray: true}})
|
||||
Enterprise = $resource('/admin/enterprises/for_order_cycle/:enterprise_id.json', {}, {'index': {method: 'GET', isArray: true}})
|
||||
|
||||
{
|
||||
Enterprise: Enterprise
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
angular.module("ofn.admin").factory 'EnterpriseRelationships', ($http, enterprise_relationships) ->
|
||||
new class EnterpriseRelationships
|
||||
create_errors: ""
|
||||
all_permissions: [
|
||||
'add_to_order_cycle'
|
||||
'manage_products'
|
||||
]
|
||||
|
||||
constructor: ->
|
||||
@enterprise_relationships = enterprise_relationships
|
||||
|
||||
create: (parent_id, child_id) ->
|
||||
$http.post('/admin/enterprise_relationships', {enterprise_relationship: {parent_id: parent_id, child_id: child_id}}).success (data, status) =>
|
||||
create: (parent_id, child_id, permissions) ->
|
||||
permissions = (name for name, enabled of permissions when enabled)
|
||||
$http.post('/admin/enterprise_relationships', {enterprise_relationship: {parent_id: parent_id, child_id: child_id, permissions_list: permissions}}).success (data, status) =>
|
||||
@enterprise_relationships.unshift(data)
|
||||
@create_errors = ""
|
||||
|
||||
@@ -16,3 +21,8 @@ angular.module("ofn.admin").factory 'EnterpriseRelationships', ($http, enterpris
|
||||
delete: (er) ->
|
||||
$http.delete('/admin/enterprise_relationships/' + er.id).success (data) =>
|
||||
@enterprise_relationships.splice @enterprise_relationships.indexOf(er), 1
|
||||
|
||||
permission_presentation: (permission) ->
|
||||
switch permission
|
||||
when "add_to_order_cycle" then "can add to order cycle"
|
||||
when "manage_products" then "can manage the products of"
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
angular.module("ofn.admin").factory 'EnterpriseRoles', ($http, enterpriseRoles) ->
|
||||
new class EnterpriseRoles
|
||||
create_errors: ""
|
||||
|
||||
constructor: ->
|
||||
@enterprise_roles = enterpriseRoles
|
||||
|
||||
create: (user_id, enterprise_id) ->
|
||||
$http.post('/admin/enterprise_roles', {enterprise_role: {user_id: user_id, enterprise_id: enterprise_id}}).success (data, status) =>
|
||||
@enterprise_roles.unshift(data)
|
||||
@create_errors = ""
|
||||
|
||||
.error (response, status) =>
|
||||
@create_errors = response.errors
|
||||
|
||||
delete: (er) ->
|
||||
$http.delete('/admin/enterprise_roles/' + er.id).success (data) =>
|
||||
@enterprise_roles.splice @enterprise_roles.indexOf(er), 1
|
||||
@@ -1,13 +1,15 @@
|
||||
angular.module("ofn.admin").factory "Taxons", ($resource) ->
|
||||
resource = $resource "/admin/taxons/search"
|
||||
angular.module("ofn.admin").factory "Taxons", (taxons, $filter) ->
|
||||
new class Taxons
|
||||
constructor: ->
|
||||
@taxons = taxons
|
||||
|
||||
return {
|
||||
# For finding a single Taxon
|
||||
findByID: (id) ->
|
||||
$filter('filter')(@taxons, {id: id}, true)[0]
|
||||
|
||||
# For finding multiple Taxons represented by comma delimited string
|
||||
findByIDs: (ids) ->
|
||||
resource.get { ids: ids }
|
||||
taxon for taxon in @taxons when taxon.id.toString() in ids.split(",")
|
||||
|
||||
findByTerm: (term) ->
|
||||
resource.get { q: term }
|
||||
|
||||
cleanTaxons: (data) ->
|
||||
data['taxons'].map (result) -> result
|
||||
}
|
||||
$filter('filter')(@taxons, term)
|
||||
4
app/assets/javascripts/admin/services/users.js.coffee
Normal file
4
app/assets/javascripts/admin/services/users.js.coffee
Normal file
@@ -0,0 +1,4 @@
|
||||
angular.module("ofn.admin").factory 'Users', (users) ->
|
||||
new class Users
|
||||
constructor: ->
|
||||
@users = users
|
||||
@@ -11,7 +11,6 @@
|
||||
#= require lodash.underscore.js
|
||||
#= require angular-scroll.min.js
|
||||
#= require angular-google-maps.min.js
|
||||
#= require angular-timer.min.js
|
||||
#= require ../shared/mm-foundation-tpls-0.2.2.min.js
|
||||
#= require ../shared/bindonce.min.js
|
||||
#= require ../shared/ng-infinite-scroll.min.js
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
Darkswarm.controller "LoginCtrl", ($scope, $http, AuthenticationService, Redirections, Loading) ->
|
||||
Darkswarm.controller "LoginCtrl", ($scope, $http, $window, AuthenticationService, Redirections, Loading) ->
|
||||
$scope.path = "/login"
|
||||
|
||||
$scope.submit = ->
|
||||
Loading.message = "Hold on a moment, we're logging you in"
|
||||
$http.post("/user/spree_user/sign_in", {spree_user: $scope.spree_user}).success (data)->
|
||||
if Redirections.after_login
|
||||
location.href = location.origin + Redirections.after_login
|
||||
$window.location.href = $window.location.origin + Redirections.after_login
|
||||
else
|
||||
location.href = location.origin + location.pathname # Strips out hash fragments
|
||||
$window.location.href = $window.location.origin + $window.location.pathname # Strips out hash fragments
|
||||
.error (data) ->
|
||||
Loading.clear()
|
||||
$scope.errors = data.message
|
||||
|
||||
@@ -3,7 +3,7 @@ Darkswarm.controller "CheckoutCtrl", ($scope, storage, Checkout, CurrentUser, Cu
|
||||
|
||||
# Bind to local storage
|
||||
$scope.fieldsToBind = ["bill_address", "email", "payment_method_id", "shipping_method_id", "ship_address"]
|
||||
prefix = "order_#{Checkout.order.id}#{Checkout.order.user_id}#{CurrentHub.hub.id}"
|
||||
prefix = "order_#{Checkout.order.id}#{CurrentUser?.id}#{CurrentHub.hub.id}"
|
||||
|
||||
for field in $scope.fieldsToBind
|
||||
storage.bind $scope, "Checkout.order.#{field}",
|
||||
|
||||
@@ -6,7 +6,7 @@ Darkswarm.controller "PaymentCtrl", ($scope, $timeout) ->
|
||||
{key: "January", value: "1"},
|
||||
{key: "February", value: "2"},
|
||||
{key: "March", value: "3"},
|
||||
{key: "April", value: "4"},
|
||||
{key: "April", value: "4"},
|
||||
{key: "May", value: "5"},
|
||||
{key: "June", value: "6"},
|
||||
{key: "July", value: "7"},
|
||||
@@ -20,4 +20,4 @@ Darkswarm.controller "PaymentCtrl", ($scope, $timeout) ->
|
||||
$scope.years = [moment().year()..(moment().year()+15)]
|
||||
$scope.secrets.card_month = "1"
|
||||
$scope.secrets.card_year = moment().year()
|
||||
$timeout $scope.onTimeout
|
||||
$timeout $scope.onTimeout
|
||||
|
||||
@@ -6,7 +6,8 @@ Darkswarm.controller "ProductsCtrl", ($scope, $rootScope, Products, OrderCycle,
|
||||
$scope.filterText = FilterSelectorsService.filterText
|
||||
$scope.FilterSelectorsService = FilterSelectorsService
|
||||
$scope.limit = 3
|
||||
$scope.ordering = {order: "name"}
|
||||
$scope.ordering =
|
||||
order: "primary_taxon.name"
|
||||
$scope.order_cycle = OrderCycle.order_cycle
|
||||
|
||||
$scope.incrementLimit = ->
|
||||
|
||||
@@ -5,7 +5,6 @@ window.Darkswarm = angular.module("Darkswarm", ["ngResource",
|
||||
'infinite-scroll',
|
||||
'angular-flash.service',
|
||||
'templates',
|
||||
'timer',
|
||||
'ngSanitize',
|
||||
'ngAnimate',
|
||||
'google-maps',
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
Darkswarm.directive "activeSelector", ->
|
||||
# A generic selector that allows an object/scope to be toggled between active and inactive
|
||||
# Used in the filters, but hypothetically useable anywhere
|
||||
restrict: 'E'
|
||||
transclude: true
|
||||
replace: true
|
||||
@@ -8,5 +10,6 @@ Darkswarm.directive "activeSelector", ->
|
||||
elem.bind "click", ->
|
||||
scope.$apply ->
|
||||
scope.selector.active = !scope.selector.active
|
||||
scope.emit()
|
||||
# This function is a convention, e.g. a callback on the scope applied when active changes
|
||||
scope.emit() if scope.emit
|
||||
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
Darkswarm.directive "activeTableHubLink", (CurrentHub, CurrentOrder) ->
|
||||
# Change the text of the hub link based on CurrentHub
|
||||
# To be used with ofnEmptiesCart
|
||||
# Takes "change" and "shop" as text string attributes
|
||||
restrict: "A"
|
||||
scope:
|
||||
hub: '=activeTableHubLink'
|
||||
template: "{{action}}"
|
||||
link: (scope, elm, attr)->
|
||||
# Swap out the text of the hub link depending on whether it'll change current hub
|
||||
# To be used with ofnEmptiesCart
|
||||
if CurrentHub.hub?.id and CurrentHub.hub.id isnt scope.hub.id
|
||||
scope.action = attr.change
|
||||
else
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
Darkswarm.directive "cart", ->
|
||||
# Toggles visibility of the "cart" popover
|
||||
restrict: 'A'
|
||||
link: (scope, elem, attr)->
|
||||
scope.open = false
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
Darkswarm.directive "ngDebounce", ($timeout) ->
|
||||
# Slows down ng-model updates, only triggering binding ngDebounce milliseconds
|
||||
# after the last change. Used to prevent squirrely UI
|
||||
restrict: "A"
|
||||
require: "ngModel"
|
||||
priority: 99
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
Darkswarm.directive "ofnDisableEnter", ()->
|
||||
# Stops enter from doing normal enter things
|
||||
restrict: 'A'
|
||||
link: (scope, element, attrs)->
|
||||
element.bind "keydown keypress", (e)->
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
Darkswarm.directive "ofnDisableScroll", ()->
|
||||
# Stops scrolling from incrementing or decrementing input value
|
||||
# Useful for number inputs
|
||||
restrict: 'A'
|
||||
|
||||
link: (scope, element, attrs)->
|
||||
element.bind 'focus', ->
|
||||
element.bind 'mousewheel', (e)->
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
Darkswarm.directive "ofnEmptiesCart", (CurrentHub, CurrentOrder, Navigation, storage) ->
|
||||
Darkswarm.directive "ofnEmptiesCart", (CurrentHub, Cart, Navigation, storage) ->
|
||||
# Compares scope.hub with CurrentHub. Will trigger an confirmation if they are different,
|
||||
# and Cart isn't empty
|
||||
restrict: "A"
|
||||
scope:
|
||||
hub: "=ofnEmptiesCart"
|
||||
link: (scope, elm, attr)->
|
||||
hub = scope.$eval(attr.ofnEmptiesCart)
|
||||
# A hub is selected, we're changing to a different hub, and the cart isn't empty
|
||||
if CurrentHub.hub?.id and CurrentHub.hub.id isnt hub.id
|
||||
unless CurrentOrder.empty()
|
||||
elm.bind 'click', (ev)->
|
||||
ev.preventDefault()
|
||||
if confirm "Are you sure? This will change your selected Hub and remove any items in you shopping cart."
|
||||
storage.clearAll() # One day this will have to be moar GRANULAR
|
||||
Navigation.go scope.hub.path
|
||||
if CurrentHub.hub?.id and CurrentHub.hub.id isnt scope.hub.id and !Cart.empty()
|
||||
elm.bind 'click', (ev)->
|
||||
ev.preventDefault()
|
||||
if confirm "Are you sure? This will change your selected Hub and remove any items in you shopping cart."
|
||||
storage.clearAll() # One day this will have to be moar GRANULAR
|
||||
Navigation.go scope.hub.path
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
Darkswarm.directive "fillVertical", ($window)->
|
||||
# Makes something fill the window vertically. Used on the Google Map.
|
||||
restrict: 'A'
|
||||
|
||||
link: (scope, element, attrs)->
|
||||
setSize = ->
|
||||
element.css "height", ($window.innerHeight - element.offset().top)
|
||||
setSize()
|
||||
|
||||
angular.element($window).bind "resize", ->
|
||||
setSize()
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
Darkswarm.directive "ofnFlash", (flash, $timeout, RailsFlashLoader)->
|
||||
# Mappings between flash types (left) and Foundation classes
|
||||
# Our own flash class. Uses the "flash" service (third party), and a directive
|
||||
# called RailsFlashLoader to render
|
||||
typePairings =
|
||||
info: "info"
|
||||
error: "alert"
|
||||
@@ -13,6 +14,8 @@ Darkswarm.directive "ofnFlash", (flash, $timeout, RailsFlashLoader)->
|
||||
|
||||
link: ($scope, element, attr) ->
|
||||
$scope.flashes = []
|
||||
|
||||
# Callback when a new flash message is pushed to flash service
|
||||
show = (message, type)=>
|
||||
if message
|
||||
$scope.flashes.push({message: message, type: typePairings[type]})
|
||||
@@ -21,5 +24,6 @@ Darkswarm.directive "ofnFlash", (flash, $timeout, RailsFlashLoader)->
|
||||
$scope.delete = ->
|
||||
$scope.flashes.shift()
|
||||
|
||||
# Register our callback (above) with flash service
|
||||
flash.subscribe(show)
|
||||
RailsFlashLoader.initFlash()
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
Darkswarm.directive "ofnFocus", ->
|
||||
# Takes an expression attrs.ofnFocus
|
||||
# Watches value of expression, triggers element.focus() when value is truthy
|
||||
# Used to automatically focus on specific inputs in various circumstances
|
||||
restrict: "A"
|
||||
link: (scope, element, attrs) ->
|
||||
scope.$watch attrs.ofnFocus, ((focus) ->
|
||||
focus and element.focus()
|
||||
return
|
||||
), true
|
||||
|
||||
return
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
Darkswarm.directive "loading", (Loading)->
|
||||
# Triggers a screen-wide "loading" thing when Ajaxy stuff is happening
|
||||
scope: {}
|
||||
restrict: 'E'
|
||||
templateUrl: 'loading.html'
|
||||
@@ -6,5 +7,3 @@ Darkswarm.directive "loading", (Loading)->
|
||||
$scope.Loading = Loading
|
||||
$scope.show = ->
|
||||
$scope.Loading.message?
|
||||
|
||||
link: ($scope, element, attr)->
|
||||
|
||||
@@ -3,7 +3,7 @@ Darkswarm.directive 'mapSearch', ($timeout)->
|
||||
restrict: 'E'
|
||||
require: '^googleMap'
|
||||
replace: true
|
||||
template: '<input id="pac-input"></input>'
|
||||
template: '<input id="pac-input" placeholder="Type in a location..."></input>'
|
||||
link: (scope, elem, attrs, ctrl)->
|
||||
$timeout =>
|
||||
map = ctrl.getMap()
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
Darkswarm.directive "max", ->
|
||||
restrict: 'A'
|
||||
link: (scope, elem, attr)->
|
||||
elem.bind 'input', ->
|
||||
if elem.val() > +attr.max
|
||||
elem.val attr.max
|
||||
@@ -1,16 +1,19 @@
|
||||
Darkswarm.directive "ofnModal", ($modal)->
|
||||
# Generic modal! Uses transclusion so designer-types can do stuff like:
|
||||
# %ofn-modal
|
||||
# CONTENT
|
||||
# Only works for simple cases, so roll your own when necessary!
|
||||
restrict: 'E'
|
||||
replace: true
|
||||
transclude: true
|
||||
scope: {}
|
||||
scope: true
|
||||
template: "<a>{{title}}</a>"
|
||||
|
||||
# Instead of using ng-transclude we compile the transcluded template to a string
|
||||
# This compiled template is sent to the $modal service! Such magic!
|
||||
# In theory we could compile the template directly inside link rather than onclick, but it's performant so meh!
|
||||
link: (scope, elem, attrs, ctrl, transclude)->
|
||||
scope.title = attrs.title
|
||||
contents = null
|
||||
elem.on "click", =>
|
||||
# We're using an isolate scope, which is a child of the original scope
|
||||
# We have to compile the transclude against the original scope, not the isolate
|
||||
transclude scope.$parent, (clone)->
|
||||
contents = clone
|
||||
scope.modalInstance = $modal.open(controller: ctrl, template: contents, scope: scope.$parent)
|
||||
transclude scope, (clone)->
|
||||
scope.modalInstance = $modal.open(controller: ctrl, template: clone, scope: scope)
|
||||
|
||||
@@ -1,11 +1,20 @@
|
||||
Darkswarm.directive "priceBreakdown", ($tooltip)->
|
||||
tooltip = $tooltip 'priceBreakdown', 'priceBreakdown', 'click'
|
||||
# We use the $tooltip service from Angular foundation to give us boilerplate
|
||||
# Subsequently we patch the scope, template and restrictions
|
||||
tooltip = $tooltip 'priceBreakdown', 'priceBreakdown', 'click'
|
||||
tooltip.scope =
|
||||
variant: "="
|
||||
tooltip.templateUrl = "price_breakdown_button.html"
|
||||
tooltip.replace = true
|
||||
tooltip.restrict = 'E'
|
||||
tooltip
|
||||
|
||||
# This is automatically referenced via naming convention in $tooltip
|
||||
Darkswarm.directive 'priceBreakdownPopup', ->
|
||||
restrict: 'EA'
|
||||
replace: true
|
||||
templateUrl: 'price_breakdown.html'
|
||||
scope: true
|
||||
scope: false
|
||||
|
||||
link: (scope, elem, attrs) ->
|
||||
scope.expanded = false unless scope.expanded?
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
Darkswarm.directive "pricePercentage", ->
|
||||
restrict: 'E'
|
||||
replace: true
|
||||
templateUrl: 'price_percentage.html'
|
||||
scope:
|
||||
percentage: '='
|
||||
|
||||
link: (scope, elem, attrs) ->
|
||||
elem.find(".meter").css
|
||||
width: "#{scope.percentage}%"
|
||||
@@ -1,7 +1,11 @@
|
||||
Darkswarm.directive "renderSvg", ()->
|
||||
# Magical directive that'll render SVGs from URLs
|
||||
# If only there were a neater way of doing this
|
||||
restrict: 'E'
|
||||
priority: 99
|
||||
template: "<svg-wrapper></svg-wrapper>"
|
||||
|
||||
# Fetch SVG via ajax, inject into page using DOM
|
||||
link: (scope, elem, attr)->
|
||||
if /.svg/.test attr.path # Only do this if we've got an svg
|
||||
$.ajax
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
Darkswarm.directive 'scrollAfterLoad', ($timeout, $location, $document)->
|
||||
# Scroll to an element on page load
|
||||
restrict: "A"
|
||||
link: (scope, element, attr) ->
|
||||
if scope.$last is true
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
Darkswarm.directive "ofnScrollTo", ($location, $anchorScroll)->
|
||||
# Onclick sets $location.hash to attrs.ofnScrollTo
|
||||
# Then triggers anchorScroll
|
||||
restrict: 'A'
|
||||
link: (scope, element, attrs)->
|
||||
element.bind 'click', (ev)->
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
Darkswarm.directive "shippingTypeSelector", (FilterSelectorsService)->
|
||||
# Builds selector for shipping types
|
||||
restrict: 'E'
|
||||
replace: true
|
||||
templateUrl: 'shipping_type_selector.html'
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
Darkswarm.directive "shopVariant", ->
|
||||
restrict: 'E'
|
||||
replace: true
|
||||
templateUrl: 'shop_variant.html'
|
||||
scope:
|
||||
variant: '='
|
||||
@@ -1,4 +1,6 @@
|
||||
Darkswarm.directive "taxonSelector", (FilterSelectorsService)->
|
||||
# Automatically builds activeSelectors for taxons
|
||||
# Lots of magic here
|
||||
restrict: 'E'
|
||||
replace: true
|
||||
scope:
|
||||
@@ -8,7 +10,7 @@ Darkswarm.directive "taxonSelector", (FilterSelectorsService)->
|
||||
|
||||
link: (scope, elem, attr)->
|
||||
selectors_by_id = {}
|
||||
selectors = ["foo"]
|
||||
selectors = null # To get scoping/closure right
|
||||
|
||||
scope.emit = ->
|
||||
scope.results = selectors.filter (selector)->
|
||||
@@ -16,6 +18,7 @@ Darkswarm.directive "taxonSelector", (FilterSelectorsService)->
|
||||
.map (selector)->
|
||||
selector.taxon.id
|
||||
|
||||
# Build hash of unique taxons, each of which gets an ActiveSelector
|
||||
scope.selectors = ->
|
||||
taxons = {}
|
||||
selectors = []
|
||||
@@ -25,7 +28,11 @@ Darkswarm.directive "taxonSelector", (FilterSelectorsService)->
|
||||
if object.supplied_taxons
|
||||
for taxon in object.supplied_taxons
|
||||
taxons[taxon.id] = taxon
|
||||
|
||||
|
||||
# Generate a selector for each taxon.
|
||||
# NOTE: THESE ARE MEMOIZED to stop new selectors from being created constantly, otherwise function always returns non-identical results
|
||||
# This means the $digest cycle can never close and times out
|
||||
# See http://stackoverflow.com/questions/19306452/how-to-fix-10-digest-iterations-reached-aborting-error-in-angular-1-2-fil
|
||||
for id, taxon of taxons
|
||||
if selector = selectors_by_id[id]
|
||||
selectors.push selector
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
window.FieldsetMixin = ($scope)->
|
||||
$scope.next = (event = false)->
|
||||
event.preventDefault() if event
|
||||
$scope.show $scope.nextPanel
|
||||
$scope.show $scope.nextPanel
|
||||
|
||||
$scope.onTimeout = ->
|
||||
if $scope[$scope.name].$valid
|
||||
@@ -36,7 +36,6 @@ window.FieldsetMixin = ($scope)->
|
||||
when "number" then "must be number"
|
||||
when "email" then "must be email address"
|
||||
|
||||
#server_errors = $scope.Order.errors[path.replace('order.', '')]
|
||||
#errors.push server_errors if server_errors?
|
||||
(errors.filter (error) -> error?).join ", "
|
||||
|
||||
#server_errors = $scope.Order.errors[path.replace('order.', '')]
|
||||
#errors.push server_errors if server_errors?
|
||||
(errors.filter (error) -> error?).join ", "
|
||||
@@ -19,7 +19,7 @@ Darkswarm.factory 'Cart', (CurrentOrder, Variants, $timeout, $http)->
|
||||
$http.post('/orders/populate', @data()).success (data, status)=>
|
||||
@saved()
|
||||
.error (response, status)=>
|
||||
alert "There was an error on the server! Please refresh the page"
|
||||
# TODO what shall we do here?
|
||||
|
||||
data: =>
|
||||
variants = {}
|
||||
@@ -43,6 +43,9 @@ Darkswarm.factory 'Cart', (CurrentOrder, Variants, $timeout, $http)->
|
||||
@line_items.filter (li)->
|
||||
li.quantity > 0
|
||||
|
||||
empty: =>
|
||||
@line_items_present().length == 0
|
||||
|
||||
total: =>
|
||||
@line_items_present().map (li)->
|
||||
li.variant.getPrice()
|
||||
@@ -57,6 +60,6 @@ Darkswarm.factory 'Cart', (CurrentOrder, Variants, $timeout, $http)->
|
||||
create_line_item: (variant)->
|
||||
variant.line_item =
|
||||
variant: variant
|
||||
quantity: 0
|
||||
quantity: null
|
||||
max_quantity: null
|
||||
@line_items.push variant.line_item
|
||||
|
||||
@@ -32,6 +32,10 @@ Darkswarm.factory 'Checkout', (CurrentOrder, ShippingMethods, PaymentMethods, $h
|
||||
|
||||
if @ship_address_same_as_billing
|
||||
munged_order.ship_address_attributes = munged_order.bill_address_attributes
|
||||
# If the order already has a ship and bill address (as with logged in users with
|
||||
# past orders), and we don't remove id here, then this will set the wrong id for
|
||||
# ship address, and Rails will error with a 404 for that address.
|
||||
delete munged_order.ship_address_attributes.id
|
||||
|
||||
if @paymentMethod()?.method_type == 'gateway'
|
||||
angular.extend munged_order.payments_attributes[0], {
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
Darkswarm.factory 'CurrentOrder', (currentOrder) ->
|
||||
new class CurrentOrder
|
||||
order: currentOrder
|
||||
empty: =>
|
||||
@order.line_items.length == 0
|
||||
|
||||
@@ -28,6 +28,8 @@ Darkswarm.factory 'Products', ($resource, Enterprises, Dereferencer, Taxons, Car
|
||||
for product in @products
|
||||
if product.variants
|
||||
product.variants = (Variants.register variant for variant in product.variants)
|
||||
variant.product = product for variant in product.variants
|
||||
product.master.product = product
|
||||
product.master = Variants.register product.master if product.master
|
||||
|
||||
registerVariantsWithCart: ->
|
||||
@@ -44,5 +46,6 @@ Darkswarm.factory 'Products', ($resource, Enterprises, Dereferencer, Taxons, Car
|
||||
product.price = Math.min.apply(null, prices)
|
||||
product.hasVariants = product.variants?.length > 0
|
||||
|
||||
product.primaryImage = product.images[0]?.small_url
|
||||
product.primaryImage = product.images[0]?.small_url if product.images
|
||||
product.primaryImageOrMissing = product.primaryImage || "/assets/noimage/small.png"
|
||||
product.largeImage = product.images[0]?.large_url if product.images
|
||||
|
||||
@@ -4,5 +4,6 @@ Darkswarm.factory "ShippingMethods", (shippingMethods)->
|
||||
shipping_methods_by_id: {}
|
||||
constructor: ->
|
||||
for method in @shipping_methods
|
||||
method.price = parseFloat(method.price)
|
||||
@shipping_methods_by_id[method.id] = method
|
||||
|
||||
|
||||
@@ -7,4 +7,5 @@ Darkswarm.factory 'Variants', ->
|
||||
extend: (variant)->
|
||||
variant.getPrice = ->
|
||||
variant.price * variant.line_item.quantity
|
||||
variant.basePricePercentage = Math.round(variant.base_price / variant.price * 100)
|
||||
variant
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
%div{bindonce: true}
|
||||
%div.contact-container{bindonce: true}
|
||||
%div.modal-centered{"bo-if" => "enterprise.email || enterprise.website || enterprise.phone"}
|
||||
%p.modal-header Contact
|
||||
%p{"ng-if" => "enterprise.phone"}
|
||||
{{ enterprise.phone }}
|
||||
|
||||
%p{"ng-if" => "enterprise.email"}
|
||||
%p.word-wrap{"ng-if" => "enterprise.email"}
|
||||
%a{"ng-href" => "{{enterprise.email | stripUrl}}", target: "_blank", mailto: true}
|
||||
%span.email
|
||||
{{ enterprise.email | stripUrl }}
|
||||
|
||||
%p{"ng-if" => "enterprise.website"}
|
||||
%p.word-wrap{"ng-if" => "enterprise.website"}
|
||||
%a{"ng-href" => "http://{{enterprise.website | stripUrl}}", target: "_blank" }
|
||||
{{ enterprise.website | stripUrl }}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
.highlight
|
||||
.highlight-top
|
||||
%p.right
|
||||
{{ [enterprise.address.city, enterprise.address.state] | printArray}}
|
||||
{{ [enterprise.address.city, enterprise.address.state_name] | printArray}}
|
||||
%h3{"ng-if" => "enterprise.is_distributor"}
|
||||
%a{"bo-href" => "enterprise.path", "ofn-empties-cart" => "enterprise", bindonce: true}
|
||||
%i.ofn-i_040-hub
|
||||
{{ enterprise.name }}
|
||||
%span {{ enterprise.name }}
|
||||
%h3{"ng-if" => "!enterprise.is_distributor"}
|
||||
%i.ofn-i_036-producers
|
||||
{{ enterprise.name }}
|
||||
%span {{ enterprise.name }}
|
||||
%img.hero-img{"ng-src" => "{{enterprise.promo_image}}"}
|
||||
|
||||
@@ -11,6 +11,6 @@
|
||||
%i.ofn-i_033-open-sign{"bo-if" => "hub.active"}
|
||||
%i.ofn-i_032-closed-sign{"bo-if" => "!hub.active"}
|
||||
{{hub.name}}
|
||||
.button-address {{ hub.address.city }} , {{hub.address.state}}
|
||||
.button-address {{ hub.address.city }} , {{hub.address.state_name}}
|
||||
%i.ofn-i_007-caret-right
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
.row.pad-top{bindonce: true}
|
||||
.cta-container.small-12.columns
|
||||
.row
|
||||
.small-12.large-6.columns
|
||||
%label{"active-table-hub-link" => "enterprise", change: "Change hub to", shop: "Shop now at"}
|
||||
.small-12.large-6.columns.right
|
||||
.right{"bo-if" => "enterprise.pickup || enterprise.delivery"}
|
||||
.small-4.columns
|
||||
%label{"active-table-hub-link" => "enterprise", change: "Change hub to:", shop: "Shop now at:"}
|
||||
.small-8.columns.right
|
||||
%label.right{"bo-if" => "enterprise.pickup || enterprise.delivery"}
|
||||
Delivery options:
|
||||
%span{"bo-if" => "enterprise.pickup"}
|
||||
%i.ofn-i_038-takeaway
|
||||
@@ -20,5 +20,5 @@
|
||||
%i.ofn-i_033-open-sign{"bo-if" => "enterprise.active"}
|
||||
%i.ofn-i_032-closed-sign{"bo-if" => "!enterprise.active"}
|
||||
{{enterprise.name}}
|
||||
.button-address {{ enterprise.address.city }} , {{enterprise.address.state}}
|
||||
.button-address {{ enterprise.address.city }} , {{enterprise.address.state_name}}
|
||||
%i.ofn-i_007-caret-right
|
||||
|
||||
@@ -1,4 +1,38 @@
|
||||
.joyride-tip-guide{"ng-class" => "{ in: tt_isOpen, fade: tt_animation }"}
|
||||
%span.joyride-nub.bottom
|
||||
.joyride-tip-guide.price_breakdown{bindonce: true, "ng-class" => "{ in: tt_isOpen, fade: tt_animation }"}
|
||||
%span.joyride-nub.right
|
||||
.joyride-content-wrapper
|
||||
{{ variant.id }}
|
||||
.collapsed{"ng-show" => "!expanded"}
|
||||
%price-percentage{percentage: 'variant.basePricePercentage'}
|
||||
%a{"ng-click" => "expanded = !expanded"}
|
||||
Full price breakdown
|
||||
%i.ofn-i_005-caret-down
|
||||
|
||||
.expanded{"ng-show" => "expanded"}
|
||||
%ul
|
||||
%li.cost
|
||||
.right {{ variant.base_price | currency }}
|
||||
Item cost
|
||||
%li{"bo-if" => "variant.fees.admin"}
|
||||
.right {{ variant.fees.admin | currency }}
|
||||
Admin fee
|
||||
%li{"bo-if" => "variant.fees.sales"}
|
||||
.right {{ variant.fees.sales | currency }}
|
||||
Sales fee
|
||||
%li{"bo-if" => "variant.fees.packing"}
|
||||
.right {{ variant.fees.packing | currency }}
|
||||
Packing fee
|
||||
%li{"bo-if" => "variant.fees.transport"}
|
||||
.right {{ variant.fees.transport | currency }}
|
||||
Transport fee
|
||||
%li{"bo-if" => "variant.fees.fundraising"}
|
||||
.right {{ variant.fees.fundraising | currency }}
|
||||
Fundraising fee
|
||||
%li
|
||||
%strong
|
||||
.right = {{ variant.price | currency }}
|
||||
|
||||
|
||||
%a{"ng-click" => "expanded = !expanded"}
|
||||
Price graph
|
||||
%i.ofn-i_006-caret-up
|
||||
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
%button.graph-button{"ng-class" => "{open: tt_isOpen}"}
|
||||
%i.ofn-i-058-graph
|
||||
@@ -0,0 +1,5 @@
|
||||
.progress
|
||||
.right Fees
|
||||
.meter
|
||||
Item cost
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
.row
|
||||
.columns.small-12.large-6
|
||||
%img.product-img{"ng-src" => "{{product.primaryImage}}", "ng-if" => "product.primaryImage"}
|
||||
%img.product-img{"ng-src" => "{{product.largeImage}}", "ng-if" => "product.largeImage"}
|
||||
.columns.small-12.large-6.product-header
|
||||
%h2
|
||||
%render-svg{path: "{{product.primary_taxon.icon}}"}
|
||||
/ %render-svg{path: "{{product.primary_taxon.icon}}"}
|
||||
{{product.name}}
|
||||
%p {{product.description}}
|
||||
%ng-include{src: "'partials/close.html'"}
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
.row.variants{bindonce: true,
|
||||
"ng-repeat" => "variant in product.variants track by variant.id"}
|
||||
|
||||
.variants.row
|
||||
.small-12.medium-4.large-4.columns.variant-name
|
||||
.table-cell
|
||||
.inline {{ variant.name_to_display }}
|
||||
.bulk-buy.inline{"bo-if" => "product.group_buy"}
|
||||
.bulk-buy.inline{"bo-if" => "variant.product.group_buy"}
|
||||
%i.ofn-i_056-bulk><
|
||||
%em><
|
||||
\ Bulk
|
||||
|
||||
-# WITHOUT GROUP BUY
|
||||
.small-5.medium-3.large-3.columns.text-right{"bo-if" => "!product.group_buy"}
|
||||
.small-5.medium-3.large-3.columns.text-right{"bo-if" => "!variant.product.group_buy"}
|
||||
|
||||
%input{type: :number,
|
||||
value: nil,
|
||||
min: 0,
|
||||
@@ -22,7 +21,7 @@
|
||||
|
||||
|
||||
-# WITH GROUP BUY
|
||||
.small-5.medium-3.large-3.columns.text-right{"bo-if" => "product.group_buy"}
|
||||
.small-5.medium-3.large-3.columns.text-right{"bo-if" => "variant.product.group_buy"}
|
||||
%span.bulk-input-container
|
||||
%span.bulk-input
|
||||
%input.bulk.first{type: :number,
|
||||
@@ -33,7 +32,7 @@
|
||||
"ofn-disable-scroll" => true,
|
||||
max: "{{variant.on_demand && 9999 || variant.count_on_hand }}",
|
||||
name: "variants[{{variant.id}}]", id: "variants_{{variant.id}}"}
|
||||
%span.bulk-input{"bo-if" => "product.group_buy"}
|
||||
%span.bulk-input{"bo-if" => "variant.product.group_buy"}
|
||||
%input.bulk.second{type: :number,
|
||||
min: 0,
|
||||
"ng-model" => "variant.line_item.max_quantity",
|
||||
@@ -51,8 +50,11 @@
|
||||
%i.ofn-i_009-close
|
||||
{{ variant.price | currency }}
|
||||
|
||||
/ %button.graph-button{popover: "This is the popover text", "popover-title" => "The title.", "popover-animation" => "true", "popover-trigger" =>"mouseenter", "popover-placement" => "top", "tabindex" => "-1"}
|
||||
/ %i.ofn-i-058-graph
|
||||
-# Now in a template in app/assets/javascripts/templates !
|
||||
%price-breakdown{"price-breakdown" => "_", variant: "variant",
|
||||
"price-breakdown-append-to-body" => "true",
|
||||
"price-breakdown-placement" => "left",
|
||||
"price-breakdown-animation" => true}
|
||||
|
||||
.small-12.medium-2.large-2.columns.total-price.text-right
|
||||
.table-cell
|
||||
@@ -140,6 +140,24 @@ table#listing_enterprise_groups {
|
||||
}
|
||||
}
|
||||
|
||||
#no_results {
|
||||
font-weight:bold;
|
||||
color: #DA5354;
|
||||
}
|
||||
|
||||
|
||||
#loading {
|
||||
text-align: center;
|
||||
img.spinner {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
}
|
||||
h1 {
|
||||
margin-top: 20px;
|
||||
color: gray;
|
||||
}
|
||||
}
|
||||
|
||||
.ofn_drop_down {
|
||||
padding: 7px 15px;
|
||||
border-radius: 3px;
|
||||
|
||||
@@ -2,59 +2,6 @@
|
||||
display: block;
|
||||
}
|
||||
|
||||
div.pagination {
|
||||
div.pagenav {
|
||||
margin: 0px;
|
||||
span.first, span.prev, span.next, span.last {
|
||||
padding: 5px 0px;
|
||||
display:inline-block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
div.pagination_info {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
|
||||
|
||||
div.applied_filter {
|
||||
margin-bottom: 5px;
|
||||
border: solid 2px #5498da;
|
||||
padding: 5px 0px;
|
||||
border-radius: 5px;
|
||||
div.four.columns {
|
||||
padding-left: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
div.option_tabs {
|
||||
div.applied_filters, div.filters, div.column_toggle {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
div.option_tab_titles {
|
||||
h6 {
|
||||
border-radius: 3px;
|
||||
border: 1px solid #cee1f4;
|
||||
padding: 3px;
|
||||
text-align: center;
|
||||
color: darken(#cee1f4, 3);
|
||||
cursor: pointer;
|
||||
-moz-user-select: none;
|
||||
-khtml-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
h6.selected {
|
||||
border: 1px solid #5498da;
|
||||
color: #5498da;
|
||||
}
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
tbody.odd {
|
||||
tr.product { td { background-color: white; } }
|
||||
tr.variant.odd { td { background-color: lighten(#eff5fc, 3); } }
|
||||
@@ -76,41 +23,41 @@ th.left-actions, td.left-actions {
|
||||
border-right: 1px solid #cee1f4 !important;
|
||||
}
|
||||
|
||||
li.column-list-item {
|
||||
border-radius: 3px;
|
||||
padding: 2px 20px;
|
||||
margin: 2px 1px;
|
||||
background-color: white;
|
||||
border: 2px solid lightgray;
|
||||
color: darkgray;
|
||||
font-size: 100%;
|
||||
cursor: default;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
-moz-user-select: none;
|
||||
-khtml-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
li.column-list-item.selected {
|
||||
border: 2px solid #5498da;
|
||||
background-color: #5498da;
|
||||
color: white;
|
||||
font-size: 100%;
|
||||
}
|
||||
|
||||
ul.column-list {
|
||||
list-style: none;
|
||||
#update-status-message {
|
||||
margin: 4px 0px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
table#listing_products.bulk {
|
||||
clear: both;
|
||||
|
||||
td.supplier {
|
||||
select {
|
||||
width: 125px;
|
||||
colgroup col {
|
||||
&.producer {
|
||||
width: 18%;
|
||||
}
|
||||
&.name {
|
||||
width: 18%;
|
||||
}
|
||||
&.unit {
|
||||
width: 14%;
|
||||
}
|
||||
&.display_as {
|
||||
width: 12%;
|
||||
}
|
||||
&.price {
|
||||
width: 10%;
|
||||
}
|
||||
&.on_hand {
|
||||
width: 10%;
|
||||
}
|
||||
&.category {
|
||||
width: 15%;
|
||||
}
|
||||
&.available_on {
|
||||
width: 15%;
|
||||
}
|
||||
&.actions {
|
||||
width: 3%;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,10 @@
|
||||
background-color: #fff
|
||||
|
||||
|
||||
table#enterprise-relationships
|
||||
table#enterprise-relationships, table#enterprise-roles
|
||||
ul
|
||||
list-style-type: none
|
||||
|
||||
th.actions, td.actions
|
||||
width: 16%
|
||||
.errors
|
||||
@@ -1,37 +1,117 @@
|
||||
@import mixins
|
||||
@import branding
|
||||
|
||||
.darkswarm
|
||||
product
|
||||
// Pop over
|
||||
// .darkswarm
|
||||
// product
|
||||
|
||||
// Foundation overrides
|
||||
.joyride-tip-guide
|
||||
// JS needs to be tweaked to adjust for left alignment - this is dynamic can't rewrite in CSS
|
||||
background-color: #ebebeb
|
||||
border: 1px solid #a5a5a5
|
||||
color: #1f1f1f
|
||||
|
||||
h1, h2, h3, h4, h5, h6
|
||||
color: #1f1f1f
|
||||
|
||||
.joyride-nub.bottom
|
||||
border-color: #a5a5a5 !important
|
||||
border-bottom-color: transparent !important
|
||||
border-left-color: transparent !important
|
||||
border-right-color: transparent !important
|
||||
|
||||
button.graph-button
|
||||
padding: 0
|
||||
ordercycle
|
||||
.joyride-tip-guide
|
||||
background-color: $clr-brick
|
||||
.joyride-nub.right
|
||||
border-color: $clr-brick !important
|
||||
border-top-color: transparent !important
|
||||
border-right-color: transparent !important
|
||||
border-bottom-color: transparent !important
|
||||
p
|
||||
margin: 0
|
||||
@include border-radius(0)
|
||||
display: inline
|
||||
background: none
|
||||
font-weight: 700
|
||||
|
||||
// Pop over
|
||||
// Foundation overrides
|
||||
.joyride-tip-guide.price_breakdown
|
||||
// JS needs to be tweaked to adjust for left alignment - this is dynamic can't rewrite in CSS
|
||||
background-color: #999
|
||||
color: #1f1f1f
|
||||
margin-left: -8px
|
||||
@include box-shadow(0 1px 2px 0 rgba(0,0,0,0.7))
|
||||
|
||||
.joyride-content-wrapper
|
||||
padding: 1.125rem 1.25rem 1.5rem
|
||||
padding: 1rem
|
||||
margin: 1%
|
||||
width: 98%
|
||||
background-color: white
|
||||
|
||||
h1, h2, h3, h4, h5, h6
|
||||
color: #1f1f1f
|
||||
|
||||
.joyride-nub.right
|
||||
top: 38px
|
||||
border-color: #999 !important
|
||||
border-top-color: transparent !important
|
||||
border-right-color: transparent !important
|
||||
border-bottom-color: transparent !important
|
||||
|
||||
.progress
|
||||
background-color: #13bf85
|
||||
padding: 0
|
||||
border: none
|
||||
color: white
|
||||
font-size: 0.75rem
|
||||
font-style: oblique
|
||||
line-height: 1
|
||||
height: auto
|
||||
.right
|
||||
padding: 0.5rem 0.25rem 0 0
|
||||
.meter
|
||||
background-color: #0b8c61
|
||||
padding: 0.5rem 0.25rem
|
||||
border-right: 1px solid #539f92
|
||||
|
||||
.expanded
|
||||
ul, li
|
||||
list-style: none
|
||||
margin: 0
|
||||
font-size: 0.875rem
|
||||
li
|
||||
background-color: #13bf85
|
||||
padding: 0 0.25rem
|
||||
margin-bottom: 2px
|
||||
color: white
|
||||
li.cost
|
||||
background-color: #0b8c61
|
||||
li:last-child
|
||||
margin-bottom: 0.75rem
|
||||
|
||||
|
||||
button.graph-button
|
||||
z-index: 9999999
|
||||
border: 1px solid transparent
|
||||
@include box-shadow(none)
|
||||
padding: 0
|
||||
margin: 0
|
||||
@include border-radius(999rem)
|
||||
display: inline
|
||||
background-color: rgba(255,255,255,0.5)
|
||||
padding: 5px
|
||||
|
||||
&:hover, &:active, &:focus
|
||||
background-color: rgba(255,255,255,1)
|
||||
i.ofn-i-058-graph
|
||||
color: $clr-brick-bright
|
||||
|
||||
i.ofn-i-058-graph
|
||||
color: #999
|
||||
margin: 0
|
||||
padding: 0
|
||||
font-size: 1rem
|
||||
|
||||
@media all and (max-width: 640px)
|
||||
padding: 3px
|
||||
i.ofn-i-058-graph
|
||||
font-size: 0.75rem
|
||||
|
||||
button.graph-button.open
|
||||
@include box-shadow(inset 0 1px 1px 0 rgba(0,0,0,0.35))
|
||||
border: 1px solid #999
|
||||
|
||||
&:hover, &:active, &:focus
|
||||
background-color: rgba(255,255,255,1)
|
||||
i.ofn-i-058-graph
|
||||
color: $clr-brick-bright
|
||||
|
||||
i.ofn-i-058-graph
|
||||
color: $clr-brick
|
||||
|
||||
|
||||
i.ofn-i-058-graph
|
||||
color: #999
|
||||
margin: 0
|
||||
padding: 0
|
||||
font-size: 1rem
|
||||
|
||||
&:hover, &:focus, &:active, &.active
|
||||
color: #444
|
||||
@@ -1,3 +1,5 @@
|
||||
@import branding.css.sass
|
||||
|
||||
.darkswarm
|
||||
products
|
||||
product
|
||||
@@ -25,9 +27,13 @@
|
||||
.row.variants
|
||||
margin-left: 0
|
||||
margin-right: 0
|
||||
background: url("/assets/gray_jean.png") top left repeat
|
||||
background-color: #ECECEC
|
||||
&:hover, &:focus, &:active
|
||||
background-color: $clr-brick-light
|
||||
&:nth-of-type(even)
|
||||
background: url("/assets/gray_jean_light.png") top left repeat
|
||||
background-color: #f9f9f9
|
||||
&:hover, &:focus, &:active
|
||||
background-color: $clr-brick-ultra-light
|
||||
|
||||
// Variant name
|
||||
.variant-name
|
||||
@@ -72,7 +78,6 @@
|
||||
.table-cell
|
||||
height: 27px
|
||||
|
||||
|
||||
// ROW SUMMARY
|
||||
.row.summary
|
||||
margin-left: 0
|
||||
@@ -100,12 +105,10 @@
|
||||
h3
|
||||
font-size: 1.5rem
|
||||
margin: 0
|
||||
a h3
|
||||
color: black
|
||||
|
||||
|
||||
|
||||
|
||||
h3 a
|
||||
color: #222
|
||||
&:hover, &:focus, &:active
|
||||
color: black
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
float: left
|
||||
display: block
|
||||
z-index: 999999
|
||||
background-color: #999
|
||||
background-color: white
|
||||
overflow: hidden
|
||||
|
||||
@media all and (max-width: 768px)
|
||||
|
||||
@@ -8,12 +8,13 @@
|
||||
// $clr-turquoise: #097563
|
||||
// $clr-turquoise-light: #cef2ec
|
||||
// $clr-turquoise-ultra-light: #e6faf7
|
||||
// $clr-turquoise-bright: #1d8f7c
|
||||
// $clr-turquoise-bright: #1d8f7c
|
||||
|
||||
$clr-brick: #c1122b
|
||||
$clr-brick-light: #f5e6e7
|
||||
$clr-brick-ultra-light: #faf5f6
|
||||
$clr-brick-bright: #eb4c46
|
||||
$clr-brick-med-bright: #e5a2a0
|
||||
$clr-brick-light-bright: #f5c4c9
|
||||
|
||||
$clr-turquoise: #0b8c61
|
||||
|
||||
@@ -3,10 +3,19 @@
|
||||
|
||||
checkout
|
||||
display: block
|
||||
|
||||
@media all and (max-width: 640px)
|
||||
&.row .row
|
||||
margin-left: 0
|
||||
margin-right: 0
|
||||
|
||||
orderdetails
|
||||
.button, table
|
||||
width: 100%
|
||||
@media all and (max-width: 640px)
|
||||
form.edit_order
|
||||
border: 1px solid $disabled-bright
|
||||
margin-bottom: 2rem
|
||||
|
||||
#details, #billing, #shipping, #payment
|
||||
border: 0
|
||||
|
||||
@@ -7,24 +7,19 @@
|
||||
background-color: black
|
||||
background-image: url("/assets/home/ofn_bg_1.jpg")
|
||||
@include fullbg
|
||||
height: 500px
|
||||
height: 400px
|
||||
padding: 40px 0px
|
||||
h1, h2, span, small, timer
|
||||
h1, h2, p
|
||||
color: white
|
||||
p
|
||||
color: $clr-brick-light
|
||||
h1
|
||||
margin-bottom: 3rem
|
||||
margin-bottom: 1em
|
||||
h2
|
||||
font-size: 1.6875rem
|
||||
max-width: 610px
|
||||
margin: 0 auto
|
||||
padding-bottom: 0.5rem
|
||||
|
||||
a
|
||||
color: white
|
||||
&:hover, &:active, &:focus
|
||||
color: $clr-brick-light-bright
|
||||
@include textsoftpress
|
||||
a.button.primary
|
||||
color: white
|
||||
@@ -12,7 +12,7 @@
|
||||
|
||||
//Hub icon styline
|
||||
i.ofn-i_040-hub
|
||||
@include border-radius(9999em)
|
||||
@include border-radius(99999rem)
|
||||
font-size: 1.15rem
|
||||
display: inline-block
|
||||
padding: 0.2rem
|
||||
|
||||
@@ -11,9 +11,9 @@
|
||||
@include box-shadow(0 1px 2px 1px rgba(0,0,0,0.25))
|
||||
|
||||
.hero-img
|
||||
background-color: #333
|
||||
border-bottom: 1px solid $disabled-bright
|
||||
width: 100%
|
||||
min-height: 160px
|
||||
min-height: 56px
|
||||
height: inherit
|
||||
max-height: 260px
|
||||
overflow: hidden
|
||||
|
||||
@@ -1,16 +1,27 @@
|
||||
// Place all the styles related to the map controller here.
|
||||
// They will automatically be included in application.css.
|
||||
// You can use Sass (SCSS) here: http://sass-lang.com/
|
||||
@import big-input
|
||||
|
||||
.map-container
|
||||
width: 100%
|
||||
map, .angular-google-map-container, google-map, .angular-google-map
|
||||
display: block
|
||||
height: 100%
|
||||
width: 100%
|
||||
|
||||
img // https://github.com/zurb/foundation/issues/112
|
||||
max-width: none
|
||||
height: auto
|
||||
|
||||
#pac-input
|
||||
padding: 4px
|
||||
font-size: 2em
|
||||
#pac-input
|
||||
@include big-input(#888, #333, $clr-brick)
|
||||
@include big-input-static
|
||||
font-size: 1.5rem
|
||||
background: rgba(255,255,255,0.85)
|
||||
width: 50%
|
||||
margin-top: 1.2rem
|
||||
@media all and (max-width: 768px)
|
||||
width: 80%
|
||||
&:active, &:focus, &.active
|
||||
background: rgba(255,255,255, 1)
|
||||
|
||||
@@ -22,6 +22,9 @@
|
||||
p
|
||||
line-height: 2
|
||||
|
||||
h3 a:hover span
|
||||
border-bottom: 1px solid $clr-brick-bright
|
||||
|
||||
|
||||
// ABOUT Enterprise
|
||||
|
||||
@@ -41,6 +44,11 @@
|
||||
max-width: 180px
|
||||
max-height: 180px
|
||||
|
||||
// CONTACT Enterprise
|
||||
|
||||
.contact-container
|
||||
a:hover
|
||||
text-decoration: underline
|
||||
|
||||
// FOLLOW Enterprise
|
||||
|
||||
@@ -60,19 +68,26 @@
|
||||
// CALL TO ACTION - hub click throughs
|
||||
|
||||
.cta-container
|
||||
background: url("/assets/gray_jean.png") repeat
|
||||
background-color: #ececec
|
||||
padding-top: 0.75rem
|
||||
|
||||
label
|
||||
text-transform: uppercase
|
||||
font-size: 0.875rem
|
||||
margin-bottom: 0.5rem
|
||||
margin-bottom: 0
|
||||
5rem
|
||||
color: $dark-grey
|
||||
|
||||
label.right
|
||||
color: $disabled-dark
|
||||
span
|
||||
text-transform: capitalize
|
||||
|
||||
.button.secondary
|
||||
background-color: #999
|
||||
.button.hub
|
||||
margin-right: 1rem
|
||||
margin-top: 0.25rem
|
||||
margin-bottom: 1rem
|
||||
padding-left: 1rem
|
||||
padding-right: 1rem
|
||||
@@ -114,4 +129,4 @@
|
||||
border-bottom: 1px solid $disabled-dark
|
||||
margin-top: 0.75rem
|
||||
margin-bottom: 0.5rem
|
||||
|
||||
|
||||
|
||||
@@ -4,46 +4,66 @@
|
||||
dialog, .reveal-modal
|
||||
border: none
|
||||
outline: none
|
||||
padding: 1rem
|
||||
div
|
||||
overflow: scroll
|
||||
@media only screen and (min-width: 40.063em)
|
||||
max-height: 580px
|
||||
@media all and (max-width: 768px)
|
||||
max-height: 440px
|
||||
@media all and (max-width: 640px)
|
||||
max-height: 400px
|
||||
@media all and (max-width: 640px)
|
||||
max-height: inherit
|
||||
overflow: scroll
|
||||
padding: 1rem
|
||||
overflow-y: scroll
|
||||
|
||||
// Sets up max heights based on device height
|
||||
@media all and (min-height: 1025px)
|
||||
max-height: 80%
|
||||
|
||||
@media all and (min-height: 700px) and (max-height: 1024px)
|
||||
max-height: 70%
|
||||
|
||||
@media all and (min-height: 600px) and (max-height: 699px)
|
||||
max-height: 60%
|
||||
|
||||
@media all and (min-height: 481px) and (max-height: 599px)
|
||||
max-height: 60%
|
||||
|
||||
@media only screen and (max-height: 480px) and (min-width: 641px)
|
||||
max-height: 60%
|
||||
|
||||
@media all and (max-height: 480px)
|
||||
overflow-y: scroll
|
||||
|
||||
|
||||
.reveal-modal-bg
|
||||
background-color: rgba(0,0,0,0.65)
|
||||
|
||||
dialog .close-reveal-modal.outside, .reveal-modal .close-reveal-modal.outside
|
||||
top: -2.5rem
|
||||
right: -2.5rem
|
||||
font-size: 2rem
|
||||
color: white
|
||||
dialog .close-reveal-modal, .reveal-modal .close-reveal-modal
|
||||
right: 0.4rem
|
||||
background-color: rgba(235,235,235,0.85)
|
||||
text-shadow: none
|
||||
padding: 0.25rem
|
||||
@include border-radius(999999)
|
||||
border: 1px solid transparent
|
||||
padding: 0.3rem
|
||||
@include border-radius(999999rem)
|
||||
&:hover, &:active, &:focus
|
||||
text-shadow: 0 1px 3px #333
|
||||
border: 1px solid white
|
||||
background-color: rgba(235,235,235,1)
|
||||
color: #333
|
||||
|
||||
@media all and (max-width: 640px)
|
||||
top: 0.5rem
|
||||
right: 0.5rem
|
||||
font-size: 2rem
|
||||
color: white
|
||||
text-shadow: none
|
||||
padding: 0.25rem
|
||||
background-color: rgba(150,150,150,0.85)
|
||||
@include border-radius(999999)
|
||||
border: 1px solid transparent
|
||||
&:hover, &:active, &:focus
|
||||
text-shadow: 0 1px 3px #333
|
||||
border: 1px solid white
|
||||
// dialog .close-reveal-modal.outside, .reveal-modal .close-reveal-modal.outside
|
||||
// top: -2.5rem
|
||||
// right: -2.5rem
|
||||
// font-size: 2rem
|
||||
// color: white
|
||||
// text-shadow: none
|
||||
// padding: 0.25rem
|
||||
// @include border-radius(999999)
|
||||
// border: 1px solid transparent
|
||||
// &:hover, &:active, &:focus
|
||||
// text-shadow: 0 1px 3px #333
|
||||
// border: 1px solid white
|
||||
|
||||
// @media all and (max-width: 640px)
|
||||
// top: 0.5rem
|
||||
// right: 0.5rem
|
||||
// font-size: 2rem
|
||||
// color: white
|
||||
// text-shadow: none
|
||||
// padding: 0.25rem
|
||||
// background-color: rgba(150,150,150,0.85)
|
||||
// @include border-radius(999999)
|
||||
// border: 1px solid transparent
|
||||
// &:hover, &:active, &:focus
|
||||
// text-shadow: 0 1px 3px #333
|
||||
// border: 1px solid white
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
|
||||
|
||||
product
|
||||
@include csstrans
|
||||
border-bottom: 1px solid #e5e5e5
|
||||
border-top: 1px solid #e5e5e5
|
||||
padding-bottom: 1px
|
||||
@@ -38,6 +39,10 @@
|
||||
display: block
|
||||
color: #444
|
||||
|
||||
&:hover, &:focus, &:active
|
||||
border-bottom: 1px solid $clr-brick-med-bright
|
||||
border-top: 1px solid $clr-brick-med-bright
|
||||
|
||||
// BULK
|
||||
.bulk-buy
|
||||
font-size: 0.875rem
|
||||
@@ -55,6 +60,8 @@
|
||||
i
|
||||
font-size: 0.75em
|
||||
padding-right: 0.9375rem
|
||||
@media all and (max-width: 640px)
|
||||
padding-right: 0.25rem
|
||||
|
||||
i.ofn-i_056-bulk, i.ofn-i_036-producers
|
||||
font-size: 1rem
|
||||
|
||||
@@ -12,14 +12,21 @@
|
||||
display: block
|
||||
right: 10px
|
||||
top: 55px
|
||||
width: 400px
|
||||
width: 480px
|
||||
@media screen and (max-width: 640px)
|
||||
width: 96%
|
||||
|
||||
.joyride-nub
|
||||
right: 22px !important
|
||||
left: auto
|
||||
|
||||
ul, li
|
||||
list-style: none
|
||||
margin-left: 0
|
||||
|
||||
li
|
||||
float: none
|
||||
|
||||
.row .columns
|
||||
padding-left: 0.25rem
|
||||
padding-right: 0.25rem
|
||||
|
||||
@@ -34,6 +34,10 @@ small, .small
|
||||
margin-bottom: 0.5rem
|
||||
&, & *
|
||||
font-size: 0.875rem
|
||||
|
||||
.word-wrap
|
||||
word-wrap: break-word
|
||||
|
||||
.light
|
||||
color: #999
|
||||
display: inline
|
||||
|
||||
@@ -46,6 +46,7 @@
|
||||
|
||||
.button, button
|
||||
@include border-radius(0.5em)
|
||||
outline: none // Turn off blue highlight on chrome
|
||||
|
||||
.button.primary, button.primary
|
||||
font-family: 'Open Sans', Calibri, Candara, Segoe, "Segoe UI", Optima, Arial, sans-serif
|
||||
@@ -57,10 +58,10 @@
|
||||
text-shadow: 0 1px 0 $clr-brick
|
||||
|
||||
button.success, .button.success
|
||||
background: $clr-turquoise
|
||||
background: #0096ad
|
||||
|
||||
.button.success:hover, .button.success:active, .button.success:focus, button.success:hover, button.success:active, button.success:focus
|
||||
background: $clr-turquoise-bright
|
||||
background: #14b6cc
|
||||
|
||||
// Responsive
|
||||
@media screen and (min-width: 768px)
|
||||
|
||||
@@ -10,7 +10,7 @@ module Admin
|
||||
@enterprise_relationship = EnterpriseRelationship.new params[:enterprise_relationship]
|
||||
|
||||
if @enterprise_relationship.save
|
||||
render partial: "admin/json/enterprise_relationship", locals: {enterprise_relationship: @enterprise_relationship}
|
||||
render text: Api::Admin::EnterpriseRelationshipSerializer.new(@enterprise_relationship).to_json
|
||||
else
|
||||
render status: 400, json: {errors: @enterprise_relationship.errors.full_messages.join(', ')}
|
||||
end
|
||||
|
||||
26
app/controllers/admin/enterprise_roles_controller.rb
Normal file
26
app/controllers/admin/enterprise_roles_controller.rb
Normal file
@@ -0,0 +1,26 @@
|
||||
module Admin
|
||||
class EnterpriseRolesController < ResourceController
|
||||
def index
|
||||
@enterprise_roles = EnterpriseRole.by_user_email
|
||||
@users = Spree::User.order('spree_users.email')
|
||||
@my_enterprises = @all_enterprises = Enterprise.by_name
|
||||
end
|
||||
|
||||
def create
|
||||
@enterprise_role = EnterpriseRole.new params[:enterprise_role]
|
||||
|
||||
if @enterprise_role.save
|
||||
render text: Api::Admin::EnterpriseRoleSerializer.new(@enterprise_role).to_json
|
||||
|
||||
else
|
||||
render status: 400, json: {errors: @enterprise_role.errors.full_messages.join(', ')}
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
@enterprise_role = EnterpriseRole.find params[:id]
|
||||
@enterprise_role.destroy
|
||||
render nothing: true
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -6,6 +6,11 @@ module Admin
|
||||
create.after :grant_management
|
||||
|
||||
helper 'spree/products'
|
||||
include OrderCyclesHelper
|
||||
|
||||
def for_order_cycle
|
||||
@collection = order_cycle_permitted_enterprises
|
||||
end
|
||||
|
||||
|
||||
def bulk_update
|
||||
@@ -53,7 +58,7 @@ module Admin
|
||||
end
|
||||
|
||||
def collection_actions
|
||||
[:index, :bulk_update]
|
||||
[:index, :for_order_cycle, :bulk_update]
|
||||
end
|
||||
|
||||
def load_methods_and_fees
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
require 'open_food_network/permissions'
|
||||
require 'open_food_network/order_cycle_form_applicator'
|
||||
|
||||
module Admin
|
||||
class OrderCyclesController < ResourceController
|
||||
include OrderCyclesHelper
|
||||
|
||||
before_filter :load_order_cycle_set, :only => :index
|
||||
|
||||
def show
|
||||
@@ -23,7 +26,7 @@ module Admin
|
||||
|
||||
respond_to do |format|
|
||||
if @order_cycle.save
|
||||
OpenFoodNetwork::OrderCycleFormApplicator.new(@order_cycle, managed_enterprises).go!
|
||||
OpenFoodNetwork::OrderCycleFormApplicator.new(@order_cycle, order_cycle_permitted_enterprises).go!
|
||||
|
||||
flash[:notice] = 'Your order cycle has been created.'
|
||||
format.html { redirect_to admin_order_cycles_path }
|
||||
@@ -40,7 +43,7 @@ module Admin
|
||||
|
||||
respond_to do |format|
|
||||
if @order_cycle.update_attributes(params[:order_cycle])
|
||||
OpenFoodNetwork::OrderCycleFormApplicator.new(@order_cycle, managed_enterprises).go!
|
||||
OpenFoodNetwork::OrderCycleFormApplicator.new(@order_cycle, order_cycle_permitted_enterprises).go!
|
||||
|
||||
flash[:notice] = 'Your order cycle has been updated.'
|
||||
format.html { redirect_to admin_order_cycles_path }
|
||||
|
||||
@@ -1,25 +1,62 @@
|
||||
Spree::Admin::PaymentMethodsController.class_eval do
|
||||
# Only show payment methods that user has access to and sort by distributor name
|
||||
# ! Redundant code copied from Spree::Admin::ResourceController with modifications marked
|
||||
def collection
|
||||
return parent.send(controller_name) if parent_data.present?
|
||||
collection = if model_class.respond_to?(:accessible_by) &&
|
||||
!current_ability.has_block?(params[:action], model_class)
|
||||
module Spree
|
||||
module Admin
|
||||
PaymentMethodsController.class_eval do
|
||||
skip_before_filter :load_resource, only: [:show_provider_preferences]
|
||||
before_filter :load_hubs, only: [:new, :edit, :update]
|
||||
create.before :load_hubs
|
||||
|
||||
model_class.accessible_by(current_ability, action)
|
||||
# Only show payment methods that user has access to and sort by distributor name
|
||||
# ! Redundant code copied from Spree::Admin::ResourceController with modifications marked
|
||||
def collection
|
||||
return parent.send(controller_name) if parent_data.present?
|
||||
collection = if model_class.respond_to?(:accessible_by) &&
|
||||
!current_ability.has_block?(params[:action], model_class)
|
||||
|
||||
else
|
||||
model_class.scoped
|
||||
end
|
||||
model_class.accessible_by(current_ability, action)
|
||||
|
||||
collection = collection.managed_by(spree_current_user).by_name # This line added
|
||||
else
|
||||
model_class.scoped
|
||||
end
|
||||
|
||||
# This block added
|
||||
if params.key? :enterprise_id
|
||||
distributor = Enterprise.find params[:enterprise_id]
|
||||
collection = collection.for_distributor(distributor)
|
||||
collection = collection.managed_by(spree_current_user).by_name # This line added
|
||||
|
||||
# This block added
|
||||
if params.key? :enterprise_id
|
||||
distributor = Enterprise.find params[:enterprise_id]
|
||||
collection = collection.for_distributor(distributor)
|
||||
end
|
||||
|
||||
collection
|
||||
end
|
||||
|
||||
def show_provider_preferences
|
||||
if params[:pm_id].present?
|
||||
@payment_method = PaymentMethod.find(params[:pm_id])
|
||||
authorize! :show_provider_preferences, @payment_method
|
||||
payment_method_type = params[:provider_type]
|
||||
if @payment_method['type'].to_s != payment_method_type
|
||||
@payment_method.update_column(:type, payment_method_type)
|
||||
@payment_method = PaymentMethod.find(params[:pm_id])
|
||||
end
|
||||
else
|
||||
@payment_method = params[:provider_type].constantize.new()
|
||||
end
|
||||
render partial: 'provider_settings'
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def load_data
|
||||
if spree_current_user.admin? || Rails.env.test?
|
||||
@providers = Gateway.providers.sort{|p1, p2| p1.name <=> p2.name }
|
||||
else
|
||||
@providers = Gateway.providers.reject{ |p| p.name.include? "Bogus" }.sort{|p1, p2| p1.name <=> p2.name }
|
||||
end
|
||||
end
|
||||
|
||||
def load_hubs
|
||||
@hubs = Enterprise.managed_by(spree_current_user).is_distributor.sort_by!{ |d| [(@payment_method.has_distributor? d) ? 0 : 1, d.name] }
|
||||
end
|
||||
end
|
||||
|
||||
collection
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
Spree::Admin::ProductsController.class_eval do
|
||||
before_filter :load_spree_api_key, :only => :bulk_edit
|
||||
before_filter :load_bpe_data, :only => :bulk_edit
|
||||
|
||||
alias_method :location_after_save_original, :location_after_save
|
||||
|
||||
@@ -30,10 +30,17 @@ Spree::Admin::ProductsController.class_eval do
|
||||
"#{string}q[#{filter[:property][:db_column]}_#{filter[:predicate][:predicate]}]=#{filter[:value]};"
|
||||
end
|
||||
|
||||
# Ensure we're authorised to update all products
|
||||
product_set.collection.each { |p| authorize! :update, p }
|
||||
|
||||
if product_set.save
|
||||
redirect_to "/api/products/managed?template=bulk_index;page=1;per_page=500;#{bulk_index_query}"
|
||||
redirect_to "/api/products/bulk_products?page=1;per_page=500;#{bulk_index_query}"
|
||||
else
|
||||
render :nothing => true, :status => 418
|
||||
if product_set.errors.present?
|
||||
render json: { errors: product_set.errors }, status: 400
|
||||
else
|
||||
render :nothing => true, :status => 500
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -78,8 +85,10 @@ Spree::Admin::ProductsController.class_eval do
|
||||
|
||||
private
|
||||
|
||||
def load_spree_api_key
|
||||
def load_bpe_data
|
||||
current_user.generate_spree_api_key! unless spree_current_user.spree_api_key
|
||||
@spree_api_key = spree_current_user.spree_api_key
|
||||
@producers = OpenFoodNetwork::Permissions.new(spree_current_user).managed_product_enterprises.is_primary_producer.by_name
|
||||
@taxons = Spree::Taxon.order(:name)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -4,12 +4,9 @@ require 'open_food_network/products_and_inventory_report'
|
||||
require 'open_food_network/group_buy_report'
|
||||
require 'open_food_network/order_grouper'
|
||||
require 'open_food_network/customers_report'
|
||||
require 'open_food_network/model_class_from_controller_name'
|
||||
|
||||
Spree::Admin::ReportsController.class_eval do
|
||||
include OpenFoodNetwork::ModelClassFromControllerName
|
||||
|
||||
# Fetches user's distributors, suppliers and order_cycles
|
||||
# Fetches user's distributors, suppliers and order_cycles
|
||||
before_filter :load_data, only: [:customers, :products_and_inventory]
|
||||
|
||||
# Render a partial for orders and fulfillment description
|
||||
@@ -365,9 +362,9 @@ Spree::Admin::ReportsController.class_eval do
|
||||
lis
|
||||
end.flatten
|
||||
#payments = orders.map { |o| o.payments.select { |payment| payment.completed? } }.flatten # Only select completed payments
|
||||
|
||||
|
||||
# -- Prepare form options
|
||||
my_distributors = Enterprise.is_distributor.managed_by(spree_current_user)
|
||||
my_distributors = Enterprise.is_distributor.managed_by(spree_current_user)
|
||||
my_suppliers = Enterprise.is_primary_producer.managed_by(spree_current_user)
|
||||
|
||||
# My distributors and any distributors distributing products I supply
|
||||
@@ -386,12 +383,13 @@ Spree::Admin::ReportsController.class_eval do
|
||||
table_items = @line_items
|
||||
@include_blank = 'All'
|
||||
|
||||
header = ["Producer", "Product", "Variant", "Amount", "Curr. Cost per Unit", "Total Cost", "Status", "Incoming Transport"]
|
||||
header = ["Producer", "Product", "Variant", "Amount", "Total Units", "Curr. Cost per Unit", "Total Cost", "Status", "Incoming Transport"]
|
||||
|
||||
columns = [ proc { |line_items| line_items.first.variant.product.supplier.name },
|
||||
proc { |line_items| line_items.first.variant.product.name },
|
||||
proc { |line_items| line_items.first.variant.full_name },
|
||||
proc { |line_items| line_items.sum { |li| li.quantity } },
|
||||
proc { |line_items| total_units(line_items) },
|
||||
proc { |line_items| line_items.first.variant.price },
|
||||
proc { |line_items| line_items.sum { |li| li.quantity * li.price } },
|
||||
proc { |line_items| "" },
|
||||
@@ -596,10 +594,10 @@ Spree::Admin::ReportsController.class_eval do
|
||||
def load_data
|
||||
my_distributors = Enterprise.is_distributor.managed_by(spree_current_user)
|
||||
my_suppliers = Enterprise.is_primary_producer.managed_by(spree_current_user)
|
||||
distributors_of_my_products = Enterprise.with_distributed_products_outer.merge(Spree::Product.in_any_supplier(my_suppliers))
|
||||
@distributors = my_distributors | distributors_of_my_products
|
||||
distributors_of_my_products = Enterprise.with_distributed_products_outer.merge(Spree::Product.in_any_supplier(my_suppliers))
|
||||
@distributors = my_distributors | distributors_of_my_products
|
||||
suppliers_of_products_I_distribute = my_distributors.map { |d| Spree::Product.in_distributor(d) }.flatten.map(&:supplier).uniq
|
||||
@suppliers = my_suppliers | suppliers_of_products_I_distribute
|
||||
@suppliers = my_suppliers | suppliers_of_products_I_distribute
|
||||
@order_cycles = OrderCycle.active_or_complete.accessible_by(spree_current_user).order('orders_close_at DESC')
|
||||
end
|
||||
|
||||
@@ -617,4 +615,13 @@ Spree::Admin::ReportsController.class_eval do
|
||||
end
|
||||
reports
|
||||
end
|
||||
|
||||
def total_units(line_items)
|
||||
return " " if line_items.map{ |li| li.variant.unit_value.nil? }.any?
|
||||
total_units = line_items.sum do |li|
|
||||
scale_factor = ( li.product.variant_unit == 'weight' ? 1000 : 1 )
|
||||
li.quantity * li.variant.unit_value / scale_factor
|
||||
end
|
||||
total_units.round(3)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
require 'open_food_network/permissions'
|
||||
|
||||
Spree::Api::ProductsController.class_eval do
|
||||
def managed
|
||||
authorize! :admin, Spree::Product
|
||||
@@ -7,6 +9,14 @@ Spree::Api::ProductsController.class_eval do
|
||||
respond_with(@products, default_template: :index)
|
||||
end
|
||||
|
||||
def bulk_products
|
||||
@products = OpenFoodNetwork::Permissions.new(current_api_user).managed_products.
|
||||
merge(product_scope).
|
||||
ransack(params[:q]).result.
|
||||
page(params[:page]).per(params[:per_page])
|
||||
|
||||
render text: { products: ActiveModel::ArraySerializer.new(@products, each_serializer: Spree::Api::ProductSerializer), pages: @products.num_pages }.to_json
|
||||
end
|
||||
|
||||
def soft_delete
|
||||
authorize! :delete, Spree::Product
|
||||
|
||||
@@ -4,6 +4,19 @@ module Admin
|
||||
admin_inject_json_ams "admin.enterprises", "enterprise", @enterprise, Api::Admin::EnterpriseSerializer
|
||||
end
|
||||
|
||||
def admin_inject_enterprises
|
||||
admin_inject_json_ams_array("ofn.admin", "my_enterprises", @my_enterprises, Api::Admin::EnterpriseSerializer) +
|
||||
admin_inject_json_ams_array("ofn.admin", "all_enterprises", @all_enterprises, Api::Admin::EnterpriseSerializer)
|
||||
end
|
||||
|
||||
def admin_inject_enterprise_relationships
|
||||
admin_inject_json_ams_array "ofn.admin", "enterprise_relationships", @enterprise_relationships, Api::Admin::EnterpriseRelationshipSerializer
|
||||
end
|
||||
|
||||
def admin_inject_enterprise_roles
|
||||
admin_inject_json_ams_array "ofn.admin", "enterpriseRoles", @enterprise_roles, Api::Admin::EnterpriseRoleSerializer
|
||||
end
|
||||
|
||||
def admin_inject_payment_methods
|
||||
admin_inject_json_ams_array "admin.payment_methods", "paymentMethods", @payment_methods, Api::Admin::IdNameSerializer
|
||||
end
|
||||
@@ -12,6 +25,21 @@ module Admin
|
||||
admin_inject_json_ams_array "admin.shipping_methods", "shippingMethods", @shipping_methods, Api::Admin::IdNameSerializer
|
||||
end
|
||||
|
||||
def admin_inject_producers
|
||||
admin_inject_json_ams_array "ofn.admin", "producers", @producers, Api::Admin::IdNameSerializer
|
||||
end
|
||||
|
||||
def admin_inject_taxons
|
||||
admin_inject_json_ams_array "ofn.admin", "taxons", @taxons, Api::Admin::TaxonSerializer
|
||||
end
|
||||
|
||||
def admin_inject_users
|
||||
admin_inject_json_ams_array "ofn.admin", "users", @users, Api::Admin::UserSerializer
|
||||
end
|
||||
|
||||
|
||||
|
||||
|
||||
def admin_inject_json_ams(ngModule, name, data, serializer, opts = {})
|
||||
json = serializer.new(data).to_json
|
||||
render partial: "admin/json/injection_ams", locals: {ngModule: ngModule, name: name, json: json}
|
||||
@@ -22,4 +50,4 @@ module Admin
|
||||
render partial: "admin/json/injection_ams", locals: {ngModule: ngModule, name: name, json: json}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,18 +1,31 @@
|
||||
module CheckoutHelper
|
||||
def checkout_adjustments_for_summary(order, opts={})
|
||||
adjustments = order.adjustments.eligible
|
||||
exclude = opts[:exclude] || {}
|
||||
|
||||
# Remove empty tax adjustments and (optionally) shipping fees
|
||||
adjustments.reject! { |a| a.originator_type == 'Spree::TaxRate' && a.amount == 0 }
|
||||
adjustments.reject! { |a| a.originator_type == 'Spree::ShippingMethod' } if opts[:exclude_shipping]
|
||||
adjustments.reject! { |a| a.originator_type == 'Spree::ShippingMethod' } if exclude.include? :shipping
|
||||
|
||||
enterprise_fee_adjustments = adjustments.select { |a| a.originator_type == 'EnterpriseFee' }
|
||||
adjustments.reject! { |a| a.originator_type == 'EnterpriseFee' }
|
||||
adjustments << Spree::Adjustment.new(label: 'Distribution', amount: enterprise_fee_adjustments.sum(&:amount))
|
||||
unless exclude.include? :distribution
|
||||
adjustments << Spree::Adjustment.new(label: 'Distribution', amount: enterprise_fee_adjustments.sum(&:amount))
|
||||
end
|
||||
|
||||
adjustments
|
||||
end
|
||||
|
||||
def checkout_adjustments_total(order)
|
||||
adjustments = checkout_adjustments_for_summary(order, exclude: [:shipping])
|
||||
adjustments.sum &:display_amount
|
||||
end
|
||||
|
||||
def checkout_cart_total_with_adjustments(order)
|
||||
order.display_item_total.money.to_f + checkout_adjustments_total(order).money.to_f
|
||||
end
|
||||
|
||||
|
||||
def validated_input(name, path, args = {})
|
||||
attributes = {
|
||||
required: true,
|
||||
|
||||
@@ -3,8 +3,20 @@ module OrderCyclesHelper
|
||||
@current_order_cycle ||= current_order(false).andand.order_cycle
|
||||
end
|
||||
|
||||
def order_cycle_permitted_enterprises
|
||||
OpenFoodNetwork::Permissions.new(spree_current_user).order_cycle_enterprises
|
||||
end
|
||||
|
||||
def order_cycle_producer_enterprises
|
||||
order_cycle_permitted_enterprises.is_primary_producer.by_name
|
||||
end
|
||||
|
||||
def coordinating_enterprises
|
||||
Enterprise.is_distributor.managed_by(spree_current_user).order('name')
|
||||
order_cycle_hub_enterprises
|
||||
end
|
||||
|
||||
def order_cycle_hub_enterprises
|
||||
order_cycle_permitted_enterprises.is_distributor.by_name
|
||||
end
|
||||
|
||||
def order_cycle_local_remote_class(distributor, order_cycle)
|
||||
|
||||
16
app/helpers/spree/admin/navigation_helper_decorator.rb
Normal file
16
app/helpers/spree/admin/navigation_helper_decorator.rb
Normal file
@@ -0,0 +1,16 @@
|
||||
module Spree
|
||||
module Admin
|
||||
module NavigationHelper
|
||||
# Make it so that the Reports admin tab can be enabled/disabled through the cancan
|
||||
# :report resource, since it does not have a corresponding resource class (unlike
|
||||
# eg. Spree::Product).
|
||||
def klass_for_with_sym_fallback(name)
|
||||
klass = klass_for_without_sym_fallback(name)
|
||||
klass ||= name.singularize.to_sym
|
||||
klass = :overview if klass == :dashboard
|
||||
klass
|
||||
end
|
||||
alias_method_chain :klass_for, :sym_fallback
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,6 +1,9 @@
|
||||
class Enterprise < ActiveRecord::Base
|
||||
TYPES = %w(full single profile)
|
||||
ENTERPRISE_SEARCH_RADIUS = 100
|
||||
|
||||
self.inheritance_column = nil
|
||||
|
||||
acts_as_gmappable :process_geocoding => false
|
||||
|
||||
has_and_belongs_to_many :groups, class_name: 'EnterpriseGroup'
|
||||
@@ -40,9 +43,9 @@ class Enterprise < ActiveRecord::Base
|
||||
supports_s3 :promo_image
|
||||
|
||||
|
||||
validates_presence_of :name
|
||||
validates_presence_of :address
|
||||
validates_associated :address
|
||||
validates :name, presence: true
|
||||
validates :type, presence: true, inclusion: {in: TYPES}
|
||||
validates :address, presence: true, associated: true
|
||||
|
||||
before_validation :set_unused_address_fields
|
||||
after_validation :geocode_address
|
||||
|
||||
@@ -10,7 +10,7 @@ class EnterpriseFee < ActiveRecord::Base
|
||||
|
||||
attr_accessible :enterprise_id, :fee_type, :name, :calculator_type
|
||||
|
||||
FEE_TYPES = %w(packing transport admin sales)
|
||||
FEE_TYPES = %w(packing transport admin sales fundraising)
|
||||
PER_ORDER_CALCULATORS = ['Spree::Calculator::FlatRate', 'Spree::Calculator::FlexiRate']
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
class EnterpriseRelationship < ActiveRecord::Base
|
||||
belongs_to :parent, class_name: 'Enterprise', touch: true
|
||||
belongs_to :child, class_name: 'Enterprise', touch: true
|
||||
has_many :permissions, class_name: 'EnterpriseRelationshipPermission'
|
||||
|
||||
validates_presence_of :parent_id, :child_id
|
||||
validates_uniqueness_of :child_id, scope: :parent_id, message: "^That relationship is already established."
|
||||
@@ -8,9 +9,22 @@ class EnterpriseRelationship < ActiveRecord::Base
|
||||
scope :with_enterprises,
|
||||
joins('LEFT JOIN enterprises AS parent_enterprises ON parent_enterprises.id = enterprise_relationships.parent_id').
|
||||
joins('LEFT JOIN enterprises AS child_enterprises ON child_enterprises.id = enterprise_relationships.child_id')
|
||||
scope :by_name, with_enterprises.order('parent_enterprises.name, child_enterprises.name')
|
||||
|
||||
scope :involving_enterprises, ->(enterprises) {
|
||||
where('parent_id IN (?) OR child_id IN (?)', enterprises, enterprises)
|
||||
}
|
||||
|
||||
scope :permitting, ->(enterprises) { where('child_id IN (?)', enterprises) }
|
||||
|
||||
scope :with_permission, ->(permission) {
|
||||
joins(:permissions).
|
||||
where('enterprise_relationship_permissions.name = ?', permission)
|
||||
}
|
||||
|
||||
scope :by_name, with_enterprises.order('child_enterprises.name, parent_enterprises.name')
|
||||
|
||||
|
||||
def permissions_list=(perms)
|
||||
perms.andand.each { |name| permissions.build name: name }
|
||||
end
|
||||
end
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user