Compare commits

..

5 Commits

Author SHA1 Message Date
Mohamed ABDELLANI
1c60a3a660 read rendering options directly from the model ReportRendingOptions 2022-11-16 12:40:02 +01:00
Mohamed ABDELLANI
ac7a7b11a5 store display_header_row flag in the model 2022-11-16 12:29:44 +01:00
Mohamed ABDELLANI
0fb273ce93 store display_summary_row in the model 2022-11-16 12:27:52 +01:00
Mohamed ABDELLANI
709dfa42bc load fields_to_show from DB when user access the report 2022-11-16 12:08:32 +01:00
Mohamed ABDELLANI
9561140466 create ReportRenderingOptions model 2022-11-16 12:02:47 +01:00
309 changed files with 5715 additions and 6399 deletions

View File

@@ -23,7 +23,8 @@ assignees: ''
## Finish on Tuesday
- [ ] Publish and notify [#global-community] (this is automatically posted with a plugin)
- [ ] Publish and notify [#global-community]:
> The next release is ready: https://github.com/openfoodfoundation/openfoodnetwork/releases/latest
- [ ] Deploy the new release to all managed instances.
<details><summary>Command line instructions</summary>
<pre>

View File

@@ -8,7 +8,7 @@ on:
pull_request:
env:
DISABLE_KNAPSACK_PRO: false
DISABLE_KNAPSACK: true
TIMEZONE: UTC
COVERAGE: true
RAILS_ENV: test
@@ -17,7 +17,7 @@ permissions:
contents: read
jobs:
knapsack_rspec_controllers:
rspec:
runs-on: ubuntu-20.04
services:
postgres:
@@ -33,15 +33,19 @@ jobs:
POSTGRES_USER: ofn
POSTGRES_PASSWORD: f00d
strategy:
fail-fast: false
matrix:
# [n] - where the n is a number of parallel jobs you want to run your tests on.
# Use a higher number if you have slow tests to split them between more parallel jobs.
# Remember to update the value of the `ci_node_index` below to (0..n-1).
ci_node_total: [8]
# Indexes for parallel jobs (starting from zero).
# E.g. use [0, 1] for 2 parallel jobs, [0, 1, 2] for 3 parallel jobs, etc.
ci_node_index: [0, 1, 2, 3, 4, 5, 6, 7]
specs:
- "spec/controllers"
- "spec/models"
- "spec/lib"
- "spec/migrations"
- "spec/serializers"
- "spec/system/admin/[a-o0-9]*"
- "spec/system/admin/[p-z]*"
- "spec/system/consumer/[a-o0-9]*"
- "spec/system/consumer/[p-z]*"
- "engines/*/spec"
fail-fast: false
steps:
- uses: actions/checkout@v3
@@ -68,161 +72,7 @@ jobs:
bundle exec rake db:schema:load
- name: Run tests
env:
KNAPSACK_PRO_TEST_SUITE_TOKEN_RSPEC: 864ef557d85ea8e603e086c0387d5154
KNAPSACK_PRO_CI_NODE_TOTAL: ${{ matrix.ci_node_total }}
KNAPSACK_PRO_CI_NODE_INDEX: ${{ matrix.ci_node_index }}
KNAPSACK_PRO_LOG_LEVEL: info
# if you use Knapsack Pro Queue Mode you must set below env variable
# to be able to retry CI build and run previously recorded tests
# https://github.com/KnapsackPro/knapsack_pro-ruby#knapsack_pro_fixed_queue_split-remember-queue-split-on-retry-ci-node
# KNAPSACK_PRO_FIXED_QUEUE_SPLIT: false
# RSpec split test files by test examples feature - it's optional
# https://knapsackpro.com/faq/question/how-to-split-slow-rspec-test-files-by-test-examples-by-individual-it
#KNAPSACK_PRO_RSPEC_SPLIT_BY_TEST_EXAMPLES: true
KNAPSACK_PRO_TEST_FILE_PATTERN: "{spec/controllers/**/*_spec.rb}"
run: |
bundle exec rake knapsack_pro:rspec
knapsack_rspec_models:
runs-on: ubuntu-20.04
services:
postgres:
image: postgres:10
ports: ["5432:5432"]
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
env:
POSTGRES_DB: open_food_network_test
POSTGRES_USER: ofn
POSTGRES_PASSWORD: f00d
strategy:
fail-fast: false
matrix:
# [n] - where the n is a number of parallel jobs you want to run your tests on.
# Use a higher number if you have slow tests to split them between more parallel jobs.
# Remember to update the value of the `ci_node_index` below to (0..n-1).
ci_node_total: [7]
# Indexes for parallel jobs (starting from zero).
# E.g. use [0, 1] for 2 parallel jobs, [0, 1, 2] for 3 parallel jobs, etc.
ci_node_index: [0, 1, 2, 3, 4, 5, 6]
steps:
- uses: actions/checkout@v3
- name: Setup redis
uses: supercharge/redis-github-action@1.4.0
with:
redis-version: 6
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
bundler-cache: true # runs 'bundle install' and caches installed gems automatically
- uses: actions/setup-node@v3
with:
node-version: 16
- name: Install JS dependencies
run: yarn install --frozen-lockfile
- name: Set up database
run: |
bundle exec rake db:create
bundle exec rake db:schema:load
- name: Run tests
env:
KNAPSACK_PRO_TEST_SUITE_TOKEN_RSPEC: 09476e2ce491c12083df62768667c674
KNAPSACK_PRO_CI_NODE_TOTAL: ${{ matrix.ci_node_total }}
KNAPSACK_PRO_CI_NODE_INDEX: ${{ matrix.ci_node_index }}
KNAPSACK_PRO_LOG_LEVEL: info
# if you use Knapsack Pro Queue Mode you must set below env variable
# to be able to retry CI build and run previously recorded tests
# https://github.com/KnapsackPro/knapsack_pro-ruby#knapsack_pro_fixed_queue_split-remember-queue-split-on-retry-ci-node
# KNAPSACK_PRO_FIXED_QUEUE_SPLIT: false
# RSpec split test files by test examples feature - it's optional
# https://knapsackpro.com/faq/question/how-to-split-slow-rspec-test-files-by-test-examples-by-individual-it
#KNAPSACK_PRO_RSPEC_SPLIT_BY_TEST_EXAMPLES: true
KNAPSACK_PRO_TEST_FILE_PATTERN: "{spec/models/**/*_spec.rb}"
run: |
bundle exec rake knapsack_pro:rspec
knapsack_rspec_system_admin:
runs-on: ubuntu-20.04
services:
postgres:
image: postgres:10
ports: ["5432:5432"]
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
env:
POSTGRES_DB: open_food_network_test
POSTGRES_USER: ofn
POSTGRES_PASSWORD: f00d
strategy:
fail-fast: false
matrix:
# [n] - where the n is a number of parallel jobs you want to run your tests on.
# Use a higher number if you have slow tests to split them between more parallel jobs.
# Remember to update the value of the `ci_node_index` below to (0..n-1).
ci_node_total: [10]
# Indexes for parallel jobs (starting from zero).
# E.g. use [0, 1] for 2 parallel jobs, [0, 1, 2] for 3 parallel jobs, etc.
ci_node_index: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
steps:
- uses: actions/checkout@v3
- name: Setup redis
uses: supercharge/redis-github-action@1.4.0
with:
redis-version: 6
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
bundler-cache: true # runs 'bundle install' and caches installed gems automatically
- uses: actions/setup-node@v3
with:
node-version: 16
- name: Install JS dependencies
run: yarn install --frozen-lockfile
- name: Set up database
run: |
bundle exec rake db:create
bundle exec rake db:schema:load
- name: Run tests
env:
KNAPSACK_PRO_TEST_SUITE_TOKEN_RSPEC: ff2456e64c9f2aa5157eb0daf711d3c3
KNAPSACK_PRO_CI_NODE_TOTAL: ${{ matrix.ci_node_total }}
KNAPSACK_PRO_CI_NODE_INDEX: ${{ matrix.ci_node_index }}
KNAPSACK_PRO_LOG_LEVEL: info
# if you use Knapsack Pro Queue Mode you must set below env variable
# to be able to retry CI build and run previously recorded tests
# https://github.com/KnapsackPro/knapsack_pro-ruby#knapsack_pro_fixed_queue_split-remember-queue-split-on-retry-ci-node
KNAPSACK_PRO_FIXED_QUEUE_SPLIT: true
# RSpec split test files by test examples feature - it's optional
# https://knapsackpro.com/faq/question/how-to-split-slow-rspec-test-files-by-test-examples-by-individual-it
#KNAPSACK_PRO_RSPEC_SPLIT_BY_TEST_EXAMPLES: true
KNAPSACK_PRO_TEST_FILE_PATTERN: "{spec/system/admin/**/*_spec.rb}"
run: |
bundle exec rake knapsack_pro:queue:rspec
run: bundle exec rspec --profile -- ${{ matrix.specs }}
- name: Archive failed tests screenshots
if: failure()
@@ -233,233 +83,7 @@ jobs:
retention-days: 7
if-no-files-found: ignore
knapsack_rspec_system_consumer:
runs-on: ubuntu-20.04
services:
postgres:
image: postgres:10
ports: ["5432:5432"]
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
env:
POSTGRES_DB: open_food_network_test
POSTGRES_USER: ofn
POSTGRES_PASSWORD: f00d
strategy:
fail-fast: false
matrix:
# [n] - where the n is a number of parallel jobs you want to run your tests on.
# Use a higher number if you have slow tests to split them between more parallel jobs.
# Remember to update the value of the `ci_node_index` below to (0..n-1).
ci_node_total: [10]
# Indexes for parallel jobs (starting from zero).
# E.g. use [0, 1] for 2 parallel jobs, [0, 1, 2] for 3 parallel jobs, etc.
ci_node_index: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
steps:
- uses: actions/checkout@v3
- name: Setup redis
uses: supercharge/redis-github-action@1.4.0
with:
redis-version: 6
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
bundler-cache: true # runs 'bundle install' and caches installed gems automatically
- uses: actions/setup-node@v3
with:
node-version: 16
- name: Install JS dependencies
run: yarn install --frozen-lockfile
- name: Set up database
run: |
bundle exec rake db:create
bundle exec rake db:schema:load
- name: Run tests
env:
KNAPSACK_PRO_TEST_SUITE_TOKEN_RSPEC: e52bd4390c853e6c5bdfe4d0334586c1
KNAPSACK_PRO_CI_NODE_TOTAL: ${{ matrix.ci_node_total }}
KNAPSACK_PRO_CI_NODE_INDEX: ${{ matrix.ci_node_index }}
KNAPSACK_PRO_LOG_LEVEL: info
# if you use Knapsack Pro Queue Mode you must set below env variable
# to be able to retry CI build and run previously recorded tests
# https://github.com/KnapsackPro/knapsack_pro-ruby#knapsack_pro_fixed_queue_split-remember-queue-split-on-retry-ci-node
KNAPSACK_PRO_FIXED_QUEUE_SPLIT: true
# RSpec split test files by test examples feature - it's optional
# https://knapsackpro.com/faq/question/how-to-split-slow-rspec-test-files-by-test-examples-by-individual-it
#KNAPSACK_PRO_RSPEC_SPLIT_BY_TEST_EXAMPLES: true
KNAPSACK_PRO_TEST_FILE_PATTERN: "{spec/system/consumer/**/*_spec.rb}"
run: |
bundle exec rake knapsack_pro:queue:rspec
- name: Archive failed tests screenshots
if: failure()
uses: actions/upload-artifact@v3
with:
name: failed-tests-screenshots
path: tmp/capybara/screenshots/*.png
retention-days: 7
if-no-files-found: ignore
knapsack_rspec_engines:
runs-on: ubuntu-20.04
services:
postgres:
image: postgres:10
ports: ["5432:5432"]
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
env:
POSTGRES_DB: open_food_network_test
POSTGRES_USER: ofn
POSTGRES_PASSWORD: f00d
strategy:
fail-fast: false
matrix:
# [n] - where the n is a number of parallel jobs you want to run your tests on.
# Use a higher number if you have slow tests to split them between more parallel jobs.
# Remember to update the value of the `ci_node_index` below to (0..n-1).
ci_node_total: [5]
# Indexes for parallel jobs (starting from zero).
# E.g. use [0, 1] for 2 parallel jobs, [0, 1, 2] for 3 parallel jobs, etc.
ci_node_index: [0, 1, 2, 3, 4]
steps:
- uses: actions/checkout@v3
- name: Setup redis
uses: supercharge/redis-github-action@1.4.0
with:
redis-version: 6
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
bundler-cache: true # runs 'bundle install' and caches installed gems automatically
- uses: actions/setup-node@v3
with:
node-version: 16
- name: Install JS dependencies
run: yarn install --frozen-lockfile
- name: Set up database
run: |
bundle exec rake db:create
bundle exec rake db:schema:load
- name: Run tests
env:
KNAPSACK_PRO_TEST_SUITE_TOKEN_RSPEC: d6ea7ceb766404ccd016c19aa2c81b1c
KNAPSACK_PRO_CI_NODE_TOTAL: ${{ matrix.ci_node_total }}
KNAPSACK_PRO_CI_NODE_INDEX: ${{ matrix.ci_node_index }}
KNAPSACK_PRO_LOG_LEVEL: info
# if you use Knapsack Pro Queue Mode you must set below env variable
# to be able to retry CI build and run previously recorded tests
# https://github.com/KnapsackPro/knapsack_pro-ruby#knapsack_pro_fixed_queue_split-remember-queue-split-on-retry-ci-node
# KNAPSACK_PRO_FIXED_QUEUE_SPLIT: false
# RSpec split test files by test examples feature - it's optional
# https://knapsackpro.com/faq/question/how-to-split-slow-rspec-test-files-by-test-examples-by-individual-it
#KNAPSACK_PRO_RSPEC_SPLIT_BY_TEST_EXAMPLES: true
KNAPSACK_PRO_TEST_FILE_PATTERN: "{spec/lib/**/*_spec.rb,spec/migrations/**/*_spec.rb,spec/serializers/**/*_spec.rb,engines/**/*_spec.rb}"
run: |
bundle exec rake knapsack_pro:rspec
- name: Archive failed tests screenshots
if: failure()
uses: actions/upload-artifact@v3
with:
name: failed-tests-screenshots
path: tmp/capybara/screenshots/*.png
retention-days: 7
if-no-files-found: ignore
knapsack_rspec_test_the_rest:
runs-on: ubuntu-20.04
services:
postgres:
image: postgres:10
ports: ["5432:5432"]
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
env:
POSTGRES_DB: open_food_network_test
POSTGRES_USER: ofn
POSTGRES_PASSWORD: f00d
strategy:
fail-fast: false
matrix:
# [n] - where the n is a number of parallel jobs you want to run your tests on.
# Use a higher number if you have slow tests to split them between more parallel jobs.
# Remember to update the value of the `ci_node_index` below to (0..n-1).
ci_node_total: [5]
# Indexes for parallel jobs (starting from zero).
# E.g. use [0, 1] for 2 parallel jobs, [0, 1, 2] for 3 parallel jobs, etc.
ci_node_index: [0, 1, 2, 3, 4]
steps:
- uses: actions/checkout@v3
- name: Setup redis
uses: supercharge/redis-github-action@1.4.0
with:
redis-version: 6
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
bundler-cache: true # runs 'bundle install' and caches installed gems automatically
- uses: actions/setup-node@v3
with:
node-version: 16
- name: Install JS dependencies
run: yarn install --frozen-lockfile
- name: Set up database
run: |
bundle exec rake db:create
bundle exec rake db:schema:load
- name: Run tests
env:
KNAPSACK_PRO_TEST_SUITE_TOKEN_RSPEC: e3b8800198d2d89b70c7edbdd85f8fd8
KNAPSACK_PRO_CI_NODE_TOTAL: ${{ matrix.ci_node_total }}
KNAPSACK_PRO_CI_NODE_INDEX: ${{ matrix.ci_node_index }}
KNAPSACK_PRO_LOG_LEVEL: info
# if you use Knapsack Pro Queue Mode you must set below env variable
# to be able to retry CI build and run previously recorded tests
# https://github.com/KnapsackPro/knapsack_pro-ruby#knapsack_pro_fixed_queue_split-remember-queue-split-on-retry-ci-node
# KNAPSACK_PRO_FIXED_QUEUE_SPLIT: false
# RSpec split test files by test examples feature - it's optional
# https://knapsackpro.com/faq/question/how-to-split-slow-rspec-test-files-by-test-examples-by-individual-it
#KNAPSACK_PRO_RSPEC_SPLIT_BY_TEST_EXAMPLES: true
KNAPSACK_PRO_TEST_FILE_EXCLUDE_PATTERN: "{engines/**/*_spec.rb,spec/models/**/*_spec.rb,spec/controllers/**/*_spec.rb,spec/serializers/**/*_spec.rb,spec/lib/**/*_spec.rb,spec/migrations/**/*_spec.rb,spec/system/**/*_spec.rb}"
run: |
bundle exec rake knapsack_pro:rspec
non_knapsack_jest_karma:
test-the-rest:
runs-on: ubuntu-20.04
services:
postgres:
@@ -498,8 +122,12 @@ jobs:
run: |
bundle exec rake db:create
bundle exec rake db:schema:load
- name: Run JS tests
run: bundle exec rake karma:run
- name: Run jest tests
run: yarn jest
- name: Run all other tests
run: bundle exec rake ofn:specs:run:excluding_folders["models,controllers,serializers,features,lib,migrations,system"]

