Compare commits
354 Commits
feature/IO
...
feature/IO
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b479684fe4 | ||
|
|
5b81912bd3 | ||
|
|
3c98a94c38 | ||
|
|
1d98de6d4d | ||
|
|
0ce5d9063a | ||
|
|
3b84e1d6ec | ||
|
|
d62f6e2116 | ||
|
|
71a26cc4ac | ||
|
|
32441e9406 | ||
|
|
e6dade1206 | ||
|
|
43d34cae07 | ||
|
|
a72a7948fe | ||
|
|
a24f6639a1 | ||
|
|
b2a0af32e9 | ||
|
|
cc58d14d32 | ||
|
|
9ce419b949 | ||
|
|
5053816be7 | ||
|
|
30ca34ea93 | ||
|
|
68d1a404b3 | ||
|
|
85e82b85ea | ||
|
|
23467280b4 | ||
|
|
aedad1c48f | ||
|
|
05cc4dd188 | ||
|
|
ea6351ea06 | ||
|
|
87d3ceb408 | ||
|
|
d08dd2b506 | ||
|
|
8a047d14a1 | ||
|
|
e103772aa4 | ||
|
|
c332699dc8 | ||
|
|
25e6e61d10 | ||
|
|
cdcd6b636a | ||
|
|
7879591bcf | ||
|
|
7fc6556866 | ||
|
|
3f5489ce7e | ||
|
|
5a90854861 | ||
|
|
8347a8c098 | ||
|
|
2bf074d85a | ||
|
|
50d47cd679 | ||
|
|
3a4e06eaa2 | ||
|
|
4be71726d4 | ||
|
|
c78db7eb08 | ||
|
|
e4dc711481 | ||
|
|
5114138c67 | ||
|
|
68b8743002 | ||
|
|
8f312bfffb | ||
|
|
7e7e109cfe | ||
|
|
05e5545466 | ||
|
|
ddb0990645 | ||
|
|
04dec6d91c | ||
|
|
a883b817b0 | ||
|
|
b7423aebf6 | ||
|
|
ee70aeb952 | ||
|
|
74d95e7cbb | ||
|
|
f6f6fab5ba | ||
|
|
699ffc822a | ||
|
|
4e35f5402c | ||
|
|
9b997d0924 | ||
|
|
d705f8211e | ||
|
|
03761bbb2a | ||
|
|
4d0794e90e | ||
|
|
e615c4a55b | ||
|
|
51eb3423f3 | ||
|
|
f6318666d9 | ||
|
|
544d4b8136 | ||
|
|
edf4846d55 | ||
|
|
f3754de843 | ||
|
|
3d920ad151 | ||
|
|
575f056360 | ||
|
|
716d9affb5 | ||
|
|
b01dd52da2 | ||
|
|
c75fddc2c0 | ||
|
|
db0c16f31d | ||
|
|
b286ab2439 | ||
|
|
fa57828ebd | ||
|
|
8052767002 | ||
|
|
932f572fb5 | ||
|
|
328a64eb90 | ||
|
|
c661fce8f1 | ||
|
|
60d1396011 | ||
|
|
3b647dfd37 | ||
|
|
50fe588949 | ||
|
|
0ced053d21 | ||
|
|
b8cf4a4d75 | ||
|
|
ff72657a82 | ||
|
|
92a96fdae6 | ||
|
|
b1a96d55ad | ||
|
|
49657816c6 | ||
|
|
7094b6ffbf | ||
|
|
ed7c2574eb | ||
|
|
45a9e37342 | ||
|
|
9e6a458203 | ||
|
|
55a279a700 | ||
|
|
82e2e332cf | ||
|
|
103d7c2bb2 | ||
|
|
f5f0b75617 | ||
|
|
c163554c3f | ||
|
|
bd75f593c2 | ||
|
|
fbc1866363 | ||
|
|
6480f7f2aa | ||
|
|
4cb3a79429 | ||
|
|
ca521eaeba | ||
|
|
f287ba2dac | ||
|
|
ff46bbbb3f | ||
|
|
cafca35500 | ||
|
|
9803841617 | ||
|
|
4dffbfe6fa | ||
|
|
6e61159608 | ||
|
|
3c85de3e34 | ||
|
|
1b5cddd371 | ||
|
|
6a7005299a | ||
|
|
1a4c9faab1 | ||
|
|
bfbf34e11d | ||
|
|
646754732d | ||
|
|
439d9e7b74 | ||
|
|
464f7044f0 | ||
|
|
7cde2f64af | ||
|
|
f674fff930 | ||
|
|
efc1157653 | ||
|
|
0677712d6e | ||
|
|
2e106a5d07 | ||
|
|
da7b97042e | ||
|
|
f018a2b2a6 | ||
|
|
c3f7d7bad2 | ||
|
|
70d857bfec | ||
|
|
f3265901b6 | ||
|
|
7c8ac50426 | ||
|
|
8ad39fe855 | ||
|
|
13b6218c43 | ||
|
|
bece3278f4 | ||
|
|
4c0a1960ad | ||
|
|
47324422a6 | ||
|
|
3b1da6901d | ||
|
|
fc6ec54233 | ||
|
|
64928d0849 | ||
|
|
56a580b1e7 | ||
|
|
f7af3b407b | ||
|
|
9a0674f5d7 | ||
|
|
cc30ea658e | ||
|
|
59869def31 | ||
|
|
a5d3f2caf1 | ||
|
|
4ad87a522c | ||
|
|
453812222b | ||
|
|
145cf7cc93 | ||
|
|
c09e22ed96 | ||
|
|
cdb2d4d2d6 | ||
|
|
29f0031c1e | ||
|
|
e8099e130a | ||
|
|
1cbca1ddf0 | ||
|
|
eeed004fe2 | ||
|
|
5a180b86fb | ||
|
|
e3059b41ae | ||
|
|
1a5c71048c | ||
|
|
fc4e97c9b5 | ||
|
|
3cd3d7414d | ||
|
|
1bb2212e4a | ||
|
|
a088f27f1d | ||
|
|
0e9ad1258d | ||
|
|
2a33f462a3 | ||
|
|
cbc164dbeb | ||
|
|
0bfc7033a9 | ||
|
|
2ec0d90a58 | ||
|
|
6382fdf19c | ||
|
|
9287e6608d | ||
|
|
0fcee5b25e | ||
|
|
d221763064 | ||
|
|
b39a5b755e | ||
|
|
30cb4ef562 | ||
|
|
449330441a | ||
|
|
fcab5e6ef2 | ||
|
|
0212b837ea | ||
|
|
e7438a099e | ||
|
|
b3303e3c38 | ||
|
|
c69c86d193 | ||
|
|
73ec8b8a70 | ||
|
|
af09796df8 | ||
|
|
954504de8d | ||
|
|
0aba040338 | ||
|
|
c3bfe87674 | ||
|
|
9aa1279144 | ||
|
|
4e6c45b195 | ||
|
|
4fdb939bd2 | ||
|
|
062a1dcc72 | ||
|
|
7b420b1855 | ||
|
|
40f61bbc8f | ||
|
|
f5d821c394 | ||
|
|
3958ec9189 | ||
|
|
1e4f52e541 | ||
|
|
5cc5cb444e | ||
|
|
4acf0c59ca | ||
|
|
2858a5e871 | ||
|
|
24496d3ee1 | ||
|
|
0a5df69b12 | ||
|
|
80efea02c6 | ||
|
|
9f5c282b41 | ||
|
|
b2602c3385 | ||
|
|
0e584af424 | ||
|
|
cdc3de2a33 | ||
|
|
3bfa556b02 | ||
|
|
44cb7577e2 | ||
|
|
46d2b08477 | ||
|
|
0193ff9e65 | ||
|
|
fd9a51209f | ||
|
|
d0a7b87e04 | ||
|
|
799b24c90e | ||
|
|
3e1a8c87d1 | ||
|
|
c886d874de | ||
|
|
4dfb020089 | ||
|
|
bc6f05acbc | ||
|
|
2701bbd501 | ||
|
|
1f2040d97c | ||
|
|
43963a3e91 | ||
|
|
4287311adb | ||
|
|
d0e8589a76 | ||
|
|
c4bab72947 | ||
|
|
aa4b4998fa | ||
|
|
ed4566e00f | ||
|
|
5c2cdfe16c | ||
|
|
12c75357b5 | ||
|
|
d40f3ee45a | ||
|
|
96a0def846 | ||
|
|
1fd595d0de | ||
|
|
52cf4f3d1f | ||
|
|
4d9be1d232 | ||
|
|
fb2bc20b4f | ||
|
|
744593e96a | ||
|
|
1e9308be9b | ||
|
|
411605e121 | ||
|
|
1da8d6abb3 | ||
|
|
cdcef798df | ||
|
|
f7207a9f3f | ||
|
|
7a54b55bd4 | ||
|
|
991dfc2ad5 | ||
|
|
718c8291a8 | ||
|
|
f1e84c348b | ||
|
|
2a2d399a98 | ||
|
|
5f513a8bef | ||
|
|
4b96d5a707 | ||
|
|
220f3d4410 | ||
|
|
841f62bd84 | ||
|
|
f3f16b78d5 | ||
|
|
91e2e7931b | ||
|
|
1e855799f8 | ||
|
|
3c6faf8473 | ||
|
|
c994eaaa8e | ||
|
|
517d8f4163 | ||
|
|
9deb2964a5 | ||
|
|
9cf9f8b844 | ||
|
|
ad46ea74c0 | ||
|
|
2a28855e4b | ||
|
|
8d25f60097 | ||
|
|
982a51f16e | ||
|
|
68d02648d7 | ||
|
|
6e8122849a | ||
|
|
b04ae84941 | ||
|
|
932979d5fb | ||
|
|
f7ef32c58d | ||
|
|
f7108b4b8c | ||
|
|
882038a794 | ||
|
|
aec23fe46b | ||
|
|
89d5b1cfe4 | ||
|
|
35ac0b0c6a | ||
|
|
2a2a0f8961 | ||
|
|
d9902b9744 | ||
|
|
f82478a362 | ||
|
|
bb3d3fbe72 | ||
|
|
4fa0593bb5 | ||
|
|
41517ca7d4 | ||
|
|
35c9f649ad | ||
|
|
ad2f2e55a5 | ||
|
|
41c446ddb3 | ||
|
|
7d6aa8489d | ||
|
|
63f1e0f07c | ||
|
|
98f4423624 | ||
|
|
1ac4cbb59f | ||
|
|
24ebfbfbf5 | ||
|
|
7ff1051d3c | ||
|
|
8af3364660 | ||
|
|
02f4677aef | ||
|
|
11785f3b86 | ||
|
|
90532427b6 | ||
|
|
cc9979ff4b | ||
|
|
c89e4f1b41 | ||
|
|
c3e6d3dc48 | ||
|
|
ad1ce7b220 | ||
|
|
fd4dbdfb3a | ||
|
|
153cf6a840 | ||
|
|
a567d0d6dd | ||
|
|
297599a45b | ||
|
|
678ca591c1 | ||
|
|
c19f8167e8 | ||
|
|
cc2d474fda | ||
|
|
9058aca16e | ||
|
|
1c186f7fa5 | ||
|
|
46da3285f8 | ||
|
|
b419929ad7 | ||
|
|
8018daa2dc | ||
|
|
1e7c285fef | ||
|
|
0b072e6089 | ||
|
|
4fd6203987 | ||
|
|
51d264098c | ||
|
|
680a66b156 | ||
|
|
481a14e529 | ||
|
|
f3e43334c4 | ||
|
|
0054b00d01 | ||
|
|
82ecb5533f | ||
|
|
d3289d85f1 | ||
|
|
e628b1364c | ||
|
|
6c421c1447 | ||
|
|
99369e7040 | ||
|
|
01cbdf14a9 | ||
|
|
f691aca241 | ||
|
|
85495a11e3 | ||
|
|
134ce05d27 | ||
|
|
3498fbc8f1 | ||
|
|
f49f72ce7f | ||
|
|
a5e3b6ce33 | ||
|
|
0fd945b859 | ||
|
|
879eba0247 | ||
|
|
bb49dd77a1 | ||
|
|
ae705322f8 | ||
|
|
36d92d4060 | ||
|
|
3ce2b1ab19 | ||
|
|
52e756a78a | ||
|
|
5a36cb7cf1 | ||
|
|
9138f4be16 | ||
|
|
df93357cec | ||
|
|
8ab23c4ca6 | ||
|
|
f179d69420 | ||
|
|
730a7a233d | ||
|
|
84ad10fa9c | ||
|
|
b1cda41f56 | ||
|
|
0bce921f69 | ||
|
|
97282740f5 | ||
|
|
150ae02978 | ||
|
|
70058b6bd4 | ||
|
|
67c63f81d9 | ||
|
|
abeffb2a19 | ||
|
|
fb8452b2bb | ||
|
|
223c705e8f | ||
|
|
00d8b533f4 | ||
|
|
3b25a8fe07 | ||
|
|
a57b9cddb5 | ||
|
|
064ed1bb8b | ||
|
|
efda254981 | ||
|
|
3fa6b8b6ac | ||
|
|
4603643240 | ||
|
|
f82b02958f | ||
|
|
5d1f61753b | ||
|
|
883043cde3 | ||
|
|
85ce7c638d | ||
|
|
fa6d4cce2a | ||
|
|
c7188d5a71 | ||
|
|
c333d72743 | ||
|
|
2a1ec4eff3 |
@@ -5,6 +5,7 @@ orbs:
|
|||||||
aws-s3: circleci/aws-s3@4.0.0
|
aws-s3: circleci/aws-s3@4.0.0
|
||||||
aws-cli: circleci/aws-cli@4.0
|
aws-cli: circleci/aws-cli@4.0
|
||||||
eb: circleci/aws-elastic-beanstalk@2.0.1
|
eb: circleci/aws-elastic-beanstalk@2.0.1
|
||||||
|
jira: circleci/jira@2.1.0
|
||||||
jobs:
|
jobs:
|
||||||
imex-api-deploy:
|
imex-api-deploy:
|
||||||
docker:
|
docker:
|
||||||
@@ -18,6 +19,12 @@ jobs:
|
|||||||
eb status --verbose
|
eb status --verbose
|
||||||
eb deploy
|
eb deploy
|
||||||
eb status
|
eb status
|
||||||
|
- jira/notify:
|
||||||
|
environment: Production (ImEX) - API
|
||||||
|
environment_type: production
|
||||||
|
job_type: deployment
|
||||||
|
pipeline_id: << pipeline.id >>
|
||||||
|
pipeline_number: << pipeline.number >>
|
||||||
|
|
||||||
imex-hasura-migrate:
|
imex-hasura-migrate:
|
||||||
docker:
|
docker:
|
||||||
@@ -33,11 +40,16 @@ jobs:
|
|||||||
- run:
|
- run:
|
||||||
name: Execute migration
|
name: Execute migration
|
||||||
command: |
|
command: |
|
||||||
npm install hasura-cli -g
|
curl -L https://github.com/hasura/graphql-engine/raw/stable/cli/get.sh | bash
|
||||||
hasura migrate apply --endpoint https://db.imex.online/ --admin-secret << parameters.secret >>
|
hasura migrate apply --endpoint https://db.imex.online/ --admin-secret << parameters.secret >>
|
||||||
hasura metadata apply --endpoint https://db.imex.online/ --admin-secret << parameters.secret >>
|
hasura metadata apply --endpoint https://db.imex.online/ --admin-secret << parameters.secret >>
|
||||||
hasura metadata reload --endpoint https://db.imex.online/ --admin-secret << parameters.secret >>
|
hasura metadata reload --endpoint https://db.imex.online/ --admin-secret << parameters.secret >>
|
||||||
|
- jira/notify:
|
||||||
|
environment: Production (ImEX) - Hasura
|
||||||
|
environment_type: production
|
||||||
|
pipeline_id: << pipeline.id >>
|
||||||
|
job_type: deployment
|
||||||
|
pipeline_number: << pipeline.number >>
|
||||||
imex-app-build:
|
imex-app-build:
|
||||||
docker:
|
docker:
|
||||||
- image: cimg/node:18.18.2
|
- image: cimg/node:18.18.2
|
||||||
@@ -62,6 +74,7 @@ jobs:
|
|||||||
to: "s3://imex-online-production/"
|
to: "s3://imex-online-production/"
|
||||||
arguments: "--exclude '*.map'"
|
arguments: "--exclude '*.map'"
|
||||||
|
|
||||||
|
|
||||||
imex-app-beta-build:
|
imex-app-beta-build:
|
||||||
docker:
|
docker:
|
||||||
- image: cimg/node:18.18.2
|
- image: cimg/node:18.18.2
|
||||||
@@ -86,6 +99,12 @@ jobs:
|
|||||||
from: dist
|
from: dist
|
||||||
to: "s3://imex-online-beta/"
|
to: "s3://imex-online-beta/"
|
||||||
arguments: "--exclude '*.map'"
|
arguments: "--exclude '*.map'"
|
||||||
|
- jira/notify:
|
||||||
|
environment: Production (ImEX) - Front End
|
||||||
|
environment_type: production
|
||||||
|
pipeline_id: << pipeline.id >>
|
||||||
|
job_type: deployment
|
||||||
|
pipeline_number: << pipeline.number >>
|
||||||
|
|
||||||
rome-api-deploy:
|
rome-api-deploy:
|
||||||
docker:
|
docker:
|
||||||
@@ -99,7 +118,12 @@ jobs:
|
|||||||
eb status --verbose
|
eb status --verbose
|
||||||
eb deploy
|
eb deploy
|
||||||
eb status
|
eb status
|
||||||
|
- jira/notify:
|
||||||
|
environment: Production (Rome) - API
|
||||||
|
environment_type: production
|
||||||
|
pipeline_id: << pipeline.id >>
|
||||||
|
job_type: deployment
|
||||||
|
pipeline_number: << pipeline.number >>
|
||||||
rome-hasura-migrate:
|
rome-hasura-migrate:
|
||||||
docker:
|
docker:
|
||||||
- image: cimg/node:18.18.2
|
- image: cimg/node:18.18.2
|
||||||
@@ -114,11 +138,16 @@ jobs:
|
|||||||
- run:
|
- run:
|
||||||
name: Execute migration
|
name: Execute migration
|
||||||
command: |
|
command: |
|
||||||
npm install hasura-cli -g
|
curl -L https://github.com/hasura/graphql-engine/raw/stable/cli/get.sh | bash
|
||||||
hasura migrate apply --endpoint https://db.romeonline.io/ --admin-secret << parameters.secret >>
|
hasura migrate apply --endpoint https://db.romeonline.io/ --admin-secret << parameters.secret >>
|
||||||
hasura metadata apply --endpoint https://db.romeonline.io/ --admin-secret << parameters.secret >>
|
hasura metadata apply --endpoint https://db.romeonline.io/ --admin-secret << parameters.secret >>
|
||||||
hasura metadata reload --endpoint https://db.romeonline.io/ --admin-secret << parameters.secret >>
|
hasura metadata reload --endpoint https://db.romeonline.io/ --admin-secret << parameters.secret >>
|
||||||
|
- jira/notify:
|
||||||
|
environment: Production (Rome) - Hasura
|
||||||
|
environment_type: production
|
||||||
|
pipeline_id: << pipeline.id >>
|
||||||
|
job_type: deployment
|
||||||
|
pipeline_number: << pipeline.number >>
|
||||||
rome-app-build:
|
rome-app-build:
|
||||||
docker:
|
docker:
|
||||||
- image: cimg/node:18.18.2
|
- image: cimg/node:18.18.2
|
||||||
@@ -143,6 +172,12 @@ jobs:
|
|||||||
from: dist
|
from: dist
|
||||||
to: "s3://rome-online-production/"
|
to: "s3://rome-online-production/"
|
||||||
arguments: "--exclude '*.map'"
|
arguments: "--exclude '*.map'"
|
||||||
|
- jira/notify:
|
||||||
|
environment: Production (Rome) - Front End
|
||||||
|
environment_type: production
|
||||||
|
pipeline_id: << pipeline.id >>
|
||||||
|
job_type: deployment
|
||||||
|
pipeline_number: << pipeline.number >>
|
||||||
|
|
||||||
promanager-app-build:
|
promanager-app-build:
|
||||||
docker:
|
docker:
|
||||||
@@ -168,6 +203,12 @@ jobs:
|
|||||||
from: dist
|
from: dist
|
||||||
to: "s3://promanager-production/"
|
to: "s3://promanager-production/"
|
||||||
arguments: "--exclude '*.map'"
|
arguments: "--exclude '*.map'"
|
||||||
|
- jira/notify:
|
||||||
|
environment: Production (ProManager) - Front End
|
||||||
|
environment_type: production
|
||||||
|
pipeline_id: << pipeline.id >>
|
||||||
|
job_type: deployment
|
||||||
|
pipeline_number: << pipeline.number >>
|
||||||
|
|
||||||
test-rome-hasura-migrate:
|
test-rome-hasura-migrate:
|
||||||
docker:
|
docker:
|
||||||
@@ -183,10 +224,18 @@ jobs:
|
|||||||
- run:
|
- run:
|
||||||
name: Execute migration
|
name: Execute migration
|
||||||
command: |
|
command: |
|
||||||
npm install hasura-cli -g
|
curl -L https://github.com/hasura/graphql-engine/raw/stable/cli/get.sh | bash
|
||||||
hasura migrate apply --endpoint https://db.test.romeonline.io/ --admin-secret << parameters.secret >>
|
hasura migrate apply --endpoint https://db.test.romeonline.io/ --admin-secret << parameters.secret >>
|
||||||
|
sleep 5
|
||||||
hasura metadata apply --endpoint https://db.test.romeonline.io/ --admin-secret << parameters.secret >>
|
hasura metadata apply --endpoint https://db.test.romeonline.io/ --admin-secret << parameters.secret >>
|
||||||
|
sleep 10
|
||||||
hasura metadata reload --endpoint https://db.test.romeonline.io/ --admin-secret << parameters.secret >>
|
hasura metadata reload --endpoint https://db.test.romeonline.io/ --admin-secret << parameters.secret >>
|
||||||
|
- jira/notify:
|
||||||
|
environment: Test (Rome) - Hasura
|
||||||
|
environment_type: testing
|
||||||
|
pipeline_id: << pipeline.id >>
|
||||||
|
job_type: deployment
|
||||||
|
pipeline_number: << pipeline.number >>
|
||||||
|
|
||||||
test-rome-app-build:
|
test-rome-app-build:
|
||||||
docker:
|
docker:
|
||||||
@@ -212,6 +261,12 @@ jobs:
|
|||||||
from: dist
|
from: dist
|
||||||
to: "s3://rome-online-test/"
|
to: "s3://rome-online-test/"
|
||||||
arguments: "--exclude '*.map'"
|
arguments: "--exclude '*.map'"
|
||||||
|
- jira/notify:
|
||||||
|
environment: Test (Rome) - Front End
|
||||||
|
environment_type: testing
|
||||||
|
pipeline_id: << pipeline.id >>
|
||||||
|
job_type: deployment
|
||||||
|
pipeline_number: << pipeline.number >>
|
||||||
|
|
||||||
test-promanager-app-build:
|
test-promanager-app-build:
|
||||||
docker:
|
docker:
|
||||||
@@ -237,6 +292,12 @@ jobs:
|
|||||||
from: dist
|
from: dist
|
||||||
to: "s3://promanager-testing/"
|
to: "s3://promanager-testing/"
|
||||||
arguments: "--exclude '*.map'"
|
arguments: "--exclude '*.map'"
|
||||||
|
- jira/notify:
|
||||||
|
environment: Test (ProManager) - Front End
|
||||||
|
environment_type: testing
|
||||||
|
pipeline_id: << pipeline.id >>
|
||||||
|
job_type: deployment
|
||||||
|
pipeline_number: << pipeline.number >>
|
||||||
|
|
||||||
test-hasura-migrate:
|
test-hasura-migrate:
|
||||||
docker:
|
docker:
|
||||||
@@ -252,10 +313,18 @@ jobs:
|
|||||||
- run:
|
- run:
|
||||||
name: Execute migration
|
name: Execute migration
|
||||||
command: |
|
command: |
|
||||||
npm install hasura-cli -g
|
curl -L https://github.com/hasura/graphql-engine/raw/stable/cli/get.sh | bash
|
||||||
hasura migrate apply --endpoint https://db.test.bodyshop.app/ --admin-secret << parameters.secret >>
|
hasura migrate apply --endpoint https://db.test.bodyshop.app/ --admin-secret << parameters.secret >>
|
||||||
|
sleep 15
|
||||||
hasura metadata apply --endpoint https://db.test.bodyshop.app/ --admin-secret << parameters.secret >>
|
hasura metadata apply --endpoint https://db.test.bodyshop.app/ --admin-secret << parameters.secret >>
|
||||||
|
sleep 30
|
||||||
hasura metadata reload --endpoint https://db.test.bodyshop.app/ --admin-secret << parameters.secret >>
|
hasura metadata reload --endpoint https://db.test.bodyshop.app/ --admin-secret << parameters.secret >>
|
||||||
|
- jira/notify:
|
||||||
|
environment: Test (ImEX) - Hasura
|
||||||
|
environment_type: testing
|
||||||
|
pipeline_id: << pipeline.id >>
|
||||||
|
job_type: deployment
|
||||||
|
pipeline_number: << pipeline.number >>
|
||||||
|
|
||||||
imex-test-app-build:
|
imex-test-app-build:
|
||||||
docker:
|
docker:
|
||||||
@@ -302,7 +371,12 @@ jobs:
|
|||||||
from: dist
|
from: dist
|
||||||
to: "s3://imex-online-test-beta/"
|
to: "s3://imex-online-test-beta/"
|
||||||
arguments: "--exclude '*.map'"
|
arguments: "--exclude '*.map'"
|
||||||
|
- jira/notify:
|
||||||
|
environment: Test (ImEX) - Front End
|
||||||
|
environment_type: testing
|
||||||
|
pipeline_id: << pipeline.id >>
|
||||||
|
job_type: deployment
|
||||||
|
pipeline_number: << pipeline.number >>
|
||||||
|
|
||||||
admin-app-build:
|
admin-app-build:
|
||||||
docker:
|
docker:
|
||||||
@@ -353,7 +427,7 @@ workflows:
|
|||||||
secret: ${HASURA_PROD_SECRET}
|
secret: ${HASURA_PROD_SECRET}
|
||||||
filters:
|
filters:
|
||||||
branches:
|
branches:
|
||||||
only: master
|
only: master-AIO
|
||||||
- rome-api-deploy:
|
- rome-api-deploy:
|
||||||
filters:
|
filters:
|
||||||
branches:
|
branches:
|
||||||
@@ -363,7 +437,7 @@ workflows:
|
|||||||
branches:
|
branches:
|
||||||
only: master-AIO
|
only: master-AIO
|
||||||
- rome-hasura-migrate:
|
- rome-hasura-migrate:
|
||||||
secret: ${HASURA_PROD_SECRET}
|
secret: ${HASURA_ROME_PROD_SECRET}
|
||||||
filters:
|
filters:
|
||||||
branches:
|
branches:
|
||||||
only: master-AIO
|
only: master-AIO
|
||||||
|
|||||||
24
.dockerignore
Normal file
24
.dockerignore
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
# Directories to exclude
|
||||||
|
.circleci
|
||||||
|
.idea
|
||||||
|
.platform
|
||||||
|
.vscode
|
||||||
|
_reference
|
||||||
|
client
|
||||||
|
redis/dockerdata
|
||||||
|
hasura
|
||||||
|
node_modules
|
||||||
|
# Files to exclude
|
||||||
|
.ebignore
|
||||||
|
.editorconfig
|
||||||
|
.eslintrc.json
|
||||||
|
.gitignore
|
||||||
|
.prettierrc.js
|
||||||
|
Dockerfile
|
||||||
|
README.MD
|
||||||
|
bodyshop_translations.babel
|
||||||
|
docker-compose.yml
|
||||||
|
ecosystem.config.js
|
||||||
|
|
||||||
|
# Optional: Exclude logs and temporary files
|
||||||
|
*.log
|
||||||
0
.localstack/.gitkeep
Normal file
0
.localstack/.gitkeep
Normal file
15
.vscode/launch.json
vendored
15
.vscode/launch.json
vendored
@@ -14,6 +14,21 @@
|
|||||||
"request": "launch",
|
"request": "launch",
|
||||||
"url": "http://localhost:3000",
|
"url": "http://localhost:3000",
|
||||||
"webRoot": "${workspaceRoot}/client/src"
|
"webRoot": "${workspaceRoot}/client/src"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Attach to Node.js in Docker",
|
||||||
|
"type": "node",
|
||||||
|
"request": "attach",
|
||||||
|
"address": "localhost",
|
||||||
|
"port": 9229,
|
||||||
|
"localRoot": "${workspaceFolder}",
|
||||||
|
"remoteRoot": "/app",
|
||||||
|
"protocol": "inspector",
|
||||||
|
"restart": true,
|
||||||
|
"sourceMaps": true,
|
||||||
|
"skipFiles": [
|
||||||
|
"<node_internals>/**"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
47
Dockerfile
Normal file
47
Dockerfile
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
# Use Amazon Linux 2023 as the base image
|
||||||
|
FROM amazonlinux:2023
|
||||||
|
|
||||||
|
# Install Git and Node.js (Amazon Linux 2023 uses the DNF package manager)
|
||||||
|
RUN dnf install -y git \
|
||||||
|
&& curl -sL https://rpm.nodesource.com/setup_20.x | bash - \
|
||||||
|
&& dnf install -y nodejs \
|
||||||
|
&& dnf clean all
|
||||||
|
|
||||||
|
|
||||||
|
# Install dependencies required by node-canvas
|
||||||
|
RUN dnf install -y \
|
||||||
|
gcc \
|
||||||
|
gcc-c++ \
|
||||||
|
cairo-devel \
|
||||||
|
pango-devel \
|
||||||
|
libjpeg-turbo-devel \
|
||||||
|
giflib-devel \
|
||||||
|
libpng-devel \
|
||||||
|
make \
|
||||||
|
python3 \
|
||||||
|
python3-pip \
|
||||||
|
&& dnf clean all
|
||||||
|
|
||||||
|
# Set the working directory
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# This is because our test route uses a git commit hash
|
||||||
|
RUN git config --global --add safe.directory /app
|
||||||
|
|
||||||
|
# Copy package.json and package-lock.json
|
||||||
|
COPY package.json ./
|
||||||
|
|
||||||
|
# Install Nodemon
|
||||||
|
RUN npm install -g nodemon
|
||||||
|
|
||||||
|
# Install dependencies
|
||||||
|
RUN npm i --no-package-lock
|
||||||
|
|
||||||
|
# Copy the rest of your application code
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Expose the port your app runs on (adjust if necessary)
|
||||||
|
EXPOSE 4000 9229
|
||||||
|
|
||||||
|
# Start the application
|
||||||
|
CMD ["nodemon", "--legacy-watch", "--inspect=0.0.0.0:9229", "server.js"]
|
||||||
64
_reference/dockerreadme.md
Normal file
64
_reference/dockerreadme.md
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
# Setting up External Networking and Static IP for WSL2 using Hyper-V
|
||||||
|
|
||||||
|
This guide will walk you through the steps to configure your WSL2 (Windows Subsystem for Linux) instance to use an external Hyper-V virtual switch, enabling it to connect directly to your local network. Additionally, you'll learn how to assign a static IP address to your WSL2 instance.
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
1. **Windows 11**
|
||||||
|
2. **Docker Desktop For Windows (Latest Version)
|
||||||
|
|
||||||
|
# Docker Setup
|
||||||
|
Inside the root of the project exists the `docker-compose.yaml` file, you can simply run
|
||||||
|
`docker-compose up` to launch the backend.
|
||||||
|
|
||||||
|
Things to note:
|
||||||
|
- When installing NPM packages, you will need to rebuild the `node-app` container
|
||||||
|
- Making changes to the server files will restart the `node-app`
|
||||||
|
|
||||||
|
# Local Stack
|
||||||
|
- LocalStack Front end (Optional) - https://apps.microsoft.com/detail/9ntrnft9zws2?hl=en-us&gl=US
|
||||||
|
- http://localhost:4566/_aws/ses will allow you to see emails sent
|
||||||
|
|
||||||
|
# Docker Commands
|
||||||
|
|
||||||
|
## General `docker-compose` Commands:
|
||||||
|
1. Bring up the services, force a rebuild of all services, and do not use the cache: `docker-compose up --build --no-cache`
|
||||||
|
2. Start Containers in Detached Mode: This will run the containers in the background (detached mode): `docker-compose up -d`
|
||||||
|
3. Stop and Remove Containers: Stops and removes the containers gracefully: `docker-compose down`
|
||||||
|
4. Stop containers without removing them: `docker-compose stop`
|
||||||
|
5. Remove Containers, Volumes, and Networks: `docker-compose down --volumes`
|
||||||
|
6. Force rebuild of containers: `docker-compose build --no-cache`
|
||||||
|
7. View running Containers: `docker-compose ps`
|
||||||
|
8. View a specific containers logs: `docker-compose logs <container-name>`
|
||||||
|
9. Scale services (multiple instances of a service): `docker-compose up --scale <container-name>=<instances number> -d`
|
||||||
|
10. Watch a specific containers logs in realtime with timestamps: `docker-compose logs -f --timestamps <container-name>`
|
||||||
|
|
||||||
|
## Volume Management Commands
|
||||||
|
1. List Docker volumes: `docker volume ls`
|
||||||
|
2. Remove Unused volumes `docker volume prune`
|
||||||
|
3. Remove specific volumes `docker volume rm <volume-name>`
|
||||||
|
4. Inspect a volume: `docker volume inspect <volume-name>`
|
||||||
|
|
||||||
|
## Container Image Management Commands:
|
||||||
|
1. List running containers: `docker ps`
|
||||||
|
2. List all containers: `docker os -a`
|
||||||
|
3. Remove Stopped containers: `docker container prune`
|
||||||
|
4. Remove a specific container: `docker container rm <container-name>`
|
||||||
|
5. Remove a specific image: `docker rmi <image-name>:<version>`
|
||||||
|
6. Remove all unused images: `docker image prune -a`
|
||||||
|
|
||||||
|
## Network Management Commands:
|
||||||
|
1. List networks: `docker network ls`
|
||||||
|
2. Inspect a specific network: `docker network inspect <network-name>`
|
||||||
|
3. Remove a specific network: `docker network rm <network-name>`
|
||||||
|
4. Remove unused networks: `docker network prune`
|
||||||
|
|
||||||
|
## Debugging and maintenance:
|
||||||
|
1. Enter a Running container: `docker exec -it <container name> /bin/bash` (could also be `/bin/sh` or for example `redis-cli` on a redis node)
|
||||||
|
2. View container resource usage: `docker stats`
|
||||||
|
3. Check Disk space used by Docker: `docker system df`
|
||||||
|
4. Remove all unused Data (Nuclear option): `docker system prune`
|
||||||
|
|
||||||
|
## Specific examples
|
||||||
|
1. To simulate a Clean state, one should run `docker system prune` followed by `docker volume prune -a`
|
||||||
|
2. You can run `docker-compose up` without the `-d` option, and you will get what is identical to the experience you were used to, this includes being able to control-c and bring the entire stack down
|
||||||
59
_reference/prHelper.html
Normal file
59
_reference/prHelper.html
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>IMEX IO Extractor</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
textarea {
|
||||||
|
width: 100%;
|
||||||
|
height: 200px;
|
||||||
|
}
|
||||||
|
.output-box {
|
||||||
|
margin-top: 20px;
|
||||||
|
padding: 10px;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
background-color: #f9f9f9;
|
||||||
|
min-height: 40px;
|
||||||
|
}
|
||||||
|
.copy-button {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>IMEX IO Extractor</h1>
|
||||||
|
<textarea id="inputText" placeholder="Paste your text here..."></textarea>
|
||||||
|
<br>
|
||||||
|
<button onclick="extractIO()">Extract</button>
|
||||||
|
|
||||||
|
<div class="output-box" id="outputBox" contenteditable="true"></div>
|
||||||
|
<button class="copy-button" onclick="copyToClipboard()">Copy to Clipboard</button>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function extractIO() {
|
||||||
|
const inputText = document.getElementById('inputText').value;
|
||||||
|
const ioNumbers = [...new Set(inputText.match(/IO-\d{4}/g))] // Extract unique IO-#### matches
|
||||||
|
.map(io => ({ io, num: parseInt(io.split('-')[1]) })) // Extract number part for sorting
|
||||||
|
.sort((a, b) => a.num - b.num) // Sort by the number
|
||||||
|
.map(item => item.io); // Extract sorted IO-####
|
||||||
|
|
||||||
|
document.getElementById('outputBox').innerText = ioNumbers.join(', '); // Display horizontally
|
||||||
|
}
|
||||||
|
|
||||||
|
function copyToClipboard() {
|
||||||
|
const outputBox = document.getElementById('outputBox');
|
||||||
|
const range = document.createRange();
|
||||||
|
range.selectNodeContents(outputBox);
|
||||||
|
const selection = window.getSelection();
|
||||||
|
selection.removeAllRanges();
|
||||||
|
selection.addRange(range);
|
||||||
|
document.execCommand('copy');
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -31,3 +31,11 @@
|
|||||||
|
|
||||||
These allow users to turn fields on or off, turning them all off will show the card in the most minimal form
|
These allow users to turn fields on or off, turning them all off will show the card in the most minimal form
|
||||||
|
|
||||||
|
|
||||||
|
### Statistics
|
||||||
|
|
||||||
|
- The statistics section allows users to see accumulations of both jobs on the board, and jobs in production.
|
||||||
|
- you can click a statistic to turn it on and off, and drag and drop the statistics to rearrange them
|
||||||
|
|
||||||
|
### Filters
|
||||||
|
- Allows you to set, and persist filters for estimators and insurance companies
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
20
certs/cert.pem
Normal file
20
certs/cert.pem
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
-----BEGIN CERTIFICATE-----
|
||||||
|
MIIDWzCCAkOgAwIBAgIUD/QBSAXy/AlJ/cS4DaPWJLpChxgwDQYJKoZIhvcNAQEL
|
||||||
|
BQAwPTELMAkGA1UEBhMCQ0ExCzAJBgNVBAgMAk9OMSEwHwYDVQQKDBhJbnRlcm5l
|
||||||
|
dCBXaWRnaXRzIFB0eSBMdGQwHhcNMjQwOTA5MTU0MjA1WhcNMjUwOTA5MTU0MjA1
|
||||||
|
WjA9MQswCQYDVQQGEwJDQTELMAkGA1UECAwCT04xITAfBgNVBAoMGEludGVybmV0
|
||||||
|
IFdpZGdpdHMgUHR5IEx0ZDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
|
||||||
|
AKSd0l7NJCNBwvtPU+dVPQkteg0AfC3sGqRnZMQteCRVa2oIgC4NoF3A9BK/yHbF
|
||||||
|
ZF25OnXTck5vzc8yb3v73ndfTD9ASKNoiaZE84/GFBsxqlKR8cs0qVwzuAsdijMv
|
||||||
|
vlMPNlMRyE1Rb7nR6HXGkPXNyxgMko03NXPkvIje9zRudm0Lf8L4q/hPyPkS7Mrm
|
||||||
|
/uQfAAJe+xFcupkEX2XY7r0x1C+z6E8lA1UcuhK3SHdW7CWYqp1vU5/dnnUiXwCa
|
||||||
|
GiC6Y1bCJB0pDAVISzy3JUDdINZdiqGR+y8ho3pstChf2mp/76s3N9eG9KA/qaFK
|
||||||
|
BrGk2PvCoZ8/Aj1aMsRYFHECAwEAAaNTMFEwHQYDVR0OBBYEFDLJ2fbWP4VUJgOp
|
||||||
|
PSs+NGHcVgRmMB8GA1UdIwQYMBaAFDLJ2fbWP4VUJgOpPSs+NGHcVgRmMA8GA1Ud
|
||||||
|
EwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBABfv5ut/y03atq0NMB0jeDY4
|
||||||
|
AvW4ukk0k1svyqxFZCw9o7m2lHb/IjmVrZG1Sj4JWrrSv0s02ccb26/t6vazNa5L
|
||||||
|
Powe3eyfHgfjTZJmgs8hyeMwKS0wWk/SPuu9JDhIJakiquqD+UVBGkHpP+XYvhDv
|
||||||
|
vhS2XRlW+aEjpUmr1oCyyrc6WbzrYRNadqEsn/AxwcMyUbht3Ugjkg+OpidcTIQp
|
||||||
|
5lv63waKo6I1vQofzBQ3L7JYsKo8kC0vAP7wkLxvzBii335uZJzzpFYFVOyVNezi
|
||||||
|
dJdazPbRYbXz4LjltdEn/SNfRuKX8ZRiN2OSo7OfSrZaMTS87SfCSFJGgQM8Yrk=
|
||||||
|
-----END CERTIFICATE-----
|
||||||
27
certs/id_rsa
Normal file
27
certs/id_rsa
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
-----BEGIN OPENSSH PRIVATE KEY-----
|
||||||
|
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdzc2gtcn
|
||||||
|
NhAAAAAwEAAQAAAQEAvNl5fuVmLNv72BZNxnTqX5CHf5Xi8UxjYaYxHITSCx7blnhpVYLd
|
||||||
|
qXvcOWXzbsfjch/den73QiW4n2FYz75oGMhUGlOYzdWKA9I9Sj09Qy1R06RhwDiZGd5qaM
|
||||||
|
swEeXpkNmi2u4Qd2kJeDfUQUigjC09V81O/vrniGtQAJScfiG/itdm+Ufn09Z4MYk0HWjq
|
||||||
|
iDokNEskoEPsibYIrb+Q6vdtuPkZO+wU/smXhPtgw5ST6oQdmm/gVNsRg5XNzxrire+z1G
|
||||||
|
WatnnVL3hPnnfpnf8W589dyms7GGJwhPerSGTN1bn0T4+9C69Cd7LBJtxiuFdRmdlGLLLP
|
||||||
|
RR48Rur71wAAA9AEfVsdBH1bHQAAAAdzc2gtcnNhAAABAQC82Xl+5WYs2/vYFk3GdOpfkI
|
||||||
|
d/leLxTGNhpjEchNILHtuWeGlVgt2pe9w5ZfNux+NyH916fvdCJbifYVjPvmgYyFQaU5jN
|
||||||
|
1YoD0j1KPT1DLVHTpGHAOJkZ3mpoyzAR5emQ2aLa7hB3aQl4N9RBSKCMLT1XzU7++ueIa1
|
||||||
|
AAlJx+Ib+K12b5R+fT1ngxiTQdaOqIOiQ0SySgQ+yJtgitv5Dq9224+Rk77BT+yZeE+2DD
|
||||||
|
lJPqhB2ab+BU2xGDlc3PGuKt77PUZZq2edUveE+ed+md/xbnz13KazsYYnCE96tIZM3Vuf
|
||||||
|
RPj70Lr0J3ssEm3GK4V1GZ2UYsss9FHjxG6vvXAAAAAwEAAQAAAQAQTosSLQbMmtY9S3e9
|
||||||
|
yjyusdExcCTfhyQRu4MEHmfws+JsNMuLqbgwOVTD1AzYJQR7x0qdmDcLjCxL/uDnV16vvS
|
||||||
|
Sd/Vf1dhnryIyoS29tzI0DRG94ZKq7tBvmHp1w/jRT4KcSVnovhW9e5Rs74+SRFhr06PKI
|
||||||
|
S+wQOIv48Nwue9+QUMsMCpWgKXHx7SHNTHvnAfqdhi9O29SWlMA+v+mELZ5Cl+HU0UTt2I
|
||||||
|
A1BxOe1N8FjN7KE2viJexsl3is1PuqMkpLl/wyHBJTVzUadl6DRALJQIm7/YO5goE72YOV
|
||||||
|
Lpo27do3zjhC87dlKdATvZUzfKV0LuUVdxq/PNDZMUbBAAAAgQDShAqDZiDrdTUaGXfUVm
|
||||||
|
QzcnVNbh2/KgZh4uux9QNHST562W6cnN7qxoRwVrM4BCOk1Kl73QQZW4nDvXX3PVC5j038
|
||||||
|
8AXkcBHS9j9f4h72ue7D2jqlbHFa7aGU9zYgk9mbBF+GX3tDntkAIQjLtwOLfj1iiJ/clX
|
||||||
|
mHFUAY1V4L8AAAAIEA3E4t/v0yU5D9AOI0r17UNYqfeyDoKAEDR4QbbFjO1l0kLnEJy7Zx
|
||||||
|
Mhj18GilYg2y0P0v8dSM/oWXS8Hua2t5i9Exlv6gHhGlQ80mwYcVGIxewZ/pPeCPw0U+kt
|
||||||
|
EKUjt09m9Oe7+6xHQsTBj9hY8/vqPmQwRalZFcLdhHiDiVKTcAAACBANtykaPXdVzEFx7D
|
||||||
|
UOlsjVL7zM0EVOFXf9JJQ6BhazhmsEI2PYt3IpgGMo8cXkoUofAOIYjf421AabN1BqSO5J
|
||||||
|
XTMxM0ZV3JmLLi804Mu9h1iFrVTBdLYOMJdc2VCo1EwHWpo9SXOyjxce/znvcIOU04aZhu
|
||||||
|
TaPg816X+E+gw5JhAAAAFGRhdmVARGF2ZVJpY2hlci1JTUVYAQIDBAUG
|
||||||
|
-----END OPENSSH PRIVATE KEY-----
|
||||||
1
certs/id_rsa.pub
Normal file
1
certs/id_rsa.pub
Normal file
@@ -0,0 +1 @@
|
|||||||
|
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC82Xl+5WYs2/vYFk3GdOpfkId/leLxTGNhpjEchNILHtuWeGlVgt2pe9w5ZfNux+NyH916fvdCJbifYVjPvmgYyFQaU5jN1YoD0j1KPT1DLVHTpGHAOJkZ3mpoyzAR5emQ2aLa7hB3aQl4N9RBSKCMLT1XzU7++ueIa1AAlJx+Ib+K12b5R+fT1ngxiTQdaOqIOiQ0SySgQ+yJtgitv5Dq9224+Rk77BT+yZeE+2DDlJPqhB2ab+BU2xGDlc3PGuKt77PUZZq2edUveE+ed+md/xbnz13KazsYYnCE96tIZM3VufRPj70Lr0J3ssEm3GK4V1GZ2UYsss9FHjxG6vvX dave@DaveRicher-IMEX
|
||||||
28
certs/key.pem
Normal file
28
certs/key.pem
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
-----BEGIN PRIVATE KEY-----
|
||||||
|
MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCkndJezSQjQcL7
|
||||||
|
T1PnVT0JLXoNAHwt7BqkZ2TELXgkVWtqCIAuDaBdwPQSv8h2xWRduTp103JOb83P
|
||||||
|
Mm97+953X0w/QEijaImmRPOPxhQbMapSkfHLNKlcM7gLHYozL75TDzZTEchNUW+5
|
||||||
|
0eh1xpD1zcsYDJKNNzVz5LyI3vc0bnZtC3/C+Kv4T8j5EuzK5v7kHwACXvsRXLqZ
|
||||||
|
BF9l2O69MdQvs+hPJQNVHLoSt0h3VuwlmKqdb1Of3Z51Il8AmhogumNWwiQdKQwF
|
||||||
|
SEs8tyVA3SDWXYqhkfsvIaN6bLQoX9pqf++rNzfXhvSgP6mhSgaxpNj7wqGfPwI9
|
||||||
|
WjLEWBRxAgMBAAECggEAUNpHYlLFxh9dokujPUMreF+Cy/IKDBAkQc2au5RNpyLh
|
||||||
|
YDIOqw/8TTAhcTgLQPLQygvZP9f8E7RsVLFD+pSJ/v2qmIJ9au1Edor1Sg+S/oxV
|
||||||
|
SLrwFMunx2aLpcH7iAqSI3+cQg7A3+D4zD7iOz6tIl3Su9wo+v073tFhHKTOrEwv
|
||||||
|
Qgr9Jf3viIiKV1ym+uQEVQndocfsj46FnFpXTQ2qs7kAF6FgAOLDGfQQwzkiqEBD
|
||||||
|
NsqsDmbYIx6foZL+DEz1ZVO2M5B+xxpbNK82KwuQilVpimW8ui4LZHCe+RIFzt9+
|
||||||
|
BK6KGlLpSEwTFliivI3nahy18JzskZsfyah0CPZlQQKBgQDVv+A0qIPGvOP3Sx+9
|
||||||
|
HyeQCV23SkvvSvw8p8pMB0gvwv63YdJ7N/rJzBGS6YUHFWWZZgEeTgkJ6VJvoe0r
|
||||||
|
8JL1el9uSUa7f0eayjmFBOGuzpktNVdIn2Tg7A9MWA4JqPNNC69RMOh86ewGD4J3
|
||||||
|
a8Hz2a1bHxAmy/AZt2ukypY6eQKBgQDFJ7kqeOPkRBz9WbALRgVIXo8YWf5di0sQ
|
||||||
|
r0HC03GAISHQ725A2IFBPHJWeqj0jaMiIZD0y+Obgp7KAskrJaLfsd7Ug775kFfw
|
||||||
|
oUI9UAl6kRuPKvm3BaVAm46SQm+56VsgxTi73YN0NUp75THHZgAJjepF9zSpVJxq
|
||||||
|
VY9DjEGruQKBgQCQCpGIatcCol/tUg69X7VFd0pULhkl1J5OMbQ9r9qRdRI5eg5h
|
||||||
|
QsQaIQ7mtb8TmvOwf/DY/zVQHI+U8sXlCmW+TwzoQTENQSR7xzMj1LpRFqBaustr
|
||||||
|
AR72A537kItFLzll/i3SxOam5uxK2UDOQSuerF4KPdCglGXkrpo3nt3F4QKBgQCa
|
||||||
|
RArPAOjQo7tLQfJN3+wiRFsTYtd1uphx5bA/EdOtvj8HjVFnzADXWsTchf3N3UXY
|
||||||
|
XwtdgGwIMpys1KEz8a8P+c2x26SDAj7NOmDqOMYx8Xju/WGHpBM6Cn30U6e4gK+d
|
||||||
|
ZLSPyzQgqdIuP5hDvbwpvbGiLVw3Ys1BJtGCuSxpgQJ/eHnRiuSi5Zq5jGg+GpA+
|
||||||
|
FEEc9NCy772rR+4uzEOqyIwqewffqzSuVWuIsY/8MP3fh+NDxl/mU6cB5QVeD54Z
|
||||||
|
JZUKwmpM26muiM6WvVnM4ExPdSGA2+l4pZjby/KKd6F/w0tgZ1jb9Pb2/0vN3qVA
|
||||||
|
Y4U4XNTMt2fxUACqiL4SHA==
|
||||||
|
-----END PRIVATE KEY-----
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
VITE_APP_GRAPHQL_ENDPOINT=https://db.dev.bodyshop.app/v1/graphql
|
VITE_APP_GRAPHQL_ENDPOINT=https://db.dev.imex.online/v1/graphql
|
||||||
VITE_APP_GRAPHQL_ENDPOINT_WS=wss://db.dev.bodyshop.app/v1/graphql
|
VITE_APP_GRAPHQL_ENDPOINT_WS=wss://db.dev.imex.online/v1/graphql
|
||||||
VITE_APP_GA_CODE=231099835
|
VITE_APP_GA_CODE=231099835
|
||||||
VITE_APP_FIREBASE_CONFIG={"apiKey":"AIzaSyDPLT8GiDHDR1R4nI66Qi0BY1aYviDPioc","authDomain":"imex-dev.firebaseapp.com","databaseURL":"https://imex-dev.firebaseio.com","projectId":"imex-dev","storageBucket":"imex-dev.appspot.com","messagingSenderId":"759548147434","appId":"1:759548147434:web:e8239868a48ceb36700993","measurementId":"G-K5XRBVVB4S"}
|
VITE_APP_FIREBASE_CONFIG={"apiKey":"AIzaSyDPLT8GiDHDR1R4nI66Qi0BY1aYviDPioc","authDomain":"imex-dev.firebaseapp.com","databaseURL":"https://imex-dev.firebaseio.com","projectId":"imex-dev","storageBucket":"imex-dev.appspot.com","messagingSenderId":"759548147434","appId":"1:759548147434:web:e8239868a48ceb36700993","measurementId":"G-K5XRBVVB4S"}
|
||||||
VITE_APP_CLOUDINARY_ENDPOINT_API=https://api.cloudinary.com/v1_1/io-test
|
VITE_APP_CLOUDINARY_ENDPOINT_API=https://api.cloudinary.com/v1_1/io-test
|
||||||
@@ -8,7 +8,7 @@ VITE_APP_CLOUDINARY_API_KEY=957865933348715
|
|||||||
VITE_APP_CLOUDINARY_THUMB_TRANSFORMATIONS=c_fill,h_250,w_250
|
VITE_APP_CLOUDINARY_THUMB_TRANSFORMATIONS=c_fill,h_250,w_250
|
||||||
VITE_APP_FIREBASE_PUBLIC_VAPID_KEY='BG3tzU7L2BXlGZ_3VLK4PNaRceoEXEnmHfxcVbRMF5o5g05ejslhVPki9kBM9cBBT-08Ad9kN3HSpS6JmrWD6h4'
|
VITE_APP_FIREBASE_PUBLIC_VAPID_KEY='BG3tzU7L2BXlGZ_3VLK4PNaRceoEXEnmHfxcVbRMF5o5g05ejslhVPki9kBM9cBBT-08Ad9kN3HSpS6JmrWD6h4'
|
||||||
VITE_APP_STRIPE_PUBLIC_KEY=pk_test_51GqB4TJl3nQjrZ0wCQWAxAhlNF8jKe0tipIa6ExBaxwJGitwvFsIZUEua4dUzaMIAuXp4qwYHXx7lgjyQSwP0Pe900vzm38C7g
|
VITE_APP_STRIPE_PUBLIC_KEY=pk_test_51GqB4TJl3nQjrZ0wCQWAxAhlNF8jKe0tipIa6ExBaxwJGitwvFsIZUEua4dUzaMIAuXp4qwYHXx7lgjyQSwP0Pe900vzm38C7g
|
||||||
VITE_APP_AXIOS_BASE_API_URL=http://localhost:4000
|
VITE_APP_AXIOS_BASE_API_URL=/api/
|
||||||
VITE_APP_REPORTS_SERVER_URL=https://reports3.test.imex.online
|
VITE_APP_REPORTS_SERVER_URL=https://reports3.test.imex.online
|
||||||
VITE_APP_SPLIT_API=ts615lqgnmk84thn72uk18uu5pgce6e0l4rc
|
VITE_APP_SPLIT_API=ts615lqgnmk84thn72uk18uu5pgce6e0l4rc
|
||||||
VITE_APP_INSTANCE=IMEX
|
VITE_APP_INSTANCE=IMEX
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
VITE_APP_GRAPHQL_ENDPOINT=https://db.dev.bodyshop.app/v1/graphql
|
VITE_APP_GRAPHQL_ENDPOINT=https://db.dev.imex.online/v1/graphql
|
||||||
VITE_APP_GRAPHQL_ENDPOINT_WS=wss://db.dev.bodyshop.app/v1/graphql
|
VITE_APP_GRAPHQL_ENDPOINT_WS=wss://db.dev.imex.online/v1/graphql
|
||||||
VITE_APP_GA_CODE=231099835
|
VITE_APP_GA_CODE=231099835
|
||||||
VITE_APP_FIREBASE_CONFIG={"apiKey":"AIzaSyDPLT8GiDHDR1R4nI66Qi0BY1aYviDPioc","authDomain":"imex-dev.firebaseapp.com","databaseURL":"https://imex-dev.firebaseio.com","projectId":"imex-dev","storageBucket":"imex-dev.appspot.com","messagingSenderId":"759548147434","appId":"1:759548147434:web:e8239868a48ceb36700993","measurementId":"G-K5XRBVVB4S"}
|
VITE_APP_FIREBASE_CONFIG={"apiKey":"AIzaSyDPLT8GiDHDR1R4nI66Qi0BY1aYviDPioc","authDomain":"imex-dev.firebaseapp.com","databaseURL":"https://imex-dev.firebaseio.com","projectId":"imex-dev","storageBucket":"imex-dev.appspot.com","messagingSenderId":"759548147434","appId":"1:759548147434:web:e8239868a48ceb36700993","measurementId":"G-K5XRBVVB4S"}
|
||||||
VITE_APP_CLOUDINARY_ENDPOINT_API=https://api.cloudinary.com/v1_1/io-test
|
VITE_APP_CLOUDINARY_ENDPOINT_API=https://api.cloudinary.com/v1_1/io-test
|
||||||
@@ -8,7 +8,7 @@ VITE_APP_CLOUDINARY_API_KEY=957865933348715
|
|||||||
VITE_APP_CLOUDINARY_THUMB_TRANSFORMATIONS=c_fill,h_250,w_250
|
VITE_APP_CLOUDINARY_THUMB_TRANSFORMATIONS=c_fill,h_250,w_250
|
||||||
VITE_APP_FIREBASE_PUBLIC_VAPID_KEY='BG3tzU7L2BXlGZ_3VLK4PNaRceoEXEnmHfxcVbRMF5o5g05ejslhVPki9kBM9cBBT-08Ad9kN3HSpS6JmrWD6h4'
|
VITE_APP_FIREBASE_PUBLIC_VAPID_KEY='BG3tzU7L2BXlGZ_3VLK4PNaRceoEXEnmHfxcVbRMF5o5g05ejslhVPki9kBM9cBBT-08Ad9kN3HSpS6JmrWD6h4'
|
||||||
VITE_APP_STRIPE_PUBLIC_KEY=pk_test_51GqB4TJl3nQjrZ0wCQWAxAhlNF8jKe0tipIa6ExBaxwJGitwvFsIZUEua4dUzaMIAuXp4qwYHXx7lgjyQSwP0Pe900vzm38C7g
|
VITE_APP_STRIPE_PUBLIC_KEY=pk_test_51GqB4TJl3nQjrZ0wCQWAxAhlNF8jKe0tipIa6ExBaxwJGitwvFsIZUEua4dUzaMIAuXp4qwYHXx7lgjyQSwP0Pe900vzm38C7g
|
||||||
VITE_APP_AXIOS_BASE_API_URL=http://localhost:4000
|
VITE_APP_AXIOS_BASE_API_URL=/api/
|
||||||
VITE_APP_REPORTS_SERVER_URL=https://reports3.test.imex.online
|
VITE_APP_REPORTS_SERVER_URL=https://reports3.test.imex.online
|
||||||
VITE_APP_SPLIT_API=ts615lqgnmk84thn72uk18uu5pgce6e0l4rc
|
VITE_APP_SPLIT_API=ts615lqgnmk84thn72uk18uu5pgce6e0l4rc
|
||||||
VITE_APP_INSTANCE=PROMANAGER
|
VITE_APP_INSTANCE=PROMANAGER
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
VITE_APP_GRAPHQL_ENDPOINT=https://db.dev.bodyshop.app/v1/graphql
|
VITE_APP_GRAPHQL_ENDPOINT=https://db.dev.imex.online/v1/graphql
|
||||||
VITE_APP_GRAPHQL_ENDPOINT_WS=wss://db.dev.bodyshop.app/v1/graphql
|
VITE_APP_GRAPHQL_ENDPOINT_WS=wss://db.dev.imex.online/v1/graphql
|
||||||
VITE_APP_GA_CODE=231099835
|
VITE_APP_GA_CODE=231099835
|
||||||
VITE_APP_FIREBASE_CONFIG={ "apiKey": "AIzaSyAuLQR9SV5LsVxjU8wh9hvFLdhcAHU6cxE", "authDomain": "rome-prod-1.firebaseapp.com", "projectId": "rome-prod-1", "storageBucket": "rome-prod-1.appspot.com", "messagingSenderId": "147786367145", "appId": "1:147786367145:web:9d4cba68071c3f29a8a9b8", "measurementId": "G-G8Z9DRHTZS"}
|
# VITE_APP_FIREBASE_CONFIG={ "apiKey": "AIzaSyAuLQR9SV5LsVxjU8wh9hvFLdhcAHU6cxE", "authDomain": "rome-prod-1.firebaseapp.com", "projectId": "rome-prod-1", "storageBucket": "rome-prod-1.appspot.com", "messagingSenderId": "147786367145", "appId": "1:147786367145:web:9d4cba68071c3f29a8a9b8", "measurementId": "G-G8Z9DRHTZS"}
|
||||||
|
VITE_APP_FIREBASE_CONFIG={"apiKey":"AIzaSyDPLT8GiDHDR1R4nI66Qi0BY1aYviDPioc","authDomain":"imex-dev.firebaseapp.com","databaseURL":"https://imex-dev.firebaseio.com","projectId":"imex-dev","storageBucket":"imex-dev.appspot.com","messagingSenderId":"759548147434","appId":"1:759548147434:web:e8239868a48ceb36700993","measurementId":"G-K5XRBVVB4S"}
|
||||||
VITE_APP_CLOUDINARY_ENDPOINT_API=https://api.cloudinary.com/v1_1/io-test
|
VITE_APP_CLOUDINARY_ENDPOINT_API=https://api.cloudinary.com/v1_1/io-test
|
||||||
VITE_APP_CLOUDINARY_ENDPOINT=https://res.cloudinary.com/io-test
|
VITE_APP_CLOUDINARY_ENDPOINT=https://res.cloudinary.com/io-test
|
||||||
VITE_APP_CLOUDINARY_API_KEY=957865933348715
|
VITE_APP_CLOUDINARY_API_KEY=957865933348715
|
||||||
VITE_APP_CLOUDINARY_THUMB_TRANSFORMATIONS=c_fill,h_250,w_250
|
VITE_APP_CLOUDINARY_THUMB_TRANSFORMATIONS=c_fill,h_250,w_250
|
||||||
VITE_APP_FIREBASE_PUBLIC_VAPID_KEY='BP1B7ZTYpn-KMt6nOxlld6aS8Skt3Q7ZLEqP0hAvGHxG4UojPYiXZ6kPlzZkUC5jH-EcWXomTLtmadAIxurfcHo'
|
VITE_APP_FIREBASE_PUBLIC_VAPID_KEY='BP1B7ZTYpn-KMt6nOxlld6aS8Skt3Q7ZLEqP0hAvGHxG4UojPYiXZ6kPlzZkUC5jH-EcWXomTLtmadAIxurfcHo'
|
||||||
VITE_APP_STRIPE_PUBLIC_KEY=pk_test_51GqB4TJl3nQjrZ0wCQWAxAhlNF8jKe0tipIa6ExBaxwJGitwvFsIZUEua4dUzaMIAuXp4qwYHXx7lgjyQSwP0Pe900vzm38C7g
|
VITE_APP_STRIPE_PUBLIC_KEY=pk_test_51GqB4TJl3nQjrZ0wCQWAxAhlNF8jKe0tipIa6ExBaxwJGitwvFsIZUEua4dUzaMIAuXp4qwYHXx7lgjyQSwP0Pe900vzm38C7g
|
||||||
VITE_APP_AXIOS_BASE_API_URL=http://localhost:4000
|
VITE_APP_AXIOS_BASE_API_URL=/api/
|
||||||
VITE_APP_REPORTS_SERVER_URL=https://reports3.test.imex.online
|
VITE_APP_REPORTS_SERVER_URL=https://reports3.test.imex.online
|
||||||
VITE_APP_SPLIT_API=ts615lqgnmk84thn72uk18uu5pgce6e0l4rc
|
VITE_APP_SPLIT_API=ts615lqgnmk84thn72uk18uu5pgce6e0l4rc
|
||||||
VITE_APP_COUNTRY=USA
|
VITE_APP_COUNTRY=USA
|
||||||
|
|||||||
1
client/.gitignore
vendored
1
client/.gitignore
vendored
@@ -1,3 +1,4 @@
|
|||||||
|
|
||||||
# Sentry Config File
|
# Sentry Config File
|
||||||
.sentryclirc
|
.sentryclirc
|
||||||
|
/dev-dist
|
||||||
|
|||||||
@@ -12,6 +12,6 @@ module.exports = defineConfig({
|
|||||||
setupNodeEvents(on, config) {
|
setupNodeEvents(on, config) {
|
||||||
return require("./cypress/plugins/index.js")(on, config);
|
return require("./cypress/plugins/index.js")(on, config);
|
||||||
},
|
},
|
||||||
baseUrl: "http://localhost:3000"
|
baseUrl: "https://localhost:3000"
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,174 +1,126 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8"/>
|
||||||
<% if (env.VITE_APP_INSTANCE === 'IMEX') { %>
|
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
|
||||||
<link rel="icon" href="/favicon.png" />
|
<meta http-equiv="Pragma" content="no-cache">
|
||||||
<% } %> <% if (env.VITE_APP_INSTANCE === 'ROME') { %>
|
<meta http-equiv="Expires" content="0">
|
||||||
<link rel="icon" href="/ro-favicon.png" />
|
<% if (env.VITE_APP_INSTANCE === 'IMEX') { %>
|
||||||
<% } %> <% if (env.VITE_APP_INSTANCE === 'PROMANAGER') { %>
|
<link rel="icon" href="/favicon.png"/>
|
||||||
<link rel="icon" href="/pm/pm-favicon.ico" />
|
<% } %> <% if (env.VITE_APP_INSTANCE === 'ROME') { %>
|
||||||
<% } %>
|
<link rel="icon" href="/ro-favicon.png"/>
|
||||||
|
<% } %> <% if (env.VITE_APP_INSTANCE === 'PROMANAGER') { %>
|
||||||
|
<link rel="icon" href="/pm/pm-favicon.ico"/>
|
||||||
|
<% } %>
|
||||||
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||||
<meta name="theme-color" content="#1690ff" />
|
<meta name="theme-color" content="#1690ff"/>
|
||||||
<!-- <link rel="apple-touch-icon" href="logo192.png" /> -->
|
<!-- <link rel="apple-touch-icon" href="logo192.png" /> -->
|
||||||
<!-- TODO:AIo Update the individual logos for each.-->
|
<!-- TODO:AIo Update the individual logos for each.-->
|
||||||
<link rel="apple-touch-icon" href="public/logo192.png" />
|
<link rel="apple-touch-icon" href="/logo192.png"/>
|
||||||
<link rel="mask-icon" href="/mask-icon.svg" color="#FFFFFF">
|
<link rel="mask-icon" href="/mask-icon.svg" color="#FFFFFF">
|
||||||
<!--
|
<!--
|
||||||
manifest.json provides metadata used when your web app is installed on a
|
manifest.json provides metadata used when your web app is installed on a
|
||||||
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||||
-->
|
-->
|
||||||
<!--
|
<!--
|
||||||
Notice the use of %PUBLIC_URL% in the tags above.
|
Notice the use of %PUBLIC_URL% in the tags above.
|
||||||
It will be replaced with the URL of the `public` folder during the build.
|
It will be replaced with the URL of the `public` folder during the build.
|
||||||
Only files inside the `public` folder can be referenced from the HTML.
|
Only files inside the `public` folder can be referenced from the HTML.
|
||||||
|
|
||||||
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
||||||
work correctly both with client-side routing and a non-root public URL.
|
work correctly both with client-side routing and a non-root public URL.
|
||||||
Learn how to configure a non-root public URL by running `npm run build`.
|
Learn how to configure a non-root public URL by running `npm run build`.
|
||||||
-->
|
-->
|
||||||
<% if (env.VITE_APP_INSTANCE === 'IMEX') { %>
|
<% if (env.VITE_APP_INSTANCE === 'IMEX') { %>
|
||||||
<meta name="description" content="ImEX Online" />
|
<meta name="description" content="ImEX Online"/>
|
||||||
<title>ImEX Online</title>
|
<title>ImEX Online</title>
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
window.$crisp = [];
|
window.$crisp = [];
|
||||||
window.CRISP_WEBSITE_ID = '36724f62-2eb0-4b29-9cdd-9905fb99913e';
|
window.CRISP_WEBSITE_ID = '36724f62-2eb0-4b29-9cdd-9905fb99913e';
|
||||||
(function () {
|
(function () {
|
||||||
d = document;
|
d = document;
|
||||||
s = d.createElement('script');
|
s = d.createElement('script');
|
||||||
s.src = 'https://client.crisp.chat/l.js';
|
s.src = 'https://client.crisp.chat/l.js';
|
||||||
s.async = 1;
|
s.async = 1;
|
||||||
d.getElementsByTagName('head')[0].appendChild(s);
|
d.getElementsByTagName('head')[0].appendChild(s);
|
||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
<% } %> <% if (env.VITE_APP_INSTANCE === 'ROME') { %>
|
<% } %> <% if (env.VITE_APP_INSTANCE === 'ROME') { %>
|
||||||
<meta name="description" content="Rome Online" />
|
<meta name="description" content="Rome Online"/>
|
||||||
<title>Rome Online</title>
|
<title>Rome Online</title>
|
||||||
|
<script type="text/javascript" id="zsiqchat">
|
||||||
|
var $zoho = $zoho || {};
|
||||||
|
$zoho.salesiq = $zoho.salesiq || {
|
||||||
|
widgetcode: "siq01bb8ac617280bdacddfeb528f07734dadc64ef3f05efef9f769c1ec171af666",
|
||||||
|
values: {},
|
||||||
|
ready: function () {
|
||||||
|
}
|
||||||
|
};
|
||||||
|
var d = document;
|
||||||
|
s = d.createElement("script");
|
||||||
|
s.type = "text/javascript";
|
||||||
|
s.id = "zsiqscript";
|
||||||
|
s.defer = true;
|
||||||
|
s.src = "https://salesiq.zohopublic.com/widget";
|
||||||
|
t = d.getElementsByTagName("script")[0];
|
||||||
|
t.parentNode.insertBefore(s, t);
|
||||||
|
</script>
|
||||||
|
|
||||||
<!--Use the below code snippet to provide real time updates to the live chat plugin without the need of copying and paste each time to your website when changes are made via PBX-->
|
<% } %> <% if (env.VITE_APP_INSTANCE === 'PROMANAGER') { %>
|
||||||
|
<title>ProManager</title>
|
||||||
|
<meta name="description" content="ProManager"/>
|
||||||
|
|
||||||
<call-us-selector phonesystem-url=https://rometech.east.3cx.us:5001 party="LiveChat528346"></call-us-selector>
|
<% } %>
|
||||||
|
<script>
|
||||||
|
!(function () {
|
||||||
|
'use strict';
|
||||||
|
var e = [
|
||||||
|
'debug',
|
||||||
|
'destroy',
|
||||||
|
'do',
|
||||||
|
'help',
|
||||||
|
'identify',
|
||||||
|
'is',
|
||||||
|
'off',
|
||||||
|
'on',
|
||||||
|
'ready',
|
||||||
|
'render',
|
||||||
|
'reset',
|
||||||
|
'safe',
|
||||||
|
'set',
|
||||||
|
];
|
||||||
|
if (window.noticeable) console.warn('Noticeable SDK code snippet loaded more than once');
|
||||||
|
else {
|
||||||
|
var n = (window.noticeable = window.noticeable || []);
|
||||||
|
|
||||||
<!--Incase you don't want real time updates to the live chat plugin when options are changed, use the below code snippet. Please note that each time you change the settings you will need to copy and paste the snippet code to your website-->
|
function t(e) {
|
||||||
|
return function () {
|
||||||
<!--<call-us
|
var t = Array.prototype.slice.call(arguments);
|
||||||
|
return t.unshift(e), n.push(t), n;
|
||||||
phonesystem-url=https://rometech.east.3cx.us:5001
|
};
|
||||||
|
|
||||||
style="position:fixed;font-size:16px;line-height:17px;z-index: 99999;right: 20px; bottom: 20px;"
|
|
||||||
|
|
||||||
id="wp-live-chat-by-3CX"
|
|
||||||
|
|
||||||
minimized="true"
|
|
||||||
|
|
||||||
animation-style="noanimation"
|
|
||||||
|
|
||||||
party="LiveChat528346"
|
|
||||||
|
|
||||||
minimized-style="bubbleright"
|
|
||||||
|
|
||||||
allow-call="true"
|
|
||||||
|
|
||||||
allow-video="false"
|
|
||||||
|
|
||||||
allow-soundnotifications="true"
|
|
||||||
|
|
||||||
enable-mute="true"
|
|
||||||
|
|
||||||
enable-onmobile="true"
|
|
||||||
|
|
||||||
offline-enabled="true"
|
|
||||||
|
|
||||||
enable="true"
|
|
||||||
|
|
||||||
ignore-queueownership="false"
|
|
||||||
|
|
||||||
authentication="both"
|
|
||||||
|
|
||||||
show-operator-actual-name="true"
|
|
||||||
|
|
||||||
aknowledge-received="true"
|
|
||||||
|
|
||||||
gdpr-enabled="false"
|
|
||||||
|
|
||||||
message-userinfo-format="name"
|
|
||||||
|
|
||||||
message-dateformat="both"
|
|
||||||
|
|
||||||
lang="browser"
|
|
||||||
|
|
||||||
button-icon-type="default"
|
|
||||||
|
|
||||||
greeting-visibility="none"
|
|
||||||
|
|
||||||
greeting-offline-visibility="none"
|
|
||||||
|
|
||||||
chat-delay="2000"
|
|
||||||
|
|
||||||
enable-direct-call="true"
|
|
||||||
|
|
||||||
enable-ga="false"
|
|
||||||
|
|
||||||
></call-us>-->
|
|
||||||
|
|
||||||
<script defer src=https://downloads-global.3cx.com/downloads/livechatandtalk/v1/callus.js id="tcx-callus-js" charset="utf-8"></script>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<% } %> <% if (env.VITE_APP_INSTANCE === 'PROMANAGER') { %>
|
|
||||||
<title>ProManager</title>
|
|
||||||
<meta name="description" content="ProManager" />
|
|
||||||
|
|
||||||
<% } %>
|
|
||||||
<script>
|
|
||||||
!(function () {
|
|
||||||
'use strict';
|
|
||||||
var e = [
|
|
||||||
'debug',
|
|
||||||
'destroy',
|
|
||||||
'do',
|
|
||||||
'help',
|
|
||||||
'identify',
|
|
||||||
'is',
|
|
||||||
'off',
|
|
||||||
'on',
|
|
||||||
'ready',
|
|
||||||
'render',
|
|
||||||
'reset',
|
|
||||||
'safe',
|
|
||||||
'set',
|
|
||||||
];
|
|
||||||
if (window.noticeable) console.warn('Noticeable SDK code snippet loaded more than once');
|
|
||||||
else {
|
|
||||||
var n = (window.noticeable = window.noticeable || []);
|
|
||||||
function t(e) {
|
|
||||||
return function () {
|
|
||||||
var t = Array.prototype.slice.call(arguments);
|
|
||||||
return t.unshift(e), n.push(t), n;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
!(function () {
|
|
||||||
for (var o = 0; o < e.length; o++) {
|
|
||||||
var r = e[o];
|
|
||||||
n[r] = t(r);
|
|
||||||
}
|
|
||||||
})(),
|
|
||||||
(function () {
|
|
||||||
var e = document.createElement('script');
|
|
||||||
(e.async = !0), (e.src = 'https://sdk.noticeable.io/l.js');
|
|
||||||
var n = document.head;
|
|
||||||
n.insertBefore(e, n.firstChild);
|
|
||||||
})();
|
|
||||||
}
|
}
|
||||||
})();
|
|
||||||
</script>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
|
||||||
<div id="root"></div>
|
|
||||||
|
|
||||||
<script type="module" src="src/index.jsx"></script>
|
!(function () {
|
||||||
</body>
|
for (var o = 0; o < e.length; o++) {
|
||||||
|
var r = e[o];
|
||||||
|
n[r] = t(r);
|
||||||
|
}
|
||||||
|
})(),
|
||||||
|
(function () {
|
||||||
|
var e = document.createElement('script');
|
||||||
|
(e.async = !0), (e.src = 'https://sdk.noticeable.io/l.js');
|
||||||
|
var n = document.head;
|
||||||
|
n.insertBefore(e, n.firstChild);
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
|
<div id="root"></div>
|
||||||
|
|
||||||
|
<script type="module" src="src/index.jsx"></script>
|
||||||
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
2942
client/package-lock.json
generated
2942
client/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -8,89 +8,92 @@
|
|||||||
"private": true,
|
"private": true,
|
||||||
"proxy": "http://localhost:4000",
|
"proxy": "http://localhost:4000",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ant-design/pro-layout": "^7.19.11",
|
"@ant-design/pro-layout": "^7.19.12",
|
||||||
"@apollo/client": "^3.10.8",
|
"@apollo/client": "^3.11.8",
|
||||||
"@emotion/is-prop-valid": "^1.3.0",
|
"@emotion/is-prop-valid": "^1.3.1",
|
||||||
"@fingerprintjs/fingerprintjs": "^4.4.3",
|
"@fingerprintjs/fingerprintjs": "^4.5.0",
|
||||||
"@jsreport/browser-client": "^3.1.0",
|
"@jsreport/browser-client": "^3.1.0",
|
||||||
"@reduxjs/toolkit": "^2.2.6",
|
"@reduxjs/toolkit": "^2.2.7",
|
||||||
"@sentry/cli": "^2.32.2",
|
"@sentry/cli": "^2.36.2",
|
||||||
"@sentry/react": "^7.114.0",
|
"@sentry/react": "^7.114.0",
|
||||||
"@splitsoftware/splitio-react": "^1.12.0",
|
"@splitsoftware/splitio-react": "^1.13.0",
|
||||||
"@tanem/react-nprogress": "^5.0.51",
|
"@tanem/react-nprogress": "^5.0.51",
|
||||||
"@vitejs/plugin-react": "^4.3.1",
|
"@vitejs/plugin-react": "^4.3.1",
|
||||||
"antd": "^5.19.3",
|
"antd": "^5.20.1",
|
||||||
"apollo-link-logger": "^2.0.1",
|
"apollo-link-logger": "^2.0.1",
|
||||||
"apollo-link-sentry": "^3.3.0",
|
"apollo-link-sentry": "^3.3.0",
|
||||||
"autosize": "^6.0.1",
|
"autosize": "^6.0.1",
|
||||||
"axios": "^1.6.8",
|
"axios": "^1.7.7",
|
||||||
"classnames": "^2.5.1",
|
"classnames": "^2.5.1",
|
||||||
"css-box-model": "^1.2.1",
|
"css-box-model": "^1.2.1",
|
||||||
"dayjs": "^1.11.12",
|
"dayjs": "^1.11.13",
|
||||||
"dayjs-business-days2": "^1.2.2",
|
"dayjs-business-days2": "^1.2.2",
|
||||||
"dinero.js": "^1.9.1",
|
"dinero.js": "^1.9.1",
|
||||||
"dotenv": "^16.4.5",
|
"dotenv": "^16.4.5",
|
||||||
"env-cmd": "^10.1.0",
|
"env-cmd": "^10.1.0",
|
||||||
"exifr": "^7.1.3",
|
"exifr": "^7.1.3",
|
||||||
"firebase": "^10.12.4",
|
"firebase": "^10.13.2",
|
||||||
"graphql": "^16.9.0",
|
"graphql": "^16.9.0",
|
||||||
"i18next": "^23.12.2",
|
"i18next": "^23.15.1",
|
||||||
"i18next-browser-languagedetector": "^8.0.0",
|
"i18next-browser-languagedetector": "^8.0.0",
|
||||||
"immutability-helper": "^3.1.1",
|
"immutability-helper": "^3.1.1",
|
||||||
"libphonenumber-js": "^1.11.4",
|
"libphonenumber-js": "^1.11.9",
|
||||||
"logrocket": "^8.1.1",
|
"logrocket": "^8.1.2",
|
||||||
"markerjs2": "^2.32.1",
|
"markerjs2": "^2.32.2",
|
||||||
"memoize-one": "^6.0.0",
|
"memoize-one": "^6.0.0",
|
||||||
"normalize-url": "^8.0.1",
|
"normalize-url": "^8.0.1",
|
||||||
"object-hash": "^3.0.0",
|
"object-hash": "^3.0.0",
|
||||||
"prop-types": "^15.8.1",
|
"prop-types": "^15.8.1",
|
||||||
"query-string": "^9.0.0",
|
"query-string": "^9.1.0",
|
||||||
"raf-schd": "^4.0.3",
|
"raf-schd": "^4.0.3",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-big-calendar": "^1.13.1",
|
"react-big-calendar": "^1.14.1",
|
||||||
"react-color": "^2.19.3",
|
"react-color": "^2.19.3",
|
||||||
"react-cookie": "^7.1.4",
|
"react-cookie": "^7.2.0",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
"react-drag-listview": "^2.0.0",
|
"react-drag-listview": "^2.0.0",
|
||||||
"react-grid-gallery": "^1.0.1",
|
"react-grid-gallery": "^1.0.1",
|
||||||
"react-grid-layout": "1.3.4",
|
"react-grid-layout": "1.3.4",
|
||||||
"react-i18next": "^14.1.3",
|
"react-i18next": "^14.1.3",
|
||||||
"react-icons": "^5.2.1",
|
"react-icons": "^5.3.0",
|
||||||
"react-image-lightbox": "^5.1.4",
|
"react-image-lightbox": "^5.1.4",
|
||||||
"react-joyride": "^2.8.2",
|
|
||||||
"react-markdown": "^9.0.1",
|
"react-markdown": "^9.0.1",
|
||||||
"react-number-format": "^5.4.0",
|
"react-number-format": "^5.4.2",
|
||||||
"react-popopo": "^2.1.9",
|
"react-popopo": "^2.1.9",
|
||||||
"react-product-fruits": "^2.2.6",
|
"react-product-fruits": "^2.2.61",
|
||||||
"react-redux": "^9.1.2",
|
"react-redux": "^9.1.2",
|
||||||
"react-resizable": "^3.0.5",
|
"react-resizable": "^3.0.5",
|
||||||
"react-router-dom": "^6.25.1",
|
"react-router-dom": "^6.26.2",
|
||||||
"react-sticky": "^6.0.3",
|
"react-sticky": "^6.0.3",
|
||||||
"react-virtualized": "^9.22.5",
|
"react-virtualized": "^9.22.5",
|
||||||
"react-virtuoso": "^4.7.12",
|
"react-virtuoso": "^4.10.4",
|
||||||
"recharts": "^2.12.7",
|
"recharts": "^2.12.7",
|
||||||
"redux": "^5.0.1",
|
"redux": "^5.0.1",
|
||||||
"redux-actions": "^3.0.0",
|
"redux-actions": "^3.0.3",
|
||||||
"redux-persist": "^6.0.0",
|
"redux-persist": "^6.0.0",
|
||||||
"redux-saga": "^1.3.0",
|
"redux-saga": "^1.3.0",
|
||||||
"redux-state-sync": "^3.1.4",
|
"redux-state-sync": "^3.1.4",
|
||||||
"reselect": "^5.1.1",
|
"reselect": "^5.1.1",
|
||||||
"sass": "^1.77.8",
|
"sass": "^1.79.3",
|
||||||
"socket.io-client": "^4.7.5",
|
"socket.io-client": "^4.8.0",
|
||||||
"styled-components": "^6.1.12",
|
"styled-components": "^6.1.13",
|
||||||
"subscriptions-transport-ws": "^0.11.0",
|
"subscriptions-transport-ws": "^0.11.0",
|
||||||
"use-memo-one": "^1.1.3",
|
"use-memo-one": "^1.1.3",
|
||||||
"userpilot": "^1.3.2",
|
"userpilot": "^1.3.6",
|
||||||
"vite-plugin-ejs": "^1.7.0",
|
"vite-plugin-ejs": "^1.7.0",
|
||||||
"web-vitals": "^3.5.2"
|
"web-vitals": "^3.5.2"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
"postinstall": "echo 'when updating react-big-calendar, remember to check to localizer in the calendar wrapper'",
|
||||||
"analyze": "source-map-explorer 'build/static/js/*.js'",
|
"analyze": "source-map-explorer 'build/static/js/*.js'",
|
||||||
"start": "vite",
|
"start": "vite",
|
||||||
"build": "dotenvx run --env-file=.env.development.imex -- vite build",
|
"build": "dotenvx run --env-file=.env.development.imex -- vite build",
|
||||||
"start:imex": "dotenvx run --env-file=.env.development.imex -- vite",
|
"start:imex": "dotenvx run --env-file=.env.development.imex -- vite",
|
||||||
"start:rome": "dotenvx run --env-file=.env.development.rome -- vite",
|
"start:rome": "dotenvx run --env-file=.env.development.rome -- vite",
|
||||||
"start:promanager": "dotenvx run --env-file=.env.development.promanager -- vite",
|
"start:promanager": "dotenvx run --env-file=.env.development.promanager -- vite",
|
||||||
|
"preview:imex": "dotenvx run --env-file=.env.development.imex -- vite preview",
|
||||||
|
"preview:rome": "dotenvx run --env-file=.env.development.rome -- vite preview",
|
||||||
|
"preview:promanager": "dotenvx run --env-file=.env.development.promanager -- vite preview",
|
||||||
"build:test:imex": "env-cmd -f .env.test.imex npm run build",
|
"build:test:imex": "env-cmd -f .env.test.imex npm run build",
|
||||||
"build:test:rome": "env-cmd -f .env.test.rome npm run build",
|
"build:test:rome": "env-cmd -f .env.test.rome npm run build",
|
||||||
"build:test:promanager": "env-cmd -f .env.test.promanager npm run build",
|
"build:test:promanager": "env-cmd -f .env.test.promanager npm run build",
|
||||||
@@ -131,29 +134,30 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
|
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
|
||||||
"@babel/preset-react": "^7.24.7",
|
"@babel/preset-react": "^7.24.7",
|
||||||
"@dotenvx/dotenvx": "^1.6.4",
|
"@dotenvx/dotenvx": "^1.14.1",
|
||||||
"@emotion/babel-plugin": "^11.12.0",
|
"@emotion/babel-plugin": "^11.12.0",
|
||||||
"@emotion/react": "^11.12.0",
|
"@emotion/react": "^11.13.3",
|
||||||
"@sentry/webpack-plugin": "^2.21.1",
|
"@sentry/webpack-plugin": "^2.22.4",
|
||||||
"@testing-library/cypress": "^10.0.2",
|
"@testing-library/cypress": "^10.0.2",
|
||||||
"browserslist": "^4.23.2",
|
"browserslist": "^4.23.3",
|
||||||
"browserslist-to-esbuild": "^2.1.1",
|
"browserslist-to-esbuild": "^2.1.1",
|
||||||
|
"chalk": "^5.3.0",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
"cypress": "^13.13.1",
|
"cypress": "^13.14.2",
|
||||||
"eslint": "^8.57.0",
|
"eslint": "^8.57.0",
|
||||||
"eslint-config-react-app": "^7.0.1",
|
"eslint-config-react-app": "^7.0.1",
|
||||||
"eslint-plugin-cypress": "^2.15.1",
|
"eslint-plugin-cypress": "^2.15.1",
|
||||||
"memfs": "^4.9.3",
|
"memfs": "^4.12.0",
|
||||||
"os-browserify": "^0.3.0",
|
"os-browserify": "^0.3.0",
|
||||||
"react-error-overlay": "6.0.11",
|
"react-error-overlay": "6.0.11",
|
||||||
"redux-logger": "^3.0.6",
|
"redux-logger": "^3.0.6",
|
||||||
"source-map-explorer": "^2.5.3",
|
"source-map-explorer": "^2.5.3",
|
||||||
"vite": "^5.3.4",
|
"vite": "^5.4.7",
|
||||||
"vite-plugin-babel": "^1.2.0",
|
"vite-plugin-babel": "^1.2.0",
|
||||||
"vite-plugin-eslint": "^1.8.1",
|
"vite-plugin-eslint": "^1.8.1",
|
||||||
"vite-plugin-legacy": "^2.1.0",
|
|
||||||
"vite-plugin-node-polyfills": "^0.22.0",
|
"vite-plugin-node-polyfills": "^0.22.0",
|
||||||
"vite-plugin-pwa": "^0.20.0",
|
"vite-plugin-pwa": "^0.20.5",
|
||||||
"vite-plugin-style-import": "^2.0.0"
|
"vite-plugin-style-import": "^2.0.0",
|
||||||
|
"workbox-window": "^7.1.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,10 +18,10 @@ import { checkUserSession } from "../redux/user/user.actions";
|
|||||||
import { selectBodyshop, selectCurrentEula, selectCurrentUser } from "../redux/user/user.selectors";
|
import { selectBodyshop, selectCurrentEula, selectCurrentUser } from "../redux/user/user.selectors";
|
||||||
import PrivateRoute from "../components/PrivateRoute";
|
import PrivateRoute from "../components/PrivateRoute";
|
||||||
import "./App.styles.scss";
|
import "./App.styles.scss";
|
||||||
import handleBeta from "../utils/betaHandler";
|
|
||||||
import Eula from "../components/eula/eula.component";
|
import Eula from "../components/eula/eula.component";
|
||||||
import InstanceRenderMgr from "../utils/instanceRenderMgr";
|
import InstanceRenderMgr from "../utils/instanceRenderMgr";
|
||||||
import ProductFruitsWrapper from "./ProductFruitsWrapper.jsx";
|
import ProductFruitsWrapper from "./ProductFruitsWrapper.jsx";
|
||||||
|
import { SocketProvider } from "../contexts/SocketIO/socketContext.jsx";
|
||||||
|
|
||||||
const ResetPassword = lazy(() => import("../pages/reset-password/reset-password.component"));
|
const ResetPassword = lazy(() => import("../pages/reset-password/reset-password.component"));
|
||||||
const ManagePage = lazy(() => import("../pages/manage/manage.page.container"));
|
const ManagePage = lazy(() => import("../pages/manage/manage.page.container"));
|
||||||
@@ -108,8 +108,6 @@ export function App({ bodyshop, checkUserSession, currentUser, online, setOnline
|
|||||||
return <LoadingSpinner message={t("general.labels.loggingin")} />;
|
return <LoadingSpinner message={t("general.labels.loggingin")} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
handleBeta();
|
|
||||||
|
|
||||||
if (!online) {
|
if (!online) {
|
||||||
return (
|
return (
|
||||||
<Result
|
<Result
|
||||||
@@ -204,7 +202,9 @@ export function App({ bodyshop, checkUserSession, currentUser, online, setOnline
|
|||||||
path="/manage/*"
|
path="/manage/*"
|
||||||
element={
|
element={
|
||||||
<ErrorBoundary>
|
<ErrorBoundary>
|
||||||
<PrivateRoute isAuthorized={currentUser.authorized} />
|
<SocketProvider bodyshop={bodyshop}>
|
||||||
|
<PrivateRoute isAuthorized={currentUser.authorized} />
|
||||||
|
</SocketProvider>
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
@@ -214,7 +214,9 @@ export function App({ bodyshop, checkUserSession, currentUser, online, setOnline
|
|||||||
path="/tech/*"
|
path="/tech/*"
|
||||||
element={
|
element={
|
||||||
<ErrorBoundary>
|
<ErrorBoundary>
|
||||||
<PrivateRoute isAuthorized={currentUser.authorized} />
|
<SocketProvider bodyshop={bodyshop}>
|
||||||
|
<PrivateRoute isAuthorized={currentUser.authorized} />
|
||||||
|
</SocketProvider>
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -161,3 +161,15 @@
|
|||||||
.rowWithColor > td {
|
.rowWithColor > td {
|
||||||
background-color: var(--bgColor) !important;
|
background-color: var(--bgColor) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.muted-button {
|
||||||
|
color: lightgray;
|
||||||
|
border: none;
|
||||||
|
background: none;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 16px; /* Adjust as needed */
|
||||||
|
}
|
||||||
|
|
||||||
|
.muted-button:hover {
|
||||||
|
color: darkgrey;
|
||||||
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { logImEXEvent } from "../../firebase/firebase.utils";
|
|||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||||
import { DateFormatter } from "../../utils/DateFormatter";
|
import { DateFormatter } from "../../utils/DateFormatter";
|
||||||
import { pageLimit } from "../../utils/config";
|
import { exportPageLimit } from "../../utils/config";
|
||||||
import { alphaSort, dateSort } from "../../utils/sorters";
|
import { alphaSort, dateSort } from "../../utils/sorters";
|
||||||
import ExportLogsCountDisplay from "../export-logs-count-display/export-logs-count-display.component";
|
import ExportLogsCountDisplay from "../export-logs-count-display/export-logs-count-display.component";
|
||||||
import PayableExportAll from "../payable-export-all-button/payable-export-all-button.component";
|
import PayableExportAll from "../payable-export-all-button/payable-export-all-button.component";
|
||||||
@@ -175,7 +175,7 @@ export function AccountingPayablesTableComponent({ bodyshop, loading, bills, ref
|
|||||||
<Table
|
<Table
|
||||||
loading={loading}
|
loading={loading}
|
||||||
dataSource={dataSource}
|
dataSource={dataSource}
|
||||||
pagination={{ position: "top", pageSize: pageLimit }}
|
pagination={{ position: "top", pageSize: exportPageLimit }}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
onChange={handleTableChange}
|
onChange={handleTableChange}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { logImEXEvent } from "../../firebase/firebase.utils";
|
|||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||||
import { DateFormatter, DateTimeFormatter } from "../../utils/DateFormatter";
|
import { DateFormatter, DateTimeFormatter } from "../../utils/DateFormatter";
|
||||||
import { pageLimit } from "../../utils/config";
|
import { exportPageLimit } from "../../utils/config";
|
||||||
import { alphaSort, dateSort } from "../../utils/sorters";
|
import { alphaSort, dateSort } from "../../utils/sorters";
|
||||||
import ExportLogsCountDisplay from "../export-logs-count-display/export-logs-count-display.component";
|
import ExportLogsCountDisplay from "../export-logs-count-display/export-logs-count-display.component";
|
||||||
import OwnerNameDisplay, { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component";
|
import OwnerNameDisplay, { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component";
|
||||||
@@ -177,7 +177,7 @@ export function AccountingPayablesTableComponent({ bodyshop, loading, payments,
|
|||||||
<Table
|
<Table
|
||||||
loading={loading}
|
loading={loading}
|
||||||
dataSource={dataSource}
|
dataSource={dataSource}
|
||||||
pagination={{ position: "top", pageSize: pageLimit }}
|
pagination={{ position: "top", pageSize: exportPageLimit }}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
onChange={handleTableChange}
|
onChange={handleTableChange}
|
||||||
|
|||||||
@@ -1,18 +1,19 @@
|
|||||||
import { Button, Card, Input, Space, Table } from "antd";
|
import { Button, Card, Input, Space, Table } from "antd";
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { connect } from "react-redux";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
|
import { createStructuredSelector } from "reselect";
|
||||||
import { logImEXEvent } from "../../firebase/firebase.utils";
|
import { logImEXEvent } from "../../firebase/firebase.utils";
|
||||||
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
|
import { exportPageLimit } from "../../utils/config";
|
||||||
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||||
|
import { DateFormatter } from "../../utils/DateFormatter";
|
||||||
import { alphaSort, dateSort, statusSort } from "../../utils/sorters";
|
import { alphaSort, dateSort, statusSort } from "../../utils/sorters";
|
||||||
|
import ExportLogsCountDisplay from "../export-logs-count-display/export-logs-count-display.component";
|
||||||
import JobExportButton from "../jobs-close-export-button/jobs-close-export-button.component";
|
import JobExportButton from "../jobs-close-export-button/jobs-close-export-button.component";
|
||||||
import JobsExportAllButton from "../jobs-export-all-button/jobs-export-all-button.component";
|
import JobsExportAllButton from "../jobs-export-all-button/jobs-export-all-button.component";
|
||||||
|
import JobMarkSelectedExported from "../jobs-mark-selected-exported/jobs-mark-selected-exported";
|
||||||
import { connect } from "react-redux";
|
|
||||||
import { createStructuredSelector } from "reselect";
|
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
|
||||||
import { DateFormatter } from "../../utils/DateFormatter";
|
|
||||||
import ExportLogsCountDisplay from "../export-logs-count-display/export-logs-count-display.component";
|
|
||||||
import OwnerNameDisplay, { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component";
|
import OwnerNameDisplay, { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component";
|
||||||
import QboAuthorizeComponent from "../qbo-authorize/qbo-authorize.component";
|
import QboAuthorizeComponent from "../qbo-authorize/qbo-authorize.component";
|
||||||
|
|
||||||
@@ -170,13 +171,22 @@ export function AccountingReceivablesTableComponent({ bodyshop, loading, jobs, r
|
|||||||
extra={
|
extra={
|
||||||
<Space wrap>
|
<Space wrap>
|
||||||
{!bodyshop.cdk_dealerid && !bodyshop.pbs_serialnumber && (
|
{!bodyshop.cdk_dealerid && !bodyshop.pbs_serialnumber && (
|
||||||
<JobsExportAllButton
|
<>
|
||||||
jobIds={selectedJobs}
|
<JobMarkSelectedExported
|
||||||
disabled={transInProgress || selectedJobs.length === 0}
|
jobIds={selectedJobs}
|
||||||
loadingCallback={setTransInProgress}
|
disabled={transInProgress || selectedJobs.length === 0}
|
||||||
completedCallback={setSelectedJobs}
|
loadingCallback={setTransInProgress}
|
||||||
refetch={refetch}
|
completedCallback={setSelectedJobs}
|
||||||
/>
|
refetch={refetch}
|
||||||
|
/>
|
||||||
|
<JobsExportAllButton
|
||||||
|
jobIds={selectedJobs}
|
||||||
|
disabled={transInProgress || selectedJobs.length === 0}
|
||||||
|
loadingCallback={setTransInProgress}
|
||||||
|
completedCallback={setSelectedJobs}
|
||||||
|
refetch={refetch}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
{bodyshop.accountingconfig && bodyshop.accountingconfig.qbo && <QboAuthorizeComponent />}
|
{bodyshop.accountingconfig && bodyshop.accountingconfig.qbo && <QboAuthorizeComponent />}
|
||||||
<Input.Search
|
<Input.Search
|
||||||
@@ -191,7 +201,7 @@ export function AccountingReceivablesTableComponent({ bodyshop, loading, jobs, r
|
|||||||
<Table
|
<Table
|
||||||
loading={loading}
|
loading={loading}
|
||||||
dataSource={dataSource}
|
dataSource={dataSource}
|
||||||
pagination={{ position: "top" }}
|
pagination={{ position: "top", pageSize: exportPageLimit }}
|
||||||
columns={columns}
|
columns={columns}
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
onChange={handleTableChange}
|
onChange={handleTableChange}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
|
import { PageHeader } from "@ant-design/pro-layout";
|
||||||
import { useMutation, useQuery } from "@apollo/client";
|
import { useMutation, useQuery } from "@apollo/client";
|
||||||
import { Button, Divider, Form, Popconfirm, Space } from "antd";
|
import { Button, Divider, Form, Popconfirm, Space } from "antd";
|
||||||
import dayjs from "../../utils/day";
|
|
||||||
import queryString from "query-string";
|
import queryString from "query-string";
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
@@ -13,6 +13,7 @@ import { insertAuditTrail } from "../../redux/application/application.actions";
|
|||||||
import { setModalContext } from "../../redux/modals/modals.actions";
|
import { setModalContext } from "../../redux/modals/modals.actions";
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
||||||
|
import dayjs from "../../utils/day";
|
||||||
import AlertComponent from "../alert/alert.component";
|
import AlertComponent from "../alert/alert.component";
|
||||||
import BillFormContainer from "../bill-form/bill-form.container";
|
import BillFormContainer from "../bill-form/bill-form.container";
|
||||||
import BillMarkExportedButton from "../bill-mark-exported-button/bill-mark-exported-button.component";
|
import BillMarkExportedButton from "../bill-mark-exported-button/bill-mark-exported-button.component";
|
||||||
@@ -22,7 +23,6 @@ import JobDocumentsGallery from "../jobs-documents-gallery/jobs-documents-galler
|
|||||||
import JobsDocumentsLocalGallery from "../jobs-documents-local-gallery/jobs-documents-local-gallery.container";
|
import JobsDocumentsLocalGallery from "../jobs-documents-local-gallery/jobs-documents-local-gallery.container";
|
||||||
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component";
|
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component";
|
||||||
import BillDetailEditReturn from "./bill-detail-edit-return.component";
|
import BillDetailEditReturn from "./bill-detail-edit-return.component";
|
||||||
import { PageHeader } from "@ant-design/pro-layout";
|
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop
|
bodyshop: selectBodyshop
|
||||||
@@ -98,7 +98,7 @@ export function BillDetailEditcontainer({ setPartsOrderContext, insertAuditTrail
|
|||||||
});
|
});
|
||||||
|
|
||||||
billlines.forEach((billline) => {
|
billlines.forEach((billline) => {
|
||||||
const { deductedfromlbr, inventories, jobline, ...il } = billline;
|
const { deductedfromlbr, inventories, jobline, original_actual_price, create_ppc, ...il } = billline;
|
||||||
delete il.__typename;
|
delete il.__typename;
|
||||||
|
|
||||||
if (il.id) {
|
if (il.id) {
|
||||||
@@ -153,6 +153,7 @@ export function BillDetailEditcontainer({ setPartsOrderContext, insertAuditTrail
|
|||||||
if (!search.billid) return <></>; //<div>{t("bills.labels.noneselected")}</div>;
|
if (!search.billid) return <></>; //<div>{t("bills.labels.noneselected")}</div>;
|
||||||
|
|
||||||
const exported = data && data.bills_by_pk && data.bills_by_pk.exported;
|
const exported = data && data.bills_by_pk && data.bills_by_pk.exported;
|
||||||
|
const isinhouse = data && data.bills_by_pk && data.bills_by_pk.isinhouse;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -188,7 +189,7 @@ export function BillDetailEditcontainer({ setPartsOrderContext, insertAuditTrail
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<Form form={form} onFinish={handleFinish} initialValues={transformData(data)} layout="vertical">
|
<Form form={form} onFinish={handleFinish} initialValues={transformData(data)} layout="vertical">
|
||||||
<BillFormContainer form={form} billEdit disabled={exported} />
|
<BillFormContainer form={form} billEdit disabled={exported} disableInHouse={isinhouse} />
|
||||||
<Divider orientation="left">{t("general.labels.media")}</Divider>
|
<Divider orientation="left">{t("general.labels.media")}</Divider>
|
||||||
{bodyshop.uselocalmediaserver ? (
|
{bodyshop.uselocalmediaserver ? (
|
||||||
<JobsDocumentsLocalGallery
|
<JobsDocumentsLocalGallery
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ import dayjs from "../../utils/day";
|
|||||||
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
||||||
import AlertComponent from "../alert/alert.component";
|
import AlertComponent from "../alert/alert.component";
|
||||||
import BillFormLinesExtended from "../bill-form-lines-extended/bill-form-lines-extended.component";
|
import BillFormLinesExtended from "../bill-form-lines-extended/bill-form-lines-extended.component";
|
||||||
import FormDatePicker from "../form-date-picker/form-date-picker.component";
|
|
||||||
import FormFieldsChanged from "../form-fields-changed-alert/form-fields-changed-alert.component";
|
import FormFieldsChanged from "../form-fields-changed-alert/form-fields-changed-alert.component";
|
||||||
import CurrencyInput from "../form-items-formatted/currency-form-item.component";
|
import CurrencyInput from "../form-items-formatted/currency-form-item.component";
|
||||||
import JobSearchSelect from "../job-search-select/job-search-select.component";
|
import JobSearchSelect from "../job-search-select/job-search-select.component";
|
||||||
@@ -22,6 +21,7 @@ import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
|||||||
import VendorSearchSelect from "../vendor-search-select/vendor-search-select.component";
|
import VendorSearchSelect from "../vendor-search-select/vendor-search-select.component";
|
||||||
import BillFormLines from "./bill-form.lines.component";
|
import BillFormLines from "./bill-form.lines.component";
|
||||||
import { CalculateBillTotal } from "./bill-form.totals.utility";
|
import { CalculateBillTotal } from "./bill-form.totals.utility";
|
||||||
|
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component.jsx";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop
|
bodyshop: selectBodyshop
|
||||||
@@ -41,7 +41,8 @@ export function BillFormComponent({
|
|||||||
job,
|
job,
|
||||||
loadOutstandingReturns,
|
loadOutstandingReturns,
|
||||||
loadInventory,
|
loadInventory,
|
||||||
preferredMake
|
preferredMake,
|
||||||
|
disableInHouse
|
||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const client = useApolloClient();
|
const client = useApolloClient();
|
||||||
@@ -177,7 +178,7 @@ export function BillFormComponent({
|
|||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<VendorSearchSelect
|
<VendorSearchSelect
|
||||||
disabled={disabled}
|
disabled={disabled || disableInHouse}
|
||||||
options={vendorAutoCompleteOptions}
|
options={vendorAutoCompleteOptions}
|
||||||
preferredMake={preferredMake}
|
preferredMake={preferredMake}
|
||||||
onSelect={handleVendorSelect}
|
onSelect={handleVendorSelect}
|
||||||
@@ -243,7 +244,7 @@ export function BillFormComponent({
|
|||||||
})
|
})
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<Input disabled={disabled || disableInvNumber} />
|
<Input disabled={disabled || disableInvNumber || disableInHouse} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={t("bills.fields.date")}
|
label={t("bills.fields.date")}
|
||||||
@@ -275,7 +276,7 @@ export function BillFormComponent({
|
|||||||
})
|
})
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<FormDatePicker disabled={disabled} />
|
<DateTimePicker isDateOnly disabled={disabled} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={t("bills.fields.is_credit_memo")}
|
label={t("bills.fields.is_credit_memo")}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ const mapStateToProps = createStructuredSelector({
|
|||||||
bodyshop: selectBodyshop
|
bodyshop: selectBodyshop
|
||||||
});
|
});
|
||||||
|
|
||||||
export function BillFormContainer({ bodyshop, form, billEdit, disabled, disableInvNumber }) {
|
export function BillFormContainer({ bodyshop, form, billEdit, disabled, disableInvNumber, disableInHouse }) {
|
||||||
const {
|
const {
|
||||||
treatments: { Simple_Inventory }
|
treatments: { Simple_Inventory }
|
||||||
} = useSplitTreatments({
|
} = useSplitTreatments({
|
||||||
@@ -47,6 +47,7 @@ export function BillFormContainer({ bodyshop, form, billEdit, disabled, disableI
|
|||||||
job={lineData ? lineData.jobs_by_pk : null}
|
job={lineData ? lineData.jobs_by_pk : null}
|
||||||
responsibilityCenters={bodyshop.md_responsibility_centers || null}
|
responsibilityCenters={bodyshop.md_responsibility_centers || null}
|
||||||
disableInvNumber={disableInvNumber}
|
disableInvNumber={disableInvNumber}
|
||||||
|
disableInHouse={disableInHouse}
|
||||||
loadOutstandingReturns={loadOutstandingReturns}
|
loadOutstandingReturns={loadOutstandingReturns}
|
||||||
loadInventory={loadInventory}
|
loadInventory={loadInventory}
|
||||||
preferredMake={lineData ? lineData.jobs_by_pk.v_make_desc : null}
|
preferredMake={lineData ? lineData.jobs_by_pk.v_make_desc : null}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { DeleteFilled } from "@ant-design/icons";
|
import { DeleteFilled, CopyFilled } from "@ant-design/icons";
|
||||||
import { useLazyQuery, useMutation } from "@apollo/client";
|
import { useLazyQuery, useMutation } from "@apollo/client";
|
||||||
import { Button, Card, Col, Form, Input, Row, Space, Spin, Statistic, notification } from "antd";
|
import { Button, Card, Col, Form, Input, Row, Space, Spin, Statistic, message, notification } from "antd";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
@@ -14,10 +14,12 @@ import { selectBodyshop } from "../../redux/user/user.selectors";
|
|||||||
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
||||||
import CurrencyFormItemComponent from "../form-items-formatted/currency-form-item.component";
|
import CurrencyFormItemComponent from "../form-items-formatted/currency-form-item.component";
|
||||||
import JobSearchSelectComponent from "../job-search-select/job-search-select.component";
|
import JobSearchSelectComponent from "../job-search-select/job-search-select.component";
|
||||||
|
import { getCurrentUser } from "../../firebase/firebase.utils";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
cardPaymentModal: selectCardPayment,
|
cardPaymentModal: selectCardPayment,
|
||||||
bodyshop: selectBodyshop
|
bodyshop: selectBodyshop,
|
||||||
|
currentUser: getCurrentUser
|
||||||
});
|
});
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
@@ -25,11 +27,17 @@ const mapDispatchToProps = (dispatch) => ({
|
|||||||
toggleModalVisible: () => dispatch(toggleModalVisible("cardPayment"))
|
toggleModalVisible: () => dispatch(toggleModalVisible("cardPayment"))
|
||||||
});
|
});
|
||||||
|
|
||||||
const CardPaymentModalComponent = ({ bodyshop, cardPaymentModal, toggleModalVisible, insertAuditTrail }) => {
|
const CardPaymentModalComponent = ({
|
||||||
|
bodyshop,
|
||||||
|
currentUser,
|
||||||
|
cardPaymentModal,
|
||||||
|
toggleModalVisible,
|
||||||
|
insertAuditTrail
|
||||||
|
}) => {
|
||||||
const { context, actions } = cardPaymentModal;
|
const { context, actions } = cardPaymentModal;
|
||||||
|
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
|
const [paymentLink, setPaymentLink] = useState();
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
// const [insertPayment] = useMutation(INSERT_NEW_PAYMENT);
|
// const [insertPayment] = useMutation(INSERT_NEW_PAYMENT);
|
||||||
const [insertPaymentResponse] = useMutation(INSERT_PAYMENT_RESPONSE);
|
const [insertPaymentResponse] = useMutation(INSERT_PAYMENT_RESPONSE);
|
||||||
@@ -37,7 +45,7 @@ const CardPaymentModalComponent = ({ bodyshop, cardPaymentModal, toggleModalVisi
|
|||||||
|
|
||||||
const [, { data, refetch, queryLoading }] = useLazyQuery(QUERY_RO_AND_OWNER_BY_JOB_PKS, {
|
const [, { data, refetch, queryLoading }] = useLazyQuery(QUERY_RO_AND_OWNER_BY_JOB_PKS, {
|
||||||
variables: { jobids: [context.jobid] },
|
variables: { jobids: [context.jobid] },
|
||||||
skip: true
|
skip: !context?.jobid
|
||||||
});
|
});
|
||||||
|
|
||||||
//Initialize the intellipay window.
|
//Initialize the intellipay window.
|
||||||
@@ -51,8 +59,7 @@ const CardPaymentModalComponent = ({ bodyshop, cardPaymentModal, toggleModalVisi
|
|||||||
//2024-04-25: Nothing is going to happen here anymore. We'll completely rely on the callback.
|
//2024-04-25: Nothing is going to happen here anymore. We'll completely rely on the callback.
|
||||||
//Add a slight delay to allow the refetch to properly get the data.
|
//Add a slight delay to allow the refetch to properly get the data.
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (actions && actions.refetch && typeof actions.refetch === "function")
|
if (actions && actions.refetch && typeof actions.refetch === "function") actions.refetch();
|
||||||
actions.refetch();
|
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
toggleModalVisible();
|
toggleModalVisible();
|
||||||
}, 750);
|
}, 750);
|
||||||
@@ -86,7 +93,6 @@ const CardPaymentModalComponent = ({ bodyshop, cardPaymentModal, toggleModalVisi
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const handleIntelliPayCharge = async () => {
|
const handleIntelliPayCharge = async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
//Validate
|
//Validate
|
||||||
@@ -101,7 +107,7 @@ const CardPaymentModalComponent = ({ bodyshop, cardPaymentModal, toggleModalVisi
|
|||||||
const response = await axios.post("/intellipay/lightbox_credentials", {
|
const response = await axios.post("/intellipay/lightbox_credentials", {
|
||||||
bodyshop,
|
bodyshop,
|
||||||
refresh: !!window.intellipay,
|
refresh: !!window.intellipay,
|
||||||
paymentSplitMeta: form.getFieldsValue(),
|
paymentSplitMeta: form.getFieldsValue()
|
||||||
});
|
});
|
||||||
|
|
||||||
if (window.intellipay) {
|
if (window.intellipay) {
|
||||||
@@ -126,6 +132,42 @@ const CardPaymentModalComponent = ({ bodyshop, cardPaymentModal, toggleModalVisi
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleIntelliPayChargeShortLink = async () => {
|
||||||
|
setLoading(true);
|
||||||
|
//Validate
|
||||||
|
try {
|
||||||
|
await form.validateFields();
|
||||||
|
} catch (error) {
|
||||||
|
setLoading(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { payments } = form.getFieldsValue();
|
||||||
|
const response = await axios.post("/intellipay/generate_payment_url", {
|
||||||
|
bodyshop,
|
||||||
|
amount: payments?.reduce((acc, val) => {
|
||||||
|
return acc + (val?.amount || 0);
|
||||||
|
}, 0),
|
||||||
|
account: payments && data && data.jobs.length > 0 ? data.jobs.map((j) => j.ro_number).join(", ") : null,
|
||||||
|
comment: btoa(JSON.stringify({ payments, userEmail: currentUser.email })),
|
||||||
|
paymentSplitMeta: form.getFieldsValue()
|
||||||
|
});
|
||||||
|
if (response.data) {
|
||||||
|
setPaymentLink(response.data?.shorUrl);
|
||||||
|
navigator.clipboard.writeText(response.data?.shorUrl);
|
||||||
|
message.success(t("general.actions.copied"));
|
||||||
|
}
|
||||||
|
setLoading(false);
|
||||||
|
} catch (error) {
|
||||||
|
notification.open({
|
||||||
|
type: "error",
|
||||||
|
message: t("job_payments.notifications.error.openingip")
|
||||||
|
});
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card title="Card Payment">
|
<Card title="Card Payment">
|
||||||
<Spin spinning={loading}>
|
<Spin spinning={loading}>
|
||||||
@@ -202,16 +244,14 @@ const CardPaymentModalComponent = ({ bodyshop, cardPaymentModal, toggleModalVisi
|
|||||||
|
|
||||||
<Form.Item
|
<Form.Item
|
||||||
shouldUpdate={(prevValues, curValues) =>
|
shouldUpdate={(prevValues, curValues) =>
|
||||||
prevValues.payments?.map((p) => p?.jobid).join() !== curValues.payments?.map((p) => p?.jobid).join()
|
prevValues.payments?.map((p) => p?.jobid + p?.amount).join() !==
|
||||||
|
curValues.payments?.map((p) => p?.jobid + p?.amount).join()
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{() => {
|
{() => {
|
||||||
//If all of the job ids have been fileld in, then query and update the IP field.
|
//If all of the job ids have been fileld in, then query and update the IP field.
|
||||||
const { payments } = form.getFieldsValue();
|
const { payments } = form.getFieldsValue();
|
||||||
if (
|
if (payments?.length > 0 && payments?.filter((p) => p?.jobid).length === payments?.length) {
|
||||||
payments?.length > 0 &&
|
|
||||||
payments?.filter((p) => p?.jobid).length === payments?.length
|
|
||||||
) {
|
|
||||||
refetch({ jobids: payments.map((p) => p.jobid) });
|
refetch({ jobids: payments.map((p) => p.jobid) });
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
@@ -246,7 +286,6 @@ const CardPaymentModalComponent = ({ bodyshop, cardPaymentModal, toggleModalVisi
|
|||||||
const totalAmountToCharge = payments?.reduce((acc, val) => {
|
const totalAmountToCharge = payments?.reduce((acc, val) => {
|
||||||
return acc + (val?.amount || 0);
|
return acc + (val?.amount || 0);
|
||||||
}, 0);
|
}, 0);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Space style={{ float: "right" }}>
|
<Space style={{ float: "right" }}>
|
||||||
<Statistic title="Amount To Charge" value={totalAmountToCharge} precision={2} />
|
<Statistic title="Amount To Charge" value={totalAmountToCharge} precision={2} />
|
||||||
@@ -273,11 +312,36 @@ const CardPaymentModalComponent = ({ bodyshop, cardPaymentModal, toggleModalVisi
|
|||||||
>
|
>
|
||||||
{t("job_payments.buttons.proceedtopayment")}
|
{t("job_payments.buttons.proceedtopayment")}
|
||||||
</Button>
|
</Button>
|
||||||
|
<Space direction="vertical" align="center">
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
// data-ipayname="submit"
|
||||||
|
className="ipayfield"
|
||||||
|
loading={queryLoading || loading}
|
||||||
|
disabled={!(totalAmountToCharge > 0)}
|
||||||
|
onClick={handleIntelliPayChargeShortLink}
|
||||||
|
>
|
||||||
|
{t("job_payments.buttons.create_short_link")}
|
||||||
|
</Button>
|
||||||
|
</Space>
|
||||||
</Space>
|
</Space>
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Form>
|
</Form>
|
||||||
|
{paymentLink && (
|
||||||
|
<Space
|
||||||
|
style={{ cursor: "pointer", float: "right" }}
|
||||||
|
align="end"
|
||||||
|
onClick={() => {
|
||||||
|
navigator.clipboard.writeText(paymentLink);
|
||||||
|
message.success(t("general.actions.copied"));
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div>{paymentLink}</div>
|
||||||
|
<CopyFilled />
|
||||||
|
</Space>
|
||||||
|
)}
|
||||||
</Spin>
|
</Spin>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ import { DateFormatter } from "../../utils/DateFormatter";
|
|||||||
import ContractStatusSelector from "../contract-status-select/contract-status-select.component";
|
import ContractStatusSelector from "../contract-status-select/contract-status-select.component";
|
||||||
import ContractsRatesChangeButton from "../contracts-rates-change-button/contracts-rates-change-button.component";
|
import ContractsRatesChangeButton from "../contracts-rates-change-button/contracts-rates-change-button.component";
|
||||||
import CourtesyCarFuelSlider from "../courtesy-car-fuel-select/courtesy-car-fuel-select.component";
|
import CourtesyCarFuelSlider from "../courtesy-car-fuel-select/courtesy-car-fuel-select.component";
|
||||||
import FormDatePicker from "../form-date-picker/form-date-picker.component";
|
|
||||||
import FormDateTimePicker from "../form-date-time-picker/form-date-time-picker.component";
|
import FormDateTimePicker from "../form-date-time-picker/form-date-time-picker.component";
|
||||||
|
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component";
|
||||||
import FormFieldsChanged from "../form-fields-changed-alert/form-fields-changed-alert.component";
|
import FormFieldsChanged from "../form-fields-changed-alert/form-fields-changed-alert.component";
|
||||||
import InputPhone, { PhoneItemFormatterValidation } from "../form-items-formatted/phone-form-item.component";
|
import InputPhone, { PhoneItemFormatterValidation } from "../form-items-formatted/phone-form-item.component";
|
||||||
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
||||||
@@ -196,7 +196,7 @@ export default function ContractFormComponent({ form, create = false, selectedJo
|
|||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<FormDatePicker />
|
<DateTimePicker isDateOnly />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
{dlExpiresBeforeReturn && (
|
{dlExpiresBeforeReturn && (
|
||||||
<Space style={{ color: "tomato" }}>
|
<Space style={{ color: "tomato" }}>
|
||||||
@@ -274,7 +274,7 @@ export default function ContractFormComponent({ form, create = false, selectedJo
|
|||||||
<InputPhone />
|
<InputPhone />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label={t("contracts.fields.driver_dob")} name="driver_dob">
|
<Form.Item label={t("contracts.fields.driver_dob")} name="driver_dob">
|
||||||
<FormDatePicker />
|
<DateTimePicker isDateOnly />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</LayoutFormRow>
|
</LayoutFormRow>
|
||||||
<ContractsRatesChangeButton form={form} />
|
<ContractsRatesChangeButton form={form} />
|
||||||
|
|||||||
@@ -10,16 +10,12 @@ import { DateFormatter } from "../../utils/DateFormatter";
|
|||||||
import CourtesyCarFuelSlider from "../courtesy-car-fuel-select/courtesy-car-fuel-select.component";
|
import CourtesyCarFuelSlider from "../courtesy-car-fuel-select/courtesy-car-fuel-select.component";
|
||||||
import CourtesyCarReadiness from "../courtesy-car-readiness-select/courtesy-car-readiness-select.component";
|
import CourtesyCarReadiness from "../courtesy-car-readiness-select/courtesy-car-readiness-select.component";
|
||||||
import CourtesyCarStatus from "../courtesy-car-status-select/courtesy-car-status-select.component";
|
import CourtesyCarStatus from "../courtesy-car-status-select/courtesy-car-status-select.component";
|
||||||
import FormDatePicker from "../form-date-picker/form-date-picker.component";
|
|
||||||
import FormFieldsChanged from "../form-fields-changed-alert/form-fields-changed-alert.component";
|
import FormFieldsChanged from "../form-fields-changed-alert/form-fields-changed-alert.component";
|
||||||
import CurrencyInput from "../form-items-formatted/currency-form-item.component";
|
import CurrencyInput from "../form-items-formatted/currency-form-item.component";
|
||||||
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
||||||
|
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component.jsx";
|
||||||
|
|
||||||
export default function CourtesyCarCreateFormComponent({
|
export default function CourtesyCarCreateFormComponent({ form, saveLoading, newCC }) {
|
||||||
form,
|
|
||||||
saveLoading,
|
|
||||||
newCC,
|
|
||||||
}) {
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const client = useApolloClient();
|
const client = useApolloClient();
|
||||||
|
|
||||||
@@ -161,16 +157,16 @@ export default function CourtesyCarCreateFormComponent({
|
|||||||
<Input />
|
<Input />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label={t("courtesycars.fields.purchasedate")} name="purchasedate">
|
<Form.Item label={t("courtesycars.fields.purchasedate")} name="purchasedate">
|
||||||
<FormDatePicker />
|
<DateTimePicker isDateOnly />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label={t("courtesycars.fields.servicestartdate")} name="servicestartdate">
|
<Form.Item label={t("courtesycars.fields.servicestartdate")} name="servicestartdate">
|
||||||
<FormDatePicker />
|
<DateTimePicker isDateOnly />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label={t("courtesycars.fields.serviceenddate")} name="serviceenddate">
|
<Form.Item label={t("courtesycars.fields.serviceenddate")} name="serviceenddate">
|
||||||
<FormDatePicker />
|
<DateTimePicker isDateOnly />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label={t("courtesycars.fields.leaseenddate")} name="leaseenddate">
|
<Form.Item label={t("courtesycars.fields.leaseenddate")} name="leaseenddate">
|
||||||
<FormDatePicker />
|
<DateTimePicker isDateOnly />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</LayoutFormRow>
|
</LayoutFormRow>
|
||||||
|
|
||||||
@@ -228,7 +224,7 @@ export default function CourtesyCarCreateFormComponent({
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<Form.Item label={t("courtesycars.fields.nextservicedate")} name="nextservicedate">
|
<Form.Item label={t("courtesycars.fields.nextservicedate")} name="nextservicedate">
|
||||||
<FormDatePicker />
|
<DateTimePicker isDateOnly />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item shouldUpdate={(p, c) => p.nextservicedate !== c.nextservicedate}>
|
<Form.Item shouldUpdate={(p, c) => p.nextservicedate !== c.nextservicedate}>
|
||||||
{() => {
|
{() => {
|
||||||
@@ -260,7 +256,7 @@ export default function CourtesyCarCreateFormComponent({
|
|||||||
</Form.Item>
|
</Form.Item>
|
||||||
<div>
|
<div>
|
||||||
<Form.Item label={t("courtesycars.fields.registrationexpires")} name="registrationexpires">
|
<Form.Item label={t("courtesycars.fields.registrationexpires")} name="registrationexpires">
|
||||||
<FormDatePicker />
|
<DateTimePicker isDateOnly />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item shouldUpdate={(p, c) => p.registrationexpires !== c.registrationexpires}>
|
<Form.Item shouldUpdate={(p, c) => p.registrationexpires !== c.registrationexpires}>
|
||||||
{() => {
|
{() => {
|
||||||
@@ -293,7 +289,7 @@ export default function CourtesyCarCreateFormComponent({
|
|||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<FormDatePicker />
|
<DateTimePicker isDateOnly />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item shouldUpdate={(p, c) => p.insuranceexpires !== c.insuranceexpires}>
|
<Form.Item shouldUpdate={(p, c) => p.insuranceexpires !== c.insuranceexpires}>
|
||||||
{() => {
|
{() => {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { Form, InputNumber } from "antd";
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import CourtesyCarFuelSlider from "../courtesy-car-fuel-select/courtesy-car-fuel-select.component";
|
import CourtesyCarFuelSlider from "../courtesy-car-fuel-select/courtesy-car-fuel-select.component";
|
||||||
import FormDatePicker from "../form-date-picker/form-date-picker.component";
|
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component.jsx";
|
||||||
|
|
||||||
export default function CourtesyCarReturnModalComponent() {
|
export default function CourtesyCarReturnModalComponent() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@@ -19,7 +19,7 @@ export default function CourtesyCarReturnModalComponent() {
|
|||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<FormDatePicker />
|
<DateTimePicker isDateOnly />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={t("contracts.fields.kmend")}
|
label={t("contracts.fields.kmend")}
|
||||||
|
|||||||
@@ -36,9 +36,6 @@ export default function DashboardScheduledOutToday({ data, ...cardProps }) {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log("Scheduled Out Today");
|
|
||||||
console.dir(scheduledOutToday);
|
|
||||||
|
|
||||||
const tvFontSize = 18;
|
const tvFontSize = 18;
|
||||||
const tvFontWeight = "bold";
|
const tvFontWeight = "bold";
|
||||||
|
|
||||||
|
|||||||
@@ -24,9 +24,9 @@ import i18n from "../../translations/i18n";
|
|||||||
import dayjs from "../../utils/day";
|
import dayjs from "../../utils/day";
|
||||||
import DmsCdkMakes from "../dms-cdk-makes/dms-cdk-makes.component";
|
import DmsCdkMakes from "../dms-cdk-makes/dms-cdk-makes.component";
|
||||||
import DmsCdkMakesRefetch from "../dms-cdk-makes/dms-cdk-makes.refetch.component";
|
import DmsCdkMakesRefetch from "../dms-cdk-makes/dms-cdk-makes.refetch.component";
|
||||||
import FormDatePicker from "../form-date-picker/form-date-picker.component";
|
|
||||||
import CurrencyInput from "../form-items-formatted/currency-form-item.component";
|
import CurrencyInput from "../form-items-formatted/currency-form-item.component";
|
||||||
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
||||||
|
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component.jsx";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop
|
bodyshop: selectBodyshop
|
||||||
@@ -164,7 +164,7 @@ export function DmsPostForm({ bodyshop, socket, job, logsRef }) {
|
|||||||
<Input disabled />
|
<Input disabled />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item name="inservicedate" label={t("jobs.fields.dms.inservicedate")}>
|
<Form.Item name="inservicedate" label={t("jobs.fields.dms.inservicedate")}>
|
||||||
<FormDatePicker />
|
<DateTimePicker isDateOnly />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</LayoutFormRow>
|
</LayoutFormRow>
|
||||||
<Space>
|
<Space>
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import Markdown from "react-markdown";
|
|||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import { selectCurrentEula, selectCurrentUser } from "../../redux/user/user.selectors";
|
import { selectCurrentEula, selectCurrentUser } from "../../redux/user/user.selectors";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { FormDatePicker } from "../form-date-picker/form-date-picker.component";
|
|
||||||
import { INSERT_EULA_ACCEPTANCE } from "../../graphql/user.queries";
|
import { INSERT_EULA_ACCEPTANCE } from "../../graphql/user.queries";
|
||||||
import { useMutation } from "@apollo/client";
|
import { useMutation } from "@apollo/client";
|
||||||
import { acceptEula } from "../../redux/user/user.actions";
|
import { acceptEula } from "../../redux/user/user.actions";
|
||||||
@@ -12,6 +11,7 @@ import { useTranslation } from "react-i18next";
|
|||||||
import day from "../../utils/day";
|
import day from "../../utils/day";
|
||||||
|
|
||||||
import "./eula.styles.scss";
|
import "./eula.styles.scss";
|
||||||
|
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component.jsx";
|
||||||
|
|
||||||
const Eula = ({ currentEula, currentUser, acceptEula }) => {
|
const Eula = ({ currentEula, currentUser, acceptEula }) => {
|
||||||
const [formReady, setFormReady] = useState(false);
|
const [formReady, setFormReady] = useState(false);
|
||||||
@@ -216,7 +216,7 @@ const EulaFormComponent = ({ form, handleChange, onFinish, t }) => (
|
|||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<FormDatePicker onChange={handleChange} onlyToday aria-label={t("eula.labels.date_accepted")} />
|
<DateTimePicker isDateOnly onChange={handleChange} onlyToday aria-label={t("eula.labels.date_accepted")} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|||||||
@@ -1,123 +0,0 @@
|
|||||||
import { DatePicker } from "antd";
|
|
||||||
import dayjs from "../../utils/day";
|
|
||||||
import React, { useRef } from "react";
|
|
||||||
|
|
||||||
import { connect } from "react-redux";
|
|
||||||
import { createStructuredSelector } from "reselect";
|
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
|
||||||
bodyshop: selectBodyshop
|
|
||||||
});
|
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
|
||||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
|
||||||
});
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(FormDatePicker);
|
|
||||||
|
|
||||||
const dateFormat = "MM/DD/YYYY";
|
|
||||||
|
|
||||||
export function FormDatePicker({
|
|
||||||
bodyshop,
|
|
||||||
value,
|
|
||||||
onChange,
|
|
||||||
onBlur,
|
|
||||||
onlyFuture,
|
|
||||||
onlyToday,
|
|
||||||
isDateOnly = true,
|
|
||||||
...restProps
|
|
||||||
}) {
|
|
||||||
const ref = useRef();
|
|
||||||
|
|
||||||
const handleChange = (newDate) => {
|
|
||||||
if (value !== newDate && onChange) {
|
|
||||||
onChange(isDateOnly ? newDate && newDate.format("YYYY-MM-DD") : newDate);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleKeyDown = (e) => {
|
|
||||||
if (e.key.toLowerCase() === "t") {
|
|
||||||
if (onChange) {
|
|
||||||
onChange(isDateOnly ? dayjs().format("YYYY-MM-DD") : dayjs());
|
|
||||||
}
|
|
||||||
} else if (e.key.toLowerCase() === "enter") {
|
|
||||||
if (ref.current && ref.current.blur) ref.current.blur();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleBlur = (e) => {
|
|
||||||
const v = e.target.value;
|
|
||||||
if (!v) return;
|
|
||||||
|
|
||||||
const formats = [
|
|
||||||
"MMDDYY",
|
|
||||||
"MMDDYYYY",
|
|
||||||
"MM/DD/YY",
|
|
||||||
"MM/DD/YYYY",
|
|
||||||
"M/DD/YY",
|
|
||||||
"M/DD/YYYY",
|
|
||||||
"MM/D/YY",
|
|
||||||
"MM/D/YYYY",
|
|
||||||
"M/D/YY",
|
|
||||||
"M/D/YYYY",
|
|
||||||
"D/MM/YY",
|
|
||||||
"D/MM/YYYY",
|
|
||||||
"DD/M/YY",
|
|
||||||
"DD/M/YYYY",
|
|
||||||
"D/M/YY",
|
|
||||||
"D/M/YYYY"
|
|
||||||
];
|
|
||||||
|
|
||||||
let _a;
|
|
||||||
|
|
||||||
// Iterate through formats to find the correct one
|
|
||||||
for (let format of formats) {
|
|
||||||
_a = dayjs(v, format);
|
|
||||||
if (v === _a.format(format)) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_a.isValid() && value && value.isValid && value.isValid()) {
|
|
||||||
_a.set({
|
|
||||||
hours: value.hours(),
|
|
||||||
minutes: value.minutes(),
|
|
||||||
seconds: value.seconds(),
|
|
||||||
milliseconds: value.milliseconds()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_a.isValid() && onChange) {
|
|
||||||
if (onlyFuture) {
|
|
||||||
if (dayjs().subtract(1, "day").isBefore(_a)) {
|
|
||||||
onChange(isDateOnly ? _a.format("YYYY-MM-DD") : _a);
|
|
||||||
} else {
|
|
||||||
onChange(isDateOnly ? dayjs().format("YYYY-MM-DD") : dayjs());
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
onChange(isDateOnly ? _a.format("YYYY-MM-DD") : _a);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div onKeyDown={handleKeyDown}>
|
|
||||||
<DatePicker
|
|
||||||
ref={ref}
|
|
||||||
value={value ? dayjs(value) : null}
|
|
||||||
onChange={handleChange}
|
|
||||||
format={dateFormat}
|
|
||||||
onBlur={onBlur || handleBlur}
|
|
||||||
showToday={false}
|
|
||||||
disabledTime
|
|
||||||
disabledDate={(d) => {
|
|
||||||
if (onlyToday) {
|
|
||||||
return !dayjs().isSame(d, "day");
|
|
||||||
} else if (onlyFuture) {
|
|
||||||
return dayjs().subtract(1, "day").isAfter(d);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
{...restProps}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
import { DatePicker } from "antd";
|
|
||||||
import dayjs from "../../utils/day.js";
|
|
||||||
import React, { useRef } from "react";
|
|
||||||
|
|
||||||
import { connect } from "react-redux";
|
|
||||||
import { createStructuredSelector } from "reselect";
|
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors.js";
|
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
|
||||||
bodyshop: selectBodyshop
|
|
||||||
});
|
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
|
||||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
|
||||||
});
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(FormDateTimePickerEnhanced);
|
|
||||||
|
|
||||||
const dateFormat = "MM/DD/YYYY h:mm a";
|
|
||||||
|
|
||||||
export function FormDateTimePickerEnhanced({
|
|
||||||
bodyshop,
|
|
||||||
value,
|
|
||||||
onBlur,
|
|
||||||
onlyFuture,
|
|
||||||
onlyToday,
|
|
||||||
isDateOnly = true,
|
|
||||||
...restProps
|
|
||||||
}) {
|
|
||||||
const ref = useRef();
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<DatePicker
|
|
||||||
ref={ref}
|
|
||||||
value={value ? dayjs(value) : null}
|
|
||||||
format={dateFormat}
|
|
||||||
onBlur={onBlur}
|
|
||||||
showToday={false}
|
|
||||||
disabledDate={(d) => {
|
|
||||||
if (onlyToday) {
|
|
||||||
return !dayjs().isSame(d, "day");
|
|
||||||
} else if (onlyFuture) {
|
|
||||||
return dayjs().subtract(1, "day").isAfter(d);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
{...restProps}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,46 +1,105 @@
|
|||||||
import React, { forwardRef } from "react";
|
import { DatePicker } from "antd";
|
||||||
//import DatePicker from "react-datepicker";
|
import PropTypes from "prop-types";
|
||||||
//import "react-datepicker/src/stylesheets/datepicker.scss";
|
import React, { useCallback, useState } from "react";
|
||||||
import { Space, TimePicker } from "antd";
|
import { useTranslation } from "react-i18next";
|
||||||
import dayjs from "../../utils/day";
|
import dayjs from "../../utils/day";
|
||||||
import FormDatePicker from "../form-date-picker/form-date-picker.component";
|
import { fuzzyMatchDate } from "./formats.js";
|
||||||
//To be used as a form element only.
|
|
||||||
|
|
||||||
const DateTimePicker = ({ value, onChange, onBlur, id, onlyFuture, ...restProps }, ref) => {
|
const DateTimePicker = ({ value, onChange, onBlur, id, onlyFuture, onlyToday, isDateOnly = false, ...restProps }) => {
|
||||||
// const handleChange = (newDate) => {
|
const [isManualInput, setIsManualInput] = useState(false);
|
||||||
// if (value !== newDate && onChange) {
|
const { t } = useTranslation();
|
||||||
// onChange(newDate);
|
|
||||||
// }
|
const handleChange = useCallback(
|
||||||
// };
|
(newDate) => {
|
||||||
|
if (onChange) {
|
||||||
|
onChange(newDate || null);
|
||||||
|
}
|
||||||
|
setIsManualInput(false);
|
||||||
|
},
|
||||||
|
[onChange]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleBlur = useCallback(
|
||||||
|
(e) => {
|
||||||
|
// Bail if this is not a manual input
|
||||||
|
if (!isManualInput) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Reset manual input flag
|
||||||
|
setIsManualInput(false);
|
||||||
|
|
||||||
|
const v = e?.target?.value;
|
||||||
|
|
||||||
|
if (!v) return;
|
||||||
|
|
||||||
|
let parsedDate = isDateOnly ? fuzzyMatchDate(v)?.startOf("day") : fuzzyMatchDate(v);
|
||||||
|
|
||||||
|
if (parsedDate && onChange) {
|
||||||
|
onChange(parsedDate);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[isManualInput, isDateOnly, onChange]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleKeyDown = useCallback(
|
||||||
|
(e) => {
|
||||||
|
setIsManualInput(true);
|
||||||
|
|
||||||
|
if (e.key.toLowerCase() === "t" && onChange) {
|
||||||
|
e.preventDefault();
|
||||||
|
setIsManualInput(false);
|
||||||
|
onChange(dayjs());
|
||||||
|
} else if (e.key.toLowerCase() === "enter") {
|
||||||
|
handleBlur(e);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[onChange, handleBlur]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleDisabledDate = useCallback(
|
||||||
|
(current) => {
|
||||||
|
if (onlyToday) {
|
||||||
|
return !dayjs().isSame(current, "day");
|
||||||
|
} else if (onlyFuture) {
|
||||||
|
return dayjs().subtract(1, "day").isAfter(current);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
[onlyToday, onlyFuture]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Space direction="vertical" style={{ width: "100%" }} id={id}>
|
<div onKeyDown={handleKeyDown} id={id} style={{ width: "100%" }}>
|
||||||
<FormDatePicker
|
<DatePicker
|
||||||
{...restProps}
|
showTime={
|
||||||
{...(onlyFuture && {
|
isDateOnly
|
||||||
disabledDate: (d) => dayjs().subtract(1, "day").isAfter(d)
|
? false
|
||||||
})}
|
: {
|
||||||
value={value}
|
format: "hh:mm a",
|
||||||
onBlur={onBlur}
|
minuteStep: 15,
|
||||||
onChange={onChange}
|
defaultValue: dayjs(dayjs(), "HH:mm:ss")
|
||||||
onlyFuture={onlyFuture}
|
}
|
||||||
isDateOnly={false}
|
}
|
||||||
/>
|
format={isDateOnly ? "MM/DD/YYYY" : "MM/DD/YYYY hh:mm a"}
|
||||||
|
|
||||||
<TimePicker
|
|
||||||
value={value ? dayjs(value) : null}
|
value={value ? dayjs(value) : null}
|
||||||
{...(onlyFuture && {
|
onChange={handleChange}
|
||||||
disabledDate: (d) => dayjs().isAfter(d)
|
placeholder={isDateOnly ? t("general.labels.date") : t("general.labels.datetime")}
|
||||||
})}
|
onBlur={onBlur || handleBlur}
|
||||||
onChange={onChange}
|
disabledDate={handleDisabledDate}
|
||||||
disableSeconds={true}
|
|
||||||
minuteStep={15}
|
|
||||||
onBlur={onBlur}
|
|
||||||
format="hh:mm a"
|
|
||||||
{...restProps}
|
{...restProps}
|
||||||
/>
|
/>
|
||||||
</Space>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default forwardRef(DateTimePicker);
|
DateTimePicker.propTypes = {
|
||||||
|
value: PropTypes.any,
|
||||||
|
onChange: PropTypes.func,
|
||||||
|
onBlur: PropTypes.func,
|
||||||
|
id: PropTypes.string,
|
||||||
|
onlyFuture: PropTypes.bool,
|
||||||
|
onlyToday: PropTypes.bool,
|
||||||
|
isDateOnly: PropTypes.bool
|
||||||
|
};
|
||||||
|
|
||||||
|
export default React.memo(DateTimePicker);
|
||||||
|
|||||||
63
client/src/components/form-date-time-picker/formats.js
Normal file
63
client/src/components/form-date-time-picker/formats.js
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
import dayjs from "../../utils/day";
|
||||||
|
|
||||||
|
const dateFormats = [
|
||||||
|
"MMDDYYYY",
|
||||||
|
"MMDDYY",
|
||||||
|
"M/D/YYYY",
|
||||||
|
"MM/D/YYYY",
|
||||||
|
"M/DD/YYYY",
|
||||||
|
"MM/DD/YYYY",
|
||||||
|
"M/D/YY",
|
||||||
|
"MM/D/YY",
|
||||||
|
"M/DD/YY",
|
||||||
|
"MM/DD/YY"
|
||||||
|
];
|
||||||
|
|
||||||
|
const timeFormats = ["h:mm A", "h:mmA", "h A", "hA", "hh:mm A", "hh:mm:ss A"];
|
||||||
|
|
||||||
|
const dateTimeFormats = [
|
||||||
|
...["M/D/YYYY", "MM/D/YYYY", "M/DD/YYYY", "MM/DD/YYYY", "M/D/YY", "MM/D/YY", "M/DD/YY", "MM/DD/YY"].flatMap(
|
||||||
|
(dateFormat) => timeFormats.map((timeFormat) => `${dateFormat} ${timeFormat}`)
|
||||||
|
),
|
||||||
|
|
||||||
|
...["MMDDYYYY", "MMDDYY"].flatMap((dateFormat) => timeFormats.map((timeFormat) => `${dateFormat} ${timeFormat}`)),
|
||||||
|
|
||||||
|
"M/D/YYYY",
|
||||||
|
"MM/D/YYYY",
|
||||||
|
"M/DD/YYYY",
|
||||||
|
"MM/DD/YYYY",
|
||||||
|
"M/D/YY",
|
||||||
|
"MM/D/YY",
|
||||||
|
"M/DD/YY",
|
||||||
|
"MM/DD/YY",
|
||||||
|
"MMDDYYYY",
|
||||||
|
"MMDDYY"
|
||||||
|
];
|
||||||
|
|
||||||
|
const sanitizeInput = (input) =>
|
||||||
|
input
|
||||||
|
.trim()
|
||||||
|
.toUpperCase()
|
||||||
|
.replace(/\s*(am|pm)\s*/i, " $1")
|
||||||
|
.replaceAll(".", "/")
|
||||||
|
.replaceAll("-", "/");
|
||||||
|
|
||||||
|
export const fuzzyMatchDate = (dateString) => {
|
||||||
|
const sanitizedInput = sanitizeInput(dateString);
|
||||||
|
|
||||||
|
for (const format of dateFormats) {
|
||||||
|
const parsedDate = dayjs(sanitizedInput, format, true);
|
||||||
|
if (parsedDate.isValid()) {
|
||||||
|
return parsedDate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const format of dateTimeFormats) {
|
||||||
|
const parsedDateTime = dayjs(sanitizedInput, format, true);
|
||||||
|
if (parsedDateTime.isValid()) {
|
||||||
|
return parsedDateTime; // Return the dayjs object
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null; // If no matching format is found
|
||||||
|
};
|
||||||
@@ -3,13 +3,15 @@ import axios from "axios";
|
|||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Link } from "react-router-dom";
|
import { Link, useNavigate } from "react-router-dom";
|
||||||
import PhoneNumberFormatter from "../../utils/PhoneFormatter";
|
import PhoneNumberFormatter from "../../utils/PhoneFormatter";
|
||||||
import OwnerNameDisplay, { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component";
|
import OwnerNameDisplay, { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component";
|
||||||
import VehicleVinDisplay from "../vehicle-vin-display/vehicle-vin-display.component";
|
import VehicleVinDisplay from "../vehicle-vin-display/vehicle-vin-display.component";
|
||||||
|
|
||||||
export default function GlobalSearchOs() {
|
export default function GlobalSearchOs() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [data, setData] = useState(false);
|
const [data, setData] = useState(false);
|
||||||
|
|
||||||
@@ -177,7 +179,18 @@ export default function GlobalSearchOs() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AutoComplete options={data} onSearch={handleSearch} defaultActiveFirstOption onClear={() => setData([])}>
|
<AutoComplete
|
||||||
|
options={data}
|
||||||
|
onSearch={handleSearch}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.key !== "Enter") return;
|
||||||
|
const firstUrlForSearch = data?.[0]?.options?.[0]?.label?.props?.to;
|
||||||
|
if (!firstUrlForSearch) return;
|
||||||
|
navigate(firstUrlForSearch);
|
||||||
|
}}
|
||||||
|
defaultActiveFirstOption
|
||||||
|
onClear={() => setData([])}
|
||||||
|
>
|
||||||
<Input.Search
|
<Input.Search
|
||||||
size="large"
|
size="large"
|
||||||
placeholder={t("general.labels.globalsearch")}
|
placeholder={t("general.labels.globalsearch")}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { AutoComplete, Divider, Input, Space } from "antd";
|
|||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Link } from "react-router-dom";
|
import { Link, useNavigate } from "react-router-dom";
|
||||||
import { GLOBAL_SEARCH_QUERY } from "../../graphql/search.queries";
|
import { GLOBAL_SEARCH_QUERY } from "../../graphql/search.queries";
|
||||||
import PhoneNumberFormatter from "../../utils/PhoneFormatter";
|
import PhoneNumberFormatter from "../../utils/PhoneFormatter";
|
||||||
import AlertComponent from "../alert/alert.component";
|
import AlertComponent from "../alert/alert.component";
|
||||||
@@ -13,6 +13,7 @@ import VehicleVinDisplay from "../vehicle-vin-display/vehicle-vin-display.compon
|
|||||||
export default function GlobalSearch() {
|
export default function GlobalSearch() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [callSearch, { loading, error, data }] = useLazyQuery(GLOBAL_SEARCH_QUERY);
|
const [callSearch, { loading, error, data }] = useLazyQuery(GLOBAL_SEARCH_QUERY);
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const executeSearch = (v) => {
|
const executeSearch = (v) => {
|
||||||
if (v && v.variables.search && v.variables.search !== "" && v.variables.search.length >= 3) callSearch(v);
|
if (v && v.variables.search && v.variables.search !== "" && v.variables.search.length >= 3) callSearch(v);
|
||||||
@@ -20,7 +21,6 @@ export default function GlobalSearch() {
|
|||||||
const debouncedExecuteSearch = _.debounce(executeSearch, 750);
|
const debouncedExecuteSearch = _.debounce(executeSearch, 750);
|
||||||
|
|
||||||
const handleSearch = (value) => {
|
const handleSearch = (value) => {
|
||||||
console.log("Handle Search");
|
|
||||||
debouncedExecuteSearch({ variables: { search: value } });
|
debouncedExecuteSearch({ variables: { search: value } });
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -156,7 +156,17 @@ export default function GlobalSearch() {
|
|||||||
if (error) return <AlertComponent message={error.message} type="error" />;
|
if (error) return <AlertComponent message={error.message} type="error" />;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AutoComplete options={options} onSearch={handleSearch} defaultActiveFirstOption>
|
<AutoComplete
|
||||||
|
options={options}
|
||||||
|
onSearch={handleSearch}
|
||||||
|
defaultActiveFirstOption
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.key !== "Enter") return;
|
||||||
|
const firstUrlForSearch = options?.[0]?.options?.[0]?.label?.props?.to;
|
||||||
|
if (!firstUrlForSearch) return;
|
||||||
|
navigate(firstUrlForSearch);
|
||||||
|
}}
|
||||||
|
>
|
||||||
<Input.Search
|
<Input.Search
|
||||||
size="large"
|
size="large"
|
||||||
placeholder={t("general.labels.globalsearch")}
|
placeholder={t("general.labels.globalsearch")}
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import Icon, {
|
|||||||
FileFilled,
|
FileFilled,
|
||||||
HomeFilled,
|
HomeFilled,
|
||||||
ImportOutlined,
|
ImportOutlined,
|
||||||
InfoCircleOutlined,
|
|
||||||
LineChartOutlined,
|
LineChartOutlined,
|
||||||
PaperClipOutlined,
|
PaperClipOutlined,
|
||||||
PhoneOutlined,
|
PhoneOutlined,
|
||||||
@@ -27,8 +26,8 @@ import Icon, {
|
|||||||
UserOutlined
|
UserOutlined
|
||||||
} from "@ant-design/icons";
|
} from "@ant-design/icons";
|
||||||
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
||||||
import { Layout, Menu, Switch, Tooltip } from "antd";
|
import { Layout, Menu } from "antd";
|
||||||
import React, { useEffect, useState } from "react";
|
import React from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { BsKanban } from "react-icons/bs";
|
import { BsKanban } from "react-icons/bs";
|
||||||
import { FaCalendarAlt, FaCarCrash, FaCreditCard, FaFileInvoiceDollar, FaTasks } from "react-icons/fa";
|
import { FaCalendarAlt, FaCarCrash, FaCreditCard, FaFileInvoiceDollar, FaTasks } from "react-icons/fa";
|
||||||
@@ -43,7 +42,6 @@ import { selectRecentItems, selectSelectedHeader } from "../../redux/application
|
|||||||
import { setModalContext } from "../../redux/modals/modals.actions";
|
import { setModalContext } from "../../redux/modals/modals.actions";
|
||||||
import { signOutStart } from "../../redux/user/user.actions";
|
import { signOutStart } from "../../redux/user/user.actions";
|
||||||
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors";
|
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors";
|
||||||
import { checkBeta, handleBeta, setBeta } from "../../utils/betaHandler";
|
|
||||||
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
||||||
import { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component";
|
import { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component";
|
||||||
|
|
||||||
@@ -115,20 +113,22 @@ function Header({
|
|||||||
names: ["ImEXPay", "DmsAp", "Simple_Inventory"],
|
names: ["ImEXPay", "DmsAp", "Simple_Inventory"],
|
||||||
splitKey: bodyshop && bodyshop.imexshopid
|
splitKey: bodyshop && bodyshop.imexshopid
|
||||||
});
|
});
|
||||||
const [betaSwitch, setBetaSwitch] = useState(false);
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
useEffect(() => {
|
const deleteBetaCookie = () => {
|
||||||
const isBeta = checkBeta();
|
const cookieExists = document.cookie.split("; ").some((row) => row.startsWith(`betaSwitchImex=`));
|
||||||
setBetaSwitch(isBeta);
|
if (cookieExists) {
|
||||||
}, []);
|
const domain = window.location.hostname.split(".").slice(-2).join(".");
|
||||||
|
document.cookie = `betaSwitchImex=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/; domain=.${domain}`;
|
||||||
const betaSwitchChange = (checked) => {
|
console.log(`betaSwitchImex cookie deleted`);
|
||||||
setBeta(checked);
|
} else {
|
||||||
setBetaSwitch(checked);
|
console.log(`betaSwitchImex cookie does not exist`);
|
||||||
handleBeta();
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
deleteBetaCookie();
|
||||||
|
|
||||||
const accountingChildren = [];
|
const accountingChildren = [];
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@@ -695,31 +695,6 @@ function Header({
|
|||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
InstanceRenderManager({
|
|
||||||
executeFunction: true,
|
|
||||||
args: [],
|
|
||||||
imex: () => {
|
|
||||||
menuItems.push({
|
|
||||||
key: "beta-switch",
|
|
||||||
id: "header-beta-switch",
|
|
||||||
style: { marginLeft: "auto" },
|
|
||||||
label: (
|
|
||||||
<Tooltip
|
|
||||||
title={`A more modern ${InstanceRenderManager({
|
|
||||||
imex: t("titles.imexonline"),
|
|
||||||
rome: t("titles.romeonline"),
|
|
||||||
promanager: t("titles.promanager")
|
|
||||||
})} is ready for you to try! You can switch back at any time.`}
|
|
||||||
>
|
|
||||||
<InfoCircleOutlined />
|
|
||||||
<span style={{ marginRight: 8 }}>Try the new app</span>
|
|
||||||
<Switch checked={betaSwitch} onChange={betaSwitchChange} />
|
|
||||||
</Tooltip>
|
|
||||||
)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout.Header>
|
<Layout.Header>
|
||||||
<Menu
|
<Menu
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
import { useMutation } from "@apollo/client";
|
import { useMutation } from "@apollo/client";
|
||||||
|
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
||||||
import { notification } from "antd";
|
import { notification } from "antd";
|
||||||
|
import Axios from "axios";
|
||||||
|
import Dinero from "dinero.js";
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
@@ -7,13 +10,10 @@ import { createStructuredSelector } from "reselect";
|
|||||||
import { INSERT_NEW_JOB_LINE, UPDATE_JOB_LINE } from "../../graphql/jobs-lines.queries";
|
import { INSERT_NEW_JOB_LINE, UPDATE_JOB_LINE } from "../../graphql/jobs-lines.queries";
|
||||||
import { toggleModalVisible } from "../../redux/modals/modals.actions";
|
import { toggleModalVisible } from "../../redux/modals/modals.actions";
|
||||||
import { selectJobLineEditModal } from "../../redux/modals/modals.selectors";
|
import { selectJobLineEditModal } from "../../redux/modals/modals.selectors";
|
||||||
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
|
import CriticalPartsScan from "../../utils/criticalPartsScan";
|
||||||
import UndefinedToNull from "../../utils/undefinedtonull";
|
import UndefinedToNull from "../../utils/undefinedtonull";
|
||||||
import JobLinesUpdsertModal from "./job-lines-upsert-modal.component";
|
import JobLinesUpdsertModal from "./job-lines-upsert-modal.component";
|
||||||
import Axios from "axios";
|
|
||||||
import Dinero from "dinero.js";
|
|
||||||
import CriticalPartsScan from "../../utils/criticalPartsScan";
|
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
|
||||||
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
jobLineEditModal: selectJobLineEditModal,
|
jobLineEditModal: selectJobLineEditModal,
|
||||||
@@ -82,13 +82,15 @@ function JobLinesUpsertModalContainer({ jobLineEditModal, toggleModalVisible, bo
|
|||||||
variables: {
|
variables: {
|
||||||
lineId: jobLineEditModal.context.id,
|
lineId: jobLineEditModal.context.id,
|
||||||
line: {
|
line: {
|
||||||
...values,
|
...UndefinedToNull({
|
||||||
prt_dsmk_m: Dinero({
|
...values,
|
||||||
amount: Math.round(values.act_price * 100)
|
prt_dsmk_m: Dinero({
|
||||||
|
amount: Math.round(values.act_price * 100)
|
||||||
|
})
|
||||||
|
.percentage(Math.abs(values.prt_dsmk_p || 0))
|
||||||
|
.multiply(values.prt_dsmk_p >= 0 ? 1 : -1)
|
||||||
|
.toFormat(0.0)
|
||||||
})
|
})
|
||||||
.percentage(Math.abs(values.prt_dsmk_p || 0))
|
|
||||||
.multiply(values.prt_dsmk_p >= 0 ? 1 : -1)
|
|
||||||
.toFormat(0.0)
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
refetchQueries: ["GET_LINE_TICKET_BY_PK"]
|
refetchQueries: ["GET_LINE_TICKET_BY_PK"]
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ import {
|
|||||||
QUERY_SCOREBOARD_ENTRY,
|
QUERY_SCOREBOARD_ENTRY,
|
||||||
UPDATE_SCOREBOARD_ENTRY
|
UPDATE_SCOREBOARD_ENTRY
|
||||||
} from "../../graphql/scoreboard.queries";
|
} from "../../graphql/scoreboard.queries";
|
||||||
import FormDatePicker from "../form-date-picker/form-date-picker.component";
|
|
||||||
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
|
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
|
||||||
|
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component.jsx";
|
||||||
|
|
||||||
export default function ScoreboardAddButton({ job, disabled, ...otherBtnProps }) {
|
export default function ScoreboardAddButton({ job, disabled, ...otherBtnProps }) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@@ -86,7 +86,7 @@ export default function ScoreboardAddButton({ job, disabled, ...otherBtnProps })
|
|||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<FormDatePicker />
|
<DateTimePicker isDateOnly />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={t("scoreboard.fields.bodyhrs")}
|
label={t("scoreboard.fields.bodyhrs")}
|
||||||
|
|||||||
@@ -141,10 +141,14 @@ export function JobTotalsTableTotals({ bodyshop, job }) {
|
|||||||
key: t("jobs.fields.ded_amt"),
|
key: t("jobs.fields.ded_amt"),
|
||||||
total: job.job_totals.totals.custPayable.deductible
|
total: job.job_totals.totals.custPayable.deductible
|
||||||
},
|
},
|
||||||
// {
|
...(InstanceRenderManager({
|
||||||
// key: t("jobs.fields.federal_tax_payable"),
|
imex: [{
|
||||||
// total: job.job_totals.totals.custPayable.federal_tax,
|
key: t("jobs.fields.federal_tax_payable"),
|
||||||
// },
|
total: job.job_totals.totals.custPayable.federal_tax
|
||||||
|
}],
|
||||||
|
rome: [],
|
||||||
|
promanager: "USE_ROME"
|
||||||
|
})),
|
||||||
{
|
{
|
||||||
key: t("jobs.fields.other_amount_payable"),
|
key: t("jobs.fields.other_amount_payable"),
|
||||||
total: job.job_totals.totals.custPayable.other_customer_amount
|
total: job.job_totals.totals.custPayable.other_customer_amount
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import React, { useEffect, useState } from "react";
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { UPDATE_JOB } from "../../graphql/jobs.queries";
|
import { UPDATE_JOB } from "../../graphql/jobs.queries";
|
||||||
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
||||||
import FormDatePicker from "../form-date-picker/form-date-picker.component";
|
|
||||||
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component";
|
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component";
|
||||||
import FormFieldsChanged from "../form-fields-changed-alert/form-fields-changed-alert.component";
|
import FormFieldsChanged from "../form-fields-changed-alert/form-fields-changed-alert.component";
|
||||||
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
||||||
@@ -20,7 +19,14 @@ const mapStateToProps = createStructuredSelector({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
insertAuditTrail: ({ jobid, operation, type }) => dispatch(insertAuditTrail({ jobid, operation, type }))
|
insertAuditTrail: ({ jobid, operation, type }) =>
|
||||||
|
dispatch(
|
||||||
|
insertAuditTrail({
|
||||||
|
jobid,
|
||||||
|
operation,
|
||||||
|
type
|
||||||
|
})
|
||||||
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(JobsAdminDatesChange);
|
export default connect(mapStateToProps, mapDispatchToProps)(JobsAdminDatesChange);
|
||||||
@@ -87,7 +93,7 @@ export function JobsAdminDatesChange({ insertAuditTrail, job }) {
|
|||||||
<FormFieldsChanged form={form} />
|
<FormFieldsChanged form={form} />
|
||||||
<LayoutFormRow header={t("jobs.forms.estdates")}>
|
<LayoutFormRow header={t("jobs.forms.estdates")}>
|
||||||
<Form.Item label={t("jobs.fields.date_estimated")} name="date_estimated">
|
<Form.Item label={t("jobs.fields.date_estimated")} name="date_estimated">
|
||||||
<FormDatePicker format="MM/DD/YYYY" />
|
<DateTimePicker format="MM/DD/YYYY" isDateOnly />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label={t("jobs.fields.date_towin")} name="date_towin">
|
<Form.Item label={t("jobs.fields.date_towin")} name="date_towin">
|
||||||
<DateTimePicker />
|
<DateTimePicker />
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
|||||||
import { Col, Row, notification } from "antd";
|
import { Col, Row, notification } from "antd";
|
||||||
import Axios from "axios";
|
import Axios from "axios";
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
import dayjs from "../../utils/day";
|
|
||||||
import queryString from "query-string";
|
import queryString from "query-string";
|
||||||
import React, { useCallback, useEffect, useState } from "react";
|
import React, { useCallback, useEffect, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
@@ -24,6 +23,8 @@ import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selecto
|
|||||||
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
||||||
import confirmDialog from "../../utils/asyncConfirm";
|
import confirmDialog from "../../utils/asyncConfirm";
|
||||||
import CriticalPartsScan from "../../utils/criticalPartsScan";
|
import CriticalPartsScan from "../../utils/criticalPartsScan";
|
||||||
|
import dayjs from "../../utils/day";
|
||||||
|
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
||||||
import AlertComponent from "../alert/alert.component";
|
import AlertComponent from "../alert/alert.component";
|
||||||
import JobsAvailableScan from "../jobs-available-scan/jobs-available-scan.component";
|
import JobsAvailableScan from "../jobs-available-scan/jobs-available-scan.component";
|
||||||
import JobsFindModalContainer from "../jobs-find-modal/jobs-find-modal.container";
|
import JobsFindModalContainer from "../jobs-find-modal/jobs-find-modal.container";
|
||||||
@@ -32,7 +33,6 @@ import OwnerFindModalContainer from "../owner-find-modal/owner-find-modal.contai
|
|||||||
import { GetSupplementDelta } from "./jobs-available-supplement.estlines.util";
|
import { GetSupplementDelta } from "./jobs-available-supplement.estlines.util";
|
||||||
import HeaderFields from "./jobs-available-supplement.headerfields";
|
import HeaderFields from "./jobs-available-supplement.headerfields";
|
||||||
import JobsAvailableTableComponent from "./jobs-available-table.component";
|
import JobsAvailableTableComponent from "./jobs-available-table.component";
|
||||||
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop,
|
bodyshop: selectBodyshop,
|
||||||
@@ -580,12 +580,13 @@ function ResolveCCCLineIssues(estData, bodyshop) {
|
|||||||
InstanceRenderManager({
|
InstanceRenderManager({
|
||||||
executeFunction: true,
|
executeFunction: true,
|
||||||
args: [],
|
args: [],
|
||||||
promanager: () => {
|
rome: () => {
|
||||||
if (line.mod_lbr_ty === "LAET" || line.mod_lbr_ty === "LAUT") {
|
if (line.mod_lbr_ty === "LAET" || line.mod_lbr_ty === "LAUT") {
|
||||||
// line.notes += ` | ET/UT Update (prev = ${line.mod_lbr_ty})`;
|
// line.notes += ` | ET/UT Update (prev = ${line.mod_lbr_ty})`;
|
||||||
line.mod_lbr_ty = "LAR";
|
line.mod_lbr_ty = "LAR";
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
promanager: "USE_ROME"
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,18 +1,9 @@
|
|||||||
import {
|
import { Collapse, Form, Input, InputNumber, Select, Space, Switch } from "antd";
|
||||||
Collapse,
|
|
||||||
Form,
|
|
||||||
Input,
|
|
||||||
InputNumber,
|
|
||||||
Select,
|
|
||||||
Space,
|
|
||||||
Switch,
|
|
||||||
} from "antd";
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
import FormDatePicker from "../form-date-picker/form-date-picker.component";
|
|
||||||
import CurrencyInput from "../form-items-formatted/currency-form-item.component";
|
import CurrencyInput from "../form-items-formatted/currency-form-item.component";
|
||||||
import FormItemEmail from "../form-items-formatted/email-form-item.component";
|
import FormItemEmail from "../form-items-formatted/email-form-item.component";
|
||||||
import FormItemPhone, { PhoneItemFormatterValidation } from "../form-items-formatted/phone-form-item.component";
|
import FormItemPhone, { PhoneItemFormatterValidation } from "../form-items-formatted/phone-form-item.component";
|
||||||
@@ -29,6 +20,7 @@ import JobsDetailRatesTaxes from "../jobs-detail-rates/jobs-detail-rates.taxes.c
|
|||||||
import JobsMarkPstExempt from "../jobs-mark-pst-exempt/jobs-mark-pst-exempt.component";
|
import JobsMarkPstExempt from "../jobs-mark-pst-exempt/jobs-mark-pst-exempt.component";
|
||||||
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
||||||
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
||||||
|
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component.jsx";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
//currentUser: selectCurrentUser
|
//currentUser: selectCurrentUser
|
||||||
@@ -61,10 +53,7 @@ export function JobsCreateJobsInfo({ bodyshop, form, selected }) {
|
|||||||
<Form.Item label={t("jobs.fields.policy_no")} name="policy_no">
|
<Form.Item label={t("jobs.fields.policy_no")} name="policy_no">
|
||||||
<Input />
|
<Input />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item label={t("jobs.fields.regie_number")} name="regie_number">
|
||||||
label={t("jobs.fields.regie_number")}
|
|
||||||
name="regie_number"
|
|
||||||
>
|
|
||||||
<Input />
|
<Input />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label={t("jobs.fields.ins_co_nm")} name="ins_co_nm">
|
<Form.Item label={t("jobs.fields.ins_co_nm")} name="ins_co_nm">
|
||||||
@@ -116,7 +105,7 @@ export function JobsCreateJobsInfo({ bodyshop, form, selected }) {
|
|||||||
<FormItemEmail email={getFieldValue("ins_ea")} />
|
<FormItemEmail email={getFieldValue("ins_ea")} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label={t("jobs.fields.loss_date")} name="loss_date">
|
<Form.Item label={t("jobs.fields.loss_date")} name="loss_date">
|
||||||
<FormDatePicker />
|
<DateTimePicker isDateOnly />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label={t("jobs.fields.kmin")} name="kmin">
|
<Form.Item label={t("jobs.fields.kmin")} name="kmin">
|
||||||
<Input />
|
<Input />
|
||||||
|
|||||||
@@ -2,9 +2,9 @@ import { Form, Input } from "antd";
|
|||||||
import React, { useContext } from "react";
|
import React, { useContext } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import JobCreateContext from "../../pages/jobs-create/jobs-create.context";
|
import JobCreateContext from "../../pages/jobs-create/jobs-create.context";
|
||||||
import FormDatePicker from "../form-date-picker/form-date-picker.component";
|
|
||||||
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
||||||
import JobsCreateVehicleInfoPredefined from "./jobs-create-vehicle-info.predefined.component";
|
import JobsCreateVehicleInfoPredefined from "./jobs-create-vehicle-info.predefined.component";
|
||||||
|
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component.jsx";
|
||||||
|
|
||||||
export default function JobsCreateVehicleInfoNewComponent({ form }) {
|
export default function JobsCreateVehicleInfoNewComponent({ form }) {
|
||||||
const [state] = useContext(JobCreateContext);
|
const [state] = useContext(JobCreateContext);
|
||||||
@@ -113,7 +113,7 @@ export default function JobsCreateVehicleInfoNewComponent({ form }) {
|
|||||||
<Input disabled={!state.vehicle.new} />
|
<Input disabled={!state.vehicle.new} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label={t("vehicles.fields.v_prod_dt")} name={["vehicle", "data", "v_prod_dt"]}>
|
<Form.Item label={t("vehicles.fields.v_prod_dt")} name={["vehicle", "data", "v_prod_dt"]}>
|
||||||
<FormDatePicker disabled={!state.vehicle.new} />
|
<DateTimePicker isDateOnly disabled={!state.vehicle.new} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</LayoutFormRow>
|
</LayoutFormRow>
|
||||||
<LayoutFormRow grow>
|
<LayoutFormRow grow>
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import { connect } from "react-redux";
|
|||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import { selectJobReadOnly } from "../../redux/application/application.selectors";
|
import { selectJobReadOnly } from "../../redux/application/application.selectors";
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
import FormDatePicker from "../form-date-picker/form-date-picker.component";
|
|
||||||
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component";
|
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component";
|
||||||
import FormRow from "../layout-form-row/layout-form-row.component";
|
import FormRow from "../layout-form-row/layout-form-row.component";
|
||||||
|
|
||||||
@@ -30,7 +29,7 @@ export function JobsDetailDatesComponent({ jobRO, job, bodyshop }) {
|
|||||||
<div>
|
<div>
|
||||||
<FormRow header={t("jobs.forms.estdates")}>
|
<FormRow header={t("jobs.forms.estdates")}>
|
||||||
<Form.Item label={t("jobs.fields.date_estimated")} name="date_estimated">
|
<Form.Item label={t("jobs.fields.date_estimated")} name="date_estimated">
|
||||||
<FormDatePicker disabled={jobRO} />
|
<DateTimePicker disabled={jobRO} isDateOnly />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label={t("jobs.fields.date_open")} name="date_open">
|
<Form.Item label={t("jobs.fields.date_open")} name="date_open">
|
||||||
<DateTimePicker disabled={jobRO} />
|
<DateTimePicker disabled={jobRO} />
|
||||||
@@ -45,7 +44,7 @@ export function JobsDetailDatesComponent({ jobRO, job, bodyshop }) {
|
|||||||
|
|
||||||
<FormRow header={t("jobs.forms.scheddates")}>
|
<FormRow header={t("jobs.forms.scheddates")}>
|
||||||
<Form.Item label={t("jobs.fields.date_scheduled")} name="date_scheduled">
|
<Form.Item label={t("jobs.fields.date_scheduled")} name="date_scheduled">
|
||||||
<FormDatePicker disabled={jobRO} />
|
<DateTimePicker disabled={jobRO} isDateOnly />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Tooltip title={t("jobs.labels.scheduledinchange")}>
|
<Tooltip title={t("jobs.labels.scheduledinchange")}>
|
||||||
<Form.Item label={t("jobs.fields.scheduled_in")} name="scheduled_in">
|
<Form.Item label={t("jobs.fields.scheduled_in")} name="scheduled_in">
|
||||||
@@ -85,7 +84,6 @@ export function JobsDetailDatesComponent({ jobRO, job, bodyshop }) {
|
|||||||
rules={[
|
rules={[
|
||||||
{
|
{
|
||||||
required: jobInPostProduction
|
required: jobInPostProduction
|
||||||
//message: t("general.validation.required"),
|
|
||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import { connect } from "react-redux";
|
|||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import { selectJobReadOnly } from "../../redux/application/application.selectors";
|
import { selectJobReadOnly } from "../../redux/application/application.selectors";
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
import FormDatePicker from "../form-date-picker/form-date-picker.component";
|
|
||||||
import CurrencyInput from "../form-items-formatted/currency-form-item.component";
|
import CurrencyInput from "../form-items-formatted/currency-form-item.component";
|
||||||
import FormItemEmail from "../form-items-formatted/email-form-item.component";
|
import FormItemEmail from "../form-items-formatted/email-form-item.component";
|
||||||
import FormItemPhone, { PhoneItemFormatterValidation } from "../form-items-formatted/phone-form-item.component";
|
import FormItemPhone, { PhoneItemFormatterValidation } from "../form-items-formatted/phone-form-item.component";
|
||||||
@@ -13,6 +12,7 @@ import Car from "../job-damage-visual/job-damage-visual.component";
|
|||||||
import JobsDetailChangeEstimator from "../jobs-detail-change-estimator/jobs-detail-change-estimator.component";
|
import JobsDetailChangeEstimator from "../jobs-detail-change-estimator/jobs-detail-change-estimator.component";
|
||||||
import JobsDetailChangeFileHandler from "../jobs-detail-change-filehandler/jobs-detail-change-filehandler.component";
|
import JobsDetailChangeFileHandler from "../jobs-detail-change-filehandler/jobs-detail-change-filehandler.component";
|
||||||
import FormRow from "../layout-form-row/layout-form-row.component";
|
import FormRow from "../layout-form-row/layout-form-row.component";
|
||||||
|
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component.jsx";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
jobRO: selectJobReadOnly,
|
jobRO: selectJobReadOnly,
|
||||||
@@ -152,7 +152,7 @@ export function JobsDetailGeneral({ bodyshop, jobRO, job, form }) {
|
|||||||
<Input disabled={jobRO} />
|
<Input disabled={jobRO} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label={t("jobs.fields.loss_date")} name="loss_date">
|
<Form.Item label={t("jobs.fields.loss_date")} name="loss_date">
|
||||||
<FormDatePicker disabled={jobRO} />
|
<DateTimePicker isDateOnly disabled={jobRO} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label={t("jobs.fields.loss_of_use")} name="loss_of_use">
|
<Form.Item label={t("jobs.fields.loss_of_use")} name="loss_of_use">
|
||||||
<Input disabled={jobRO} />
|
<Input disabled={jobRO} />
|
||||||
|
|||||||
@@ -219,7 +219,7 @@ export function JobsExportAllButton({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button onClick={handleQbxml} loading={loading} disabled={disabled}>
|
<Button onClick={handleQbxml} loading={loading} disabled={disabled || jobIds?.length > 10}>
|
||||||
{t("jobs.actions.exportselected")}
|
{t("jobs.actions.exportselected")}
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import { createStructuredSelector } from "reselect";
|
|||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||||
import { pageLimit } from "../../utils/config";
|
import { pageLimit } from "../../utils/config";
|
||||||
|
import { alphaSort, statusSort } from "../../utils/sorters";
|
||||||
import useLocalStorage from "../../utils/useLocalStorage";
|
import useLocalStorage from "../../utils/useLocalStorage";
|
||||||
import StartChatButton from "../chat-open-button/chat-open-button.component";
|
import StartChatButton from "../chat-open-button/chat-open-button.component";
|
||||||
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
|
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
|
||||||
@@ -37,7 +38,10 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
|
|||||||
title: t("jobs.fields.ro_number"),
|
title: t("jobs.fields.ro_number"),
|
||||||
dataIndex: "ro_number",
|
dataIndex: "ro_number",
|
||||||
key: "ro_number",
|
key: "ro_number",
|
||||||
sorter: true, //(a, b) => alphaSort(a.ro_number, b.ro_number),
|
sorter: search?.search
|
||||||
|
? (a, b) =>
|
||||||
|
parseInt((a.ro_number || "0").replace(/\D/g, "")) - parseInt((b.ro_number || "0").replace(/\D/g, ""))
|
||||||
|
: true,
|
||||||
sortOrder: sortcolumn === "ro_number" && sortorder,
|
sortOrder: sortcolumn === "ro_number" && sortorder,
|
||||||
render: (text, record) => (
|
render: (text, record) => (
|
||||||
<Link to={"/manage/jobs/" + record.id}>{record.ro_number || t("general.labels.na")}</Link>
|
<Link to={"/manage/jobs/" + record.id}>{record.ro_number || t("general.labels.na")}</Link>
|
||||||
@@ -49,7 +53,6 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
|
|||||||
key: "ownr_ln",
|
key: "ownr_ln",
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
//sorter: true, // (a, b) => alphaSort(a.ownr_ln, b.ownr_ln),
|
//sorter: true, // (a, b) => alphaSort(a.ownr_ln, b.ownr_ln),
|
||||||
|
|
||||||
//sortOrder: sortcolumn === "ownr_ln" && sortorder,
|
//sortOrder: sortcolumn === "ownr_ln" && sortorder,
|
||||||
render: (text, record) => {
|
render: (text, record) => {
|
||||||
return record.ownerid ? (
|
return record.ownerid ? (
|
||||||
@@ -67,7 +70,6 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
|
|||||||
title: t("jobs.fields.ownr_ph1"),
|
title: t("jobs.fields.ownr_ph1"),
|
||||||
dataIndex: "ownr_ph1",
|
dataIndex: "ownr_ph1",
|
||||||
key: "ownr_ph1",
|
key: "ownr_ph1",
|
||||||
|
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
render: (text, record) => <StartChatButton phone={record.ownr_ph1} jobid={record.id} />
|
render: (text, record) => <StartChatButton phone={record.ownr_ph1} jobid={record.id} />
|
||||||
},
|
},
|
||||||
@@ -75,7 +77,6 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
|
|||||||
title: t("jobs.fields.ownr_ph2"),
|
title: t("jobs.fields.ownr_ph2"),
|
||||||
dataIndex: "ownr_ph2",
|
dataIndex: "ownr_ph2",
|
||||||
key: "ownr_ph2",
|
key: "ownr_ph2",
|
||||||
|
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
render: (text, record) => <StartChatButton phone={record.ownr_ph2} jobid={record.id} />
|
render: (text, record) => <StartChatButton phone={record.ownr_ph2} jobid={record.id} />
|
||||||
},
|
},
|
||||||
@@ -85,7 +86,7 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
|
|||||||
key: "status",
|
key: "status",
|
||||||
|
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
sorter: true, // (a, b) => alphaSort(a.status, b.status),
|
sorter: search?.search ? (a, b) => statusSort(a.status, b.status, bodyshop.md_ro_statuses.active_statuses) : true,
|
||||||
sortOrder: sortcolumn === "status" && sortorder,
|
sortOrder: sortcolumn === "status" && sortorder,
|
||||||
render: (text, record) => {
|
render: (text, record) => {
|
||||||
return record.status || t("general.labels.na");
|
return record.status || t("general.labels.na");
|
||||||
@@ -100,7 +101,6 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
|
|||||||
title: t("jobs.fields.vehicle"),
|
title: t("jobs.fields.vehicle"),
|
||||||
dataIndex: "vehicle",
|
dataIndex: "vehicle",
|
||||||
key: "vehicle",
|
key: "vehicle",
|
||||||
|
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
render: (text, record) => {
|
render: (text, record) => {
|
||||||
return record.vehicleid ? (
|
return record.vehicleid ? (
|
||||||
@@ -117,7 +117,7 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
|
|||||||
dataIndex: "plate_no",
|
dataIndex: "plate_no",
|
||||||
key: "plate_no",
|
key: "plate_no",
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
sorter: true, //(a, b) => alphaSort(a.plate_no, b.plate_no),
|
sorter: search?.search ? (a, b) => alphaSort(a.plate_no, b.plate_no) : true,
|
||||||
sortOrder: sortcolumn === "plate_no" && sortorder,
|
sortOrder: sortcolumn === "plate_no" && sortorder,
|
||||||
render: (text, record) => {
|
render: (text, record) => {
|
||||||
return record.plate_no ? record.plate_no : "";
|
return record.plate_no ? record.plate_no : "";
|
||||||
@@ -128,7 +128,7 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
|
|||||||
dataIndex: "clm_no",
|
dataIndex: "clm_no",
|
||||||
key: "clm_no",
|
key: "clm_no",
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
sorter: true, //(a, b) => alphaSort(a.clm_no, b.clm_no),
|
sorter: search?.search ? (a, b) => alphaSort(a.clm_no, b.clm_no) : true,
|
||||||
sortOrder: sortcolumn === "clm_no" && sortorder,
|
sortOrder: sortcolumn === "clm_no" && sortorder,
|
||||||
render: (text, record) => `${record.clm_no || ""}${record.po_number ? ` (PO: ${record.po_number})` : ""}`
|
render: (text, record) => `${record.clm_no || ""}${record.po_number ? ` (PO: ${record.po_number})` : ""}`
|
||||||
},
|
},
|
||||||
@@ -142,8 +142,7 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
|
|||||||
title: t("jobs.fields.clm_total"),
|
title: t("jobs.fields.clm_total"),
|
||||||
dataIndex: "clm_total",
|
dataIndex: "clm_total",
|
||||||
key: "clm_total",
|
key: "clm_total",
|
||||||
|
sorter: search?.search ? (a, b) => a.clm_total - b.clm_total : true,
|
||||||
sorter: true, //(a, b) => a.clm_total - b.clm_total,
|
|
||||||
sortOrder: sortcolumn === "clm_total" && sortorder,
|
sortOrder: sortcolumn === "clm_total" && sortorder,
|
||||||
render: (text, record) => {
|
render: (text, record) => {
|
||||||
return record.clm_total ? (
|
return record.clm_total ? (
|
||||||
@@ -157,7 +156,6 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
|
|||||||
title: t("jobs.fields.owner_owing"),
|
title: t("jobs.fields.owner_owing"),
|
||||||
dataIndex: "owner_owing",
|
dataIndex: "owner_owing",
|
||||||
key: "owner_owing",
|
key: "owner_owing",
|
||||||
|
|
||||||
render: (text, record) => <CurrencyFormatter>{record.owner_owing}</CurrencyFormatter>
|
render: (text, record) => <CurrencyFormatter>{record.owner_owing}</CurrencyFormatter>
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -16,18 +16,15 @@ import useLocalStorage from "../../utils/useLocalStorage";
|
|||||||
import AlertComponent from "../alert/alert.component";
|
import AlertComponent from "../alert/alert.component";
|
||||||
import ChatOpenButton from "../chat-open-button/chat-open-button.component";
|
import ChatOpenButton from "../chat-open-button/chat-open-button.component";
|
||||||
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
|
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
|
||||||
import { setJoyRideSteps } from "../../redux/application/application.actions";
|
|
||||||
import { OwnerNameDisplayFunction } from "./../owner-name-display/owner-name-display.component";
|
import { OwnerNameDisplayFunction } from "./../owner-name-display/owner-name-display.component";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop
|
bodyshop: selectBodyshop
|
||||||
});
|
});
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch) => ({});
|
||||||
setJoyRideSteps: (steps) => dispatch(setJoyRideSteps(steps))
|
|
||||||
});
|
|
||||||
|
|
||||||
export function JobsList({ bodyshop, setJoyRideSteps }) {
|
export function JobsList({ bodyshop }) {
|
||||||
const searchParams = queryString.parse(useLocation().search);
|
const searchParams = queryString.parse(useLocation().search);
|
||||||
const { selected } = searchParams;
|
const { selected } = searchParams;
|
||||||
const selectedBreakpoint = Object.entries(Grid.useBreakpoint())
|
const selectedBreakpoint = Object.entries(Grid.useBreakpoint())
|
||||||
@@ -253,8 +250,8 @@ export function JobsList({ bodyshop, setJoyRideSteps }) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t("jobs.labels.estimator"),
|
title: t("jobs.labels.estimator"),
|
||||||
dataIndex: "jobs.labels.estimator",
|
dataIndex: "estimator",
|
||||||
key: "jobs.labels.estimator",
|
key: "estimator",
|
||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
responsive: ["xl"],
|
responsive: ["xl"],
|
||||||
sorter: (a, b) =>
|
sorter: (a, b) =>
|
||||||
|
|||||||
@@ -0,0 +1,105 @@
|
|||||||
|
import { useMutation } from "@apollo/client";
|
||||||
|
import { Button, notification, Popconfirm } from "antd";
|
||||||
|
import React, { useState } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
import { createStructuredSelector } from "reselect";
|
||||||
|
import { INSERT_EXPORT_LOG } from "../../graphql/accounting.queries";
|
||||||
|
import { UPDATE_JOBS } from "../../graphql/jobs.queries";
|
||||||
|
import { insertAuditTrail } from "../../redux/application/application.actions";
|
||||||
|
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors";
|
||||||
|
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
||||||
|
|
||||||
|
const mapStateToProps = createStructuredSelector({
|
||||||
|
bodyshop: selectBodyshop,
|
||||||
|
currentUser: selectCurrentUser
|
||||||
|
});
|
||||||
|
|
||||||
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
|
insertAuditTrail: ({ jobid, operation, type }) => dispatch(insertAuditTrail({ jobid, operation, type }))
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(mapStateToProps, mapDispatchToProps)(JobMarkSelectedExported);
|
||||||
|
|
||||||
|
export function JobMarkSelectedExported({
|
||||||
|
bodyshop,
|
||||||
|
currentUser,
|
||||||
|
jobIds,
|
||||||
|
disabled,
|
||||||
|
loadingCallback,
|
||||||
|
completedCallback,
|
||||||
|
refetch,
|
||||||
|
insertAuditTrail
|
||||||
|
}) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
const [insertExportLog] = useMutation(INSERT_EXPORT_LOG);
|
||||||
|
|
||||||
|
const [updateJob] = useMutation(UPDATE_JOBS);
|
||||||
|
const handleUpdate = async () => {
|
||||||
|
setLoading(true);
|
||||||
|
loadingCallback(true);
|
||||||
|
const result = await updateJob({
|
||||||
|
variables: {
|
||||||
|
jobIds: jobIds,
|
||||||
|
fields: {
|
||||||
|
status: bodyshop.md_ro_statuses.default_exported || "Exported*",
|
||||||
|
date_exported: new Date()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
update(cache) {}
|
||||||
|
});
|
||||||
|
|
||||||
|
await insertExportLog({
|
||||||
|
variables: {
|
||||||
|
logs: jobIds.map((id) => {
|
||||||
|
return {
|
||||||
|
bodyshopid: bodyshop.id,
|
||||||
|
jobid: id,
|
||||||
|
successful: true,
|
||||||
|
message: JSON.stringify([t("general.labels.markedexported")]),
|
||||||
|
useremail: currentUser.email
|
||||||
|
};
|
||||||
|
})
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!result.errors) {
|
||||||
|
notification["success"]({ message: t("jobs.successes.save") });
|
||||||
|
result.data.update_jobs.returning.forEach((job) => {
|
||||||
|
console.log("results job", job.id, "audit: ", AuditTrailMapping.admin_jobmarkexported());
|
||||||
|
insertAuditTrail({
|
||||||
|
jobid: job.id,
|
||||||
|
operation: AuditTrailMapping.admin_jobmarkexported(),
|
||||||
|
type: "admin_jobmarkexported"
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
notification["error"]({
|
||||||
|
message: t("jobs.errors.saving", {
|
||||||
|
error: JSON.stringify(result.errors)
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
loadingCallback(false);
|
||||||
|
completedCallback && completedCallback([]);
|
||||||
|
setLoading(false);
|
||||||
|
refetch && refetch();
|
||||||
|
setOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Popconfirm
|
||||||
|
open={open}
|
||||||
|
title={t("general.labels.areyousure")}
|
||||||
|
onCancel={() => setOpen(false)}
|
||||||
|
onConfirm={handleUpdate}
|
||||||
|
disabled={disabled}
|
||||||
|
>
|
||||||
|
<Button loading={loading} disabled={disabled} onClick={() => setOpen(true)} type="primary" danger>
|
||||||
|
{t("jobs.actions.markasexported")}
|
||||||
|
</Button>
|
||||||
|
</Popconfirm>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -23,7 +23,7 @@ export function PartnerPingComponent({ bodyshop }) {
|
|||||||
// Execute the created function directly
|
// Execute the created function directly
|
||||||
checkPartnerStatus(bodyshop);
|
checkPartnerStatus(bodyshop);
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [bodyshop]);
|
}, [bodyshop?.id]);
|
||||||
|
|
||||||
return <></>;
|
return <></>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ import { logImEXEvent } from "../../firebase/firebase.utils";
|
|||||||
import { MUTATION_UPDATE_BO_ETA } from "../../graphql/parts-orders.queries";
|
import { MUTATION_UPDATE_BO_ETA } from "../../graphql/parts-orders.queries";
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
import { DateFormatter } from "../../utils/DateFormatter";
|
import { DateFormatter } from "../../utils/DateFormatter";
|
||||||
import FormDatePicker from "../form-date-picker/form-date-picker.component";
|
|
||||||
import { CalendarFilled } from "@ant-design/icons";
|
import { CalendarFilled } from "@ant-design/icons";
|
||||||
|
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component.jsx";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop
|
bodyshop: selectBodyshop
|
||||||
@@ -62,7 +62,7 @@ export function PartsOrderBackorderEta({
|
|||||||
<div>
|
<div>
|
||||||
<Form form={form} onFinish={handleFinish}>
|
<Form form={form} onFinish={handleFinish}>
|
||||||
<Form.Item name="eta">
|
<Form.Item name="eta">
|
||||||
<FormDatePicker />
|
<DateTimePicker isDateOnly />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Button type="primary" onClick={() => form.submit()}>
|
<Button type="primary" onClick={() => form.submit()}>
|
||||||
{t("general.actions.save")}
|
{t("general.actions.save")}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { createStructuredSelector } from "reselect";
|
|||||||
import { logImEXEvent } from "../../firebase/firebase.utils";
|
import { logImEXEvent } from "../../firebase/firebase.utils";
|
||||||
import { MUTATION_BACKORDER_PART_LINE } from "../../graphql/parts-orders.queries";
|
import { MUTATION_BACKORDER_PART_LINE } from "../../graphql/parts-orders.queries";
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
import FormDatePicker from "../form-date-picker/form-date-picker.component";
|
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component.jsx";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop
|
bodyshop: selectBodyshop
|
||||||
@@ -71,7 +71,7 @@ export function PartsOrderLineBackorderButton({ partsOrderStatus, partsLineId, j
|
|||||||
<div>
|
<div>
|
||||||
<Form form={form} onFinish={handleFinish}>
|
<Form form={form} onFinish={handleFinish}>
|
||||||
<Form.Item name="eta">
|
<Form.Item name="eta">
|
||||||
<FormDatePicker />
|
<DateTimePicker isDateOnly />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Button type="primary" onClick={() => form.submit()}>
|
<Button type="primary" onClick={() => form.submit()}>
|
||||||
{t("parts_orders.actions.backordered")}
|
{t("parts_orders.actions.backordered")}
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import { DeleteFilled, EyeFilled } from "@ant-design/icons";
|
import { DeleteFilled } from "@ant-design/icons";
|
||||||
import { PageHeader } from "@ant-design/pro-layout";
|
import { PageHeader } from "@ant-design/pro-layout";
|
||||||
import { useLazyQuery, useMutation } from "@apollo/client";
|
import { useLazyQuery, useMutation } from "@apollo/client";
|
||||||
import { Button, Drawer, Grid, Popconfirm, Space, Table } from "antd";
|
import { Button, Drawer, Grid, Popconfirm, Space, Table } from "antd";
|
||||||
|
|
||||||
import queryString from "query-string";
|
import queryString from "query-string";
|
||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
@@ -83,47 +82,34 @@ export function PartsOrderListTableDrawerComponent({
|
|||||||
sortedInfo: {}
|
sortedInfo: {}
|
||||||
});
|
});
|
||||||
|
|
||||||
const [returnfrombill, setReturnFromBill] = useState();
|
const [billData, setBillData] = useState(null);
|
||||||
const [billData, setBillData] = useState();
|
|
||||||
const search = queryString.parse(useLocation().search);
|
const search = queryString.parse(useLocation().search);
|
||||||
const selectedpartsorder = search.partsorderid;
|
const selectedpartsorder = search.partsorderid;
|
||||||
|
|
||||||
const [billQuery] = useLazyQuery(QUERY_BILL_BY_PK);
|
|
||||||
const [deletePartsOrder] = useMutation(DELETE_PARTS_ORDER);
|
const [deletePartsOrder] = useMutation(DELETE_PARTS_ORDER);
|
||||||
const parts_orders = billsQuery.data ? billsQuery.data.parts_orders : [];
|
const parts_orders = billsQuery.data ? billsQuery.data.parts_orders : [];
|
||||||
const { refetch } = billsQuery;
|
const { refetch } = billsQuery;
|
||||||
|
const [billQuery] = useLazyQuery(QUERY_BILL_BY_PK);
|
||||||
|
const selectedPartsOrderRecord = parts_orders.find((r) => r.id === selectedpartsorder);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (returnfrombill === null) {
|
const fetchData = async () => {
|
||||||
setBillData(null);
|
if (selectedPartsOrderRecord?.returnfrombill) {
|
||||||
} else {
|
try {
|
||||||
const fetchData = async () => {
|
const { data } = await billQuery({
|
||||||
const result = await billQuery({
|
variables: { billid: selectedPartsOrderRecord.returnfrombill }
|
||||||
variables: { billid: returnfrombill }
|
});
|
||||||
});
|
setBillData(data);
|
||||||
setBillData(result.data);
|
} catch (error) {
|
||||||
};
|
console.error("Error fetching bill data:", error);
|
||||||
fetchData();
|
}
|
||||||
}
|
} else setBillData(null);
|
||||||
}, [returnfrombill, billQuery]);
|
};
|
||||||
|
fetchData();
|
||||||
|
}, [selectedPartsOrderRecord, billQuery]);
|
||||||
|
|
||||||
const recordActions = (record, showView = false) => (
|
const recordActions = (record) => (
|
||||||
<Space direction="horizontal" wrap>
|
<Space direction="horizontal" wrap>
|
||||||
{showView && (
|
|
||||||
<Button
|
|
||||||
onClick={() => {
|
|
||||||
if (record.returnfrombill) {
|
|
||||||
setReturnFromBill(record.returnfrombill);
|
|
||||||
} else {
|
|
||||||
setReturnFromBill(null);
|
|
||||||
}
|
|
||||||
handleOnRowClick(record);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<EyeFilled />
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
disabled={jobRO || record.return || record.vendor.id === bodyshop.inhousevendorid}
|
disabled={jobRO || record.return || record.vendor.id === bodyshop.inhousevendorid}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@@ -133,16 +119,14 @@ export function PartsOrderListTableDrawerComponent({
|
|||||||
context: {
|
context: {
|
||||||
jobId: job.id,
|
jobId: job.id,
|
||||||
job: job,
|
job: job,
|
||||||
partsorderlines: record.parts_order_lines.map((pol) => {
|
partsorderlines: record.parts_order_lines.map((pol) => ({
|
||||||
return {
|
joblineid: pol.job_line_id,
|
||||||
joblineid: pol.job_line_id,
|
id: pol.id,
|
||||||
id: pol.id,
|
line_desc: pol.line_desc,
|
||||||
line_desc: pol.line_desc,
|
quantity: pol.quantity,
|
||||||
quantity: pol.quantity,
|
act_price: pol.act_price,
|
||||||
act_price: pol.act_price,
|
oem_partno: pol.oem_partno
|
||||||
oem_partno: pol.oem_partno
|
}))
|
||||||
};
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
@@ -167,7 +151,6 @@ export function PartsOrderListTableDrawerComponent({
|
|||||||
disabled={jobRO}
|
disabled={jobRO}
|
||||||
onConfirm={async () => {
|
onConfirm={async () => {
|
||||||
//Delete the parts return.!
|
//Delete the parts return.!
|
||||||
|
|
||||||
await deletePartsOrder({
|
await deletePartsOrder({
|
||||||
variables: { partsOrderId: record.id },
|
variables: { partsOrderId: record.id },
|
||||||
update(cache) {
|
update(cache) {
|
||||||
@@ -191,7 +174,6 @@ export function PartsOrderListTableDrawerComponent({
|
|||||||
disabled={(jobRO ? !record.return : jobRO) || record.vendor.id === bodyshop.inhousevendorid}
|
disabled={(jobRO ? !record.return : jobRO) || record.vendor.id === bodyshop.inhousevendorid}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
logImEXEvent("parts_order_receive_bill");
|
logImEXEvent("parts_order_receive_bill");
|
||||||
|
|
||||||
setBillEnterContext({
|
setBillEnterContext({
|
||||||
actions: { refetch: refetch },
|
actions: { refetch: refetch },
|
||||||
context: {
|
context: {
|
||||||
@@ -199,24 +181,20 @@ export function PartsOrderListTableDrawerComponent({
|
|||||||
bill: {
|
bill: {
|
||||||
vendorid: record.vendor.id,
|
vendorid: record.vendor.id,
|
||||||
is_credit_memo: record.return,
|
is_credit_memo: record.return,
|
||||||
billlines: record.parts_order_lines.map((pol) => {
|
billlines: record.parts_order_lines.map((pol) => ({
|
||||||
return {
|
joblineid: pol.job_line_id || "noline",
|
||||||
joblineid: pol.job_line_id || "noline",
|
line_desc: pol.line_desc,
|
||||||
line_desc: pol.line_desc,
|
quantity: pol.quantity,
|
||||||
quantity: pol.quantity,
|
actual_price: pol.act_price,
|
||||||
|
cost_center: pol.jobline?.part_type
|
||||||
actual_price: pol.act_price,
|
? bodyshop.pbs_serialnumber || bodyshop.cdk_dealerid
|
||||||
|
? pol.jobline.part_type !== "PAE"
|
||||||
cost_center: pol.jobline?.part_type
|
? pol.jobline.part_type
|
||||||
? bodyshop.pbs_serialnumber || bodyshop.cdk_dealerid
|
: null
|
||||||
? pol.jobline.part_type !== "PAE"
|
: responsibilityCenters.defaults &&
|
||||||
? pol.jobline.part_type
|
(responsibilityCenters.defaults.costs[pol.jobline.part_type] || null)
|
||||||
: null
|
: null
|
||||||
: responsibilityCenters.defaults &&
|
}))
|
||||||
(responsibilityCenters.defaults.costs[pol.jobline.part_type] || null)
|
|
||||||
: null
|
|
||||||
};
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -243,8 +221,6 @@ export function PartsOrderListTableDrawerComponent({
|
|||||||
setState({ ...state, filteredInfo: filters, sortedInfo: sorter });
|
setState({ ...state, filteredInfo: filters, sortedInfo: sorter });
|
||||||
};
|
};
|
||||||
|
|
||||||
const selectedPartsOrderRecord = parts_orders.find((r) => r.id === selectedpartsorder);
|
|
||||||
|
|
||||||
const rowExpander = (record) => {
|
const rowExpander = (record) => {
|
||||||
const columns = [
|
const columns = [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -242,7 +242,8 @@ export function PartsOrderListTableComponent({
|
|||||||
title: t("general.labels.actions"),
|
title: t("general.labels.actions"),
|
||||||
dataIndex: "actions",
|
dataIndex: "actions",
|
||||||
key: "actions",
|
key: "actions",
|
||||||
render: (text, record) => recordActions(record, true)
|
render: (text, record) => recordActions(record, true),
|
||||||
|
id: "parts-order-list-table-actions"
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -27,6 +27,10 @@ export default function PartsOrderModalPriceChange({ form, field }) {
|
|||||||
key: "25",
|
key: "25",
|
||||||
label: t("parts_orders.labels.discount", { percent: "25%" })
|
label: t("parts_orders.labels.discount", { percent: "25%" })
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
key: "40",
|
||||||
|
label: t("parts_orders.labels.discount", { percent: "40%" })
|
||||||
|
},
|
||||||
{
|
{
|
||||||
key: "custom",
|
key: "custom",
|
||||||
label: (
|
label: (
|
||||||
|
|||||||
@@ -6,12 +6,12 @@ import { useTranslation } from "react-i18next";
|
|||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
import FormDatePicker from "../form-date-picker/form-date-picker.component";
|
|
||||||
import CurrencyInput from "../form-items-formatted/currency-form-item.component";
|
import CurrencyInput from "../form-items-formatted/currency-form-item.component";
|
||||||
import FormListMoveArrows from "../form-list-move-arrows/form-list-move-arrows.component";
|
import FormListMoveArrows from "../form-list-move-arrows/form-list-move-arrows.component";
|
||||||
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
||||||
import VendorSearchSelect from "../vendor-search-select/vendor-search-select.component";
|
import VendorSearchSelect from "../vendor-search-select/vendor-search-select.component";
|
||||||
import PartsOrderModalPriceChange from "./parts-order-modal-price-change.component";
|
import PartsOrderModalPriceChange from "./parts-order-modal-price-change.component";
|
||||||
|
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component.jsx";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop
|
bodyshop: selectBodyshop
|
||||||
@@ -74,7 +74,7 @@ export function PartsOrderModalComponent({ bodyshop, vendorList, sendTypeState,
|
|||||||
]}
|
]}
|
||||||
label={t("parts_orders.fields.deliver_by")}
|
label={t("parts_orders.fields.deliver_by")}
|
||||||
>
|
>
|
||||||
<FormDatePicker onlyFuture />
|
<DateTimePicker isDateOnly onlyFuture />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
{job && job.special_coverage_policy && (
|
{job && job.special_coverage_policy && (
|
||||||
<Tag color="tomato">
|
<Tag color="tomato">
|
||||||
|
|||||||
@@ -200,7 +200,7 @@ export function PayableExportAll({
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button onClick={handleQbxml} loading={loading} disabled={disabled}>
|
<Button onClick={handleQbxml} loading={loading} disabled={disabled || billids?.length > 10}>
|
||||||
{t("jobs.actions.exportselected")}
|
{t("jobs.actions.exportselected")}
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ export function BillMarkSelectedExported({
|
|||||||
onConfirm={handleUpdate}
|
onConfirm={handleUpdate}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
>
|
>
|
||||||
<Button loading={loading} disabled={disabled} onClick={() => setOpen(true)}>
|
<Button loading={loading} disabled={disabled} onClick={() => setOpen(true)} type="primary" danger>
|
||||||
{t("bills.labels.markexported")}
|
{t("bills.labels.markexported")}
|
||||||
</Button>
|
</Button>
|
||||||
</Popconfirm>
|
</Popconfirm>
|
||||||
|
|||||||
@@ -5,11 +5,11 @@ import { useTranslation } from "react-i18next";
|
|||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
import DatePickerFormItem from "../form-date-picker/form-date-picker.component";
|
|
||||||
import CurrencyInput from "../form-items-formatted/currency-form-item.component";
|
import CurrencyInput from "../form-items-formatted/currency-form-item.component";
|
||||||
import JobSearchSelect from "../job-search-select/job-search-select.component";
|
import JobSearchSelect from "../job-search-select/job-search-select.component";
|
||||||
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
||||||
import PaymentFormTotalPayments from "./payment-form.totalpayments.component";
|
import PaymentFormTotalPayments from "./payment-form.totalpayments.component";
|
||||||
|
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component.jsx";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop
|
bodyshop: selectBodyshop
|
||||||
@@ -77,7 +77,7 @@ export function PaymentFormComponent({ form, bodyshop, disabled }) {
|
|||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<DatePickerFormItem disabled={disabled} />
|
<DateTimePicker isDateOnly disabled={disabled} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</LayoutFormRow>
|
</LayoutFormRow>
|
||||||
|
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ export function PaymentMarkSelectedExported({
|
|||||||
onConfirm={handleUpdate}
|
onConfirm={handleUpdate}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
>
|
>
|
||||||
<Button loading={loading} disabled={disabled} onClick={() => setOpen(true)}>
|
<Button loading={loading} disabled={disabled} onClick={() => setOpen(true)} type="primary" danger>
|
||||||
{t("bills.labels.markexported")}
|
{t("bills.labels.markexported")}
|
||||||
</Button>
|
</Button>
|
||||||
</Popconfirm>
|
</Popconfirm>
|
||||||
|
|||||||
@@ -180,7 +180,7 @@ export function PaymentsExportAllButton({
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button onClick={handleQbxml} loading={loading} disabled={disabled}>
|
<Button onClick={handleQbxml} loading={loading} disabled={disabled || paymentIds?.length > 10}>
|
||||||
{t("jobs.actions.exportselected")}
|
{t("jobs.actions.exportselected")}
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -8,11 +8,12 @@ import { useTranslation } from "react-i18next";
|
|||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import { openChatByPhone, setMessage } from "../../redux/messaging/messaging.actions";
|
import { openChatByPhone, setMessage } from "../../redux/messaging/messaging.actions";
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors";
|
||||||
import CurrencyFormItemComponent from "../form-items-formatted/currency-form-item.component";
|
import CurrencyFormItemComponent from "../form-items-formatted/currency-form-item.component";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop
|
bodyshop: selectBodyshop,
|
||||||
|
currentUser: selectCurrentUser
|
||||||
});
|
});
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
openChatByPhone: (phone) => dispatch(openChatByPhone(phone)),
|
openChatByPhone: (phone) => dispatch(openChatByPhone(phone)),
|
||||||
@@ -20,7 +21,7 @@ const mapDispatchToProps = (dispatch) => ({
|
|||||||
});
|
});
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(PaymentsGenerateLink);
|
export default connect(mapStateToProps, mapDispatchToProps)(PaymentsGenerateLink);
|
||||||
|
|
||||||
export function PaymentsGenerateLink({ bodyshop, callback, job, openChatByPhone, setMessage }) {
|
export function PaymentsGenerateLink({ bodyshop, currentUser, callback, job, openChatByPhone, setMessage }) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
|
|
||||||
@@ -30,29 +31,35 @@ export function PaymentsGenerateLink({ bodyshop, callback, job, openChatByPhone,
|
|||||||
|
|
||||||
const handleFinish = async ({ amount }) => {
|
const handleFinish = async ({ amount }) => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
|
let p;
|
||||||
const p = parsePhoneNumber(job.ownr_ph1, "CA");
|
try {
|
||||||
|
p = parsePhoneNumber(job.ownr_ph1 || "", "CA");
|
||||||
|
} catch (error) {
|
||||||
|
console.log("Unable to parse phone number");
|
||||||
|
}
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const response = await axios.post("/intellipay/generate_payment_url", {
|
const response = await axios.post("/intellipay/generate_payment_url", {
|
||||||
bodyshop,
|
bodyshop,
|
||||||
amount: amount,
|
amount: amount,
|
||||||
account: job.ro_number,
|
account: job.ro_number,
|
||||||
invoice: job.id
|
comment: btoa(JSON.stringify({ payments: [{ jobid: job.id, amount }], userEmail: currentUser.email }))
|
||||||
});
|
});
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
setPaymentLink(response.data.shorUrl);
|
setPaymentLink(response.data.shorUrl);
|
||||||
|
|
||||||
openChatByPhone({
|
if (p) {
|
||||||
phone_num: p.formatInternational(),
|
openChatByPhone({
|
||||||
jobid: job.id
|
phone_num: p.formatInternational(),
|
||||||
});
|
jobid: job.id
|
||||||
setMessage(
|
});
|
||||||
t("payments.labels.smspaymentreminder", {
|
setMessage(
|
||||||
shopname: bodyshop.shopname,
|
t("payments.labels.smspaymentreminder", {
|
||||||
amount: amount,
|
shopname: bodyshop.shopname,
|
||||||
payment_link: response.data.shorUrl
|
amount: amount,
|
||||||
})
|
payment_link: response.data.shorUrl
|
||||||
);
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
//Add in confirmation & errors.
|
//Add in confirmation & errors.
|
||||||
if (callback) callback();
|
if (callback) callback();
|
||||||
|
|||||||
@@ -1,22 +1,32 @@
|
|||||||
import { Input, Space, Spin } from "antd";
|
import { Button, Input, Space, Spin } from "antd";
|
||||||
import React from "react";
|
import React, { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
|
import { ExclamationCircleFilled, ExclamationCircleOutlined } from "@ant-design/icons";
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
import EmployeeSearchSelectComponent from "../employee-search-select/employee-search-select.component";
|
import EmployeeSearchSelectComponent from "../employee-search-select/employee-search-select.component";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
//currentUser: selectCurrentUser
|
|
||||||
bodyshop: selectBodyshop
|
bodyshop: selectBodyshop
|
||||||
});
|
});
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(ProductionBoardFilters);
|
export default connect(mapStateToProps, mapDispatchToProps)(ProductionBoardFilters);
|
||||||
|
|
||||||
export function ProductionBoardFilters({ bodyshop, filter, setFilter, loading }) {
|
export function ProductionBoardFilters({ bodyshop, filter, setFilter, loading }) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const [alertFilter, setAlertFilter] = useState(false);
|
||||||
|
|
||||||
|
const toggleAlertFilter = () => {
|
||||||
|
const newAlertFilter = !alertFilter;
|
||||||
|
setAlertFilter(newAlertFilter);
|
||||||
|
setFilter({ ...filter, alert: newAlertFilter });
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Space wrap>
|
<Space wrap>
|
||||||
{loading && <Spin />}
|
{loading && <Spin />}
|
||||||
@@ -35,6 +45,13 @@ export function ProductionBoardFilters({ bodyshop, filter, setFilter, loading })
|
|||||||
onChange={(emp) => setFilter({ ...filter, employeeId: emp })}
|
onChange={(emp) => setFilter({ ...filter, employeeId: emp })}
|
||||||
allowClear
|
allowClear
|
||||||
/>
|
/>
|
||||||
|
<Button
|
||||||
|
type={alertFilter ? "primary" : "default"}
|
||||||
|
onClick={toggleAlertFilter}
|
||||||
|
icon={alertFilter ? <ExclamationCircleFilled /> : <ExclamationCircleOutlined />}
|
||||||
|
>
|
||||||
|
{t("production.labels.alerts")}
|
||||||
|
</Button>
|
||||||
</Space>
|
</Space>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,11 +6,11 @@ import {
|
|||||||
PauseCircleOutlined
|
PauseCircleOutlined
|
||||||
} from "@ant-design/icons";
|
} from "@ant-design/icons";
|
||||||
import { Card, Col, Row, Space, Tooltip } from "antd";
|
import { Card, Col, Row, Space, Tooltip } from "antd";
|
||||||
|
import Dinero from "dinero.js";
|
||||||
import React, { useMemo } from "react";
|
import React, { useMemo } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { DateTimeFormatter } from "../../utils/DateFormatter";
|
import { DateTimeFormatter } from "../../utils/DateFormatter";
|
||||||
import Dinero from "dinero.js";
|
|
||||||
|
|
||||||
import ProductionAlert from "../production-list-columns/production-list-columns.alert.component";
|
import ProductionAlert from "../production-list-columns/production-list-columns.alert.component";
|
||||||
import ProductionListColumnProductionNote from "../production-list-columns/production-list-columns.productionnote.component";
|
import ProductionListColumnProductionNote from "../production-list-columns/production-list-columns.productionnote.component";
|
||||||
@@ -18,8 +18,8 @@ import ProductionSubletsManageComponent from "../production-sublets-manage/produ
|
|||||||
|
|
||||||
import dayjs from "../../utils/day";
|
import dayjs from "../../utils/day";
|
||||||
|
|
||||||
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
|
|
||||||
import JobPartsQueueCount from "../job-parts-queue-count/job-parts-queue-count.component";
|
import JobPartsQueueCount from "../job-parts-queue-count/job-parts-queue-count.component";
|
||||||
|
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
|
||||||
|
|
||||||
const cardColor = (ssbuckets, totalHrs) => {
|
const cardColor = (ssbuckets, totalHrs) => {
|
||||||
const bucket = ssbuckets.find((bucket) => bucket.gte <= totalHrs && (!bucket.lt || bucket.lt > totalHrs));
|
const bucket = ssbuckets.find((bucket) => bucket.gte <= totalHrs && (!bucket.lt || bucket.lt > totalHrs));
|
||||||
@@ -213,21 +213,13 @@ const EstimatorToolTip = ({ metadata, cardSettings }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const SubtotalTooltip = ({ metadata, cardSettings, t }) => {
|
const SubtotalTooltip = ({ metadata, cardSettings, t }) => {
|
||||||
const amount = metadata?.job_totals?.totals?.subtotal?.amount;
|
const dineroAmount = Dinero(metadata?.job_totals?.totals?.subtotal ?? Dinero()).toFormat();
|
||||||
const dineroAmount = amount ? Dinero({ amount: parseInt(amount * 100) }).toFormat("0,0.00") : null;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
cardSettings?.subtotal && (
|
cardSettings?.subtotal && (
|
||||||
<Col span={cardSettings.compact ? 24 : 12}>
|
<Col span={cardSettings.compact ? 24 : 12}>
|
||||||
<EllipsesToolTip
|
<EllipsesToolTip title={`${dineroAmount}`} kiosk={cardSettings.kiosk}>
|
||||||
title={!!amount ? `${t("production.statistics.currency_symbol")}${dineroAmount}` : null}
|
{dineroAmount}
|
||||||
kiosk={cardSettings.kiosk}
|
|
||||||
>
|
|
||||||
{!!amount ? (
|
|
||||||
<span>{`${t("production.statistics.currency_symbol")}${dineroAmount}`}</span>
|
|
||||||
) : (
|
|
||||||
<span> </span>
|
|
||||||
)}
|
|
||||||
</EllipsesToolTip>
|
</EllipsesToolTip>
|
||||||
</Col>
|
</Col>
|
||||||
)
|
)
|
||||||
@@ -290,6 +282,22 @@ const PartsStatusComponent = ({ metadata, cardSettings }) =>
|
|||||||
</Col>
|
</Col>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const TasksToolTip = ({ metadata, cardSettings, t }) =>
|
||||||
|
cardSettings?.tasks && (
|
||||||
|
<Col span={12}>
|
||||||
|
<EllipsesToolTip
|
||||||
|
title={`${t("production.labels.tasks")}: ${metadata.tasks_aggregate?.aggregate?.count || 0}`}
|
||||||
|
kiosk={cardSettings.kiosk}
|
||||||
|
>
|
||||||
|
{metadata.tasks_aggregate?.aggregate?.count ? (
|
||||||
|
`T: ${metadata.tasks_aggregate.aggregate.count}`
|
||||||
|
) : (
|
||||||
|
<span>T: 0</span>
|
||||||
|
)}
|
||||||
|
</EllipsesToolTip>
|
||||||
|
</Col>
|
||||||
|
);
|
||||||
|
|
||||||
export default function ProductionBoardCard({ technician, card, bodyshop, cardSettings, clone }) {
|
export default function ProductionBoardCard({ technician, card, bodyshop, cardSettings, clone }) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { metadata } = card;
|
const { metadata } = card;
|
||||||
@@ -336,21 +344,15 @@ export default function ProductionBoardCard({ technician, card, bodyshop, cardSe
|
|||||||
cardSettings?.production_note ||
|
cardSettings?.production_note ||
|
||||||
cardSettings?.partsstatus ||
|
cardSettings?.partsstatus ||
|
||||||
cardSettings?.estimator ||
|
cardSettings?.estimator ||
|
||||||
cardSettings?.subtotal
|
cardSettings?.subtotal ||
|
||||||
|
cardSettings?.tasks
|
||||||
);
|
);
|
||||||
}, [cardSettings]);
|
}, [cardSettings]);
|
||||||
|
|
||||||
const headerContent = (
|
const headerContent = (
|
||||||
<div className="header-content-container">
|
<div className="header-content-container">
|
||||||
<div className="inner-container">
|
<div className="inner-container">
|
||||||
<ProductionAlert
|
<ProductionAlert id={card.id} productionVars={metadata?.production_vars} refetch={card?.refetch} key="alert" />
|
||||||
record={{
|
|
||||||
id: card.id,
|
|
||||||
production_vars: card?.metadata.production_vars,
|
|
||||||
refetch: card?.refetch
|
|
||||||
}}
|
|
||||||
key="alert"
|
|
||||||
/>
|
|
||||||
{metadata?.suspended && <PauseCircleOutlined className="circle-outline" key="suspended" />}
|
{metadata?.suspended && <PauseCircleOutlined className="circle-outline" key="suspended" />}
|
||||||
{metadata?.iouparent && (
|
{metadata?.iouparent && (
|
||||||
<EllipsesToolTip
|
<EllipsesToolTip
|
||||||
@@ -393,6 +395,7 @@ export default function ProductionBoardCard({ technician, card, bodyshop, cardSe
|
|||||||
employee_csr={employee_csr}
|
employee_csr={employee_csr}
|
||||||
/>
|
/>
|
||||||
<EstimatorToolTip metadata={metadata} cardSettings={cardSettings} />
|
<EstimatorToolTip metadata={metadata} cardSettings={cardSettings} />
|
||||||
|
<TasksToolTip metadata={metadata} cardSettings={cardSettings} t={t} />
|
||||||
<SubtotalTooltip metadata={metadata} cardSettings={cardSettings} t={t} />
|
<SubtotalTooltip metadata={metadata} cardSettings={cardSettings} t={t} />
|
||||||
<ActualInToolTip metadata={metadata} cardSettings={cardSettings} />
|
<ActualInToolTip metadata={metadata} cardSettings={cardSettings} />
|
||||||
<ScheduledCompletionToolTip metadata={metadata} cardSettings={cardSettings} pastDueAlert={pastDueAlert} />
|
<ScheduledCompletionToolTip metadata={metadata} cardSettings={cardSettings} pastDueAlert={pastDueAlert} />
|
||||||
@@ -15,13 +15,13 @@ import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
|||||||
import IndefiniteLoading from "../indefinite-loading/indefinite-loading.component";
|
import IndefiniteLoading from "../indefinite-loading/indefinite-loading.component";
|
||||||
import ProductionBoardFilters from "../production-board-filters/production-board-filters.component";
|
import ProductionBoardFilters from "../production-board-filters/production-board-filters.component";
|
||||||
import ProductionListDetailComponent from "../production-list-detail/production-list-detail.component";
|
import ProductionListDetailComponent from "../production-list-detail/production-list-detail.component";
|
||||||
import CardColorLegend from "../production-board-kanban-card/production-board-kanban-card-color-legend.component";
|
import CardColorLegend from "./production-board-kanban-card-color-legend.component.jsx";
|
||||||
import "./production-board-kanban.styles.scss";
|
import "./production-board-kanban.styles.scss";
|
||||||
import { createBoardData } from "./production-board-kanban.utils.js";
|
import { createBoardData } from "./production-board-kanban.utils.js";
|
||||||
import ProductionBoardKanbanSettings from "./settings/production-board-kanban.settings.component.jsx";
|
import ProductionBoardKanbanSettings from "./settings/production-board-kanban.settings.component.jsx";
|
||||||
import cloneDeep from "lodash/cloneDeep";
|
import cloneDeep from "lodash/cloneDeep";
|
||||||
import isEqual from "lodash/isEqual";
|
import isEqual from "lodash/isEqual";
|
||||||
import { defaultKanbanSettings } from "./settings/defaultKanbanSettings.js";
|
import { defaultFilters, mergeWithDefaults } from "./settings/defaultKanbanSettings.js";
|
||||||
import NoteUpsertModal from "../../components/note-upsert-modal/note-upsert-modal.container";
|
import NoteUpsertModal from "../../components/note-upsert-modal/note-upsert-modal.container";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
@@ -41,7 +41,7 @@ const mapDispatchToProps = (dispatch) => ({
|
|||||||
|
|
||||||
function ProductionBoardKanbanComponent({ data, bodyshop, refetch, insertAuditTrail, associationSettings, statuses }) {
|
function ProductionBoardKanbanComponent({ data, bodyshop, refetch, insertAuditTrail, associationSettings, statuses }) {
|
||||||
const [boardLanes, setBoardLanes] = useState({ lanes: [] });
|
const [boardLanes, setBoardLanes] = useState({ lanes: [] });
|
||||||
const [filter, setFilter] = useState({ search: "", employeeId: null });
|
const [filter, setFilter] = useState(defaultFilters);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [isMoving, setIsMoving] = useState(false);
|
const [isMoving, setIsMoving] = useState(false);
|
||||||
const [orientation, setOrientation] = useState("vertical");
|
const [orientation, setOrientation] = useState("vertical");
|
||||||
@@ -182,19 +182,14 @@ function ProductionBoardKanbanComponent({ data, bodyshop, refetch, insertAuditTr
|
|||||||
[boardLanes, client, getCardByID, isMoving, t, insertAuditTrail]
|
[boardLanes, client, getCardByID, isMoving, t, insertAuditTrail]
|
||||||
);
|
);
|
||||||
|
|
||||||
const cardSettings = useMemo(
|
const cardSettings = useMemo(() => {
|
||||||
() =>
|
const kanbanSettings = associationSettings?.kanban_settings;
|
||||||
associationSettings?.kanban_settings && Object.keys(associationSettings.kanban_settings).length > 0
|
return mergeWithDefaults(kanbanSettings);
|
||||||
? associationSettings.kanban_settings
|
}, [associationSettings?.kanban_settings]);
|
||||||
: defaultKanbanSettings,
|
|
||||||
[associationSettings]
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleSettingsChange = useCallback((newSettings) => {
|
const handleSettingsChange = () => {
|
||||||
setLoading(true);
|
setFilter(defaultFilters);
|
||||||
setOrientation(newSettings.orientation ? "vertical" : "horizontal");
|
};
|
||||||
setLoading(false);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return <Skeleton active />;
|
return <Skeleton active />;
|
||||||
|
|||||||
@@ -1,18 +1,40 @@
|
|||||||
import React, { useEffect, useMemo } from "react";
|
import React, { useContext, useEffect, useMemo, useRef } from "react";
|
||||||
import { useQuery, useSubscription } from "@apollo/client";
|
import { useApolloClient, useQuery, useSubscription } from "@apollo/client";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import { QUERY_JOBS_IN_PRODUCTION, SUBSCRIPTION_JOBS_IN_PRODUCTION } from "../../graphql/jobs.queries";
|
import {
|
||||||
|
QUERY_EXACT_JOB_IN_PRODUCTION,
|
||||||
|
QUERY_JOBS_IN_PRODUCTION,
|
||||||
|
SUBSCRIPTION_JOBS_IN_PRODUCTION,
|
||||||
|
SUBSCRIPTION_JOBS_IN_PRODUCTION_VIEW
|
||||||
|
} from "../../graphql/jobs.queries";
|
||||||
import { QUERY_KANBAN_SETTINGS } from "../../graphql/user.queries";
|
import { QUERY_KANBAN_SETTINGS } from "../../graphql/user.queries";
|
||||||
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors";
|
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors";
|
||||||
import ProductionBoardKanbanComponent from "./production-board-kanban.component";
|
import ProductionBoardKanbanComponent from "./production-board-kanban.component";
|
||||||
|
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
||||||
|
import SocketContext from "../../contexts/SocketIO/socketContext.jsx";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop,
|
bodyshop: selectBodyshop,
|
||||||
currentUser: selectCurrentUser
|
currentUser: selectCurrentUser
|
||||||
});
|
});
|
||||||
|
|
||||||
function ProductionBoardKanbanContainer({ bodyshop, currentUser }) {
|
function ProductionBoardKanbanContainer({ bodyshop, currentUser, subscriptionType = "direct" }) {
|
||||||
|
const fired = useRef(false);
|
||||||
|
const client = useApolloClient();
|
||||||
|
const { socket } = useContext(SocketContext); // Get the socket from context
|
||||||
|
const reconnectTimeout = useRef(null); // To store the reconnect timeout
|
||||||
|
const disconnectTime = useRef(null); // To track disconnection time
|
||||||
|
const acceptableReconnectTime = 2000; // 2 seconds threshold
|
||||||
|
|
||||||
|
const {
|
||||||
|
treatments: { Websocket_Production }
|
||||||
|
} = useSplitTreatments({
|
||||||
|
attributes: {},
|
||||||
|
names: ["Websocket_Production"],
|
||||||
|
splitKey: bodyshop && bodyshop.imexshopid
|
||||||
|
});
|
||||||
|
|
||||||
const combinedStatuses = useMemo(
|
const combinedStatuses = useMemo(
|
||||||
() => [
|
() => [
|
||||||
...bodyshop.md_ro_statuses.production_statuses,
|
...bodyshop.md_ro_statuses.production_statuses,
|
||||||
@@ -28,26 +50,127 @@ function ProductionBoardKanbanContainer({ bodyshop, currentUser }) {
|
|||||||
onError: (error) => console.error(`Error fetching jobs in production: ${error.message}`)
|
onError: (error) => console.error(`Error fetching jobs in production: ${error.message}`)
|
||||||
});
|
});
|
||||||
|
|
||||||
const { data: updatedJobs } = useSubscription(SUBSCRIPTION_JOBS_IN_PRODUCTION, {
|
const subscriptionEnabled = Websocket_Production?.treatment === "off";
|
||||||
onError: (error) => console.error(`Error subscribing to jobs in production: ${error.message}`)
|
|
||||||
});
|
const { data: updatedJobs } = useSubscription(
|
||||||
|
subscriptionType === "view" ? SUBSCRIPTION_JOBS_IN_PRODUCTION_VIEW : SUBSCRIPTION_JOBS_IN_PRODUCTION,
|
||||||
|
{
|
||||||
|
skip: !subscriptionEnabled,
|
||||||
|
onError: (error) => console.error(`Error subscribing to jobs in production: ${error.message}`)
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
const { loading: associationSettingsLoading, data: associationSettings } = useQuery(QUERY_KANBAN_SETTINGS, {
|
const { loading: associationSettingsLoading, data: associationSettings } = useQuery(QUERY_KANBAN_SETTINGS, {
|
||||||
variables: { email: currentUser.email },
|
variables: { email: currentUser.email },
|
||||||
onError: (error) => console.error(`Error fetching Kanban settings: ${error.message}`)
|
onError: (error) => console.error(`Error fetching Kanban settings: ${error.message}`)
|
||||||
});
|
});
|
||||||
|
|
||||||
// const currentReducerData = useSelector((state) => (state.trello.lanes ? state.trello : {}));
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (updatedJobs && data) {
|
if (subscriptionEnabled) {
|
||||||
|
if (!updatedJobs) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!fired.current) {
|
||||||
|
fired.current = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
refetch().catch((err) => console.error(`Error re-fetching jobs in production: ${err.message}`));
|
refetch().catch((err) => console.error(`Error re-fetching jobs in production: ${err.message}`));
|
||||||
}
|
}
|
||||||
}, [updatedJobs, data, refetch]);
|
}, [updatedJobs, refetch, subscriptionEnabled]);
|
||||||
|
|
||||||
|
// Socket.IO implementation for users with Split treatment "off"
|
||||||
|
useEffect(() => {
|
||||||
|
if (subscriptionEnabled || !socket || !bodyshop || !bodyshop.id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleJobUpdates = async (jobChangedData) => {
|
||||||
|
const jobId = jobChangedData.id;
|
||||||
|
|
||||||
|
// Access the existing cache for QUERY_JOBS_IN_PRODUCTION
|
||||||
|
const existingJobsCache = client.readQuery({
|
||||||
|
query: QUERY_JOBS_IN_PRODUCTION
|
||||||
|
});
|
||||||
|
|
||||||
|
const existingJobs = existingJobsCache?.jobs || [];
|
||||||
|
|
||||||
|
// Check if the job already exists in the cached jobs
|
||||||
|
const existingJob = existingJobs.find((job) => job.id === jobId);
|
||||||
|
|
||||||
|
if (existingJob) {
|
||||||
|
// If the job exists, we update the cache without making any additional queries
|
||||||
|
client.writeQuery({
|
||||||
|
query: QUERY_JOBS_IN_PRODUCTION,
|
||||||
|
data: {
|
||||||
|
jobs: existingJobs.map((job) =>
|
||||||
|
job.id === jobId ? { ...existingJob, ...jobChangedData, __typename: "jobs" } : job
|
||||||
|
)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// If the job doesn't exist, fetch it from the server and then add it to the cache
|
||||||
|
try {
|
||||||
|
const { data: jobData } = await client.query({
|
||||||
|
query: QUERY_EXACT_JOB_IN_PRODUCTION,
|
||||||
|
variables: { id: jobId },
|
||||||
|
fetchPolicy: "network-only"
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add the job to the existing cached jobs
|
||||||
|
client.writeQuery({
|
||||||
|
query: QUERY_JOBS_IN_PRODUCTION,
|
||||||
|
data: {
|
||||||
|
jobs: [...existingJobs, { ...jobData.job, __typename: "jobs" }]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error fetching job ${jobId}: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDisconnect = () => {
|
||||||
|
// Capture the disconnection time
|
||||||
|
disconnectTime.current = Date.now();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleReconnect = () => {
|
||||||
|
const reconnectTime = Date.now();
|
||||||
|
const disconnectionDuration = reconnectTime - disconnectTime.current;
|
||||||
|
|
||||||
|
// Only refetch if disconnection was longer than the acceptable reconnect time
|
||||||
|
if (disconnectionDuration >= acceptableReconnectTime) {
|
||||||
|
if (!reconnectTimeout.current) {
|
||||||
|
reconnectTimeout.current = setTimeout(() => {
|
||||||
|
const randomDelay = Math.floor(Math.random() * (30000 - 10000 + 1)) + 10000; // Random delay between 10 and 30 seconds
|
||||||
|
setTimeout(() => {
|
||||||
|
if (refetch) refetch().catch((err) => console.error(`Issue `));
|
||||||
|
reconnectTimeout.current = null; // Clear the timeout reference after refetch
|
||||||
|
}, randomDelay);
|
||||||
|
}, acceptableReconnectTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Listen for 'job-changed', 'disconnect', and 'connect' events
|
||||||
|
socket.on("production-job-updated", handleJobUpdates);
|
||||||
|
socket.on("disconnect", handleDisconnect);
|
||||||
|
socket.on("connect", handleReconnect);
|
||||||
|
|
||||||
|
// Clean up on unmount or when dependencies change
|
||||||
|
return () => {
|
||||||
|
socket.off("production-job-updated", handleJobUpdates);
|
||||||
|
socket.off("disconnect", handleDisconnect);
|
||||||
|
socket.off("connect", handleReconnect);
|
||||||
|
if (reconnectTimeout.current) {
|
||||||
|
clearTimeout(reconnectTimeout.current);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [subscriptionEnabled, socket, bodyshop, client, refetch]);
|
||||||
|
|
||||||
const filteredAssociationSettings = useMemo(() => {
|
const filteredAssociationSettings = useMemo(() => {
|
||||||
return associationSettings?.associations[0] || null;
|
return associationSettings?.associations[0] || null;
|
||||||
}, [associationSettings]);
|
}, [associationSettings?.associations]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ProductionBoardKanbanComponent
|
<ProductionBoardKanbanComponent
|
||||||
|
|||||||
@@ -2,11 +2,14 @@ import React, { useMemo } from "react";
|
|||||||
import { Card, Statistic } from "antd";
|
import { Card, Statistic } from "antd";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import PropTypes from "prop-types";
|
import PropTypes from "prop-types";
|
||||||
import { statisticsItems, defaultKanbanSettings } from "./settings/defaultKanbanSettings.js";
|
import { defaultKanbanSettings, statisticsItems } from "./settings/defaultKanbanSettings.js";
|
||||||
|
import Dinero from "dinero.js";
|
||||||
|
|
||||||
export const StatisticType = {
|
export const StatisticType = {
|
||||||
HOURS: "hours",
|
HOURS: "hours",
|
||||||
AMOUNT: "amount",
|
AMOUNT: "amount",
|
||||||
JOBS: "jobs"
|
JOBS: "jobs",
|
||||||
|
TASKS: "tasks"
|
||||||
};
|
};
|
||||||
|
|
||||||
const mergeStatistics = (items, values) => {
|
const mergeStatistics = (items, values) => {
|
||||||
@@ -30,7 +33,21 @@ const ProductionStatistics = ({ data, cardSettings, reducerData }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const calculateTotalAmount = (items, key) => {
|
const calculateTotalAmount = (items, key) => {
|
||||||
return items.reduce((acc, item) => acc + (item[key]?.totals?.subtotal?.amount || 0), 0);
|
return items.reduce((acc, item) => acc.add(Dinero(item[key]?.totals?.subtotal ?? Dinero())), Dinero({ amount: 0 }));
|
||||||
|
};
|
||||||
|
|
||||||
|
const calculateReducerTotalAmount = (lanes, key) => {
|
||||||
|
return lanes.reduce(
|
||||||
|
(acc, lane) => {
|
||||||
|
return acc.add(
|
||||||
|
lane.cards.reduce(
|
||||||
|
(laneAcc, card) => laneAcc.add(Dinero(card.metadata[key]?.totals?.subtotal ?? Dinero())),
|
||||||
|
Dinero({ amount: 0 })
|
||||||
|
)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
Dinero({ amount: 0 })
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const calculateReducerTotal = (lanes, key, subKey) => {
|
const calculateReducerTotal = (lanes, key, subKey) => {
|
||||||
@@ -41,14 +58,6 @@ const ProductionStatistics = ({ data, cardSettings, reducerData }) => {
|
|||||||
}, 0);
|
}, 0);
|
||||||
};
|
};
|
||||||
|
|
||||||
const calculateReducerTotalAmount = (lanes, key) => {
|
|
||||||
return lanes.reduce((acc, lane) => {
|
|
||||||
return (
|
|
||||||
acc + lane.cards.reduce((laneAcc, card) => laneAcc + (card.metadata[key]?.totals?.subtotal?.amount || 0), 0)
|
|
||||||
);
|
|
||||||
}, 0);
|
|
||||||
};
|
|
||||||
|
|
||||||
const formatValue = (value, type) => {
|
const formatValue = (value, type) => {
|
||||||
if (type === StatisticType.JOBS) {
|
if (type === StatisticType.JOBS) {
|
||||||
return value.toFixed(0);
|
return value.toFixed(0);
|
||||||
@@ -85,9 +94,15 @@ const ProductionStatistics = ({ data, cardSettings, reducerData }) => {
|
|||||||
const totalAmountInProduction = useMemo(() => {
|
const totalAmountInProduction = useMemo(() => {
|
||||||
if (!cardSettings.totalAmountInProduction) return null;
|
if (!cardSettings.totalAmountInProduction) return null;
|
||||||
const total = calculateTotalAmount(data, "job_totals");
|
const total = calculateTotalAmount(data, "job_totals");
|
||||||
return parseFloat(total.toFixed(2));
|
return total.toFormat("$0,0.00");
|
||||||
}, [data, cardSettings.totalAmountInProduction]);
|
}, [data, cardSettings.totalAmountInProduction]);
|
||||||
|
|
||||||
|
const totalAmountOnBoard = useMemo(() => {
|
||||||
|
if (!reducerData || !cardSettings.totalAmountOnBoard) return null;
|
||||||
|
const total = calculateReducerTotalAmount(reducerData.lanes, "job_totals");
|
||||||
|
return total.toFormat("$0,0.00");
|
||||||
|
}, [reducerData, cardSettings.totalAmountOnBoard]);
|
||||||
|
|
||||||
const totalHrsOnBoard = useMemo(() => {
|
const totalHrsOnBoard = useMemo(() => {
|
||||||
if (!reducerData || !cardSettings.totalHrsOnBoard) return null;
|
if (!reducerData || !cardSettings.totalHrsOnBoard) return null;
|
||||||
const total =
|
const total =
|
||||||
@@ -116,11 +131,19 @@ const ProductionStatistics = ({ data, cardSettings, reducerData }) => {
|
|||||||
[reducerData, cardSettings.jobsOnBoard]
|
[reducerData, cardSettings.jobsOnBoard]
|
||||||
);
|
);
|
||||||
|
|
||||||
const totalAmountOnBoard = useMemo(() => {
|
const tasksInProduction = useMemo(() => {
|
||||||
if (!reducerData || !cardSettings.totalAmountOnBoard) return null;
|
if (!data || !cardSettings.tasksInProduction) return null;
|
||||||
const total = calculateReducerTotalAmount(reducerData.lanes, "job_totals");
|
return data.reduce((acc, item) => acc + (item.tasks_aggregate?.aggregate?.count || 0), 0);
|
||||||
return parseFloat(total.toFixed(2));
|
}, [data, cardSettings.tasksInProduction]);
|
||||||
}, [reducerData, cardSettings.totalAmountOnBoard]);
|
|
||||||
|
const tasksOnBoard = useMemo(() => {
|
||||||
|
if (!reducerData || !cardSettings.tasksOnBoard) return null;
|
||||||
|
return reducerData.lanes.reduce((acc, lane) => {
|
||||||
|
return (
|
||||||
|
acc + lane.cards.reduce((laneAcc, card) => laneAcc + (card.metadata.tasks_aggregate?.aggregate?.count || 0), 0)
|
||||||
|
);
|
||||||
|
}, 0);
|
||||||
|
}, [reducerData, cardSettings.tasksOnBoard]);
|
||||||
|
|
||||||
const statistics = useMemo(
|
const statistics = useMemo(
|
||||||
() =>
|
() =>
|
||||||
@@ -134,7 +157,9 @@ const ProductionStatistics = ({ data, cardSettings, reducerData }) => {
|
|||||||
{ id: 6, value: totalAmountOnBoard, type: StatisticType.AMOUNT },
|
{ id: 6, value: totalAmountOnBoard, type: StatisticType.AMOUNT },
|
||||||
{ id: 7, value: totalLABOnBoard, type: StatisticType.HOURS },
|
{ id: 7, value: totalLABOnBoard, type: StatisticType.HOURS },
|
||||||
{ id: 8, value: totalLAROnBoard, type: StatisticType.HOURS },
|
{ id: 8, value: totalLAROnBoard, type: StatisticType.HOURS },
|
||||||
{ id: 9, value: jobsOnBoard, type: StatisticType.JOBS }
|
{ id: 9, value: jobsOnBoard, type: StatisticType.JOBS },
|
||||||
|
{ id: 10, value: tasksOnBoard, type: StatisticType.TASKS },
|
||||||
|
{ id: 11, value: tasksInProduction, type: StatisticType.TASKS }
|
||||||
]),
|
]),
|
||||||
[
|
[
|
||||||
totalHrs,
|
totalHrs,
|
||||||
@@ -146,7 +171,9 @@ const ProductionStatistics = ({ data, cardSettings, reducerData }) => {
|
|||||||
totalAmountOnBoard,
|
totalAmountOnBoard,
|
||||||
totalLABOnBoard,
|
totalLABOnBoard,
|
||||||
totalLAROnBoard,
|
totalLAROnBoard,
|
||||||
jobsOnBoard
|
jobsOnBoard,
|
||||||
|
tasksOnBoard,
|
||||||
|
tasksInProduction
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -171,7 +198,6 @@ const ProductionStatistics = ({ data, cardSettings, reducerData }) => {
|
|||||||
<Statistic
|
<Statistic
|
||||||
title={t(`production.statistics.${stat.label}`)}
|
title={t(`production.statistics.${stat.label}`)}
|
||||||
value={formatValue(stat.value, stat.type)}
|
value={formatValue(stat.value, stat.type)}
|
||||||
prefix={stat.type === StatisticType.AMOUNT ? t("production.statistics.currency_symbol") : undefined}
|
|
||||||
suffix={
|
suffix={
|
||||||
stat.type === StatisticType.HOURS
|
stat.type === StatisticType.HOURS
|
||||||
? t("production.statistics.hours")
|
? t("production.statistics.hours")
|
||||||
@@ -187,37 +213,9 @@ const ProductionStatistics = ({ data, cardSettings, reducerData }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
ProductionStatistics.propTypes = {
|
ProductionStatistics.propTypes = {
|
||||||
data: PropTypes.arrayOf(
|
data: PropTypes.array.isRequired,
|
||||||
PropTypes.shape({
|
cardSettings: PropTypes.object.isRequired,
|
||||||
labhrs: PropTypes.object,
|
reducerData: PropTypes.object
|
||||||
larhrs: PropTypes.object,
|
|
||||||
job_totals: PropTypes.object
|
|
||||||
})
|
|
||||||
).isRequired,
|
|
||||||
cardSettings: PropTypes.shape({
|
|
||||||
totalHrs: PropTypes.bool,
|
|
||||||
totalLAB: PropTypes.bool,
|
|
||||||
totalLAR: PropTypes.bool,
|
|
||||||
jobsInProduction: PropTypes.bool,
|
|
||||||
totalAmountInProduction: PropTypes.bool,
|
|
||||||
totalHrsOnBoard: PropTypes.bool,
|
|
||||||
totalLABOnBoard: PropTypes.bool,
|
|
||||||
totalLAROnBoard: PropTypes.bool,
|
|
||||||
jobsOnBoard: PropTypes.bool,
|
|
||||||
totalAmountOnBoard: PropTypes.bool,
|
|
||||||
statisticsOrder: PropTypes.arrayOf(PropTypes.number)
|
|
||||||
}).isRequired,
|
|
||||||
reducerData: PropTypes.shape({
|
|
||||||
lanes: PropTypes.arrayOf(
|
|
||||||
PropTypes.shape({
|
|
||||||
cards: PropTypes.arrayOf(
|
|
||||||
PropTypes.shape({
|
|
||||||
metadata: PropTypes.object
|
|
||||||
})
|
|
||||||
).isRequired
|
|
||||||
})
|
|
||||||
).isRequired
|
|
||||||
})
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ProductionStatistics;
|
export default ProductionStatistics;
|
||||||
|
|||||||
@@ -17,7 +17,6 @@
|
|||||||
border-radius: 5px 5px 0 0;
|
border-radius: 5px 5px 0 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.production-alert {
|
.production-alert {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
border: none;
|
border: none;
|
||||||
@@ -70,3 +69,8 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.clone.is-dragging .ant-card {
|
||||||
|
border: #1890ff 2px solid !important;
|
||||||
|
border-radius: 12px;
|
||||||
|
}
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ const sortByParentId = (arr) => {
|
|||||||
|
|
||||||
// Function to create board data based on statuses and jobs, with optional filtering
|
// Function to create board data based on statuses and jobs, with optional filtering
|
||||||
export const createBoardData = ({ statuses, data, filter, cardSettings }) => {
|
export const createBoardData = ({ statuses, data, filter, cardSettings }) => {
|
||||||
const { search, employeeId } = filter;
|
const { search, employeeId, alert } = filter;
|
||||||
|
|
||||||
const lanes = statuses.map((status) => ({
|
const lanes = statuses.map((status) => ({
|
||||||
id: status,
|
id: status,
|
||||||
@@ -52,6 +52,11 @@ export const createBoardData = ({ statuses, data, filter, cardSettings }) => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Filter jobs by alert if alert filter is true
|
||||||
|
if (alert) {
|
||||||
|
filteredJobs = filteredJobs.filter((job) => job.production_vars?.alert);
|
||||||
|
}
|
||||||
|
|
||||||
const DataGroupedByStatus = groupBy(filteredJobs, "status");
|
const DataGroupedByStatus = groupBy(filteredJobs, "status");
|
||||||
|
|
||||||
Object.keys(DataGroupedByStatus).forEach((statusGroupKey) => {
|
Object.keys(DataGroupedByStatus).forEach((statusGroupKey) => {
|
||||||
|
|||||||
@@ -18,7 +18,8 @@ const InformationSettings = ({ t }) => (
|
|||||||
"sublets",
|
"sublets",
|
||||||
"partsstatus",
|
"partsstatus",
|
||||||
"estimator",
|
"estimator",
|
||||||
"subtotal"
|
"subtotal",
|
||||||
|
"tasks"
|
||||||
].map((item) => (
|
].map((item) => (
|
||||||
<Col span={4} key={item}>
|
<Col span={4} key={item}>
|
||||||
<Form.Item name={item} valuePropName="checked">
|
<Form.Item name={item} valuePropName="checked">
|
||||||
|
|||||||
@@ -1,14 +1,67 @@
|
|||||||
|
import InstanceRenderManager from "../../../utils/instanceRenderMgr.js";
|
||||||
|
|
||||||
const statisticsItems = [
|
const statisticsItems = [
|
||||||
{ id: 0, name: "totalHrs", label: "total_hours_in_production" },
|
{ id: 0, name: "totalHrs", label: "total_hours_in_production" },
|
||||||
{ id: 1, name: "totalAmountInProduction", label: "total_amount_in_production" },
|
{ id: 1, name: "totalAmountInProduction", label: "total_amount_in_production" },
|
||||||
{ id: 2, name: "totalLAB", label: "total_lab_in_production" },
|
{ id: 2, name: "totalLAB", label: "total_lab_in_production" },
|
||||||
{ id: 3, name: "totalLAR", label: "total_lar_in_production" },
|
{ id: 3, name: "totalLAR", label: "total_lar_in_production" },
|
||||||
{ id: 4, name: "jobsInProduction", label: "jobs_in_production" },
|
{ id: 4, name: "jobsInProduction", label: "jobs_in_production" },
|
||||||
{ id: 5, name: "totalHrsOnBoard", label: "total_hours_on_board" },
|
|
||||||
{ id: 6, name: "totalAmountOnBoard", label: "total_amount_on_board" },
|
{
|
||||||
{ id: 7, name: "totalLABOnBoard", label: "total_lab_on_board" },
|
id: 5,
|
||||||
{ id: 8, name: "totalLAROnBoard", label: "total_lar_on_board" },
|
name: "totalHrsOnBoard",
|
||||||
{ id: 9, name: "jobsOnBoard", label: "total_jobs_on_board" }
|
label: InstanceRenderManager({
|
||||||
|
imex: "total_hours_in_view",
|
||||||
|
rome: "total_hours_on_board",
|
||||||
|
promanager: "total_hours_on_board"
|
||||||
|
})
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 6,
|
||||||
|
name: "totalAmountOnBoard",
|
||||||
|
label: InstanceRenderManager({
|
||||||
|
imex: "total_amount_in_view",
|
||||||
|
rome: "total_amount_on_board",
|
||||||
|
promanager: "total_amount_on_board"
|
||||||
|
})
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 7,
|
||||||
|
name: "totalLABOnBoard",
|
||||||
|
label: InstanceRenderManager({
|
||||||
|
imex: "total_lab_in_view",
|
||||||
|
rome: "total_lab_on_board",
|
||||||
|
promanager: "total_lab_on_board"
|
||||||
|
})
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 8,
|
||||||
|
name: "totalLAROnBoard",
|
||||||
|
label: InstanceRenderManager({
|
||||||
|
imex: "total_lar_in_view",
|
||||||
|
rome: "total_lar_on_board",
|
||||||
|
promanager: "total_lar_on_board"
|
||||||
|
})
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 9,
|
||||||
|
name: "jobsOnBoard",
|
||||||
|
label: InstanceRenderManager({
|
||||||
|
imex: "total_jobs_in_view",
|
||||||
|
rome: "total_jobs_on_board",
|
||||||
|
promanager: "total_jobs_on_board"
|
||||||
|
})
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 10,
|
||||||
|
name: "tasksOnBoard",
|
||||||
|
label: InstanceRenderManager({
|
||||||
|
imex: "tasks_in_view",
|
||||||
|
rome: "tasks_on_board",
|
||||||
|
promanager: "tasks_on_board"
|
||||||
|
})
|
||||||
|
},
|
||||||
|
{ id: 11, name: "tasksInProduction", label: "tasks_in_production" }
|
||||||
];
|
];
|
||||||
|
|
||||||
const defaultKanbanSettings = {
|
const defaultKanbanSettings = {
|
||||||
@@ -23,6 +76,7 @@ const defaultKanbanSettings = {
|
|||||||
scheduled_completion: true,
|
scheduled_completion: true,
|
||||||
cardcolor: false,
|
cardcolor: false,
|
||||||
orientation: false,
|
orientation: false,
|
||||||
|
tasks: false,
|
||||||
cardSize: "small",
|
cardSize: "small",
|
||||||
model_info: true,
|
model_info: true,
|
||||||
kiosk: false,
|
kiosk: false,
|
||||||
@@ -35,6 +89,8 @@ const defaultKanbanSettings = {
|
|||||||
totalLABOnBoard: false,
|
totalLABOnBoard: false,
|
||||||
totalLAROnBoard: false,
|
totalLAROnBoard: false,
|
||||||
jobsOnBoard: false,
|
jobsOnBoard: false,
|
||||||
|
tasksOnBoard: false,
|
||||||
|
tasksInProduction: false,
|
||||||
totalAmountOnBoard: true,
|
totalAmountOnBoard: true,
|
||||||
estimator: false,
|
estimator: false,
|
||||||
subtotal: false,
|
subtotal: false,
|
||||||
@@ -43,4 +99,22 @@ const defaultKanbanSettings = {
|
|||||||
selectedEstimators: []
|
selectedEstimators: []
|
||||||
};
|
};
|
||||||
|
|
||||||
export { defaultKanbanSettings, statisticsItems };
|
const defaultFilters = { search: "", employeeId: null, alert: false };
|
||||||
|
|
||||||
|
const mergeWithDefaults = (settings) => {
|
||||||
|
// Create a new object that starts with the default settings
|
||||||
|
const mergedSettings = { ...defaultKanbanSettings };
|
||||||
|
|
||||||
|
// Override with the provided settings, if any
|
||||||
|
if (settings) {
|
||||||
|
for (const key in settings) {
|
||||||
|
if (settings.hasOwnProperty(key)) {
|
||||||
|
mergedSettings[key] = settings[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return mergedSettings;
|
||||||
|
};
|
||||||
|
|
||||||
|
export { defaultKanbanSettings, statisticsItems, mergeWithDefaults, defaultFilters };
|
||||||
|
|||||||
@@ -3,13 +3,15 @@ import { Button, Card, Col, Form, notification, Popover, Row, Tabs } from "antd"
|
|||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { UPDATE_KANBAN_SETTINGS } from "../../../graphql/user.queries.js";
|
import { UPDATE_KANBAN_SETTINGS } from "../../../graphql/user.queries.js";
|
||||||
import { defaultKanbanSettings } from "./defaultKanbanSettings.js";
|
import { defaultKanbanSettings, mergeWithDefaults } from "./defaultKanbanSettings.js";
|
||||||
import LayoutSettings from "./LayoutSettings.jsx";
|
import LayoutSettings from "./LayoutSettings.jsx";
|
||||||
import InformationSettings from "./InformationSettings.jsx";
|
import InformationSettings from "./InformationSettings.jsx";
|
||||||
import StatisticsSettings from "./StatisticsSettings.jsx";
|
import StatisticsSettings from "./StatisticsSettings.jsx";
|
||||||
import FilterSettings from "./FilterSettings.jsx";
|
import FilterSettings from "./FilterSettings.jsx";
|
||||||
|
import PropTypes from "prop-types";
|
||||||
|
import { isFunction } from "lodash";
|
||||||
|
|
||||||
export default function ProductionBoardKanbanSettings({ associationSettings, parentLoading, bodyshop, data }) {
|
function ProductionBoardKanbanSettings({ associationSettings, parentLoading, bodyshop, data, onSettingsChange }) {
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
@@ -23,16 +25,11 @@ export default function ProductionBoardKanbanSettings({ associationSettings, par
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (associationSettings?.kanban_settings) {
|
if (associationSettings?.kanban_settings) {
|
||||||
form.setFieldsValue(associationSettings.kanban_settings);
|
const finalSettings = mergeWithDefaults(associationSettings.kanban_settings);
|
||||||
if (associationSettings.kanban_settings.statisticsOrder) {
|
form.setFieldsValue(finalSettings);
|
||||||
setStatisticsOrder(associationSettings.kanban_settings.statisticsOrder);
|
setStatisticsOrder(finalSettings.statisticsOrder);
|
||||||
}
|
setSelectedMdInsCos(finalSettings.selectedMdInsCos);
|
||||||
if (associationSettings.kanban_settings.selectedMdInsCos) {
|
setSelectedEstimators(finalSettings.selectedEstimators);
|
||||||
setSelectedMdInsCos(associationSettings.kanban_settings.selectedMdInsCos);
|
|
||||||
}
|
|
||||||
if (associationSettings.kanban_settings.selectedEstimators) {
|
|
||||||
setSelectedEstimators(associationSettings.kanban_settings.selectedEstimators);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}, [form, associationSettings]);
|
}, [form, associationSettings]);
|
||||||
|
|
||||||
@@ -65,6 +62,11 @@ export default function ProductionBoardKanbanSettings({ associationSettings, par
|
|||||||
setOpen(false);
|
setOpen(false);
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
parentLoading(false);
|
parentLoading(false);
|
||||||
|
|
||||||
|
if (onSettingsChange && isFunction(onSettingsChange)) {
|
||||||
|
onSettingsChange(values);
|
||||||
|
}
|
||||||
|
|
||||||
setHasChanges(false);
|
setHasChanges(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -155,3 +157,13 @@ export default function ProductionBoardKanbanSettings({ associationSettings, par
|
|||||||
</Popover>
|
</Popover>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ProductionBoardKanbanSettings.propTypes = {
|
||||||
|
associationSettings: PropTypes.object,
|
||||||
|
parentLoading: PropTypes.func.isRequired,
|
||||||
|
bodyshop: PropTypes.object.isRequired,
|
||||||
|
onSettingsChange: PropTypes.func,
|
||||||
|
data: PropTypes.array
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ProductionBoardKanbanSettings;
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import { EyeInvisibleOutlined, EyeOutlined } from "@ant-design/icons";
|
|||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import { selectBodyshop } from "../../../../redux/user/user.selectors.js";
|
import { selectBodyshop } from "../../../../redux/user/user.selectors.js";
|
||||||
import { selectTechnician } from "../../../../redux/tech/tech.selectors.js";
|
import { selectTechnician } from "../../../../redux/tech/tech.selectors.js";
|
||||||
import ProductionBoardCard from "../../../production-board-kanban-card/production-board-kanban-card.component.jsx";
|
import ProductionBoardCard from "../../production-board-kanban-card.component.jsx";
|
||||||
import HeightMemoryWrapper from "../components/HeightMemoryWrapper.jsx";
|
import HeightMemoryWrapper from "../components/HeightMemoryWrapper.jsx";
|
||||||
import SizeMemoryWrapper from "../components/SizeMemoryWrapper.jsx";
|
import SizeMemoryWrapper from "../components/SizeMemoryWrapper.jsx";
|
||||||
import ListComponent from "../components/ListComponent.jsx";
|
import ListComponent from "../components/ListComponent.jsx";
|
||||||
|
|||||||
@@ -25,8 +25,8 @@ function getFurthestAway({ pageBorderBox, draggable, candidates }) {
|
|||||||
const axis = candidate.axis;
|
const axis = candidate.axis;
|
||||||
const target = patch(
|
const target = patch(
|
||||||
candidate.axis.line,
|
candidate.axis.line,
|
||||||
// use the current center of the dragging item on the main axis
|
// use the center of the list on the main axis
|
||||||
pageBorderBox.center[axis.line],
|
candidate.page.borderBox.center[axis.line],
|
||||||
// use the center of the list on the cross axis
|
// use the center of the list on the cross axis
|
||||||
candidate.page.borderBox.center[axis.crossAxisLine]
|
candidate.page.borderBox.center[axis.crossAxisLine]
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import getBodyElement from "../get-body-element";
|
|||||||
const isEqual = (base) => (value) => base === value;
|
const isEqual = (base) => (value) => base === value;
|
||||||
const isScroll = isEqual("scroll");
|
const isScroll = isEqual("scroll");
|
||||||
const isAuto = isEqual("auto");
|
const isAuto = isEqual("auto");
|
||||||
|
const isOverlay = isEqual("overlay");
|
||||||
const isVisible = isEqual("visible");
|
const isVisible = isEqual("visible");
|
||||||
const isEither = (overflow, fn) => fn(overflow.overflowX) || fn(overflow.overflowY);
|
const isEither = (overflow, fn) => fn(overflow.overflowX) || fn(overflow.overflowY);
|
||||||
const isBoth = (overflow, fn) => fn(overflow.overflowX) && fn(overflow.overflowY);
|
const isBoth = (overflow, fn) => fn(overflow.overflowX) && fn(overflow.overflowY);
|
||||||
@@ -14,7 +15,7 @@ const isElementScrollable = (el) => {
|
|||||||
overflowX: style.overflowX,
|
overflowX: style.overflowX,
|
||||||
overflowY: style.overflowY
|
overflowY: style.overflowY
|
||||||
};
|
};
|
||||||
return isEither(overflow, isScroll) || isEither(overflow, isAuto);
|
return isEither(overflow, isScroll) || isEither(overflow, isAuto) || isEither(overflow, isOverlay);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Special case for a body element
|
// Special case for a body element
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ function getSelector(contextId) {
|
|||||||
return `[${attributes.dragHandle.contextId}="${contextId}"]`;
|
return `[${attributes.dragHandle.contextId}="${contextId}"]`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function findClosestDragHandleFromEvent(contextId, event) {
|
export function findClosestDragHandleFromEvent(contextId, event) {
|
||||||
const target = event.target;
|
const target = event.target;
|
||||||
if (!isElement(target)) {
|
if (!isElement(target)) {
|
||||||
warning("event.target must be a Element");
|
warning("event.target must be a Element");
|
||||||
|
|||||||
@@ -240,11 +240,14 @@ export default function useTouchSensor(api) {
|
|||||||
y: clientY
|
y: clientY
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handle = api.findClosestDragHandle(event);
|
||||||
|
invariant(handle, "Touch sensor unable to find drag handle");
|
||||||
|
|
||||||
// unbind this event handler
|
// unbind this event handler
|
||||||
unbindEventsRef.current();
|
unbindEventsRef.current();
|
||||||
|
|
||||||
// eslint-disable-next-line no-use-before-define
|
// eslint-disable-next-line no-use-before-define
|
||||||
startPendingDrag(actions, point);
|
startPendingDrag(actions, point, handle);
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
// not including stop or startPendingDrag as it is not defined initially
|
// not including stop or startPendingDrag as it is not defined initially
|
||||||
@@ -288,7 +291,7 @@ export default function useTouchSensor(api) {
|
|||||||
}
|
}
|
||||||
}, [stop]);
|
}, [stop]);
|
||||||
const bindCapturingEvents = useCallback(
|
const bindCapturingEvents = useCallback(
|
||||||
function bindCapturingEvents() {
|
function bindCapturingEvents(target) {
|
||||||
const options = {
|
const options = {
|
||||||
capture: true,
|
capture: true,
|
||||||
passive: false
|
passive: false
|
||||||
@@ -307,7 +310,7 @@ export default function useTouchSensor(api) {
|
|||||||
// Old behaviour:
|
// Old behaviour:
|
||||||
// https://gist.github.com/parris/dda613e3ae78f14eb2dc9fa0f4bfce3d
|
// https://gist.github.com/parris/dda613e3ae78f14eb2dc9fa0f4bfce3d
|
||||||
// https://stackoverflow.com/questions/33298828/touch-move-event-dont-fire-after-touch-start-target-is-removed
|
// https://stackoverflow.com/questions/33298828/touch-move-event-dont-fire-after-touch-start-target-is-removed
|
||||||
const unbindTarget = bindEvents(window, getHandleBindings(args), options);
|
const unbindTarget = bindEvents(target, getHandleBindings(args), options);
|
||||||
const unbindWindow = bindEvents(window, getWindowBindings(args), options);
|
const unbindWindow = bindEvents(window, getWindowBindings(args), options);
|
||||||
unbindEventsRef.current = function unbindAll() {
|
unbindEventsRef.current = function unbindAll() {
|
||||||
unbindTarget();
|
unbindTarget();
|
||||||
@@ -330,7 +333,7 @@ export default function useTouchSensor(api) {
|
|||||||
[getPhase, setPhase]
|
[getPhase, setPhase]
|
||||||
);
|
);
|
||||||
const startPendingDrag = useCallback(
|
const startPendingDrag = useCallback(
|
||||||
function startPendingDrag(actions, point) {
|
function startPendingDrag(actions, point, target) {
|
||||||
invariant(getPhase().type === "IDLE", "Expected to move from IDLE to PENDING drag");
|
invariant(getPhase().type === "IDLE", "Expected to move from IDLE to PENDING drag");
|
||||||
const longPressTimerId = setTimeout(startDragging, timeForLongPress);
|
const longPressTimerId = setTimeout(startDragging, timeForLongPress);
|
||||||
setPhase({
|
setPhase({
|
||||||
@@ -339,7 +342,7 @@ export default function useTouchSensor(api) {
|
|||||||
actions,
|
actions,
|
||||||
longPressTimerId
|
longPressTimerId
|
||||||
});
|
});
|
||||||
bindCapturingEvents();
|
bindCapturingEvents(target);
|
||||||
},
|
},
|
||||||
[bindCapturingEvents, getPhase, setPhase, startDragging]
|
[bindCapturingEvents, getPhase, setPhase, startDragging]
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -23,7 +23,9 @@ import getBorderBoxCenterPosition from "../get-border-box-center-position";
|
|||||||
import { warning } from "../../dev-warning";
|
import { warning } from "../../dev-warning";
|
||||||
import useLayoutEffect from "../use-isomorphic-layout-effect";
|
import useLayoutEffect from "../use-isomorphic-layout-effect";
|
||||||
import { noop } from "../../empty";
|
import { noop } from "../../empty";
|
||||||
import findClosestDraggableIdFromEvent from "./find-closest-draggable-id-from-event";
|
import findClosestDraggableIdFromEvent, {
|
||||||
|
findClosestDragHandleFromEvent
|
||||||
|
} from "./find-closest-draggable-id-from-event";
|
||||||
import findDraggable from "../get-elements/find-draggable";
|
import findDraggable from "../get-elements/find-draggable";
|
||||||
import bindEvents from "../event-bindings/bind-events";
|
import bindEvents from "../event-bindings/bind-events";
|
||||||
|
|
||||||
@@ -339,6 +341,9 @@ export default function useSensorMarshal({ contextId, store, registry, customSen
|
|||||||
}),
|
}),
|
||||||
[contextId, lockAPI, registry, store]
|
[contextId, lockAPI, registry, store]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const findClosestDragHandle = useCallback((event) => findClosestDragHandleFromEvent(contextId, event), [contextId]);
|
||||||
|
|
||||||
const findClosestDraggableId = useCallback((event) => findClosestDraggableIdFromEvent(contextId, event), [contextId]);
|
const findClosestDraggableId = useCallback((event) => findClosestDraggableIdFromEvent(contextId, event), [contextId]);
|
||||||
const findOptionsForDraggable = useCallback(
|
const findOptionsForDraggable = useCallback(
|
||||||
(id) => {
|
(id) => {
|
||||||
@@ -370,9 +375,18 @@ export default function useSensorMarshal({ contextId, store, registry, customSen
|
|||||||
findClosestDraggableId,
|
findClosestDraggableId,
|
||||||
findOptionsForDraggable,
|
findOptionsForDraggable,
|
||||||
tryReleaseLock,
|
tryReleaseLock,
|
||||||
isLockClaimed
|
isLockClaimed,
|
||||||
|
findClosestDragHandle
|
||||||
}),
|
}),
|
||||||
[canGetLock, tryGetLock, findClosestDraggableId, findOptionsForDraggable, tryReleaseLock, isLockClaimed]
|
[
|
||||||
|
canGetLock,
|
||||||
|
tryGetLock,
|
||||||
|
findClosestDraggableId,
|
||||||
|
findOptionsForDraggable,
|
||||||
|
tryReleaseLock,
|
||||||
|
isLockClaimed,
|
||||||
|
findClosestDragHandle
|
||||||
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
// Bad ass
|
// Bad ass
|
||||||
|
|||||||
@@ -83,7 +83,13 @@ const getFinalStyles = (contextId) => {
|
|||||||
return {
|
return {
|
||||||
selector: getSelector(attributes.draggable.contextId),
|
selector: getSelector(attributes.draggable.contextId),
|
||||||
styles: {
|
styles: {
|
||||||
dragging: transition,
|
dragging: `
|
||||||
|
${transition}
|
||||||
|
user-select: none;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
-moz-user-select: none;
|
||||||
|
-ms-user-select: none;
|
||||||
|
`,
|
||||||
dropAnimating: transition,
|
dropAnimating: transition,
|
||||||
userCancel: transition
|
userCancel: transition
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -67,7 +67,9 @@ export default function useStyleMarshal(contextId, nonce) {
|
|||||||
const remove = (ref) => {
|
const remove = (ref) => {
|
||||||
const current = ref.current;
|
const current = ref.current;
|
||||||
invariant(current, "Cannot unmount ref as it is not set");
|
invariant(current, "Cannot unmount ref as it is not set");
|
||||||
getHead().removeChild(current);
|
if (getHead().contains(current)) {
|
||||||
|
getHead().removeChild(current);
|
||||||
|
}
|
||||||
ref.current = null;
|
ref.current = null;
|
||||||
};
|
};
|
||||||
remove(alwaysRef);
|
remove(alwaysRef);
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import React from "react";
|
|||||||
import { Button, Dropdown } from "antd";
|
import { Button, Dropdown } from "antd";
|
||||||
import dataSource from "./production-list-columns.data";
|
import dataSource from "./production-list-columns.data";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import { selectTechnician } from "../../redux/tech/tech.selectors";
|
import { selectTechnician } from "../../redux/tech/tech.selectors";
|
||||||
@@ -10,16 +9,23 @@ import { selectBodyshop } from "../../redux/user/user.selectors";
|
|||||||
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
//currentUser: selectCurrentUser
|
|
||||||
technician: selectTechnician,
|
technician: selectTechnician,
|
||||||
bodyshop: selectBodyshop
|
bodyshop: selectBodyshop
|
||||||
});
|
});
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
|
||||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
|
||||||
});
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(ProductionColumnsComponent);
|
|
||||||
|
|
||||||
export function ProductionColumnsComponent({ columnState, technician, bodyshop, data, tableState, refetch }) {
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
|
// Add any necessary dispatch actions here
|
||||||
|
});
|
||||||
|
|
||||||
|
export function ProductionColumnsComponent({
|
||||||
|
columnState,
|
||||||
|
technician,
|
||||||
|
bodyshop,
|
||||||
|
data,
|
||||||
|
tableState,
|
||||||
|
refetch,
|
||||||
|
onColumnAdd
|
||||||
|
}) {
|
||||||
const [columns, setColumns] = columnState;
|
const [columns, setColumns] = columnState;
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const {
|
const {
|
||||||
@@ -29,18 +35,26 @@ export function ProductionColumnsComponent({ columnState, technician, bodyshop,
|
|||||||
names: ["Enhanced_Payroll"],
|
names: ["Enhanced_Payroll"],
|
||||||
splitKey: bodyshop.imexshopid
|
splitKey: bodyshop.imexshopid
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleAdd = (e) => {
|
const handleAdd = (e) => {
|
||||||
setColumns([
|
const newColumn = dataSource({
|
||||||
...columns,
|
bodyshop,
|
||||||
...dataSource({
|
technician,
|
||||||
bodyshop,
|
state: tableState,
|
||||||
technician,
|
data,
|
||||||
state: tableState,
|
activeStatuses: bodyshop.md_ro_statuses.active_statuses,
|
||||||
data,
|
treatments: { Enhanced_Payroll }
|
||||||
activeStatuses: bodyshop.md_ro_statuses.active_statuses,
|
}).find((i) => i.key === e.key);
|
||||||
treatments: { Enhanced_Payroll }
|
|
||||||
}).filter((i) => i.key === e.key)
|
if (newColumn) {
|
||||||
]);
|
const updatedColumns = [...columns, newColumn];
|
||||||
|
setColumns(updatedColumns);
|
||||||
|
|
||||||
|
// Call the onColumnAdd function passed as a prop
|
||||||
|
if (onColumnAdd) {
|
||||||
|
onColumnAdd(newColumn);
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const columnKeys = columns.map((i) => i.key);
|
const columnKeys = columns.map((i) => i.key);
|
||||||
@@ -76,12 +90,4 @@ export function ProductionColumnsComponent({ columnState, technician, bodyshop,
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// <Transfer
|
export default connect(mapStateToProps, mapDispatchToProps)(ProductionColumnsComponent);
|
||||||
// dataSource={dataSource}
|
|
||||||
// titles={["Source", "Target"]}
|
|
||||||
// targetKeys={columns.map((c) => c.key)}
|
|
||||||
// render={(item) => item.title}
|
|
||||||
// onChange={(nextTargetKeys, direction, moveKeys) => {
|
|
||||||
// setColumns(dataSource.filter((i) => nextTargetKeys.includes(i.key)));
|
|
||||||
// }}
|
|
||||||
// />
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { ExclamationCircleFilled } from "@ant-design/icons";
|
import { ExclamationCircleFilled, PlusCircleFilled } from "@ant-design/icons";
|
||||||
import { useMutation } from "@apollo/client";
|
import { useMutation } from "@apollo/client";
|
||||||
import { Button } from "antd";
|
import { Button, Popconfirm } from "antd";
|
||||||
import React, { useCallback } from "react";
|
import React, { useCallback } from "react";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
@@ -8,6 +8,7 @@ import { logImEXEvent } from "../../firebase/firebase.utils";
|
|||||||
import { UPDATE_JOB } from "../../graphql/jobs.queries";
|
import { UPDATE_JOB } from "../../graphql/jobs.queries";
|
||||||
import { insertAuditTrail } from "../../redux/application/application.actions";
|
import { insertAuditTrail } from "../../redux/application/application.actions";
|
||||||
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({});
|
const mapStateToProps = createStructuredSelector({});
|
||||||
|
|
||||||
@@ -22,22 +23,24 @@ const mapDispatchToProps = (dispatch) => ({
|
|||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
const ProductionListColumnAlert = ({ record, insertAuditTrail }) => {
|
const ProductionListColumnAlert = ({ id, productionVars, refetch, insertAuditTrail }) => {
|
||||||
const [updateAlert] = useMutation(UPDATE_JOB);
|
const [updateAlert] = useMutation(UPDATE_JOB);
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const handleAlertToggle = useCallback(() => {
|
const handleAlertToggle = useCallback(() => {
|
||||||
logImEXEvent("production_toggle_alert");
|
logImEXEvent("production_toggle_alert");
|
||||||
|
|
||||||
const newAlertState = !!record.production_vars?.alert ? !record.production_vars.alert : true;
|
const newAlertState = !!productionVars?.alert ? !productionVars?.alert : true;
|
||||||
|
const finalProductionVars = {
|
||||||
|
...productionVars,
|
||||||
|
alert: newAlertState
|
||||||
|
};
|
||||||
|
|
||||||
updateAlert({
|
updateAlert({
|
||||||
variables: {
|
variables: {
|
||||||
jobId: record.id,
|
jobId: id,
|
||||||
job: {
|
job: {
|
||||||
production_vars: {
|
production_vars: finalProductionVars
|
||||||
...record.production_vars,
|
|
||||||
alert: newAlertState
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
@@ -45,17 +48,26 @@ const ProductionListColumnAlert = ({ record, insertAuditTrail }) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
insertAuditTrail({
|
insertAuditTrail({
|
||||||
jobid: record.id,
|
jobid: id,
|
||||||
operation: AuditTrailMapping.alertToggle(newAlertState),
|
operation: AuditTrailMapping.alertToggle(newAlertState),
|
||||||
type: "alertToggle"
|
type: "alertToggle"
|
||||||
});
|
});
|
||||||
|
|
||||||
if (record.refetch) record.refetch();
|
if (refetch) refetch();
|
||||||
}, [updateAlert, insertAuditTrail, record]);
|
}, [updateAlert, insertAuditTrail, id, productionVars, refetch]);
|
||||||
|
|
||||||
if (!record.production_vars?.alert) return null;
|
return productionVars?.alert ? (
|
||||||
|
<Popconfirm
|
||||||
return <Button className="production-alert" icon={<ExclamationCircleFilled />} onClick={handleAlertToggle} />;
|
title={t("general.actions.remove_alert")}
|
||||||
|
onConfirm={handleAlertToggle}
|
||||||
|
okText={t("general.labels.yes")}
|
||||||
|
cancelText={t("general.labels.no")}
|
||||||
|
>
|
||||||
|
<Button className="production-alert" icon={<ExclamationCircleFilled />} />
|
||||||
|
</Popconfirm>
|
||||||
|
) : (
|
||||||
|
<Button className="muted-button" icon={<PlusCircleFilled />} onClick={handleAlertToggle} />
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(ProductionListColumnAlert);
|
export default connect(mapStateToProps, mapDispatchToProps)(ProductionListColumnAlert);
|
||||||
|
|||||||
@@ -2,10 +2,13 @@ import { BranchesOutlined, PauseCircleOutlined } from "@ant-design/icons";
|
|||||||
import { Checkbox, Space, Tooltip } from "antd";
|
import { Checkbox, Space, Tooltip } from "antd";
|
||||||
import i18n from "i18next";
|
import i18n from "i18next";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
|
import { setModalContext } from "../../redux/modals/modals.actions";
|
||||||
|
import { store } from "../../redux/store";
|
||||||
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||||
import { TimeFormatter } from "../../utils/DateFormatter";
|
import { TimeFormatter } from "../../utils/DateFormatter";
|
||||||
import PhoneFormatter from "../../utils/PhoneFormatter";
|
import PhoneFormatter from "../../utils/PhoneFormatter";
|
||||||
import { onlyUnique } from "../../utils/arrayHelper";
|
import { onlyUnique } from "../../utils/arrayHelper";
|
||||||
|
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
||||||
import { alphaSort, dateSort, statusSort } from "../../utils/sorters";
|
import { alphaSort, dateSort, statusSort } from "../../utils/sorters";
|
||||||
import JobAltTransportChange from "../job-at-change/job-at-change.component";
|
import JobAltTransportChange from "../job-at-change/job-at-change.component";
|
||||||
import JobPartsQueueCount from "../job-parts-queue-count/job-parts-queue-count.component";
|
import JobPartsQueueCount from "../job-parts-queue-count/job-parts-queue-count.component";
|
||||||
@@ -23,10 +26,12 @@ import ProductionListColumnPartsReceived from "./production-list-columns.partsre
|
|||||||
import ProductionListColumnNote from "./production-list-columns.productionnote.component";
|
import ProductionListColumnNote from "./production-list-columns.productionnote.component";
|
||||||
import ProductionListColumnCategory from "./production-list-columns.status.category";
|
import ProductionListColumnCategory from "./production-list-columns.status.category";
|
||||||
import ProductionListColumnStatus from "./production-list-columns.status.component";
|
import ProductionListColumnStatus from "./production-list-columns.status.component";
|
||||||
import ProductionlistColumnTouchTime from "./prodution-list-columns.touchtime.component";
|
import ProductionListColumnTouchTime from "./prodution-list-columns.touchtime.component";
|
||||||
import { store } from "../../redux/store";
|
|
||||||
import { setModalContext } from "../../redux/modals/modals.actions";
|
const getEmployeeName = (employeeId, employees) => {
|
||||||
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
const employee = employees.find((e) => e.id === employeeId);
|
||||||
|
return employee ? `${employee.first_name} ${employee.last_name}` : "";
|
||||||
|
};
|
||||||
|
|
||||||
const r = ({ technician, state, activeStatuses, data, bodyshop, refetch, treatments }) => {
|
const r = ({ technician, state, activeStatuses, data, bodyshop, refetch, treatments }) => {
|
||||||
const { Enhanced_Payroll } = treatments;
|
const { Enhanced_Payroll } = treatments;
|
||||||
@@ -258,7 +263,7 @@ const r = ({ technician, state, activeStatuses, data, bodyshop, refetch, treatme
|
|||||||
{ text: "True", value: true },
|
{ text: "True", value: true },
|
||||||
{ text: "False", value: false }
|
{ text: "False", value: false }
|
||||||
],
|
],
|
||||||
onFilter: (value, record) => value.includes(record.special_coverage_policy),
|
onFilter: (value, record) => value === record.special_coverage_policy,
|
||||||
render: (text, record) => <Checkbox checked={record.special_coverage_policy} />
|
render: (text, record) => <Checkbox checked={record.special_coverage_policy} />
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -293,6 +298,16 @@ const r = ({ technician, state, activeStatuses, data, bodyshop, refetch, treatme
|
|||||||
ellipsis: true,
|
ellipsis: true,
|
||||||
sorter: (a, b) => statusSort(a.status, b.status, activeStatuses),
|
sorter: (a, b) => statusSort(a.status, b.status, activeStatuses),
|
||||||
sortOrder: state.sortedInfo.columnKey === "status" && state.sortedInfo.order,
|
sortOrder: state.sortedInfo.columnKey === "status" && state.sortedInfo.order,
|
||||||
|
filters:
|
||||||
|
activeStatuses
|
||||||
|
?.map((s) => {
|
||||||
|
return {
|
||||||
|
text: s || "No Status*",
|
||||||
|
value: [s]
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.sort((a, b) => statusSort(a.text, b.text, activeStatuses)) || [],
|
||||||
|
onFilter: (value, record) => value.includes(record.status),
|
||||||
render: (text, record) => <ProductionListColumnStatus record={record} />
|
render: (text, record) => <ProductionListColumnStatus record={record} />
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -349,7 +364,14 @@ const r = ({ technician, state, activeStatuses, data, bodyshop, refetch, treatme
|
|||||||
key: "alert",
|
key: "alert",
|
||||||
sorter: (a, b) => Number(a.production_vars?.alert || false) - Number(b.production_vars?.alert || false),
|
sorter: (a, b) => Number(a.production_vars?.alert || false) - Number(b.production_vars?.alert || false),
|
||||||
sortOrder: state.sortedInfo.columnKey === "alert" && state.sortedInfo.order,
|
sortOrder: state.sortedInfo.columnKey === "alert" && state.sortedInfo.order,
|
||||||
render: (text, record) => <ProductionListColumnAlert record={{ record }} />
|
filters: [
|
||||||
|
{ text: "True", value: true },
|
||||||
|
{ text: "False", value: false }
|
||||||
|
],
|
||||||
|
onFilter: (value, record) => value === (record.production_vars?.alert || false),
|
||||||
|
render: (text, record) => (
|
||||||
|
<ProductionListColumnAlert id={record.id} productionVars={record?.production_vars} refetch={refetch} />
|
||||||
|
)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: i18n.t("production.labels.note"),
|
title: i18n.t("production.labels.note"),
|
||||||
@@ -370,7 +392,7 @@ const r = ({ technician, state, activeStatuses, data, bodyshop, refetch, treatme
|
|||||||
dataIndex: "tt",
|
dataIndex: "tt",
|
||||||
key: "tt",
|
key: "tt",
|
||||||
render: (text, record) => {
|
render: (text, record) => {
|
||||||
return <ProductionlistColumnTouchTime job={record} />;
|
return <ProductionListColumnTouchTime job={record} />;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -419,8 +441,8 @@ const r = ({ technician, state, activeStatuses, data, bodyshop, refetch, treatme
|
|||||||
sortOrder: state.sortedInfo.columnKey === "employee_body" && state.sortedInfo.order,
|
sortOrder: state.sortedInfo.columnKey === "employee_body" && state.sortedInfo.order,
|
||||||
sorter: (a, b) =>
|
sorter: (a, b) =>
|
||||||
alphaSort(
|
alphaSort(
|
||||||
bodyshop.employees?.find((e) => e.id === a.employee_body)?.first_name,
|
getEmployeeName(a.employee_body, bodyshop.employees),
|
||||||
bodyshop.employees?.find((e) => e.id === b.employee_body)?.first_name
|
getEmployeeName(b.employee_body, bodyshop.employees)
|
||||||
),
|
),
|
||||||
render: (text, record) => (
|
render: (text, record) => (
|
||||||
<ProductionListEmployeeAssignment refetch={refetch} record={record} type="employee_body" />
|
<ProductionListEmployeeAssignment refetch={refetch} record={record} type="employee_body" />
|
||||||
@@ -433,8 +455,8 @@ const r = ({ technician, state, activeStatuses, data, bodyshop, refetch, treatme
|
|||||||
sortOrder: state.sortedInfo.columnKey === "employee_prep" && state.sortedInfo.order,
|
sortOrder: state.sortedInfo.columnKey === "employee_prep" && state.sortedInfo.order,
|
||||||
sorter: (a, b) =>
|
sorter: (a, b) =>
|
||||||
alphaSort(
|
alphaSort(
|
||||||
bodyshop.employees?.find((e) => e.id === a.employee_prep)?.first_name,
|
getEmployeeName(a.employee_prep, bodyshop.employees),
|
||||||
bodyshop.employees?.find((e) => e.id === b.employee_prep)?.first_name
|
getEmployeeName(b.employee_prep, bodyshop.employees)
|
||||||
),
|
),
|
||||||
render: (text, record) => (
|
render: (text, record) => (
|
||||||
<ProductionListEmployeeAssignment record={record} refetch={refetch} type="employee_prep" />
|
<ProductionListEmployeeAssignment record={record} refetch={refetch} type="employee_prep" />
|
||||||
@@ -453,8 +475,8 @@ const r = ({ technician, state, activeStatuses, data, bodyshop, refetch, treatme
|
|||||||
sortOrder: state.sortedInfo.columnKey === "employee_csr" && state.sortedInfo.order,
|
sortOrder: state.sortedInfo.columnKey === "employee_csr" && state.sortedInfo.order,
|
||||||
sorter: (a, b) =>
|
sorter: (a, b) =>
|
||||||
alphaSort(
|
alphaSort(
|
||||||
bodyshop.employees?.find((e) => e.id === a.employee_csr)?.first_name,
|
getEmployeeName(a.employee_csr, bodyshop.employees),
|
||||||
bodyshop.employees?.find((e) => e.id === b.employee_csr)?.first_name
|
getEmployeeName(b.employee_csr, bodyshop.employees)
|
||||||
),
|
),
|
||||||
render: (text, record) => (
|
render: (text, record) => (
|
||||||
<ProductionListEmployeeAssignment refetch={refetch} record={record} type="employee_csr" />
|
<ProductionListEmployeeAssignment refetch={refetch} record={record} type="employee_csr" />
|
||||||
@@ -467,8 +489,8 @@ const r = ({ technician, state, activeStatuses, data, bodyshop, refetch, treatme
|
|||||||
sortOrder: state.sortedInfo.columnKey === "employee_refinish" && state.sortedInfo.order,
|
sortOrder: state.sortedInfo.columnKey === "employee_refinish" && state.sortedInfo.order,
|
||||||
sorter: (a, b) =>
|
sorter: (a, b) =>
|
||||||
alphaSort(
|
alphaSort(
|
||||||
bodyshop.employees?.find((e) => e.id === a.employee_refinish)?.first_name,
|
getEmployeeName(a.employee_refinish, bodyshop.employees),
|
||||||
bodyshop.employees?.find((e) => e.id === b.employee_refinish)?.first_name
|
getEmployeeName(b.employee_refinish, bodyshop.employees)
|
||||||
),
|
),
|
||||||
render: (text, record) => (
|
render: (text, record) => (
|
||||||
<ProductionListEmployeeAssignment record={record} refetch={refetch} type="employee_refinish" />
|
<ProductionListEmployeeAssignment record={record} refetch={refetch} type="employee_refinish" />
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import { useMutation } from "@apollo/client";
|
import { useMutation } from "@apollo/client";
|
||||||
import { Button, Card, Dropdown, Space, TimePicker } from "antd";
|
import { Button, Card, Dropdown, Space } from "antd";
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { logImEXEvent } from "../../firebase/firebase.utils";
|
import { logImEXEvent } from "../../firebase/firebase.utils";
|
||||||
import { UPDATE_JOB } from "../../graphql/jobs.queries";
|
import { UPDATE_JOB } from "../../graphql/jobs.queries";
|
||||||
import { DateFormatter } from "../../utils/DateFormatter";
|
import { DateFormatter } from "../../utils/DateFormatter";
|
||||||
import dayjs from "../../utils/day";
|
import dayjs from "../../utils/day";
|
||||||
import FormDatePicker from "../form-date-picker/form-date-picker.component";
|
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component.jsx";
|
||||||
|
|
||||||
export default function ProductionListDate({ record, field, time, pastIndicator }) {
|
export default function ProductionListDate({ record, field, time, pastIndicator }) {
|
||||||
const [updateAlert] = useMutation(UPDATE_JOB);
|
const [updateAlert] = useMutation(UPDATE_JOB);
|
||||||
@@ -57,22 +57,14 @@ export default function ProductionListDate({ record, field, time, pastIndicator
|
|||||||
label: (
|
label: (
|
||||||
<Card style={{ padding: "1rem" }} onClick={(e) => e.stopPropagation()}>
|
<Card style={{ padding: "1rem" }} onClick={(e) => e.stopPropagation()}>
|
||||||
<Space direction={"vertical"}>
|
<Space direction={"vertical"}>
|
||||||
<FormDatePicker
|
<DateTimePicker
|
||||||
onClick={(e) => e.stopPropagation()}
|
onClick={(e) => e.stopPropagation()}
|
||||||
value={(record[field] && dayjs(record[field])) || null}
|
value={(record[field] && dayjs(record[field])) || null}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
format="MM/DD/YYYY"
|
format={time ? "MM/DD/YYYY hh:mm a" : "MM/DD/YYYY"}
|
||||||
isDateOnly={!time}
|
isDateOnly={!time}
|
||||||
|
showTime={time ? { format: "hh:mm a", minuteStep: 15 } : false}
|
||||||
/>
|
/>
|
||||||
{time && (
|
|
||||||
<TimePicker
|
|
||||||
onClick={(e) => e.stopPropagation()}
|
|
||||||
value={(record[field] && dayjs(record[field])) || null}
|
|
||||||
onChange={handleChange}
|
|
||||||
minuteStep={15}
|
|
||||||
format="hh:mm a"
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<Button onClick={() => setOpen(false)}>{t("general.actions.close")}</Button>
|
<Button onClick={() => setOpen(false)}>{t("general.actions.close")}</Button>
|
||||||
</Space>
|
</Space>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@@ -148,6 +148,7 @@ export function ProductionListEmpAssignment({ insertAuditTrail, bodyshop, record
|
|||||||
) : (
|
) : (
|
||||||
<PlusCircleFilled
|
<PlusCircleFilled
|
||||||
style={iconStyle}
|
style={iconStyle}
|
||||||
|
className="muted-button"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setAssignment({ operation: type });
|
setAssignment({ operation: type });
|
||||||
setVisibility(true);
|
setVisibility(true);
|
||||||
|
|||||||
@@ -21,25 +21,26 @@ export function ProductionListColumnStatus({ record, bodyshop, insertAuditTrail
|
|||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
const handleSetStatus = async (e) => {
|
const handleSetStatus = async (e) => {
|
||||||
logImEXEvent("production_change_status");
|
if (bodyshop.md_ro_statuses.production_statuses.includes(record.status) && !bodyshop.md_ro_statuses.post_production_statuses.includes(record.status)) {
|
||||||
// e.stopPropagation();
|
logImEXEvent("production_change_status");
|
||||||
setLoading(true);
|
// e.stopPropagation();
|
||||||
const { key } = e;
|
setLoading(true);
|
||||||
await updateJob({
|
const { key } = e;
|
||||||
variables: {
|
await updateJob({
|
||||||
jobId: record.id,
|
variables: {
|
||||||
job: {
|
jobId: record.id,
|
||||||
status: key
|
job: {
|
||||||
|
status: key
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
});
|
insertAuditTrail({
|
||||||
insertAuditTrail({
|
jobid: record.id,
|
||||||
jobid: record.id,
|
operation: AuditTrailMapping.jobstatuschange(key),
|
||||||
operation: AuditTrailMapping.jobstatuschange(key),
|
type: "jobstatuschange"
|
||||||
type: "jobstatuschange"
|
});
|
||||||
});
|
setLoading(false);
|
||||||
|
}
|
||||||
setLoading(false);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const menu = {
|
const menu = {
|
||||||
|
|||||||
@@ -1,89 +0,0 @@
|
|||||||
import { useMutation } from "@apollo/client";
|
|
||||||
import React, { useState } from "react";
|
|
||||||
import { connect } from "react-redux";
|
|
||||||
import { createStructuredSelector } from "reselect";
|
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
|
||||||
import { Button, Form, Input, notification, Popover, Space } from "antd";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import { UPDATE_SHOP } from "../../graphql/bodyshop.queries";
|
|
||||||
import { logImEXEvent } from "../../firebase/firebase.utils";
|
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
|
||||||
//currentUser: selectCurrentUser
|
|
||||||
bodyshop: selectBodyshop
|
|
||||||
});
|
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
|
||||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
|
||||||
});
|
|
||||||
|
|
||||||
export function ProductionListSaveConfigButton({ columns, bodyshop, tableState }) {
|
|
||||||
const [updateShop] = useMutation(UPDATE_SHOP);
|
|
||||||
const [loading, setLoading] = useState(false);
|
|
||||||
const [open, setOpen] = useState(false);
|
|
||||||
const [form] = Form.useForm();
|
|
||||||
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
const handleSaveConfig = async (values) => {
|
|
||||||
logImEXEvent("production_save_config");
|
|
||||||
setLoading(true);
|
|
||||||
const result = await updateShop({
|
|
||||||
variables: {
|
|
||||||
id: bodyshop.id,
|
|
||||||
shop: {
|
|
||||||
production_config: [
|
|
||||||
...bodyshop.production_config.filter((b) => b.name !== values.name),
|
|
||||||
//Assign it to the name
|
|
||||||
{
|
|
||||||
name: values.name,
|
|
||||||
columns: {
|
|
||||||
columnKeys: columns.map((i) => {
|
|
||||||
return { key: i.key, width: i.width };
|
|
||||||
}),
|
|
||||||
tableState
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (!!!result.errors) {
|
|
||||||
notification["success"]({ message: t("bodyshop.successes.save") });
|
|
||||||
} else {
|
|
||||||
notification["error"]({
|
|
||||||
message: t("bodyshop.errors.saving", {
|
|
||||||
error: JSON.stringify(result.errors)
|
|
||||||
})
|
|
||||||
});
|
|
||||||
}
|
|
||||||
form.resetFields();
|
|
||||||
setOpen(false);
|
|
||||||
setLoading(false);
|
|
||||||
};
|
|
||||||
const popMenu = (
|
|
||||||
<div>
|
|
||||||
<Form layout="vertical" form={form} onFinish={handleSaveConfig}>
|
|
||||||
<Form.Item label={t("production.labels.viewname")} name="name" rules={[{ required: true }]}>
|
|
||||||
<Input />
|
|
||||||
</Form.Item>
|
|
||||||
|
|
||||||
<Space wrap>
|
|
||||||
<Button type="primary" danger onClick={() => form.submit()} loading={loading}>
|
|
||||||
{t("general.actions.save")}
|
|
||||||
</Button>
|
|
||||||
<Button onClick={() => setOpen(false)}>{t("general.actions.close")}</Button>
|
|
||||||
</Space>
|
|
||||||
</Form>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Popover open={open} content={popMenu}>
|
|
||||||
<Button loading={loading} onClick={() => setOpen(true)}>
|
|
||||||
{t("production.actions.saveconfig")}
|
|
||||||
</Button>
|
|
||||||
</Popover>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(ProductionListSaveConfigButton);
|
|
||||||
@@ -0,0 +1,506 @@
|
|||||||
|
import { DeleteOutlined, ExclamationCircleOutlined, PlusOutlined } from "@ant-design/icons";
|
||||||
|
import { useMutation } from "@apollo/client";
|
||||||
|
import { Button, Form, Input, Modal, notification, Popconfirm, Popover, Select, Space } from "antd";
|
||||||
|
import React, { useEffect, useState } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { UPDATE_ACTIVE_PROD_LIST_VIEW } from "../../graphql/associations.queries";
|
||||||
|
import { UPDATE_SHOP } from "../../graphql/bodyshop.queries";
|
||||||
|
import ProductionListColumns from "../production-list-columns/production-list-columns.data";
|
||||||
|
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
||||||
|
import { logImEXEvent } from "../../firebase/firebase.utils";
|
||||||
|
import { isFunction } from "lodash";
|
||||||
|
|
||||||
|
const { confirm } = Modal;
|
||||||
|
|
||||||
|
export function ProductionListConfigManager({
|
||||||
|
refetch,
|
||||||
|
bodyshop,
|
||||||
|
technician,
|
||||||
|
currentUser,
|
||||||
|
state,
|
||||||
|
data,
|
||||||
|
columns,
|
||||||
|
setColumns,
|
||||||
|
setState,
|
||||||
|
onSave,
|
||||||
|
hasUnsavedChanges,
|
||||||
|
setHasUnsavedChanges
|
||||||
|
}) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const [updateDefaultProdView] = useMutation(UPDATE_ACTIVE_PROD_LIST_VIEW);
|
||||||
|
const [updateShop] = useMutation(UPDATE_SHOP);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
const [isAddingNewProfile, setIsAddingNewProfile] = useState(false);
|
||||||
|
const [form] = Form.useForm();
|
||||||
|
|
||||||
|
const [activeView, setActiveView] = useState(() => {
|
||||||
|
const assoc = bodyshop.associations.find((a) => a.useremail === currentUser.email);
|
||||||
|
return assoc && assoc.default_prod_list_view;
|
||||||
|
});
|
||||||
|
|
||||||
|
const defaultState = {
|
||||||
|
sortedInfo: {
|
||||||
|
columnKey: "ro_number",
|
||||||
|
order: null
|
||||||
|
},
|
||||||
|
filteredInfo: {}
|
||||||
|
};
|
||||||
|
|
||||||
|
const ensureDefaultState = (state) => {
|
||||||
|
return {
|
||||||
|
sortedInfo: state?.sortedInfo || defaultState.sortedInfo,
|
||||||
|
filteredInfo: state?.filteredInfo || defaultState.filteredInfo,
|
||||||
|
...state
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const createDefaultView = async () => {
|
||||||
|
const defaultConfig = {
|
||||||
|
name: t("production.constants.main_profile"),
|
||||||
|
columns: {
|
||||||
|
columnKeys: [
|
||||||
|
{ key: "ro_number", width: 100 },
|
||||||
|
{ key: "ownr", width: 100 },
|
||||||
|
{ key: "vehicle", width: 100 },
|
||||||
|
{ key: "ins_co_nm", width: 100 },
|
||||||
|
{ key: "actual_in", width: 100 },
|
||||||
|
{ key: "scheduled_completion", width: 100 },
|
||||||
|
{ key: "labhrs", width: 100 },
|
||||||
|
{ key: "employee_body", width: 100 },
|
||||||
|
{ key: "larhrs", width: 100 },
|
||||||
|
{ key: "employee_refinish", width: 100 },
|
||||||
|
{ key: "tt", width: 100 },
|
||||||
|
{ key: "status", width: 100 },
|
||||||
|
{ key: "sublets", width: 100 },
|
||||||
|
{ key: "viewdetail", width: 100 }
|
||||||
|
],
|
||||||
|
tableState: ensureDefaultState(state)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = await updateShop({
|
||||||
|
variables: {
|
||||||
|
id: bodyshop.id,
|
||||||
|
shop: {
|
||||||
|
production_config: [defaultConfig]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!result.errors) {
|
||||||
|
await updateActiveProdView(t("production.constants.main_profile"));
|
||||||
|
window.location.reload(); // Reload the page
|
||||||
|
} else {
|
||||||
|
notification.error({
|
||||||
|
message: t("bodyshop.errors.creatingdefaultview", {
|
||||||
|
error: JSON.stringify(result.errors)
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const {
|
||||||
|
treatments: { Enhanced_Payroll }
|
||||||
|
} = useSplitTreatments({
|
||||||
|
attributes: {},
|
||||||
|
names: ["Enhanced_Payroll"],
|
||||||
|
splitKey: bodyshop.imexshopid
|
||||||
|
});
|
||||||
|
|
||||||
|
const updateActiveProdView = async (viewName) => {
|
||||||
|
const assoc = bodyshop.associations.find((a) => a.useremail === currentUser.email);
|
||||||
|
if (assoc) {
|
||||||
|
await updateDefaultProdView({
|
||||||
|
variables: { assocId: assoc.id, view: viewName },
|
||||||
|
update(cache) {
|
||||||
|
cache.modify({
|
||||||
|
id: cache.identify(bodyshop),
|
||||||
|
fields: {
|
||||||
|
associations(existingAssociations) {
|
||||||
|
return existingAssociations.map((a) => {
|
||||||
|
if (a.useremail !== currentUser.email) return a;
|
||||||
|
return { ...a, default_prod_list_view: viewName };
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
setActiveView(viewName);
|
||||||
|
setHasUnsavedChanges(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSelect = async (value) => {
|
||||||
|
if (hasUnsavedChanges) {
|
||||||
|
confirm({
|
||||||
|
title: t("general.labels.unsavedchanges"),
|
||||||
|
icon: <ExclamationCircleOutlined />,
|
||||||
|
content: t("general.messages.unsavedchangespopup"),
|
||||||
|
onOk: () => proceedWithSelect(value),
|
||||||
|
onCancel() {
|
||||||
|
// Do nothing if canceled
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
await proceedWithSelect(value);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const proceedWithSelect = async (value) => {
|
||||||
|
if (value === "add_new") {
|
||||||
|
setIsAddingNewProfile(true);
|
||||||
|
setOpen(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectedConfig = bodyshop.production_config.find((pc) => pc.name === value);
|
||||||
|
|
||||||
|
// If the selected profile doesn't exist, revert to the main profile
|
||||||
|
if (!selectedConfig) {
|
||||||
|
const mainProfileConfig = bodyshop.production_config.find(
|
||||||
|
(pc) => pc.name === t("production.constants.main_profile")
|
||||||
|
);
|
||||||
|
|
||||||
|
if (mainProfileConfig) {
|
||||||
|
await updateActiveProdView(t("production.constants.main_profile"));
|
||||||
|
setColumns(
|
||||||
|
mainProfileConfig.columns.columnKeys.map((k) => {
|
||||||
|
return {
|
||||||
|
...ProductionListColumns({
|
||||||
|
bodyshop,
|
||||||
|
refetch,
|
||||||
|
technician,
|
||||||
|
state: ensureDefaultState(state),
|
||||||
|
data: data,
|
||||||
|
activeStatuses: bodyshop.md_ro_statuses.active_statuses,
|
||||||
|
treatments: { Enhanced_Payroll }
|
||||||
|
}).find((e) => e.key === k.key),
|
||||||
|
width: k.width
|
||||||
|
};
|
||||||
|
})
|
||||||
|
);
|
||||||
|
const newState = ensureDefaultState(mainProfileConfig.columns.tableState);
|
||||||
|
setState(newState);
|
||||||
|
|
||||||
|
if (onSave && isFunction(onSave)) {
|
||||||
|
onSave();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the selected profile exists, proceed as normal
|
||||||
|
if (selectedConfig) {
|
||||||
|
const newColumns = selectedConfig.columns.columnKeys.map((k) => {
|
||||||
|
return {
|
||||||
|
...ProductionListColumns({
|
||||||
|
bodyshop,
|
||||||
|
refetch,
|
||||||
|
technician,
|
||||||
|
state: ensureDefaultState(state),
|
||||||
|
data: data,
|
||||||
|
activeStatuses: bodyshop.md_ro_statuses.active_statuses,
|
||||||
|
treatments: { Enhanced_Payroll }
|
||||||
|
}).find((e) => e.key === k.key),
|
||||||
|
width: k.width
|
||||||
|
};
|
||||||
|
});
|
||||||
|
setColumns(newColumns);
|
||||||
|
const newState = ensureDefaultState(selectedConfig.columns.tableState);
|
||||||
|
setState(newState);
|
||||||
|
|
||||||
|
await updateActiveProdView(value);
|
||||||
|
if (onSave && isFunction(onSave)) {
|
||||||
|
onSave();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleTrash = async (name) => {
|
||||||
|
if (name === t("production.constants.main_profile")) return;
|
||||||
|
|
||||||
|
const remainingConfigs = bodyshop.production_config.filter((b) => b.name !== name);
|
||||||
|
|
||||||
|
await updateShop({
|
||||||
|
variables: {
|
||||||
|
id: bodyshop.id,
|
||||||
|
shop: {
|
||||||
|
production_config: remainingConfigs
|
||||||
|
}
|
||||||
|
},
|
||||||
|
awaitRefetchQueries: true
|
||||||
|
});
|
||||||
|
|
||||||
|
if (name === activeView) {
|
||||||
|
// Only switch profiles if the deleted profile was the active profile
|
||||||
|
if (remainingConfigs.length > 0) {
|
||||||
|
const nextConfig = remainingConfigs[0];
|
||||||
|
await updateActiveProdView(nextConfig.name);
|
||||||
|
setColumns(
|
||||||
|
nextConfig.columns.columnKeys.map((k) => {
|
||||||
|
return {
|
||||||
|
...ProductionListColumns({
|
||||||
|
technician,
|
||||||
|
state: ensureDefaultState(state),
|
||||||
|
refetch,
|
||||||
|
data: data,
|
||||||
|
activeStatuses: bodyshop.md_ro_statuses.active_statuses,
|
||||||
|
treatments: { Enhanced_Payroll }
|
||||||
|
}).find((e) => e.key === k.key),
|
||||||
|
width: k.width
|
||||||
|
};
|
||||||
|
})
|
||||||
|
);
|
||||||
|
setState(ensureDefaultState(nextConfig.columns.tableState));
|
||||||
|
} else {
|
||||||
|
await updateActiveProdView(null);
|
||||||
|
setColumns([]);
|
||||||
|
setState(defaultState);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Revert back to the active view and load its columns and state
|
||||||
|
const activeConfig = bodyshop.production_config.find((pc) => pc.name === activeView);
|
||||||
|
if (activeConfig) {
|
||||||
|
await updateActiveProdView(activeView);
|
||||||
|
setColumns(
|
||||||
|
activeConfig.columns.columnKeys.map((k) => {
|
||||||
|
return {
|
||||||
|
...ProductionListColumns({
|
||||||
|
technician,
|
||||||
|
state: ensureDefaultState(state),
|
||||||
|
refetch,
|
||||||
|
data: data,
|
||||||
|
activeStatuses: bodyshop.md_ro_statuses.active_statuses,
|
||||||
|
treatments: { Enhanced_Payroll }
|
||||||
|
}).find((e) => e.key === k.key),
|
||||||
|
width: k.width
|
||||||
|
};
|
||||||
|
})
|
||||||
|
);
|
||||||
|
setState(ensureDefaultState(activeConfig.columns.tableState));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSaveConfig = async (values) => {
|
||||||
|
logImEXEvent("production_save_config");
|
||||||
|
setLoading(true);
|
||||||
|
|
||||||
|
const profileName = isAddingNewProfile ? values.name : activeView;
|
||||||
|
|
||||||
|
const result = await updateShop({
|
||||||
|
variables: {
|
||||||
|
id: bodyshop.id,
|
||||||
|
shop: {
|
||||||
|
production_config: [
|
||||||
|
...bodyshop.production_config.filter((b) => b.name !== profileName),
|
||||||
|
{
|
||||||
|
name: profileName,
|
||||||
|
columns: {
|
||||||
|
columnKeys: columns.map((i) => ({ key: i.key, width: i.width })),
|
||||||
|
tableState: ensureDefaultState(state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!result.errors) {
|
||||||
|
notification.success({ message: t("bodyshop.successes.save") });
|
||||||
|
if (isAddingNewProfile) {
|
||||||
|
await updateActiveProdView(profileName);
|
||||||
|
}
|
||||||
|
if (onSave && isFunction(onSave)) {
|
||||||
|
onSave();
|
||||||
|
}
|
||||||
|
setHasUnsavedChanges(false);
|
||||||
|
} else {
|
||||||
|
notification.error({
|
||||||
|
message: t("bodyshop.errors.saving", {
|
||||||
|
error: JSON.stringify(result.errors)
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
form.resetFields();
|
||||||
|
setOpen(false);
|
||||||
|
setLoading(false);
|
||||||
|
setIsAddingNewProfile(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const validateAndSetDefaultView = () => {
|
||||||
|
const configExists = bodyshop.production_config.some((pc) => pc.name === activeView);
|
||||||
|
|
||||||
|
if (!configExists) {
|
||||||
|
// If the default view doesn't exist, revert to the main profile
|
||||||
|
const mainProfileConfig = bodyshop.production_config.find(
|
||||||
|
(pc) => pc.name === t("production.constants.main_profile")
|
||||||
|
);
|
||||||
|
|
||||||
|
if (mainProfileConfig) {
|
||||||
|
setActiveView(t("production.constants.main_profile"));
|
||||||
|
|
||||||
|
setColumns(
|
||||||
|
mainProfileConfig.columns.columnKeys.map((k) => {
|
||||||
|
return {
|
||||||
|
...ProductionListColumns({
|
||||||
|
bodyshop,
|
||||||
|
refetch,
|
||||||
|
technician,
|
||||||
|
state: ensureDefaultState(state),
|
||||||
|
data: data,
|
||||||
|
activeStatuses: bodyshop.md_ro_statuses.active_statuses,
|
||||||
|
treatments: { Enhanced_Payroll }
|
||||||
|
}).find((e) => e.key === k.key),
|
||||||
|
width: k.width
|
||||||
|
};
|
||||||
|
})
|
||||||
|
);
|
||||||
|
setState(ensureDefaultState(mainProfileConfig.columns.tableState));
|
||||||
|
|
||||||
|
updateActiveProdView(t("production.constants.main_profile"));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// If the default view exists, set it as active
|
||||||
|
setActiveView(activeView);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!bodyshop.production_config || bodyshop.production_config.length === 0) {
|
||||||
|
createDefaultView().catch((e) => {
|
||||||
|
console.error("Something went wrong saving the production list view Config.");
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
validateAndSetDefaultView();
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [activeView, bodyshop.production_config]);
|
||||||
|
|
||||||
|
const popMenu = (
|
||||||
|
<div>
|
||||||
|
<Form layout="vertical" form={form} onFinish={handleSaveConfig}>
|
||||||
|
{isAddingNewProfile && (
|
||||||
|
<Form.Item
|
||||||
|
label={t("production.labels.viewname")}
|
||||||
|
name="name"
|
||||||
|
rules={[
|
||||||
|
{ required: true, message: t("production.errors.name_required") },
|
||||||
|
{
|
||||||
|
validator: (_, value) => {
|
||||||
|
if (!value) {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
const nameExists = bodyshop.production_config.some((pc) => pc.name === value);
|
||||||
|
if (nameExists) {
|
||||||
|
return Promise.reject(new Error(t("production.errors.name_exists")));
|
||||||
|
}
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Input />
|
||||||
|
</Form.Item>
|
||||||
|
)}
|
||||||
|
<Space wrap>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
danger
|
||||||
|
onClick={() => form.submit()}
|
||||||
|
loading={loading}
|
||||||
|
disabled={form.getFieldsError().some(({ errors }) => errors.length)}
|
||||||
|
>
|
||||||
|
{t("general.actions.save")}
|
||||||
|
</Button>
|
||||||
|
{!isAddingNewProfile && (
|
||||||
|
<Button
|
||||||
|
type="default"
|
||||||
|
onClick={() => {
|
||||||
|
setIsAddingNewProfile(true);
|
||||||
|
setOpen(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t("general.actions.saveas")}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
setIsAddingNewProfile(false);
|
||||||
|
setOpen(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t("general.actions.cancel")}
|
||||||
|
</Button>
|
||||||
|
</Space>
|
||||||
|
</Form>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Space>
|
||||||
|
<Button loading={loading} onClick={() => setOpen(true)} disabled={isAddingNewProfile || !hasUnsavedChanges}>
|
||||||
|
{t("production.actions.saveconfig")}
|
||||||
|
</Button>
|
||||||
|
<Popover open={open} content={popMenu} placement="bottom">
|
||||||
|
<Select
|
||||||
|
style={{
|
||||||
|
minWidth: "150px"
|
||||||
|
}}
|
||||||
|
onSelect={handleSelect}
|
||||||
|
placeholder={t("production.labels.selectview")}
|
||||||
|
optionLabelProp="label"
|
||||||
|
popupMatchSelectWidth={false}
|
||||||
|
value={activeView}
|
||||||
|
disabled={open || isAddingNewProfile} // Disable the Select box when the popover is open or adding a new profile
|
||||||
|
>
|
||||||
|
{bodyshop?.production_config &&
|
||||||
|
bodyshop.production_config
|
||||||
|
.slice()
|
||||||
|
.sort((a, b) =>
|
||||||
|
a.name === t("production.constants.main_profile")
|
||||||
|
? -1
|
||||||
|
: b.name === t("production.constants.main_profile")
|
||||||
|
? 1
|
||||||
|
: 0
|
||||||
|
) //
|
||||||
|
.map((config) => (
|
||||||
|
<Select.Option key={config.name} label={config.name}>
|
||||||
|
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
flex: 1,
|
||||||
|
maxWidth: "80%",
|
||||||
|
marginRight: "1rem",
|
||||||
|
textOverflow: "ellipsis"
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{config.name}
|
||||||
|
</span>
|
||||||
|
{config.name !== t("production.constants.main_profile") && (
|
||||||
|
<Popconfirm
|
||||||
|
placement="right"
|
||||||
|
title={t("general.labels.areyousure")}
|
||||||
|
onConfirm={() => handleTrash(config.name)}
|
||||||
|
onCancel={(e) => e.stopPropagation()}
|
||||||
|
>
|
||||||
|
<DeleteOutlined onClick={(e) => e.stopPropagation()} />
|
||||||
|
</Popconfirm>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</Select.Option>
|
||||||
|
))}
|
||||||
|
<Select.Option key="add_new" label={t("production.labels.addnewprofile")}>
|
||||||
|
<div style={{ display: "flex", alignItems: "center" }}>
|
||||||
|
<PlusOutlined style={{ marginRight: "0.5rem" }} />
|
||||||
|
{t("production.labels.addnewprofile")}
|
||||||
|
</div>
|
||||||
|
</Select.Option>
|
||||||
|
</Select>
|
||||||
|
</Popover>
|
||||||
|
</Space>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,157 +0,0 @@
|
|||||||
import { DeleteOutlined } from "@ant-design/icons";
|
|
||||||
import { useMutation } from "@apollo/client";
|
|
||||||
import { Popconfirm, Select } from "antd";
|
|
||||||
import React from "react";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import { connect } from "react-redux";
|
|
||||||
import { createStructuredSelector } from "reselect";
|
|
||||||
import { UPDATE_ACTIVE_PROD_LIST_VIEW } from "../../graphql/associations.queries";
|
|
||||||
import { UPDATE_SHOP } from "../../graphql/bodyshop.queries";
|
|
||||||
import { selectTechnician } from "../../redux/tech/tech.selectors";
|
|
||||||
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors";
|
|
||||||
import ProductionListColumns from "../production-list-columns/production-list-columns.data";
|
|
||||||
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
|
||||||
bodyshop: selectBodyshop,
|
|
||||||
technician: selectTechnician,
|
|
||||||
currentUser: selectCurrentUser
|
|
||||||
});
|
|
||||||
|
|
||||||
export function ProductionListTable({ refetch, bodyshop, technician, currentUser, state, data, setColumns, setState }) {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const [updateDefaultProdView] = useMutation(UPDATE_ACTIVE_PROD_LIST_VIEW);
|
|
||||||
const [updateShop] = useMutation(UPDATE_SHOP);
|
|
||||||
|
|
||||||
const {
|
|
||||||
treatments: { Enhanced_Payroll }
|
|
||||||
} = useSplitTreatments({
|
|
||||||
attributes: {},
|
|
||||||
names: ["Enhanced_Payroll"],
|
|
||||||
splitKey: bodyshop.imexshopid
|
|
||||||
});
|
|
||||||
|
|
||||||
const handleSelect = async (value, option) => {
|
|
||||||
setColumns(
|
|
||||||
bodyshop.production_config
|
|
||||||
.filter((pc) => pc.name === value)[0]
|
|
||||||
.columns.columnKeys.map((k) => {
|
|
||||||
return {
|
|
||||||
...ProductionListColumns({
|
|
||||||
bodyshop,
|
|
||||||
refetch,
|
|
||||||
technician,
|
|
||||||
state,
|
|
||||||
data: data,
|
|
||||||
activeStatuses: bodyshop.md_ro_statuses.active_statuses,
|
|
||||||
treatments: { Enhanced_Payroll }
|
|
||||||
}).find((e) => e.key === k.key),
|
|
||||||
width: k.width
|
|
||||||
};
|
|
||||||
})
|
|
||||||
);
|
|
||||||
setState(bodyshop.production_config.filter((pc) => pc.name === value)[0].columns.tableState);
|
|
||||||
|
|
||||||
const assoc = bodyshop.associations.find((a) => a.useremail === currentUser.email);
|
|
||||||
|
|
||||||
if (assoc) {
|
|
||||||
await updateDefaultProdView({
|
|
||||||
variables: { assocId: assoc.id, view: value },
|
|
||||||
update(cache) {
|
|
||||||
cache.modify({
|
|
||||||
id: cache.identify(bodyshop),
|
|
||||||
fields: {
|
|
||||||
associations(existingAssociations, { readField }) {
|
|
||||||
return existingAssociations.map((a) => {
|
|
||||||
if (a.useremail !== currentUser.email) return a;
|
|
||||||
return { ...a, default_prod_list_view: value };
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleTrash = async (name) => {
|
|
||||||
await updateShop({
|
|
||||||
variables: {
|
|
||||||
id: bodyshop.id,
|
|
||||||
shop: {
|
|
||||||
production_config: bodyshop.production_config.filter((b) => b.name !== name)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
awaitRefetchQueries: true
|
|
||||||
});
|
|
||||||
|
|
||||||
setColumns(
|
|
||||||
bodyshop.production_config[0].columns.columnKeys.map((k) => {
|
|
||||||
return {
|
|
||||||
...ProductionListColumns({
|
|
||||||
technician,
|
|
||||||
state,
|
|
||||||
refetch,
|
|
||||||
data: data,
|
|
||||||
activeStatuses: bodyshop.md_ro_statuses.active_statuses,
|
|
||||||
treatments: { Enhanced_Payroll }
|
|
||||||
}).find((e) => e.key === k.key),
|
|
||||||
width: k.width
|
|
||||||
};
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
setState(bodyshop.production_config[0].columns.tableState);
|
|
||||||
};
|
|
||||||
const assoc = bodyshop.associations.find((a) => a.useremail === currentUser.email);
|
|
||||||
|
|
||||||
const defaultView = assoc && assoc.default_prod_list_view;
|
|
||||||
return (
|
|
||||||
<div style={{ width: "10rem" }}>
|
|
||||||
<Select
|
|
||||||
onSelect={handleSelect}
|
|
||||||
placeholder={t("production.labels.selectview")}
|
|
||||||
optionLabelProp="label"
|
|
||||||
popupMatchSelectWidth={false}
|
|
||||||
defaultValue={defaultView}
|
|
||||||
>
|
|
||||||
{bodyshop.production_config.map((config) => (
|
|
||||||
<Select.Option key={config.name} label={config.name}>
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
display: "flex",
|
|
||||||
justifyContent: "space-between",
|
|
||||||
alignItems: "center"
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
style={{
|
|
||||||
flex: 1,
|
|
||||||
maxWidth: "80%",
|
|
||||||
marginRight: "1rem",
|
|
||||||
textOverflow: "ellipsis"
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{config.name}
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<Popconfirm
|
|
||||||
placement="right"
|
|
||||||
title={t("general.labels.areyousure")}
|
|
||||||
onConfirm={() => handleTrash(config.name)}
|
|
||||||
>
|
|
||||||
<DeleteOutlined
|
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Popconfirm>
|
|
||||||
</div>
|
|
||||||
</Select.Option>
|
|
||||||
))}
|
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default connect(mapStateToProps, null)(ProductionListTable);
|
|
||||||
@@ -1,20 +1,22 @@
|
|||||||
import { SyncOutlined } from "@ant-design/icons";
|
import { SyncOutlined } from "@ant-design/icons";
|
||||||
|
import { PageHeader } from "@ant-design/pro-layout";
|
||||||
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
||||||
import { Button, Dropdown, Input, Space, Statistic, Table } from "antd";
|
import { Button, Dropdown, Input, Space, Statistic, Table } from "antd";
|
||||||
import { PageHeader } from "@ant-design/pro-layout";
|
import _ from "lodash";
|
||||||
import React, { useEffect, useMemo, useState } from "react";
|
import React, { useEffect, useMemo, useRef, useState } from "react";
|
||||||
import ReactDragListView from "react-drag-listview";
|
import ReactDragListView from "react-drag-listview";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import { selectTechnician } from "../../redux/tech/tech.selectors";
|
import { selectTechnician } from "../../redux/tech/tech.selectors";
|
||||||
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors";
|
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors";
|
||||||
|
import Prompt from "../../utils/prompt.js";
|
||||||
|
import AlertComponent from "../alert/alert.component.jsx";
|
||||||
import ProductionListColumnsAdd from "../production-list-columns/production-list-columns.add.component";
|
import ProductionListColumnsAdd from "../production-list-columns/production-list-columns.add.component";
|
||||||
import ProductionListColumns from "../production-list-columns/production-list-columns.data";
|
import ProductionListColumns from "../production-list-columns/production-list-columns.data";
|
||||||
import ProductionListDetail from "../production-list-detail/production-list-detail.component";
|
import ProductionListDetail from "../production-list-detail/production-list-detail.component";
|
||||||
import ProductionListSaveConfigButton from "../production-list-save-config-button/production-list-save-config-button.component";
|
import { ProductionListConfigManager } from "./production-list-config-manager.component.jsx";
|
||||||
import ProductionListPrint from "./production-list-print.component";
|
import ProductionListPrint from "./production-list-print.component";
|
||||||
import ProductionListTableViewSelect from "./production-list-table-view-select.component";
|
|
||||||
import ResizeableTitle from "./production-list-table.resizeable.component";
|
import ResizeableTitle from "./production-list-table.resizeable.component";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
@@ -25,6 +27,7 @@ const mapStateToProps = createStructuredSelector({
|
|||||||
|
|
||||||
export function ProductionListTable({ loading, data, refetch, bodyshop, technician, currentUser }) {
|
export function ProductionListTable({ loading, data, refetch, bodyshop, technician, currentUser }) {
|
||||||
const [searchText, setSearchText] = useState("");
|
const [searchText, setSearchText] = useState("");
|
||||||
|
const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
treatments: { Production_List_Status_Colors, Enhanced_Payroll }
|
treatments: { Production_List_Status_Colors, Enhanced_Payroll }
|
||||||
@@ -35,92 +38,113 @@ export function ProductionListTable({ loading, data, refetch, bodyshop, technici
|
|||||||
});
|
});
|
||||||
|
|
||||||
const assoc = bodyshop.associations.find((a) => a.useremail === currentUser.email);
|
const assoc = bodyshop.associations.find((a) => a.useremail === currentUser.email);
|
||||||
|
|
||||||
const defaultView = assoc && assoc.default_prod_list_view;
|
const defaultView = assoc && assoc.default_prod_list_view;
|
||||||
|
|
||||||
const [state, setState] = useState(
|
const initialStateRef = useRef(
|
||||||
(bodyshop.production_config &&
|
(bodyshop.production_config &&
|
||||||
bodyshop.production_config.find((p) => p.name === defaultView)?.columns.tableState) ||
|
bodyshop.production_config.find((p) => p.name === defaultView)?.columns.tableState) ||
|
||||||
bodyshop.production_config[0]?.columns.tableState || {
|
(bodyshop.production_config && bodyshop.production_config[0]?.columns.tableState) || {
|
||||||
sortedInfo: {},
|
sortedInfo: {},
|
||||||
filteredInfo: { text: "" }
|
filteredInfo: { text: "" }
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const { t } = useTranslation();
|
const initialColumnsRef = useRef(
|
||||||
|
(initialStateRef.current &&
|
||||||
const matchingColumnConfig = useMemo(() => {
|
bodyshop?.production_config
|
||||||
return bodyshop.production_config.find((p) => p.name === defaultView);
|
?.find((p) => p.name === defaultView)
|
||||||
}, [bodyshop.production_config, defaultView]);
|
?.columns.columnKeys.map((k) => {
|
||||||
|
|
||||||
const [columns, setColumns] = useState(
|
|
||||||
(state &&
|
|
||||||
matchingColumnConfig &&
|
|
||||||
matchingColumnConfig.columns.columnKeys.map((k) => {
|
|
||||||
return {
|
|
||||||
...ProductionListColumns({
|
|
||||||
bodyshop,
|
|
||||||
refetch,
|
|
||||||
technician,
|
|
||||||
state,
|
|
||||||
data,
|
|
||||||
activeStatuses: bodyshop.md_ro_statuses.active_statuses,
|
|
||||||
treatments: { Production_List_Status_Colors, Enhanced_Payroll }
|
|
||||||
}).find((e) => e.key === k.key),
|
|
||||||
width: k.width ?? 100
|
|
||||||
};
|
|
||||||
})) ||
|
|
||||||
[]
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const newColumns =
|
|
||||||
(state &&
|
|
||||||
matchingColumnConfig &&
|
|
||||||
matchingColumnConfig.columns.columnKeys.map((k) => {
|
|
||||||
return {
|
return {
|
||||||
...ProductionListColumns({
|
...ProductionListColumns({
|
||||||
bodyshop,
|
bodyshop,
|
||||||
technician,
|
|
||||||
refetch,
|
refetch,
|
||||||
state,
|
technician,
|
||||||
data: data,
|
state: initialStateRef.current,
|
||||||
|
data,
|
||||||
activeStatuses: bodyshop.md_ro_statuses.active_statuses,
|
activeStatuses: bodyshop.md_ro_statuses.active_statuses,
|
||||||
treatments: { Production_List_Status_Colors, Enhanced_Payroll }
|
treatments: { Production_List_Status_Colors, Enhanced_Payroll }
|
||||||
}).find((e) => e.key === k.key),
|
}).find((e) => e.key === k.key),
|
||||||
width: k.width ?? 100
|
width: k.width ?? 100
|
||||||
};
|
};
|
||||||
})) ||
|
})) ||
|
||||||
[];
|
[]
|
||||||
setColumns(newColumns);
|
);
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
|
const [state, setState] = useState(initialStateRef.current);
|
||||||
|
const [columns, setColumns] = useState(initialColumnsRef.current);
|
||||||
|
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const matchingColumnConfig = useMemo(() => {
|
||||||
|
return bodyshop?.production_config?.find((p) => p.name === defaultView);
|
||||||
|
}, [bodyshop.production_config]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const newColumns =
|
||||||
|
matchingColumnConfig?.columns.columnKeys.map((k) => {
|
||||||
|
return {
|
||||||
|
...ProductionListColumns({
|
||||||
|
bodyshop,
|
||||||
|
technician,
|
||||||
|
refetch,
|
||||||
|
state,
|
||||||
|
data: data,
|
||||||
|
activeStatuses: bodyshop.md_ro_statuses.active_statuses,
|
||||||
|
treatments: { Production_List_Status_Colors, Enhanced_Payroll }
|
||||||
|
}).find((e) => e.key === k.key),
|
||||||
|
width: k.width ?? 100
|
||||||
|
};
|
||||||
|
}) || [];
|
||||||
|
|
||||||
|
// Only update columns if they haven't been manually changed by the user
|
||||||
|
if (_.isEqual(initialColumnsRef.current, columns)) {
|
||||||
|
setColumns(newColumns);
|
||||||
|
}
|
||||||
}, [
|
}, [
|
||||||
//state,
|
|
||||||
matchingColumnConfig,
|
matchingColumnConfig,
|
||||||
bodyshop,
|
bodyshop,
|
||||||
technician,
|
technician,
|
||||||
data
|
data,
|
||||||
]); //State removed from dependency array as it causes race condition when removing columns from table view and is not needed.
|
Enhanced_Payroll,
|
||||||
|
Production_List_Status_Colors,
|
||||||
|
refetch,
|
||||||
|
state,
|
||||||
|
columns
|
||||||
|
]);
|
||||||
|
|
||||||
const handleTableChange = (pagination, filters, sorter) => {
|
const handleTableChange = (pagination, filters, sorter) => {
|
||||||
setState({
|
const newState = {
|
||||||
...state,
|
...state,
|
||||||
filteredInfo: filters,
|
filteredInfo: filters,
|
||||||
sortedInfo: { columnKey: sorter.columnKey, order: sorter.order }
|
sortedInfo: { columnKey: sorter.columnKey, order: sorter.order }
|
||||||
});
|
};
|
||||||
|
if (!_.isEqual(newState, state)) {
|
||||||
|
setState(newState);
|
||||||
|
setHasUnsavedChanges(true);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const onDragEnd = (fromIndex, toIndex) => {
|
const onDragEnd = (fromIndex, toIndex) => {
|
||||||
const columnsCopy = columns.slice();
|
if (fromIndex === toIndex) return;
|
||||||
const item = columnsCopy.splice(fromIndex, 1)[0];
|
|
||||||
columnsCopy.splice(toIndex, 0, item);
|
const columnsCopy = [...columns];
|
||||||
setColumns(columnsCopy);
|
const [movedItem] = columnsCopy.splice(fromIndex, 1);
|
||||||
|
columnsCopy.splice(toIndex, 0, movedItem);
|
||||||
|
|
||||||
|
if (!_.isEqual(columnsCopy, columns)) {
|
||||||
|
setColumns(columnsCopy);
|
||||||
|
setHasUnsavedChanges(true);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const removeColumn = (e) => {
|
const removeColumn = (e) => {
|
||||||
const { key } = e;
|
const { key } = e;
|
||||||
const newColumns = columns.filter((i) => i.key !== key);
|
const newColumns = columns.filter((i) => i.key !== key);
|
||||||
setColumns(newColumns);
|
|
||||||
|
if (!_.isEqual(newColumns, columns)) {
|
||||||
|
setColumns(newColumns);
|
||||||
|
setHasUnsavedChanges(true);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleResize =
|
const handleResize =
|
||||||
@@ -131,9 +155,21 @@ export function ProductionListTable({ loading, data, refetch, bodyshop, technici
|
|||||||
...nextColumns[index],
|
...nextColumns[index],
|
||||||
width: size.width
|
width: size.width
|
||||||
};
|
};
|
||||||
setColumns(nextColumns);
|
|
||||||
|
if (!_.isEqual(nextColumns, columns)) {
|
||||||
|
setColumns(nextColumns);
|
||||||
|
setHasUnsavedChanges(true);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const addColumn = (newColumn) => {
|
||||||
|
const updatedColumns = [...columns, newColumn];
|
||||||
|
if (!_.isEqual(updatedColumns, columns)) {
|
||||||
|
setColumns(updatedColumns);
|
||||||
|
setHasUnsavedChanges(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const headerItem = (col) => {
|
const headerItem = (col) => {
|
||||||
const menu = {
|
const menu = {
|
||||||
onClick: removeColumn,
|
onClick: removeColumn,
|
||||||
@@ -152,29 +188,29 @@ export function ProductionListTable({ loading, data, refetch, bodyshop, technici
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const dataSource =
|
const resetChanges = () => {
|
||||||
searchText === ""
|
setState(initialStateRef.current);
|
||||||
? data
|
setColumns(initialColumnsRef.current);
|
||||||
: data.filter(
|
setHasUnsavedChanges(false);
|
||||||
(j) =>
|
};
|
||||||
(j.ro_number || "").toString().toLowerCase().includes(searchText.toLowerCase()) ||
|
|
||||||
(j.ownr_co_nm || "").toLowerCase().includes(searchText.toLowerCase()) ||
|
|
||||||
(j.ownr_fn || "").toLowerCase().includes(searchText.toLowerCase()) ||
|
|
||||||
(j.ownr_ln || "").toLowerCase().includes(searchText.toLowerCase()) ||
|
|
||||||
(j.status || "").toLowerCase().includes(searchText.toLowerCase()) ||
|
|
||||||
(j.ins_co_nm || "").toLowerCase().includes(searchText.toLowerCase()) ||
|
|
||||||
(j.clm_no || "").toLowerCase().includes(searchText.toLowerCase()) ||
|
|
||||||
(j.v_model_desc || "").toLowerCase().includes(searchText.toLowerCase()) ||
|
|
||||||
(j.v_make_desc || "").toLowerCase().includes(searchText.toLowerCase())
|
|
||||||
);
|
|
||||||
|
|
||||||
// const handleSelectRecord = (record) => {
|
const filterData = (item, searchText) => {
|
||||||
// if (selected !== record.id) {
|
const fieldsToSearch = [
|
||||||
// setSelected(record.id);
|
item.ro_number,
|
||||||
// } else {
|
item.ownr_co_nm,
|
||||||
// setSelected(null);
|
item.ownr_fn,
|
||||||
// }
|
item.ownr_ln,
|
||||||
// };
|
item.status,
|
||||||
|
item.ins_co_nm,
|
||||||
|
item.clm_no,
|
||||||
|
item.v_model_desc,
|
||||||
|
item.v_make_desc
|
||||||
|
];
|
||||||
|
|
||||||
|
return fieldsToSearch.some((field) => (field || "").toString().toLowerCase().includes(searchText.toLowerCase()));
|
||||||
|
};
|
||||||
|
|
||||||
|
const dataSource = searchText === "" ? data : data.filter((j) => filterData(j, searchText));
|
||||||
|
|
||||||
if (!!!columns) return <div>No columns found.</div>;
|
if (!!!columns) return <div>No columns found.</div>;
|
||||||
|
|
||||||
@@ -186,8 +222,29 @@ export function ProductionListTable({ loading, data, refetch, bodyshop, technici
|
|||||||
.toFixed(1);
|
.toFixed(1);
|
||||||
const totalLAB = data.reduce((acc, val) => acc + (val.labhrs?.aggregate?.sum?.mod_lb_hrs || 0), 0).toFixed(1);
|
const totalLAB = data.reduce((acc, val) => acc + (val.labhrs?.aggregate?.sum?.mod_lb_hrs || 0), 0).toFixed(1);
|
||||||
const totalLAR = data.reduce((acc, val) => acc + (val.larhrs?.aggregate?.sum?.mod_lb_hrs || 0), 0).toFixed(1);
|
const totalLAR = data.reduce((acc, val) => acc + (val.larhrs?.aggregate?.sum?.mod_lb_hrs || 0), 0).toFixed(1);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
<Prompt when={hasUnsavedChanges} beforeUnload={true} message={t("general.messages.unsavedchangespopup")} />
|
||||||
|
{hasUnsavedChanges && (
|
||||||
|
<AlertComponent
|
||||||
|
type="warning"
|
||||||
|
message={
|
||||||
|
<div>
|
||||||
|
<span>{t("general.messages.unsavedchanges")} </span>
|
||||||
|
<span
|
||||||
|
onClick={resetChanges}
|
||||||
|
style={{
|
||||||
|
cursor: "pointer",
|
||||||
|
textDecoration: "underline"
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{t("general.actions.reset")}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<PageHeader
|
<PageHeader
|
||||||
title={
|
title={
|
||||||
<Space>
|
<Space>
|
||||||
@@ -199,20 +256,37 @@ export function ProductionListTable({ loading, data, refetch, bodyshop, technici
|
|||||||
}
|
}
|
||||||
extra={
|
extra={
|
||||||
<Space wrap>
|
<Space wrap>
|
||||||
<Button onClick={() => refetch && refetch()}>
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
refetch && refetch();
|
||||||
|
}}
|
||||||
|
>
|
||||||
<SyncOutlined />
|
<SyncOutlined />
|
||||||
</Button>
|
</Button>
|
||||||
<ProductionListColumnsAdd columnState={[columns, setColumns]} tableState={state} data={data} />
|
<ProductionListColumnsAdd
|
||||||
<ProductionListSaveConfigButton columns={columns} tableState={state} />
|
columnState={[columns, setColumns]}
|
||||||
|
tableState={state}
|
||||||
<ProductionListTableViewSelect
|
|
||||||
state={state}
|
|
||||||
setState={setState}
|
|
||||||
setColumns={setColumns}
|
|
||||||
refetch={refetch}
|
|
||||||
data={data}
|
data={data}
|
||||||
|
onColumnAdd={addColumn}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<ProductionListConfigManager
|
||||||
|
columns={columns}
|
||||||
|
setColumns={setColumns}
|
||||||
|
state={state}
|
||||||
|
setState={setState}
|
||||||
|
refetch={refetch}
|
||||||
|
data={data}
|
||||||
|
bodyshop={bodyshop}
|
||||||
|
technician={technician}
|
||||||
|
currentUser={currentUser}
|
||||||
|
setHasUnsavedChanges={setHasUnsavedChanges}
|
||||||
|
hasUnsavedChanges={hasUnsavedChanges}
|
||||||
|
onSave={() => {
|
||||||
|
setHasUnsavedChanges(false);
|
||||||
|
initialStateRef.current = state;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
<Input
|
<Input
|
||||||
onChange={(e) => setSearchText(e.target.value)}
|
onChange={(e) => setSearchText(e.target.value)}
|
||||||
placeholder={t("general.labels.search")}
|
placeholder={t("general.labels.search")}
|
||||||
|
|||||||
@@ -1,24 +1,54 @@
|
|||||||
import { useApolloClient, useQuery, useSubscription } from "@apollo/client";
|
import { useApolloClient, useQuery, useSubscription } from "@apollo/client";
|
||||||
import React, { useEffect, useState } from "react";
|
import React, { useContext, useEffect, useState, useRef } from "react";
|
||||||
import {
|
import {
|
||||||
QUERY_EXACT_JOB_IN_PRODUCTION,
|
QUERY_EXACT_JOB_IN_PRODUCTION,
|
||||||
QUERY_EXACT_JOBS_IN_PRODUCTION,
|
QUERY_EXACT_JOBS_IN_PRODUCTION,
|
||||||
QUERY_JOBS_IN_PRODUCTION,
|
QUERY_JOBS_IN_PRODUCTION,
|
||||||
SUBSCRIPTION_JOBS_IN_PRODUCTION
|
SUBSCRIPTION_JOBS_IN_PRODUCTION,
|
||||||
|
SUBSCRIPTION_JOBS_IN_PRODUCTION_VIEW
|
||||||
} from "../../graphql/jobs.queries";
|
} from "../../graphql/jobs.queries";
|
||||||
import ProductionListTable from "./production-list-table.component";
|
import ProductionListTable from "./production-list-table.component";
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
|
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
||||||
|
import SocketContext from "../../contexts/SocketIO/socketContext.jsx";
|
||||||
|
|
||||||
export default function ProductionListTableContainer() {
|
export default function ProductionListTableContainer({ bodyshop, subscriptionType = "direct" }) {
|
||||||
|
const client = useApolloClient();
|
||||||
|
const { socket } = useContext(SocketContext);
|
||||||
|
const [joblist, setJoblist] = useState([]);
|
||||||
|
const reconnectTimeout = useRef(null); // To store the reconnect timeout
|
||||||
|
const disconnectTime = useRef(null); // To store the time of disconnection
|
||||||
|
|
||||||
|
const acceptableReconnectTime = 2000; // 2 seconds threshold
|
||||||
|
|
||||||
|
// Get Split treatment
|
||||||
|
const {
|
||||||
|
treatments: { Websocket_Production }
|
||||||
|
} = useSplitTreatments({
|
||||||
|
attributes: {},
|
||||||
|
names: ["Websocket_Production"],
|
||||||
|
splitKey: bodyshop && bodyshop.imexshopid
|
||||||
|
});
|
||||||
|
|
||||||
|
// Determine if subscription is enabled
|
||||||
|
const subscriptionEnabled = Websocket_Production?.treatment === "off";
|
||||||
|
|
||||||
|
// Use GraphQL query
|
||||||
const { refetch, loading, data } = useQuery(QUERY_JOBS_IN_PRODUCTION, {
|
const { refetch, loading, data } = useQuery(QUERY_JOBS_IN_PRODUCTION, {
|
||||||
pollInterval: 3600000,
|
pollInterval: 3600000,
|
||||||
fetchPolicy: "network-only",
|
fetchPolicy: "network-only",
|
||||||
nextFetchPolicy: "network-only"
|
nextFetchPolicy: "network-only"
|
||||||
});
|
});
|
||||||
const client = useApolloClient();
|
|
||||||
const [joblist, setJoblist] = useState([]);
|
|
||||||
const { data: updatedJobs } = useSubscription(SUBSCRIPTION_JOBS_IN_PRODUCTION);
|
|
||||||
|
|
||||||
|
// Use GraphQL subscription when subscription is enabled
|
||||||
|
const { data: updatedJobs } = useSubscription(
|
||||||
|
subscriptionType === "view" ? SUBSCRIPTION_JOBS_IN_PRODUCTION_VIEW : SUBSCRIPTION_JOBS_IN_PRODUCTION,
|
||||||
|
{
|
||||||
|
skip: !subscriptionEnabled
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Update joblist when data changes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!(data && data.jobs)) return;
|
if (!(data && data.jobs)) return;
|
||||||
setJoblist(
|
setJoblist(
|
||||||
@@ -28,34 +58,134 @@ export default function ProductionListTableContainer() {
|
|||||||
);
|
);
|
||||||
}, [data]);
|
}, [data]);
|
||||||
|
|
||||||
|
// Handle updates from GraphQL subscription
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!updatedJobs || joblist.length === 0) return;
|
if (subscriptionEnabled) {
|
||||||
|
if (!updatedJobs || joblist.length === 0) return;
|
||||||
|
|
||||||
const jobDiff = _.differenceWith(
|
const jobDiff = _.differenceWith(
|
||||||
joblist,
|
joblist,
|
||||||
updatedJobs.jobs,
|
updatedJobs.jobs,
|
||||||
(a, b) => a.id === b.id && a.updated_at === b.updated_at
|
(a, b) => a.id === b.id && a.updated_at === b.updated_at
|
||||||
);
|
);
|
||||||
|
|
||||||
if (jobDiff.length > 1) {
|
if (jobDiff.length > 1) {
|
||||||
getUpdatedJobsData(jobDiff.map((j) => j.id));
|
getUpdatedJobsData(jobDiff.map((j) => j.id));
|
||||||
} else if (jobDiff.length === 1) {
|
} else if (jobDiff.length === 1) {
|
||||||
jobDiff.forEach((job) => {
|
jobDiff.forEach((job) => {
|
||||||
getUpdatedJobData(job.id);
|
getUpdatedJobData(job.id);
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
setJoblist(updatedJobs.jobs);
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [updatedJobs, subscriptionEnabled]);
|
||||||
|
|
||||||
|
// Handle updates from Socket.IO when subscription is disabled
|
||||||
|
useEffect(() => {
|
||||||
|
if (subscriptionEnabled || !socket || !bodyshop || !bodyshop.id) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setJoblist(updatedJobs.jobs);
|
const handleJobUpdates = async (jobChangedData) => {
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
const jobId = jobChangedData.id;
|
||||||
}, [updatedJobs]);
|
|
||||||
|
|
||||||
|
// Access the existing cache for QUERY_JOBS_IN_PRODUCTION
|
||||||
|
const existingJobsCache = client.readQuery({
|
||||||
|
query: QUERY_JOBS_IN_PRODUCTION
|
||||||
|
});
|
||||||
|
|
||||||
|
const existingJobs = existingJobsCache?.jobs || [];
|
||||||
|
|
||||||
|
// Check if the job already exists in the cached jobs
|
||||||
|
const existingJob = existingJobs.find((job) => job.id === jobId);
|
||||||
|
|
||||||
|
if (existingJob) {
|
||||||
|
// If the job exists, we update the cache without making any additional queries
|
||||||
|
client.writeQuery({
|
||||||
|
query: QUERY_JOBS_IN_PRODUCTION,
|
||||||
|
data: {
|
||||||
|
jobs: existingJobs.map((job) =>
|
||||||
|
job.id === jobId ? { ...existingJob, ...jobChangedData, __typename: "jobs" } : job
|
||||||
|
)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// If the job doesn't exist, fetch it from the server and then add it to the cache
|
||||||
|
try {
|
||||||
|
const { data: jobData } = await client.query({
|
||||||
|
query: QUERY_EXACT_JOB_IN_PRODUCTION,
|
||||||
|
variables: { id: jobId },
|
||||||
|
fetchPolicy: "network-only"
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add the job to the existing cached jobs
|
||||||
|
client.writeQuery({
|
||||||
|
query: QUERY_JOBS_IN_PRODUCTION,
|
||||||
|
data: {
|
||||||
|
jobs: [...existingJobs, { ...jobData.job, __typename: "jobs" }]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error fetching job ${jobId}: ${error.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDisconnect = () => {
|
||||||
|
// Capture the time when the disconnection happens
|
||||||
|
disconnectTime.current = Date.now();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleReconnect = () => {
|
||||||
|
// Calculate how long the disconnection lasted
|
||||||
|
const reconnectTime = Date.now();
|
||||||
|
const disconnectionDuration = reconnectTime - disconnectTime.current;
|
||||||
|
|
||||||
|
// If disconnection lasted less than acceptable reconnect time, do nothing
|
||||||
|
if (disconnectionDuration < acceptableReconnectTime) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Schedule a refetch with a random delay between 10 and 30 seconds
|
||||||
|
if (!reconnectTimeout.current) {
|
||||||
|
reconnectTimeout.current = setTimeout(() => {
|
||||||
|
const randomDelay = Math.floor(Math.random() * (30000 - 10000 + 1)) + 10000; // Random delay between 10 and 30 seconds
|
||||||
|
setTimeout(() => {
|
||||||
|
if (refetch) refetch();
|
||||||
|
reconnectTimeout.current = null; // Clear the timeout reference after refetch
|
||||||
|
}, randomDelay);
|
||||||
|
}, acceptableReconnectTime);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Listen for 'production-job-updated', 'disconnect', and 'connect' events
|
||||||
|
socket.on("production-job-updated", handleJobUpdates);
|
||||||
|
socket.on("disconnect", handleDisconnect);
|
||||||
|
socket.on("connect", handleReconnect);
|
||||||
|
|
||||||
|
// Clean up on unmount or when dependencies change
|
||||||
|
return () => {
|
||||||
|
socket.off("production-job-updated", handleJobUpdates);
|
||||||
|
socket.off("disconnect", handleDisconnect);
|
||||||
|
socket.off("connect", handleReconnect);
|
||||||
|
if (reconnectTimeout.current) {
|
||||||
|
clearTimeout(reconnectTimeout.current);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [subscriptionEnabled, socket, bodyshop, client, refetch]);
|
||||||
|
|
||||||
|
// Functions to fetch updated job data
|
||||||
const getUpdatedJobData = async (jobId) => {
|
const getUpdatedJobData = async (jobId) => {
|
||||||
client.query({
|
await client.query({
|
||||||
query: QUERY_EXACT_JOB_IN_PRODUCTION,
|
query: QUERY_EXACT_JOB_IN_PRODUCTION,
|
||||||
variables: { id: jobId }
|
variables: { id: jobId },
|
||||||
|
fetchPolicy: "network-only"
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
const getUpdatedJobsData = async (jobIds) => {
|
|
||||||
|
const getUpdatedJobsData = (jobIds) => {
|
||||||
client.query({
|
client.query({
|
||||||
query: QUERY_EXACT_JOBS_IN_PRODUCTION,
|
query: QUERY_EXACT_JOBS_IN_PRODUCTION,
|
||||||
variables: { ids: jobIds }
|
variables: { ids: jobIds }
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { useTranslation } from "react-i18next";
|
|||||||
import { getOrderOperatorsByType, getWhereOperatorsByType } from "../../utils/graphQLmodifier";
|
import { getOrderOperatorsByType, getWhereOperatorsByType } from "../../utils/graphQLmodifier";
|
||||||
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component";
|
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component";
|
||||||
import { generateInternalReflections } from "./report-center-modal-utils";
|
import { generateInternalReflections } from "./report-center-modal-utils";
|
||||||
import { FormDatePicker } from "../form-date-picker/form-date-picker.component.jsx";
|
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component.jsx";
|
||||||
|
|
||||||
export default function ReportCenterModalFiltersSortersComponent({ form, bodyshop }) {
|
export default function ReportCenterModalFiltersSortersComponent({ form, bodyshop }) {
|
||||||
return (
|
return (
|
||||||
@@ -196,7 +196,8 @@ function FiltersSection({ filters, form, bodyshop }) {
|
|||||||
// We have a type of date, so we will use a date picker
|
// We have a type of date, so we will use a date picker
|
||||||
if (type === "date") {
|
if (type === "date") {
|
||||||
return (
|
return (
|
||||||
<FormDatePicker
|
<DateTimePicker
|
||||||
|
isDateOnly
|
||||||
disabled={!operator}
|
disabled={!operator}
|
||||||
onChange={(date) => form.setFieldValue(fieldPath, date)}
|
onChange={(date) => form.setFieldValue(fieldPath, date)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user