mirror of
https://github.com/openfoodfoundation/openfoodnetwork
synced 2026-01-18 19:36:48 +00:00
Compare commits
371 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c54942d1ce | ||
|
|
fdc78b717e | ||
|
|
95821098d8 | ||
|
|
27643945ed | ||
|
|
b0048cb453 | ||
|
|
1e1ec95a5a | ||
|
|
2d86e8857a | ||
|
|
f1bb4fdf38 | ||
|
|
a6856f3d21 | ||
|
|
ca1a850d4a | ||
|
|
921e1f8083 | ||
|
|
928b4144d5 | ||
|
|
93e736fdf6 | ||
|
|
988dcc6081 | ||
|
|
d43f0331ac | ||
|
|
dcbe9dd1cb | ||
|
|
c96bba8094 | ||
|
|
45a49d01ec | ||
|
|
3b6d0a8760 | ||
|
|
5f4aed92d8 | ||
|
|
a118c786ef | ||
|
|
1572a81274 | ||
|
|
6f8dc4dd79 | ||
|
|
6879643044 | ||
|
|
2a0cd5e275 | ||
|
|
af7c11bb3e | ||
|
|
046d7d5f44 | ||
|
|
81e1bb19a3 | ||
|
|
d30d3d256d | ||
|
|
850c845992 | ||
|
|
f1b81613d6 | ||
|
|
e3914ed306 | ||
|
|
ef092dd53a | ||
|
|
5e0287d93f | ||
|
|
58fb2ebe29 | ||
|
|
0d37614e90 | ||
|
|
cb4b54c927 | ||
|
|
a9a7ad5cb3 | ||
|
|
46562aace7 | ||
|
|
a57d1863c0 | ||
|
|
fd74473bdd | ||
|
|
78c29f2c6a | ||
|
|
7a62a95e0b | ||
|
|
b8e4dcac7c | ||
|
|
944d552f6b | ||
|
|
c3e4c53a01 | ||
|
|
ae59621341 | ||
|
|
d61affd82b | ||
|
|
c0364a845d | ||
|
|
47e100607c | ||
|
|
7f17bfaa9a | ||
|
|
e9f92d39aa | ||
|
|
23462e9965 | ||
|
|
0d0c568b10 | ||
|
|
155b8ed725 | ||
|
|
11ea6b39cf | ||
|
|
18e9aba6b8 | ||
|
|
561458ce43 | ||
|
|
6f3d27b99b | ||
|
|
8e5495c34d | ||
|
|
59b727aa95 | ||
|
|
3b179e75a4 | ||
|
|
33896a73f3 | ||
|
|
d97f49d1d4 | ||
|
|
1ca305ab7c | ||
|
|
ea07795304 | ||
|
|
f8ec71d0d8 | ||
|
|
006ad6be73 | ||
|
|
d995e7630c | ||
|
|
36b3cbb300 | ||
|
|
0a5eb9386c | ||
|
|
741dc0e29c | ||
|
|
d801b6e970 | ||
|
|
7fe7a672ae | ||
|
|
fd95970774 | ||
|
|
a0056553b0 | ||
|
|
87ae8a40d1 | ||
|
|
ac494f97ec | ||
|
|
a046d0538b | ||
|
|
9cd35ddd65 | ||
|
|
ac87daab4b | ||
|
|
ea45462e50 | ||
|
|
bd0b251724 | ||
|
|
f3d82b41ad | ||
|
|
a1d939af24 | ||
|
|
eeacdb676e | ||
|
|
e971f46611 | ||
|
|
5fd78a8db5 | ||
|
|
311675f2cf | ||
|
|
97c3aaebb2 | ||
|
|
4716ed19dc | ||
|
|
cd743e5c2c | ||
|
|
21366f8109 | ||
|
|
e4bfe7fac6 | ||
|
|
a8278446b3 | ||
|
|
10bfe00123 | ||
|
|
e790fc2b7b | ||
|
|
deff728ca8 | ||
|
|
064e97d7ee | ||
|
|
3003ba0fc0 | ||
|
|
9fbdb311b0 | ||
|
|
5ed6e5599d | ||
|
|
900ff6d053 | ||
|
|
4d605c7060 | ||
|
|
ffba628691 | ||
|
|
8b416a8eda | ||
|
|
2c18e97fb8 | ||
|
|
bfd396e644 | ||
|
|
e11d644a95 | ||
|
|
26131d105e | ||
|
|
84aedaa1a1 | ||
|
|
cfa0a502f1 | ||
|
|
869a6b42d3 | ||
|
|
fdf3a0d7b7 | ||
|
|
d5c9c53d8b | ||
|
|
1f98ef5074 | ||
|
|
996d3afe4f | ||
|
|
500c4e8a2f | ||
|
|
5002870b39 | ||
|
|
d97e9ae824 | ||
|
|
f0e0bac3b5 | ||
|
|
ec31d63c58 | ||
|
|
9dfc22451e | ||
|
|
3cf01623da | ||
|
|
274ee2c6f6 | ||
|
|
940b554d23 | ||
|
|
91d1ecea2e | ||
|
|
fbf2315a93 | ||
|
|
428256a323 | ||
|
|
20218c00ab | ||
|
|
18adcb7fa4 | ||
|
|
56b94342eb | ||
|
|
758c40c646 | ||
|
|
98391b60c6 | ||
|
|
3e03208cf5 | ||
|
|
3ae5db907a | ||
|
|
27c1fe2f06 | ||
|
|
122677bc7a | ||
|
|
22d13621f4 | ||
|
|
265b4823a7 | ||
|
|
5e689fcce3 | ||
|
|
e801bb5e40 | ||
|
|
d75c62a621 | ||
|
|
1de7fb6fe8 | ||
|
|
d37cd09c84 | ||
|
|
cb24efbc4a | ||
|
|
c23d4f63ed | ||
|
|
c7197364d1 | ||
|
|
069b314ae7 | ||
|
|
7716d2734e | ||
|
|
8adcdf14a7 | ||
|
|
224daf2591 | ||
|
|
4fa88b9c18 | ||
|
|
1adb22be71 | ||
|
|
d4cfa7b368 | ||
|
|
5c5a0c98f1 | ||
|
|
8ce3c9f449 | ||
|
|
5ea7bea9b8 | ||
|
|
461d31bef1 | ||
|
|
8758a2701c | ||
|
|
2b7bccf890 | ||
|
|
7692cebbd3 | ||
|
|
1869536529 | ||
|
|
d7d29e3654 | ||
|
|
2aea5d4957 | ||
|
|
a328888fc2 | ||
|
|
c33c4860c8 | ||
|
|
efe8030786 | ||
|
|
76412bfcae | ||
|
|
69faf590b0 | ||
|
|
2f037c3965 | ||
|
|
70193539b2 | ||
|
|
8ee5050a49 | ||
|
|
2e4a8f8ae8 | ||
|
|
8ee05789ef | ||
|
|
e2a8ab16b4 | ||
|
|
6fb337b81c | ||
|
|
387a46e1db | ||
|
|
1db85e295b | ||
|
|
86c25e1d48 | ||
|
|
368cd76611 | ||
|
|
7df2ad13c6 | ||
|
|
fee99a95f8 | ||
|
|
ab70a85866 | ||
|
|
d31c2d95b3 | ||
|
|
44af982b3e | ||
|
|
2ea22248e0 | ||
|
|
9d58f16012 | ||
|
|
f2838105fe | ||
|
|
500c23ce27 | ||
|
|
fac8926396 | ||
|
|
bd4b3110c1 | ||
|
|
f7cd168f6d | ||
|
|
ccddb7ad65 | ||
|
|
07c29df801 | ||
|
|
6ac21eaab4 | ||
|
|
ff0b84c9c0 | ||
|
|
26f40dea6f | ||
|
|
188a0f5d25 | ||
|
|
454c3d6e31 | ||
|
|
f76f885ffa | ||
|
|
f654e52fbb | ||
|
|
b8400e3655 | ||
|
|
8168f122a0 | ||
|
|
7a2084e4e7 | ||
|
|
92dcf926b5 | ||
|
|
1fcc98af80 | ||
|
|
67dbf552b1 | ||
|
|
899879b88d | ||
|
|
764c8d7d27 | ||
|
|
9a57935709 | ||
|
|
5de928f452 | ||
|
|
2fa05ae248 | ||
|
|
ca795fcf6e | ||
|
|
524d68496a | ||
|
|
b117608229 | ||
|
|
1ccd580c7b | ||
|
|
bff38b2c5b | ||
|
|
eb911d9fe3 | ||
|
|
9fa46d378f | ||
|
|
2e5f0d302f | ||
|
|
3c4845813d | ||
|
|
7166fa6a25 | ||
|
|
9d36d1942e | ||
|
|
9dc3f2d69e | ||
|
|
7b07817bbf | ||
|
|
a36627927d | ||
|
|
cc80949d56 | ||
|
|
5c8bbb3a38 | ||
|
|
e8669f0178 | ||
|
|
ef31e1d063 | ||
|
|
4ef5609b0e | ||
|
|
4e70a86fa9 | ||
|
|
44f52c21db | ||
|
|
e8b8a8961e | ||
|
|
9a849bb2ee | ||
|
|
9c8525b797 | ||
|
|
a9edc0cb9c | ||
|
|
7556eee5d4 | ||
|
|
44760ce8b6 | ||
|
|
005d5cafde | ||
|
|
25ab334d73 | ||
|
|
85dc32ffcb | ||
|
|
763d922510 | ||
|
|
a8bb05c575 | ||
|
|
0ce1ccf2d6 | ||
|
|
e288c7a551 | ||
|
|
dc030182db | ||
|
|
ddd0b939fe | ||
|
|
a4b6c05d58 | ||
|
|
b3d0c0f2d5 | ||
|
|
3afd8b7189 | ||
|
|
7d51bd7b83 | ||
|
|
974842300c | ||
|
|
9474b574ad | ||
|
|
3a490a1f26 | ||
|
|
fce3c46e55 | ||
|
|
a95bef141f | ||
|
|
e20e519201 | ||
|
|
c35c003e5a | ||
|
|
e25f4b1daa | ||
|
|
7b965718aa | ||
|
|
61e7b59437 | ||
|
|
ff9544e08b | ||
|
|
029c0afaa9 | ||
|
|
33ee03388f | ||
|
|
4d49266f0f | ||
|
|
aea6f864d9 | ||
|
|
4cdb224434 | ||
|
|
8d6615aa6b | ||
|
|
b0aa7b7b55 | ||
|
|
c90d2c7f9a | ||
|
|
63c1cd7bff | ||
|
|
ee3ec15bc4 | ||
|
|
b9a43df7fe | ||
|
|
fd274447fe | ||
|
|
52a98989e0 | ||
|
|
96193a27a4 | ||
|
|
2630fde763 | ||
|
|
a501bc9687 | ||
|
|
f25fb19fe8 | ||
|
|
25ed405da7 | ||
|
|
00176b2b95 | ||
|
|
8f25718e62 | ||
|
|
98b9395c31 | ||
|
|
540c673924 | ||
|
|
4149c14e8d | ||
|
|
af43fe9a5b | ||
|
|
2a3c4d6649 | ||
|
|
c954187322 | ||
|
|
dbb7155114 | ||
|
|
27b396cbc2 | ||
|
|
1abc406156 | ||
|
|
5cab0f7f3d | ||
|
|
56ce4ab04f | ||
|
|
d351bf9a9a | ||
|
|
b447e4052c | ||
|
|
29196375f5 | ||
|
|
23570be6a1 | ||
|
|
845133d3f6 | ||
|
|
b6a7ca3047 | ||
|
|
6652b7fe5c | ||
|
|
6cb17905cd | ||
|
|
6aa449aacb | ||
|
|
c2f82202a6 | ||
|
|
6c219b31a3 | ||
|
|
b889f31c1a | ||
|
|
83c7764456 | ||
|
|
23c3573b8f | ||
|
|
7c8a3bd7c9 | ||
|
|
b66d263bc6 | ||
|
|
9afa75d91c | ||
|
|
d15708d4e3 | ||
|
|
dafbefe8e3 | ||
|
|
9d33402d9e | ||
|
|
abf8d44935 | ||
|
|
fb15b19e88 | ||
|
|
e5c6ff433c | ||
|
|
bb35629960 | ||
|
|
c4e75dc557 | ||
|
|
10d1d2066e | ||
|
|
3c53214daa | ||
|
|
40a9c36c85 | ||
|
|
48ecbaaa16 | ||
|
|
ef7684e079 | ||
|
|
04c3b8b9cf | ||
|
|
fd3a54a904 | ||
|
|
236abe485a | ||
|
|
8fb11b9151 | ||
|
|
b9471d4f01 | ||
|
|
b5da120805 | ||
|
|
da5976d85d | ||
|
|
9e1047288f | ||
|
|
ff9985dd88 | ||
|
|
e62eca93c1 | ||
|
|
8d532ae262 | ||
|
|
d2f28e2095 | ||
|
|
dbd5186dfa | ||
|
|
61cb7c4402 | ||
|
|
ccfb83d2cd | ||
|
|
d6aa0168b2 | ||
|
|
2a8ef5a45e | ||
|
|
c423641102 | ||
|
|
22559a755f | ||
|
|
e877f94e07 | ||
|
|
820fff0528 | ||
|
|
09bcc772ff | ||
|
|
49e376f9b6 | ||
|
|
85d3f5f9d7 | ||
|
|
9dd81604e9 | ||
|
|
61d3f79444 | ||
|
|
b33d382069 | ||
|
|
a8816b3788 | ||
|
|
204295dc91 | ||
|
|
4ed71f1883 | ||
|
|
92585b65c5 | ||
|
|
099f75befb | ||
|
|
2eaf79b149 | ||
|
|
60f0a58508 | ||
|
|
688020a304 | ||
|
|
4b1bd362e0 | ||
|
|
4b3465de78 | ||
|
|
b358468952 | ||
|
|
c1c0a72184 | ||
|
|
fbe6dcba7a | ||
|
|
4f265eed2a | ||
|
|
bdf132a833 | ||
|
|
262b546836 | ||
|
|
4bfd39caec | ||
|
|
58c8eae08c | ||
|
|
cb1b24695e |
412
.github/workflows/build.yml
vendored
412
.github/workflows/build.yml
vendored
@@ -8,7 +8,7 @@ on:
|
||||
pull_request:
|
||||
|
||||
env:
|
||||
DISABLE_KNAPSACK: true
|
||||
DISABLE_KNAPSACK_PRO: false
|
||||
TIMEZONE: UTC
|
||||
COVERAGE: true
|
||||
RAILS_ENV: test
|
||||
@@ -17,7 +17,7 @@ permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
rspec:
|
||||
knapsack_rspec_controllers:
|
||||
runs-on: ubuntu-20.04
|
||||
services:
|
||||
postgres:
|
||||
@@ -33,19 +33,15 @@ jobs:
|
||||
POSTGRES_USER: ofn
|
||||
POSTGRES_PASSWORD: f00d
|
||||
strategy:
|
||||
matrix:
|
||||
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
|
||||
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]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
@@ -72,7 +68,161 @@ jobs:
|
||||
bundle exec rake db:schema:load
|
||||
|
||||
- name: Run tests
|
||||
run: bundle exec rspec --profile -- ${{ matrix.specs }}
|
||||
|
||||
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
|
||||
|
||||
- name: Archive failed tests screenshots
|
||||
if: failure()
|
||||
@@ -83,7 +233,233 @@ jobs:
|
||||
retention-days: 7
|
||||
if-no-files-found: ignore
|
||||
|
||||
test-the-rest:
|
||||
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:
|
||||
runs-on: ubuntu-20.04
|
||||
services:
|
||||
postgres:
|
||||
@@ -122,12 +498,8 @@ 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 rspec --exclude-pattern "./models/**/*_spec.rb, ./controllers/**/*_spec.rb, ./serializers/**/*_spec.rb, ./lib/**/*_spec.rb, ./migrations/**/*_spec.rb, ./system/**/*_spec.rb"
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -50,7 +50,6 @@ vendor/bundle/
|
||||
coverage
|
||||
/reports/
|
||||
!/reports/README.md
|
||||
/spec/components/stories/**/*.stories.json
|
||||
|
||||
/public/packs
|
||||
/public/packs-test
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
# 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
|
||||
@@ -10,7 +12,6 @@
|
||||
babel.config.js
|
||||
postcss.config.js
|
||||
|
||||
.storybook/
|
||||
/app/assets/
|
||||
/config/
|
||||
/coverage/
|
||||
|
||||
@@ -440,6 +440,7 @@ 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'
|
||||
@@ -681,7 +682,7 @@ Rails/ActiveRecordOverride:
|
||||
# This cop supports unsafe autocorrection (--autocorrect-all).
|
||||
Rails/ApplicationController:
|
||||
Exclude:
|
||||
- 'engines/dfc_provider/app/controllers/dfc_provider/api/base_controller.rb'
|
||||
- 'engines/dfc_provider/app/controllers/dfc_provider/base_controller.rb'
|
||||
|
||||
# Offense count: 6
|
||||
# This cop supports unsafe autocorrection (--autocorrect-all).
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
module.exports = {
|
||||
stories: ['../spec/components/stories/**/*.stories.json'],
|
||||
addons: [
|
||||
'@storybook/addon-docs',
|
||||
'@storybook/addon-controls',
|
||||
],
|
||||
};
|
||||
@@ -1,2 +0,0 @@
|
||||
<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" />
|
||||
@@ -1,5 +0,0 @@
|
||||
export const parameters = {
|
||||
server: {
|
||||
url: `http://localhost:3000/rails/stories`,
|
||||
},
|
||||
};
|
||||
7
Gemfile
7
Gemfile
@@ -135,6 +135,9 @@ 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'
|
||||
@@ -150,7 +153,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', require: false
|
||||
gem 'knapsack_pro'
|
||||
gem 'letter_opener', '>= 1.4.1'
|
||||
gem 'rspec-rails', ">= 3.5.2"
|
||||
gem 'rspec-retry', require: false
|
||||
@@ -164,7 +167,6 @@ 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
|
||||
@@ -183,7 +185,6 @@ group :development do
|
||||
gem 'spring-commands-rspec'
|
||||
gem 'web-console'
|
||||
|
||||
gem "view_component_storybook"
|
||||
|
||||
gem 'rack-mini-profiler', '< 3.0.0'
|
||||
end
|
||||
|
||||
95
Gemfile.lock
95
Gemfile.lock
@@ -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.651.0)
|
||||
aws-sdk-core (3.166.0)
|
||||
aws-partitions (1.669.0)
|
||||
aws-sdk-core (3.168.2)
|
||||
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.59.0)
|
||||
aws-sdk-kms (1.60.0)
|
||||
aws-sdk-core (~> 3, >= 3.165.0)
|
||||
aws-sigv4 (~> 1.1)
|
||||
aws-sdk-s3 (1.117.1)
|
||||
aws-sdk-s3 (1.117.2)
|
||||
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.13.0)
|
||||
bootsnap (1.15.0)
|
||||
msgpack (~> 1.2)
|
||||
bugsnag (6.24.2)
|
||||
bugsnag (6.25.0)
|
||||
concurrent-ruby (~> 1.0)
|
||||
builder (3.2.4)
|
||||
bullet (7.0.3)
|
||||
bullet (7.0.4)
|
||||
activesupport (>= 3.0.0)
|
||||
uniform_notifier (~> 1.11)
|
||||
cable_ready (5.0.0.pre9)
|
||||
@@ -241,8 +241,8 @@ GEM
|
||||
debase-ruby_core_source (= 0.10.12)
|
||||
msgpack
|
||||
debase-ruby_core_source (0.10.12)
|
||||
debug (1.6.3)
|
||||
irb (>= 1.3.6)
|
||||
debug (1.7.1)
|
||||
irb (>= 1.5.0)
|
||||
reline (>= 0.3.1)
|
||||
debugger-linecache (1.2.0)
|
||||
devise (4.8.1)
|
||||
@@ -258,7 +258,7 @@ GEM
|
||||
devise-token_authenticatable (1.1.0)
|
||||
devise (>= 4.0.0, < 5.0.0)
|
||||
diff-lcs (1.5.0)
|
||||
digest (3.1.0)
|
||||
digest (3.1.1)
|
||||
docile (1.4.0)
|
||||
dotenv (2.8.1)
|
||||
dotenv-rails (2.8.1)
|
||||
@@ -349,18 +349,18 @@ GEM
|
||||
ruby-vips (>= 2.0.17, < 3)
|
||||
immigrant (0.3.6)
|
||||
activerecord (>= 3.0)
|
||||
io-console (0.5.11)
|
||||
io-console (0.6.0)
|
||||
ipaddress (0.8.3)
|
||||
irb (1.4.2)
|
||||
irb (1.6.2)
|
||||
reline (>= 0.3.0)
|
||||
jmespath (1.6.1)
|
||||
jmespath (1.6.2)
|
||||
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.2)
|
||||
json (2.6.3)
|
||||
json-jwt (1.16.0)
|
||||
activesupport (>= 4.2)
|
||||
aes_key_wrap
|
||||
@@ -374,8 +374,8 @@ GEM
|
||||
rspec (>= 2.0, < 4.0)
|
||||
jsonapi-serializer (2.2.0)
|
||||
activesupport (>= 4.2)
|
||||
jwt (2.5.0)
|
||||
knapsack (4.0.0)
|
||||
jwt (2.6.0)
|
||||
knapsack_pro (3.6.0)
|
||||
rake
|
||||
launchy (2.5.0)
|
||||
addressable (~> 2.7)
|
||||
@@ -385,7 +385,7 @@ GEM
|
||||
listen (3.7.1)
|
||||
rb-fsevent (~> 0.10, >= 0.10.3)
|
||||
rb-inotify (~> 0.9, >= 0.9.10)
|
||||
loofah (2.19.0)
|
||||
loofah (2.19.1)
|
||||
crass (~> 1.0.2)
|
||||
nokogiri (>= 1.5.9)
|
||||
mail (2.7.1)
|
||||
@@ -401,7 +401,7 @@ GEM
|
||||
rake
|
||||
mini_magick (4.11.0)
|
||||
mini_mime (1.1.2)
|
||||
mini_portile2 (2.8.0)
|
||||
mini_portile2 (2.8.1)
|
||||
mini_racer (0.4.0)
|
||||
libv8-node (~> 15.14.0.0)
|
||||
minitest (5.16.3)
|
||||
@@ -409,7 +409,7 @@ GEM
|
||||
money (~> 6.12)
|
||||
money (6.16.0)
|
||||
i18n (>= 0.6.4, <= 2)
|
||||
msgpack (1.5.4)
|
||||
msgpack (1.6.0)
|
||||
multi_json (1.15.0)
|
||||
multi_xml (0.6.0)
|
||||
net-protocol (0.1.3)
|
||||
@@ -417,7 +417,7 @@ GEM
|
||||
net-smtp (0.3.2)
|
||||
net-protocol
|
||||
nio4r (2.5.8)
|
||||
nokogiri (1.13.9)
|
||||
nokogiri (1.13.10)
|
||||
mini_portile2 (~> 2.8.0)
|
||||
racc (~> 1.4)
|
||||
oauth2 (1.4.11)
|
||||
@@ -453,7 +453,7 @@ GEM
|
||||
parallel (1.22.1)
|
||||
paranoia (2.6.1)
|
||||
activerecord (>= 5.1, < 7.1)
|
||||
parser (3.1.2.1)
|
||||
parser (3.1.3.0)
|
||||
ast (~> 2.4.1)
|
||||
paypal-sdk-core (0.3.4)
|
||||
multi_json (~> 1.0)
|
||||
@@ -467,7 +467,7 @@ GEM
|
||||
ruby-rc4
|
||||
ttfunk
|
||||
pg (1.2.3)
|
||||
power_assert (2.0.1)
|
||||
power_assert (2.0.2)
|
||||
pry (0.13.1)
|
||||
coderay (~> 1.1)
|
||||
method_source (~> 1.0)
|
||||
@@ -475,7 +475,7 @@ GEM
|
||||
puma (5.6.5)
|
||||
nio4r (~> 2.0)
|
||||
raabro (1.4.0)
|
||||
racc (1.6.0)
|
||||
racc (1.6.1)
|
||||
rack (2.2.4)
|
||||
rack-mini-profiler (2.3.4)
|
||||
rack (>= 1.2.0)
|
||||
@@ -522,8 +522,8 @@ GEM
|
||||
activesupport (>= 4.2)
|
||||
choice (~> 0.2.0)
|
||||
ruby-graphviz (~> 1.2)
|
||||
rails-html-sanitizer (1.4.3)
|
||||
loofah (~> 2.3)
|
||||
rails-html-sanitizer (1.4.4)
|
||||
loofah (~> 2.19, >= 2.19.1)
|
||||
rails-i18n (7.0.6)
|
||||
i18n (>= 0.7, < 2)
|
||||
railties (>= 6.0.0, < 8)
|
||||
@@ -545,8 +545,8 @@ GEM
|
||||
ffi (~> 1.0)
|
||||
redcarpet (3.5.1)
|
||||
redis (4.8.0)
|
||||
regexp_parser (2.6.0)
|
||||
reline (0.3.1)
|
||||
regexp_parser (2.6.1)
|
||||
reline (0.3.2)
|
||||
io-console (~> 0.5)
|
||||
request_store (1.5.0)
|
||||
rack (>= 1.4)
|
||||
@@ -589,17 +589,17 @@ GEM
|
||||
rspec-retry (0.6.2)
|
||||
rspec-core (> 3.3)
|
||||
rspec-support (3.10.3)
|
||||
rswag-api (2.7.0)
|
||||
rswag-api (2.8.0)
|
||||
railties (>= 3.1, < 7.1)
|
||||
rswag-specs (2.8.0)
|
||||
activesupport (>= 3.1, < 7.1)
|
||||
json-schema (>= 2.2, < 4.0)
|
||||
railties (>= 3.1, < 7.1)
|
||||
rspec-core (>= 2.14)
|
||||
rswag-ui (2.7.0)
|
||||
rswag-ui (2.8.0)
|
||||
actionpack (>= 3.1, < 7.1)
|
||||
railties (>= 3.1, < 7.1)
|
||||
rubocop (1.39.0)
|
||||
rubocop (1.41.1)
|
||||
json (~> 2.3)
|
||||
parallel (~> 1.10)
|
||||
parser (>= 3.1.2.1)
|
||||
@@ -609,9 +609,9 @@ GEM
|
||||
rubocop-ast (>= 1.23.0, < 2.0)
|
||||
ruby-progressbar (~> 1.7)
|
||||
unicode-display_width (>= 1.4.0, < 3.0)
|
||||
rubocop-ast (1.23.0)
|
||||
rubocop-ast (1.24.0)
|
||||
parser (>= 3.1.1.0)
|
||||
rubocop-rails (2.17.2)
|
||||
rubocop-rails (2.17.4)
|
||||
activesupport (>= 4.2.0)
|
||||
rack (>= 1.1)
|
||||
rubocop (>= 1.33.0, < 2.0)
|
||||
@@ -634,7 +634,7 @@ GEM
|
||||
tilt (>= 1.1, < 3)
|
||||
sd_notify (0.1.1)
|
||||
semantic_range (3.0.0)
|
||||
shoulda-matchers (5.2.0)
|
||||
shoulda-matchers (5.3.0)
|
||||
activesupport (>= 5.2.0)
|
||||
sidekiq (6.5.8)
|
||||
connection_pool (>= 2.2.5, < 3)
|
||||
@@ -645,12 +645,12 @@ GEM
|
||||
rufus-scheduler (~> 3.2)
|
||||
sidekiq (>= 4, < 7)
|
||||
tilt (>= 1.4.0)
|
||||
simplecov (0.21.2)
|
||||
simplecov (0.22.0)
|
||||
docile (~> 1.1)
|
||||
simplecov-html (~> 0.11)
|
||||
simplecov_json_formatter (~> 0.1)
|
||||
simplecov-html (0.12.3)
|
||||
simplecov_json_formatter (0.1.3)
|
||||
simplecov_json_formatter (0.1.4)
|
||||
spreadsheet_architect (5.0.0)
|
||||
caxlsx (>= 3.3.0, < 4)
|
||||
rodf (>= 1.0.0, < 2)
|
||||
@@ -682,19 +682,18 @@ GEM
|
||||
railties (>= 5.2)
|
||||
redis
|
||||
stringex (2.8.5)
|
||||
stripe (7.1.0)
|
||||
stripe (8.0.0)
|
||||
swd (1.3.0)
|
||||
activesupport (>= 3)
|
||||
attr_required (>= 0.0.5)
|
||||
httpclient (>= 2.4)
|
||||
temple (0.8.2)
|
||||
test-prof (1.0.11)
|
||||
test-unit (3.5.5)
|
||||
test-unit (3.5.7)
|
||||
power_assert
|
||||
thor (1.2.1)
|
||||
thread-local (1.1.0)
|
||||
tilt (2.0.11)
|
||||
timecop (0.9.5)
|
||||
timecop (0.9.6)
|
||||
timeout (0.3.0)
|
||||
ttfunk (1.7.0)
|
||||
tzinfo (2.0.5)
|
||||
@@ -711,12 +710,14 @@ GEM
|
||||
activemodel (>= 3.0.0)
|
||||
public_suffix
|
||||
vcr (6.1.0)
|
||||
view_component (2.76.0)
|
||||
activesupport (>= 5.0.0, < 8.0)
|
||||
view_component (2.80.0)
|
||||
activesupport (>= 5.2.0, < 8.0)
|
||||
concurrent-ruby (~> 1.0)
|
||||
method_source (~> 1.0)
|
||||
view_component_storybook (0.11.1)
|
||||
view_component (>= 2.36)
|
||||
view_component_reflex (3.1.14.pre9)
|
||||
rails (>= 5.2, < 8.0)
|
||||
stimulus_reflex (>= 3.5.0.pre2)
|
||||
view_component (>= 2.28.0)
|
||||
warden (1.2.9)
|
||||
rack (>= 2.0.9)
|
||||
web-console (4.2.0)
|
||||
@@ -744,7 +745,7 @@ GEM
|
||||
chronic (>= 0.6.3)
|
||||
wicked_pdf (2.6.3)
|
||||
activesupport
|
||||
wkhtmltopdf-binary (0.12.6.5)
|
||||
wkhtmltopdf-binary (0.12.6.6)
|
||||
xml-simple (1.1.8)
|
||||
xpath (3.2.0)
|
||||
nokogiri (~> 1.8)
|
||||
@@ -818,11 +819,12 @@ DEPENDENCIES
|
||||
json_spec (~> 1.1.4)
|
||||
jsonapi-serializer
|
||||
jwt (~> 2.3)
|
||||
knapsack
|
||||
knapsack_pro
|
||||
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)
|
||||
@@ -874,13 +876,12 @@ 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_storybook
|
||||
view_component_reflex (= 3.1.14.pre9)
|
||||
web!
|
||||
web-console
|
||||
webmock
|
||||
|
||||
3
Rakefile
3
Rakefile
@@ -8,6 +8,3 @@ require_relative 'config/application'
|
||||
|
||||
Openfoodnetwork::Application.load_tasks
|
||||
|
||||
if !ENV['DISABLE_KNAPSACK'] && defined?(Knapsack)
|
||||
Knapsack.load_tasks
|
||||
end
|
||||
|
||||
@@ -3,10 +3,6 @@ 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 = {}
|
||||
@@ -17,42 +13,29 @@ 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()
|
||||
|
||||
@@ -67,11 +50,6 @@ 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
|
||||
@@ -79,6 +57,14 @@ 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(
|
||||
@@ -277,5 +263,4 @@ 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.resetFilters()
|
||||
$scope.refreshData()
|
||||
$scope.resetSelectFilters()
|
||||
|
||||
@@ -38,3 +38,4 @@ angular.module('admin.orderCycles')
|
||||
$scope.removeCoordinatorFee = ($event, index) ->
|
||||
$event.preventDefault()
|
||||
OrderCycle.removeCoordinatorFee(index)
|
||||
$scope.order_cycle_form.$dirty = true
|
||||
|
||||
@@ -21,8 +21,14 @@ 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 && $scope.product.variant_unit_scale
|
||||
$scope.product.variant_unit_with_scale = VariantUnitManager.getUnitWithScale($scope.product.variant_unit, parseFloat($scope.product.variant_unit_scale))
|
||||
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
|
||||
$scope.product.variant_unit = $scope.product.variant_unit_scale = null
|
||||
|
||||
|
||||
15
app/components/pagination_component.rb
Normal file
15
app/components/pagination_component.rb
Normal file
@@ -0,0 +1,15 @@
|
||||
# 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
|
||||
@@ -0,0 +1,16 @@
|
||||
= 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"
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
30
app/components/product_component.rb
Normal file
30
app/components/product_component.rb
Normal file
@@ -0,0 +1,30 @@
|
||||
# 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
|
||||
@@ -0,0 +1,6 @@
|
||||
%tr
|
||||
- @columns.each do |column|
|
||||
%td.products_column{class: column[:id]}
|
||||
- if column[:id] == "name" && @image
|
||||
= image_tag @image.url(:mini)
|
||||
= column[:value]
|
||||
152
app/components/products_table_component.rb
Normal file
152
app/components/products_table_component.rb
Normal file
@@ -0,0 +1,152 @@
|
||||
# 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
|
||||
@@ -0,0 +1,21 @@
|
||||
= 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)))
|
||||
@@ -0,0 +1,47 @@
|
||||
.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");
|
||||
}
|
||||
}
|
||||
}
|
||||
9
app/components/search_input_component.rb
Normal file
9
app/components/search_input_component.rb
Normal file
@@ -0,0 +1,9 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class SearchInputComponent < ViewComponentReflex::Component
|
||||
def initialize(value: nil, data: {})
|
||||
super
|
||||
@value = value
|
||||
@data = data
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,5 @@
|
||||
= 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
|
||||
@@ -0,0 +1,23 @@
|
||||
.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;
|
||||
}
|
||||
|
||||
}
|
||||
17
app/components/selector_component.rb
Normal file
17
app/components/selector_component.rb
Normal file
@@ -0,0 +1,17 @@
|
||||
# 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
|
||||
@@ -0,0 +1,11 @@
|
||||
= 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]
|
||||
86
app/components/selector_component/selector_component.scss
Normal file
86
app/components/selector_component/selector_component.scss
Normal file
@@ -0,0 +1,86 @@
|
||||
.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
app/components/selector_with_filter_component.rb
Normal file
11
app/components/selector_with_filter_component.rb
Normal file
@@ -0,0 +1,11 @@
|
||||
# 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
|
||||
@@ -0,0 +1,22 @@
|
||||
= 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]
|
||||
@@ -0,0 +1,51 @@
|
||||
|
||||
.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%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
10
app/components/table_header_component.rb
Normal file
10
app/components/table_header_component.rb
Normal file
@@ -0,0 +1,10 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class TableHeaderComponent < ViewComponentReflex::Component
|
||||
def initialize(columns:, sort:, data: {})
|
||||
super
|
||||
@columns = columns
|
||||
@sort = sort
|
||||
@data = data
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,7 @@
|
||||
|
||||
= 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]
|
||||
@@ -0,0 +1,23 @@
|
||||
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: "⇩";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -116,21 +116,7 @@ module Admin
|
||||
|
||||
# Fetches tags for all customers of the enterprise and returns a hash indexed by customer_id
|
||||
def customer_tags_by_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
|
||||
BatchTaggableTagsQuery.call(Customer.of(managed_enterprise_id))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
7
app/controllers/admin/products_controller.rb
Normal file
7
app/controllers/admin/products_controller.rb
Normal file
@@ -0,0 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Admin
|
||||
class ProductsController < Spree::Admin::BaseController
|
||||
def index; end
|
||||
end
|
||||
end
|
||||
@@ -48,15 +48,7 @@ module Admin
|
||||
else
|
||||
I18n.t(:name, scope: [:admin, :reports, @report_type])
|
||||
end
|
||||
|
||||
# Initialize data
|
||||
params[:display_summary_row] = true if request.get?
|
||||
@params_fields_to_show = if request.get?
|
||||
@report.columns.keys - @report.fields_to_hide
|
||||
else
|
||||
params[:fields_to_show]
|
||||
end
|
||||
|
||||
@rendering_options = rendering_options
|
||||
@data = Reporting::FrontendData.new(spree_current_user)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -162,25 +162,20 @@ 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 = ::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
|
||||
@payment_method_tags_by_id ||= BatchTaggableTagsQuery.call(
|
||||
Spree::PaymentMethod.from(managed_enterprise_id)
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module OrderStockCheck
|
||||
include CablecarResponses
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
def valid_order_line_items?
|
||||
@@ -29,6 +30,9 @@ 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
|
||||
|
||||
@@ -62,4 +62,37 @@ module ReportsActions
|
||||
def i18n_scope
|
||||
'admin.reports'
|
||||
end
|
||||
|
||||
def rendering_options
|
||||
@rendering_options ||= ReportRenderingOptions.where(
|
||||
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
|
||||
}
|
||||
end
|
||||
update_rendering_options
|
||||
@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
|
||||
|
||||
@@ -31,6 +31,7 @@ class SplitCheckoutController < ::BaseController
|
||||
if confirm_order || update_order
|
||||
return if performed?
|
||||
|
||||
check_payments_adjustments
|
||||
clear_invalid_payments
|
||||
advance_order_state
|
||||
redirect_to_step
|
||||
@@ -53,6 +54,10 @@ 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
|
||||
@@ -65,6 +70,7 @@ class SplitCheckoutController < ::BaseController
|
||||
|
||||
return true if redirect_to_payment_gateway
|
||||
|
||||
@order.process_payments!
|
||||
@order.confirm!
|
||||
order_completion_reset @order
|
||||
end
|
||||
|
||||
@@ -34,6 +34,7 @@ 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
|
||||
|
||||
|
||||
@@ -6,15 +6,16 @@ class Enterprise < ApplicationRecord
|
||||
# The next Rails version will have named variants but we need to store them
|
||||
# ourselves for now.
|
||||
LOGO_SIZES = {
|
||||
thumb: { resize_to_limit: [100, 100] },
|
||||
small: { resize_to_limit: [180, 180] },
|
||||
medium: { resize_to_limit: [300, 300] },
|
||||
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' },
|
||||
}.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
|
||||
@@ -99,6 +100,7 @@ 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
|
||||
@@ -456,7 +458,7 @@ class Enterprise < ApplicationRecord
|
||||
end
|
||||
|
||||
def correct_instagram_url(url)
|
||||
url && strip_url(url).sub(%r{www.instagram.com/}, '').delete("@")
|
||||
url && strip_url(url.downcase).sub(%r{www.instagram.com/}, '').sub(%r{instagram.com/}, '').delete("@")
|
||||
end
|
||||
|
||||
def correct_twitter_url(url)
|
||||
|
||||
@@ -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_not_updatable(entry, "variant_unit_name")
|
||||
mark_as_values_must_be_same(entry, "variant_unit_name")
|
||||
end
|
||||
|
||||
def producer_validation(entry)
|
||||
@@ -425,6 +425,11 @@ 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
|
||||
|
||||
6
app/models/report_rendering_options.rb
Normal file
6
app/models/report_rendering_options.rb
Normal file
@@ -0,0 +1,6 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class ReportRenderingOptions < ApplicationRecord
|
||||
belongs_to :user, class_name: "Spree::User"
|
||||
serialize :options, Hash
|
||||
end
|
||||
@@ -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
|
||||
|
||||
20
app/queries/batch_taggable_tags_query.rb
Normal file
20
app/queries/batch_taggable_tags_query.rb
Normal file
@@ -0,0 +1,20 @@
|
||||
# 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
|
||||
38
app/reflexes/example_reflex.rb
Normal file
38
app/reflexes/example_reflex.rb
Normal file
@@ -0,0 +1,38 @@
|
||||
# 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
|
||||
@@ -42,12 +42,18 @@ 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
|
||||
object.tag_list.join(",")
|
||||
preloaded_tag_list.join(",")
|
||||
end
|
||||
|
||||
def tags
|
||||
object.tag_list.map{ |t| { text: t } }
|
||||
preloaded_tag_list.map { |tag| { text: tag } }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -34,7 +34,8 @@ module Api
|
||||
|
||||
ActiveModel::ArraySerializer.
|
||||
new(scoped_exchanges, each_serializer: Api::Admin::ExchangeSerializer,
|
||||
current_user: options[:current_user])
|
||||
current_user: options[:current_user],
|
||||
preloaded_tags: BatchTaggableTagsQuery.call(scoped_exchanges))
|
||||
end
|
||||
|
||||
def editable_variants_for_incoming_exchanges
|
||||
|
||||
@@ -30,8 +30,14 @@ class OrderAvailablePaymentMethods
|
||||
distributor.payment_methods
|
||||
else
|
||||
distributor.payment_methods.where(
|
||||
id: order_cycle.distributor_payment_methods.select(:payment_method_id)
|
||||
id: available_distributor_payment_methods_ids
|
||||
)
|
||||
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
|
||||
|
||||
@@ -30,8 +30,14 @@ class OrderAvailableShippingMethods
|
||||
distributor.shipping_methods
|
||||
else
|
||||
distributor.shipping_methods.where(
|
||||
id: order_cycle.distributor_shipping_methods.select(:shipping_method_id)
|
||||
id: available_distributor_shipping_methods_ids
|
||||
)
|
||||
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
|
||||
|
||||
@@ -102,7 +102,6 @@
|
||||
= 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
|
||||
|
||||
@@ -41,7 +41,6 @@
|
||||
= 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) }
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
%br
|
||||
100 x 100 pixels
|
||||
.omega.eight.columns
|
||||
%img{ class: 'image-field-group__preview-image', ng: { src: '{{ Enterprise.logo.medium }}', if: 'Enterprise.logo' } }
|
||||
%img{ class: 'image-field-group__preview-image', ng: { src: '{{ Enterprise.logo.thumb }}', if: 'Enterprise.logo' } }
|
||||
%br
|
||||
= f.file_field :logo
|
||||
%a.button.red{ href: '', ng: {click: 'removeLogo()', if: 'Enterprise.logo'} }
|
||||
= t('.remove_logo')
|
||||
|
||||
8
app/views/admin/products/index.html.haml
Normal file
8
app/views/admin/products/index.html.haml
Normal file
@@ -0,0 +1,8 @@
|
||||
|
||||
- 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))
|
||||
@@ -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 }
|
||||
= 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" }
|
||||
%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 }
|
||||
= 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" }
|
||||
|
||||
@@ -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, params[:display_header_row]
|
||||
= check_box_tag :display_header_row, true, @rendering_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, params[:display_summary_row], { "data-csv-select-target": "checkbox" }
|
||||
= check_box_tag :display_summary_row, true, @rendering_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: @params_fields_to_show)
|
||||
= render MultipleCheckedSelectComponent.new(name: "fields_to_show", options: @report.available_headers, selected: @rendering_options.options[:fields_to_show])
|
||||
|
||||
@@ -1 +1,5 @@
|
||||
= 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})
|
||||
|
||||
@@ -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}}", loading: "lazy"}
|
||||
%img{"ng-src" => "{{::product.primaryImageOrMissing}}"}
|
||||
|
||||
.summary
|
||||
.summary-header
|
||||
|
||||
@@ -3,51 +3,53 @@
|
||||
%div.checkout-substep
|
||||
-# YOUR DETAILS
|
||||
%div.checkout-title
|
||||
= t("split_checkout.step1.your_details.title")
|
||||
= t("split_checkout.step1.contact_information.title")
|
||||
|
||||
%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"
|
||||
.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 :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-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-substep
|
||||
-# BILLING ADDRESS
|
||||
%div.checkout-title
|
||||
= t("split_checkout.step1.billing_address.title")
|
||||
|
||||
%div.checkout-input
|
||||
.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"}
|
||||
= bill_address.label :address1, t("split_checkout.step1.address.address1.label")
|
||||
= bill_address.text_field :address1, { placeholder: t("split_checkout.step1.address.address1.placeholder") }
|
||||
= bill_address.text_field :address1, { placeholder: " " }
|
||||
= f.error_message_on "bill_address.address1"
|
||||
|
||||
%div.checkout-input
|
||||
%div.checkout-input.with-floating-label{ "data-controller": "floating-label"}
|
||||
= bill_address.label :address2, t("split_checkout.step1.address.address2.label")
|
||||
= bill_address.text_field :address2, { placeholder: t("split_checkout.step1.address.address2.placeholder") }
|
||||
= bill_address.text_field :address2, { placeholder: " " }
|
||||
= f.error_message_on "bill_address.address2"
|
||||
|
||||
%div.checkout-input
|
||||
%div.checkout-input.with-floating-label{ "data-controller": "floating-label"}
|
||||
= bill_address.label :city, t("split_checkout.step1.address.city.label")
|
||||
= bill_address.text_field :city, { placeholder: t("split_checkout.step1.address.city.placeholder") }
|
||||
= bill_address.text_field :city, { placeholder: " " }
|
||||
= f.error_message_on "bill_address.city"
|
||||
|
||||
%div.checkout-input
|
||||
%div.checkout-input.with-floating-label{ "data-controller": "floating-label"}
|
||||
= bill_address.label :zipcode, t("split_checkout.step1.address.zipcode.label")
|
||||
= bill_address.text_field :zipcode, { placeholder: t("split_checkout.step1.address.zipcode.placeholder") }
|
||||
= bill_address.text_field :zipcode, { placeholder: " " }
|
||||
= f.error_message_on "bill_address.zipcode"
|
||||
|
||||
%div{ "data-controller": "dependent-select", "data-dependent-select-options-value": countries_with_states }
|
||||
@@ -76,33 +78,30 @@
|
||||
|
||||
- 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: (shipping_method.id == selected_shipping_method.to_i),
|
||||
checked: ship_method_is_selected,
|
||||
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.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
|
||||
|
||||
%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)
|
||||
|
||||
= 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
|
||||
@@ -110,24 +109,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
|
||||
%div.checkout-input.with-floating-label{ "data-controller": "floating-label"}
|
||||
= ship_address.label :address1, t("split_checkout.step1.address.address1.label")
|
||||
= ship_address.text_field :address1, { placeholder: t("split_checkout.step1.address.address1.placeholder") }
|
||||
= ship_address.text_field :address1, { placeholder: " " }
|
||||
= f.error_message_on "ship_address.address1"
|
||||
|
||||
%div.checkout-input
|
||||
%div.checkout-input.with-floating-label{ "data-controller": "floating-label"}
|
||||
= ship_address.label :address2, t("split_checkout.step1.address.address2.label")
|
||||
= ship_address.text_field :address2, { placeholder: t("split_checkout.step1.address.address2.placeholder") }
|
||||
= ship_address.text_field :address2, { placeholder: " " }
|
||||
= f.error_message_on "ship_address.address2"
|
||||
|
||||
%div.checkout-input
|
||||
%div.checkout-input.with-floating-label{ "data-controller": "floating-label"}
|
||||
= ship_address.label :city, t("split_checkout.step1.address.city.label")
|
||||
= ship_address.text_field :city, { placeholder: t("split_checkout.step1.address.city.placeholder") }
|
||||
= ship_address.text_field :city, { placeholder: " " }
|
||||
= f.error_message_on "ship_address.city"
|
||||
|
||||
%div.checkout-input
|
||||
%div.checkout-input.with-floating-label{ "data-controller": "floating-label"}
|
||||
= ship_address.label :zipcode, t("split_checkout.step1.address.zipcode.label")
|
||||
= ship_address.text_field :zipcode, { placeholder: t("split_checkout.step1.address.zipcode.placeholder") }
|
||||
= ship_address.text_field :zipcode, { placeholder: " " }
|
||||
= f.error_message_on "ship_address.zipcode"
|
||||
|
||||
%div{ "data-controller": "dependent-select", "data-dependent-select-options-value": countries_with_states }
|
||||
|
||||
@@ -14,19 +14,18 @@
|
||||
"data-action": "paymentmethod#selectPaymentMethod",
|
||||
"data-paymentmethod-id": "paymentmethod#{payment_method.id}",
|
||||
"data-paymentmethod-target": "input"
|
||||
= f.label :payment_method_id, "#{payment_method.name} (#{payment_or_shipping_price(payment_method, @order)})", for: "payment_method_#{payment_method.id}"
|
||||
= f.label :payment_method_id, "#{payment_method.name}", for: "payment_method_#{payment_method.id}"
|
||||
%em=payment_or_shipping_price(payment_method, @order)
|
||||
|
||||
= 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"}"}
|
||||
.paymentmethod-container{"data-paymentmethod-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")
|
||||
|
||||
|
||||
@@ -5,25 +5,25 @@
|
||||
|
||||
%div.summary
|
||||
%span.summary-label
|
||||
= t("split_checkout.step1.your_details.first_name.label")
|
||||
= t("split_checkout.step1.billing_address.first_name.label")
|
||||
%span.summary-value
|
||||
= @order.bill_address.firstname
|
||||
|
||||
%div.summary
|
||||
%span.summary-label
|
||||
= t("split_checkout.step1.your_details.last_name.label")
|
||||
= t("split_checkout.step1.billing_address.last_name.label")
|
||||
%span.summary-value
|
||||
= @order.bill_address.lastname
|
||||
|
||||
%div.summary
|
||||
%span.summary-label
|
||||
= t("split_checkout.step1.your_details.email.label")
|
||||
= t("split_checkout.step1.contact_information.email.label")
|
||||
%span.summary-value
|
||||
= @order.user ? @order.user.email : "Change me"
|
||||
|
||||
%div.summary
|
||||
%span.summary-label
|
||||
= t("split_checkout.step1.your_details.phone.label")
|
||||
= t("split_checkout.step1.contact_information.phone.label")
|
||||
%span.summary-value
|
||||
= @order.bill_address.phone
|
||||
|
||||
|
||||
@@ -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"}
|
||||
%a.button{'ng-click' => 'clearFilters()', "id": "clear_filters_button", "class": ("secondary" if feature?(:admin_style_v2, spree_current_user)) }
|
||||
= t(:clear_filters)
|
||||
|
||||
|
||||
@@ -12,39 +12,42 @@
|
||||
= admin_inject_column_preferences module: 'admin.lineItems'
|
||||
= admin_inject_available_units
|
||||
|
||||
%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
|
||||
.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()" }
|
||||
%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)
|
||||
|
||||
%hr.divider.sixteen.columns.alpha.omega{ ng: { show: 'unitsVariantSelected()' } }
|
||||
|
||||
|
||||
@@ -95,17 +95,17 @@
|
||||
%td.align-center
|
||||
%span{'ng-bind-html' => 'order.display_total'}
|
||||
%td.actions
|
||||
%div.row-loading-icons
|
||||
%div{ng: {show: 'rowStatus[order.id] == "loading"', cloak: true}, style: "width: 30px; height: 30px;"}
|
||||
= render partial: "components/spinner"
|
||||
%i.success.icon-ok-sign{ng: {show: 'rowStatus[order.id] == "success"'} }
|
||||
%i.error.icon-remove-sign.with-tip{ng: {show: 'rowStatus[order.id] == "error"'}, 'ofn-with-tip' => t('.order_not_updated')}
|
||||
%a.icon_link.with-tip.icon-edit.no-text{'ng-href' => '{{order.edit_path}}', 'data-action' => 'edit', 'ofn-with-tip' => t('.edit')}
|
||||
%div{'ng-if' => 'order.ready_to_ship'}
|
||||
%button.icon-road.icon_link.with-tip.no-text{'ng-click' => 'shipOrder(order)', rel: 'nofollow', 'ofn-with-tip' => t('.ship')}
|
||||
%div{'ng-if' => 'order.ready_to_capture'}
|
||||
%button.icon-capture.icon_link.no-text{'ng-click' => 'capturePayment(order)', rel: 'nofollow', 'ofn-with-tip' => t('.capture')}
|
||||
|
||||
.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')}
|
||||
.sixteen.columns.alpha#loading{ 'ng-show' => 'RequestMonitor.loading' }
|
||||
= render partial: "components/admin_spinner"
|
||||
%h1
|
||||
|
||||
@@ -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 => 'None' }, { :class => 'select2' })
|
||||
= f.collection_select(:shipping_category_id, @shipping_categories, :id, :name, { :include_blank => t(: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 => 'None' }, { :class => 'select2' })
|
||||
= f.collection_select(:tax_category_id, @tax_categories, :id, :name, { :include_blank => t(:none) }, { :class => 'select2' })
|
||||
= f.error_message_on :tax_category
|
||||
|
||||
.clear
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
= f.field_container :primary_taxon do
|
||||
= f.label :primary_taxon_id, t('.product_category')
|
||||
= f.label :primary_taxon, t('.product_category')
|
||||
%span.required *
|
||||
%br
|
||||
= f.collection_select(:primary_taxon_id, Spree::Taxon.order(:name), :id, :name, {:include_blank => true}, {:class => "select2 fullwidth"})
|
||||
|
||||
@@ -9,8 +9,9 @@
|
||||
.sixteen.columns.alpha
|
||||
.eight.columns.alpha
|
||||
= f.field_container :supplier do
|
||||
= f.label :supplier_id, t(".supplier")
|
||||
= f.select :supplier_id, options_from_collection_for_select(@producers, :id, :name, @product.supplier_id), {}, { "data-controller": "tom-select", class: "primary" }
|
||||
= f.label :supplier, t(".supplier")
|
||||
%span.required *
|
||||
= f.select :supplier_id, options_from_collection_for_select(@producers, :id, :name, @product.supplier_id), { include_blank: t("spree.admin.products.new.supplier_select_placeholder") }, { "data-controller": "tom-select", class: "primary" }
|
||||
= f.error_message_on :supplier
|
||||
.eight.columns.omega
|
||||
= f.field_container :name do
|
||||
@@ -22,7 +23,7 @@
|
||||
.sixteen.columns.alpha
|
||||
.eight.columns.alpha
|
||||
= f.field_container :variant_unit do
|
||||
= f.label :variant_unit_with_scale, t(".units")
|
||||
= f.label :variant_unit, t(".units")
|
||||
%span.required *
|
||||
%select{id: 'product_variant_unit_with_scale', 'ng-model' => 'product.variant_unit_with_scale', 'ng-options' => 'unit[1] as unit[0] for unit in variant_unit_options', "data-controller": "tom-select","data-tom-select-options-value": '{"allowEmptyOption":false}', class: "primary"}
|
||||
%option{'value' => '', 'ng-hide' => "hasUnit(product)"}
|
||||
@@ -31,9 +32,9 @@
|
||||
= f.error_message_on :variant_unit
|
||||
.two.columns
|
||||
= f.field_container :unit_value do
|
||||
= f.label :unit_value_with_description, t(".value"), 'ng-disabled' => "!hasUnit(product)"
|
||||
= f.label :unit_value, t(".value"), 'ng-disabled' => "!hasUnit(product)"
|
||||
%span.required *
|
||||
%input.fullwidth{ id: 'product_unit_value_with_description', 'ng-model' => 'product.master.unit_value_with_description', :type => 'text', placeholder: "eg. 2", 'ng-disabled' => "!hasUnit(product)" }
|
||||
%input.fullwidth{ id: 'product_unit_value', 'ng-model' => 'product.master.unit_value_with_description', :type => 'text', placeholder: "eg. 2", 'ng-disabled' => "!hasUnit(product)" }
|
||||
%input{ type: 'hidden', 'ng-value': 'product.master.unit_value', "ng-init": "product.master.unit_value='#{@product.master.unit_value}'", name: 'product[unit_value]' }
|
||||
%input{ type: 'hidden', 'ng-value': 'product.master.unit_description', "ng-init": "product.master.unit_description='#{@product.master.unit_description}'", name: 'product[unit_description]' }
|
||||
= f.error_message_on :unit_value
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
= csrf_meta_tags
|
||||
= action_cable_meta_tag
|
||||
|
||||
= action_cable_meta_tag
|
||||
|
||||
%title
|
||||
- if content_for? :html_title
|
||||
= yield :html_title
|
||||
@@ -10,7 +12,7 @@
|
||||
= t(controller.controller_name, :default => controller.controller_name.titleize)
|
||||
= " - OFN #{t(:administration)}"
|
||||
|
||||
%link{:href => "https://fonts.googleapis.com/css?family=Open+Sans:400italic,600italic,400,600&subset=latin,cyrillic,greek,vietnamese", :rel => "stylesheet", :type => "text/css"}
|
||||
%link{:href => "https://fonts.googleapis.com/css?family=Open+Sans:400italic,600italic,400,600,700&subset=latin,cyrillic,greek,vietnamese", :rel => "stylesheet", :type => "text/css"}
|
||||
|
||||
= stylesheet_pack_tag 'admin-styles', media: "screen, print"
|
||||
= render "layouts/bugsnag_js"
|
||||
|
||||
@@ -4,3 +4,5 @@
|
||||
= tab :properties
|
||||
= tab :variant_overrides, url: main_app.admin_inventory_path, match_path: '/inventory'
|
||||
= tab :import, url: main_app.admin_product_import_path, match_path: '/product_import'
|
||||
- if feature?(:new_products_page, spree_current_user)
|
||||
= tab :new_products, url: main_app.admin_new_products_path, match_path: '/new_products'
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
%head{"data-hook" => "admin_inside_head"}
|
||||
= render :partial => 'spree/admin/shared/head'
|
||||
|
||||
%body.admin
|
||||
%body.admin{ "class": ("admin-v2" if feature?(:admin_style_v2, spree_current_user)) }
|
||||
- if content_for?(:main_ng_app_name)
|
||||
- if content_for?(:main_ng_ctrl_name)
|
||||
%div{ "ng-app" => yield(:main_ng_app_name).strip.html_safe, "ng-controller" => yield(:main_ng_ctrl_name).strip.html_safe }
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
.sub-header.show-for-medium-down
|
||||
= render partial: "shopping_shared/order_cycles"
|
||||
|
||||
%fieldset.footer-pad
|
||||
#cart-container
|
||||
- if @order.line_items.empty?
|
||||
%div.row{"data-hook" => "empty_cart"}
|
||||
%p= t(:your_cart_is_empty)
|
||||
|
||||
@@ -1,21 +1,31 @@
|
||||
import { Controller } from "stimulus";
|
||||
|
||||
export default class extends Controller {
|
||||
static targets = ["reportType", "checkbox", "label"]
|
||||
static targets = ["reportType", "checkbox", "label"];
|
||||
|
||||
handleSelectChange() {
|
||||
this.reportTypeTarget.value == "csv" ? this.disableField() : this.enableField()
|
||||
this.reportTypeTarget.value == "csv"
|
||||
? this.disableField()
|
||||
: this.enableField();
|
||||
}
|
||||
|
||||
disableField() {
|
||||
this.checkboxTarget.checked = false;
|
||||
this.checkboxTarget.disabled = true;
|
||||
this.labelTarget.classList.add("disabled");
|
||||
if (this.hasCheckboxTarget) {
|
||||
this.checkboxTarget.checked = false;
|
||||
this.checkboxTarget.disabled = true;
|
||||
}
|
||||
if (this.hasLabelTarget) {
|
||||
this.labelTarget.classList.add("disabled");
|
||||
}
|
||||
}
|
||||
|
||||
enableField() {
|
||||
this.checkboxTarget.checked = true;
|
||||
this.checkboxTarget.disabled = false;
|
||||
this.labelTarget.classList.remove("disabled");
|
||||
if (this.hasCheckboxTarget) {
|
||||
this.checkboxTarget.checked = true;
|
||||
this.checkboxTarget.disabled = false;
|
||||
}
|
||||
if (this.hasLabelTarget) {
|
||||
this.labelTarget.classList.remove("disabled");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,10 @@ import ShortcutButtonsPlugin from "shortcut-buttons-flatpickr";
|
||||
import labelPlugin from "flatpickr/dist/plugins/labelPlugin/labelPlugin";
|
||||
|
||||
export default class extends Flatpickr {
|
||||
static values = { enableTime: Boolean, mode: String };
|
||||
/*
|
||||
* defaultDate (optional): "startOfDay" | "endOfDay"
|
||||
*/
|
||||
static values = { enableTime: Boolean, mode: String, defaultDate: String };
|
||||
static targets = ["start", "end"];
|
||||
locales = {
|
||||
ar: ar,
|
||||
@@ -60,6 +63,9 @@ export default class extends Flatpickr {
|
||||
|
||||
open() {
|
||||
this.fp.element.dispatchEvent(new Event("focus"));
|
||||
if (!this.fp.selectedDates.length) {
|
||||
this.setDefaultDateValue();
|
||||
}
|
||||
}
|
||||
onChangeEvent(e) {
|
||||
if (
|
||||
@@ -136,4 +142,16 @@ export default class extends Flatpickr {
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
setDefaultDateValue() {
|
||||
if (this.defaultDateValue === "startOfDay") {
|
||||
this.fp.setDate(moment().startOf("day").format());
|
||||
} else if (this.defaultDateValue === "endOfDay") {
|
||||
/*
|
||||
* We use "startOf('day')" of tomorrow in order to not lose
|
||||
* the records between [23:59:00 ~ 23:59:59] of today
|
||||
*/
|
||||
this.fp.setDate(moment().add(1, "days").startOf("day").format());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
30
app/webpacker/controllers/floating_label_controller.js
Normal file
30
app/webpacker/controllers/floating_label_controller.js
Normal file
@@ -0,0 +1,30 @@
|
||||
import { Controller } from "stimulus";
|
||||
|
||||
export default class extends Controller {
|
||||
connect() {
|
||||
const input = this.element.querySelector("input");
|
||||
input.addEventListener("focus", this.focus.bind(this));
|
||||
input.addEventListener("blur", this.blur.bind(this));
|
||||
if (input.value.length > 0) {
|
||||
this.focus();
|
||||
}
|
||||
|
||||
const label = this.element.querySelector("label");
|
||||
// Add transition class to the label and display the label
|
||||
// after a short delay to avoid flickering
|
||||
setTimeout(() => {
|
||||
label.classList.add("with-transition");
|
||||
label.style.display = "block";
|
||||
}, 100);
|
||||
}
|
||||
|
||||
focus() {
|
||||
this.element.classList.add("active");
|
||||
}
|
||||
|
||||
blur(e) {
|
||||
if (e.target.value.length === 0) {
|
||||
this.element.classList.remove("active");
|
||||
}
|
||||
}
|
||||
}
|
||||
16
app/webpacker/controllers/index.js
Normal file
16
app/webpacker/controllers/index.js
Normal file
@@ -0,0 +1,16 @@
|
||||
// Load all the controllers within this directory and all subdirectories.
|
||||
// Controller files must be named *_controller.js.
|
||||
import { Application } from "stimulus";
|
||||
import { definitionsFromContext } from "stimulus/webpack-helpers";
|
||||
import StimulusReflex from "stimulus_reflex";
|
||||
import consumer from "../channels/consumer";
|
||||
import controller from "../controllers/application_controller";
|
||||
import CableReady from "cable_ready";
|
||||
|
||||
const application = Application.start();
|
||||
const context = require.context("controllers", true, /_controller\.js$/);
|
||||
application.load(definitionsFromContext(context));
|
||||
application.consumer = consumer;
|
||||
StimulusReflex.initialize(application, { controller, isolate: true });
|
||||
StimulusReflex.debug = process.env.RAILS_ENV === "development";
|
||||
CableReady.initialize({ consumer });
|
||||
@@ -16,7 +16,9 @@ export default class extends Controller {
|
||||
const stripeCardSelector =
|
||||
this.application.getControllerForElementAndIdentifier(
|
||||
document
|
||||
.getElementById(event.target.dataset.paymentmethodId)
|
||||
.querySelector(
|
||||
`[data-paymentmethod-id="${event.target.dataset.paymentmethodId}"]`
|
||||
)
|
||||
.querySelector('[data-controller="stripe-cards"]'),
|
||||
"stripe-cards"
|
||||
);
|
||||
@@ -27,7 +29,8 @@ export default class extends Controller {
|
||||
Array.from(
|
||||
document.getElementsByClassName("paymentmethod-container")
|
||||
).forEach((container) => {
|
||||
const enabled = container.id === paymentMethodContainerId;
|
||||
const enabled =
|
||||
container.dataset.paymentmethodId === paymentMethodContainerId;
|
||||
|
||||
if (enabled) {
|
||||
container.style.display = "block";
|
||||
|
||||
46
app/webpacker/controllers/products_table_controller.js
Normal file
46
app/webpacker/controllers/products_table_controller.js
Normal file
@@ -0,0 +1,46 @@
|
||||
import ApplicationController from "./application_controller";
|
||||
|
||||
export default class extends ApplicationController {
|
||||
connect() {
|
||||
super.connect();
|
||||
document.addEventListener(
|
||||
"stimulus-reflex:before",
|
||||
this.handleBeforeReflex.bind(this)
|
||||
);
|
||||
document.addEventListener(
|
||||
"stimulus-reflex:after",
|
||||
this.handleAfterReflex.bind(this)
|
||||
);
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
super.disconnect();
|
||||
document.removeEventListener(
|
||||
"stimulus-reflex:before",
|
||||
this.handleBeforeReflex.bind(this)
|
||||
);
|
||||
document.removeEventListener(
|
||||
"stimulus-reflex:after",
|
||||
this.handleAfterReflex.bind(this)
|
||||
);
|
||||
}
|
||||
|
||||
handleBeforeReflex(event) {
|
||||
if (event.detail.reflex.indexOf("ProductsTableComponent#") !== -1) {
|
||||
this.showLoading();
|
||||
}
|
||||
}
|
||||
|
||||
handleAfterReflex(event) {
|
||||
if (event.detail.reflex.indexOf("ProductsTableComponent#") !== -1) {
|
||||
this.hideLoading();
|
||||
}
|
||||
}
|
||||
|
||||
showLoading() {
|
||||
this.element.classList.add("loading");
|
||||
}
|
||||
hideLoading() {
|
||||
this.element.classList.remove("loading");
|
||||
}
|
||||
}
|
||||
28
app/webpacker/controllers/search-input_controller.js
Normal file
28
app/webpacker/controllers/search-input_controller.js
Normal file
@@ -0,0 +1,28 @@
|
||||
import ApplicationController from "./application_controller";
|
||||
|
||||
export default class extends ApplicationController {
|
||||
connect() {
|
||||
super.connect();
|
||||
this.element
|
||||
.querySelector("input")
|
||||
.addEventListener("keydown", this.searchOnEnter);
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
super.disconnect();
|
||||
this.element
|
||||
.querySelector("input")
|
||||
.removeEventListener("keydown", this.searchOnEnter);
|
||||
}
|
||||
|
||||
searchOnEnter = (e) => {
|
||||
if (e.key === "Enter") {
|
||||
this.element.querySelector(".search-button").click();
|
||||
}
|
||||
};
|
||||
|
||||
search(e) {
|
||||
this.element.querySelector(".search-button").dataset["value"] =
|
||||
e.target.value;
|
||||
}
|
||||
}
|
||||
52
app/webpacker/controllers/selector_controller.js
Normal file
52
app/webpacker/controllers/selector_controller.js
Normal file
@@ -0,0 +1,52 @@
|
||||
import ApplicationController from "./application_controller";
|
||||
|
||||
export default class extends ApplicationController {
|
||||
connect() {
|
||||
super.connect();
|
||||
window.addEventListener("click", this.closeOnClickOutside);
|
||||
this.computeItemsHeight();
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
super.disconnect();
|
||||
window.removeEventListener("click", this.closeOnClickOutside);
|
||||
}
|
||||
|
||||
initialize() {
|
||||
this.close();
|
||||
}
|
||||
|
||||
afterReflex() {
|
||||
this.computeItemsHeight();
|
||||
}
|
||||
|
||||
toggle = (event) => {
|
||||
event.preventDefault();
|
||||
this.element.querySelector(".selector").classList.toggle("selector-close");
|
||||
};
|
||||
|
||||
// Private
|
||||
closeOnClickOutside = (event) => {
|
||||
if (
|
||||
!this.element.contains(event.target) &&
|
||||
this.isVisible(this.element.querySelector(".selector-wrapper"))
|
||||
) {
|
||||
this.close();
|
||||
}
|
||||
};
|
||||
|
||||
computeItemsHeight = () => {
|
||||
const items = this.element.querySelector(".selector-items");
|
||||
const rect = items.getBoundingClientRect();
|
||||
items.style.maxHeight = `calc(100vh - ${rect.height}px)`;
|
||||
};
|
||||
|
||||
isVisible = (element) => {
|
||||
const style = window.getComputedStyle(element);
|
||||
return style.display !== "none" && style.visibility !== "hidden";
|
||||
};
|
||||
|
||||
close = () => {
|
||||
this.element.querySelector(".selector").classList.add("selector-close");
|
||||
};
|
||||
}
|
||||
15
app/webpacker/controllers/selector_with_filter_controller.js
Normal file
15
app/webpacker/controllers/selector_with_filter_controller.js
Normal file
@@ -0,0 +1,15 @@
|
||||
import SelectorController from "./selector_controller";
|
||||
|
||||
export default class extends SelectorController {
|
||||
static targets = ["items"];
|
||||
|
||||
filter = (event) => {
|
||||
const query = event.target.value;
|
||||
|
||||
this.itemsTargets.forEach((el, i) => {
|
||||
el.style.display = el.textContent.toLowerCase().includes(query)
|
||||
? ""
|
||||
: "none";
|
||||
});
|
||||
};
|
||||
}
|
||||
@@ -2,27 +2,24 @@ import { Controller } from "stimulus";
|
||||
export default class extends Controller {
|
||||
static targets = [
|
||||
"shippingMethodDescription",
|
||||
"shippingMethodDescriptionContent",
|
||||
"shippingMethodAddress",
|
||||
"shippingAddressCheckbox",
|
||||
];
|
||||
connect() {
|
||||
// Hide shippingMethodDescription by default
|
||||
}
|
||||
connect() {}
|
||||
|
||||
selectShippingMethod(event) {
|
||||
const input = event.target;
|
||||
if (input.tagName === "INPUT") {
|
||||
// Shipping method description
|
||||
if (input.dataset.description.length > 0) {
|
||||
this.shippingMethodDescriptionTarget.style.display = "block";
|
||||
this.shippingMethodDescriptionContentTarget.innerText =
|
||||
input.dataset.description;
|
||||
} else {
|
||||
this.shippingMethodDescriptionTarget.style.display = "none";
|
||||
this.shippingMethodDescriptionContentTarget.innerText = null;
|
||||
}
|
||||
// Require a ship address
|
||||
// -- Shipping method description
|
||||
// Hide all shipping method descriptions
|
||||
this.shippingMethodDescriptionTargets.forEach((t) => {
|
||||
t.style.display = "none";
|
||||
});
|
||||
// but not the one we want ie. the one that matches the shipping method id
|
||||
this.shippingMethodDescriptionTargets.find(
|
||||
(e) => e.dataset["shippingmethodid"] == input.value
|
||||
).style.display = "block";
|
||||
// -- Require a ship address
|
||||
if (
|
||||
input.dataset.requireaddress === "true" &&
|
||||
!this.shippingAddressCheckboxTarget.checked
|
||||
|
||||
@@ -15,7 +15,7 @@ export default class extends Controller {
|
||||
this.containerTarget.style.bottom = "-1px";
|
||||
const observer = new IntersectionObserver(
|
||||
([e]) => {
|
||||
e.target.classList.toggle("sticked", e.intersectionRatio < 1);
|
||||
e.target.classList.toggle("sticked", e.intersectionRatio <= 1);
|
||||
},
|
||||
{ threshold: [1] }
|
||||
);
|
||||
|
||||
@@ -120,3 +120,12 @@
|
||||
@import "components/tom_select";
|
||||
|
||||
@import 'app/components/help_modal_component/help_modal_component';
|
||||
@import "app/components/product_component/product_component";
|
||||
@import "app/components/selector_component/selector_component";
|
||||
@import "app/components/products_table_component/products_table_component";
|
||||
@import "app/components/selector_with_filter_component/selector_with_filter_component";
|
||||
@import "app/components/pagination_component/pagination_component";
|
||||
@import "app/components/table_header_component/table_header_component";
|
||||
@import "app/components/search_input_component/search_input_component";
|
||||
|
||||
@import "v2/main.scss";
|
||||
|
||||
@@ -78,7 +78,6 @@ nav.menu {
|
||||
text-transform: uppercase;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
font-weight: 600;
|
||||
|
||||
i {
|
||||
display: inline;
|
||||
|
||||
66
app/webpacker/css/admin/v2/components/buttons.scss
Normal file
66
app/webpacker/css/admin/v2/components/buttons.scss
Normal file
@@ -0,0 +1,66 @@
|
||||
/* Overide buttons.scss app/webpacker/css/admin/components/buttons.scss */
|
||||
|
||||
@mixin backgroundAndBorder($color) {
|
||||
background-color: $color;
|
||||
border: 2px solid $color;
|
||||
}
|
||||
|
||||
input[type="submit"],
|
||||
select[type="submit"],
|
||||
.select2-container-multi [type="submit"].select2-choices,
|
||||
input[type="button"],
|
||||
select[type="button"],
|
||||
.select2-container-multi [type="button"].select2-choices,
|
||||
button,
|
||||
.button,
|
||||
.actions a:not([class*="icon-"]),
|
||||
.admin__section-header .ofn-drop-down // Same behavior as the button
|
||||
{
|
||||
&.disabled,
|
||||
&[disabled] {
|
||||
@include backgroundAndBorder($v2-light-grey);
|
||||
}
|
||||
|
||||
&:not(.disabled):not([disabled]):not(.secondary) {
|
||||
// Change the color of the button only if it's not disabled
|
||||
@include backgroundAndBorder($v2-blue-light);
|
||||
|
||||
&:hover {
|
||||
@include backgroundAndBorder($v2-blue);
|
||||
box-shadow: $v2-box-shadow;
|
||||
}
|
||||
}
|
||||
|
||||
&.secondary {
|
||||
background-color: $white;
|
||||
border: 2px solid $v2-blue-light;
|
||||
color: $v2-blue-light;
|
||||
|
||||
&:hover {
|
||||
background-color: $v2-blue-lightest;
|
||||
color: $v2-blue;
|
||||
box-shadow: $v2-box-shadow;
|
||||
}
|
||||
}
|
||||
|
||||
&.active {
|
||||
@include backgroundAndBorder($v2-blue);
|
||||
}
|
||||
}
|
||||
|
||||
#table-filter .actions {
|
||||
/* used to draw a line on the right and left of the actions buttons
|
||||
We can then remove the dropshadow on the buttons */
|
||||
&:before,
|
||||
&:after {
|
||||
background-color: $v2-light-grey; // same color as the border of the fieldset. see forms.scss
|
||||
height: 1px;
|
||||
content: attr(data-initials);
|
||||
flex-grow: 1; // make the line as long as it can
|
||||
}
|
||||
}
|
||||
|
||||
#table-filter fieldset:has(.actions) {
|
||||
// do not apply border to filter actions as it's drawn by the #table-filter .actions before and after pseudo elements
|
||||
border-bottom: 0;
|
||||
}
|
||||
9
app/webpacker/css/admin/v2/components/progress.scss
Normal file
9
app/webpacker/css/admin/v2/components/progress.scss
Normal file
@@ -0,0 +1,9 @@
|
||||
/* Overide app/webpacker/css/admin/components/progress.scss file */
|
||||
|
||||
#progress {
|
||||
background-color: $v2-blue;
|
||||
}
|
||||
|
||||
#loading {
|
||||
color: $v2-blue;
|
||||
}
|
||||
73
app/webpacker/css/admin/v2/components/tables.scss
Normal file
73
app/webpacker/css/admin/v2/components/tables.scss
Normal file
@@ -0,0 +1,73 @@
|
||||
/* Overide tables.scss app/webpacker/css/admin/components/tables.scss */
|
||||
|
||||
table thead th {
|
||||
background-color: $v2-medium-light-grey;
|
||||
border: none;
|
||||
color: $v2-blue;
|
||||
text-transform: capitalize;
|
||||
font-size: 13px;
|
||||
|
||||
a {
|
||||
border: none;
|
||||
color: $v2-blue;
|
||||
}
|
||||
}
|
||||
|
||||
table tbody tr {
|
||||
&:first-child th,
|
||||
&:first-child td {
|
||||
border-top: none; // Don't show the top border of the first row
|
||||
}
|
||||
td:not(:first-child) {
|
||||
border-left: none; // Only show left border on the first cells, as it indicates the order state by its color
|
||||
}
|
||||
|
||||
td {
|
||||
border-bottom: none; // By default, do not show the border of the cells
|
||||
border-right: none;
|
||||
border-top: none;
|
||||
|
||||
border-bottom: 2px solid $v2-medium-light-grey;
|
||||
&.actions {
|
||||
border-bottom: 2px solid $v2-medium-light-grey !important; // needs to be important because of already defined with important
|
||||
}
|
||||
> .flex {
|
||||
display: flex;
|
||||
column-gap: 10px;
|
||||
}
|
||||
}
|
||||
&.even,
|
||||
&.odd {
|
||||
td {
|
||||
background-color: transparent; // Do not use odd and even colors for background
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
table th.actions,
|
||||
table td.actions {
|
||||
// Special for icons in the actions column
|
||||
[class*="icon-"].no-text {
|
||||
border: 2px solid $v2-blue-light;
|
||||
background-color: $v2-blue-lightest;
|
||||
|
||||
&:hover {
|
||||
border-color: $v2-blue;
|
||||
background-color: $v2-blue-light;
|
||||
box-shadow: $v2-box-shadow;
|
||||
|
||||
&:before {
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
table#listing_orders td {
|
||||
// When the table is the listing of orders, we need to increase the height of the cells
|
||||
padding: 20px 0;
|
||||
|
||||
&.actions {
|
||||
padding-left: 20px;
|
||||
}
|
||||
}
|
||||
13
app/webpacker/css/admin/v2/main.scss
Normal file
13
app/webpacker/css/admin/v2/main.scss
Normal file
@@ -0,0 +1,13 @@
|
||||
@import "variables.scss";
|
||||
@import "shared/typography.scss";
|
||||
|
||||
body.admin.admin-v2 {
|
||||
@import "navigation.scss";
|
||||
@import "plugins/select2.scss";
|
||||
@import "plugins/powertip.scss";
|
||||
@import "plugins/flatpickr-customization.scss";
|
||||
@import "shared/forms.scss";
|
||||
@import "components/buttons.scss";
|
||||
@import "components/tables.scss";
|
||||
@import "components/progress.scss";
|
||||
}
|
||||
62
app/webpacker/css/admin/v2/navigation.scss
Normal file
62
app/webpacker/css/admin/v2/navigation.scss
Normal file
@@ -0,0 +1,62 @@
|
||||
#header {
|
||||
#login-nav {
|
||||
li {
|
||||
color: $v2-medium-dark-grey;
|
||||
|
||||
i {
|
||||
@include v2-link-color();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#admin-menu {
|
||||
background-color: $v2-orange;
|
||||
|
||||
li a span.text {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
li.tab-with-icon a:before {
|
||||
display: none;
|
||||
}
|
||||
|
||||
li a:hover,
|
||||
li.selected a {
|
||||
background-color: $v2-orange-light;
|
||||
|
||||
span {
|
||||
text-shadow: 1px 1px 9px $v2-orange;
|
||||
}
|
||||
|
||||
&:after {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#sub-menu {
|
||||
background-color: $v2-orange-light;
|
||||
|
||||
li a {
|
||||
font-size: 100%;
|
||||
}
|
||||
|
||||
li a:hover,
|
||||
li.selected a {
|
||||
text-shadow: 1px 1px 9px $v2-orange;
|
||||
background-color: lighten($v2-orange-light, 7%);
|
||||
&:after {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.admin__section-header {
|
||||
background-color: transparent;
|
||||
border-bottom: none;
|
||||
|
||||
h1.js-admin-page-title {
|
||||
color: $v2-medium-dark-grey;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
/* Override flatpickr styles: app/webpacker/css/admin/plugins/flatpickr-customization.scss */
|
||||
|
||||
.flatpickr-calendar {
|
||||
&.arrowTop::after {
|
||||
border-bottom-color: $v2-blue-light;
|
||||
}
|
||||
.flatpickr-months .flatpickr-month,
|
||||
.flatpickr-current-month .flatpickr-monthDropdown-months {
|
||||
background: $v2-blue-light;
|
||||
color: white;
|
||||
input {
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
|
||||
.flatpickr-weekdays {
|
||||
background: $v2-blue-light;
|
||||
|
||||
.flatpickr-weekday {
|
||||
background: $v2-blue-light;
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
|
||||
.flatpickr-day.selected,
|
||||
.flatpickr-day.startRange,
|
||||
.flatpickr-day.endRange,
|
||||
.flatpickr-day.selected.inRange,
|
||||
.flatpickr-day.startRange.inRange,
|
||||
.flatpickr-day.endRange.inRange,
|
||||
.flatpickr-day.selected:focus,
|
||||
.flatpickr-day.startRange:focus,
|
||||
.flatpickr-day.endRange:focus,
|
||||
.flatpickr-day.selected:hover,
|
||||
.flatpickr-day.startRange:hover,
|
||||
.flatpickr-day.endRange:hover,
|
||||
.flatpickr-day.selected.prevMonthDay,
|
||||
.flatpickr-day.startRange.prevMonthDay,
|
||||
.flatpickr-day.endRange.prevMonthDay,
|
||||
.flatpickr-day.selected.nextMonthDay,
|
||||
.flatpickr-day.startRange.nextMonthDay,
|
||||
.flatpickr-day.endRange.nextMonthDay {
|
||||
background: $v2-blue-light;
|
||||
border-color: $v2-blue-light;
|
||||
}
|
||||
}
|
||||
31
app/webpacker/css/admin/v2/plugins/powertip.scss
Normal file
31
app/webpacker/css/admin/v2/plugins/powertip.scss
Normal file
@@ -0,0 +1,31 @@
|
||||
/* Overide file powertip.scss app/webpacker/css/admin/v2/plugins/powertip.scss */
|
||||
|
||||
#powerTip {
|
||||
background-color: $v2-blue;
|
||||
|
||||
&.n:before,
|
||||
&.ne:before,
|
||||
&.nw:before {
|
||||
border-top-color: $v2-blue;
|
||||
}
|
||||
|
||||
&.e:before {
|
||||
border-right-color: $v2-blue;
|
||||
}
|
||||
&.s:before,
|
||||
&.se:before,
|
||||
&.sw:before {
|
||||
border-bottom-color: $v2-blue;
|
||||
}
|
||||
&.w:before {
|
||||
border-left-color: $v2-blue;
|
||||
}
|
||||
&.ne:before,
|
||||
&.se:before {
|
||||
border-right-color: $v2-blue;
|
||||
}
|
||||
&.nw:before,
|
||||
&.sw:before {
|
||||
border-right-color: $v2-blue;
|
||||
}
|
||||
}
|
||||
70
app/webpacker/css/admin/v2/plugins/select2.scss
Normal file
70
app/webpacker/css/admin/v2/plugins/select2.scss
Normal file
@@ -0,0 +1,70 @@
|
||||
/* Override select2 styles app/webpacker/css/admin/plugins/select2.scss */
|
||||
|
||||
.select2-container {
|
||||
.select2-choice {
|
||||
background-color: transparent;
|
||||
border: 1px solid $v2-light-grey !important;
|
||||
color: $v2-medium-grey !important;
|
||||
padding-left: 5px;
|
||||
font-size: 13px;
|
||||
padding-top: 3px;
|
||||
|
||||
.select2-arrow {
|
||||
color: $v2-medium-grey;
|
||||
}
|
||||
}
|
||||
|
||||
&.select2-container-active,
|
||||
&:hover {
|
||||
.select2-choice {
|
||||
background-color: transparent !important;
|
||||
border-color: $v2-medium-grey !important;
|
||||
}
|
||||
}
|
||||
|
||||
&.select2-dropdown-open.select2-container-active {
|
||||
&:not(.select2-drop-above) {
|
||||
.select2-choice {
|
||||
border-bottom-left-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
}
|
||||
&.select2-drop-above .select.select2-choice {
|
||||
border-top-left-radius: 0;
|
||||
border-top-right-radius: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.select2-container-multi {
|
||||
.select2-choices {
|
||||
border-color: $v2-medium-grey !important;
|
||||
.select2-search-choice {
|
||||
background-color: $v2-blue-light;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.select2-drop {
|
||||
border-color: $v2-medium-grey;
|
||||
}
|
||||
|
||||
.select2-search {
|
||||
color: $v2-medium-grey;
|
||||
}
|
||||
|
||||
.select2-results {
|
||||
margin-right: 0;
|
||||
li {
|
||||
.select2-result-label {
|
||||
color: $v2-medium-grey;
|
||||
}
|
||||
&.select2-highlighted {
|
||||
background-color: $v2-blue;
|
||||
}
|
||||
&.select2-no-results,
|
||||
&.select2-searching {
|
||||
color: $v2-medium-grey;
|
||||
}
|
||||
}
|
||||
}
|
||||
55
app/webpacker/css/admin/v2/shared/forms.scss
Normal file
55
app/webpacker/css/admin/v2/shared/forms.scss
Normal file
@@ -0,0 +1,55 @@
|
||||
/* Overide forms.scss app/webpacker/css/admin/shared/forms.scss */
|
||||
|
||||
input[type="text"],
|
||||
.select2-container-multi .select2-choices,
|
||||
.select2-search input,
|
||||
.select2-search select,
|
||||
select,
|
||||
input[type="password"],
|
||||
input[type="email"],
|
||||
input[type="url"],
|
||||
input[type="tel"],
|
||||
input[type="date"],
|
||||
input[type="datetime"],
|
||||
input[type="time"],
|
||||
input[type="number"],
|
||||
textarea,
|
||||
fieldset {
|
||||
// Change the color of all inputs
|
||||
color: $v2-medium-grey;
|
||||
border-color: $v2-light-grey !important;
|
||||
|
||||
&:focus {
|
||||
border-color: $v2-medium-grey;
|
||||
}
|
||||
}
|
||||
|
||||
fieldset label {
|
||||
color: $v2-medium-grey;
|
||||
}
|
||||
|
||||
fieldset legend {
|
||||
color: $v2-blue;
|
||||
}
|
||||
|
||||
input[type="checkbox"],
|
||||
input[type="radio"] {
|
||||
accent-color: $v2-blue;
|
||||
}
|
||||
|
||||
fieldset .filter-actions {
|
||||
button,
|
||||
.button,
|
||||
input[type="submit"] {
|
||||
box-shadow: none;
|
||||
width: 200px; // adjust at the same size in order to center them. Not ideal but works for now.
|
||||
|
||||
&:first-of-type {
|
||||
margin-right: 0; // don't see any reason to have a margin right on the first button as it's managed by the flexbox
|
||||
}
|
||||
|
||||
&:hover {
|
||||
box-shadow: $v2-box-shadow;
|
||||
}
|
||||
}
|
||||
}
|
||||
18
app/webpacker/css/admin/v2/shared/typography.scss
Normal file
18
app/webpacker/css/admin/v2/shared/typography.scss
Normal file
@@ -0,0 +1,18 @@
|
||||
// Overide app/webpacker/css/admin/shared/typography.scss
|
||||
|
||||
@mixin v2-link-color() {
|
||||
color: $v2-blue;
|
||||
border-color: $v2-blue;
|
||||
&:hover {
|
||||
color: $v2-blue-dark;
|
||||
border-color: $v2-blue-dark;
|
||||
}
|
||||
}
|
||||
|
||||
body.admin.admin-v2 {
|
||||
color: $v2-body-grey;
|
||||
|
||||
a:not(.button) {
|
||||
@include v2-link-color();
|
||||
}
|
||||
}
|
||||
23
app/webpacker/css/admin/v2/variables.scss
Normal file
23
app/webpacker/css/admin/v2/variables.scss
Normal file
@@ -0,0 +1,23 @@
|
||||
// Should finally replace (or at least complete) the file /admin/globale/variables.scss
|
||||
|
||||
$v2-orange: #f27052;
|
||||
$v2-orange-light: #f5947d;
|
||||
$v2-orange-lighter: #f8b7a8;
|
||||
$v2-orange-lightest: #fcdbd4;
|
||||
|
||||
$v2-dark-grey: #333333;
|
||||
$v2-medium-dark-grey: #444444;
|
||||
$v2-body-grey: #666666;
|
||||
$v2-medium-grey: #717171;
|
||||
$v2-medium-light-grey: #e6e6e6;
|
||||
$v2-light-grey: #e7e7e7;
|
||||
|
||||
$v2-blue: #017a9a;
|
||||
$v2-blue-dark: #005e7a;
|
||||
$v2-blue-light: #0096ad;
|
||||
$v2-blue-lightest: #e6f7fa; // Could be used as a background color for the action icons
|
||||
|
||||
$v2-green: #019854;
|
||||
$v2-green-light: #01cb70;
|
||||
|
||||
$v2-box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.25); // Default box shadow for actions stuff
|
||||
@@ -1,3 +1,8 @@
|
||||
#cart-container {
|
||||
padding: 25px;
|
||||
padding-bottom: 100px;
|
||||
}
|
||||
|
||||
#update-cart {
|
||||
#errorExplanation {
|
||||
display: none;
|
||||
@@ -6,6 +11,8 @@
|
||||
|
||||
#cart-detail {
|
||||
width: 100%;
|
||||
display: block;
|
||||
overflow-x: auto;
|
||||
|
||||
.cart-item-delete,
|
||||
.bought-item-delete {
|
||||
|
||||
@@ -34,13 +34,16 @@
|
||||
|
||||
span, span > a {
|
||||
color: $white;
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
&.success {
|
||||
border-bottom: 5px solid $clr-turquoise-bright;
|
||||
|
||||
span {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
span.checkout-tab-label {
|
||||
&:after {
|
||||
content: '✓';
|
||||
@@ -98,7 +101,6 @@
|
||||
font-size: 1.06rem;
|
||||
@include headingFont;
|
||||
font-weight: $font-weight-bold;
|
||||
text-decoration: underline;
|
||||
color: $darker-grey;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
@@ -158,6 +160,18 @@
|
||||
|
||||
label {
|
||||
margin-top: 0.3rem;
|
||||
& + em {
|
||||
margin-left: -0.5rem;
|
||||
font-size: 14px; // same as label
|
||||
font-weight: bold;
|
||||
// Add opening and closing parentheses
|
||||
&:before {
|
||||
content: "(";
|
||||
}
|
||||
&:after {
|
||||
content: ")";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -240,8 +254,10 @@
|
||||
}
|
||||
|
||||
&.cancel {
|
||||
background-color: $grey-100;
|
||||
color: $black;
|
||||
background-color: $white;
|
||||
border: 1px solid $orange-500;
|
||||
color: $orange-500;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -306,3 +322,62 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#line-items {
|
||||
display: block;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.two-columns-inputs {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
justify-content: space-between;
|
||||
|
||||
> .checkout-input {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
&:last-child > .checkout-input {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 700px) {
|
||||
flex-direction: column;
|
||||
gap: 0;
|
||||
&:last-child > .checkout-input {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Shows label as a placeholder, inside the input and when the input is focused shows the label above the input
|
||||
.checkout-input.with-floating-label {
|
||||
position: relative;
|
||||
|
||||
label {
|
||||
display: none; // Display none by default, and shown by floating_label_controller
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
font-size: 0.875rem;
|
||||
padding: 8px;
|
||||
padding-left: 9px;
|
||||
color: $min-accessible-grey;
|
||||
pointer-events: none;
|
||||
transform: translateY(0);
|
||||
transform-origin: top left;
|
||||
&.with-transition {
|
||||
transition: all 0.2s ease-in-out;
|
||||
}
|
||||
}
|
||||
&.active {
|
||||
label {
|
||||
transform: translateY(-9px) translateX(10px) scale(0.9);
|
||||
background-color: white;
|
||||
padding-left: 3px;
|
||||
padding-right: 3px;
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,3 +13,6 @@ application.consumer = consumer;
|
||||
StimulusReflex.initialize(application, { controller, isolate: true });
|
||||
StimulusReflex.debug = process.env.RAILS_ENV === "development";
|
||||
CableReady.initialize({ consumer });
|
||||
|
||||
import debounced from "debounced";
|
||||
debounced.initialize({ input: { wait: 300 } });
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user