1
.gitignore vendored
View File

@@ -50,6 +50,7 @@ vendor/bundle/
coverage
/reports/
!/reports/README.md
/spec/components/stories/**/*.stories.json
/public/packs
/public/packs-test

View File

@@ -1,8 +1,6 @@
# Basically, ignore everythings expect app/webpacker/controllers/*.js and app/webpacker/packs/*.js
*.css
*.scss
# Except v2
!/app/webpacker/css/admin/v2/**/*.scss
*.md
*.yml
*.yaml
@@ -12,6 +10,7 @@
babel.config.js
postcss.config.js
.storybook/
/app/assets/
/config/
/coverage/

View File

@@ -440,7 +440,6 @@ Metrics/BlockNesting:
# Configuration parameters: CountComments, Max, CountAsOne.
Metrics/ClassLength:
Exclude:
- 'app/components/products_table_component.rb'
- 'app/controllers/admin/customers_controller.rb'
- 'app/controllers/admin/enterprises_controller.rb'
- 'app/controllers/admin/order_cycles_controller.rb'
@@ -682,7 +681,7 @@ Rails/ActiveRecordOverride:
# This cop supports unsafe autocorrection (--autocorrect-all).
Rails/ApplicationController:
Exclude:
- 'engines/dfc_provider/app/controllers/dfc_provider/base_controller.rb'
- 'engines/dfc_provider/app/controllers/dfc_provider/api/base_controller.rb'
# Offense count: 6
# This cop supports unsafe autocorrection (--autocorrect-all).

7
.storybook/main.js Normal file
View File

@@ -0,0 +1,7 @@
module.exports = {
stories: ['../spec/components/stories/**/*.stories.json'],
addons: [
'@storybook/addon-docs',
'@storybook/addon-controls',
],
};

View File

@@ -0,0 +1,2 @@
<link href='https://fonts.googleapis.com/css?family=Roboto:400,300italic,400italic,300,700,700italic|Oswald:300,400,700' rel='stylesheet' type='text/css'>
<link rel="stylesheet" media="screen" href="http://localhost:3000/assets/darkswarm/all.css" />

5
.storybook/preview.js Normal file
View File

@@ -0,0 +1,5 @@
export const parameters = {
server: {
url: `http://localhost:3000/rails/stories`,
},
};

View File

@@ -83,7 +83,7 @@ Then the main application tests can be run with:
The tests of all custom engines can be run with:
bundle exec rspec ./engines
bundle exec rake ofn:specs:engines:rspec
Note: If your OS is not explicitly supported in the setup guides then not all tests may pass. However, you may still be able to develop.

View File

@@ -135,9 +135,6 @@ gem 'flipper-active_record'
gem 'flipper-ui'
gem "view_component"
gem 'view_component_reflex', '3.1.14.pre9'
gem 'mini_portile2', '~> 2.8'
group :production, :staging do
gem 'ddtrace'
@@ -153,7 +150,7 @@ group :test, :development do
gem "factory_bot_rails", '6.2.0', require: false
gem 'fuubar', '~> 2.5.1'
gem 'json_spec', '~> 1.1.4'
gem 'knapsack_pro'
gem 'knapsack', require: false
gem 'letter_opener', '>= 1.4.1'
gem 'rspec-rails', ">= 3.5.2"
gem 'rspec-retry', require: false
@@ -167,6 +164,7 @@ group :test do
gem 'pdf-reader'
gem 'rails-controller-testing'
gem 'simplecov', require: false
gem 'test-prof', require: false
gem 'vcr', require: false
gem 'webmock', require: false
# See spec/spec_helper.rb for instructions
@@ -185,6 +183,7 @@ group :development do
gem 'spring-commands-rspec'
gem 'web-console'
gem "view_component_storybook"
gem 'rack-mini-profiler', '< 3.0.0'
end

View File

@@ -158,16 +158,16 @@ GEM
awesome_nested_set (3.5.0)
activerecord (>= 4.0.0, < 7.1)
aws-eventstream (1.2.0)
aws-partitions (1.669.0)
aws-sdk-core (3.168.2)
aws-partitions (1.651.0)
aws-sdk-core (3.166.0)
aws-eventstream (~> 1, >= 1.0.2)
aws-partitions (~> 1, >= 1.651.0)
aws-sigv4 (~> 1.5)
jmespath (~> 1, >= 1.6.1)
aws-sdk-kms (1.60.0)
aws-sdk-kms (1.59.0)
aws-sdk-core (~> 3, >= 3.165.0)
aws-sigv4 (~> 1.1)
aws-sdk-s3 (1.117.2)
aws-sdk-s3 (1.117.1)
aws-sdk-core (~> 3, >= 3.165.0)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.4)
@@ -177,12 +177,12 @@ GEM
bigdecimal (3.0.2)
bindata (2.4.12)
bindex (0.8.1)
bootsnap (1.15.0)
bootsnap (1.13.0)
msgpack (~> 1.2)
bugsnag (6.25.1)
bugsnag (6.24.2)
concurrent-ruby (~> 1.0)
builder (3.2.4)
bullet (7.0.7)
bullet (7.0.3)
activesupport (>= 3.0.0)
uniform_notifier (~> 1.11)
cable_ready (5.0.0.pre9)
@@ -210,6 +210,7 @@ GEM
rubyzip (>= 1.3.0, < 3)
choice (0.2.0)
chronic (0.10.2)
cliver (0.3.2)
coderay (1.1.3)
coffee-rails (5.0.0)
coffee-script (>= 2.2.0)
@@ -228,9 +229,9 @@ GEM
crass (1.0.6)
css_parser (1.11.0)
addressable
cuprite (0.14.3)
capybara (~> 3.0)
ferrum (~> 0.13.0)
cuprite (0.13)
capybara (>= 2.1, < 4)
ferrum (~> 0.11.0)
database_cleaner (2.0.1)
database_cleaner-active_record (~> 2.0.0)
database_cleaner-active_record (2.0.0)
@@ -241,8 +242,8 @@ GEM
debase-ruby_core_source (= 0.10.12)
msgpack
debase-ruby_core_source (0.10.12)
debug (1.7.1)
irb (>= 1.5.0)
debug (1.6.3)
irb (>= 1.3.6)
reline (>= 0.3.1)
debugger-linecache (1.2.0)
devise (4.8.1)
@@ -258,7 +259,7 @@ GEM
devise-token_authenticatable (1.1.0)
devise (>= 4.0.0, < 5.0.0)
diff-lcs (1.5.0)
digest (3.1.1)
digest (3.1.0)
docile (1.4.0)
dotenv (2.8.1)
dotenv-rails (2.8.1)
@@ -280,10 +281,10 @@ GEM
faraday-follow_redirects (0.3.0)
faraday (>= 1, < 3)
faraday-net_http (3.0.1)
ferrum (0.13)
ferrum (0.11)
addressable (~> 2.5)
cliver (~> 0.3)
concurrent-ruby (~> 1.1)
webrick (~> 1.7)
websocket-driver (>= 0.6, < 0.8)
ffaker (2.21.0)
ffi (1.15.5)
@@ -349,18 +350,18 @@ GEM
ruby-vips (>= 2.0.17, < 3)
immigrant (0.3.6)
activerecord (>= 3.0)
io-console (0.6.0)
io-console (0.5.11)
ipaddress (0.8.3)
irb (1.6.2)
irb (1.4.2)
reline (>= 0.3.0)
jmespath (1.6.2)
jmespath (1.6.1)
jquery-rails (4.4.0)
rails-dom-testing (>= 1, < 3)
railties (>= 4.2.0)
thor (>= 0.14, < 2.0)
jquery-ui-rails (4.2.1)
railties (>= 3.2.16)
json (2.6.3)
json (2.6.2)
json-jwt (1.16.0)
activesupport (>= 4.2)
aes_key_wrap
@@ -374,18 +375,18 @@ GEM
rspec (>= 2.0, < 4.0)
jsonapi-serializer (2.2.0)
activesupport (>= 4.2)
jwt (2.6.0)
knapsack_pro (3.7.0)
jwt (2.5.0)
knapsack (4.0.0)
rake
launchy (2.5.0)
addressable (~> 2.7)
letter_opener (1.8.1)
launchy (>= 2.2, < 3)
libv8-node (15.14.0.1)
listen (3.8.0)
listen (3.7.1)
rb-fsevent (~> 0.10, >= 0.10.3)
rb-inotify (~> 0.9, >= 0.9.10)
loofah (2.19.1)
loofah (2.19.0)
crass (~> 1.0.2)
nokogiri (>= 1.5.9)
mail (2.7.1)
@@ -401,15 +402,15 @@ GEM
rake
mini_magick (4.11.0)
mini_mime (1.1.2)
mini_portile2 (2.8.1)
mini_portile2 (2.8.0)
mini_racer (0.4.0)
libv8-node (~> 15.14.0.0)
minitest (5.17.0)
minitest (5.16.3)
monetize (1.12.0)
money (~> 6.12)
money (6.16.0)
i18n (>= 0.6.4, <= 2)
msgpack (1.6.0)
msgpack (1.5.4)
multi_json (1.15.0)
multi_xml (0.6.0)
net-protocol (0.1.3)
@@ -417,7 +418,7 @@ GEM
net-smtp (0.3.2)
net-protocol
nio4r (2.5.8)
nokogiri (1.13.10)
nokogiri (1.13.9)
mini_portile2 (~> 2.8.0)
racc (~> 1.4)
oauth2 (1.4.11)
@@ -451,9 +452,9 @@ GEM
activerecord (>= 5.2)
request_store (~> 1.1)
parallel (1.22.1)
paranoia (2.6.1)
paranoia (2.6.0)
activerecord (>= 5.1, < 7.1)
parser (3.1.3.0)
parser (3.1.2.1)
ast (~> 2.4.1)
paypal-sdk-core (0.3.4)
multi_json (~> 1.0)
@@ -467,15 +468,15 @@ GEM
ruby-rc4
ttfunk
pg (1.2.3)
power_assert (2.0.2)
power_assert (2.0.1)
pry (0.13.1)
coderay (~> 1.1)
method_source (~> 1.0)
public_suffix (5.0.0)
puma (6.0.2)
puma (5.6.5)
nio4r (~> 2.0)
raabro (1.4.0)
racc (1.6.1)
racc (1.6.0)
rack (2.2.4)
rack-mini-profiler (2.3.4)
rack (>= 1.2.0)
@@ -522,9 +523,9 @@ GEM
activesupport (>= 4.2)
choice (~> 0.2.0)
ruby-graphviz (~> 1.2)
rails-html-sanitizer (1.4.4)
loofah (~> 2.19, >= 2.19.1)
rails-i18n (7.0.6)
rails-html-sanitizer (1.4.3)
loofah (~> 2.3)
rails-i18n (7.0.5)
i18n (>= 0.7, < 2)
railties (>= 6.0.0, < 8)
rails_safe_tasks (1.0.0)
@@ -540,13 +541,13 @@ GEM
activerecord (>= 5.2.4)
activesupport (>= 5.2.4)
i18n
rb-fsevent (0.11.2)
rb-fsevent (0.11.0)
rb-inotify (0.10.1)
ffi (~> 1.0)
redcarpet (3.5.1)
redis (4.8.0)
regexp_parser (2.6.1)
reline (0.3.2)
regexp_parser (2.6.0)
reline (0.3.1)
io-console (~> 0.5)
request_store (1.5.0)
rack (>= 1.4)
@@ -589,29 +590,29 @@ GEM
rspec-retry (0.6.2)
rspec-core (> 3.3)
rspec-support (3.10.3)
rswag-api (2.8.0)
rswag-api (2.7.0)
railties (>= 3.1, < 7.1)
rswag-specs (2.8.0)
rswag-specs (2.7.0)
activesupport (>= 3.1, < 7.1)
json-schema (>= 2.2, < 4.0)
railties (>= 3.1, < 7.1)
rspec-core (>= 2.14)
rswag-ui (2.8.0)
rswag-ui (2.7.0)
actionpack (>= 3.1, < 7.1)
railties (>= 3.1, < 7.1)
rubocop (1.42.0)
rubocop (1.38.0)
json (~> 2.3)
parallel (~> 1.10)
parser (>= 3.1.2.1)
rainbow (>= 2.2.2, < 4.0)
regexp_parser (>= 1.8, < 3.0)
rexml (>= 3.2.5, < 4.0)
rubocop-ast (>= 1.24.1, < 2.0)
rubocop-ast (>= 1.23.0, < 2.0)
ruby-progressbar (~> 1.7)
unicode-display_width (>= 1.4.0, < 3.0)
rubocop-ast (1.24.1)
rubocop-ast (1.23.0)
parser (>= 3.1.1.0)
rubocop-rails (2.17.4)
rubocop-rails (2.17.2)
activesupport (>= 4.2.0)
rack (>= 1.1)
rubocop (>= 1.33.0, < 2.0)
@@ -634,7 +635,7 @@ GEM
tilt (>= 1.1, < 3)
sd_notify (0.1.1)
semantic_range (3.0.0)
shoulda-matchers (5.3.0)
shoulda-matchers (5.2.0)
activesupport (>= 5.2.0)
sidekiq (6.5.8)
connection_pool (>= 2.2.5, < 3)
@@ -645,16 +646,16 @@ GEM
rufus-scheduler (~> 3.2)
sidekiq (>= 4, < 7)
tilt (>= 1.4.0)
simplecov (0.22.0)
simplecov (0.21.2)
docile (~> 1.1)
simplecov-html (~> 0.11)
simplecov_json_formatter (~> 0.1)
simplecov-html (0.12.3)
simplecov_json_formatter (0.1.4)
simplecov_json_formatter (0.1.3)
spreadsheet_architect (5.0.0)
caxlsx (>= 3.3.0, < 4)
rodf (>= 1.0.0, < 2)
spring (4.1.1)
spring (4.1.0)
spring-commands-rspec (1.0.4)
spring (>= 0.9.1)
sprockets (3.7.2)
@@ -682,18 +683,19 @@ GEM
railties (>= 5.2)
redis
stringex (2.8.5)
stripe (8.0.0)
stripe (7.1.0)
swd (1.3.0)
activesupport (>= 3)
attr_required (>= 0.0.5)
httpclient (>= 2.4)
temple (0.8.2)
test-unit (3.5.7)
test-prof (1.0.11)
test-unit (3.5.5)
power_assert
thor (1.2.1)
thread-local (1.1.0)
tilt (2.0.11)
timecop (0.9.6)
timecop (0.9.5)
timeout (0.3.0)
ttfunk (1.7.0)
tzinfo (2.0.5)
@@ -710,14 +712,12 @@ GEM
activemodel (>= 3.0.0)
public_suffix
vcr (6.1.0)
view_component (2.82.0)
activesupport (>= 5.2.0, < 8.0)
view_component (2.75.0)
activesupport (>= 5.0.0, < 8.0)
concurrent-ruby (~> 1.0)
method_source (~> 1.0)
view_component_reflex (3.1.14.pre9)
rails (>= 5.2, < 8.0)
stimulus_reflex (>= 3.5.0.pre2)
view_component (>= 2.28.0)
view_component_storybook (0.11.1)
view_component (>= 2.36)
warden (1.2.9)
rack (>= 2.0.9)
web-console (4.2.0)
@@ -737,7 +737,6 @@ GEM
rack-proxy (>= 0.6.1)
railties (>= 5.2)
semantic_range (>= 2.3.0)
webrick (1.7.0)
websocket-driver (0.7.5)
websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.5)
@@ -745,11 +744,11 @@ GEM
chronic (>= 0.6.3)
wicked_pdf (2.6.3)
activesupport
wkhtmltopdf-binary (0.12.6.6)
wkhtmltopdf-binary (0.12.6.5)
xml-simple (1.1.8)
xpath (3.2.0)
nokogiri (~> 1.8)
zeitwerk (2.6.6)
zeitwerk (2.6.4)
PLATFORMS
ruby
@@ -819,12 +818,11 @@ DEPENDENCIES
json_spec (~> 1.1.4)
jsonapi-serializer
jwt (~> 2.3)
knapsack_pro
knapsack
letter_opener (>= 1.4.1)
listen
mime-types
mimemagic (> 0.3.5)
mini_portile2 (~> 2.8)
mini_racer (= 0.4.0)
monetize (~> 1.11)
oauth2 (~> 1.4.7)
@@ -876,12 +874,13 @@ DEPENDENCIES
stimulus_reflex (= 3.5.0.pre9)
stringex (~> 2.8.5)
stripe
test-prof
test-unit (~> 3.5)
timecop
valid_email2
vcr
view_component
view_component_reflex (= 3.1.14.pre9)
view_component_storybook
web!
web-console
webmock

