Compare commits
142 Commits
IO-2825-No
...
feature/IO
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fc6ec54233 | ||
|
|
64928d0849 | ||
|
|
56a580b1e7 | ||
|
|
f7af3b407b | ||
|
|
9a0674f5d7 | ||
|
|
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 |
@@ -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
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
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
|
||||||
|
|||||||
@@ -2,6 +2,9 @@
|
|||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8"/>
|
<meta charset="utf-8"/>
|
||||||
|
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
|
||||||
|
<meta http-equiv="Pragma" content="no-cache">
|
||||||
|
<meta http-equiv="Expires" content="0">
|
||||||
<% if (env.VITE_APP_INSTANCE === 'IMEX') { %>
|
<% if (env.VITE_APP_INSTANCE === 'IMEX') { %>
|
||||||
<link rel="icon" href="/favicon.png"/>
|
<link rel="icon" href="/favicon.png"/>
|
||||||
<% } %> <% if (env.VITE_APP_INSTANCE === 'ROME') { %>
|
<% } %> <% if (env.VITE_APP_INSTANCE === 'ROME') { %>
|
||||||
|
|||||||
4
client/package-lock.json
generated
4
client/package-lock.json
generated
@@ -109,7 +109,8 @@
|
|||||||
"vite-plugin-legacy": "^2.1.0",
|
"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.1",
|
"vite-plugin-pwa": "^0.20.1",
|
||||||
"vite-plugin-style-import": "^2.0.0"
|
"vite-plugin-style-import": "^2.0.0",
|
||||||
|
"workbox-window": "^7.1.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.18.2"
|
"node": ">=18.18.2"
|
||||||
@@ -18429,6 +18430,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/workbox-window/-/workbox-window-7.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/workbox-window/-/workbox-window-7.1.0.tgz",
|
||||||
"integrity": "sha512-ZHeROyqR+AS5UPzholQRDttLFqGMwP0Np8MKWAdyxsDETxq3qOAyXvqessc3GniohG6e0mAqSQyKOHmT8zPF7g==",
|
"integrity": "sha512-ZHeROyqR+AS5UPzholQRDttLFqGMwP0Np8MKWAdyxsDETxq3qOAyXvqessc3GniohG6e0mAqSQyKOHmT8zPF7g==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/trusted-types": "^2.0.2",
|
"@types/trusted-types": "^2.0.2",
|
||||||
"workbox-core": "7.1.0"
|
"workbox-core": "7.1.0"
|
||||||
|
|||||||
@@ -153,6 +153,7 @@
|
|||||||
"vite-plugin-legacy": "^2.1.0",
|
"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.1",
|
"vite-plugin-pwa": "^0.20.1",
|
||||||
"vite-plugin-style-import": "^2.0.0"
|
"vite-plugin-style-import": "^2.0.0",
|
||||||
|
"workbox-window": "^7.1.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ 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/handleBeta";
|
|
||||||
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";
|
||||||
@@ -108,8 +107,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
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -276,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")}
|
||||||
|
|||||||
@@ -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")}
|
||||||
|
|||||||
@@ -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
|
||||||
|
};
|
||||||
@@ -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/handleBeta";
|
|
||||||
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
|
||||||
|
|||||||
@@ -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 />
|
||||||
|
|||||||
@@ -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} />
|
||||||
|
|||||||
@@ -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 = [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,8 +1,12 @@
|
|||||||
import React, { useEffect, useMemo } from "react";
|
import React, { useEffect, useMemo, useRef } from "react";
|
||||||
import { useQuery, useSubscription } from "@apollo/client";
|
import { 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_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";
|
||||||
@@ -12,7 +16,9 @@ const mapStateToProps = createStructuredSelector({
|
|||||||
currentUser: selectCurrentUser
|
currentUser: selectCurrentUser
|
||||||
});
|
});
|
||||||
|
|
||||||
function ProductionBoardKanbanContainer({ bodyshop, currentUser }) {
|
function ProductionBoardKanbanContainer({ bodyshop, currentUser, subscriptionType = "direct" }) {
|
||||||
|
const fired = useRef(false); // useRef to keep track of whether the subscription fired
|
||||||
|
|
||||||
const combinedStatuses = useMemo(
|
const combinedStatuses = useMemo(
|
||||||
() => [
|
() => [
|
||||||
...bodyshop.md_ro_statuses.production_statuses,
|
...bodyshop.md_ro_statuses.production_statuses,
|
||||||
@@ -28,9 +34,12 @@ 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 { data: updatedJobs } = useSubscription(
|
||||||
onError: (error) => console.error(`Error subscribing to jobs in production: ${error.message}`)
|
subscriptionType === "view" ? SUBSCRIPTION_JOBS_IN_PRODUCTION_VIEW : SUBSCRIPTION_JOBS_IN_PRODUCTION,
|
||||||
});
|
{
|
||||||
|
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 },
|
||||||
@@ -40,10 +49,15 @@ function ProductionBoardKanbanContainer({ bodyshop, currentUser }) {
|
|||||||
// const currentReducerData = useSelector((state) => (state.trello.lanes ? state.trello : {}));
|
// const currentReducerData = useSelector((state) => (state.trello.lanes ? state.trello : {}));
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (updatedJobs && data) {
|
if (!updatedJobs) {
|
||||||
refetch().catch((err) => console.error(`Error re-fetching jobs in production: ${err.message}`));
|
return;
|
||||||
}
|
}
|
||||||
}, [updatedJobs, data, refetch]);
|
if (!fired.current) {
|
||||||
|
fired.current = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
refetch().catch((err) => console.error(`Error re-fetching jobs in production: ${err.message}`));
|
||||||
|
}, [updatedJobs, refetch]);
|
||||||
|
|
||||||
const filteredAssociationSettings = useMemo(() => {
|
const filteredAssociationSettings = useMemo(() => {
|
||||||
return associationSettings?.associations[0] || null;
|
return associationSettings?.associations[0] || null;
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ 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 { defaultKanbanSettings, statisticsItems } 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",
|
||||||
@@ -32,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) => {
|
||||||
@@ -43,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);
|
||||||
@@ -87,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 =
|
||||||
@@ -118,12 +131,6 @@ const ProductionStatistics = ({ data, cardSettings, reducerData }) => {
|
|||||||
[reducerData, cardSettings.jobsOnBoard]
|
[reducerData, cardSettings.jobsOnBoard]
|
||||||
);
|
);
|
||||||
|
|
||||||
const totalAmountOnBoard = useMemo(() => {
|
|
||||||
if (!reducerData || !cardSettings.totalAmountOnBoard) return null;
|
|
||||||
const total = calculateReducerTotalAmount(reducerData.lanes, "job_totals");
|
|
||||||
return parseFloat(total.toFixed(2));
|
|
||||||
}, [reducerData, cardSettings.totalAmountOnBoard]);
|
|
||||||
|
|
||||||
const tasksInProduction = useMemo(() => {
|
const tasksInProduction = useMemo(() => {
|
||||||
if (!data || !cardSettings.tasksInProduction) return null;
|
if (!data || !cardSettings.tasksInProduction) return null;
|
||||||
return data.reduce((acc, item) => acc + (item.tasks_aggregate?.aggregate?.count || 0), 0);
|
return data.reduce((acc, item) => acc + (item.tasks_aggregate?.aggregate?.count || 0), 0);
|
||||||
@@ -191,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")
|
||||||
|
|||||||
@@ -28,6 +28,11 @@ import ProductionListColumnCategory from "./production-list-columns.status.categ
|
|||||||
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";
|
||||||
|
|
||||||
|
const getEmployeeName = (employeeId, employees) => {
|
||||||
|
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;
|
||||||
return [
|
return [
|
||||||
@@ -426,8 +431,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" />
|
||||||
@@ -440,8 +445,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" />
|
||||||
@@ -460,8 +465,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" />
|
||||||
@@ -474,8 +479,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>
|
||||||
|
|||||||
@@ -1,93 +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";
|
|
||||||
import { isFunction } from "lodash";
|
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
|
||||||
//currentUser: selectCurrentUser
|
|
||||||
bodyshop: selectBodyshop
|
|
||||||
});
|
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
|
||||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
|
||||||
});
|
|
||||||
|
|
||||||
export function ProductionListSaveConfigButton({ columns, bodyshop, tableState, onSave }) {
|
|
||||||
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") });
|
|
||||||
if (onSave && isFunction(onSave)) {
|
|
||||||
onSave();
|
|
||||||
}
|
|
||||||
} 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,505 @@
|
|||||||
|
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
|
||||||
|
.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,172 +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";
|
|
||||||
import { isFunction } from "lodash";
|
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
|
||||||
bodyshop: selectBodyshop,
|
|
||||||
technician: selectTechnician,
|
|
||||||
currentUser: selectCurrentUser
|
|
||||||
});
|
|
||||||
|
|
||||||
export function ProductionListTable({
|
|
||||||
refetch,
|
|
||||||
bodyshop,
|
|
||||||
technician,
|
|
||||||
currentUser,
|
|
||||||
state,
|
|
||||||
data,
|
|
||||||
setColumns,
|
|
||||||
setState,
|
|
||||||
onProfileChange
|
|
||||||
}) {
|
|
||||||
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) => {
|
|
||||||
const newColumns = 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
|
|
||||||
};
|
|
||||||
});
|
|
||||||
setColumns(newColumns);
|
|
||||||
const newState = bodyshop.production_config.filter((pc) => pc.name === value)[0].columns.tableState;
|
|
||||||
setState(newState);
|
|
||||||
|
|
||||||
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 };
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (onProfileChange && isFunction(onProfileChange)) {
|
|
||||||
onProfileChange({ value, option, newColumns, newState, assoc });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
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,24 +1,23 @@
|
|||||||
import React, { useEffect, useMemo, useRef, useState } from "react";
|
import { SyncOutlined } from "@ant-design/icons";
|
||||||
import { Button, Dropdown, Input, Space, Statistic, Table } from "antd";
|
|
||||||
import { PageHeader } from "@ant-design/pro-layout";
|
import { PageHeader } from "@ant-design/pro-layout";
|
||||||
|
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
||||||
|
import { Button, Dropdown, Input, Space, Statistic, Table } from "antd";
|
||||||
|
import _ from "lodash";
|
||||||
|
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";
|
||||||
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
|
||||||
import { SyncOutlined } from "@ant-design/icons";
|
|
||||||
import Prompt from "../../utils/prompt.js";
|
|
||||||
import _ from "lodash";
|
|
||||||
import AlertComponent from "../alert/alert.component.jsx";
|
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop,
|
bodyshop: selectBodyshop,
|
||||||
@@ -44,7 +43,7 @@ export function ProductionListTable({ loading, data, refetch, bodyshop, technici
|
|||||||
const initialStateRef = useRef(
|
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: "" }
|
||||||
}
|
}
|
||||||
@@ -270,23 +269,23 @@ export function ProductionListTable({ loading, data, refetch, bodyshop, technici
|
|||||||
data={data}
|
data={data}
|
||||||
onColumnAdd={addColumn}
|
onColumnAdd={addColumn}
|
||||||
/>
|
/>
|
||||||
<ProductionListSaveConfigButton
|
|
||||||
|
<ProductionListConfigManager
|
||||||
columns={columns}
|
columns={columns}
|
||||||
tableState={state}
|
setColumns={setColumns}
|
||||||
onSave={() => {
|
|
||||||
setHasUnsavedChanges(false);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<ProductionListTableViewSelect
|
|
||||||
state={state}
|
state={state}
|
||||||
setState={setState}
|
setState={setState}
|
||||||
setColumns={setColumns}
|
|
||||||
onProfileChange={() => {
|
|
||||||
initialStateRef.current = state;
|
|
||||||
setHasUnsavedChanges(false);
|
|
||||||
}}
|
|
||||||
refetch={refetch}
|
refetch={refetch}
|
||||||
data={data}
|
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)}
|
||||||
|
|||||||
@@ -4,12 +4,13 @@ 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";
|
||||||
|
|
||||||
export default function ProductionListTableContainer() {
|
export default function ProductionListTableContainer({ subscriptionType = "direct" }) {
|
||||||
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",
|
||||||
@@ -17,7 +18,9 @@ export default function ProductionListTableContainer() {
|
|||||||
});
|
});
|
||||||
const client = useApolloClient();
|
const client = useApolloClient();
|
||||||
const [joblist, setJoblist] = useState([]);
|
const [joblist, setJoblist] = useState([]);
|
||||||
const { data: updatedJobs } = useSubscription(SUBSCRIPTION_JOBS_IN_PRODUCTION);
|
const { data: updatedJobs } = useSubscription(
|
||||||
|
subscriptionType === "view" ? SUBSCRIPTION_JOBS_IN_PRODUCTION_VIEW : SUBSCRIPTION_JOBS_IN_PRODUCTION
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!(data && data.jobs)) return;
|
if (!(data && data.jobs)) return;
|
||||||
|
|||||||
@@ -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)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -34,28 +34,34 @@ export function ReportCenterModalComponent({ reportCenterModal, bodyshop }) {
|
|||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
const [search, setSearch] = useState("");
|
const [search, setSearch] = useState("");
|
||||||
const {
|
const {
|
||||||
treatments: { Enhanced_Payroll }
|
treatments: { Enhanced_Payroll, ADPPayroll }
|
||||||
} = useSplitTreatments({
|
} = useSplitTreatments({
|
||||||
attributes: {},
|
attributes: {},
|
||||||
names: ["Enhanced_Payroll"],
|
names: ["Enhanced_Payroll", "ADPPayroll"],
|
||||||
splitKey: bodyshop.imexshopid
|
splitKey: bodyshop.imexshopid
|
||||||
});
|
});
|
||||||
|
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const Templates = TemplateList("report_center");
|
const Templates = TemplateList("report_center");
|
||||||
const ReportsList =
|
const ReportsList = Object.keys(Templates)
|
||||||
Enhanced_Payroll.treatment === "on"
|
.map((key) => Templates[key])
|
||||||
? Object.keys(Templates)
|
.filter((temp) => {
|
||||||
.map((key) => {
|
const enhancedPayrollOn = Enhanced_Payroll.treatment === "on";
|
||||||
return Templates[key];
|
const adpPayrollOn = ADPPayroll.treatment === "on";
|
||||||
})
|
|
||||||
.filter((temp) => temp.enhanced_payroll === undefined || temp.enhanced_payroll === true)
|
if (enhancedPayrollOn && adpPayrollOn) {
|
||||||
: Object.keys(Templates)
|
return temp.enhanced_payroll !== false || temp.adp_payroll !== false;
|
||||||
.map((key) => {
|
}
|
||||||
return Templates[key];
|
if (enhancedPayrollOn) {
|
||||||
})
|
return temp.enhanced_payroll !== false && temp.adp_payroll !== true;
|
||||||
.filter((temp) => temp.enhanced_payroll === undefined || temp.enhanced_payroll === false);
|
}
|
||||||
|
if (adpPayrollOn) {
|
||||||
|
return temp.adp_payroll !== false && temp.enhanced_payroll !== true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return temp.enhanced_payroll !== true && temp.adp_payroll !== true;
|
||||||
|
});
|
||||||
|
|
||||||
const { open } = reportCenterModal;
|
const { open } = reportCenterModal;
|
||||||
|
|
||||||
@@ -104,7 +110,7 @@ export function ReportCenterModalComponent({ reportCenterModal, bodyshop }) {
|
|||||||
to: values.to,
|
to: values.to,
|
||||||
subject: Templates[values.key]?.subject
|
subject: Templates[values.key]?.subject
|
||||||
},
|
},
|
||||||
values.sendbyexcel === "excel" ? "x" : values.sendby === "email" ? "e" : "p",
|
values.sendbytext === "text" ? values.sendbytext : values.sendbyexcel === "excel" ? "x" : values.sendby === "email" ? "e" : "p",
|
||||||
id
|
id
|
||||||
);
|
);
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
@@ -291,7 +297,15 @@ export function ReportCenterModalComponent({ reportCenterModal, bodyshop }) {
|
|||||||
</Radio.Group>
|
</Radio.Group>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
);
|
);
|
||||||
if (reporttype !== "excel")
|
if (reporttype === "text")
|
||||||
|
return (
|
||||||
|
<Form.Item label={t("general.labels.sendby")} name="sendbytext" initialValue="text">
|
||||||
|
<Radio.Group>
|
||||||
|
<Radio value="text">{t("general.labels.text")}</Radio>
|
||||||
|
</Radio.Group>
|
||||||
|
</Form.Item>
|
||||||
|
);
|
||||||
|
if (reporttype !== "excel" || reporttype !== "text")
|
||||||
return (
|
return (
|
||||||
<Form.Item label={t("general.labels.sendby")} name="sendby" initialValue="print">
|
<Form.Item label={t("general.labels.sendby")} name="sendby" initialValue="print">
|
||||||
<Radio.Group>
|
<Radio.Group>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import dayjs from "../../utils/day";
|
|||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { UPDATE_SCOREBOARD_ENTRY } from "../../graphql/scoreboard.queries";
|
import { UPDATE_SCOREBOARD_ENTRY } from "../../graphql/scoreboard.queries";
|
||||||
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 ScoreboardEntryEdit({ entry }) {
|
export default function ScoreboardEntryEdit({ entry }) {
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
@@ -52,7 +52,7 @@ export default function ScoreboardEntryEdit({ entry }) {
|
|||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<FormDatePicker />
|
<DateTimePicker isDateOnly />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={t("scoreboard.fields.bodyhrs")}
|
label={t("scoreboard.fields.bodyhrs")}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ 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 { INSERT_VACATION } from "../../graphql/employees.queries";
|
import { INSERT_VACATION } from "../../graphql/employees.queries";
|
||||||
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 ShopEmployeeAddVacation({ employee }) {
|
export default function ShopEmployeeAddVacation({ employee }) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@@ -64,7 +64,7 @@ export default function ShopEmployeeAddVacation({ employee }) {
|
|||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<FormDatePicker />
|
<DateTimePicker isDateOnly />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={t("employees.fields.vacation.end")}
|
label={t("employees.fields.vacation.end")}
|
||||||
@@ -90,7 +90,7 @@ export default function ShopEmployeeAddVacation({ employee }) {
|
|||||||
})
|
})
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<FormDatePicker />
|
<DateTimePicker isDateOnly />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Space wrap>
|
<Space wrap>
|
||||||
|
|||||||
@@ -21,12 +21,12 @@ import { selectBodyshop } from "../../redux/user/user.selectors";
|
|||||||
import CiecaSelect from "../../utils/Ciecaselect";
|
import CiecaSelect from "../../utils/Ciecaselect";
|
||||||
import { DateFormatter } from "../../utils/DateFormatter";
|
import { DateFormatter } from "../../utils/DateFormatter";
|
||||||
import AlertComponent from "../alert/alert.component";
|
import AlertComponent from "../alert/alert.component";
|
||||||
import FormDatePicker from "../form-date-picker/form-date-picker.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 ShopEmployeeAddVacation from "./shop-employees-add-vacation.component";
|
import ShopEmployeeAddVacation from "./shop-employees-add-vacation.component";
|
||||||
import queryString from "query-string";
|
import queryString from "query-string";
|
||||||
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
||||||
|
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component.jsx";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop
|
bodyshop: selectBodyshop
|
||||||
@@ -266,10 +266,10 @@ export function ShopEmployeesFormComponent({ bodyshop }) {
|
|||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<FormDatePicker />
|
<DateTimePicker isDateOnly />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label={t("employees.fields.termination_date")} name="termination_date">
|
<Form.Item label={t("employees.fields.termination_date")} name="termination_date">
|
||||||
<FormDatePicker />
|
<DateTimePicker isDateOnly />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={t("employees.fields.user_email")}
|
label={t("employees.fields.user_email")}
|
||||||
|
|||||||
@@ -7,13 +7,13 @@ 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 DatePickerRanges from "../../utils/DatePickerRanges";
|
import DatePickerRanges from "../../utils/DatePickerRanges";
|
||||||
|
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
||||||
|
import FeatureWrapper, { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.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 PhoneFormItem, { PhoneItemFormatterValidation } from "../form-items-formatted/phone-form-item.component";
|
import PhoneFormItem, { PhoneItemFormatterValidation } from "../form-items-formatted/phone-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 InstanceRenderManager from "../../utils/instanceRenderMgr";
|
|
||||||
import FeatureWrapper, { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component";
|
|
||||||
// TODO: Client Update, this might break
|
// TODO: Client Update, this might break
|
||||||
const timeZonesList = Intl.supportedValuesOf("timeZone");
|
const timeZonesList = Intl.supportedValuesOf("timeZone");
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
@@ -28,10 +28,10 @@ export function ShopInfoGeneral({ form, bodyshop }) {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
treatments: { ClosingPeriod }
|
treatments: { ClosingPeriod, ADPPayroll }
|
||||||
} = useSplitTreatments({
|
} = useSplitTreatments({
|
||||||
attributes: {},
|
attributes: {},
|
||||||
names: ["ClosingPeriod"],
|
names: ["ClosingPeriod", "ADPPayroll"],
|
||||||
splitKey: bodyshop && bodyshop.imexshopid
|
splitKey: bodyshop && bodyshop.imexshopid
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -98,7 +98,6 @@ export function ShopInfoGeneral({ form, bodyshop }) {
|
|||||||
<Form.Item label={t("bodyshop.fields.email")} name="email">
|
<Form.Item label={t("bodyshop.fields.email")} name="email">
|
||||||
<Input />
|
<Input />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={t("bodyshop.fields.phone")}
|
label={t("bodyshop.fields.phone")}
|
||||||
name="phone"
|
name="phone"
|
||||||
@@ -356,14 +355,22 @@ export function ShopInfoGeneral({ form, bodyshop }) {
|
|||||||
<Select mode="tags" />
|
<Select mode="tags" />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
{ClosingPeriod.treatment === "on" && (
|
{ClosingPeriod.treatment === "on" && (
|
||||||
<>
|
<Form.Item
|
||||||
<Form.Item
|
name={["accountingconfig", "ClosingPeriod"]}
|
||||||
name={["accountingconfig", "ClosingPeriod"]}
|
label={t("bodyshop.fields.closingperiod")} //{t("reportcenter.labels.dates")}
|
||||||
label={t("bodyshop.fields.closingperiod")} //{t("reportcenter.labels.dates")}
|
>
|
||||||
>
|
<DatePicker.RangePicker format="MM/DD/YYYY" presets={DatePickerRanges} />
|
||||||
<DatePicker.RangePicker format="MM/DD/YYYY" presets={DatePickerRanges} />
|
</Form.Item>
|
||||||
</Form.Item>
|
)}
|
||||||
</>
|
{ADPPayroll.treatment === "on" && (
|
||||||
|
<Form.Item name={["accountingconfig", "companyCode"]} label={t("bodyshop.fields.companycode")}>
|
||||||
|
<Input />
|
||||||
|
</Form.Item>
|
||||||
|
)}
|
||||||
|
{ADPPayroll.treatment === "on" && (
|
||||||
|
<Form.Item name={["accountingconfig", "batchID"]} label={t("bodyshop.fields.batchid")}>
|
||||||
|
<Input />
|
||||||
|
</Form.Item>
|
||||||
)}
|
)}
|
||||||
</LayoutFormRow>
|
</LayoutFormRow>
|
||||||
</FeatureWrapper>
|
</FeatureWrapper>
|
||||||
|
|||||||
@@ -30,219 +30,226 @@ export function ShopInfoRbacComponent({ form, bodyshop }) {
|
|||||||
return (
|
return (
|
||||||
<RbacWrapper action="shop:rbac">
|
<RbacWrapper action="shop:rbac">
|
||||||
<LayoutFormRow>
|
<LayoutFormRow>
|
||||||
{...HasFeatureAccess({ featureName: "export", bodyshop }) ? [
|
{...HasFeatureAccess({ featureName: "export", bodyshop })
|
||||||
<Form.Item
|
? [
|
||||||
label={t("bodyshop.fields.rbac.accounting.exportlog")}
|
<Form.Item
|
||||||
rules={[
|
label={t("bodyshop.fields.rbac.accounting.exportlog")}
|
||||||
{
|
rules={[
|
||||||
required: true
|
{
|
||||||
//message: t("general.validation.required"),
|
required: true
|
||||||
}
|
//message: t("general.validation.required"),
|
||||||
]}
|
}
|
||||||
name={["md_rbac", "accounting:exportlog"]}
|
]}
|
||||||
>
|
name={["md_rbac", "accounting:exportlog"]}
|
||||||
<InputNumber />
|
>
|
||||||
</Form.Item>,
|
<InputNumber />
|
||||||
<Form.Item
|
</Form.Item>,
|
||||||
label={t("bodyshop.fields.rbac.accounting.payables")}
|
<Form.Item
|
||||||
rules={[
|
label={t("bodyshop.fields.rbac.accounting.payables")}
|
||||||
{
|
rules={[
|
||||||
required: true
|
{
|
||||||
//message: t("general.validation.required"),
|
required: true
|
||||||
}
|
//message: t("general.validation.required"),
|
||||||
]}
|
}
|
||||||
name={["md_rbac", "accounting:payables"]}
|
]}
|
||||||
>
|
name={["md_rbac", "accounting:payables"]}
|
||||||
<InputNumber />
|
>
|
||||||
</Form.Item>,
|
<InputNumber />
|
||||||
<Form.Item
|
</Form.Item>,
|
||||||
label={t("bodyshop.fields.rbac.accounting.payments")}
|
<Form.Item
|
||||||
rules={[
|
label={t("bodyshop.fields.rbac.accounting.payments")}
|
||||||
{
|
rules={[
|
||||||
required: true
|
{
|
||||||
//message: t("general.validation.required"),
|
required: true
|
||||||
}
|
//message: t("general.validation.required"),
|
||||||
]}
|
}
|
||||||
name={["md_rbac", "accounting:payments"]}
|
]}
|
||||||
>
|
name={["md_rbac", "accounting:payments"]}
|
||||||
<InputNumber />
|
>
|
||||||
</Form.Item>,
|
<InputNumber />
|
||||||
<Form.Item
|
</Form.Item>,
|
||||||
label={t("bodyshop.fields.rbac.accounting.receivables")}
|
<Form.Item
|
||||||
rules={[
|
label={t("bodyshop.fields.rbac.accounting.receivables")}
|
||||||
{
|
rules={[
|
||||||
required: true
|
{
|
||||||
//message: t("general.validation.required"),
|
required: true
|
||||||
}
|
//message: t("general.validation.required"),
|
||||||
]}
|
}
|
||||||
name={["md_rbac", "accounting:receivables"]}
|
]}
|
||||||
>
|
name={["md_rbac", "accounting:receivables"]}
|
||||||
<InputNumber />
|
>
|
||||||
</Form.Item>
|
<InputNumber />
|
||||||
]:[]}
|
</Form.Item>
|
||||||
{...HasFeatureAccess({ featureName: "bills", bodyshop }) ? [
|
]
|
||||||
<Form.Item
|
: []}
|
||||||
label={t("bodyshop.fields.rbac.bills.delete")}
|
{...HasFeatureAccess({ featureName: "bills", bodyshop })
|
||||||
rules={[
|
? [
|
||||||
{
|
<Form.Item
|
||||||
required: true
|
label={t("bodyshop.fields.rbac.bills.delete")}
|
||||||
//message: t("general.validation.required"),
|
rules={[
|
||||||
}
|
{
|
||||||
]}
|
required: true
|
||||||
name={["md_rbac", "bills:delete"]}
|
//message: t("general.validation.required"),
|
||||||
>
|
}
|
||||||
<InputNumber />
|
]}
|
||||||
</Form.Item>,
|
name={["md_rbac", "bills:delete"]}
|
||||||
<Form.Item
|
>
|
||||||
label={t("bodyshop.fields.rbac.bills.enter")}
|
<InputNumber />
|
||||||
rules={[
|
</Form.Item>,
|
||||||
{
|
<Form.Item
|
||||||
required: true
|
label={t("bodyshop.fields.rbac.bills.enter")}
|
||||||
//message: t("general.validation.required"),
|
rules={[
|
||||||
}
|
{
|
||||||
]}
|
required: true
|
||||||
name={["md_rbac", "bills:enter"]}
|
//message: t("general.validation.required"),
|
||||||
>
|
}
|
||||||
<InputNumber />
|
]}
|
||||||
</Form.Item>,
|
name={["md_rbac", "bills:enter"]}
|
||||||
<Form.Item
|
>
|
||||||
label={t("bodyshop.fields.rbac.bills.list")}
|
<InputNumber />
|
||||||
rules={[
|
</Form.Item>,
|
||||||
{
|
<Form.Item
|
||||||
required: true
|
label={t("bodyshop.fields.rbac.bills.list")}
|
||||||
//message: t("general.validation.required"),
|
rules={[
|
||||||
}
|
{
|
||||||
]}
|
required: true
|
||||||
name={["md_rbac", "bills:list"]}
|
//message: t("general.validation.required"),
|
||||||
>
|
}
|
||||||
<InputNumber />
|
]}
|
||||||
</Form.Item>,
|
name={["md_rbac", "bills:list"]}
|
||||||
<Form.Item
|
>
|
||||||
label={t("bodyshop.fields.rbac.bills.reexport")}
|
<InputNumber />
|
||||||
rules={[
|
</Form.Item>,
|
||||||
{
|
<Form.Item
|
||||||
required: true
|
label={t("bodyshop.fields.rbac.bills.reexport")}
|
||||||
//message: t("general.validation.required"),
|
rules={[
|
||||||
}
|
{
|
||||||
]}
|
required: true
|
||||||
name={["md_rbac", "bills:reexport"]}
|
//message: t("general.validation.required"),
|
||||||
>
|
}
|
||||||
<InputNumber />
|
]}
|
||||||
</Form.Item>,
|
name={["md_rbac", "bills:reexport"]}
|
||||||
<Form.Item
|
>
|
||||||
label={t("bodyshop.fields.rbac.bills.view")}
|
<InputNumber />
|
||||||
rules={[
|
</Form.Item>,
|
||||||
{
|
<Form.Item
|
||||||
required: true
|
label={t("bodyshop.fields.rbac.bills.view")}
|
||||||
//message: t("general.validation.required"),
|
rules={[
|
||||||
}
|
{
|
||||||
]}
|
required: true
|
||||||
name={["md_rbac", "bills:view"]}
|
//message: t("general.validation.required"),
|
||||||
>
|
}
|
||||||
<InputNumber />
|
]}
|
||||||
</Form.Item>
|
name={["md_rbac", "bills:view"]}
|
||||||
]:[]}
|
>
|
||||||
|
<InputNumber />
|
||||||
{...HasFeatureAccess({ featureName: "courtesycars", bodyshop }) ? [
|
</Form.Item>
|
||||||
<Form.Item
|
]
|
||||||
label={t("bodyshop.fields.rbac.contracts.create")}
|
: []}
|
||||||
rules={[
|
{...HasFeatureAccess({ featureName: "courtesycars", bodyshop })
|
||||||
{
|
? [
|
||||||
required: true
|
<Form.Item
|
||||||
//message: t("general.validation.required"),
|
label={t("bodyshop.fields.rbac.contracts.create")}
|
||||||
}
|
rules={[
|
||||||
]}
|
{
|
||||||
name={["md_rbac", "contracts:create"]}
|
required: true
|
||||||
>
|
//message: t("general.validation.required"),
|
||||||
<InputNumber />
|
}
|
||||||
</Form.Item>,
|
]}
|
||||||
<Form.Item
|
name={["md_rbac", "contracts:create"]}
|
||||||
label={t("bodyshop.fields.rbac.contracts.detail")}
|
>
|
||||||
rules={[
|
<InputNumber />
|
||||||
{
|
</Form.Item>,
|
||||||
required: true
|
<Form.Item
|
||||||
//message: t("general.validation.required"),
|
label={t("bodyshop.fields.rbac.contracts.detail")}
|
||||||
}
|
rules={[
|
||||||
]}
|
{
|
||||||
name={["md_rbac", "contracts:detail"]}
|
required: true
|
||||||
>
|
//message: t("general.validation.required"),
|
||||||
<InputNumber />
|
}
|
||||||
</Form.Item>,
|
]}
|
||||||
<Form.Item
|
name={["md_rbac", "contracts:detail"]}
|
||||||
label={t("bodyshop.fields.rbac.contracts.list")}
|
>
|
||||||
rules={[
|
<InputNumber />
|
||||||
{
|
</Form.Item>,
|
||||||
required: true
|
<Form.Item
|
||||||
//message: t("general.validation.required"),
|
label={t("bodyshop.fields.rbac.contracts.list")}
|
||||||
}
|
rules={[
|
||||||
]}
|
{
|
||||||
name={["md_rbac", "contracts:list"]}
|
required: true
|
||||||
>
|
//message: t("general.validation.required"),
|
||||||
<InputNumber />
|
}
|
||||||
</Form.Item>,
|
]}
|
||||||
<Form.Item
|
name={["md_rbac", "contracts:list"]}
|
||||||
label={t("bodyshop.fields.rbac.courtesycar.create")}
|
>
|
||||||
rules={[
|
<InputNumber />
|
||||||
{
|
</Form.Item>,
|
||||||
required: true
|
<Form.Item
|
||||||
//message: t("general.validation.required"),
|
label={t("bodyshop.fields.rbac.courtesycar.create")}
|
||||||
}
|
rules={[
|
||||||
]}
|
{
|
||||||
name={["md_rbac", "courtesycar:create"]}
|
required: true
|
||||||
>
|
//message: t("general.validation.required"),
|
||||||
<InputNumber />
|
}
|
||||||
</Form.Item>,
|
]}
|
||||||
<Form.Item
|
name={["md_rbac", "courtesycar:create"]}
|
||||||
label={t("bodyshop.fields.rbac.courtesycar.detail")}
|
>
|
||||||
rules={[
|
<InputNumber />
|
||||||
{
|
</Form.Item>,
|
||||||
required: true
|
<Form.Item
|
||||||
//message: t("general.validation.required"),
|
label={t("bodyshop.fields.rbac.courtesycar.detail")}
|
||||||
}
|
rules={[
|
||||||
]}
|
{
|
||||||
name={["md_rbac", "courtesycar:detail"]}
|
required: true
|
||||||
>
|
//message: t("general.validation.required"),
|
||||||
<InputNumber />
|
}
|
||||||
</Form.Item>,
|
]}
|
||||||
<Form.Item
|
name={["md_rbac", "courtesycar:detail"]}
|
||||||
label={t("bodyshop.fields.rbac.courtesycar.list")}
|
>
|
||||||
rules={[
|
<InputNumber />
|
||||||
{
|
</Form.Item>,
|
||||||
required: true
|
<Form.Item
|
||||||
//message: t("general.validation.required"),
|
label={t("bodyshop.fields.rbac.courtesycar.list")}
|
||||||
}
|
rules={[
|
||||||
]}
|
{
|
||||||
name={["md_rbac", "courtesycar:list"]}
|
required: true
|
||||||
>
|
//message: t("general.validation.required"),
|
||||||
<InputNumber />
|
}
|
||||||
</Form.Item>
|
]}
|
||||||
]:[]}
|
name={["md_rbac", "courtesycar:list"]}
|
||||||
{...HasFeatureAccess({ featureName: "csi", bodyshop }) ? [
|
>
|
||||||
<Form.Item
|
<InputNumber />
|
||||||
label={t("bodyshop.fields.rbac.csi.export")}
|
</Form.Item>
|
||||||
rules={[
|
]
|
||||||
{
|
: []}
|
||||||
required: true
|
{...HasFeatureAccess({ featureName: "csi", bodyshop })
|
||||||
//message: t("general.validation.required"),
|
? [
|
||||||
}
|
<Form.Item
|
||||||
]}
|
label={t("bodyshop.fields.rbac.csi.export")}
|
||||||
name={["md_rbac", "csi:export"]}
|
rules={[
|
||||||
>
|
{
|
||||||
<InputNumber />
|
required: true
|
||||||
</Form.Item>,
|
//message: t("general.validation.required"),
|
||||||
<Form.Item
|
}
|
||||||
label={t("bodyshop.fields.rbac.csi.page")}
|
]}
|
||||||
rules={[
|
name={["md_rbac", "csi:export"]}
|
||||||
{
|
>
|
||||||
required: true
|
<InputNumber />
|
||||||
//message: t("general.validation.required"),
|
</Form.Item>,
|
||||||
}
|
<Form.Item
|
||||||
]}
|
label={t("bodyshop.fields.rbac.csi.page")}
|
||||||
name={["md_rbac", "csi:page"]}
|
rules={[
|
||||||
>
|
{
|
||||||
<InputNumber />
|
required: true
|
||||||
</Form.Item>
|
//message: t("general.validation.required"),
|
||||||
]:[]}
|
}
|
||||||
|
]}
|
||||||
|
name={["md_rbac", "csi:page"]}
|
||||||
|
>
|
||||||
|
<InputNumber />
|
||||||
|
</Form.Item>
|
||||||
|
]
|
||||||
|
: []}
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={t("bodyshop.fields.rbac.employees.page")}
|
label={t("bodyshop.fields.rbac.employees.page")}
|
||||||
rules={[
|
rules={[
|
||||||
@@ -255,6 +262,18 @@ export function ShopInfoRbacComponent({ form, bodyshop }) {
|
|||||||
>
|
>
|
||||||
<InputNumber />
|
<InputNumber />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
label={t("bodyshop.fields.rbac.employee_teams.page")}
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: true
|
||||||
|
//message: t("general.validation.required"),
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
name={["md_rbac", "employee_teams:page"]}
|
||||||
|
>
|
||||||
|
<InputNumber />
|
||||||
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={t("bodyshop.fields.rbac.jobs.admin")}
|
label={t("bodyshop.fields.rbac.jobs.admin")}
|
||||||
rules={[
|
rules={[
|
||||||
@@ -435,31 +454,6 @@ export function ShopInfoRbacComponent({ form, bodyshop }) {
|
|||||||
>
|
>
|
||||||
<InputNumber />
|
<InputNumber />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
|
||||||
label={t("bodyshop.fields.rbac.employees.page")}
|
|
||||||
rules={[
|
|
||||||
{
|
|
||||||
required: true
|
|
||||||
//message: t("general.validation.required"),
|
|
||||||
}
|
|
||||||
]}
|
|
||||||
name={["md_rbac", "employees:page"]}
|
|
||||||
>
|
|
||||||
<InputNumber />
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item
|
|
||||||
label={t("bodyshop.fields.rbac.employee_teams.page")}
|
|
||||||
rules={[
|
|
||||||
{
|
|
||||||
required: true
|
|
||||||
//message: t("general.validation.required"),
|
|
||||||
}
|
|
||||||
]}
|
|
||||||
name={["md_rbac", "employee_teams:page"]}
|
|
||||||
>
|
|
||||||
<InputNumber />
|
|
||||||
</Form.Item>
|
|
||||||
|
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={t("bodyshop.fields.rbac.payments.enter")}
|
label={t("bodyshop.fields.rbac.payments.enter")}
|
||||||
rules={[
|
rules={[
|
||||||
@@ -522,7 +516,6 @@ export function ShopInfoRbacComponent({ form, bodyshop }) {
|
|||||||
<InputNumber />
|
<InputNumber />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={t("bodyshop.fields.rbac.production.list")}
|
label={t("bodyshop.fields.rbac.production.list")}
|
||||||
rules={[
|
rules={[
|
||||||
@@ -561,128 +554,118 @@ export function ShopInfoRbacComponent({ form, bodyshop }) {
|
|||||||
<InputNumber />
|
<InputNumber />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
)}
|
)}
|
||||||
{...HasFeatureAccess({ featureName: "timetickets", bodyshop }) ? [
|
{...HasFeatureAccess({ featureName: "timetickets", bodyshop })
|
||||||
<Form.Item
|
? [
|
||||||
label={t("bodyshop.fields.rbac.shiftclock.view")}
|
<Form.Item
|
||||||
rules={[
|
label={t("bodyshop.fields.rbac.shiftclock.view")}
|
||||||
{
|
rules={[
|
||||||
required: true
|
{
|
||||||
//message: t("general.validation.required"),
|
required: true
|
||||||
}
|
//message: t("general.validation.required"),
|
||||||
]}
|
}
|
||||||
name={["md_rbac", "shiftclock:view"]}
|
]}
|
||||||
>
|
name={["md_rbac", "shiftclock:view"]}
|
||||||
<InputNumber />
|
>
|
||||||
</Form.Item>,
|
<InputNumber />
|
||||||
<Form.Item
|
</Form.Item>,
|
||||||
label={t("bodyshop.fields.rbac.shop.config")}
|
<Form.Item
|
||||||
rules={[
|
label={t("bodyshop.fields.rbac.shop.config")}
|
||||||
{
|
rules={[
|
||||||
required: true
|
{
|
||||||
//message: t("general.validation.required"),
|
required: true
|
||||||
}
|
//message: t("general.validation.required"),
|
||||||
]}
|
}
|
||||||
name={["md_rbac", "shop:config"]}
|
]}
|
||||||
>
|
name={["md_rbac", "shop:config"]}
|
||||||
<InputNumber />
|
>
|
||||||
</Form.Item>,
|
<InputNumber />
|
||||||
<Form.Item
|
</Form.Item>,
|
||||||
label={t("bodyshop.fields.rbac.timetickets.edit")}
|
<Form.Item
|
||||||
rules={[
|
label={t("bodyshop.fields.rbac.timetickets.edit")}
|
||||||
{
|
rules={[
|
||||||
required: true
|
{
|
||||||
//message: t("general.validation.required"),
|
required: true
|
||||||
}
|
//message: t("general.validation.required"),
|
||||||
]}
|
}
|
||||||
name={["md_rbac", "timetickets:edit"]}
|
]}
|
||||||
>
|
name={["md_rbac", "timetickets:edit"]}
|
||||||
<InputNumber />
|
>
|
||||||
</Form.Item>,
|
<InputNumber />
|
||||||
<Form.Item
|
</Form.Item>,
|
||||||
label={t("bodyshop.fields.rbac.timetickets.shiftedit")}
|
<Form.Item
|
||||||
rules={[
|
label={t("bodyshop.fields.rbac.timetickets.shiftedit")}
|
||||||
{
|
rules={[
|
||||||
required: true
|
{
|
||||||
//message: t("general.validation.required"),
|
required: true
|
||||||
}
|
//message: t("general.validation.required"),
|
||||||
]}
|
}
|
||||||
name={["md_rbac", "timetickets:shiftedit"]}
|
]}
|
||||||
>
|
name={["md_rbac", "timetickets:shiftedit"]}
|
||||||
<InputNumber />
|
>
|
||||||
</Form.Item>,
|
<InputNumber />
|
||||||
<Form.Item
|
</Form.Item>,
|
||||||
label={t("bodyshop.fields.rbac.timetickets.editcommitted")}
|
<Form.Item
|
||||||
rules={[
|
label={t("bodyshop.fields.rbac.timetickets.editcommitted")}
|
||||||
{
|
rules={[
|
||||||
required: true
|
{
|
||||||
//message: t("general.validation.required"),
|
required: true
|
||||||
}
|
//message: t("general.validation.required"),
|
||||||
]}
|
}
|
||||||
name={["md_rbac", "timetickets:editcommitted"]}
|
]}
|
||||||
>
|
name={["md_rbac", "timetickets:editcommitted"]}
|
||||||
<InputNumber />
|
>
|
||||||
</Form.Item>,
|
<InputNumber />
|
||||||
<Form.Item
|
</Form.Item>,
|
||||||
label={t("bodyshop.fields.rbac.ttapprovals.view")}
|
<Form.Item
|
||||||
rules={[
|
label={t("bodyshop.fields.rbac.ttapprovals.view")}
|
||||||
{
|
rules={[
|
||||||
required: true
|
{
|
||||||
//message: t("general.validation.required"),
|
required: true
|
||||||
}
|
//message: t("general.validation.required"),
|
||||||
]}
|
}
|
||||||
name={["md_rbac", "ttapprovals:view"]}
|
]}
|
||||||
>
|
name={["md_rbac", "ttapprovals:view"]}
|
||||||
<InputNumber />
|
>
|
||||||
</Form.Item>,
|
<InputNumber />
|
||||||
<Form.Item
|
</Form.Item>,
|
||||||
label={t("bodyshop.fields.rbac.ttapprovals.approve")}
|
<Form.Item
|
||||||
rules={[
|
label={t("bodyshop.fields.rbac.ttapprovals.approve")}
|
||||||
{
|
rules={[
|
||||||
required: true
|
{
|
||||||
//message: t("general.validation.required"),
|
required: true
|
||||||
}
|
//message: t("general.validation.required"),
|
||||||
]}
|
}
|
||||||
name={["md_rbac", "ttapprovals:approve"]}
|
]}
|
||||||
>
|
name={["md_rbac", "ttapprovals:approve"]}
|
||||||
<InputNumber />
|
>
|
||||||
</Form.Item>,
|
<InputNumber />
|
||||||
<Form.Item
|
</Form.Item>,
|
||||||
label={t("bodyshop.fields.rbac.timetickets.enter")}
|
<Form.Item
|
||||||
rules={[
|
label={t("bodyshop.fields.rbac.timetickets.enter")}
|
||||||
{
|
rules={[
|
||||||
required: true
|
{
|
||||||
//message: t("general.validation.required"),
|
required: true
|
||||||
}
|
//message: t("general.validation.required"),
|
||||||
]}
|
}
|
||||||
name={["md_rbac", "timetickets:enter"]}
|
]}
|
||||||
>
|
name={["md_rbac", "timetickets:enter"]}
|
||||||
<InputNumber />
|
>
|
||||||
</Form.Item>,
|
<InputNumber />
|
||||||
<Form.Item
|
</Form.Item>,
|
||||||
label={t("bodyshop.fields.rbac.timetickets.list")}
|
<Form.Item
|
||||||
rules={[
|
label={t("bodyshop.fields.rbac.timetickets.list")}
|
||||||
{
|
rules={[
|
||||||
required: true
|
{
|
||||||
//message: t("general.validation.required"),
|
required: true
|
||||||
}
|
//message: t("general.validation.required"),
|
||||||
]}
|
}
|
||||||
name={["md_rbac", "timetickets:list"]}
|
]}
|
||||||
>
|
name={["md_rbac", "timetickets:list"]}
|
||||||
<InputNumber />
|
>
|
||||||
</Form.Item>,
|
<InputNumber />
|
||||||
<Form.Item
|
</Form.Item>
|
||||||
label={t("bodyshop.fields.rbac.timetickets.shiftedit")}
|
]
|
||||||
rules={[
|
: []}
|
||||||
{
|
|
||||||
required: true
|
|
||||||
//message: t("general.validation.required"),
|
|
||||||
}
|
|
||||||
]}
|
|
||||||
name={["md_rbac", "timetickets:shiftedit"]}
|
|
||||||
>
|
|
||||||
<InputNumber />
|
|
||||||
</Form.Item>
|
|
||||||
]:[]}
|
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={t("bodyshop.fields.rbac.shop.vendors")}
|
label={t("bodyshop.fields.rbac.shop.vendors")}
|
||||||
rules={[
|
rules={[
|
||||||
@@ -757,7 +740,6 @@ export function ShopInfoRbacComponent({ form, bodyshop }) {
|
|||||||
<InputNumber />
|
<InputNumber />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={t("bodyshop.fields.rbac.users.editaccess")}
|
label={t("bodyshop.fields.rbac.users.editaccess")}
|
||||||
rules={[
|
rules={[
|
||||||
|
|||||||
@@ -1,14 +1,13 @@
|
|||||||
import { Col, Form, Input, Row, Select, Switch } from "antd";
|
import { Col, Form, Input, Row, Select, Switch } from "antd";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { FormDatePicker } from "../form-date-picker/form-date-picker.component.jsx";
|
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors.js";
|
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors.js";
|
||||||
import dayjs from "../../utils/day";
|
import dayjs from "../../utils/day";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component.jsx";
|
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component.jsx";
|
||||||
import JobSearchSelectComponent from "../job-search-select/job-search-select.component.jsx";
|
import JobSearchSelectComponent from "../job-search-select/job-search-select.component.jsx";
|
||||||
import { FormDateTimePickerEnhanced } from "../form-date-time-picker-enhanced/form-date-time-picker-enhanced.component.jsx";
|
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component.jsx";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop,
|
bodyshop: selectBodyshop,
|
||||||
@@ -246,7 +245,8 @@ export function TaskUpsertModalComponent({
|
|||||||
</Col>
|
</Col>
|
||||||
<Col span={8}>
|
<Col span={8}>
|
||||||
<Form.Item label={t("tasks.fields.due_date")} name="due_date">
|
<Form.Item label={t("tasks.fields.due_date")} name="due_date">
|
||||||
<FormDatePicker
|
<DateTimePicker
|
||||||
|
isDateOnly
|
||||||
onlyFuture
|
onlyFuture
|
||||||
format="MM/DD/YYYY"
|
format="MM/DD/YYYY"
|
||||||
presets={generatePresets(selectedJobDetails)}
|
presets={generatePresets(selectedJobDetails)}
|
||||||
@@ -278,12 +278,7 @@ export function TaskUpsertModalComponent({
|
|||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<FormDateTimePickerEnhanced
|
<DateTimePicker onlyFuture presets={generatePresets(selectedJobDetails)} />
|
||||||
onlyFuture
|
|
||||||
showTime
|
|
||||||
minuteStep={15}
|
|
||||||
presets={generatePresets(selectedJobDetails)}
|
|
||||||
/>
|
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { Button, Card, DatePicker, Form, Popover, Radio, Space } from "antd";
|
import { Button, Card, DatePicker, Form, Popover, Radio, Space } from "antd";
|
||||||
import dayjs from "../../utils/day";
|
|
||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
@@ -7,10 +6,12 @@ import { createStructuredSelector } from "reselect";
|
|||||||
import { logImEXEvent } from "../../firebase/firebase.utils";
|
import { logImEXEvent } from "../../firebase/firebase.utils";
|
||||||
import { selectTechnician } from "../../redux/tech/tech.selectors";
|
import { selectTechnician } from "../../redux/tech/tech.selectors";
|
||||||
import DatePIckerRanges from "../../utils/DatePickerRanges";
|
import DatePIckerRanges from "../../utils/DatePickerRanges";
|
||||||
|
import dayjs from "../../utils/day";
|
||||||
import { GenerateDocument } from "../../utils/RenderTemplate";
|
import { GenerateDocument } from "../../utils/RenderTemplate";
|
||||||
import { TemplateList } from "../../utils/TemplateConstants";
|
import { TemplateList } from "../../utils/TemplateConstants";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
|
bodyshop: selectTechnician,
|
||||||
technician: selectTechnician
|
technician: selectTechnician
|
||||||
});
|
});
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
@@ -18,7 +19,7 @@ const mapDispatchToProps = (dispatch) => ({
|
|||||||
});
|
});
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(TechJobPrintTickets);
|
export default connect(mapStateToProps, mapDispatchToProps)(TechJobPrintTickets);
|
||||||
|
|
||||||
export function TechJobPrintTickets({ technician, event, attendacePrint }) {
|
export function TechJobPrintTickets({ bodyshop, technician, event, attendacePrint }) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
@@ -57,7 +58,8 @@ export function TechJobPrintTickets({ technician, event, attendacePrint }) {
|
|||||||
subject:
|
subject:
|
||||||
attendacePrint === true ? Templates.attendance_employee.subject : Templates.timetickets_employee.subject
|
attendacePrint === true ? Templates.attendance_employee.subject : Templates.timetickets_employee.subject
|
||||||
},
|
},
|
||||||
values.sendby // === "email" ? "e" : "p"
|
values.sendby,
|
||||||
|
bodyshop
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
|
|||||||
@@ -7,9 +7,9 @@ import { connect } from "react-redux";
|
|||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import { GET_JOB_INFO_DRAW_CALCULATIONS } from "../../graphql/jobs-lines.queries";
|
import { GET_JOB_INFO_DRAW_CALCULATIONS } from "../../graphql/jobs-lines.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 JobSearchSelectComponent from "../job-search-select/job-search-select.component";
|
import JobSearchSelectComponent 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 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 TimeTicketListTeamPay({ bodyshop, context, actions }) {
|
|||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<FormDatePicker />
|
<DateTimePicker isDateOnly />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</LayoutFormRow>
|
</LayoutFormRow>
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { EditFilled, SyncOutlined } from "@ant-design/icons";
|
import { EditFilled, SyncOutlined } from "@ant-design/icons";
|
||||||
|
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
||||||
import { Button, Card, Checkbox, Space, Table } from "antd";
|
import { Button, Card, Checkbox, Space, Table } from "antd";
|
||||||
import dayjs from "../../utils/day";
|
|
||||||
import React, { useMemo, useState } from "react";
|
import React, { useMemo, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
@@ -10,10 +10,10 @@ import { setModalContext } from "../../redux/modals/modals.actions";
|
|||||||
import { selectAuthLevel, selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors";
|
import { selectAuthLevel, selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors";
|
||||||
import { DateFormatter, DateTimeFormatter } from "../../utils/DateFormatter";
|
import { DateFormatter, DateTimeFormatter } from "../../utils/DateFormatter";
|
||||||
import { onlyUnique } from "../../utils/arrayHelper";
|
import { onlyUnique } from "../../utils/arrayHelper";
|
||||||
|
import dayjs from "../../utils/day";
|
||||||
import { alphaSort, dateSort } from "../../utils/sorters";
|
import { alphaSort, dateSort } from "../../utils/sorters";
|
||||||
import RbacWrapper, { HasRbacAccess } from "../rbac-wrapper/rbac-wrapper.component";
|
import { HasRbacAccess } from "../rbac-wrapper/rbac-wrapper.component";
|
||||||
import TimeTicketEnterButton from "../time-ticket-enter-button/time-ticket-enter-button.component";
|
import TimeTicketEnterButton from "../time-ticket-enter-button/time-ticket-enter-button.component";
|
||||||
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop,
|
bodyshop: selectBodyshop,
|
||||||
@@ -52,6 +52,10 @@ export function TimeTicketList({
|
|||||||
splitKey: bodyshop.imexshopid
|
splitKey: bodyshop.imexshopid
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const canEditCommittedTimeTickets = HasRbacAccess({ bodyshop, authLevel, action: "timetickets:editcommitted" });
|
||||||
|
const canEditTimeTickets = HasRbacAccess({ bodyshop, authLevel, action: "timetickets:edit" });
|
||||||
|
const canEditShiftTickets = HasRbacAccess({ bodyshop, authLevel, action: "timetickets:shiftedit" });
|
||||||
|
|
||||||
const totals = useMemo(() => {
|
const totals = useMemo(() => {
|
||||||
if (timetickets)
|
if (timetickets)
|
||||||
return timetickets.reduce(
|
return timetickets.reduce(
|
||||||
@@ -65,6 +69,18 @@ export function TimeTicketList({
|
|||||||
return { productivehrs: 0, actualhrs: 0 };
|
return { productivehrs: 0, actualhrs: 0 };
|
||||||
}, [timetickets]);
|
}, [timetickets]);
|
||||||
|
|
||||||
|
const isDisabled = (record) => {
|
||||||
|
if (disabled === true || !record.id) return true;
|
||||||
|
|
||||||
|
const isShiftTicket = !record.ciecacode;
|
||||||
|
const isCommitted = record.committed_at;
|
||||||
|
if (isShiftTicket) {
|
||||||
|
return !(canEditShiftTickets && (!isCommitted || canEditCommittedTimeTickets));
|
||||||
|
}
|
||||||
|
|
||||||
|
return !(canEditTimeTickets && (!isCommitted || canEditCommittedTimeTickets));
|
||||||
|
};
|
||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
...(Enhanced_Payroll.treatment === "on"
|
...(Enhanced_Payroll.treatment === "on"
|
||||||
? [
|
? [
|
||||||
@@ -165,7 +181,7 @@ export function TimeTicketList({
|
|||||||
key: "memo",
|
key: "memo",
|
||||||
sorter: (a, b) => alphaSort(a.memo, b.memo),
|
sorter: (a, b) => alphaSort(a.memo, b.memo),
|
||||||
sortOrder: state.sortedInfo.columnKey === "memo" && state.sortedInfo.order,
|
sortOrder: state.sortedInfo.columnKey === "memo" && state.sortedInfo.order,
|
||||||
render: (text, record) => (record.clockon || record.clockoff ? t(record.memo) : record.memo)
|
render: (text, record) => (record.memo?.startsWith("timetickets.labels") ? t(record.memo) : record.memo)
|
||||||
},
|
},
|
||||||
...(Enhanced_Payroll.treatment === "on"
|
...(Enhanced_Payroll.treatment === "on"
|
||||||
? [
|
? [
|
||||||
@@ -206,76 +222,55 @@ export function TimeTicketList({
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
|
|
||||||
]),
|
]),
|
||||||
{
|
{
|
||||||
title: t("timetickets.fields.created_by"),
|
title: t("timetickets.fields.created_by"),
|
||||||
dataIndex: "created_by",
|
dataIndex: "created_by",
|
||||||
key: "created_by",
|
key: "created_by",
|
||||||
sorter: (a, b) => alphaSort(a.created_by, b.created_by),
|
sorter: (a, b) => alphaSort(a.created_by, b.created_by),
|
||||||
sortOrder: state.sortedInfo.columnKey === "created_by" && state.sortedInfo.order,
|
sortOrder: state.sortedInfo.columnKey === "created_by" && state.sortedInfo.order,
|
||||||
render: (text, record) => record.created_by
|
render: (text, record) => record.created_by
|
||||||
},
|
},
|
||||||
// {
|
// {
|
||||||
// title: "Pay",
|
// title: "Pay",
|
||||||
// dataIndex: "pay",
|
// dataIndex: "pay",
|
||||||
// key: "pay",
|
// key: "pay",
|
||||||
// render: (text, record) =>
|
// render: (text, record) =>
|
||||||
// Dinero({ amount: Math.round(record.rate * 100) })
|
// Dinero({ amount: Math.round(record.rate * 100) })
|
||||||
// .multiply(record.flat_rate ? record.productivehrs : record.actualhrs)
|
// .multiply(record.flat_rate ? record.productivehrs : record.actualhrs)
|
||||||
// .toFormat("$0.00"),
|
// .toFormat("$0.00"),
|
||||||
// },
|
// },
|
||||||
{
|
{
|
||||||
title: t("general.labels.actions"),
|
title: t("general.labels.actions"),
|
||||||
dataIndex: "actions",
|
dataIndex: "actions",
|
||||||
key: "actions",
|
key: "actions",
|
||||||
render: (text, record) => (
|
render: (text, record) => (
|
||||||
<Space wrap>
|
<Space wrap>
|
||||||
{techConsole && (
|
{techConsole && (
|
||||||
<TimeTicketEnterButton
|
<TimeTicketEnterButton
|
||||||
actions={{ refetch }}
|
actions={{ refetch }}
|
||||||
context={{ id: record.id, timeticket: record }}
|
context={{ id: record.id, timeticket: record }}
|
||||||
disabled={!record.job || disabled}
|
disabled={!record.job || disabled}
|
||||||
>
|
>
|
||||||
<EditFilled />
|
<EditFilled />
|
||||||
</TimeTicketEnterButton>
|
</TimeTicketEnterButton>
|
||||||
)}
|
)}
|
||||||
{!techConsole && (
|
{!techConsole && (
|
||||||
<RbacWrapper
|
<TimeTicketEnterButton
|
||||||
action="timetickets:edit"
|
actions={{ refetch }}
|
||||||
noauth={() => {
|
context={{
|
||||||
return <div />;
|
id: record.id,
|
||||||
}}
|
timeticket: record
|
||||||
>
|
}}
|
||||||
<TimeTicketEnterButton
|
disabled={isDisabled(record)}
|
||||||
actions={{ refetch }}
|
>
|
||||||
context={{
|
<EditFilled />
|
||||||
id: record.id,
|
</TimeTicketEnterButton>
|
||||||
timeticket: record
|
)}
|
||||||
}}
|
</Space>
|
||||||
disabled={
|
)
|
||||||
HasRbacAccess({
|
}
|
||||||
bodyshop,
|
|
||||||
authLevel: authLevel,
|
|
||||||
action: "timetickets:editcommitted"
|
|
||||||
}) &&
|
|
||||||
HasRbacAccess({
|
|
||||||
bodyshop,
|
|
||||||
authLevel: authLevel,
|
|
||||||
action: "timetickets:shiftedit"
|
|
||||||
})
|
|
||||||
? disabled
|
|
||||||
: !record.jobid
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<EditFilled />
|
|
||||||
</TimeTicketEnterButton>
|
|
||||||
</RbacWrapper>
|
|
||||||
)}
|
|
||||||
</Space>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
];
|
];
|
||||||
|
|
||||||
const handleTableChange = (pagination, filters, sorter) => {
|
const handleTableChange = (pagination, filters, sorter) => {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { useLazyQuery } from "@apollo/client";
|
import { useLazyQuery } from "@apollo/client";
|
||||||
|
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
||||||
import { Form, Input, InputNumber, Select, Switch } from "antd";
|
import { Form, Input, InputNumber, Select, Switch } from "antd";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
@@ -7,8 +8,10 @@ import { createStructuredSelector } from "reselect";
|
|||||||
import { GET_LINE_TICKET_BY_PK } from "../../graphql/jobs-lines.queries";
|
import { GET_LINE_TICKET_BY_PK } from "../../graphql/jobs-lines.queries";
|
||||||
import { selectAuthLevel, selectBodyshop } from "../../redux/user/user.selectors";
|
import { selectAuthLevel, selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
import EmployeeSearchSelect from "../employee-search-select/employee-search-select.component";
|
import EmployeeSearchSelect from "../employee-search-select/employee-search-select.component";
|
||||||
import FormDatePicker from "../form-date-picker/form-date-picker.component";
|
import {
|
||||||
import FormDateTimePicker from "../form-date-time-picker/form-date-time-picker.component";
|
default as DateTimePicker,
|
||||||
|
default as FormDateTimePicker
|
||||||
|
} from "../form-date-time-picker/form-date-time-picker.component";
|
||||||
import JobSearchSelect from "../job-search-select/job-search-select.component";
|
import JobSearchSelect from "../job-search-select/job-search-select.component";
|
||||||
import LaborAllocationsTable from "../labor-allocations-table/labor-allocations-table.component";
|
import LaborAllocationsTable from "../labor-allocations-table/labor-allocations-table.component";
|
||||||
import { CalculateAllocationsTotals } from "../labor-allocations-table/labor-allocations-table.utility";
|
import { CalculateAllocationsTotals } from "../labor-allocations-table/labor-allocations-table.utility";
|
||||||
@@ -16,7 +19,6 @@ import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
|||||||
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component";
|
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component";
|
||||||
import { HasRbacAccess } from "../rbac-wrapper/rbac-wrapper.component";
|
import { HasRbacAccess } from "../rbac-wrapper/rbac-wrapper.component";
|
||||||
import TimeTicketList from "../time-ticket-list/time-ticket-list.component";
|
import TimeTicketList from "../time-ticket-list/time-ticket-list.component";
|
||||||
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop,
|
bodyshop: selectBodyshop,
|
||||||
@@ -60,8 +62,8 @@ export function TimeTicketModalComponent({
|
|||||||
{item.cost_center === "timetickets.labels.shift"
|
{item.cost_center === "timetickets.labels.shift"
|
||||||
? t(item.cost_center)
|
? t(item.cost_center)
|
||||||
: bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber || Enhanced_Payroll.treatment === "on"
|
: bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber || Enhanced_Payroll.treatment === "on"
|
||||||
? t(`joblines.fields.lbr_types.${item.cost_center.toUpperCase()}`)
|
? t(`joblines.fields.lbr_types.${item.cost_center.toUpperCase()}`)
|
||||||
: item.cost_center}
|
: item.cost_center}
|
||||||
</Select.Option>
|
</Select.Option>
|
||||||
))}
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
@@ -69,13 +71,7 @@ export function TimeTicketModalComponent({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const MemoInput = ({ value, ...props }) => {
|
const MemoInput = ({ value, ...props }) => {
|
||||||
return (
|
return <Input value={value?.startsWith("timetickets.labels") ? t(value) : value} {...props} />;
|
||||||
<Input
|
|
||||||
value={value?.startsWith("timetickets.") ? t(value) : value}
|
|
||||||
{...props}
|
|
||||||
disabled={value?.startsWith("timetickets.") || disabled}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -111,7 +107,7 @@ export function TimeTicketModalComponent({
|
|||||||
}
|
}
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<FormDatePicker />
|
<DateTimePicker isDateOnly />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name="employeeid"
|
name="employeeid"
|
||||||
@@ -333,7 +329,9 @@ export function LaborAllocationContainer({ jobid, loading, lineTicketData, hideT
|
|||||||
timetickets={lineTicketData.timetickets}
|
timetickets={lineTicketData.timetickets}
|
||||||
adjustments={lineTicketData.jobs_by_pk.lbr_adjustments}
|
adjustments={lineTicketData.jobs_by_pk.lbr_adjustments}
|
||||||
/>
|
/>
|
||||||
{!hideTimeTickets && <TimeTicketList loading={loading} timetickets={lineTicketData.timetickets} techConsole />}
|
{!hideTimeTickets && (
|
||||||
|
<TimeTicketList loading={loading} timetickets={jobid ? lineTicketData.timetickets : []} techConsole />
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ export default function TimeTicketShiftActive({ timetickets, refetch, isTechCons
|
|||||||
renderItem={(ticket) => (
|
renderItem={(ticket) => (
|
||||||
<List.Item>
|
<List.Item>
|
||||||
<Card
|
<Card
|
||||||
title={t(ticket.memo)}
|
title={ticket.memo?.startsWith("timetickets.labels") ? t(ticket.memo) : ticket.memo}
|
||||||
actions={[
|
actions={[
|
||||||
<TechClockOffButton
|
<TechClockOffButton
|
||||||
jobId={ticket.jobid}
|
jobId={ticket.jobid}
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
import { AlertOutlined } from "@ant-design/icons";
|
import { AlertOutlined } from "@ant-design/icons";
|
||||||
import { Alert, Button, Col, Row, Space } from "antd";
|
import { Alert, Button, Col, notification, Row, Space } from "antd";
|
||||||
import i18n from "i18next";
|
import i18n from "i18next";
|
||||||
import React, { useEffect } from "react";
|
import React, { useCallback, useEffect, 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 { selectUpdateAvailable } from "../../redux/application/application.selectors";
|
import { selectUpdateAvailable } from "../../redux/application/application.selectors";
|
||||||
import { useRegisterSW } from "virtual:pwa-register/react";
|
import { useRegisterSW } from "virtual:pwa-register/react";
|
||||||
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
||||||
|
import useCountDown from "../../utils/countdownHook";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
updateAvailable: selectUpdateAvailable
|
updateAvailable: selectUpdateAvailable
|
||||||
@@ -19,6 +20,15 @@ const mapDispatchToProps = (dispatch) => ({
|
|||||||
|
|
||||||
export function UpdateAlert({ updateAvailable }) {
|
export function UpdateAlert({ updateAvailable }) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const [timerStarted, setTimerStarted] = useState(false);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [
|
||||||
|
timeLeft,
|
||||||
|
{
|
||||||
|
start //pause, resume, reset
|
||||||
|
}
|
||||||
|
] = useCountDown(180000, 1000);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
offlineReady: [offlineReady],
|
offlineReady: [offlineReady],
|
||||||
needRefresh: [needRefresh],
|
needRefresh: [needRefresh],
|
||||||
@@ -31,7 +41,7 @@ export function UpdateAlert({ updateAvailable }) {
|
|||||||
() => {
|
() => {
|
||||||
r.update();
|
r.update();
|
||||||
},
|
},
|
||||||
10 * 60 * 1000
|
30 * 60 * 1000
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -40,11 +50,43 @@ export function UpdateAlert({ updateAvailable }) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const ReloadNewVersion = useCallback(() => {
|
||||||
|
setLoading(true);
|
||||||
|
updateServiceWorker(true);
|
||||||
|
setTimeout(() => {
|
||||||
|
window.location.reload(true);
|
||||||
|
}, 5000);
|
||||||
|
}, [updateServiceWorker]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (import.meta.env.DEV) {
|
if (needRefresh) {
|
||||||
console.log(`SW Status => Refresh? ${needRefresh} - offlineReady? ${offlineReady}`);
|
start();
|
||||||
|
setTimerStarted(true);
|
||||||
}
|
}
|
||||||
}, [needRefresh, offlineReady]);
|
}, [start, needRefresh, offlineReady]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (needRefresh && timerStarted && timeLeft < 60000) {
|
||||||
|
notification.open({
|
||||||
|
type: "warning",
|
||||||
|
closable: false,
|
||||||
|
duration: 65000,
|
||||||
|
key: "autoupdate",
|
||||||
|
message: t("general.actions.autoupdate", {
|
||||||
|
time: (timeLeft / 1000).toFixed(0),
|
||||||
|
app: InstanceRenderManager({
|
||||||
|
imex: "$t(titles.imexonline)",
|
||||||
|
rome: "$t(titles.romeonline)",
|
||||||
|
promanager: "$t(titles.promanager)"
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
placement: "bottomRight"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (needRefresh && timerStarted && timeLeft <= 0) {
|
||||||
|
ReloadNewVersion();
|
||||||
|
}
|
||||||
|
}, [timeLeft, t, needRefresh, ReloadNewVersion, timerStarted]);
|
||||||
|
|
||||||
if (!needRefresh) return null;
|
if (!needRefresh) return null;
|
||||||
|
|
||||||
@@ -75,9 +117,10 @@ export function UpdateAlert({ updateAvailable }) {
|
|||||||
<Button onClick={() => window.open("https://imex-online.noticeable.news/", "_blank")}>
|
<Button onClick={() => window.open("https://imex-online.noticeable.news/", "_blank")}>
|
||||||
{i18n.t("general.actions.viewreleasenotes")}
|
{i18n.t("general.actions.viewreleasenotes")}
|
||||||
</Button>
|
</Button>
|
||||||
<Button type="primary" onClick={() => updateServiceWorker(true)}>
|
<Button loading={loading} type="primary" onClick={() => ReloadNewVersion()}>
|
||||||
{i18n.t("general.actions.refresh")}
|
{i18n.t("general.actions.refresh")} {`(${(timeLeft / 1000).toFixed(0)} s)`}
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button onClick={() => start(300000)}>{i18n.t("general.actions.delay")}</Button>
|
||||||
</Space>
|
</Space>
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { Form, Input } from "antd";
|
import { Form, Input } from "antd";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
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 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 VehicleDetailFormComponent({ form, loading }) {
|
export default function VehicleDetailFormComponent({ form, loading }) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@@ -102,7 +102,7 @@ export default function VehicleDetailFormComponent({ form, loading }) {
|
|||||||
<Input />
|
<Input />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label={t("vehicles.fields.v_prod_dt")} name="v_prod_dt">
|
<Form.Item label={t("vehicles.fields.v_prod_dt")} name="v_prod_dt">
|
||||||
<FormDatePicker />
|
<DateTimePicker isDateOnly />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item label={t("vehicles.fields.v_paint_codes", { number: 1 })} name={["v_paint_codes", "paint_cd1"]}>
|
<Form.Item label={t("vehicles.fields.v_paint_codes", { number: 1 })} name={["v_paint_codes", "paint_cd1"]}>
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import PhoneNumberFormatter from "../../utils/PhoneFormatter";
|
|||||||
|
|
||||||
const { Option } = Select;
|
const { Option } = Select;
|
||||||
|
|
||||||
//To be used as a form element only.
|
// To be used as a form element only.
|
||||||
|
|
||||||
const VendorSearchSelect = ({ value, onChange, options, onSelect, disabled, preferredMake, showPhone }, ref) => {
|
const VendorSearchSelect = ({ value, onChange, options, onSelect, disabled, preferredMake, showPhone }, ref) => {
|
||||||
const [option, setOption] = useState(value);
|
const [option, setOption] = useState(value);
|
||||||
@@ -33,9 +33,25 @@ const VendorSearchSelect = ({ value, onChange, options, onSelect, disabled, pref
|
|||||||
if (!value || !options) return label;
|
if (!value || !options) return label;
|
||||||
const discount = options?.find((o) => o.id === value)?.discount;
|
const discount = options?.find((o) => o.id === value)?.discount;
|
||||||
return (
|
return (
|
||||||
<div className="imex-flex-row" style={{ width: "100%" }}>
|
<div
|
||||||
<div style={{ flex: 1 }}>{label}</div>
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
flexWrap: "nowrap",
|
||||||
|
width: "100%"
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
flex: 1,
|
||||||
|
minWidth: 0,
|
||||||
|
overflow: "hidden",
|
||||||
|
textOverflow: "ellipsis",
|
||||||
|
whiteSpace: "nowrap"
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{label}
|
||||||
|
</div>
|
||||||
{discount && discount !== 0 ? <Tag color="green">{`${discount * 100}%`}</Tag> : null}
|
{discount && discount !== 0 ? <Tag color="green">{`${discount * 100}%`}</Tag> : null}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -45,36 +61,67 @@ const VendorSearchSelect = ({ value, onChange, options, onSelect, disabled, pref
|
|||||||
optionFilterProp="name"
|
optionFilterProp="name"
|
||||||
onSelect={onSelect}
|
onSelect={onSelect}
|
||||||
disabled={disabled || false}
|
disabled={disabled || false}
|
||||||
optionLabelProp={"name"}
|
optionLabelProp="name"
|
||||||
>
|
>
|
||||||
{favorites
|
{favorites &&
|
||||||
? favorites.map((o) => (
|
favorites.map((o) => (
|
||||||
<Option key={`favorite-${o.id}`} value={o.id} name={o.name} discount={o.discount}>
|
<Option key={`favorite-${o.id}`} value={o.id} name={o.name} discount={o.discount}>
|
||||||
<div className="imex-flex-row">
|
<div
|
||||||
<div style={{ flex: 1 }}>{o.name}</div>
|
style={{
|
||||||
<Space style={{ marginLeft: "1rem" }}>
|
display: "flex",
|
||||||
<HeartOutlined style={{ color: "red" }} />
|
alignItems: "center",
|
||||||
{o.phone && showPhone && <PhoneNumberFormatter>{o.phone}</PhoneNumberFormatter>}
|
flexWrap: "nowrap",
|
||||||
{o.discount && o.discount !== 0 ? <Tag color="green">{`${o.discount * 100}%`}</Tag> : null}
|
width: "100%"
|
||||||
</Space>
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
flex: 1,
|
||||||
|
minWidth: 0,
|
||||||
|
overflow: "hidden",
|
||||||
|
textOverflow: "ellipsis",
|
||||||
|
whiteSpace: "nowrap"
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{o.name}
|
||||||
</div>
|
</div>
|
||||||
</Option>
|
<Space style={{ marginLeft: "1rem" }}>
|
||||||
))
|
<HeartOutlined style={{ color: "red" }} />
|
||||||
: null}
|
{o.phone && showPhone && <PhoneNumberFormatter>{o.phone}</PhoneNumberFormatter>}
|
||||||
{options
|
{o.discount && o.discount !== 0 ? <Tag color="green">{`${o.discount * 100}%`}</Tag> : null}
|
||||||
? options.map((o) => (
|
</Space>
|
||||||
<Option key={o.id} value={o.id} name={o.name} discount={o.discount}>
|
</div>
|
||||||
<div className="imex-flex-row" style={{ width: "100%" }}>
|
</Option>
|
||||||
<div style={{ flex: 1 }}>{o.name}</div>
|
))}
|
||||||
|
{options &&
|
||||||
<Space style={{ marginLeft: "1rem" }}>
|
options.map((o) => (
|
||||||
{o.phone && showPhone && <PhoneNumberFormatter>{o.phone}</PhoneNumberFormatter>}
|
<Option key={o.id} value={o.id} name={o.name} discount={o.discount}>
|
||||||
{o.discount && o.discount !== 0 ? <Tag color="green">{`${o.discount * 100}%`}</Tag> : null}
|
<div
|
||||||
</Space>
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
flexWrap: "nowrap",
|
||||||
|
width: "100%"
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
flex: 1,
|
||||||
|
minWidth: 0,
|
||||||
|
overflow: "hidden",
|
||||||
|
textOverflow: "ellipsis",
|
||||||
|
whiteSpace: "nowrap"
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{o.name}
|
||||||
</div>
|
</div>
|
||||||
</Option>
|
<Space style={{ marginLeft: "1rem" }}>
|
||||||
))
|
{o.phone && showPhone && <PhoneNumberFormatter>{o.phone}</PhoneNumberFormatter>}
|
||||||
: null}
|
{o.discount && o.discount !== 0 ? <Tag color="green">{`${o.discount * 100}%`}</Tag> : null}
|
||||||
|
</Space>
|
||||||
|
</div>
|
||||||
|
</Option>
|
||||||
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import { getFirestore } from "firebase/firestore";
|
|||||||
import { getMessaging, getToken, onMessage } from "firebase/messaging";
|
import { getMessaging, getToken, onMessage } from "firebase/messaging";
|
||||||
import { store } from "../redux/store";
|
import { store } from "../redux/store";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import { checkBeta } from "../utils/handleBeta";
|
|
||||||
|
|
||||||
const config = JSON.parse(import.meta.env.VITE_APP_FIREBASE_CONFIG);
|
const config = JSON.parse(import.meta.env.VITE_APP_FIREBASE_CONFIG);
|
||||||
initializeApp(config);
|
initializeApp(config);
|
||||||
@@ -88,7 +87,7 @@ export const logImEXEvent = (eventName, additionalParams, stateProp = null) => {
|
|||||||
operationName: eventName,
|
operationName: eventName,
|
||||||
variables: additionalParams,
|
variables: additionalParams,
|
||||||
dbevent: false,
|
dbevent: false,
|
||||||
env: checkBeta() ? "beta" : "master"
|
env: "master"
|
||||||
});
|
});
|
||||||
// console.log(
|
// console.log(
|
||||||
// "%c[Analytics]",
|
// "%c[Analytics]",
|
||||||
|
|||||||
@@ -2461,6 +2461,14 @@ export const SUBSCRIPTION_JOBS_IN_PRODUCTION = gql`
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
export const SUBSCRIPTION_JOBS_IN_PRODUCTION_VIEW = gql`
|
||||||
|
subscription SUBSCRIPTION_JOBS_IN_PRODUCTION_VIEW {
|
||||||
|
jobs: jobs_inproduction {
|
||||||
|
id
|
||||||
|
updated_at
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
export const QUERY_JOBS_IN_PRODUCTION = gql`
|
export const QUERY_JOBS_IN_PRODUCTION = gql`
|
||||||
query QUERY_JOBS_IN_PRODUCTION {
|
query QUERY_JOBS_IN_PRODUCTION {
|
||||||
|
|||||||
@@ -571,7 +571,7 @@ export function Manage({ conflict, bodyshop }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ChatAffixContainer bodyshop={bodyshop} chatVisible={chatVisible} />
|
{import.meta.env.PROD && <ChatAffixContainer bodyshop={bodyshop} chatVisible={chatVisible} />}
|
||||||
<Layout style={{ minHeight: "100vh" }} className="layout-container">
|
<Layout style={{ minHeight: "100vh" }} className="layout-container">
|
||||||
<UpdateAlert />
|
<UpdateAlert />
|
||||||
<HeaderContainer />
|
<HeaderContainer />
|
||||||
|
|||||||
@@ -1,6 +1,26 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import ProductionBoardKanbanContainer from "../../components/production-board-kanban/production-board-kanban.container";
|
import ProductionBoardKanbanContainer from "../../components/production-board-kanban/production-board-kanban.container";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
import { createStructuredSelector } from "reselect";
|
||||||
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
|
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
||||||
|
const mapStateToProps = createStructuredSelector({
|
||||||
|
//currentUser: selectCurrentUser
|
||||||
|
bodyshop: selectBodyshop
|
||||||
|
});
|
||||||
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
|
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||||
|
});
|
||||||
|
export default connect(mapStateToProps, mapDispatchToProps)(ProductionBoardComponent);
|
||||||
|
|
||||||
export default function ProductionBoardComponent() {
|
export function ProductionBoardComponent({ bodyshop }) {
|
||||||
return <ProductionBoardKanbanContainer />;
|
const {
|
||||||
|
treatments: { Production_Use_View }
|
||||||
|
} = useSplitTreatments({
|
||||||
|
attributes: {},
|
||||||
|
names: ["Production_Use_View"],
|
||||||
|
splitKey: bodyshop && bodyshop.imexshopid
|
||||||
|
});
|
||||||
|
|
||||||
|
return <ProductionBoardKanbanContainer subscriptionType={Production_Use_View.treatment} />;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,11 +2,31 @@ import React from "react";
|
|||||||
import NoteUpsertModal from "../../components/note-upsert-modal/note-upsert-modal.container";
|
import NoteUpsertModal from "../../components/note-upsert-modal/note-upsert-modal.container";
|
||||||
import ProductionListTable from "../../components/production-list-table/production-list-table.container";
|
import ProductionListTable from "../../components/production-list-table/production-list-table.container";
|
||||||
|
|
||||||
export default function ProductionListComponent() {
|
import { connect } from "react-redux";
|
||||||
|
import { createStructuredSelector } from "reselect";
|
||||||
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
|
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
||||||
|
const mapStateToProps = createStructuredSelector({
|
||||||
|
bodyshop: selectBodyshop
|
||||||
|
});
|
||||||
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
|
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||||
|
});
|
||||||
|
export default connect(mapStateToProps, mapDispatchToProps)(ProductionListComponent);
|
||||||
|
|
||||||
|
export function ProductionListComponent({ bodyshop }) {
|
||||||
|
const {
|
||||||
|
treatments: { Production_Use_View }
|
||||||
|
} = useSplitTreatments({
|
||||||
|
attributes: {},
|
||||||
|
names: ["Production_Use_View"],
|
||||||
|
splitKey: bodyshop && bodyshop.imexshopid
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<NoteUpsertModal />
|
<NoteUpsertModal />
|
||||||
<ProductionListTable />
|
<ProductionListTable subscriptionType={Production_Use_View.treatment} />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { useTranslation } from "react-i18next";
|
|||||||
import RbacWrapperComponent from "../../components/rbac-wrapper/rbac-wrapper.component";
|
import RbacWrapperComponent from "../../components/rbac-wrapper/rbac-wrapper.component";
|
||||||
import TechLookupJobsList from "../../components/tech-lookup-jobs-list/tech-lookup-jobs-list.component";
|
import TechLookupJobsList from "../../components/tech-lookup-jobs-list/tech-lookup-jobs-list.component";
|
||||||
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
||||||
|
import TechLookupJobsDrawer from "../../components/tech-lookup-jobs-drawer/tech-lookup-jobs-drawer.component";
|
||||||
|
|
||||||
export default function TechLookupContainer() {
|
export default function TechLookupContainer() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@@ -20,6 +21,7 @@ export default function TechLookupContainer() {
|
|||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<RbacWrapperComponent action="jobs:list-active">
|
<RbacWrapperComponent action="jobs:list-active">
|
||||||
|
<TechLookupJobsDrawer />
|
||||||
<TechLookupJobsList />
|
<TechLookupJobsList />
|
||||||
</RbacWrapperComponent>
|
</RbacWrapperComponent>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import ErrorBoundary from "../../components/error-boundary/error-boundary.compon
|
|||||||
import FeatureWrapper from "../../components/feature-wrapper/feature-wrapper.component";
|
import FeatureWrapper from "../../components/feature-wrapper/feature-wrapper.component";
|
||||||
import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component";
|
import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component";
|
||||||
import TechHeader from "../../components/tech-header/tech-header.component";
|
import TechHeader from "../../components/tech-header/tech-header.component";
|
||||||
import TechLookupJobsDrawer from "../../components/tech-lookup-jobs-drawer/tech-lookup-jobs-drawer.component";
|
|
||||||
import TechSider from "../../components/tech-sider/tech-sider.component";
|
import TechSider from "../../components/tech-sider/tech-sider.component";
|
||||||
import UpdateAlert from "../../components/update-alert/update-alert.component";
|
import UpdateAlert from "../../components/update-alert/update-alert.component";
|
||||||
import { selectTechnician } from "../../redux/tech/tech.selectors";
|
import { selectTechnician } from "../../redux/tech/tech.selectors";
|
||||||
@@ -68,7 +67,7 @@ export function TechPage({ technician }) {
|
|||||||
<Layout>
|
<Layout>
|
||||||
<UpdateAlert />
|
<UpdateAlert />
|
||||||
<TechHeader />
|
<TechHeader />
|
||||||
<TechLookupJobsDrawer />
|
|
||||||
<TaskUpsertModalContainer />
|
<TaskUpsertModalContainer />
|
||||||
<Content className="tech-content-container">
|
<Content className="tech-content-container">
|
||||||
<ErrorBoundary>
|
<ErrorBoundary>
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import {
|
|||||||
signInWithEmailAndPassword,
|
signInWithEmailAndPassword,
|
||||||
signOut
|
signOut
|
||||||
} from "firebase/auth";
|
} from "firebase/auth";
|
||||||
import { doc, getDoc, setDoc } from "firebase/firestore";
|
import { arrayUnion, doc, getDoc, setDoc, updateDoc } from "firebase/firestore";
|
||||||
import { getToken } from "firebase/messaging";
|
import { getToken } from "firebase/messaging";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import LogRocket from "logrocket";
|
import LogRocket from "logrocket";
|
||||||
@@ -48,6 +48,7 @@ import {
|
|||||||
validatePasswordResetSuccess
|
validatePasswordResetSuccess
|
||||||
} from "./user.actions";
|
} from "./user.actions";
|
||||||
import UserActionTypes from "./user.types";
|
import UserActionTypes from "./user.types";
|
||||||
|
import cleanAxios from "../../utils/CleanAxios";
|
||||||
|
|
||||||
const fpPromise = FingerprintJS.load();
|
const fpPromise = FingerprintJS.load();
|
||||||
|
|
||||||
@@ -177,10 +178,24 @@ export function* setInstanceIdSaga({ payload: uid }) {
|
|||||||
// Get the visitor identifier when you need it.
|
// Get the visitor identifier when you need it.
|
||||||
const fp = yield fpPromise;
|
const fp = yield fpPromise;
|
||||||
const result = yield fp.get();
|
const result = yield fp.get();
|
||||||
yield setDoc(userInstanceRef, {
|
const res = yield cleanAxios.get("https://api.ipify.org/?format=json");
|
||||||
timestamp: new Date(),
|
const udoc = yield getDoc(userInstanceRef);
|
||||||
fingerprint: result.visitorId
|
|
||||||
});
|
if (!udoc.data()) {
|
||||||
|
yield setDoc(userInstanceRef, {
|
||||||
|
timestamp: new Date(),
|
||||||
|
fingerprint: result.visitorId,
|
||||||
|
//totalFingerprint: result,
|
||||||
|
ip: [res.data.ip]
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
yield updateDoc(userInstanceRef, {
|
||||||
|
timestamp: new Date(),
|
||||||
|
fingerprint: result.visitorId,
|
||||||
|
//totalFingerprint: result,
|
||||||
|
ip: arrayUnion(res.data.ip)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
yield put(setLocalFingerprint(result.visitorId));
|
yield put(setLocalFingerprint(result.visitorId));
|
||||||
yield delay(5 * 60 * 1000);
|
yield delay(5 * 60 * 1000);
|
||||||
|
|||||||
@@ -230,7 +230,7 @@
|
|||||||
"markexported": "Mark Exported",
|
"markexported": "Mark Exported",
|
||||||
"markforreexport": "Mark for Re-export",
|
"markforreexport": "Mark for Re-export",
|
||||||
"new": "New Bill",
|
"new": "New Bill",
|
||||||
"nobilllines": "This part has not yet been recieved.",
|
"nobilllines": "",
|
||||||
"noneselected": "No bill selected.",
|
"noneselected": "No bill selected.",
|
||||||
"onlycmforinvoiced": "Only credit memos can be entered for any Job that has been invoiced, exported, or voided.",
|
"onlycmforinvoiced": "Only credit memos can be entered for any Job that has been invoiced, exported, or voided.",
|
||||||
"printlabels": "Print Labels",
|
"printlabels": "Print Labels",
|
||||||
@@ -270,6 +270,7 @@
|
|||||||
"testrender": "Test Render"
|
"testrender": "Test Render"
|
||||||
},
|
},
|
||||||
"errors": {
|
"errors": {
|
||||||
|
"creatingdefaultview": "Error creating default view.",
|
||||||
"loading": "Unable to load shop details. Please call technical support.",
|
"loading": "Unable to load shop details. Please call technical support.",
|
||||||
"saving": "Error encountered while saving. {{message}}"
|
"saving": "Error encountered while saving. {{message}}"
|
||||||
},
|
},
|
||||||
@@ -284,19 +285,21 @@
|
|||||||
},
|
},
|
||||||
"appt_length": "Default Appointment Length",
|
"appt_length": "Default Appointment Length",
|
||||||
"attach_pdf_to_email": "Attach PDF copy to sent emails?",
|
"attach_pdf_to_email": "Attach PDF copy to sent emails?",
|
||||||
|
"batchid": "ADP Batch ID",
|
||||||
"bill_allow_post_to_closed": "Allow Bills to be posted to Closed Jobs",
|
"bill_allow_post_to_closed": "Allow Bills to be posted to Closed Jobs",
|
||||||
"bill_federal_tax_rate": "Bills - Federal Tax Rate %",
|
"bill_federal_tax_rate": "Bills - Federal Tax Rate %",
|
||||||
"bill_local_tax_rate": "Bill - Local Tax Rate %",
|
"bill_local_tax_rate": "Bill - Local Tax Rate %",
|
||||||
"bill_state_tax_rate": "Bill - Provincial/State Tax Rate %",
|
"bill_state_tax_rate": "Bill - Provincial/State Tax Rate %",
|
||||||
"city": "City",
|
"city": "City",
|
||||||
"closingperiod": "Closing Period",
|
"closingperiod": "Closing Period",
|
||||||
|
"companycode": "ADP Company Code",
|
||||||
"country": "Country",
|
"country": "Country",
|
||||||
"dailybodytarget": "Scoreboard - Daily Body Target",
|
"dailybodytarget": "Scoreboard - Daily Body Target",
|
||||||
"dailypainttarget": "Scoreboard - Daily Paint Target",
|
"dailypainttarget": "Scoreboard - Daily Paint Target",
|
||||||
"default_adjustment_rate": "Default Labor Deduction Adjustment Rate",
|
"default_adjustment_rate": "Default Labor Deduction Adjustment Rate",
|
||||||
"deliver": {
|
"deliver": {
|
||||||
"templates": "Delivery Templates",
|
"require_actual_delivery_date": "Require Actual Delivery",
|
||||||
"require_actual_delivery_date": "Require Actual Delivery"
|
"templates": "Delivery Templates"
|
||||||
},
|
},
|
||||||
"dms": {
|
"dms": {
|
||||||
"apcontrol": "AP Control Number",
|
"apcontrol": "AP Control Number",
|
||||||
@@ -699,7 +702,10 @@
|
|||||||
"workingdays": "Working Days"
|
"workingdays": "Working Days"
|
||||||
},
|
},
|
||||||
"successes": {
|
"successes": {
|
||||||
"save": "Shop configuration saved successfully. "
|
"areyousure": "Are you sure you want to continue?",
|
||||||
|
"defaultviewcreated": "Default view created successfully.",
|
||||||
|
"save": "Shop configuration saved successfully. ",
|
||||||
|
"unsavedchanges": "Unsaved changes will be lost. Are you sure you want to continue?"
|
||||||
},
|
},
|
||||||
"validation": {
|
"validation": {
|
||||||
"centermustexist": "The chosen responsibility center does not exist.",
|
"centermustexist": "The chosen responsibility center does not exist.",
|
||||||
@@ -1129,8 +1135,8 @@
|
|||||||
},
|
},
|
||||||
"general": {
|
"general": {
|
||||||
"actions": {
|
"actions": {
|
||||||
"defaults": "Defaults",
|
|
||||||
"add": "Add",
|
"add": "Add",
|
||||||
|
"autoupdate": "{{app}} will automatically update in {{time}} seconds. Please save all changes.",
|
||||||
"calculate": "Calculate",
|
"calculate": "Calculate",
|
||||||
"cancel": "Cancel",
|
"cancel": "Cancel",
|
||||||
"clear": "Clear",
|
"clear": "Clear",
|
||||||
@@ -1138,6 +1144,8 @@
|
|||||||
"copied": "Copied!",
|
"copied": "Copied!",
|
||||||
"copylink": "Copy Link",
|
"copylink": "Copy Link",
|
||||||
"create": "Create",
|
"create": "Create",
|
||||||
|
"defaults": "Defaults",
|
||||||
|
"delay": "Delay Update (5 mins)",
|
||||||
"delete": "Delete",
|
"delete": "Delete",
|
||||||
"deleteall": "Delete All",
|
"deleteall": "Delete All",
|
||||||
"deselectall": "Deselect All",
|
"deselectall": "Deselect All",
|
||||||
@@ -1149,10 +1157,12 @@
|
|||||||
"print": "Print",
|
"print": "Print",
|
||||||
"refresh": "Refresh",
|
"refresh": "Refresh",
|
||||||
"remove": "Remove",
|
"remove": "Remove",
|
||||||
|
"remove_alert": "Are you sure you want to dismiss the alert?",
|
||||||
"reset": "Reset your changes.",
|
"reset": "Reset your changes.",
|
||||||
"resetpassword": "Reset Password",
|
"resetpassword": "Reset Password",
|
||||||
"save": "Save",
|
"save": "Save",
|
||||||
"saveandnew": "Save and New",
|
"saveandnew": "Save and New",
|
||||||
|
"saveas": "Save As",
|
||||||
"selectall": "Select All",
|
"selectall": "Select All",
|
||||||
"send": "Send",
|
"send": "Send",
|
||||||
"sendbysms": "Send by SMS",
|
"sendbysms": "Send by SMS",
|
||||||
@@ -1160,8 +1170,7 @@
|
|||||||
"submit": "Submit",
|
"submit": "Submit",
|
||||||
"tryagain": "Try Again",
|
"tryagain": "Try Again",
|
||||||
"view": "View",
|
"view": "View",
|
||||||
"viewreleasenotes": "See What's Changed",
|
"viewreleasenotes": "See What's Changed"
|
||||||
"remove_alert": "Are you sure you want to dismiss the alert?"
|
|
||||||
},
|
},
|
||||||
"errors": {
|
"errors": {
|
||||||
"fcm": "You must allow notification permissions to have real time messaging. Click to try again.",
|
"fcm": "You must allow notification permissions to have real time messaging. Click to try again.",
|
||||||
@@ -1183,6 +1192,8 @@
|
|||||||
"clear": "Clear",
|
"clear": "Clear",
|
||||||
"confirmpassword": "Confirm Password",
|
"confirmpassword": "Confirm Password",
|
||||||
"created_at": "Created At",
|
"created_at": "Created At",
|
||||||
|
"date": "Select Date",
|
||||||
|
"datetime": "Select Date & Time",
|
||||||
"email": "Email",
|
"email": "Email",
|
||||||
"errors": "Errors",
|
"errors": "Errors",
|
||||||
"excel": "Excel",
|
"excel": "Excel",
|
||||||
@@ -1242,6 +1253,7 @@
|
|||||||
"tuesday": "Tuesday",
|
"tuesday": "Tuesday",
|
||||||
"tvmode": "TV Mode",
|
"tvmode": "TV Mode",
|
||||||
"unknown": "Unknown",
|
"unknown": "Unknown",
|
||||||
|
"unsavedchanges": "Unsaved changes.",
|
||||||
"username": "Username",
|
"username": "Username",
|
||||||
"view": "View",
|
"view": "View",
|
||||||
"wednesday": "Wednesday",
|
"wednesday": "Wednesday",
|
||||||
@@ -2731,38 +2743,6 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"production": {
|
"production": {
|
||||||
"options": {
|
|
||||||
"small": "Small",
|
|
||||||
"medium": "Medium",
|
|
||||||
"large": "Large",
|
|
||||||
"vertical": "Vertical",
|
|
||||||
"horizontal": "Horizontal"
|
|
||||||
},
|
|
||||||
"settings": {
|
|
||||||
"layout": "Layout",
|
|
||||||
"information": "Information",
|
|
||||||
"statistics_title": "Statistics",
|
|
||||||
"board_settings": "Board Settings",
|
|
||||||
"filters_title": "Filters",
|
|
||||||
"filters": {
|
|
||||||
"md_ins_cos": "Insurance Companies",
|
|
||||||
"md_estimators": "Estimators"
|
|
||||||
},
|
|
||||||
"statistics": {
|
|
||||||
"total_hours_in_production": "Hours in Production",
|
|
||||||
"total_lab_in_production": "Body Hours in Production",
|
|
||||||
"total_lar_in_production": "Refinish Hours in Production",
|
|
||||||
"total_amount_in_production": "Dollars in Production",
|
|
||||||
"jobs_in_production": "Jobs in Production",
|
|
||||||
"total_hours_on_board": "Hours on Board",
|
|
||||||
"total_lab_on_board": "Body Hours on Board",
|
|
||||||
"total_lar_on_board": "Refinish Hours on Board",
|
|
||||||
"total_amount_on_board": "Dollars on Board",
|
|
||||||
"total_jobs_on_board": "Jobs on Board",
|
|
||||||
"tasks_in_production": "Tasks in Production",
|
|
||||||
"tasks_on_board": "Tasks on Board"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"actions": {
|
"actions": {
|
||||||
"addcolumns": "Add Columns",
|
"addcolumns": "Add Columns",
|
||||||
"bodypriority-clear": "Clear Body Priority",
|
"bodypriority-clear": "Clear Body Priority",
|
||||||
@@ -2777,27 +2757,23 @@
|
|||||||
"suspend": "Suspend",
|
"suspend": "Suspend",
|
||||||
"unsuspend": "Unsuspend"
|
"unsuspend": "Unsuspend"
|
||||||
},
|
},
|
||||||
|
"constants": {
|
||||||
|
"main_profile": "Default"
|
||||||
|
},
|
||||||
"errors": {
|
"errors": {
|
||||||
"boardupdate": "Error encountered updating Job. {{message}}",
|
"boardupdate": "Error encountered updating Job. {{message}}",
|
||||||
|
"name_exists": "A Profile with this name already exists. Please choose a different name.",
|
||||||
|
"name_required": "Profile name is required.",
|
||||||
"removing": "Error removing from production board. {{error}}",
|
"removing": "Error removing from production board. {{error}}",
|
||||||
"settings": "Error saving board settings: {{error}}"
|
"settings": "Error saving board settings: {{error}}"
|
||||||
},
|
},
|
||||||
"labels": {
|
"labels": {
|
||||||
"kiosk_mode": "Kiosk Mode",
|
|
||||||
"on": "On",
|
|
||||||
"off": "Off",
|
|
||||||
"wide": "Wide",
|
|
||||||
"tall": "Tall",
|
|
||||||
"vertical": "Vertical",
|
|
||||||
"horizontal": "Horizontal",
|
|
||||||
"orientation": "Board Orientation",
|
|
||||||
"card_size": "Card Size",
|
|
||||||
"model_info": "Vehicle Info",
|
|
||||||
"actual_in": "Actual In",
|
"actual_in": "Actual In",
|
||||||
|
"addnewprofile": "Add New Profile",
|
||||||
"alert": "Alert",
|
"alert": "Alert",
|
||||||
"tasks": "Tasks",
|
|
||||||
"alertoff": "Remove alert from Job",
|
"alertoff": "Remove alert from Job",
|
||||||
"alerton": "Add alert to Job",
|
"alerton": "Add alert to Job",
|
||||||
|
"alerts": "Alerts",
|
||||||
"ats": "Alternative Transportation",
|
"ats": "Alternative Transportation",
|
||||||
"bodyhours": "B",
|
"bodyhours": "B",
|
||||||
"bodypriority": "B/P",
|
"bodypriority": "B/P",
|
||||||
@@ -2807,6 +2783,7 @@
|
|||||||
"qbo_usa": "QBO USA"
|
"qbo_usa": "QBO USA"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"card_size": "Card Size",
|
||||||
"cardcolor": "Colored Cards",
|
"cardcolor": "Colored Cards",
|
||||||
"cardsettings": "Card Settings",
|
"cardsettings": "Card Settings",
|
||||||
"clm_no": "Claim Number",
|
"clm_no": "Claim Number",
|
||||||
@@ -2815,47 +2792,88 @@
|
|||||||
"detailpriority": "D/P",
|
"detailpriority": "D/P",
|
||||||
"employeeassignments": "Employee Assignments",
|
"employeeassignments": "Employee Assignments",
|
||||||
"employeesearch": "Employee Search",
|
"employeesearch": "Employee Search",
|
||||||
|
"estimator": "Estimator",
|
||||||
|
"horizontal": "Horizontal",
|
||||||
"ins_co_nm": "Insurance Company Name",
|
"ins_co_nm": "Insurance Company Name",
|
||||||
"jobdetail": "Job Details",
|
"jobdetail": "Job Details",
|
||||||
|
"kiosk_mode": "Kiosk Mode",
|
||||||
"laborhrs": "Labor Hours",
|
"laborhrs": "Labor Hours",
|
||||||
"legend": "Legend:",
|
"legend": "Legend:",
|
||||||
|
"model_info": "Vehicle Info",
|
||||||
"note": "Production Note",
|
"note": "Production Note",
|
||||||
|
"off": "Off",
|
||||||
|
"on": "On",
|
||||||
|
"orientation": "Board Orientation",
|
||||||
"ownr_nm": "Customer Name",
|
"ownr_nm": "Customer Name",
|
||||||
"paintpriority": "P/P",
|
"paintpriority": "P/P",
|
||||||
"partsstatus": "Parts Status",
|
"partsstatus": "Parts Status",
|
||||||
"estimator": "Estimator",
|
|
||||||
"subtotal": "Subtotal",
|
|
||||||
"production_note": "Production Note",
|
"production_note": "Production Note",
|
||||||
"refinishhours": "R",
|
"refinishhours": "R",
|
||||||
"scheduled_completion": "Scheduled Completion",
|
"scheduled_completion": "Scheduled Completion",
|
||||||
"selectview": "Select a View",
|
"selectview": "Select a View",
|
||||||
"stickyheader": "Sticky Header (BETA)",
|
"stickyheader": "Sticky Header (BETA)",
|
||||||
"sublets": "Sublets",
|
"sublets": "Sublets",
|
||||||
|
"subtotal": "Subtotal",
|
||||||
|
"tall": "Tall",
|
||||||
|
"tasks": "Tasks",
|
||||||
"totalhours": "Total Hrs ",
|
"totalhours": "Total Hrs ",
|
||||||
"touchtime": "T/T",
|
"touchtime": "T/T",
|
||||||
|
"vertical": "Vertical",
|
||||||
"viewname": "View Name",
|
"viewname": "View Name",
|
||||||
"alerts": "Alerts"
|
"wide": "Wide"
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"horizontal": "Horizontal",
|
||||||
|
"large": "Large",
|
||||||
|
"medium": "Medium",
|
||||||
|
"small": "Small",
|
||||||
|
"vertical": "Vertical"
|
||||||
|
},
|
||||||
|
"settings": {
|
||||||
|
"board_settings": "Board Settings",
|
||||||
|
"filters": {
|
||||||
|
"md_estimators": "Estimators",
|
||||||
|
"md_ins_cos": "Insurance Companies"
|
||||||
|
},
|
||||||
|
"filters_title": "Filters",
|
||||||
|
"information": "Information",
|
||||||
|
"layout": "Layout",
|
||||||
|
"statistics": {
|
||||||
|
"jobs_in_production": "Jobs in Production",
|
||||||
|
"tasks_in_production": "Tasks in Production",
|
||||||
|
"tasks_on_board": "Tasks on Board",
|
||||||
|
"total_amount_in_production": "Dollars in Production",
|
||||||
|
"total_amount_on_board": "Dollars on Board",
|
||||||
|
"total_hours_in_production": "Hours in Production",
|
||||||
|
"total_hours_on_board": "Hours on Board",
|
||||||
|
"total_jobs_on_board": "Jobs on Board",
|
||||||
|
"total_lab_in_production": "Body Hours in Production",
|
||||||
|
"total_lab_on_board": "Body Hours on Board",
|
||||||
|
"total_lar_in_production": "Refinish Hours in Production",
|
||||||
|
"total_lar_on_board": "Refinish Hours on Board"
|
||||||
|
},
|
||||||
|
"statistics_title": "Statistics"
|
||||||
|
},
|
||||||
|
"statistics": {
|
||||||
|
"currency_symbol": "$",
|
||||||
|
"hours": "Hours",
|
||||||
|
"jobs": "Jobs",
|
||||||
|
"jobs_in_production": "Jobs in Production",
|
||||||
|
"tasks": "Tasks",
|
||||||
|
"tasks_in_production": "Tasks in Production",
|
||||||
|
"tasks_on_board": "Tasks on Board",
|
||||||
|
"total_amount_in_production": "Dollars in Production",
|
||||||
|
"total_amount_on_board": "Dollars on Board",
|
||||||
|
"total_hours_in_production": "Hours in Production",
|
||||||
|
"total_hours_on_board": "Hours on Board",
|
||||||
|
"total_jobs_on_board": "Jobs on Board",
|
||||||
|
"total_lab_in_production": "Body Hours in Production",
|
||||||
|
"total_lab_on_board": "Body Hours on Board",
|
||||||
|
"total_lar_in_production": "Refinish Hours in Production",
|
||||||
|
"total_lar_on_board": "Refinish Hours on Board"
|
||||||
},
|
},
|
||||||
"successes": {
|
"successes": {
|
||||||
"removed": "Job removed from production."
|
"removed": "Job removed from production."
|
||||||
},
|
|
||||||
"statistics": {
|
|
||||||
"total_hours_in_production": "Hours in Production",
|
|
||||||
"total_lab_in_production": "Body Hours in Production",
|
|
||||||
"total_lar_in_production": "Refinish Hours in Production",
|
|
||||||
"total_amount_in_production": "Dollars in Production",
|
|
||||||
"jobs_in_production": "Jobs in Production",
|
|
||||||
"total_hours_on_board": "Hours on Board",
|
|
||||||
"total_lab_on_board": "Body Hours on Board",
|
|
||||||
"total_lar_on_board": "Refinish Hours on Board",
|
|
||||||
"total_amount_on_board": "Dollars on Board",
|
|
||||||
"total_jobs_on_board": "Jobs on Board",
|
|
||||||
"tasks_in_production": "Tasks in Production",
|
|
||||||
"tasks_on_board": "Tasks on Board",
|
|
||||||
"tasks": "Tasks",
|
|
||||||
"hours": "Hours",
|
|
||||||
"currency_symbol": "$",
|
|
||||||
"jobs": "Jobs"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"profile": {
|
"profile": {
|
||||||
@@ -2913,6 +2931,8 @@
|
|||||||
"vendor": "Vendor"
|
"vendor": "Vendor"
|
||||||
},
|
},
|
||||||
"templates": {
|
"templates": {
|
||||||
|
"adp_payroll_flat": "ADP Payroll - Flat Rate",
|
||||||
|
"adp_payroll_straight": "ADP Payroll - Straight Time",
|
||||||
"anticipated_revenue": "Anticipated Revenue",
|
"anticipated_revenue": "Anticipated Revenue",
|
||||||
"ar_aging": "AR Aging",
|
"ar_aging": "AR Aging",
|
||||||
"attendance_detail": "Attendance (All Employees)",
|
"attendance_detail": "Attendance (All Employees)",
|
||||||
@@ -3404,6 +3424,18 @@
|
|||||||
"vehicledetail": "Vehicle Details {{vehicle}} | {{app}}",
|
"vehicledetail": "Vehicle Details {{vehicle}} | {{app}}",
|
||||||
"vehicles": "All Vehicles | {{app}}"
|
"vehicles": "All Vehicles | {{app}}"
|
||||||
},
|
},
|
||||||
|
"trello": {
|
||||||
|
"labels": {
|
||||||
|
"add_card": "Add Card",
|
||||||
|
"add_lane": "Add Lane",
|
||||||
|
"cancel": "Cancel",
|
||||||
|
"delete_lane": "Delete Lane",
|
||||||
|
"description": "Description",
|
||||||
|
"label": "Label",
|
||||||
|
"lane_actions": "Lane Actions",
|
||||||
|
"title": "Title"
|
||||||
|
}
|
||||||
|
},
|
||||||
"tt_approvals": {
|
"tt_approvals": {
|
||||||
"actions": {
|
"actions": {
|
||||||
"approveselected": "Approve Selected"
|
"approveselected": "Approve Selected"
|
||||||
@@ -3542,18 +3574,6 @@
|
|||||||
"validation": {
|
"validation": {
|
||||||
"unique_vendor_name": "You must enter a unique vendor name."
|
"unique_vendor_name": "You must enter a unique vendor name."
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"trello": {
|
|
||||||
"labels": {
|
|
||||||
"add_card": "Add Card",
|
|
||||||
"add_lane": "Add Lane",
|
|
||||||
"delete_lane": "Delete Lane",
|
|
||||||
"lane_actions": "Lane Actions",
|
|
||||||
"title": "Title",
|
|
||||||
"description": "Description",
|
|
||||||
"label": "Label",
|
|
||||||
"cancel": "Cancel"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -270,6 +270,7 @@
|
|||||||
"testrender": ""
|
"testrender": ""
|
||||||
},
|
},
|
||||||
"errors": {
|
"errors": {
|
||||||
|
"creatingdefaultview": "",
|
||||||
"loading": "Impossible de charger les détails de la boutique. Veuillez appeler le support technique.",
|
"loading": "Impossible de charger les détails de la boutique. Veuillez appeler le support technique.",
|
||||||
"saving": ""
|
"saving": ""
|
||||||
},
|
},
|
||||||
@@ -284,19 +285,21 @@
|
|||||||
},
|
},
|
||||||
"appt_length": "",
|
"appt_length": "",
|
||||||
"attach_pdf_to_email": "",
|
"attach_pdf_to_email": "",
|
||||||
|
"batchid": "",
|
||||||
"bill_allow_post_to_closed": "",
|
"bill_allow_post_to_closed": "",
|
||||||
"bill_federal_tax_rate": "",
|
"bill_federal_tax_rate": "",
|
||||||
"bill_local_tax_rate": "",
|
"bill_local_tax_rate": "",
|
||||||
"bill_state_tax_rate": "",
|
"bill_state_tax_rate": "",
|
||||||
"city": "",
|
"city": "",
|
||||||
"closingperiod": "",
|
"closingperiod": "",
|
||||||
|
"companycode": "",
|
||||||
"country": "",
|
"country": "",
|
||||||
"dailybodytarget": "",
|
"dailybodytarget": "",
|
||||||
"dailypainttarget": "",
|
"dailypainttarget": "",
|
||||||
"default_adjustment_rate": "",
|
"default_adjustment_rate": "",
|
||||||
"deliver": {
|
"deliver": {
|
||||||
"templates": "",
|
"require_actual_delivery_date": "",
|
||||||
"require_actual_delivery_date": ""
|
"templates": ""
|
||||||
},
|
},
|
||||||
"dms": {
|
"dms": {
|
||||||
"apcontrol": "",
|
"apcontrol": "",
|
||||||
@@ -699,7 +702,10 @@
|
|||||||
"workingdays": ""
|
"workingdays": ""
|
||||||
},
|
},
|
||||||
"successes": {
|
"successes": {
|
||||||
"save": ""
|
"areyousure": "",
|
||||||
|
"defaultviewcreated": "",
|
||||||
|
"save": "",
|
||||||
|
"unsavedchanges": ""
|
||||||
},
|
},
|
||||||
"validation": {
|
"validation": {
|
||||||
"centermustexist": "",
|
"centermustexist": "",
|
||||||
@@ -1129,8 +1135,8 @@
|
|||||||
},
|
},
|
||||||
"general": {
|
"general": {
|
||||||
"actions": {
|
"actions": {
|
||||||
"defaults": "",
|
|
||||||
"add": "",
|
"add": "",
|
||||||
|
"autoupdate": "",
|
||||||
"calculate": "",
|
"calculate": "",
|
||||||
"cancel": "",
|
"cancel": "",
|
||||||
"clear": "",
|
"clear": "",
|
||||||
@@ -1138,6 +1144,8 @@
|
|||||||
"copied": "",
|
"copied": "",
|
||||||
"copylink": "",
|
"copylink": "",
|
||||||
"create": "",
|
"create": "",
|
||||||
|
"defaults": "",
|
||||||
|
"delay": "",
|
||||||
"delete": "Effacer",
|
"delete": "Effacer",
|
||||||
"deleteall": "",
|
"deleteall": "",
|
||||||
"deselectall": "",
|
"deselectall": "",
|
||||||
@@ -1149,10 +1157,12 @@
|
|||||||
"print": "",
|
"print": "",
|
||||||
"refresh": "",
|
"refresh": "",
|
||||||
"remove": "",
|
"remove": "",
|
||||||
|
"remove_alert": "",
|
||||||
"reset": " Rétablir l'original.",
|
"reset": " Rétablir l'original.",
|
||||||
"resetpassword": "",
|
"resetpassword": "",
|
||||||
"save": "sauvegarder",
|
"save": "sauvegarder",
|
||||||
"saveandnew": "",
|
"saveandnew": "",
|
||||||
|
"saveas": "",
|
||||||
"selectall": "",
|
"selectall": "",
|
||||||
"send": "",
|
"send": "",
|
||||||
"sendbysms": "",
|
"sendbysms": "",
|
||||||
@@ -1160,8 +1170,7 @@
|
|||||||
"submit": "",
|
"submit": "",
|
||||||
"tryagain": "",
|
"tryagain": "",
|
||||||
"view": "",
|
"view": "",
|
||||||
"viewreleasenotes": "",
|
"viewreleasenotes": ""
|
||||||
"remove_alert": ""
|
|
||||||
},
|
},
|
||||||
"errors": {
|
"errors": {
|
||||||
"fcm": "",
|
"fcm": "",
|
||||||
@@ -1183,6 +1192,8 @@
|
|||||||
"clear": "",
|
"clear": "",
|
||||||
"confirmpassword": "",
|
"confirmpassword": "",
|
||||||
"created_at": "",
|
"created_at": "",
|
||||||
|
"date": "",
|
||||||
|
"datetime": "",
|
||||||
"email": "",
|
"email": "",
|
||||||
"errors": "",
|
"errors": "",
|
||||||
"excel": "",
|
"excel": "",
|
||||||
@@ -1242,6 +1253,7 @@
|
|||||||
"tuesday": "",
|
"tuesday": "",
|
||||||
"tvmode": "",
|
"tvmode": "",
|
||||||
"unknown": "Inconnu",
|
"unknown": "Inconnu",
|
||||||
|
"unsavedchanges": "",
|
||||||
"username": "",
|
"username": "",
|
||||||
"view": "",
|
"view": "",
|
||||||
"wednesday": "",
|
"wednesday": "",
|
||||||
@@ -2731,38 +2743,6 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"production": {
|
"production": {
|
||||||
"options": {
|
|
||||||
"small": "",
|
|
||||||
"medium": "",
|
|
||||||
"large": "",
|
|
||||||
"vertical": "",
|
|
||||||
"horizontal": ""
|
|
||||||
},
|
|
||||||
"settings": {
|
|
||||||
"layout": "",
|
|
||||||
"information": "",
|
|
||||||
"statistics_title": "",
|
|
||||||
"board_settings": "",
|
|
||||||
"filters_title": "",
|
|
||||||
"filters": {
|
|
||||||
"md_ins_cos": "",
|
|
||||||
"md_estimators": ""
|
|
||||||
},
|
|
||||||
"statistics": {
|
|
||||||
"total_hours_in_production": "",
|
|
||||||
"total_lab_in_production": "",
|
|
||||||
"total_lar_in_production": "",
|
|
||||||
"total_amount_in_production": "",
|
|
||||||
"jobs_in_production": "",
|
|
||||||
"total_hours_on_board": "",
|
|
||||||
"total_lab_on_board": "",
|
|
||||||
"total_lar_on_board": "",
|
|
||||||
"total_amount_on_board": "",
|
|
||||||
"total_jobs_on_board": "",
|
|
||||||
"tasks_in_production": "",
|
|
||||||
"tasks_on_board": ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"actions": {
|
"actions": {
|
||||||
"addcolumns": "",
|
"addcolumns": "",
|
||||||
"bodypriority-clear": "",
|
"bodypriority-clear": "",
|
||||||
@@ -2777,27 +2757,23 @@
|
|||||||
"suspend": "",
|
"suspend": "",
|
||||||
"unsuspend": ""
|
"unsuspend": ""
|
||||||
},
|
},
|
||||||
|
"constants": {
|
||||||
|
"main_profile": ""
|
||||||
|
},
|
||||||
"errors": {
|
"errors": {
|
||||||
"boardupdate": "",
|
"boardupdate": "",
|
||||||
|
"name_exists": "",
|
||||||
|
"name_required": "",
|
||||||
"removing": "",
|
"removing": "",
|
||||||
"settings": ""
|
"settings": ""
|
||||||
},
|
},
|
||||||
"labels": {
|
"labels": {
|
||||||
"kiosk_mode": "",
|
|
||||||
"on": "",
|
|
||||||
"off": "",
|
|
||||||
"wide": "",
|
|
||||||
"tall": "",
|
|
||||||
"vertical": "",
|
|
||||||
"horizontal": "",
|
|
||||||
"orientation": "",
|
|
||||||
"card_size": "",
|
|
||||||
"model_info": "",
|
|
||||||
"actual_in": "",
|
"actual_in": "",
|
||||||
|
"addnewprofile": "",
|
||||||
"alert": "",
|
"alert": "",
|
||||||
"tasks": "",
|
|
||||||
"alertoff": "",
|
"alertoff": "",
|
||||||
"alerton": "",
|
"alerton": "",
|
||||||
|
"alerts": "",
|
||||||
"ats": "",
|
"ats": "",
|
||||||
"bodyhours": "",
|
"bodyhours": "",
|
||||||
"bodypriority": "",
|
"bodypriority": "",
|
||||||
@@ -2807,6 +2783,7 @@
|
|||||||
"qbo_usa": ""
|
"qbo_usa": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"card_size": "",
|
||||||
"cardcolor": "",
|
"cardcolor": "",
|
||||||
"cardsettings": "",
|
"cardsettings": "",
|
||||||
"clm_no": "",
|
"clm_no": "",
|
||||||
@@ -2815,47 +2792,88 @@
|
|||||||
"detailpriority": "",
|
"detailpriority": "",
|
||||||
"employeeassignments": "",
|
"employeeassignments": "",
|
||||||
"employeesearch": "",
|
"employeesearch": "",
|
||||||
|
"estimator": "",
|
||||||
|
"horizontal": "",
|
||||||
"ins_co_nm": "",
|
"ins_co_nm": "",
|
||||||
"jobdetail": "",
|
"jobdetail": "",
|
||||||
|
"kiosk_mode": "",
|
||||||
"laborhrs": "",
|
"laborhrs": "",
|
||||||
"legend": "",
|
"legend": "",
|
||||||
|
"model_info": "",
|
||||||
"note": "",
|
"note": "",
|
||||||
|
"off": "",
|
||||||
|
"on": "",
|
||||||
|
"orientation": "",
|
||||||
"ownr_nm": "",
|
"ownr_nm": "",
|
||||||
"paintpriority": "",
|
"paintpriority": "",
|
||||||
"partsstatus": "",
|
"partsstatus": "",
|
||||||
"estimator": "",
|
|
||||||
"subtotal": "",
|
|
||||||
"production_note": "",
|
"production_note": "",
|
||||||
"refinishhours": "",
|
"refinishhours": "",
|
||||||
"scheduled_completion": "",
|
"scheduled_completion": "",
|
||||||
"selectview": "",
|
"selectview": "",
|
||||||
"stickyheader": "",
|
"stickyheader": "",
|
||||||
"sublets": "",
|
"sublets": "",
|
||||||
|
"subtotal": "",
|
||||||
|
"tall": "",
|
||||||
|
"tasks": "",
|
||||||
"totalhours": "",
|
"totalhours": "",
|
||||||
"touchtime": "",
|
"touchtime": "",
|
||||||
|
"vertical": "",
|
||||||
"viewname": "",
|
"viewname": "",
|
||||||
"alerts": ""
|
"wide": ""
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"horizontal": "",
|
||||||
|
"large": "",
|
||||||
|
"medium": "",
|
||||||
|
"small": "",
|
||||||
|
"vertical": ""
|
||||||
|
},
|
||||||
|
"settings": {
|
||||||
|
"board_settings": "",
|
||||||
|
"filters": {
|
||||||
|
"md_estimators": "",
|
||||||
|
"md_ins_cos": ""
|
||||||
|
},
|
||||||
|
"filters_title": "",
|
||||||
|
"information": "",
|
||||||
|
"layout": "",
|
||||||
|
"statistics": {
|
||||||
|
"jobs_in_production": "",
|
||||||
|
"tasks_in_production": "",
|
||||||
|
"tasks_on_board": "",
|
||||||
|
"total_amount_in_production": "",
|
||||||
|
"total_amount_on_board": "",
|
||||||
|
"total_hours_in_production": "",
|
||||||
|
"total_hours_on_board": "",
|
||||||
|
"total_jobs_on_board": "",
|
||||||
|
"total_lab_in_production": "",
|
||||||
|
"total_lab_on_board": "",
|
||||||
|
"total_lar_in_production": "",
|
||||||
|
"total_lar_on_board": ""
|
||||||
|
},
|
||||||
|
"statistics_title": ""
|
||||||
|
},
|
||||||
|
"statistics": {
|
||||||
|
"currency_symbol": "",
|
||||||
|
"hours": "",
|
||||||
|
"jobs": "",
|
||||||
|
"jobs_in_production": "",
|
||||||
|
"tasks": "",
|
||||||
|
"tasks_in_production": "",
|
||||||
|
"tasks_on_board": "",
|
||||||
|
"total_amount_in_production": "",
|
||||||
|
"total_amount_on_board": "",
|
||||||
|
"total_hours_in_production": "",
|
||||||
|
"total_hours_on_board": "",
|
||||||
|
"total_jobs_on_board": "",
|
||||||
|
"total_lab_in_production": "",
|
||||||
|
"total_lab_on_board": "",
|
||||||
|
"total_lar_in_production": "",
|
||||||
|
"total_lar_on_board": ""
|
||||||
},
|
},
|
||||||
"successes": {
|
"successes": {
|
||||||
"removed": ""
|
"removed": ""
|
||||||
},
|
|
||||||
"statistics": {
|
|
||||||
"total_hours_in_production": "",
|
|
||||||
"total_lab_in_production": "",
|
|
||||||
"total_lar_in_production": "",
|
|
||||||
"total_amount_in_production": "",
|
|
||||||
"jobs_in_production": "",
|
|
||||||
"total_hours_on_board": "",
|
|
||||||
"total_lab_on_board": "",
|
|
||||||
"total_lar_on_board": "",
|
|
||||||
"total_amount_on_board": "",
|
|
||||||
"total_jobs_on_board": "",
|
|
||||||
"tasks_in_production": "",
|
|
||||||
"tasks_on_board": "",
|
|
||||||
"tasks": "",
|
|
||||||
"hours": "",
|
|
||||||
"currency_symbol": "",
|
|
||||||
"jobs": ""
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"profile": {
|
"profile": {
|
||||||
@@ -2913,6 +2931,8 @@
|
|||||||
"vendor": ""
|
"vendor": ""
|
||||||
},
|
},
|
||||||
"templates": {
|
"templates": {
|
||||||
|
"adp_payroll_flat": "",
|
||||||
|
"adp_payroll_straight": "",
|
||||||
"anticipated_revenue": "",
|
"anticipated_revenue": "",
|
||||||
"ar_aging": "",
|
"ar_aging": "",
|
||||||
"attendance_detail": "",
|
"attendance_detail": "",
|
||||||
@@ -3404,6 +3424,18 @@
|
|||||||
"vehicledetail": "Détails du véhicule {{vehicle} | {{app}}",
|
"vehicledetail": "Détails du véhicule {{vehicle} | {{app}}",
|
||||||
"vehicles": "Tous les véhicules | {{app}}"
|
"vehicles": "Tous les véhicules | {{app}}"
|
||||||
},
|
},
|
||||||
|
"trello": {
|
||||||
|
"labels": {
|
||||||
|
"add_card": "",
|
||||||
|
"add_lane": "",
|
||||||
|
"cancel": "",
|
||||||
|
"delete_lane": "",
|
||||||
|
"description": "",
|
||||||
|
"label": "",
|
||||||
|
"lane_actions": "",
|
||||||
|
"title": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
"tt_approvals": {
|
"tt_approvals": {
|
||||||
"actions": {
|
"actions": {
|
||||||
"approveselected": ""
|
"approveselected": ""
|
||||||
@@ -3542,18 +3574,6 @@
|
|||||||
"validation": {
|
"validation": {
|
||||||
"unique_vendor_name": ""
|
"unique_vendor_name": ""
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"trello": {
|
|
||||||
"labels": {
|
|
||||||
"add_card": "",
|
|
||||||
"add_lane": "",
|
|
||||||
"delete_lane": "",
|
|
||||||
"lane_actions": "",
|
|
||||||
"title": "",
|
|
||||||
"description": "",
|
|
||||||
"label": "",
|
|
||||||
"cancel": ""
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2158,6 +2158,32 @@ export const TemplateList = (type, context) => {
|
|||||||
field: i18n.t("tasks.fields.created_at")
|
field: i18n.t("tasks.fields.created_at")
|
||||||
},
|
},
|
||||||
group: "jobs"
|
group: "jobs"
|
||||||
|
},
|
||||||
|
adp_payroll_flat: {
|
||||||
|
title: i18n.t("reportcenter.templates.adp_payroll_flat"),
|
||||||
|
subject: i18n.t("reportcenter.templates.adp_payroll_flat"),
|
||||||
|
key: "adp_payroll_flat",
|
||||||
|
reporttype: "text",
|
||||||
|
disabled: false,
|
||||||
|
rangeFilter: {
|
||||||
|
object: i18n.t("reportcenter.labels.objects.timetickets"),
|
||||||
|
field: i18n.t("timetickets.fields.committed_at")
|
||||||
|
},
|
||||||
|
group: "payroll",
|
||||||
|
adp_payroll: true
|
||||||
|
},
|
||||||
|
adp_payroll_straight: {
|
||||||
|
title: i18n.t("reportcenter.templates.adp_payroll_straight"),
|
||||||
|
subject: i18n.t("reportcenter.templates.adp_payroll_straight"),
|
||||||
|
key: "adp_payroll_straight",
|
||||||
|
reporttype: "text",
|
||||||
|
disabled: false,
|
||||||
|
rangeFilter: {
|
||||||
|
object: i18n.t("reportcenter.labels.objects.timetickets"),
|
||||||
|
field: i18n.t("timetickets.fields.date")
|
||||||
|
},
|
||||||
|
group: "payroll",
|
||||||
|
adp_payroll: true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
: {}),
|
: {}),
|
||||||
|
|||||||
84
client/src/utils/countdownHook.js
Normal file
84
client/src/utils/countdownHook.js
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
import React from "react";
|
||||||
|
|
||||||
|
const useCountDown = (timeToCount = 60 * 1000, interval = 1000) => {
|
||||||
|
const [timeLeft, setTimeLeft] = React.useState(0);
|
||||||
|
const timer = React.useRef({});
|
||||||
|
|
||||||
|
const run = (ts) => {
|
||||||
|
if (!timer.current.started) {
|
||||||
|
timer.current.started = ts;
|
||||||
|
timer.current.lastInterval = ts;
|
||||||
|
}
|
||||||
|
|
||||||
|
const localInterval = Math.min(interval, timer.current.timeLeft || Infinity);
|
||||||
|
if (ts - timer.current.lastInterval >= localInterval) {
|
||||||
|
timer.current.lastInterval += localInterval;
|
||||||
|
setTimeLeft((timeLeft) => {
|
||||||
|
timer.current.timeLeft = timeLeft - localInterval;
|
||||||
|
return timer.current.timeLeft;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ts - timer.current.started < timer.current.timeToCount) {
|
||||||
|
timer.current.requestId = window.requestAnimationFrame(run);
|
||||||
|
} else {
|
||||||
|
timer.current = {};
|
||||||
|
setTimeLeft(0);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const start = React.useCallback(
|
||||||
|
(ttc) => {
|
||||||
|
window.cancelAnimationFrame(timer.current.requestId);
|
||||||
|
|
||||||
|
const newTimeToCount = ttc !== undefined ? ttc : timeToCount;
|
||||||
|
timer.current.started = null;
|
||||||
|
timer.current.lastInterval = null;
|
||||||
|
timer.current.timeToCount = newTimeToCount;
|
||||||
|
timer.current.requestId = window.requestAnimationFrame(run);
|
||||||
|
|
||||||
|
setTimeLeft(newTimeToCount);
|
||||||
|
},
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
const pause = React.useCallback(() => {
|
||||||
|
window.cancelAnimationFrame(timer.current.requestId);
|
||||||
|
timer.current.started = null;
|
||||||
|
timer.current.lastInterval = null;
|
||||||
|
timer.current.timeToCount = timer.current.timeLeft;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const resume = React.useCallback(
|
||||||
|
() => {
|
||||||
|
if (!timer.current.started && timer.current.timeLeft > 0) {
|
||||||
|
window.cancelAnimationFrame(timer.current.requestId);
|
||||||
|
timer.current.requestId = window.requestAnimationFrame(run);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
const reset = React.useCallback(() => {
|
||||||
|
if (timer.current.timeLeft) {
|
||||||
|
window.cancelAnimationFrame(timer.current.requestId);
|
||||||
|
timer.current = {};
|
||||||
|
setTimeLeft(0);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const actions = React.useMemo(
|
||||||
|
() => ({ start, pause, resume, reset }), // eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
return () => window.cancelAnimationFrame(timer.current.requestId);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return [timeLeft, actions];
|
||||||
|
};
|
||||||
|
|
||||||
|
export default useCountDown;
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
export const BETA_KEY = "betaSwitchImex";
|
|
||||||
|
|
||||||
export const checkBeta = () => {
|
|
||||||
const cookie = document.cookie.split("; ").find((row) => row.startsWith(BETA_KEY));
|
|
||||||
return cookie ? cookie.split("=")[1] === "true" : false;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const setBeta = (value) => {
|
|
||||||
const domain = window.location.hostname.split(".").slice(-2).join(".");
|
|
||||||
document.cookie = `${BETA_KEY}=${value}; path=/; domain=.${domain}`;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const handleBeta = () => {
|
|
||||||
if (window.location.hostname.startsWith("localhost")) {
|
|
||||||
console.log("Not on beta or test, so no need to handle beta.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const isBeta = checkBeta();
|
|
||||||
const currentHostName = window.location.hostname;
|
|
||||||
|
|
||||||
// Determine if the host name starts with "beta" or "www.beta"
|
|
||||||
const isBetaHost = currentHostName.startsWith("beta.");
|
|
||||||
const isBetaHostWithWWW = currentHostName.startsWith("www.beta.");
|
|
||||||
|
|
||||||
if (isBeta) {
|
|
||||||
// If beta is on and we are not on a beta domain, redirect to the beta version
|
|
||||||
if (!isBetaHost && !isBetaHostWithWWW) {
|
|
||||||
const newHostName = currentHostName.startsWith("www.")
|
|
||||||
? `www.beta.${currentHostName.replace(/^www\./, "")}`
|
|
||||||
: `beta.${currentHostName}`;
|
|
||||||
const href = `${window.location.protocol}//${newHostName}${window.location.pathname}${window.location.search}${window.location.hash}`;
|
|
||||||
window.location.replace(href);
|
|
||||||
}
|
|
||||||
// Otherwise, if beta is on and we're already on a beta domain, stay there
|
|
||||||
} else {
|
|
||||||
// If beta is off and we are on a beta domain, redirect to the non-beta version
|
|
||||||
if (isBetaHost || isBetaHostWithWWW) {
|
|
||||||
const newHostName = currentHostName.replace(/^www\.beta\./, "www.").replace(/^beta\./, "");
|
|
||||||
const href = `${window.location.protocol}//${newHostName}${window.location.pathname}${window.location.search}${window.location.hash}`;
|
|
||||||
window.location.replace(href);
|
|
||||||
}
|
|
||||||
// Otherwise, if beta is off and we're not on a beta domain, stay there
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export default handleBeta;
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
version: 2
|
version: 2
|
||||||
endpoint: https://db.dev.bodyshop.app
|
endpoint: https://db.dev.imex.online
|
||||||
admin_secret: Dev-BodyShopApp!
|
admin_secret: Dev-BodyShopApp!
|
||||||
metadata_directory: metadata
|
metadata_directory: metadata
|
||||||
actions:
|
actions:
|
||||||
|
|||||||
@@ -1,3 +1,27 @@
|
|||||||
|
- name: AutoHouse Data Pump
|
||||||
|
webhook: '{{HASURA_API_URL}}/data/ah'
|
||||||
|
schedule: 0 6 * * *
|
||||||
|
include_in_metadata: true
|
||||||
|
payload: {}
|
||||||
|
headers:
|
||||||
|
- name: x-imex-auth
|
||||||
|
value_from_env: DATAPUMP_AUTH
|
||||||
|
- name: Claimscorp Data Pump
|
||||||
|
webhook: '{{HASURA_API_URL}}/data/cc'
|
||||||
|
schedule: 30 6 * * *
|
||||||
|
include_in_metadata: true
|
||||||
|
payload: {}
|
||||||
|
headers:
|
||||||
|
- name: x-imex-auth
|
||||||
|
value_from_env: DATAPUMP_AUTH
|
||||||
|
- name: Kaizen Data Pump
|
||||||
|
webhook: '{{HASURA_API_URL}}/data/kaizen'
|
||||||
|
schedule: 30 5 * * *
|
||||||
|
include_in_metadata: true
|
||||||
|
payload: {}
|
||||||
|
headers:
|
||||||
|
- name: x-imex-auth
|
||||||
|
value_from_env: DATAPUMP_AUTH
|
||||||
- name: Task Reminders
|
- name: Task Reminders
|
||||||
webhook: '{{HASURA_API_URL}}/tasks-remind-handler'
|
webhook: '{{HASURA_API_URL}}/tasks-remind-handler'
|
||||||
schedule: '*/15 * * * *'
|
schedule: '*/15 * * * *'
|
||||||
|
|||||||
@@ -918,6 +918,7 @@
|
|||||||
- bill_tax_rates
|
- bill_tax_rates
|
||||||
- cdk_configuration
|
- cdk_configuration
|
||||||
- cdk_dealerid
|
- cdk_dealerid
|
||||||
|
- chatterid
|
||||||
- city
|
- city
|
||||||
- claimscorpid
|
- claimscorpid
|
||||||
- convenient_company
|
- convenient_company
|
||||||
@@ -939,6 +940,7 @@
|
|||||||
- inhousevendorid
|
- inhousevendorid
|
||||||
- insurance_vendor_id
|
- insurance_vendor_id
|
||||||
- intakechecklist
|
- intakechecklist
|
||||||
|
- intellipay_config
|
||||||
- jc_hourly_rates
|
- jc_hourly_rates
|
||||||
- jobsizelimit
|
- jobsizelimit
|
||||||
- last_name_first
|
- last_name_first
|
||||||
@@ -1040,6 +1042,7 @@
|
|||||||
- inhousevendorid
|
- inhousevendorid
|
||||||
- insurance_vendor_id
|
- insurance_vendor_id
|
||||||
- intakechecklist
|
- intakechecklist
|
||||||
|
- intellipay_config
|
||||||
- jc_hourly_rates
|
- jc_hourly_rates
|
||||||
- last_name_first
|
- last_name_first
|
||||||
- localmediaserverhttp
|
- localmediaserverhttp
|
||||||
@@ -4240,6 +4243,63 @@
|
|||||||
- active:
|
- active:
|
||||||
_eq: true
|
_eq: true
|
||||||
event_triggers:
|
event_triggers:
|
||||||
|
- name: job_modified
|
||||||
|
definition:
|
||||||
|
enable_manual: false
|
||||||
|
update:
|
||||||
|
columns:
|
||||||
|
- clm_no
|
||||||
|
- v_make_desc
|
||||||
|
- date_next_contact
|
||||||
|
- status
|
||||||
|
- employee_csr
|
||||||
|
- employee_prep
|
||||||
|
- clm_total
|
||||||
|
- suspended
|
||||||
|
- employee_body
|
||||||
|
- ro_number
|
||||||
|
- actual_in
|
||||||
|
- ownr_co_nm
|
||||||
|
- v_model_yr
|
||||||
|
- comment
|
||||||
|
- job_totals
|
||||||
|
- v_vin
|
||||||
|
- ownr_fn
|
||||||
|
- scheduled_completion
|
||||||
|
- special_coverage_policy
|
||||||
|
- v_color
|
||||||
|
- ca_gst_registrant
|
||||||
|
- scheduled_delivery
|
||||||
|
- actual_delivery
|
||||||
|
- actual_completion
|
||||||
|
- kanbanparent
|
||||||
|
- est_ct_fn
|
||||||
|
- employee_refinish
|
||||||
|
- ownr_ph1
|
||||||
|
- date_last_contacted
|
||||||
|
- alt_transport
|
||||||
|
- inproduction
|
||||||
|
- est_ct_ln
|
||||||
|
- production_vars
|
||||||
|
- category
|
||||||
|
- v_model_desc
|
||||||
|
- date_invoiced
|
||||||
|
- est_co_nm
|
||||||
|
- ownr_ln
|
||||||
|
retry_conf:
|
||||||
|
interval_sec: 10
|
||||||
|
num_retries: 0
|
||||||
|
timeout_sec: 60
|
||||||
|
webhook_from_env: HASURA_API_URL
|
||||||
|
headers:
|
||||||
|
- name: event-secret
|
||||||
|
value_from_env: EVENT_SECRET
|
||||||
|
request_transform:
|
||||||
|
method: POST
|
||||||
|
query_params: {}
|
||||||
|
template_engine: Kriti
|
||||||
|
url: '{{$base_url}}/job/job-updated'
|
||||||
|
version: 2
|
||||||
- name: job_status_transition
|
- name: job_status_transition
|
||||||
definition:
|
definition:
|
||||||
enable_manual: true
|
enable_manual: true
|
||||||
@@ -4299,6 +4359,35 @@
|
|||||||
template_engine: Kriti
|
template_engine: Kriti
|
||||||
url: '{{$base_url}}/opensearch'
|
url: '{{$base_url}}/opensearch'
|
||||||
version: 2
|
version: 2
|
||||||
|
- table:
|
||||||
|
name: jobs_inproduction
|
||||||
|
schema: public
|
||||||
|
object_relationships:
|
||||||
|
- name: bodyshop
|
||||||
|
using:
|
||||||
|
manual_configuration:
|
||||||
|
column_mapping:
|
||||||
|
shopid: id
|
||||||
|
insertion_order: null
|
||||||
|
remote_table:
|
||||||
|
name: bodyshops
|
||||||
|
schema: public
|
||||||
|
select_permissions:
|
||||||
|
- role: user
|
||||||
|
permission:
|
||||||
|
columns:
|
||||||
|
- id
|
||||||
|
- shopid
|
||||||
|
- updated_at
|
||||||
|
filter:
|
||||||
|
bodyshop:
|
||||||
|
associations:
|
||||||
|
_and:
|
||||||
|
- user:
|
||||||
|
authid:
|
||||||
|
_eq: X-Hasura-User-Id
|
||||||
|
- active:
|
||||||
|
_eq: true
|
||||||
- table:
|
- table:
|
||||||
name: masterdata
|
name: masterdata
|
||||||
schema: public
|
schema: public
|
||||||
|
|||||||
11
hasura/migrations/1726773072213_run_sql_migration/down.sql
Normal file
11
hasura/migrations/1726773072213_run_sql_migration/down.sql
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
-- Could not auto-generate a down migration.
|
||||||
|
-- Please write an appropriate down migration for the SQL below:
|
||||||
|
-- CREATE
|
||||||
|
-- OR REPLACE VIEW "public"."jobs_inproduction" AS
|
||||||
|
-- SELECT
|
||||||
|
-- j.id,
|
||||||
|
-- j.updated_at
|
||||||
|
-- FROM
|
||||||
|
-- jobs j
|
||||||
|
-- WHERE
|
||||||
|
-- j.inproduction=true;
|
||||||
9
hasura/migrations/1726773072213_run_sql_migration/up.sql
Normal file
9
hasura/migrations/1726773072213_run_sql_migration/up.sql
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
CREATE
|
||||||
|
OR REPLACE VIEW "public"."jobs_inproduction" AS
|
||||||
|
SELECT
|
||||||
|
j.id,
|
||||||
|
j.updated_at
|
||||||
|
FROM
|
||||||
|
jobs j
|
||||||
|
WHERE
|
||||||
|
j.inproduction=true;
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
-- Could not auto-generate a down migration.
|
||||||
|
-- Please write an appropriate down migration for the SQL below:
|
||||||
|
-- CREATE OR REPLACE VIEW "public"."jobs_inproduction" AS
|
||||||
|
-- SELECT j.id,
|
||||||
|
-- j.updated_at,
|
||||||
|
-- j.shopid
|
||||||
|
-- FROM jobs j
|
||||||
|
-- WHERE (j.inproduction = true);
|
||||||
6
hasura/migrations/1726773316245_run_sql_migration/up.sql
Normal file
6
hasura/migrations/1726773316245_run_sql_migration/up.sql
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
CREATE OR REPLACE VIEW "public"."jobs_inproduction" AS
|
||||||
|
SELECT j.id,
|
||||||
|
j.updated_at,
|
||||||
|
j.shopid
|
||||||
|
FROM jobs j
|
||||||
|
WHERE (j.inproduction = true);
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
-- Could not auto-generate a down migration.
|
||||||
|
-- Please write an appropriate down migration for the SQL below:
|
||||||
|
-- CREATE OR REPLACE VIEW "public"."jobs_inproduction" AS
|
||||||
|
-- SELECT j.id,
|
||||||
|
-- j.updated_at,
|
||||||
|
-- j.shopid
|
||||||
|
-- FROM jobs j
|
||||||
|
-- WHERE (j.inproduction = true);
|
||||||
6
hasura/migrations/1726773326353_run_sql_migration/up.sql
Normal file
6
hasura/migrations/1726773326353_run_sql_migration/up.sql
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
CREATE OR REPLACE VIEW "public"."jobs_inproduction" AS
|
||||||
|
SELECT j.id,
|
||||||
|
j.updated_at,
|
||||||
|
j.shopid
|
||||||
|
FROM jobs j
|
||||||
|
WHERE (j.inproduction = true);
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
-- Could not auto-generate a down migration.
|
||||||
|
-- Please write an appropriate down migration for the SQL below:
|
||||||
|
-- CREATE INDEX idx_jobs_inproduction_true_cast ON jobs(inproduction) WHERE inproduction = ('true') :: boolean;
|
||||||
1
hasura/migrations/1726775434726_run_sql_migration/up.sql
Normal file
1
hasura/migrations/1726775434726_run_sql_migration/up.sql
Normal file
@@ -0,0 +1 @@
|
|||||||
|
CREATE INDEX idx_jobs_inproduction_true_cast ON jobs(inproduction) WHERE inproduction = ('true') :: boolean;
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
CREATE INDEX "idx_jobs_inproduction_true_cast" on
|
||||||
|
"public"."jobs" using btree ("inproduction");
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
DROP INDEX IF EXISTS "public"."idx_jobs_inproduction_true_cast";
|
||||||
@@ -94,7 +94,10 @@ exports.default = async (req, res) => {
|
|||||||
ret.push({
|
ret.push({
|
||||||
billid: bill.id,
|
billid: bill.id,
|
||||||
success: false,
|
success: false,
|
||||||
errorMessage: (error && error.authResponse && error.authResponse.body) || (error && error.message)
|
errorMessage:
|
||||||
|
(error && error.authResponse && error.authResponse.body) ||
|
||||||
|
error.response?.data?.Fault?.Error.map((e) => e.Detail).join(", ") ||
|
||||||
|
(error && error.message)
|
||||||
});
|
});
|
||||||
|
|
||||||
//Add the export log error.
|
//Add the export log error.
|
||||||
@@ -209,14 +212,14 @@ async function InsertBill(oauthClient, qbo_realmId, req, bill, vendor, bodyshop)
|
|||||||
AccountBasedExpenseLineDetail: {
|
AccountBasedExpenseLineDetail: {
|
||||||
...(bill.job.class ? { ClassRef: { value: classes[bill.job.class] } } : {}),
|
...(bill.job.class ? { ClassRef: { value: classes[bill.job.class] } } : {}),
|
||||||
AccountRef: {
|
AccountRef: {
|
||||||
value: accounts[bodyshop.md_responsibility_centers.taxes.federal.accountdesc]
|
value: accounts[bodyshop.md_responsibility_centers.taxes.federal_itc.accountdesc]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
Amount: Dinero({
|
Amount: Dinero({
|
||||||
amount: Math.round(
|
amount: Math.round(
|
||||||
bill.billlines.reduce((acc, val) => {
|
bill.billlines.reduce((acc, val) => {
|
||||||
return acc + val.actual_cost * val.quantity;
|
return acc + val.applicable_taxes?.federal ? (val.actual_cost * val.quantity ?? 0) : 0;
|
||||||
}, 0) * 100
|
}, 0) * 100
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@@ -274,6 +277,8 @@ async function InsertBill(oauthClient, qbo_realmId, req, bill, vendor, bodyshop)
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.log("qbo-payables-error", "DEBUG", req.user.email, bill.id, {
|
logger.log("qbo-payables-error", "DEBUG", req.user.email, bill.id, {
|
||||||
error: error, //(error && error.authResponse && error.authResponse.body) || (error && error.message),
|
error: error, //(error && error.authResponse && error.authResponse.body) || (error && error.message),
|
||||||
|
validationError: JSON.stringify(error?.response?.data),
|
||||||
|
accountmeta: JSON.stringify({ accounts, taxCodes, classes }),
|
||||||
method: "InsertBill"
|
method: "InsertBill"
|
||||||
});
|
});
|
||||||
throw error;
|
throw error;
|
||||||
|
|||||||
@@ -179,7 +179,11 @@ exports.default = async (req, res) => {
|
|||||||
ret.push({
|
ret.push({
|
||||||
jobid: job.id,
|
jobid: job.id,
|
||||||
success: false,
|
success: false,
|
||||||
errorMessage: (error && error.authResponse && error.authResponse.body) || (error && error.message)
|
errorMessage:
|
||||||
|
error?.authResponse?.body ||
|
||||||
|
error?.response?.data?.Fault?.Error.map((e) => e.Detail).join(", ") ||
|
||||||
|
error?.response?.data ||
|
||||||
|
error?.message
|
||||||
});
|
});
|
||||||
console.log(error);
|
console.log(error);
|
||||||
logger.log("qbo-receivable-create-error", "ERROR", req.user.email, {
|
logger.log("qbo-receivable-create-error", "ERROR", req.user.email, {
|
||||||
@@ -254,7 +258,6 @@ async function InsertInsuranceCo(oauthClient, qbo_realmId, req, job, bodyshop) {
|
|||||||
throw new Error(
|
throw new Error(
|
||||||
`Insurance Company '${job.ins_co_nm}' not found in shop configuration. Please make sure it exists or change the insurance company name on the job to one that exists.`
|
`Insurance Company '${job.ins_co_nm}' not found in shop configuration. Please make sure it exists or change the insurance company name on the job to one that exists.`
|
||||||
);
|
);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
const Customer = {
|
const Customer = {
|
||||||
DisplayName: job.ins_co_nm.trim(),
|
DisplayName: job.ins_co_nm.trim(),
|
||||||
@@ -575,7 +578,9 @@ async function InsertInvoice(oauthClient, qbo_realmId, req, job, bodyshop, paren
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.log("qbo-receivables-error", "DEBUG", req.user.email, job.id, {
|
logger.log("qbo-receivables-error", "DEBUG", req.user.email, job.id, {
|
||||||
error,
|
error,
|
||||||
method: "InsertOwner"
|
method: "InsertInvoice",
|
||||||
|
validationError: JSON.stringify(error?.response?.data),
|
||||||
|
accountmeta: JSON.stringify({ items, taxCodes, classes })
|
||||||
});
|
});
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,6 +31,12 @@ const ftpSetup = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
exports.default = async (req, res) => {
|
exports.default = async (req, res) => {
|
||||||
|
// Only process if in production environment.
|
||||||
|
if (process.env.NODE_ENV !== "production") {
|
||||||
|
res.sendStatus(403);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
//Query for the List of Bodyshop Clients.
|
//Query for the List of Bodyshop Clients.
|
||||||
logger.log("autohouse-start", "DEBUG", "api", null, null);
|
logger.log("autohouse-start", "DEBUG", "api", null, null);
|
||||||
const { bodyshops } = await client.request(queries.GET_AUTOHOUSE_SHOPS);
|
const { bodyshops } = await client.request(queries.GET_AUTOHOUSE_SHOPS);
|
||||||
|
|||||||
@@ -31,6 +31,12 @@ const ftpSetup = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
exports.default = async (req, res) => {
|
exports.default = async (req, res) => {
|
||||||
|
// Only process if in production environment.
|
||||||
|
if (process.env.NODE_ENV !== "production") {
|
||||||
|
res.sendStatus(403);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
//Query for the List of Bodyshop Clients.
|
//Query for the List of Bodyshop Clients.
|
||||||
logger.log("claimscorp-start", "DEBUG", "api", null, null);
|
logger.log("claimscorp-start", "DEBUG", "api", null, null);
|
||||||
const { bodyshops } = await client.request(queries.GET_CLAIMSCORP_SHOPS);
|
const { bodyshops } = await client.request(queries.GET_CLAIMSCORP_SHOPS);
|
||||||
|
|||||||
@@ -31,6 +31,12 @@ const ftpSetup = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
exports.default = async (req, res) => {
|
exports.default = async (req, res) => {
|
||||||
|
// Only process if in production environment.
|
||||||
|
if (process.env.NODE_ENV !== "production") {
|
||||||
|
res.sendStatus(403);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
//Query for the List of Bodyshop Clients.
|
//Query for the List of Bodyshop Clients.
|
||||||
logger.log("kaizen-start", "DEBUG", "api", null, null);
|
logger.log("kaizen-start", "DEBUG", "api", null, null);
|
||||||
const kaizenShopsIDs = ["SUMMIT", "STRATHMORE", "SUNRIDGE", "SHAW"];
|
const kaizenShopsIDs = ["SUMMIT", "STRATHMORE", "SUNRIDGE", "SHAW"];
|
||||||
@@ -56,8 +62,8 @@ exports.default = async (req, res) => {
|
|||||||
try {
|
try {
|
||||||
const { jobs, bodyshops_by_pk } = await client.request(queries.KAIZEN_QUERY, {
|
const { jobs, bodyshops_by_pk } = await client.request(queries.KAIZEN_QUERY, {
|
||||||
bodyshopid: bodyshop.id,
|
bodyshopid: bodyshop.id,
|
||||||
start: start ? moment(start).startOf("hours") : moment().subtract(2, "hours").startOf("hour"),
|
start: start ? moment(start).startOf("day") : moment().subtract(5, "days").startOf("day"),
|
||||||
...(end && { end: moment(end).endOf("hours") })
|
...(end && { end: moment(end).endOf("day") })
|
||||||
});
|
});
|
||||||
|
|
||||||
const kaizenObject = {
|
const kaizenObject = {
|
||||||
@@ -176,24 +182,19 @@ exports.default = async (req, res) => {
|
|||||||
} finally {
|
} finally {
|
||||||
sftp.end();
|
sftp.end();
|
||||||
}
|
}
|
||||||
// sendServerEmail({
|
sendServerEmail({
|
||||||
// subject: `Kaizen Report ${moment().format("MM-DD-YY")}`,
|
subject: `Kaizen Report ${moment().format("MM-DD-YY")}`,
|
||||||
// text: `Errors: ${allErrors.map((e) => JSON.stringify(e, null, 2))}
|
text: `Errors: ${allErrors.map((e) => JSON.stringify(e, null, 2))}
|
||||||
// Uploaded: ${JSON.stringify(
|
Uploaded: ${JSON.stringify(
|
||||||
// allxmlsToUpload.map((x) => ({ filename: x.filename, count: x.count })),
|
allxmlsToUpload.map((x) => ({ filename: x.filename, count: x.count })),
|
||||||
// null,
|
null,
|
||||||
// 2
|
2
|
||||||
// )}
|
)}
|
||||||
// `,
|
`
|
||||||
// });
|
});
|
||||||
res.sendStatus(200);
|
res.sendStatus(200);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
res.status(200).json(error);
|
res.status(200).json(error);
|
||||||
sendServerEmail({
|
|
||||||
subject: `Kaizen Report ${moment().format("MM-DD-YY @ HH:mm:ss")}`,
|
|
||||||
text: `Errors: JSON.stringify(error)}
|
|
||||||
All Errors: ${allErrors.map((e) => JSON.stringify(e, null, 2))}`
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -965,22 +965,17 @@ function CalculateTaxesTotals(job, otherTotals) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (job.adjustment_bottom_line) {
|
if (job.adjustment_bottom_line && job.adjustment_bottom_line !== 0) {
|
||||||
const subtotal_before_adjustment = subtotal.add(Dinero({ amount: Math.round(job.adjustment_bottom_line * -100) }));
|
for (let tyCounter = 1; tyCounter <= 5; tyCounter++) {
|
||||||
const percent_of_adjustment =
|
if (IsTrueOrYes(pfp["PAN"][`prt_tx_in${tyCounter}`])) {
|
||||||
Math.round(
|
//This amount is taxable for this type.
|
||||||
subtotal_before_adjustment.toUnit() /
|
taxableAmountsByTier[`ty${tyCounter}Tax`] = taxableAmountsByTier[`ty${tyCounter}Tax`].add(
|
||||||
(job.adjustment_bottom_line > 0 ? job.adjustment_bottom_line : job.adjustment_bottom_line * -1)
|
Dinero({
|
||||||
) / 100;
|
amount: Math.round(job.adjustment_bottom_line * 100)
|
||||||
|
})
|
||||||
Object.keys(taxableAmountsByTier).forEach((taxTierKey) => {
|
);
|
||||||
taxable_adjustment = taxableAmountsByTier[taxTierKey].multiply(percent_of_adjustment);
|
|
||||||
if (job.adjustment_bottom_line > 0) {
|
|
||||||
taxableAmountsByTier[taxTierKey] = taxableAmountsByTier[taxTierKey].add(taxable_adjustment);
|
|
||||||
} else {
|
|
||||||
taxableAmountsByTier[taxTierKey] = taxableAmountsByTier[taxTierKey].subtract(taxable_adjustment);
|
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const remainingTaxableAmounts = taxableAmountsByTier;
|
const remainingTaxableAmounts = taxableAmountsByTier;
|
||||||
|
|||||||
Reference in New Issue
Block a user