View File

@@ -8,3 +8,6 @@ require_relative 'config/application'
Openfoodnetwork::Application.load_tasks
if !ENV['DISABLE_KNAPSACK'] && defined?(Knapsack)
Knapsack.load_tasks
end

View File

@@ -0,0 +1,12 @@
angular.module("admin.indexUtils").component 'showMore',
templateUrl: 'admin/show_more.html'
bindings:
data: "="
limit: "="
increment: "="
# For now, this component is not being used.
# Something about binding "data" to a variable on the parent scope that is continually refreshed by
# being assigned within an ng-repeat means that we get $digest iteration errors. Seems to be solved
# by using the new "as" syntax for ng-repeat to assign and alias the outcome of the filters, but this
# has the limitation of not being able to be limited AFTER the assignment has been made, which we need

View File

@@ -3,6 +3,10 @@ angular.module("admin.lineItems").controller 'LineItemsCtrl', ($scope, $timeout,
$scope.RequestMonitor = RequestMonitor
$scope.line_items = LineItems.all
$scope.confirmDelete = true
$scope.startDate = moment().startOf('day').subtract(7, 'days').format('YYYY-MM-DD')
$scope.endDate = moment().startOf('day').format('YYYY-MM-DD')
$scope.previousDates = { startDate: $scope.startDate, endDate: $scope.endDate }
$scope.datesChangedOnCancelEvent = false
$scope.bulkActions = [ { name: t("admin.orders.bulk_management.actions_delete"), callback: 'deleteLineItems' } ]
$scope.selectedUnitsProduct = {}
$scope.selectedUnitsVariant = {}
@@ -13,29 +17,42 @@ angular.module("admin.lineItems").controller 'LineItemsCtrl', ($scope, $timeout,
$scope.confirmRefresh = ->
LineItems.allSaved() || confirm(t("unsaved_changes_warning"))
$scope.initStartAndEnDate = ->
$scope.startDate = moment().startOf('day').subtract(7, 'days').format('YYYY-MM-DD')
$scope.endDate = moment().startOf('day').format('YYYY-MM-DD')
$scope.resetFilters = ->
$scope.distributorFilter = ''
$scope.supplierFilter = ''
$scope.orderCycleFilter = ''
$scope.quickSearch = ''
$scope.initStartAndEnDate()
event = new CustomEvent('flatpickr:change', {
detail: {
startDate: $scope.startDate,
endDate: $scope.endDate
}
})
window.dispatchEvent(event)
$scope.resetSelectFilters = ->
$scope.resetFilters()
$scope.refreshData()
$scope.$watchCollection "[startDate, endDate]", (newValues, oldValues) ->
if newValues != oldValues && !$scope.datesChangedOnCancelEvent
state = $scope.refreshData()
if (state == "cancel")
$scope.datesChangedOnCancelEvent = true
$scope.cancelDateChange()
$scope.cancelDateChange = ->
# Reset the date filters to the previous values
$scope.startDate = $scope.previousDates.startDate
$scope.endDate = $scope.previousDates.endDate
# throw a flatpickr:change event to change the date back in the datepicker
event = new CustomEvent('flatpickr:change', {
detail: {
startDate: $scope.previousDates.startDate,
endDate: $scope.previousDates.endDate
}
})
window.dispatchEvent(event)
$timeout ->
$scope.datesChangedOnCancelEvent = false
$scope.refreshData = ->
unless !$scope.orderCycleFilter? || $scope.orderCycleFilter == ''
$scope.setOrderCycleDateRange()
$scope.formattedStartDate = moment($scope.startDate).format()
$scope.formattedEndDate = moment($scope.endDate).add(1,'day').format()
@@ -50,6 +67,11 @@ angular.module("admin.lineItems").controller 'LineItemsCtrl', ($scope, $timeout,
$scope.loadAssociatedData()
$scope.dereferenceLoadedData()
$timeout ->
# update the previous dates with the current ones since loading was successful
$scope.previousDates.startDate = $scope.startDate
$scope.previousDates.endDate = $scope.endDate
$scope.setOrderCycleDateRange = ->
start_date = OrderCycles.byID[$scope.orderCycleFilter].orders_open_at
@@ -57,14 +79,6 @@ angular.module("admin.lineItems").controller 'LineItemsCtrl', ($scope, $timeout,
format = "YYYY-MM-DD HH:mm:ss Z"
$scope.startDate = moment(start_date, format).format('YYYY-MM-DD')
$scope.endDate = moment(end_date, format).startOf('day').format('YYYY-MM-DD')
# throw a flatpickr:change event to change the date back in the datepicker
event = new CustomEvent('flatpickr:change', {
detail: {
startDate: $scope.startDate,
endDate: $scope.endDate
}
})
window.dispatchEvent(event)
$scope.loadOrders = ->
RequestMonitor.load $scope.orders = Orders.index(
@@ -263,4 +277,5 @@ angular.module("admin.lineItems").controller 'LineItemsCtrl', ($scope, $timeout,
lineItem.final_weight_volume = LineItems.pristineByID[lineItem.id].final_weight_volume * lineItem.quantity / LineItems.pristineByID[lineItem.id].quantity
$scope.weightAdjustedPrice(lineItem)
$scope.resetSelectFilters()
$scope.resetFilters()
$scope.refreshData()

View File

@@ -38,4 +38,3 @@ angular.module('admin.orderCycles')
$scope.removeCoordinatorFee = ($event, index) ->
$event.preventDefault()
OrderCycle.removeCoordinatorFee(index)
$scope.order_cycle_form.$dirty = true

View File

@@ -21,14 +21,8 @@ angular.module("admin.products")
else
$scope.product.variant_unit = $scope.product.variant_unit_with_scale
$scope.product.variant_unit_scale = null
else if $scope.product.variant_unit
# Preserves variant_unit_with_scale when form validation fails and reload triggers
if $scope.product.variant_unit_scale
$scope.product.variant_unit_with_scale = VariantUnitManager.getUnitWithScale(
$scope.product.variant_unit, parseFloat($scope.product.variant_unit_scale)
)
else
$scope.product.variant_unit_with_scale = $scope.product.variant_unit
else if $scope.product.variant_unit && $scope.product.variant_unit_scale
$scope.product.variant_unit_with_scale = VariantUnitManager.getUnitWithScale($scope.product.variant_unit, parseFloat($scope.product.variant_unit_scale))
else
$scope.product.variant_unit = $scope.product.variant_unit_scale = null

View File

@@ -0,0 +1,9 @@
angular.module('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()

View File

@@ -0,0 +1,82 @@
angular.module('Darkswarm').directive 'singleLineSelectors', ($timeout, $filter) ->
restrict: 'E'
templateUrl: "single_line_selectors.html"
scope:
selectors: "="
objects: "&"
activeSelectors: "="
selectorName: "@activeSelectors"
link: (scope, element, attrs) ->
scope.fitting = false
scope.refit = ->
if scope.allSelectors?
scope.fitting = true
selector.fits = true for selector in scope.allSelectors
$timeout(loadWidths, 0, true).then ->
$timeout fit, 0, true
fit = ->
used = $(element).find("li.more").outerWidth(true)
used += selector.width for selector in scope.allSelectors when selector.fits
available = $(element).parent(".filter-shopfront").innerWidth() - used
if available > 0
for selector in scope.allSelectors when !selector.fits
available -= selector.width
selector.fits = true if available > 0
else
if scope.allSelectors.length > 0
for i in [scope.allSelectors.length-1..0]
selector = scope.allSelectors[i]
if !selector.fits
continue
else
if available < 0
selector.fits = false
available += selector.width
scope.fitting = false
loadWidths = ->
$(element).find("li").not(".more").each (i) ->
if i < scope.allSelectors.length
scope.allSelectors[i].width = $(this).outerWidth(true)
return null # So we don't exit the loop weirdly
scope.overFlowSelectors = ->
return [] unless scope.allSelectors?
$filter('filter')(scope.allSelectors, { fits: false })
scope.selectedOverFlowSelectors = ->
$filter('filter')(scope.overFlowSelectors(), { active: true })
# had to duplicate this to make overflow selectors work
scope.emit = ->
scope.activeSelectors = scope.allSelectors.filter (selector)->
selector.active
.map (selector) ->
selector.object.id
# From: http://stackoverflow.com/questions/4298612/jquery-how-to-call-resize-event-only-once-its-finished-resizing
debouncer = (func, timeout) ->
timeoutID = undefined
timeout = timeout or 50
->
subject = this
args = arguments
clearTimeout timeoutID
timeoutID = setTimeout(->
func.apply subject, Array::slice.call(args)
, timeout)
# -- Event management
scope.$watchCollection "allSelectors", ->
scope.refit()
scope.$on "filtersToggled", ->
scope.refit()
$(window).resize debouncer (e) ->
scope.fitting = true
if scope.allSelectors?
$timeout fit, 0, true

View File

@@ -0,0 +1,3 @@
%div{ ng: { show: "data.length > limit" } }
%input{ type: 'button', value: t(:show_more), ng: { click: 'limit = limit + increment' } }
%input{ type: 'button', value: t(:show_all_with_more, num: '{{ data.length - limit }}'), ng: { click: 'limit = data.length' } }

View File

@@ -0,0 +1,14 @@
-# In order for the single-line-selector scope to have access to the available selectors,
%filter-selector{"selector-set" => "selectors", objects: "objects()", "active-selectors" => "activeSelectors", "all-selectors" => "allSelectors" }
%ul{ ng: { if: "overFlowSelectors().length > 0 || fitting" } }
%li.more
%a.dropdown{ data: { dropdown: "{{ 'show-more-' + selectorName }}" }, ng: { class: "{active: selectedOverFlowSelectors().length > 0}" } }
%span
{{ 'js.more_items' | t:{ count: overFlowSelectors().length } }}
%i.ofn-i_052-point-down
.f-dropdown.text-right.content{ ng: { attr: { id: "{{ 'show-more-' + selectorName }}" } } }
%ul
%active-selector{ ng: { repeat: "selector in overFlowSelectors()", hide: "selector.fits" } }
%render-svg{path: "{{selector.object.icon}}", ng: { if: "selector.object.icon"}}
%span {{ selector.object.name }}

View File

@@ -1,15 +0,0 @@
# frozen_string_literal: true
class ConfirmModalComponent < ModalComponent
def initialize(id:, confirm_actions: nil, controllers: nil)
super(id: id, close_button: true)
@confirm_actions = confirm_actions
@controllers = controllers
end
private
def close_button_class
"secondary"
end
end

View File

@@ -1,8 +0,0 @@
%div{ id: @id, "data-controller": "modal #{@controllers}", "data-action": "keyup@document->modal#closeIfEscapeKey" }
.reveal-modal-bg.fade{ "data-modal-target": "background", "data-action": "click->modal#close" }
.reveal-modal.fade.tiny.help-modal{ "data-modal-target": "modal" }
= content
.modal-actions
%input{ class: "button icon-plus #{close_button_class}", type: 'button', value: t('js.admin.modals.cancel'), "data-action": "click->modal#close" }
%input{ class: "button icon-plus primary", type: 'button', value: t('js.admin.modals.confirm'), "data-action": @confirm_actions }

View File

@@ -1,4 +0,0 @@
.modal-actions {
display: flex;
justify-content: space-around;
}

View File

@@ -1,7 +1,26 @@
# frozen_string_literal: true
class HelpModalComponent < ModalComponent
class HelpModalComponent < ViewComponent::Base
def initialize(id:, close_button: true)
super(id: id, close_button: close_button)
@id = id
@close_button = close_button
end
private
def close_button_class
if namespace == "admin"
"red"
else
"primary"
end
end
def close_button?
!!@close_button
end
def namespace
helpers.controller_path.split("/").first
end
end

View File

@@ -1,26 +0,0 @@
# frozen_string_literal: true
class ModalComponent < ViewComponent::Base
def initialize(id:, close_button: true)
@id = id
@close_button = close_button
end
private
def close_button_class
if namespace == "admin"
"red"
else
"primary"
end
end
def close_button?
!!@close_button
end
def namespace
helpers.controller_path.split("/").first
end
end

View File

@@ -1,15 +0,0 @@
# frozen_string_literal: true
class PaginationComponent < ViewComponentReflex::Component
def initialize(pagy:, data:)
super
@count = pagy.count
@page = pagy.page
@per_page = pagy.items
@pages = pagy.pages
@next = pagy.next
@prev = pagy.prev
@data = data
@series = pagy.series
end
end

View File

@@ -1,16 +0,0 @@
= component_controller do
%nav{"aria-label": "pagination"}
.pagination
.pagination-prev{data: @prev.nil? ? nil : @data, "data-page": @prev, class: "#{'inactive' if @prev.nil?}"}
= I18n.t "components.pagination.previous"
.pagination-pages
- @series.each do |page|
- if page == :gap
.pagination-gap
- else
.pagination-page{data: @data, "data-page": page, class: "#{'active' if page.to_i == @page}"}
= page
.pagination-next{data: @next.nil? ? nil : @data, "data-page": @next, class: "#{'inactive' if @next.nil?}"}
= I18n.t "components.pagination.next"

View File

@@ -1,69 +0,0 @@
nav {
.pagination {
display: flex;
justify-content: space-between;
align-items: flex-start;
font-size: 14px;
.pagination-prev, .pagination-next {
cursor: pointer;
&:after, &:before {
font-size: 2em;
position: relative;
top: 3px;
}
&.inactive {
cursor: default;
color: $disabled-dark;
}
}
.pagination-prev {
margin-left: 10px;
&:before {
content: "";
margin-left: 10px;
margin-right: 10px;
}
}
.pagination-next {
margin-right: 10px;
&:after {
content: "";
margin-left: 10px;
margin-right: 10px;
}
}
.pagination-pages {
display: flex;
align-items: flex-end;
.pagination-gap, .pagination-page {
padding: 0 0.5rem;
margin-left: 10px;
margin-right: 10px;
}
.pagination-gap {
color: $disabled-dark;
}
.pagination-page {
color: $color-4;
cursor: pointer;
&.active {
border-top: 3px solid $spree-blue;
color: $spree-blue;
cursor: default;
}
}
}
}
}

View File

@@ -1,30 +0,0 @@
# frozen_string_literal: true
class ProductComponent < ViewComponentReflex::Component
def initialize(product:, columns:)
super
@product = product
@image = @product.images[0] if product.images.any?
@columns = columns.map { |c|
{
id: c[:value],
value: column_value(c[:value])
}
}
end
def column_value(column)
case column
when 'name'
@product.name
when 'price'
@product.price
when 'unit'
"#{@product.unit_value} #{@product.variant_unit}"
when 'producer'
@product.supplier.name
when 'category'
@product.taxons.map(&:name).join(', ')
end
end
end

View File

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

View File

@@ -1,152 +0,0 @@
# frozen_string_literal: true
class ProductsTableComponent < ViewComponentReflex::Component
include Pagy::Backend
SORTABLE_COLUMNS = ["name"].freeze
SELECTABLE_COMUMNS = [{ label: I18n.t("admin.products_page.columns_selector.price"),
value: "price" },
{ label: I18n.t("admin.products_page.columns_selector.unit"),
value: "unit" },
{ label: I18n.t("admin.products_page.columns_selector.producer"),
value: "producer" },
{ label: I18n.t("admin.products_page.columns_selector.category"),
value: "category" }].sort { |a, b|
a[:label] <=> b[:label]
}.freeze
PER_PAGE_VALUE = [10, 25, 50, 100].freeze
PER_PAGE = PER_PAGE_VALUE.map { |value| { label: value, value: value } }
NAME_COLUMN = { label: I18n.t("admin.products_page.columns.name"), value: "name",
sortable: true }.freeze
def initialize(user:)
super
@user = user
@selectable_columns = SELECTABLE_COMUMNS
@columns_selected = ["price", "unit"]
@per_page = PER_PAGE
@per_page_selected = [10]
@categories = [{ label: "All", value: "all" }] +
Spree::Taxon.order(:name)
.map { |taxon| { label: taxon.name, value: taxon.id.to_s } }
@categories_selected = ["all"]
@producers = [{ label: "All", value: "all" }] +
OpenFoodNetwork::Permissions.new(@user)
.managed_product_enterprises.is_primary_producer.by_name
.map { |producer| { label: producer.name, value: producer.id.to_s } }
@producers_selected = ["all"]
@page = 1
@sort = { column: "name", direction: "asc" }
@search_term = ""
end
def before_render
fetch_products
refresh_columns
end
def search_term
@search_term = element.dataset['value']
end
def toggle_column
column = element.dataset['value']
@columns_selected = if @columns_selected.include?(column)
@columns_selected - [column]
else
@columns_selected + [column]
end
end
def click_sort
@sort = { column: element.dataset['sort-value'],
direction: element.dataset['sort-direction'] == "asc" ? "desc" : "asc" }
end
def toggle_per_page
selected = element.dataset['value'].to_i
@per_page_selected = [selected] if PER_PAGE_VALUE.include?(selected)
end
def toggle_category
category_clicked = element.dataset['value']
@categories_selected = toggle_selector_with_filter(category_clicked, @categories_selected)
end
def toggle_producer
producer_clicked = element.dataset['value']
@producers_selected = toggle_selector_with_filter(producer_clicked, @producers_selected)
end
def change_page
page = element.dataset['page'].to_i
@page = page if page > 0
end
private
def refresh_columns
@columns = @columns_selected.map { |column|
{ label: I18n.t("admin.products_page.columns.#{column}"), value: column,
sortable: SORTABLE_COLUMNS.include?(column) }
}.sort! { |a, b| a[:label] <=> b[:label] }
@columns.unshift(NAME_COLUMN)
end
def toggle_selector_with_filter(clicked, selected)
selected = if selected.include?(clicked)
selected - [clicked]
else
selected + [clicked]
end
if clicked == "all" || selected.empty?
selected = ["all"]
elsif selected.include?("all") && selected.length > 1
selected -= ["all"]
end
selected
end
def fetch_products
product_query = OpenFoodNetwork::Permissions.new(@user).editable_products.merge(product_scope)
@products = product_query.ransack(ransack_query).result
@pagy, @products = pagy(@products, items: @per_page_selected.first, page: @page)
end
def product_scope
scope = if @user.has_spree_role?("admin") || @user.enterprises.present?
Spree::Product
else
Spree::Product.active
end
scope.includes(product_query_includes)
end
def ransack_query
query = { s: "#{@sort[:column]} #{@sort[:direction]}" }
query = if @producers_selected.include?("all")
query.merge({ supplier_id_eq: "" })
else
query.merge({ supplier_id_in: @producers_selected })
end
query = query.merge({ name_cont: @search_term }) if @search_term.present?
if @categories_selected.include?("all")
query.merge({ primary_taxon_id_eq: "" })
else
query.merge({ primary_taxon_id_in: @categories_selected })
end
end
def product_query_includes
[
master: [:images],
variants: [:default_price, :stock_locations, :stock_items, :variant_overrides,
{ option_values: :option_type }]
]
end
end

View File

@@ -1,21 +0,0 @@
= component_controller(class: "products-table") do
.products-table-form
.products-table-form_filter_results
= render(SearchInputComponent.new(value: @search_term, data: reflex_data_attributes(:search_term)))
.products-table-form_categories_selector
= render(SelectorWithFilterComponent.new(title: t("admin.products_page.filters.categories.title"), selected: @categories_selected, items: @categories, data: reflex_data_attributes(:toggle_category), selected_items_i18n_key: "admin.products_page.filters.categories.selected_categories"))
.products-table-form_producers_selector
= render(SelectorWithFilterComponent.new(title: t("admin.products_page.filters.producers.title"), selected: @producers_selected, items: @producers, data: reflex_data_attributes(:toggle_producer), selected_items_i18n_key: "admin.products_page.filters.producers.selected_producers"))
.products-table-form_per-page_selector
= render(SelectorComponent.new(title: t('admin.products_page.filters.per_page', count: @per_page_selected[0]), selected: @per_page_selected, items: @per_page, data: reflex_data_attributes(:toggle_per_page)))
.products-table-form_columns_selector
= render(SelectorComponent.new(title: t("admin.products_page.filters.columns"), selected: @columns_selected, items: @selectable_columns, data: reflex_data_attributes(:toggle_column)))
.products-table_table
%table
= render(TableHeaderComponent.new(columns: @columns, sort: @sort, data: reflex_data_attributes(:click_sort)))
%tbody
= render(ProductComponent.with_collection(@products, columns: @columns))
.products-table-form_pagination
= render(PaginationComponent.new(pagy: @pagy, data: reflex_data_attributes(:change_page)))

View File

@@ -1,47 +0,0 @@
.products-table {
.products-table-form {
display: grid;
grid-template-columns: repeat( auto-fit, minmax(250px, 1fr) );
grid-gap: 10px;
margin-bottom: 10px;
}
.products-table_table {
box-shadow: 0 10px 10px -1px rgb(0 0 0 / 10%);
}
.products-table-form_pagination {
position: relative;
top: -15px;
nav, .pagination {
margin-top: 0;
padding-top: 0;
}
}
}
.products-table.loading {
.products-table-form_pagination, .products-table_table {
position: relative;
&:before {
content: "";
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(255, 255, 255, 0.5);
}
}
.products-table_table {
&:before {
background-position: center;
background-repeat: no-repeat;
background-size: 50px 50px;
background-image: url("../images/spinning-circles.svg");
}
}
}

View File

@@ -1,9 +0,0 @@
# frozen_string_literal: true
class SearchInputComponent < ViewComponentReflex::Component
def initialize(value: nil, data: {})
super
@value = value
@data = data
end
end

View File

@@ -1,5 +0,0 @@
= component_controller do
%div.search-input
%input{type: 'text', placeholder: t("components.search_input.placeholder"), id: 'search_query', data: {action: 'debounced:input->search-input#search'}, value: @value}
.search-button{data: @data}
%i.fa.fa-search

View File

@@ -1,23 +0,0 @@
.search-input {
border: 1px solid $disabled-light;
height: 3em;
display: flex;
line-height: 3em;
align-items: center;
input {
border: none;
height: 3em;
width: 100%;
box-sizing: border-box;
padding-right: 5px;
}
.search-button {
padding-right: 10px;
padding-left: 5px;
cursor: pointer;
color: $color-4;
}
}

View File

@@ -1,17 +0,0 @@
# frozen_string_literal: true
class SelectorComponent < ViewComponentReflex::Component
def initialize(title:, selected:, items:, data: {})
super
@title = title
@items = items.map do |item|
{
label: item[:label],
value: item[:value],
selected: selected.include?(item[:value])
}
end
@selected = selected
@data = data
end
end

View File

@@ -1,11 +0,0 @@
= component_controller do
.selector.selector-close
.selector-main{ data: { action: "click->selector#toggle" } }
.selector-main-title
= @title
.selector-arrow
.selector-wrapper
.selector-items
- @items.each do |item|
.selector-item{ class: ("selected" if item[:selected]), data: @data, "data-value": item[:value] }
= item[:label]

View File

@@ -1,86 +0,0 @@
.selector {
position: relative;
.selector-main {
border: 1px solid $disabled-light;
height: 3em;
position: relative;
cursor: pointer;
.selector-main-title {
line-height: 3em;
padding-left: 10px;
padding-right: 10px;
}
.selector-arrow {
position: absolute;
right: 0px;
height: 3em;
width: 1.5em;
top: -1px;
&:after {
content: "";
position: absolute;
top: 50%;
right: 5px;
margin-top: -5px;
width: 0;
height: 0;
border-left: 5px solid transparent;
border-right: 5px solid transparent;
border-top: 5px solid $disabled-light;
}
}
}
.selector-wrapper {
position: absolute;
left: 0px;
right: 0px;
z-index: 1;
background-color: white;
margin-top: -1px;
border: 1px solid $disabled-light;
.selector-items {
overflow-y: auto;
min-height: 6em;
.selector-item {
padding-left: 10px;
padding-right: 10px;
border-bottom: 1px solid $disabled-light;
position: relative;
height: 3em;
line-height: 3em;
&:hover {
background-color: #eee;
cursor: pointer;
}
&:last-child {
border-bottom: none;
}
&.selected {
&:after {
content: "";
display: inline-block;
position: absolute;
right: 10px;
}
}
}
}
}
&.selector-close {
.selector-wrapper {
display: none;
}
}
}

View File

@@ -1,11 +0,0 @@
# frozen_string_literal: true
class SelectorWithFilterComponent < SelectorComponent
def initialize(title:, selected:, items:, data: {},
selected_items_i18n_key: 'components.selector_with_filter.selected_items')
super(title: title, selected: selected, items: items, data: data)
@selected_items = items.select { |item| @selected.include?(item[:value]) }
@selected_items_i18n_key = selected_items_i18n_key
@items = items
end
end

View File

@@ -1,22 +0,0 @@
= component_controller do
.super-selector.selector.selector-close
.selector-main{ data: { action: "click->selector-with-filter#toggle" } }
.super-selector-label
= @title
.super-selector-selected-items
- case @selected_items.length
- when 1, 2
- @selected_items.each do |item|
.super-selector-selected-item
= item[:label]
- else
.super-selector-selected-item
= t(@selected_items_i18n_key, count: @selected_items.length)
.selector-arrow
.selector-wrapper
.super-selector-search
%input{type: "text", placeholder: t("components.selector_with_filter.search_placeholder"), data: { action: "debounced:input->selector-with-filter#filter" } }
.selector-items
- @items.each do |item|
.selector-item{ class: ("selected" if item[:selected]), data: @data.merge({ "selector-with-filter-target": "items" }), "data-value": item[:value] }
= item[:label]

View File

@@ -1,51 +0,0 @@
.super-selector {
position: relative;
.selector-main {
.super-selector-label {
padding-left: 5px;
padding-right: 5px;
margin-left: 10px;
position: absolute;
top: -1em;
background-color: white;
}
}
.super-selector-selected-items {
margin-left: 5px;
margin-right: 2em;
margin-top: 7px;
display: flex;
.super-selector-selected-item {
border: 1px solid $pale-blue;
background-color: $spree-light-blue;
border-radius: 20px;
height: 2em;
padding-left: 10px;
padding-right: 10px;
display: inline-block;
margin-right: 5px;
padding-top: 2px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
}
.selector-wrapper {
.super-selector-search {
border-bottom: 1px solid $disabled-light;
padding: 10px 5px;
input {
border: 1px solid $disabled-light;
box-sizing: border-box;
border-radius: 4px;
width: 100%;
}
}
}
}

View File

@@ -1,10 +0,0 @@
# frozen_string_literal: true
class TableHeaderComponent < ViewComponentReflex::Component
def initialize(columns:, sort:, data: {})
super
@columns = columns
@sort = sort
@data = data
end
end

View File

@@ -1,7 +0,0 @@
= component_controller do
%thead.table-header
%tr
- @columns.each do |column|
%th{class: (column[:sortable] ? "th-sortable " : "" ) + (@sort[:column] == column[:value] ? " th-sorted-#{@sort[:direction]}" : ""), data: (@data if column[:sortable] == true), "data-sort-value": column[:value], "data-sort-direction": @sort[:direction]}
= column[:label]

View File

@@ -1,23 +0,0 @@
thead.table-header {
th {
&.th-sortable {
cursor: pointer;
}
&.th-sorted-asc, &.th-sorted-desc {
&:after {
display: inline-block;
padding-left: 10px;
}
}
&.th-sorted-asc {
&:after {
content: "";
}
}
&.th-sorted-desc {
&:after {
content: "";
}
}
}
}

View File

@@ -1,22 +1,15 @@
# frozen_string_literal: true
require "open_food_network/feature_toggle"
class FeatureToggleConstraint
def initialize(feature_name, negate: false)
def initialize(feature_name)
@feature = feature_name
@negate = negate
end
def matches?(request)
enabled?(request) ^ @negate
end
def enabled?(request)
OpenFoodNetwork::FeatureToggle.enabled?(@feature, current_user(request))
end
def current_user(request)
request.env['warden']&.user
request.env['warden'].user
end
end

View File

@@ -116,7 +116,21 @@ module Admin
# Fetches tags for all customers of the enterprise and returns a hash indexed by customer_id
def customer_tags_by_id
BatchTaggableTagsQuery.call(Customer.of(managed_enterprise_id))
customer_tags = ::ActsAsTaggableOn::Tag.
joins(:taggings).
includes(:taggings).
where(taggings:
{ taggable_type: 'Customer',
taggable_id: Customer.of(managed_enterprise_id),
context: 'tags' })
customer_tags.each_with_object({}) do |tag, indexed_hash|
tag.taggings.each do |tagging|
customer_id = tagging.taggable_id
indexed_hash[customer_id] ||= []
indexed_hash[customer_id] << tag.name
end
end
end
end
end

View File

@@ -1,7 +0,0 @@
# frozen_string_literal: true
module Admin
class ProductsController < Spree::Admin::BaseController
def index; end
end
end

View File

@@ -31,7 +31,7 @@ module Admin
private
def export_report
send_data @report.render_as(report_format), filename: report_filename
send_data @report.render_as(report_format, controller: self), filename: report_filename
end
def render_report

View File

@@ -162,20 +162,25 @@ module Admin
[:index]
end
def managed_enterprise_id
Enterprise.managed_by(spree_current_user).select('enterprises.id').
find_by(id: params[:enterprise_id])
end
def subscription_params
@subscription_params ||= PermittedAttributes::Subscription.new(params).call.
to_h.with_indifferent_access
end
def payment_method_tags_by_id
@payment_method_tags_by_id ||= BatchTaggableTagsQuery.call(
Spree::PaymentMethod.from(managed_enterprise_id)
)
payment_method_tags = ::ActsAsTaggableOn::Tag.
joins(:taggings).
includes(:taggings).
where(taggings: { taggable_type: "Spree::PaymentMethod",
taggable_id: Spree::PaymentMethod.from(Enterprise.managed_by(spree_current_user).
select('enterprises.id').find_by(id: params[:enterprise_id])),
context: 'tags' })
payment_method_tags.each_with_object({}) do |tag, hash|
payment_method_id = tag.taggings.first.taggable_id
hash[payment_method_id] ||= []
hash[payment_method_id] << tag.name
end
end
end
end

View File

@@ -1,7 +1,6 @@
# frozen_string_literal: true
module OrderStockCheck
include CablecarResponses
extend ActiveSupport::Concern
def valid_order_line_items?
@@ -30,9 +29,6 @@ module OrderStockCheck
flash[:info] = I18n.t('order_cycle_closed')
respond_to do |format|
format.cable_ready {
render status: :see_other, operations: cable_car.redirect_to(url: main_app.shop_path)
}
format.json { render json: { path: main_app.shop_path }, status: :see_other }
format.html { redirect_to main_app.shop_path, status: :see_other }
end

View File

@@ -68,31 +68,21 @@ module ReportsActions
user: spree_current_user,
report_type: report_type,
report_subtype: report_subtype
).first_or_create do |report_rendering_options|
report_rendering_options.options = {
fields_to_show: if request.get?
@report.columns.keys -
@report.fields_to_hide
else
params[:fields_to_show]
end,
display_summary_row: request.get?,
display_header_row: false
}
).first_or_create do |new_instance|
new_instance.options[:fields_to_show] = if @report.present?
@report.columns.keys - @report.fields_to_hide
else
[]
end
new_instance.options[:display_summary_row] = request.get? || params[:display_summary_row].present?
params[:display_header_row] = params[:display_header_row].present?
end
update_rendering_options
if params[:fields_to_show].present?
@rendering_options.options[:fields_to_show] = params[:fields_to_show]
end
@rendering_options.options[:display_summary_row] = params[:display_summary_row].present?
@rendering_options.options[:display_header_row] = params[:display_header_row].present?
@rendering_options.save
@rendering_options
end
def update_rendering_options
return unless request.post?
@rendering_options.update(
options: {
fields_to_show: params[:fields_to_show],
display_summary_row: params[:display_summary_row].present?,
display_header_row: params[:display_header_row].present?
}
)
end
end

View File

@@ -31,7 +31,6 @@ class SplitCheckoutController < ::BaseController
if confirm_order || update_order
return if performed?
check_payments_adjustments
clear_invalid_payments
advance_order_state
redirect_to_step
@@ -54,10 +53,6 @@ class SplitCheckoutController < ::BaseController
flash[:error] = I18n.t('split_checkout.errors.no_shipping_methods_available')
end
def check_payments_adjustments
@order.payments.each(&:ensure_correct_adjustment)
end
def clear_invalid_payments
@order.payments.with_state(:invalid).delete_all
end
@@ -70,7 +65,6 @@ class SplitCheckoutController < ::BaseController
return true if redirect_to_payment_gateway
@order.process_payments!
@order.confirm!
order_completion_reset @order
end

View File

@@ -34,7 +34,6 @@ module Spree
link = link_to_with_icon(options[:icon], titleized_label, destination_url)
css_classes << 'tab-with-icon'
else
titleized_label = raw("<span class='text'>#{titleized_label}</span>")
link = link_to(titleized_label, destination_url)
end

View File

@@ -133,7 +133,7 @@ module Spree
event_label = I18n.t("cancel", scope: "actions")
button_link_to(event_label,
fire_admin_order_url(@order, e: "cancel"),
method: :put, icon: "icon-remove", form_id: "cancel_order_form")
method: :put, icon: "icon-cancel", form_id: "cancel_order_form")
end
def resume_event_link

View File

@@ -6,16 +6,15 @@ class Enterprise < ApplicationRecord
# The next Rails version will have named variants but we need to store them
# ourselves for now.
LOGO_SIZES = {
thumb: { gravity: "Center", resize: "100x100^", crop: '100x100+0+0' },
small: { gravity: "Center", resize: "180x180^", crop: '180x180+0+0' },
medium: { gravity: "Center", resize: "300x300^", crop: '300x300+0+0' },
thumb: { resize_to_limit: [100, 100] },
small: { resize_to_limit: [180, 180] },
medium: { resize_to_limit: [300, 300] },
}.freeze
PROMO_IMAGE_SIZES = {
thumb: { resize_to_limit: [100, 100] },
medium: { resize_to_fill: [720, 156] },
large: { resize_to_fill: [1200, 260] },
}.freeze
VALID_INSTAGRAM_REGEX = %r{\A[a-zA-Z0-9._]{1,30}([^/-]*)\z}
searchable_attributes :sells, :is_primary_producer
searchable_associations :properties
@@ -100,7 +99,6 @@ class Enterprise < ApplicationRecord
validate :shopfront_taxons
validate :shopfront_producers
validate :enforce_ownership_limit, if: lambda { owner_id_changed? && !owner_id.nil? }
validates :instagram, format: { with: VALID_INSTAGRAM_REGEX, message: Spree.t('errors.messages.invalid_instagram_url') }, allow_blank: true
before_validation :initialize_permalink, if: lambda { permalink.nil? }
before_validation :set_unused_address_fields
@@ -458,7 +456,7 @@ class Enterprise < ApplicationRecord
end
def correct_instagram_url(url)
url && strip_url(url.downcase).sub(%r{www.instagram.com/}, '').sub(%r{instagram.com/}, '').delete("@")
url && strip_url(url).sub(%r{www.instagram.com/}, '').delete("@")
end
def correct_twitter_url(url)

View File

@@ -211,7 +211,7 @@ module ProductImport
reference_entry = all_entries_for_product(entry).first
return if entry.variant_unit_name.to_s == reference_entry.variant_unit_name.to_s
mark_as_values_must_be_same(entry, "variant_unit_name")
mark_as_not_updatable(entry, "variant_unit_name")
end
def producer_validation(entry)
@@ -425,11 +425,6 @@ module ProductImport
error: I18n.t("admin.product_import.model.not_updatable"))
end
def mark_as_values_must_be_same(entry, attribute)
mark_as_invalid(entry, attribute: attribute,
error: I18n.t("admin.product_import.model.values_must_be_same"))
end
def import_into_inventory?
@import_settings.dig(:settings, 'import_into') == 'inventories'
end

View File

@@ -1,5 +1,3 @@
# frozen_string_literal: true
class ReportRenderingOptions < ApplicationRecord
belongs_to :user, class_name: "Spree::User"
serialize :options, Hash

View File

@@ -37,7 +37,7 @@ module Spree
foreign_key: :owner_id, inverse_of: :owner
has_many :customers
has_many :credit_cards
has_many :report_rendering_options, class_name: "::ReportRenderingOptions", dependent: :destroy
accepts_nested_attributes_for :enterprise_roles, allow_destroy: true
accepts_nested_attributes_for :bill_address

View File

@@ -1,20 +0,0 @@
# frozen_string_literal: true
class BatchTaggableTagsQuery
def self.call(taggables)
::ActsAsTaggableOn::Tag.
joins(:taggings).
includes(:taggings).
where(taggings:
{
taggable_type: taggables.model.to_s,
taggable_id: taggables,
context: 'tags'
}).order("tags.name").each_with_object({}) do |tag, indexed_hash|
tag.taggings.each do |tagging|
indexed_hash[tagging.taggable_id] ||= []
indexed_hash[tagging.taggable_id] << tag.name
end
end
end
end

View File

@@ -16,11 +16,4 @@ class ApplicationReflex < StimulusReflex::Reflex
#
# For code examples, considerations and caveats, see:
# https://docs.stimulusreflex.com/rtfm/patterns#internationalization
include CanCan::ControllerAdditions
delegate :current_user, to: :connection
def current_ability
Spree::Ability.new(current_user)
end
end

View File

@@ -1,38 +0,0 @@
# frozen_string_literal: true
class ExampleReflex < ApplicationReflex
# Add Reflex methods in this file.
#
# All Reflex instances include CableReady::Broadcaster and expose the following properties:
#
# - connection - the ActionCable connection
# - channel - the ActionCable channel
# - request - an ActionDispatch::Request proxy for the socket connection
# - session - the ActionDispatch::Session store for the current visitor
# - flash - the ActionDispatch::Flash::FlashHash for the current request
# - url - the URL of the page that triggered the reflex
# - params - parameters from the element's closest form (if any)
# - element - a Hash like object that represents the HTML element that triggered the reflex
# - signed - use a signed Global ID to map dataset attribute to a model
# eg. element.signed[:foo]
# - unsigned - use an unsigned Global ID to map dataset attribute to a model
# eg. element.unsigned[:foo]
# - cable_ready - a special cable_ready that can broadcast to the current visitor
# (no brackets needed)
# - reflex_id - a UUIDv4 that uniquely identies each Reflex
# - tab_id - a UUIDv4 that uniquely identifies the browser tab
#
# Example:
#
# before_reflex do
# # throw :abort # this will prevent the Reflex from continuing
# # learn more about callbacks at https://docs.stimulusreflex.com/rtfm/lifecycle
# end
#
# def example(argument=true)
# # Your logic here...
# # Any declared instance variables will be made available to the Rails controller and view.
# end
#
# Learn more at: https://docs.stimulusreflex.com/rtfm/reflex-classes
end

View File

@@ -1,13 +0,0 @@
# frozen_string_literal: true
class ResendConfirmationEmailReflex < ApplicationReflex
def confirm(order_ids)
Spree::Order.where(id: order_ids).find_each do |o|
Spree::OrderMailer.confirm_email_for_customer(o.id, true).deliver_later if can? :resend, o
end
flash[:success] = I18n.t("admin.resend_confirmation_emails_feedback", count: order_ids.count)
cable_ready.dispatch_event(name: "modal:close")
morph "#flashes", render(partial: "shared/flashes", locals: { flashes: flash })
end
end

View File

@@ -42,18 +42,12 @@ module Api
.visible_variants_for_outgoing_exchanges_to(object.receiver)
end
def preloaded_tag_list
return object.tag_list unless options[:preloaded_tags]
options.dig(:preloaded_tags, object.id) || []
end
def tag_list
preloaded_tag_list.join(",")
object.tag_list.join(",")
end
def tags
preloaded_tag_list.map { |tag| { text: tag } }
object.tag_list.map{ |t| { text: t } }
end
end
end

View File

@@ -34,8 +34,7 @@ module Api
ActiveModel::ArraySerializer.
new(scoped_exchanges, each_serializer: Api::Admin::ExchangeSerializer,
current_user: options[:current_user],
preloaded_tags: BatchTaggableTagsQuery.call(scoped_exchanges))
current_user: options[:current_user])
end
def editable_variants_for_incoming_exchanges

View File

@@ -30,14 +30,8 @@ class OrderAvailablePaymentMethods
distributor.payment_methods
else
distributor.payment_methods.where(
id: available_distributor_payment_methods_ids
id: order_cycle.distributor_payment_methods.select(:payment_method_id)
)
end.available.select(&:configured?)
end
def available_distributor_payment_methods_ids
order_cycle.distributor_payment_methods
.where(distributor_id: distributor.id)
.select(:payment_method_id)
end
end

View File

@@ -30,14 +30,8 @@ class OrderAvailableShippingMethods
distributor.shipping_methods
else
distributor.shipping_methods.where(
id: available_distributor_shipping_methods_ids
id: order_cycle.distributor_shipping_methods.select(:shipping_method_id)
)
end.frontend.to_a
end
def available_distributor_shipping_methods_ids
order_cycle.distributor_shipping_methods
.where(distributor_id: distributor.id)
.select(:shipping_method_id)
end
end

View File

@@ -102,6 +102,7 @@
%td.actions
%a{ 'ng-click' => "deleteCustomer(customer)", :class => "delete-customer icon-trash no-text" }
-# %show-more.text-center{ data: "filteredCustomers", limit: "customerLimit", increment: "20" }
%div.text-center{ ng: { show: "filteredCustomers.length > customerLimit" } }
%input{ type: 'button', value: t(:show_more), ng: { click: 'customerLimit = customerLimit + 20' } }
%input{ type: 'button', value: t(:show_all_with_more, num: '{{ filteredCustomers.length - customerLimit }}'), ng: { click: 'customerLimit = filteredCustomers.length' } }

View File

@@ -102,6 +102,7 @@
= af.label :latitude, t(:latitude)
\/
= af.label :longitude, t(:longitude)
%span.required *
%div{'ofn-with-tip' => t('latitude_longitude_tip')}
%a= t('admin.whats_this')
.four.columns

View File

@@ -41,6 +41,7 @@
= af.label :latitude, t(:latitude)
\/
= af.label :longitude, t(:longitude)
%span.required *
= render partial: 'admin/shared/tooltip', locals: {tooltip_text: t('latitude_longitude_tip')}
.four.columns
= af.text_field :latitude, { placeholder: t(:latitude_placeholder) }

View File

@@ -4,8 +4,7 @@
%br
100 x 100 pixels
.omega.eight.columns
%img{ class: 'image-field-group__preview-image', ng: { src: '{{ Enterprise.logo.thumb }}', if: 'Enterprise.logo' } }
%br
%img{ class: 'image-field-group__preview-image', ng: { src: '{{ Enterprise.logo.medium }}', if: 'Enterprise.logo' } }
= f.file_field :logo
%a.button.red{ href: '', ng: {click: 'removeLogo()', if: 'Enterprise.logo'} }
= t('.remove_logo')

View File

@@ -32,8 +32,6 @@
.four.columns.omega
= f.radio_button :sells, "any", 'ng-model' => 'Enterprise.sells', data: {action: "change->primary-details#enterpriseSellsChanged"}
= f.label :sells, t('.any'), value: "any"
%span{ style: "width: 30px; height: 30px;", class: "hidden", data: { "primary-details-target": "spinner" } }
= render partial: "components/admin_spinner"
.row
.three.columns.alpha
%label= t('.visible_in_search')

View File

@@ -3,4 +3,4 @@
{{ enterprise.name }}
= "{{ enterprise.issues_summary_#{type} ? '('+enterprise.issues_summary_#{type}+')' : '' }}"
= f.submit t(".add_#{type}"), 'ng-click' => "add#{type.capitalize}($event)", 'ng-disabled' => "!new_#{type}_id || !OrderCycle.novel#{type.capitalize}(new_#{type}_id)", "class": "secondary"
= f.submit t(".add_#{type}"), 'ng-click' => "add#{type.capitalize}($event)", 'ng-disabled' => "!new_#{type}_id || !OrderCycle.novel#{type.capitalize}(new_#{type}_id)"

View File

@@ -26,16 +26,15 @@
= f.submit t('.add_fee'), 'ng-click' => 'addExchangeFee($event, exchange)', 'ng-hide' => '!enterprises[exchange.enterprise_id].managed && !order_cycle.viewing_as_coordinator'
%td.actions
.flex
%a{'ng-click' => 'removeExchange($event, exchange)', :class => "icon-trash no-text remove-exchange"}
%a{'ng-click' => 'removeExchange($event, exchange)', :class => "icon-trash no-text remove-exchange"}
- if type == 'supplier'
%tr.panel-row{ object: "exchange",
panels: "{products: 'exchange_products_supplied'}",
locals: "$index,exchangeTotalVariants,order_cycle,exchange,enterprises,setExchangeVariants,selectAllVariants,suppliedVariants,removeDistributionOfVariant,initializeExchangeProductsPanel,loadMoreExchangeProducts,loadAllExchangeProducts,productsLoading",
colspan: if feature?(:admin_style_v2, spree_current_user) then 5 else 4 end }
colspan: 4 }
- if type == 'distributor'
%tr.panel-row{ object: "exchange",
panels: "{products: 'exchange_products_distributed', tags: 'exchange_tags'}",
locals: "$index,exchangeTotalVariants,order_cycle,exchange,enterprises,setExchangeVariants,incomingExchangeVariantsFor,variantSuppliedToOrderCycle,initializeExchangeProductsPanel,loadMoreExchangeProducts,loadAllExchangeProducts,productsLoading",
colspan: if feature?(:admin_style_v2, spree_current_user) then 6 else 5 end }
colspan: 5 }

View File

@@ -1,16 +1,16 @@
%colgroup
%col{ ng: { show: 'columns.name.visible' } }
%col{ ng: { show: 'columns.schedules.visible' } }
%col{ ng: { show: 'columns.open.visible' } }
%col{ ng: { show: 'columns.close.visible' } }
%col{ ng: { show: 'columns.open.visible' }, style: 'width: 20%;' }
%col{ ng: { show: 'columns.close.visible' }, style: 'width: 20%;' }
- unless simple_index
%col{ ng: { show: 'columns.producers.visible' } }
%col{ ng: { show: 'columns.coordinator.visible' } }
%col{ ng: { show: 'columns.shops.visible' } }
%col{ ng: { show: 'columns.products.visible' } }
%col
%col
%col
%col{ style: 'width: 5%;' }
%col{ style: 'width: 5%;' }
%col{ style: 'width: 5%;' }
%thead
%tr
@@ -32,3 +32,5 @@
%th{ ng: { show: 'columns.products.visible' } }
=t :products
%th.actions
%th.actions
%th.actions

View File

@@ -32,9 +32,8 @@
= t('.variants')
%td.actions
.flex
%a.edit-order-cycle.icon-edit.no-text{ ng: { href: '{{orderCycle.edit_path}}'}, 'ofn-with-tip' => t(:edit) }
%div{ ng: { if: 'orderCycle.viewing_as_coordinator' } }
%a.clone-order-cycle.icon-copy.no-text{ ng: { href: '{{orderCycle.clone_path}}'}, 'ofn-with-tip' => t(:clone) }
%div{ ng: { if: 'orderCycle.deletable && orderCycle.viewing_as_coordinator' }}
%a.delete-order-cycle.icon-trash.no-text{ ng: { href: '{{orderCycle.delete_path}}'}, data: { method: 'delete', confirm: t(:are_you_sure) }, 'ofn-with-tip' => t(:remove) }
%a.edit-order-cycle.icon-edit.no-text{ ng: { href: '{{orderCycle.edit_path}}'}, 'ofn-with-tip' => t(:edit) }
%td.actions{ ng: { if: 'orderCycle.viewing_as_coordinator' } }
%a.clone-order-cycle.icon-copy.no-text{ ng: { href: '{{orderCycle.clone_path}}'}, 'ofn-with-tip' => t(:clone) }
%td.actions{ ng: { if: 'orderCycle.deletable && orderCycle.viewing_as_coordinator' }}
%a.delete-order-cycle.icon-trash.no-text{ ng: { href: '{{orderCycle.delete_path}}'}, data: { method: 'delete', confirm: t(:are_you_sure) }, 'ofn-with-tip' => t(:remove) }

View File

@@ -1,8 +0,0 @@
- content_for :page_title do
= t('admin.products_page.title')
= render partial: 'spree/admin/shared/product_sub_menu'
#products_page
= render(ProductsTableComponent.new(user: spree_current_user))

View File

@@ -4,7 +4,7 @@
.row.date-range-filter
.alpha.two.columns= label_tag nil, t(:date_range)
.omega.fourteen.columns
= f.text_field "#{field}_gt", :class => 'datetimepicker datepicker-from', :placeholder => t(:start), data: { controller: "flatpickr", "flatpickr-enable-time-value": true, "flatpickr-default-date-value": "startOfDay" }
= f.text_field "#{field}_gt", :class => 'datetimepicker datepicker-from', :placeholder => t(:start), data: { controller: "flatpickr", "flatpickr-enable-time-value": true }
%span.range-divider
%i.icon-arrow-right
= f.text_field "#{field}_lt", :class => 'datetimepicker datepicker-to', :placeholder => t(:stop), data: { controller: "flatpickr", "flatpickr-enable-time-value": true, "flatpickr-default-date-value": "endOfDay" }
= f.text_field "#{field}_lt", :class => 'datetimepicker datepicker-to', :placeholder => t(:stop), data: { controller: "flatpickr", "flatpickr-enable-time-value": true }

View File

@@ -16,16 +16,16 @@
.omega.fourteen.columns
- if @report.header_option?
%span.inline-checkbox{ style: "margin-right: 1rem;" }
= check_box_tag :display_header_row, true, @rendering_options.options[:display_header_row]
= check_box_tag :display_header_row, true, @render_options.options[:display_header_row]
= label_tag :display_header_row, t(".header_row")
- if @report.summary_row_option?
%span.inline-checkbox
= check_box_tag :display_summary_row, true, @rendering_options.options[:display_summary_row], { "data-csv-select-target": "checkbox" }
= check_box_tag :display_summary_row, true, @render_options.options[:display_summary_row], { "data-csv-select-target": "checkbox" }
= label_tag :display_summary_row, t(".summary_row"), { "data-csv-select-target": "label" }
- if @report.available_headers.present?
.row
.alpha.two.columns= label_tag nil, t(:report_columns)
.omega.fourteen.columns
= render MultipleCheckedSelectComponent.new(name: "fields_to_show", options: @report.available_headers, selected: @rendering_options.options[:fields_to_show])
= render MultipleCheckedSelectComponent.new(name: "fields_to_show", options: @report.available_headers, selected: @render_options.options[:fields_to_show])

View File

@@ -1,5 +1,5 @@
%ul{style: "margin-left: 12pt"}
- report_subtypes.each do |report_subtype|
%li
- url = main_app.admin_report_path(report_type: report_type, report_subtype: report_subtype[1])
- url = main_app.admin_report_url(report_type: report_type, report_subtype: report_subtype[1])
= link_to report_subtype[0], url

View File

@@ -1,5 +1 @@
= render 'admin/reports/date_range_form', f: f
.row
.alpha.two.columns= label_tag nil, t(:report_hubs)
.omega.fourteen.columns= f.collection_select(:distributor_id_in, @data.orders_distributors, :id, :name, {}, {class: "select2 fullwidth", multiple: true})

View File

@@ -1,7 +1,7 @@
- content_for :page_title do
= @report_title
= form_for @report.search, :url => url_for do |f|
= form_for @report.search, :url => url_for(only_path: false) do |f|
%fieldset.no-border-bottom.print-hidden
%legend{ align: 'center'}= t(:report_filters)
= render partial: "admin/reports/filters/#{@report_type}", locals: { f: f }

View File

@@ -1,3 +1,4 @@
-# %show-more.text-center{ data: "filteredProducts", limit: "productLimit", increment: "10" }
.text-center{ ng: { show: "filteredProducts.length > productLimit" } }
%input{ type: 'button', value: t(:show_more), ng: { click: 'productLimit = productLimit + 10' } }

View File

@@ -3,7 +3,7 @@
.small-12.medium-6.columns.text-right
&nbsp;
.row.animate-show{"ng-show" => "filtersActive"}
.row.animate-show.filter-row{"ng-show" => "filtersActive"}
.small-12.columns
.row.filter-box
.small-12.columns

View File

@@ -2,6 +2,6 @@
- if defined? flashes
- flashes.each do |type, msg|
%alert.animate-show{"data-controller": "flash"}
.flash{type: "#{type}", class: "alert-box #{type == 'error' ? 'alert' : type}"}
%div{type: "#{type}", class: "alert-box #{type == 'error' ? 'alert' : type}"}
%span= msg
%a.small.close{"data-action": "click->flash#close"} ×

View File

@@ -2,7 +2,7 @@
%a{"ng-click" => "triggerProductModal()"}
%span.product-thumb__bulk-label{"ng-if" => "::product.group_buy"}
= t(".bulk")
%img{"ng-src" => "{{::product.primaryImageOrMissing}}"}
%img{"ng-src" => "{{::product.primaryImageOrMissing}}", loading: "lazy"}
.summary
.summary-header

View File

@@ -4,7 +4,7 @@
.row
= render 'shared/components/filter_controls'
.row.animate-show{"ng-show" => "filtersActive"}
.row.animate-show.filter-row{"ng-show" => "filtersActive"}
.small-12.columns
.row.filter-box
.small-12.large-9.columns

View File

@@ -3,53 +3,51 @@
%div.checkout-substep
-# YOUR DETAILS
%div.checkout-title
= t("split_checkout.step1.contact_information.title")
= t("split_checkout.step1.your_details.title")
.two-columns-inputs
%div.checkout-input.with-floating-label{ "data-controller": "floating-label" }
= f.label :email, t("split_checkout.step1.contact_information.email.label")
= f.text_field :email, { placeholder: " " }
= f.error_message_on :email
%div.checkout-input
= bill_address.label :firstname, t("split_checkout.step1.your_details.first_name.label")
= bill_address.text_field :firstname, { placeholder: t("split_checkout.step1.your_details.first_name.placeholder") }
= f.error_message_on "bill_address.firstname"
%div.checkout-input.with-floating-label{ "data-controller": "floating-label" }
= bill_address.label :phone, t("split_checkout.step1.contact_information.phone.label")
= bill_address.text_field :phone, { placeholder: " " }
= f.error_message_on "bill_address.phone"
%div.checkout-input
= bill_address.label :lastname, t("split_checkout.step1.your_details.last_name.label")
= bill_address.text_field :lastname, { placeholder: t("split_checkout.step1.your_details.last_name.placeholder") }
= f.error_message_on "bill_address.lastname"
%div.checkout-input
= f.label :email, t("split_checkout.step1.your_details.email.label")
= f.text_field :email, { placeholder: t("split_checkout.step1.your_details.email.placeholder") }
= f.error_message_on :email
%div.checkout-input
= bill_address.label :phone, t("split_checkout.step1.your_details.phone.label")
= bill_address.text_field :phone, { placeholder: t("split_checkout.step1.your_details.phone.placeholder") }
= f.error_message_on "bill_address.phone"
%div.checkout-substep
-# BILLING ADDRESS
%div.checkout-title
= t("split_checkout.step1.billing_address.title")
.two-columns-inputs
%div.checkout-input.with-floating-label{ "data-controller": "floating-label" }
= bill_address.label :firstname, t("split_checkout.step1.billing_address.first_name.label")
= bill_address.text_field :firstname, { placeholder: " " }
= f.error_message_on "bill_address.firstname"
%div.checkout-input.with-floating-label{ "data-controller": "floating-label" }
= bill_address.label :lastname, t("split_checkout.step1.billing_address.last_name.label")
= bill_address.text_field :lastname, { placeholder: " " }
= f.error_message_on "bill_address.lastname"
%div.checkout-input.with-floating-label{ "data-controller": "floating-label"}
%div.checkout-input
= bill_address.label :address1, t("split_checkout.step1.address.address1.label")
= bill_address.text_field :address1, { placeholder: " " }
= bill_address.text_field :address1, { placeholder: t("split_checkout.step1.address.address1.placeholder") }
= f.error_message_on "bill_address.address1"
%div.checkout-input.with-floating-label{ "data-controller": "floating-label"}
%div.checkout-input
= bill_address.label :address2, t("split_checkout.step1.address.address2.label")
= bill_address.text_field :address2, { placeholder: " " }
= bill_address.text_field :address2, { placeholder: t("split_checkout.step1.address.address2.placeholder") }
= f.error_message_on "bill_address.address2"
%div.checkout-input.with-floating-label{ "data-controller": "floating-label"}
%div.checkout-input
= bill_address.label :city, t("split_checkout.step1.address.city.label")
= bill_address.text_field :city, { placeholder: " " }
= bill_address.text_field :city, { placeholder: t("split_checkout.step1.address.city.placeholder") }
= f.error_message_on "bill_address.city"
%div.checkout-input.with-floating-label{ "data-controller": "floating-label"}
%div.checkout-input
= bill_address.label :zipcode, t("split_checkout.step1.address.zipcode.label")
= bill_address.text_field :zipcode, { placeholder: " " }
= bill_address.text_field :zipcode, { placeholder: t("split_checkout.step1.address.zipcode.placeholder") }
= f.error_message_on "bill_address.zipcode"
%div{ "data-controller": "dependent-select", "data-dependent-select-options-value": countries_with_states }
@@ -78,30 +76,33 @@
- selected_shipping_method ||= @shipping_methods[0].id if @shipping_methods.length == 1
- @shipping_methods.each do |shipping_method|
- ship_method_is_selected = shipping_method.id == selected_shipping_method.to_i
%div.checkout-input.checkout-input-radio
= fields_for shipping_method do |shipping_method_form|
= shipping_method_form.radio_button :name, shipping_method.id,
id: "shipping_method_#{shipping_method.id}",
checked: ship_method_is_selected,
checked: (shipping_method.id == selected_shipping_method.to_i),
name: "shipping_method_id",
"data-description": shipping_method.description,
"data-requireAddress": shipping_method.require_ship_address,
"data-action": "toggle#toggle shippingmethod#selectShippingMethod",
"data-toggle-show": shipping_method.require_ship_address
= shipping_method_form.label shipping_method.id, shipping_method.name, {for: "shipping_method_" + shipping_method.id.to_s }
%em= payment_or_shipping_price(shipping_method, @order)
- display_ship_address = display_ship_address || (ship_method_is_selected && shipping_method.require_ship_address)
%div.checkout-input{"data-shippingmethod-target": "shippingMethodDescription", "data-shippingmethodid": shipping_method.id , style: "display: #{ship_method_is_selected ? 'block' : 'none'}" }
#distributor_address.panel
- if shipping_method.description.present?
%span #{shipping_method.description}
%br/
%br/
- if @order.order_cycle.pickup_time_for(@order.distributor)
= t :checkout_ready_for
= @order.order_cycle.pickup_time_for(@order.distributor)
%em.light
= payment_or_shipping_price(shipping_method, @order)
- display_ship_address = display_ship_address || (shipping_method.id == selected_shipping_method.to_i && shipping_method.require_ship_address)
- if shipping_method.id == selected_shipping_method.to_i
- ship_method_description = shipping_method.description
= f.error_message_on :shipping_method, standalone: true
%div.checkout-input{"data-shippingmethod-target": "shippingMethodDescription", style: "display: #{ship_method_description == nil ? 'none' : 'block'}" }
#distributor_address.panel
%span{"data-shippingmethod-target": "shippingMethodDescriptionContent"} #{ship_method_description}
%br/
%br/
- if @order.order_cycle.pickup_time_for(@order.distributor)
= t :checkout_ready_for
= @order.order_cycle.pickup_time_for(@order.distributor)
%div.checkout-input{ "data-toggle-target": "content", style: "display: #{display_ship_address ? 'block' : 'none'}" }
= f.check_box :ship_address_same_as_billing, { id: "ship_address_same_as_billing", name: "ship_address_same_as_billing", "data-action": "shippingmethod#showHideShippingAddress", "data-shippingmethod-target": "shippingAddressCheckbox", checked: shipping_and_billing_match?(@order) }, 1, nil
@@ -109,24 +110,24 @@
%div{"data-shippingmethod-target": "shippingMethodAddress", style: "display: #{!display_ship_address || shipping_and_billing_match?(@order) ? 'none' : 'block'}" }
= f.fields :ship_address, model: @order.ship_address do |ship_address|
%div.checkout-input.with-floating-label{ "data-controller": "floating-label"}
%div.checkout-input
= ship_address.label :address1, t("split_checkout.step1.address.address1.label")
= ship_address.text_field :address1, { placeholder: " " }
= ship_address.text_field :address1, { placeholder: t("split_checkout.step1.address.address1.placeholder") }
= f.error_message_on "ship_address.address1"
%div.checkout-input.with-floating-label{ "data-controller": "floating-label"}
%div.checkout-input
= ship_address.label :address2, t("split_checkout.step1.address.address2.label")
= ship_address.text_field :address2, { placeholder: " " }
= ship_address.text_field :address2, { placeholder: t("split_checkout.step1.address.address2.placeholder") }
= f.error_message_on "ship_address.address2"
%div.checkout-input.with-floating-label{ "data-controller": "floating-label"}
%div.checkout-input
= ship_address.label :city, t("split_checkout.step1.address.city.label")
= ship_address.text_field :city, { placeholder: " " }
= ship_address.text_field :city, { placeholder: t("split_checkout.step1.address.city.placeholder") }
= f.error_message_on "ship_address.city"
%div.checkout-input.with-floating-label{ "data-controller": "floating-label"}
%div.checkout-input
= ship_address.label :zipcode, t("split_checkout.step1.address.zipcode.label")
= ship_address.text_field :zipcode, { placeholder: " " }
= ship_address.text_field :zipcode, { placeholder: t("split_checkout.step1.address.zipcode.placeholder") }
= f.error_message_on "ship_address.zipcode"
%div{ "data-controller": "dependent-select", "data-dependent-select-options-value": countries_with_states }

View File

@@ -14,18 +14,19 @@
"data-action": "paymentmethod#selectPaymentMethod",
"data-paymentmethod-id": "paymentmethod#{payment_method.id}",
"data-paymentmethod-target": "input"
= f.label :payment_method_id, "#{payment_method.name}", for: "payment_method_#{payment_method.id}"
%em=payment_or_shipping_price(payment_method, @order)
= f.label :payment_method_id, "#{payment_method.name} (#{payment_or_shipping_price(payment_method, @order)})", for: "payment_method_#{payment_method.id}"
.paymentmethod-container{"data-paymentmethod-id": "paymentmethod#{payment_method.id}", style: "display: #{payment_method.id == selected_payment_method ? "block" : "none"}"}
= f.error_message_on :payment_method, standalone: true
- available_payment_methods.each do |payment_method|
.paymentmethod-container{id: "paymentmethod#{payment_method.id}", style: "display: #{payment_method.id == selected_payment_method ? "block" : "none"}"}
- if payment_method.description && !payment_method.description.empty?
.paymentmethod-description.panel
#{payment_method.description}
.paymentmethod-form
= render partial: "split_checkout/payment/#{payment_method.method_type}", locals: { payment_method: payment_method, f: f }
= f.error_message_on :payment_method, standalone: true
%div.checkout-substep
= t("split_checkout.step2.explaination")

View File

@@ -5,25 +5,25 @@
%div.summary
%span.summary-label
= t("split_checkout.step1.billing_address.first_name.label")
= t("split_checkout.step1.your_details.first_name.label")
%span.summary-value
= @order.bill_address.firstname
%div.summary
%span.summary-label
= t("split_checkout.step1.billing_address.last_name.label")
= t("split_checkout.step1.your_details.last_name.label")
%span.summary-value
= @order.bill_address.lastname
%div.summary
%span.summary-label
= t("split_checkout.step1.contact_information.email.label")
= t("split_checkout.step1.your_details.email.label")
%span.summary-value
= @order.user ? @order.user.email : "Change me"
%div.summary
%span.summary-label
= t("split_checkout.step1.contact_information.phone.label")
= t("split_checkout.step1.your_details.phone.label")
%span.summary-value
= @order.bill_address.phone

View File

@@ -54,6 +54,6 @@
.actions.filter-actions
%a.button.icon-search{'ng-click' => 'fetchResults()'}
= t(:filter_results)
%a.button{'ng-click' => 'clearFilters()', "id": "clear_filters_button", "class": ("secondary" if feature?(:admin_style_v2, spree_current_user)) }
%a.button{'ng-click' => 'clearFilters()', "id": "clear_filters_button"}
= t(:clear_filters)

View File

@@ -20,8 +20,7 @@
= t(".no_note_present")
%td.actions
.flex
= link_to '', '', class: 'edit-note icon_link icon-edit no-text with-tip', data: { action: 'edit' }, title: Spree.t('edit')
- if @order.note.present?
= link_to '', '', class: 'delete-note icon_link icon-trash no-text with-tip', data: { action: 'remove' }, title: Spree.t('delete')
= link_to '', '', class: 'edit-note icon_link icon-edit no-text with-tip', data: { action: 'edit' }, title: Spree.t('edit')
- if @order.note.present?
= link_to '', '', class: 'delete-note icon_link icon-trash no-text with-tip', data: { action: 'remove' }, title: Spree.t('delete')

View File

@@ -26,7 +26,7 @@
= Spree.t(:price)
%th
= Spree.t(:quantity)
%th.force-rounded-right
%th
= Spree.t(:total)
%th.orders-actions.actions{ "data-hook" => "admin_order_form_line_items_header_actions" }
@@ -57,8 +57,8 @@
%span
= shipment.fee_adjustment.display_amount
%td.actions
- if shipment.fee_adjustment.present? && shipment.can_modify?
- if shipment.fee_adjustment.present? && shipment.can_modify?
%td.actions
- if can? :update, shipment
= link_to '', '', :class => 'edit-method icon_link icon-edit no-text with-tip', :data => { :action => 'edit' }, :title => Spree.t('edit')
@@ -70,9 +70,8 @@
%td.actions
- if can?(:update, shipment) && !shipment.canceled?
.flex
= link_to '', '', :class => 'save-tracking icon_link icon-ok no-text with-tip', :data => { 'shipment-number' => shipment.number, :action => 'save' }, :title => I18n.t('actions.save')
= link_to '', '', :class => 'cancel-tracking icon_link icon-cancel no-text with-tip', :data => { :action => 'cancel' }, :title => I18n.t('actions.cancel')
= link_to '', '', :class => 'save-tracking icon_link icon-ok no-text with-tip', :data => { 'shipment-number' => shipment.number, :action => 'save' }, :title => I18n.t('actions.save')
= link_to '', '', :class => 'cancel-tracking icon_link icon-cancel no-text with-tip', :data => { :action => 'cancel' }, :title => I18n.t('actions.cancel')
%tr.show-tracking.total
%td{ :colspan => "5" }
@@ -85,7 +84,6 @@
%td.actions
- if can?(:update, shipment) && shipment.can_modify?
.flex
= link_to '', '', :class => 'edit-tracking icon_link icon-edit no-text with-tip', :data => { :action => 'edit' }, :title => Spree.t('edit')
- if shipment.tracking.present?
= link_to '', '', :class => 'delete-tracking icon_link icon-trash no-text with-tip', :data => { 'shipment-number' => shipment.number, :action => 'remove' }, :title => Spree.t('delete')
= link_to '', '', :class => 'edit-tracking icon_link icon-edit no-text with-tip', :data => { :action => 'edit' }, :title => Spree.t('edit')
- if shipment.tracking.present?
= link_to '', '', :class => 'delete-tracking icon_link icon-trash no-text with-tip', :data => { 'shipment-number' => shipment.number, :action => 'remove' }, :title => Spree.t('delete')

View File

@@ -19,11 +19,10 @@
%td.cart-item-delete.actions{ "data-hook" => "cart_item_delete" }
- if shipment.can_modify? && can?(:update, shipment)
.flex
= link_to '', '#', :class => 'save-item icon_link icon-ok no-text with-tip', :data => {'shipment-number' => shipment.number, 'variant-id' => item.variant.id, :action => 'save'}, :title => t('actions.save'), :style => 'display: none'
= link_to '', '#', :class => 'cancel-item icon_link icon-cancel no-text with-tip', :data => {:action => 'cancel'}, :title => t('actions.cancel'), :style => 'display: none'
= link_to '', '#', :class => 'edit-item icon_link icon-edit no-text with-tip', :data => {:action => 'edit'}, :title => t('actions.edit')
= link_to '', '#', :class => 'delete-item icon-trash no-text with-tip', :data => {'shipment-number' => shipment.number, 'variant-id' => item.variant.id, :action => 'remove'}, :title => t('actions.delete')
= link_to '', '#', :class => 'save-item icon_link icon-ok no-text with-tip', :data => {'shipment-number' => shipment.number, 'variant-id' => item.variant.id, :action => 'save'}, :title => t('actions.save'), :style => 'display: none'
= link_to '', '#', :class => 'cancel-item icon_link icon-cancel no-text with-tip', :data => {:action => 'cancel'}, :title => t('actions.cancel'), :style => 'display: none'
= link_to '', '#', :class => 'edit-item icon_link icon-edit no-text with-tip', :data => {:action => 'edit'}, :title => t('actions.edit')
= link_to '', '#', :class => 'delete-item icon-trash no-text with-tip', :data => {'shipment-number' => shipment.number, 'variant-id' => item.variant.id, :action => 'remove'}, :title => t('actions.delete')
= render 'spree/admin/shared/custom-alert'
= render 'spree/admin/shared/custom-confirm'

View File

@@ -12,42 +12,39 @@
= admin_inject_column_preferences module: 'admin.lineItems'
= admin_inject_available_units
%div{ ng: { controller: 'LineItemsCtrl' }, id: "table-filter" }
%fieldset
%save-bar{ dirty: "bulk_order_form.$dirty", persist: "false" }
%input.red{ type: "button", value: "Save Changes", ng: { click: "submit()", disabled: "!bulk_order_form.$dirty" } }
%legend{ align: 'center'}= t(:search)
%div{ :class => "sixteen columns alpha" }
.filter_select{ :class => "four columns" }
%label{ :for => 'supplier_filter' }
= t("admin.producer")
%br
%input#supplier_filter.ofn-select2.fullwidth{ type: 'number', 'min-search' => 5, data: 'suppliers', placeholder: "#{t(:all)}", blank: "{ id: '', name: '#{t(:all)}' }", on: { selecting: "confirmRefresh" }, ng: { model: 'supplierFilter' } }
.filter_select{ :class => "four columns" }
%label{ :for => 'distributor_filter' }
= t("admin.shop")
%br
%input#distributor_filter.ofn-select2.fullwidth{ type: 'number', 'min-search' => 5, data: 'distributors', placeholder: "#{t(:all)}", blank: "{ id: '', name: '#{t(:all)}' }", on: { selecting: "confirmRefresh" }, ng: { model: 'distributorFilter' } }
.filter_select{ :class => "four columns" }
%label{ :for => 'order_cycle_filter' }
= t("admin.order_cycle")
%br
%input#order_cycle_filter.ofn-select2.fullwidth{ type: 'number', 'min-search' => 5, data: 'orderCycles', placeholder: "#{t(:all)}", blank: "{ id: '', name: '#{t(:all)}' }", on: { selecting: "confirmRefresh" }, ng: { model: 'orderCycleFilter', change: "setOrderCycleDateRange()" } }
.date_filter{class: "four columns"}
%label
= t("date_range")
%br
%div{ data: { controller: "flatpickr", "flatpickr-mode-value": "range", "flatpickr-default-date": "{{ [startDate, endDate] }}" } }
%input.datepicker.fullwidth{ class: "datepicker", data: { "flatpickr-target": "instance" } }
%input{ type: "text", id: 'start_date_filter', 'ng-model': "startDate", data: { "flatpickr-target": "start" }, style: "display: none;" }
%input{ type: "text", id: 'end_date_filter', 'ng-model': "endDate", data: { "flatpickr-target": "end" }, style: "display: none;" }
.clearfix
.actions.filter-actions
%a.button.icon-search{'ng-click' => 'refreshData()'}
= t(:filter_results)
%a.button{'ng-click' => 'resetSelectFilters()', "id": "clear_filters_button", "class": ("secondary" if feature?(:admin_style_v2, spree_current_user)) }
= t(:clear_filters)
%div{ ng: { controller: 'LineItemsCtrl' } }
%save-bar{ dirty: "bulk_order_form.$dirty", persist: "false" }
%input.red{ type: "button", value: "Save Changes", ng: { click: "submit()", disabled: "!bulk_order_form.$dirty" } }
.filters{ :class => "sixteen columns alpha" }
.date_filter{class: "four columns"}
%label
= t("date_range")
%br
%div{ data: { controller: "flatpickr", "flatpickr-mode-value": "range", "flatpickr-default-date": "{{ [startDate, endDate] }}" } }
%input.datepicker.fullwidth{ class: "datepicker", data: { "flatpickr-target": "instance" } }
%input{ type: "text", id: 'start_date_filter', 'ng-model': "startDate", data: { "flatpickr-target": "start" }, style: "display: none;" }
%input{ type: "text", id: 'end_date_filter', 'ng-model': "endDate", data: { "flatpickr-target": "end" }, style: "display: none;" }
.one.column &nbsp;
.filter_select{ :class => "three columns" }
%label{ :for => 'supplier_filter' }
= t("admin.producer")
%br
%input#supplier_filter.ofn-select2.fullwidth{ type: 'number', 'min-search' => 5, data: 'suppliers', placeholder: "#{t(:all)}", blank: "{ id: '', name: '#{t(:all)}' }", on: { selecting: "confirmRefresh" }, ng: { model: 'supplierFilter', change: 'refreshData()' } }
.filter_select{ :class => "three columns" }
%label{ :for => 'distributor_filter' }
= t("admin.shop")
%br
%input#distributor_filter.ofn-select2.fullwidth{ type: 'number', 'min-search' => 5, data: 'distributors', placeholder: "#{t(:all)}", blank: "{ id: '', name: '#{t(:all)}' }", on: { selecting: "confirmRefresh" }, ng: { model: 'distributorFilter', change: 'refreshData()' } }
.filter_select{ :class => "three columns" }
%label{ :for => 'order_cycle_filter' }
= t("admin.order_cycle")
%br
%input#order_cycle_filter.ofn-select2.fullwidth{ type: 'number', 'min-search' => 5, data: 'orderCycles', placeholder: "#{t(:all)}", blank: "{ id: '', name: '#{t(:all)}' }", on: { selecting: "confirmRefresh" }, ng: { model: 'orderCycleFilter', change: 'refreshData()' } }
.filter_clear{ :class => "two columns omega" }
%label{ :for => 'clear_all_filters' }
%br
%input.red.fullwidth{ :type => 'button', :id => 'clear_all_filters', :value => t('admin.clear_all'), 'ng-click' => "resetSelectFilters()" }
%hr.divider.sixteen.columns.alpha.omega{ ng: { show: 'unitsVariantSelected()' } }

View File

@@ -30,15 +30,13 @@
="#{t('admin.actions')}".html_safe
%span{ 'ng-class' => "expanded && 'icon-caret-up' || !expanded && 'icon-caret-down'" }
%div.menu{ 'ng-show' => "expanded" }
%div.menu_item
%span.name{ "data-controller": "modal-link", "data-action": "click->modal-link#open", "data-modal-link-target-value": "resend_confirmation" }
= t('.resend_confirmation')
%div.menu_item
%span.name.invoices-modal{'ng-controller' => 'bulkInvoiceCtrl', 'ng-click' => 'createBulkInvoice()' }
= t('.print_invoices')
%div.menu_item
%span.name{'ng-controller' => 'bulkCancelCtrl', 'ng-click' => 'cancelSelectedOrders()' }
= t('.cancel_orders')
= render partial: 'per_page_controls', locals: { position: "right" }
@@ -65,7 +63,7 @@
%tbody
%tr{ng: {repeat: 'order in orders track by order.id', class: {even: "'even'", odd: "'odd'"}}, 'ng-class' => "{'state-{{order.state}}': true, 'row-loading': rowStatus[order.id] == 'loading'}"}
%td.align-center
%input{type: 'checkbox', 'ng-model' => 'checkboxes[order.id]', 'ng-change' => 'toggleSelection(order.id)', value: '{{order.id}}', name: 'order_ids[]'}
%input{type: 'checkbox', 'ng-model' => 'checkboxes[order.id]', 'ng-change' => 'toggleSelection(order.id)'}
%td.align-center
{{order.distributor_name}}
%td.align-center
@@ -97,17 +95,17 @@
%td.align-center
%span{'ng-bind-html' => 'order.display_total'}
%td.actions
.flex
%div.row-loading-icons
%div{ng: {show: 'rowStatus[order.id] == "loading"', cloak: true}, style: "width: 30px; height: 30px;"}
= render partial: "components/spinner"
%i.success.icon-ok-sign{ng: {show: 'rowStatus[order.id] == "success"'} }
%i.error.icon-remove-sign.with-tip{ng: {show: 'rowStatus[order.id] == "error"'}, 'ofn-with-tip' => t('.order_not_updated')}
%a.icon_link.with-tip.icon-edit.no-text{'ng-href' => '{{order.edit_path}}', 'data-action' => 'edit', 'ofn-with-tip' => t('.edit')}
%div{'ng-if' => 'order.ready_to_ship'}
%button.icon-road.icon_link.with-tip.no-text{'ng-click' => 'shipOrder(order)', rel: 'nofollow', 'ofn-with-tip' => t('.ship')}
%div{'ng-if' => 'order.ready_to_capture'}
%button.icon-capture.icon_link.no-text{'ng-click' => 'capturePayment(order)', rel: 'nofollow', 'ofn-with-tip' => t('.capture')}
%div.row-loading-icons
%div{ng: {show: 'rowStatus[order.id] == "loading"', cloak: true}, style: "width: 30px; height: 30px;"}
= render partial: "components/spinner"
%i.success.icon-ok-sign{ng: {show: 'rowStatus[order.id] == "success"'} }
%i.error.icon-remove-sign.with-tip{ng: {show: 'rowStatus[order.id] == "error"'}, 'ofn-with-tip' => t('.order_not_updated')}
%a.icon_link.with-tip.icon-edit.no-text{'ng-href' => '{{order.edit_path}}', 'data-action' => 'edit', 'ofn-with-tip' => t('.edit')}
%div{'ng-if' => 'order.ready_to_ship'}
%button.icon-road.icon_link.with-tip.no-text{'ng-click' => 'shipOrder(order)', rel: 'nofollow', 'ofn-with-tip' => t('.ship')}
%div{'ng-if' => 'order.ready_to_capture'}
%button.icon-capture.icon_link.no-text{'ng-click' => 'capturePayment(order)', rel: 'nofollow', 'ofn-with-tip' => t('.capture')}
.sixteen.columns.alpha#loading{ 'ng-show' => 'RequestMonitor.loading' }
= render partial: "components/admin_spinner"
%h1
@@ -120,7 +118,3 @@
= t('.no_orders_found')
= render 'spree/admin/shared/custom-confirm'
= render ConfirmModalComponent.new(id: "resend_confirmation", confirm_actions: "click->resend-confirmation-email#confirm", controllers: "resend-confirmation-email") do
.margin-bottom-30
= t('.resend_confirmation_confirm_html')

View File

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

View File

@@ -45,12 +45,12 @@
= f.field_container :shipping_categories do
= f.label :shipping_category_id, t(:shipping_categories)
= f.collection_select(:shipping_category_id, @shipping_categories, :id, :name, { :include_blank => t(:none) }, { :class => 'select2' })
= f.collection_select(:shipping_category_id, @shipping_categories, :id, :name, { :include_blank => 'None' }, { :class => 'select2' })
= f.error_message_on :shipping_category
= f.field_container :tax_category do
= f.label :tax_category_id, t(:tax_category)
= f.collection_select(:tax_category_id, @tax_categories, :id, :name, { :include_blank => t(:none) }, { :class => 'select2' })
= f.collection_select(:tax_category_id, @tax_categories, :id, :name, { :include_blank => 'None' }, { :class => 'select2' })
= f.error_message_on :tax_category
.clear

View File

@@ -1,5 +1,5 @@
= f.field_container :primary_taxon do
= f.label :primary_taxon, t('.product_category')
= f.label :primary_taxon_id, t('.product_category')
%span.required *
%br
= f.collection_select(:primary_taxon_id, Spree::Taxon.order(:name), :id, :name, {:include_blank => true}, {:class => "select2 fullwidth"})

Some files were not shown because too many files have changed in this diff Show More