Compare commits

..

2 Commits

Author SHA1 Message Date
Dave Richer
18da11f593 - Merge master-aio, bump packages.
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-08-19 12:27:30 -04:00
Dave Richer
136d52ec0b - Merge Master, update packages.
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-07-22 14:47:44 -04:00
97 changed files with 3910 additions and 6590 deletions

View File

@@ -5,7 +5,6 @@ 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:
@@ -19,12 +18,6 @@ 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:
@@ -40,16 +33,11 @@ jobs:
- run: - run:
name: Execute migration name: Execute migration
command: | command: |
curl -L https://github.com/hasura/graphql-engine/raw/stable/cli/get.sh | bash npm install hasura-cli -g
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
@@ -74,7 +62,6 @@ 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
@@ -99,12 +86,6 @@ 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:
@@ -118,12 +99,7 @@ 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
@@ -138,16 +114,11 @@ jobs:
- run: - run:
name: Execute migration name: Execute migration
command: | command: |
curl -L https://github.com/hasura/graphql-engine/raw/stable/cli/get.sh | bash npm install hasura-cli -g
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
@@ -172,12 +143,6 @@ 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:
@@ -203,12 +168,6 @@ 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:
@@ -224,18 +183,10 @@ jobs:
- run: - run:
name: Execute migration name: Execute migration
command: | command: |
curl -L https://github.com/hasura/graphql-engine/raw/stable/cli/get.sh | bash npm install hasura-cli -g
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:
@@ -261,12 +212,6 @@ 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:
@@ -292,12 +237,6 @@ 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:
@@ -313,18 +252,10 @@ jobs:
- run: - run:
name: Execute migration name: Execute migration
command: | command: |
curl -L https://github.com/hasura/graphql-engine/raw/stable/cli/get.sh | bash npm install hasura-cli -g
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:
@@ -371,12 +302,7 @@ 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:
@@ -427,7 +353,7 @@ workflows:
secret: ${HASURA_PROD_SECRET} secret: ${HASURA_PROD_SECRET}
filters: filters:
branches: branches:
only: master-AIO only: master
- rome-api-deploy: - rome-api-deploy:
filters: filters:
branches: branches:
@@ -437,7 +363,7 @@ workflows:
branches: branches:
only: master-AIO only: master-AIO
- rome-hasura-migrate: - rome-hasura-migrate:
secret: ${HASURA_ROME_PROD_SECRET} secret: ${HASURA_PROD_SECRET}
filters: filters:
branches: branches:
only: master-AIO only: master-AIO

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
VITE_APP_GRAPHQL_ENDPOINT=https://db.dev.imex.online/v1/graphql VITE_APP_GRAPHQL_ENDPOINT=https://db.dev.bodyshop.app/v1/graphql
VITE_APP_GRAPHQL_ENDPOINT_WS=wss://db.dev.imex.online/v1/graphql VITE_APP_GRAPHQL_ENDPOINT_WS=wss://db.dev.bodyshop.app/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

View File

@@ -1,5 +1,5 @@
VITE_APP_GRAPHQL_ENDPOINT=https://db.dev.imex.online/v1/graphql VITE_APP_GRAPHQL_ENDPOINT=https://db.dev.bodyshop.app/v1/graphql
VITE_APP_GRAPHQL_ENDPOINT_WS=wss://db.dev.imex.online/v1/graphql VITE_APP_GRAPHQL_ENDPOINT_WS=wss://db.dev.bodyshop.app/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

View File

@@ -1,8 +1,7 @@
VITE_APP_GRAPHQL_ENDPOINT=https://db.dev.imex.online/v1/graphql VITE_APP_GRAPHQL_ENDPOINT=https://db.dev.bodyshop.app/v1/graphql
VITE_APP_GRAPHQL_ENDPOINT_WS=wss://db.dev.imex.online/v1/graphql VITE_APP_GRAPHQL_ENDPOINT_WS=wss://db.dev.bodyshop.app/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

View File

@@ -2,9 +2,6 @@
<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') { %>

183
client/package-lock.json generated
View File

@@ -19,7 +19,7 @@
"@splitsoftware/splitio-react": "^1.12.1", "@splitsoftware/splitio-react": "^1.12.1",
"@tanem/react-nprogress": "^5.0.51", "@tanem/react-nprogress": "^5.0.51",
"@vitejs/plugin-react": "^4.3.1", "@vitejs/plugin-react": "^4.3.1",
"antd": "^5.20.1", "antd": "^5.20.2",
"apollo-link-logger": "^2.0.1", "apollo-link-logger": "^2.0.1",
"apollo-link-sentry": "^3.3.0", "apollo-link-sentry": "^3.3.0",
"autosize": "^6.0.1", "autosize": "^6.0.1",
@@ -32,12 +32,12 @@
"dotenv": "^16.4.5", "dotenv": "^16.4.5",
"env-cmd": "^10.1.0", "env-cmd": "^10.1.0",
"exifr": "^7.1.3", "exifr": "^7.1.3",
"firebase": "^10.12.5", "firebase": "^10.13.0",
"graphql": "^16.9.0", "graphql": "^16.9.0",
"i18next": "^23.12.3", "i18next": "^23.14.0",
"i18next-browser-languagedetector": "^8.0.0", "i18next-browser-languagedetector": "^8.0.0",
"immutability-helper": "^3.1.1", "immutability-helper": "^3.1.1",
"libphonenumber-js": "^1.11.5", "libphonenumber-js": "^1.11.7",
"logrocket": "^8.1.2", "logrocket": "^8.1.2",
"markerjs2": "^2.32.1", "markerjs2": "^2.32.1",
"memoize-one": "^6.0.0", "memoize-one": "^6.0.0",
@@ -47,7 +47,7 @@
"query-string": "^9.1.0", "query-string": "^9.1.0",
"raf-schd": "^4.0.3", "raf-schd": "^4.0.3",
"react": "^18.3.1", "react": "^18.3.1",
"react-big-calendar": "^1.13.2", "react-big-calendar": "^1.13.3",
"react-color": "^2.19.3", "react-color": "^2.19.3",
"react-cookie": "^7.2.0", "react-cookie": "^7.2.0",
"react-dom": "^18.3.1", "react-dom": "^18.3.1",
@@ -60,10 +60,10 @@
"react-markdown": "^9.0.1", "react-markdown": "^9.0.1",
"react-number-format": "^5.4.0", "react-number-format": "^5.4.0",
"react-popopo": "^2.1.9", "react-popopo": "^2.1.9",
"react-product-fruits": "^2.2.6", "react-product-fruits": "^2.2.61",
"react-redux": "^9.1.2", "react-redux": "^9.1.2",
"react-resizable": "^3.0.5", "react-resizable": "^3.0.5",
"react-router-dom": "^6.26.0", "react-router-dom": "^6.26.1",
"react-sticky": "^6.0.3", "react-sticky": "^6.0.3",
"react-virtualized": "^9.22.5", "react-virtualized": "^9.22.5",
"react-virtuoso": "^4.10.1", "react-virtuoso": "^4.10.1",
@@ -103,14 +103,13 @@
"react-error-overlay": "6.0.11", "react-error-overlay": "6.0.11",
"redux-logger": "^3.0.6", "redux-logger": "^3.0.6",
"source-map-explorer": "^2.5.3", "source-map-explorer": "^2.5.3",
"vite": "^5.4.0", "vite": "^5.4.1",
"vite-plugin-babel": "^1.2.0", "vite-plugin-babel": "^1.2.0",
"vite-plugin-eslint": "^1.8.1", "vite-plugin-eslint": "^1.8.1",
"vite-plugin-legacy": "^2.1.0", "vite-plugin-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"
@@ -3499,9 +3498,9 @@
"license": "Apache-2.0" "license": "Apache-2.0"
}, },
"node_modules/@firebase/app": { "node_modules/@firebase/app": {
"version": "0.10.8", "version": "0.10.9",
"resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.10.8.tgz", "resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.10.9.tgz",
"integrity": "sha512-xSLmW0/RShcnUEXH7l+wC0AFWaUtty4tUFF2loIgbtXTRmra0UH/SqYDf/IcfreUninRrCsusNmvoTidGkXJPw==", "integrity": "sha512-AmGlPg/4SoDhwCdvVDeZsN5Yn+czYD/m/NAEOOCOhwn3Cz1xmEFKAKcyZKKahLrh5QPmge5Adyw+sk3cBTubBg==",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@firebase/component": "0.6.8", "@firebase/component": "0.6.8",
@@ -3555,12 +3554,12 @@
"license": "Apache-2.0" "license": "Apache-2.0"
}, },
"node_modules/@firebase/app-compat": { "node_modules/@firebase/app-compat": {
"version": "0.2.38", "version": "0.2.39",
"resolved": "https://registry.npmjs.org/@firebase/app-compat/-/app-compat-0.2.38.tgz", "resolved": "https://registry.npmjs.org/@firebase/app-compat/-/app-compat-0.2.39.tgz",
"integrity": "sha512-36ZrSvkYLW7QR01Sii2X+IY18ErMpRg6e2B2f/DVTtJBolthwXOnNBps+wvaVBvegdvdVPspgDXZUV0ppqh45w==", "integrity": "sha512-NnTFywe+M/jxZn751NIEhidgDePiDvlcfabvGxBy4YbU1E+b0TpEuJUnm3L6YDZtaZLVEz8ieoq9wbJkgGZ2rg==",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@firebase/app": "0.10.8", "@firebase/app": "0.10.9",
"@firebase/component": "0.6.8", "@firebase/component": "0.6.8",
"@firebase/logger": "0.4.2", "@firebase/logger": "0.4.2",
"@firebase/util": "1.9.7", "@firebase/util": "1.9.7",
@@ -3574,9 +3573,9 @@
"license": "Apache-2.0" "license": "Apache-2.0"
}, },
"node_modules/@firebase/auth": { "node_modules/@firebase/auth": {
"version": "1.7.6", "version": "1.7.7",
"resolved": "https://registry.npmjs.org/@firebase/auth/-/auth-1.7.6.tgz", "resolved": "https://registry.npmjs.org/@firebase/auth/-/auth-1.7.7.tgz",
"integrity": "sha512-T+lA5xoug9CByGYkD5WkfTh2ujEYq/frGZPbk0H+fNU6fNl7nqg88KcsmzsC6Fsqbjm3LLEb/i6wJvF6NSNEig==", "integrity": "sha512-gMB0uRRNiIvYorEDLtIq1mc7x5D080EsoghTIph9xnbLqcQS3qRBREEC2o21nMEhviAeiGJMelRkKhAkkggjmA==",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@firebase/component": "0.6.8", "@firebase/component": "0.6.8",
@@ -3596,12 +3595,12 @@
} }
}, },
"node_modules/@firebase/auth-compat": { "node_modules/@firebase/auth-compat": {
"version": "0.5.11", "version": "0.5.12",
"resolved": "https://registry.npmjs.org/@firebase/auth-compat/-/auth-compat-0.5.11.tgz", "resolved": "https://registry.npmjs.org/@firebase/auth-compat/-/auth-compat-0.5.12.tgz",
"integrity": "sha512-7rE3MkQDoWwI2qd8qsra4/QZCO2GzQSbCL6AVQpult9+Nbimg+5A+YeHxpLTcYAxUV6HDg2CqTDQreFLhcm1CQ==", "integrity": "sha512-K47inLqjTREez85D7pP0TmRv5aQcap22cJW67poLwJoJ6BVVH0I2NOfIoMqENetCrgGS+7vXSIZaLjvHFHwS+g==",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@firebase/auth": "1.7.6", "@firebase/auth": "1.7.7",
"@firebase/auth-types": "0.12.2", "@firebase/auth-types": "0.12.2",
"@firebase/component": "0.6.8", "@firebase/component": "0.6.8",
"@firebase/util": "1.9.7", "@firebase/util": "1.9.7",
@@ -3676,9 +3675,9 @@
} }
}, },
"node_modules/@firebase/firestore": { "node_modules/@firebase/firestore": {
"version": "4.6.5", "version": "4.7.0",
"resolved": "https://registry.npmjs.org/@firebase/firestore/-/firestore-4.6.5.tgz", "resolved": "https://registry.npmjs.org/@firebase/firestore/-/firestore-4.7.0.tgz",
"integrity": "sha512-0+Ascaht4qUzj4pCopMPWmoAujk8HKjwCpaNYOOjbYMZ65RVfZPsfZwwbWi/zWMXj6xvPsai5oBiErUUkrLwNw==", "integrity": "sha512-wGOp84P1qa1pfpdct6lckfyowTuvIlUDHoiRcN8dFDT4WnZDh0tZW1X77SMiBUVejK8xIRLBCK3yDTejlRVrUA==",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@firebase/component": "0.6.8", "@firebase/component": "0.6.8",
@@ -3698,13 +3697,13 @@
} }
}, },
"node_modules/@firebase/firestore-compat": { "node_modules/@firebase/firestore-compat": {
"version": "0.3.34", "version": "0.3.35",
"resolved": "https://registry.npmjs.org/@firebase/firestore-compat/-/firestore-compat-0.3.34.tgz", "resolved": "https://registry.npmjs.org/@firebase/firestore-compat/-/firestore-compat-0.3.35.tgz",
"integrity": "sha512-OBP2F/Ccydl2U2j8XIfpKBxf0EnQHEhbZ4LTwbSS2QlG9+8TwhvKFkKk/ZljWYqaype+qFKPuXZ5flCqYEETeA==", "integrity": "sha512-VdYQtMIrPjsgZpuBwvry6LgcS0vbUhHzpebaKm5oc9oTTvP4K7oxvR/ZJdDjIE5rBugn1SdY++uGMatcIvBkZg==",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@firebase/component": "0.6.8", "@firebase/component": "0.6.8",
"@firebase/firestore": "4.6.5", "@firebase/firestore": "4.7.0",
"@firebase/firestore-types": "3.0.2", "@firebase/firestore-types": "3.0.2",
"@firebase/util": "1.9.7", "@firebase/util": "1.9.7",
"tslib": "^2.1.0" "tslib": "^2.1.0"
@@ -3913,9 +3912,10 @@
"integrity": "sha512-0BC4+Ud7y2aPTyhXJTMTFfrGGLqdYXrUB9sJVAB8NiqJswDTc4/2qrE/yfUbnQJhbSi6ZaTTBKyG3n1nplssaA==" "integrity": "sha512-0BC4+Ud7y2aPTyhXJTMTFfrGGLqdYXrUB9sJVAB8NiqJswDTc4/2qrE/yfUbnQJhbSi6ZaTTBKyG3n1nplssaA=="
}, },
"node_modules/@firebase/storage": { "node_modules/@firebase/storage": {
"version": "0.12.6", "version": "0.13.0",
"resolved": "https://registry.npmjs.org/@firebase/storage/-/storage-0.12.6.tgz", "resolved": "https://registry.npmjs.org/@firebase/storage/-/storage-0.13.0.tgz",
"integrity": "sha512-Zgb9WuehJxzhj7pGXUvkAEaH+3HvLjD9xSZ9nepuXf5f8378xME7oGJtREr/RnepdDA5YW0XIxe0QQBNHpe1nw==", "integrity": "sha512-3RQaYpkR4TwPnPR1XlmDUAXiYt5QVQRGRGY1+/yNyS9ohHOCNNgbcs6a+QYvaDInbYTywrdddKYMFFXKKb1pRg==",
"license": "Apache-2.0",
"dependencies": { "dependencies": {
"@firebase/component": "0.6.8", "@firebase/component": "0.6.8",
"@firebase/util": "1.9.7", "@firebase/util": "1.9.7",
@@ -3927,12 +3927,13 @@
} }
}, },
"node_modules/@firebase/storage-compat": { "node_modules/@firebase/storage-compat": {
"version": "0.3.9", "version": "0.3.10",
"resolved": "https://registry.npmjs.org/@firebase/storage-compat/-/storage-compat-0.3.9.tgz", "resolved": "https://registry.npmjs.org/@firebase/storage-compat/-/storage-compat-0.3.10.tgz",
"integrity": "sha512-WWgAp5bTW961oIsCc9+98m4MIVKpEqztAlIngfHfwO/x3DYoBPRl/awMRG3CAXyVxG+7B7oHC5IsnqM+vTwx2A==", "integrity": "sha512-KcikeV5dK1H1cXi0zEb7gJ3IZ4dKKCjpyucVK8r/Qv5eNAqeQAzPgKKhsSv67wT1N6DTxmqsNEXwMo0dcrKOEg==",
"license": "Apache-2.0",
"dependencies": { "dependencies": {
"@firebase/component": "0.6.8", "@firebase/component": "0.6.8",
"@firebase/storage": "0.12.6", "@firebase/storage": "0.13.0",
"@firebase/storage-types": "0.8.2", "@firebase/storage-types": "0.8.2",
"@firebase/util": "1.9.7", "@firebase/util": "1.9.7",
"tslib": "^2.1.0" "tslib": "^2.1.0"
@@ -3945,6 +3946,7 @@
"version": "0.8.2", "version": "0.8.2",
"resolved": "https://registry.npmjs.org/@firebase/storage-types/-/storage-types-0.8.2.tgz", "resolved": "https://registry.npmjs.org/@firebase/storage-types/-/storage-types-0.8.2.tgz",
"integrity": "sha512-0vWu99rdey0g53lA7IShoA2Lol1jfnPovzLDUBuon65K7uKG9G+L5uO05brD9pMw+l4HRFw23ah3GwTGpEav6g==", "integrity": "sha512-0vWu99rdey0g53lA7IShoA2Lol1jfnPovzLDUBuon65K7uKG9G+L5uO05brD9pMw+l4HRFw23ah3GwTGpEav6g==",
"license": "Apache-2.0",
"peerDependencies": { "peerDependencies": {
"@firebase/app-types": "0.x", "@firebase/app-types": "0.x",
"@firebase/util": "1.x" "@firebase/util": "1.x"
@@ -4615,9 +4617,9 @@
} }
}, },
"node_modules/@remix-run/router": { "node_modules/@remix-run/router": {
"version": "1.19.0", "version": "1.19.1",
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.19.0.tgz", "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.19.1.tgz",
"integrity": "sha512-zDICCLKEwbVYTS6TjYaWtHXxkdoUvD/QXvyVZjGCsWz5vyH7aFeONlPffPdW+Y/t6KT0MgXb2Mfjun9YpWN1dA==", "integrity": "sha512-S45oynt/WH19bHbIXjtli6QmwNYvaz+vtnubvNpNDvUOoA/OWh6j1OikIP3G+v5GHdxyC6EXoChG3HgYGEUfcg==",
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=14.0.0" "node": ">=14.0.0"
@@ -6334,9 +6336,9 @@
} }
}, },
"node_modules/antd": { "node_modules/antd": {
"version": "5.20.1", "version": "5.20.2",
"resolved": "https://registry.npmjs.org/antd/-/antd-5.20.1.tgz", "resolved": "https://registry.npmjs.org/antd/-/antd-5.20.2.tgz",
"integrity": "sha512-YjVCYAfBjrTyNKsg+heAOR0Gm4qJNJoBZQcV1h1BX/ufwoLx0PC5RGs75g6gQFy/1nv8OrJH7DXUGdtwPMB3Vg==", "integrity": "sha512-9d6Bs5ZKIV+JhB0eD7KxYnIfnhUh86kNtTGIuNiIxHFUhbuyT1DXN2SuMksDmtSfuRYZ82/C4hq+OJjWNNbmHg==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@ant-design/colors": "^7.1.0", "@ant-design/colors": "^7.1.0",
@@ -6360,7 +6362,7 @@
"rc-dialog": "~9.5.2", "rc-dialog": "~9.5.2",
"rc-drawer": "~7.2.0", "rc-drawer": "~7.2.0",
"rc-dropdown": "~4.2.0", "rc-dropdown": "~4.2.0",
"rc-field-form": "~2.3.0", "rc-field-form": "~2.4.0",
"rc-image": "~7.9.0", "rc-image": "~7.9.0",
"rc-input": "~1.6.3", "rc-input": "~1.6.3",
"rc-input-number": "~9.2.0", "rc-input-number": "~9.2.0",
@@ -6369,7 +6371,7 @@
"rc-motion": "^2.9.2", "rc-motion": "^2.9.2",
"rc-notification": "~5.6.0", "rc-notification": "~5.6.0",
"rc-pagination": "~4.2.0", "rc-pagination": "~4.2.0",
"rc-picker": "~4.6.12", "rc-picker": "~4.6.13",
"rc-progress": "~4.0.0", "rc-progress": "~4.0.0",
"rc-rate": "~2.13.0", "rc-rate": "~2.13.0",
"rc-resize-observer": "^1.4.0", "rc-resize-observer": "^1.4.0",
@@ -6384,7 +6386,7 @@
"rc-tooltip": "~6.2.0", "rc-tooltip": "~6.2.0",
"rc-tree": "~5.8.8", "rc-tree": "~5.8.8",
"rc-tree-select": "~5.22.1", "rc-tree-select": "~5.22.1",
"rc-upload": "~4.6.0", "rc-upload": "~4.7.0",
"rc-util": "^5.43.0", "rc-util": "^5.43.0",
"scroll-into-view-if-needed": "^3.1.0", "scroll-into-view-if-needed": "^3.1.0",
"throttle-debounce": "^5.0.2" "throttle-debounce": "^5.0.2"
@@ -10150,24 +10152,24 @@
} }
}, },
"node_modules/firebase": { "node_modules/firebase": {
"version": "10.12.5", "version": "10.13.0",
"resolved": "https://registry.npmjs.org/firebase/-/firebase-10.12.5.tgz", "resolved": "https://registry.npmjs.org/firebase/-/firebase-10.13.0.tgz",
"integrity": "sha512-J0yL3yh12CfFprTkSOQ9HqBugERyqvWwOuOoo1j1QHmYe9cYLKnBmtNCvGIYInDcsVUnJoRXCM+hxbGf48oVhg==", "integrity": "sha512-a8gm8c9CYO98QuXJn7m5W5Gj7kHV8fme81/mQ9dBs+VMz9uI5HdavnMVPXCILputpZFMFpiKK+u7VVsn5lQg+w==",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@firebase/analytics": "0.10.7", "@firebase/analytics": "0.10.7",
"@firebase/analytics-compat": "0.2.13", "@firebase/analytics-compat": "0.2.13",
"@firebase/app": "0.10.8", "@firebase/app": "0.10.9",
"@firebase/app-check": "0.8.7", "@firebase/app-check": "0.8.7",
"@firebase/app-check-compat": "0.3.14", "@firebase/app-check-compat": "0.3.14",
"@firebase/app-compat": "0.2.38", "@firebase/app-compat": "0.2.39",
"@firebase/app-types": "0.9.2", "@firebase/app-types": "0.9.2",
"@firebase/auth": "1.7.6", "@firebase/auth": "1.7.7",
"@firebase/auth-compat": "0.5.11", "@firebase/auth-compat": "0.5.12",
"@firebase/database": "1.0.7", "@firebase/database": "1.0.7",
"@firebase/database-compat": "1.0.7", "@firebase/database-compat": "1.0.7",
"@firebase/firestore": "4.6.5", "@firebase/firestore": "4.7.0",
"@firebase/firestore-compat": "0.3.34", "@firebase/firestore-compat": "0.3.35",
"@firebase/functions": "0.11.6", "@firebase/functions": "0.11.6",
"@firebase/functions-compat": "0.3.12", "@firebase/functions-compat": "0.3.12",
"@firebase/installations": "0.6.8", "@firebase/installations": "0.6.8",
@@ -10178,8 +10180,8 @@
"@firebase/performance-compat": "0.2.8", "@firebase/performance-compat": "0.2.8",
"@firebase/remote-config": "0.4.8", "@firebase/remote-config": "0.4.8",
"@firebase/remote-config-compat": "0.2.8", "@firebase/remote-config-compat": "0.2.8",
"@firebase/storage": "0.12.6", "@firebase/storage": "0.13.0",
"@firebase/storage-compat": "0.3.9", "@firebase/storage-compat": "0.3.10",
"@firebase/util": "1.9.7", "@firebase/util": "1.9.7",
"@firebase/vertexai-preview": "0.0.3" "@firebase/vertexai-preview": "0.0.3"
} }
@@ -10863,9 +10865,9 @@
} }
}, },
"node_modules/i18next": { "node_modules/i18next": {
"version": "23.12.3", "version": "23.14.0",
"resolved": "https://registry.npmjs.org/i18next/-/i18next-23.12.3.tgz", "resolved": "https://registry.npmjs.org/i18next/-/i18next-23.14.0.tgz",
"integrity": "sha512-DyigQmrR10V9U2N6pjhbfahW13GY7n8BQD9swN09JuRRropgsksWVi4vRLeex0Qf7zCPnBfIqQfhcBzdZBQBYw==", "integrity": "sha512-Y5GL4OdA8IU2geRrt2+Uc1iIhsjICdHZzT9tNwQ3TVqdNzgxHToGCKf/TPRP80vTCAP6svg2WbbJL+Gx5MFQVA==",
"funding": [ "funding": [
{ {
"type": "individual", "type": "individual",
@@ -11949,9 +11951,9 @@
} }
}, },
"node_modules/libphonenumber-js": { "node_modules/libphonenumber-js": {
"version": "1.11.5", "version": "1.11.7",
"resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.11.5.tgz", "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.11.7.tgz",
"integrity": "sha512-TwHR5BZxGRODtAfz03szucAkjT5OArXr+94SMtAM2pYXIlQNVMrxvb6uSCbnaJJV6QXEyICk7+l6QPgn72WHhg==", "integrity": "sha512-x2xON4/Qg2bRIS11KIN9yCNYUjhtiEjNyptjX0mX+pyKHecxuJVLIpfX1lq9ZD6CrC/rB+y4GBi18c6CEcUR+A==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/lie": { "node_modules/lie": {
@@ -14169,9 +14171,9 @@
} }
}, },
"node_modules/rc-field-form": { "node_modules/rc-field-form": {
"version": "2.3.0", "version": "2.4.0",
"resolved": "https://registry.npmjs.org/rc-field-form/-/rc-field-form-2.3.0.tgz", "resolved": "https://registry.npmjs.org/rc-field-form/-/rc-field-form-2.4.0.tgz",
"integrity": "sha512-QyiYrE3uweGGi21MJpxHFmDW+Tb1yt5hitM1k0EbWc5hKDiSf5imOBc6NLLHrYk+sdelrw2Ju/fD4uRQdhSqNg==", "integrity": "sha512-XZ/lF9iqf9HXApIHQHqzJK5v2w4mkUMsVqAzOyWVzoiwwXEavY6Tpuw7HavgzIoD+huVff4JghSGcgEfX6eycg==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/runtime": "^7.18.0", "@babel/runtime": "^7.18.0",
@@ -14610,9 +14612,10 @@
} }
}, },
"node_modules/rc-upload": { "node_modules/rc-upload": {
"version": "4.6.0", "version": "4.7.0",
"resolved": "https://registry.npmjs.org/rc-upload/-/rc-upload-4.6.0.tgz", "resolved": "https://registry.npmjs.org/rc-upload/-/rc-upload-4.7.0.tgz",
"integrity": "sha512-Zr0DT1NHw/ApxrP7UAoxOtGaVYuzarrrCVr0ld7RiEFsKX07uFhE1EpCBxwL11ruFn89GMcshOKWp+s6FLyAlA==", "integrity": "sha512-eUwxYNHlsYe5vYhKFAUGrQG95JrnPzY+BmPi1Daq39fWNl/eOc7v4UODuWrVp2LFkQBuV3cMCG/I68iub6oBrg==",
"license": "MIT",
"dependencies": { "dependencies": {
"@babel/runtime": "^7.18.3", "@babel/runtime": "^7.18.3",
"classnames": "^2.2.5", "classnames": "^2.2.5",
@@ -14671,9 +14674,9 @@
} }
}, },
"node_modules/react-big-calendar": { "node_modules/react-big-calendar": {
"version": "1.13.2", "version": "1.13.3",
"resolved": "https://registry.npmjs.org/react-big-calendar/-/react-big-calendar-1.13.2.tgz", "resolved": "https://registry.npmjs.org/react-big-calendar/-/react-big-calendar-1.13.3.tgz",
"integrity": "sha512-yzeVRM1I+JloeJXytrZx2lJWKUfLAi5bsgGuBjh3aFSHZrdFcGnfA7LE6pBacdyOG+NGP+332m2MziszkmQWcw==", "integrity": "sha512-PaZEQMhZCPiInxkKeP9S1TgI1e0Gd4aPpueYXS97rMa89AfX2ZGEmoMjDyeOkIz+mA1utuYF/yQYaxGPNXS0Sg==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/runtime": "^7.20.7", "@babel/runtime": "^7.20.7",
@@ -14940,9 +14943,10 @@
} }
}, },
"node_modules/react-product-fruits": { "node_modules/react-product-fruits": {
"version": "2.2.6", "version": "2.2.61",
"resolved": "https://registry.npmjs.org/react-product-fruits/-/react-product-fruits-2.2.6.tgz", "resolved": "https://registry.npmjs.org/react-product-fruits/-/react-product-fruits-2.2.61.tgz",
"integrity": "sha512-f57m1rCD+Cu8QfFQSpkHJjLaWauPbD01GNrbh4icQaOHA/6bHJGpcoSSEQfAtk3g2PyQBpsDdRvL9jC+hyxhhQ==", "integrity": "sha512-zqmrtgwI6TLIR1g6pk9SzAutoKgC/7L1Y0ffsleymcAjSUxhHAla5A0bS2vMLq+WkgYKKH7Dl+Z9VxUywLEdeg==",
"license": "MIT",
"dependencies": { "dependencies": {
"product-fruits": "^1.0.25" "product-fruits": "^1.0.25"
}, },
@@ -14993,12 +14997,12 @@
} }
}, },
"node_modules/react-router": { "node_modules/react-router": {
"version": "6.26.0", "version": "6.26.1",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.26.0.tgz", "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.26.1.tgz",
"integrity": "sha512-wVQq0/iFYd3iZ9H2l3N3k4PL8EEHcb0XlU2Na8nEwmiXgIUElEH6gaJDtUQxJ+JFzmIXaQjfdpcGWaM6IoQGxg==", "integrity": "sha512-kIwJveZNwp7teQRI5QmwWo39A5bXRyqpH0COKKmPnyD2vBvDwgFXSqDUYtt1h+FEyfnE8eXr7oe0MxRzVwCcvQ==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@remix-run/router": "1.19.0" "@remix-run/router": "1.19.1"
}, },
"engines": { "engines": {
"node": ">=14.0.0" "node": ">=14.0.0"
@@ -15008,13 +15012,13 @@
} }
}, },
"node_modules/react-router-dom": { "node_modules/react-router-dom": {
"version": "6.26.0", "version": "6.26.1",
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.26.0.tgz", "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.26.1.tgz",
"integrity": "sha512-RRGUIiDtLrkX3uYcFiCIxKFWMcWQGMojpYZfcstc63A1+sSnVgILGIm9gNUA6na3Fm1QuPGSBQH2EMbAZOnMsQ==", "integrity": "sha512-veut7m41S1fLql4pLhxeSW3jlqs+4MtjRLj0xvuCEXsxusJCbs6I8yn9BxzzDX2XDgafrccY6hwjmd/bL54tFw==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@remix-run/router": "1.19.0", "@remix-run/router": "1.19.1",
"react-router": "6.26.0" "react-router": "6.26.1"
}, },
"engines": { "engines": {
"node": ">=14.0.0" "node": ">=14.0.0"
@@ -17552,14 +17556,14 @@
} }
}, },
"node_modules/vite": { "node_modules/vite": {
"version": "5.4.0", "version": "5.4.1",
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.0.tgz", "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.1.tgz",
"integrity": "sha512-5xokfMX0PIiwCMCMb9ZJcMyh5wbBun0zUzKib+L65vAZ8GY9ePZMXxFrHbr/Kyll2+LSCY7xtERPpxkBDKngwg==", "integrity": "sha512-1oE6yuNXssjrZdblI9AfBbHCC41nnyoVoEZxQnID6yvQZAFBzxxkqoFLtHUMkYunL8hwOLEjgTuxpkRxvba3kA==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"esbuild": "^0.21.3", "esbuild": "^0.21.3",
"postcss": "^8.4.40", "postcss": "^8.4.41",
"rollup": "^4.13.0" "rollup": "^4.13.0"
}, },
"bin": { "bin": {
@@ -18430,7 +18434,6 @@
"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"

View File

@@ -19,7 +19,7 @@
"@splitsoftware/splitio-react": "^1.12.1", "@splitsoftware/splitio-react": "^1.12.1",
"@tanem/react-nprogress": "^5.0.51", "@tanem/react-nprogress": "^5.0.51",
"@vitejs/plugin-react": "^4.3.1", "@vitejs/plugin-react": "^4.3.1",
"antd": "^5.20.1", "antd": "^5.20.2",
"apollo-link-logger": "^2.0.1", "apollo-link-logger": "^2.0.1",
"apollo-link-sentry": "^3.3.0", "apollo-link-sentry": "^3.3.0",
"autosize": "^6.0.1", "autosize": "^6.0.1",
@@ -32,12 +32,12 @@
"dotenv": "^16.4.5", "dotenv": "^16.4.5",
"env-cmd": "^10.1.0", "env-cmd": "^10.1.0",
"exifr": "^7.1.3", "exifr": "^7.1.3",
"firebase": "^10.12.5", "firebase": "^10.13.0",
"graphql": "^16.9.0", "graphql": "^16.9.0",
"i18next": "^23.12.3", "i18next": "^23.14.0",
"i18next-browser-languagedetector": "^8.0.0", "i18next-browser-languagedetector": "^8.0.0",
"immutability-helper": "^3.1.1", "immutability-helper": "^3.1.1",
"libphonenumber-js": "^1.11.5", "libphonenumber-js": "^1.11.7",
"logrocket": "^8.1.2", "logrocket": "^8.1.2",
"markerjs2": "^2.32.1", "markerjs2": "^2.32.1",
"memoize-one": "^6.0.0", "memoize-one": "^6.0.0",
@@ -47,7 +47,7 @@
"query-string": "^9.1.0", "query-string": "^9.1.0",
"raf-schd": "^4.0.3", "raf-schd": "^4.0.3",
"react": "^18.3.1", "react": "^18.3.1",
"react-big-calendar": "^1.13.2", "react-big-calendar": "^1.13.3",
"react-color": "^2.19.3", "react-color": "^2.19.3",
"react-cookie": "^7.2.0", "react-cookie": "^7.2.0",
"react-dom": "^18.3.1", "react-dom": "^18.3.1",
@@ -60,10 +60,10 @@
"react-markdown": "^9.0.1", "react-markdown": "^9.0.1",
"react-number-format": "^5.4.0", "react-number-format": "^5.4.0",
"react-popopo": "^2.1.9", "react-popopo": "^2.1.9",
"react-product-fruits": "^2.2.6", "react-product-fruits": "^2.2.61",
"react-redux": "^9.1.2", "react-redux": "^9.1.2",
"react-resizable": "^3.0.5", "react-resizable": "^3.0.5",
"react-router-dom": "^6.26.0", "react-router-dom": "^6.26.1",
"react-sticky": "^6.0.3", "react-sticky": "^6.0.3",
"react-virtualized": "^9.22.5", "react-virtualized": "^9.22.5",
"react-virtuoso": "^4.10.1", "react-virtuoso": "^4.10.1",
@@ -147,13 +147,12 @@
"react-error-overlay": "6.0.11", "react-error-overlay": "6.0.11",
"redux-logger": "^3.0.6", "redux-logger": "^3.0.6",
"source-map-explorer": "^2.5.3", "source-map-explorer": "^2.5.3",
"vite": "^5.4.0", "vite": "^5.4.1",
"vite-plugin-babel": "^1.2.0", "vite-plugin-babel": "^1.2.0",
"vite-plugin-eslint": "^1.8.1", "vite-plugin-eslint": "^1.8.1",
"vite-plugin-legacy": "^2.1.0", "vite-plugin-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"
} }
} }

View File

@@ -18,6 +18,7 @@ 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";
@@ -107,6 +108,8 @@ 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

View File

@@ -98,7 +98,7 @@ export function BillDetailEditcontainer({ setPartsOrderContext, insertAuditTrail
}); });
billlines.forEach((billline) => { billlines.forEach((billline) => {
const { deductedfromlbr, inventories, jobline, original_actual_price, create_ppc, ...il } = billline; const { deductedfromlbr, inventories, jobline, ...il } = billline;
delete il.__typename; delete il.__typename;
if (il.id) { if (il.id) {

View File

@@ -14,6 +14,7 @@ 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";
@@ -21,7 +22,6 @@ 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({
}) })
]} ]}
> >
<DateTimePicker isDateOnly disabled={disabled} /> <FormDatePicker disabled={disabled} />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label={t("bills.fields.is_credit_memo")} label={t("bills.fields.is_credit_memo")}

View File

@@ -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
} }
]} ]}
> >
<DateTimePicker isDateOnly /> <FormDatePicker />
</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">
<DateTimePicker isDateOnly /> <FormDatePicker />
</Form.Item> </Form.Item>
</LayoutFormRow> </LayoutFormRow>
<ContractsRatesChangeButton form={form} /> <ContractsRatesChangeButton form={form} />

View File

@@ -10,12 +10,16 @@ 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({ form, saveLoading, newCC }) { export default function CourtesyCarCreateFormComponent({
form,
saveLoading,
newCC,
}) {
const { t } = useTranslation(); const { t } = useTranslation();
const client = useApolloClient(); const client = useApolloClient();
@@ -157,16 +161,16 @@ export default function CourtesyCarCreateFormComponent({ form, saveLoading, newC
<Input /> <Input />
</Form.Item> </Form.Item>
<Form.Item label={t("courtesycars.fields.purchasedate")} name="purchasedate"> <Form.Item label={t("courtesycars.fields.purchasedate")} name="purchasedate">
<DateTimePicker isDateOnly /> <FormDatePicker />
</Form.Item> </Form.Item>
<Form.Item label={t("courtesycars.fields.servicestartdate")} name="servicestartdate"> <Form.Item label={t("courtesycars.fields.servicestartdate")} name="servicestartdate">
<DateTimePicker isDateOnly /> <FormDatePicker />
</Form.Item> </Form.Item>
<Form.Item label={t("courtesycars.fields.serviceenddate")} name="serviceenddate"> <Form.Item label={t("courtesycars.fields.serviceenddate")} name="serviceenddate">
<DateTimePicker isDateOnly /> <FormDatePicker />
</Form.Item> </Form.Item>
<Form.Item label={t("courtesycars.fields.leaseenddate")} name="leaseenddate"> <Form.Item label={t("courtesycars.fields.leaseenddate")} name="leaseenddate">
<DateTimePicker isDateOnly /> <FormDatePicker />
</Form.Item> </Form.Item>
</LayoutFormRow> </LayoutFormRow>
@@ -224,7 +228,7 @@ export default function CourtesyCarCreateFormComponent({ form, saveLoading, newC
</div> </div>
<div> <div>
<Form.Item label={t("courtesycars.fields.nextservicedate")} name="nextservicedate"> <Form.Item label={t("courtesycars.fields.nextservicedate")} name="nextservicedate">
<DateTimePicker isDateOnly /> <FormDatePicker />
</Form.Item> </Form.Item>
<Form.Item shouldUpdate={(p, c) => p.nextservicedate !== c.nextservicedate}> <Form.Item shouldUpdate={(p, c) => p.nextservicedate !== c.nextservicedate}>
{() => { {() => {
@@ -256,7 +260,7 @@ export default function CourtesyCarCreateFormComponent({ form, saveLoading, newC
</Form.Item> </Form.Item>
<div> <div>
<Form.Item label={t("courtesycars.fields.registrationexpires")} name="registrationexpires"> <Form.Item label={t("courtesycars.fields.registrationexpires")} name="registrationexpires">
<DateTimePicker isDateOnly /> <FormDatePicker />
</Form.Item> </Form.Item>
<Form.Item shouldUpdate={(p, c) => p.registrationexpires !== c.registrationexpires}> <Form.Item shouldUpdate={(p, c) => p.registrationexpires !== c.registrationexpires}>
{() => { {() => {
@@ -289,7 +293,7 @@ export default function CourtesyCarCreateFormComponent({ form, saveLoading, newC
} }
]} ]}
> >
<DateTimePicker isDateOnly /> <FormDatePicker />
</Form.Item> </Form.Item>
<Form.Item shouldUpdate={(p, c) => p.insuranceexpires !== c.insuranceexpires}> <Form.Item shouldUpdate={(p, c) => p.insuranceexpires !== c.insuranceexpires}>
{() => { {() => {

View File

@@ -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 DateTimePicker from "../form-date-time-picker/form-date-time-picker.component.jsx"; import FormDatePicker from "../form-date-picker/form-date-picker.component";
export default function CourtesyCarReturnModalComponent() { export default function CourtesyCarReturnModalComponent() {
const { t } = useTranslation(); const { t } = useTranslation();
@@ -19,7 +19,7 @@ export default function CourtesyCarReturnModalComponent() {
} }
]} ]}
> >
<DateTimePicker isDateOnly /> <FormDatePicker />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label={t("contracts.fields.kmend")} label={t("contracts.fields.kmend")}

View File

@@ -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")}>
<DateTimePicker isDateOnly /> <FormDatePicker />
</Form.Item> </Form.Item>
</LayoutFormRow> </LayoutFormRow>
<Space> <Space>

View File

@@ -4,6 +4,7 @@ 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";
@@ -11,7 +12,6 @@ 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 }) => (
} }
]} ]}
> >
<DateTimePicker isDateOnly onChange={handleChange} onlyToday aria-label={t("eula.labels.date_accepted")} /> <FormDatePicker onChange={handleChange} onlyToday aria-label={t("eula.labels.date_accepted")} />
</Form.Item> </Form.Item>
</Col> </Col>
</Row> </Row>

View File

@@ -0,0 +1,123 @@
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>
);
}

View File

@@ -0,0 +1,48 @@
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>
);
}

View File

@@ -1,105 +1,46 @@
import { DatePicker } from "antd"; import React, { forwardRef } from "react";
import PropTypes from "prop-types"; //import DatePicker from "react-datepicker";
import React, { useCallback, useState } from "react"; //import "react-datepicker/src/stylesheets/datepicker.scss";
import { useTranslation } from "react-i18next"; import { Space, TimePicker } from "antd";
import dayjs from "../../utils/day"; import dayjs from "../../utils/day";
import { fuzzyMatchDate } from "./formats.js"; import FormDatePicker from "../form-date-picker/form-date-picker.component";
//To be used as a form element only.
const DateTimePicker = ({ value, onChange, onBlur, id, onlyFuture, onlyToday, isDateOnly = false, ...restProps }) => { const DateTimePicker = ({ value, onChange, onBlur, id, onlyFuture, ...restProps }, ref) => {
const [isManualInput, setIsManualInput] = useState(false); // const handleChange = (newDate) => {
const { t } = useTranslation(); // if (value !== newDate && onChange) {
// 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 (
<div onKeyDown={handleKeyDown} id={id} style={{ width: "100%" }}> <Space direction="vertical" style={{ width: "100%" }} id={id}>
<DatePicker <FormDatePicker
showTime={ {...restProps}
isDateOnly {...(onlyFuture && {
? false disabledDate: (d) => dayjs().subtract(1, "day").isAfter(d)
: { })}
format: "hh:mm a", value={value}
minuteStep: 15, onBlur={onBlur}
defaultValue: dayjs(dayjs(), "HH:mm:ss") onChange={onChange}
} 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}
onChange={handleChange} {...(onlyFuture && {
placeholder={isDateOnly ? t("general.labels.date") : t("general.labels.datetime")} disabledDate: (d) => dayjs().isAfter(d)
onBlur={onBlur || handleBlur} })}
disabledDate={handleDisabledDate} onChange={onChange}
disableSeconds={true}
minuteStep={15}
onBlur={onBlur}
format="hh:mm a"
{...restProps} {...restProps}
/> />
</div> </Space>
); );
}; };
DateTimePicker.propTypes = { export default forwardRef(DateTimePicker);
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);

View File

@@ -1,63 +0,0 @@
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
};

View File

@@ -13,6 +13,7 @@ import Icon, {
FileFilled, FileFilled,
HomeFilled, HomeFilled,
ImportOutlined, ImportOutlined,
InfoCircleOutlined,
LineChartOutlined, LineChartOutlined,
PaperClipOutlined, PaperClipOutlined,
PhoneOutlined, PhoneOutlined,
@@ -26,8 +27,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 } from "antd"; import { Layout, Menu, Switch, Tooltip } from "antd";
import React from "react"; import React, { useEffect, useState } 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";
@@ -42,6 +43,7 @@ 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";
@@ -113,21 +115,19 @@ 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();
const deleteBetaCookie = () => { useEffect(() => {
const cookieExists = document.cookie.split("; ").some((row) => row.startsWith(`betaSwitchImex=`)); const isBeta = checkBeta();
if (cookieExists) { setBetaSwitch(isBeta);
const domain = window.location.hostname.split(".").slice(-2).join("."); }, []);
document.cookie = `betaSwitchImex=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/; domain=.${domain}`;
console.log(`betaSwitchImex cookie deleted`);
} else {
console.log(`betaSwitchImex cookie does not exist`);
}
};
deleteBetaCookie(); const betaSwitchChange = (checked) => {
setBeta(checked);
setBetaSwitch(checked);
handleBeta();
};
const accountingChildren = []; const accountingChildren = [];
@@ -695,6 +695,31 @@ 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

View File

@@ -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 })
} }
]} ]}
> >
<DateTimePicker isDateOnly /> <FormDatePicker />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label={t("scoreboard.fields.bodyhrs")} label={t("scoreboard.fields.bodyhrs")}

View File

@@ -141,14 +141,10 @@ 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({ // {
imex: [{ // key: t("jobs.fields.federal_tax_payable"),
key: t("jobs.fields.federal_tax_payable"), // total: job.job_totals.totals.custPayable.federal_tax,
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

View File

@@ -5,6 +5,7 @@ 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";
@@ -19,14 +20,7 @@ const mapStateToProps = createStructuredSelector({
}); });
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
insertAuditTrail: ({ jobid, operation, type }) => insertAuditTrail: ({ jobid, operation, type }) => dispatch(insertAuditTrail({ jobid, operation, type }))
dispatch(
insertAuditTrail({
jobid,
operation,
type
})
)
}); });
export default connect(mapStateToProps, mapDispatchToProps)(JobsAdminDatesChange); export default connect(mapStateToProps, mapDispatchToProps)(JobsAdminDatesChange);
@@ -93,7 +87,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">
<DateTimePicker format="MM/DD/YYYY" isDateOnly /> <FormDatePicker format="MM/DD/YYYY" />
</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 />

View File

@@ -1,9 +1,18 @@
import { Collapse, Form, Input, InputNumber, Select, Space, Switch } from "antd"; import {
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";
@@ -20,7 +29,6 @@ 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
@@ -53,7 +61,10 @@ 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 label={t("jobs.fields.regie_number")} name="regie_number"> <Form.Item
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">
@@ -105,7 +116,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">
<DateTimePicker isDateOnly /> <FormDatePicker />
</Form.Item> </Form.Item>
<Form.Item label={t("jobs.fields.kmin")} name="kmin"> <Form.Item label={t("jobs.fields.kmin")} name="kmin">
<Input /> <Input />

View File

@@ -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"]}>
<DateTimePicker isDateOnly disabled={!state.vehicle.new} /> <FormDatePicker disabled={!state.vehicle.new} />
</Form.Item> </Form.Item>
</LayoutFormRow> </LayoutFormRow>
<LayoutFormRow grow> <LayoutFormRow grow>

View File

@@ -5,6 +5,7 @@ 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";
@@ -29,7 +30,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">
<DateTimePicker disabled={jobRO} isDateOnly /> <FormDatePicker disabled={jobRO} />
</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} />
@@ -44,7 +45,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">
<DateTimePicker disabled={jobRO} isDateOnly /> <FormDatePicker disabled={jobRO} />
</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">
@@ -84,6 +85,7 @@ export function JobsDetailDatesComponent({ jobRO, job, bodyshop }) {
rules={[ rules={[
{ {
required: jobInPostProduction required: jobInPostProduction
//message: t("general.validation.required"),
} }
]} ]}
> >

View File

@@ -5,6 +5,7 @@ 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";
@@ -12,7 +13,6 @@ 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">
<DateTimePicker isDateOnly disabled={jobRO} /> <FormDatePicker 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} />

View File

@@ -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?.id]); }, [bodyshop]);
return <></>; return <></>;
} }

View File

@@ -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">
<DateTimePicker isDateOnly /> <FormDatePicker />
</Form.Item> </Form.Item>
<Button type="primary" onClick={() => form.submit()}> <Button type="primary" onClick={() => form.submit()}>
{t("general.actions.save")} {t("general.actions.save")}

View File

@@ -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 DateTimePicker from "../form-date-time-picker/form-date-time-picker.component.jsx"; import FormDatePicker from "../form-date-picker/form-date-picker.component";
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">
<DateTimePicker isDateOnly /> <FormDatePicker />
</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")}

View File

@@ -1,7 +1,8 @@
import { DeleteFilled } from "@ant-design/icons"; import { DeleteFilled, EyeFilled } 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";
@@ -82,34 +83,47 @@ export function PartsOrderListTableDrawerComponent({
sortedInfo: {} sortedInfo: {}
}); });
const [billData, setBillData] = useState(null); const [returnfrombill, setReturnFromBill] = useState();
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(() => {
const fetchData = async () => { if (returnfrombill === null) {
if (selectedPartsOrderRecord?.returnfrombill) { setBillData(null);
try { } else {
const { data } = await billQuery({ const fetchData = async () => {
variables: { billid: selectedPartsOrderRecord.returnfrombill } const result = await billQuery({
}); variables: { billid: returnfrombill }
setBillData(data); });
} catch (error) { setBillData(result.data);
console.error("Error fetching bill data:", error); };
} fetchData();
} else setBillData(null); }
}; }, [returnfrombill, billQuery]);
fetchData();
}, [selectedPartsOrderRecord, billQuery]);
const recordActions = (record) => ( const recordActions = (record, showView = false) => (
<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={() => {
@@ -119,14 +133,16 @@ 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) => {
joblineid: pol.job_line_id, return {
id: pol.id, joblineid: pol.job_line_id,
line_desc: pol.line_desc, id: pol.id,
quantity: pol.quantity, line_desc: pol.line_desc,
act_price: pol.act_price, quantity: pol.quantity,
oem_partno: pol.oem_partno act_price: pol.act_price,
})) oem_partno: pol.oem_partno
};
})
} }
}); });
}} }}
@@ -151,6 +167,7 @@ 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) {
@@ -174,6 +191,7 @@ 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: {
@@ -181,20 +199,24 @@ 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) => {
joblineid: pol.job_line_id || "noline", return {
line_desc: pol.line_desc, joblineid: pol.job_line_id || "noline",
quantity: pol.quantity, line_desc: pol.line_desc,
actual_price: pol.act_price, quantity: pol.quantity,
cost_center: pol.jobline?.part_type
? bodyshop.pbs_serialnumber || bodyshop.cdk_dealerid actual_price: pol.act_price,
? pol.jobline.part_type !== "PAE"
? pol.jobline.part_type cost_center: pol.jobline?.part_type
: null ? bodyshop.pbs_serialnumber || bodyshop.cdk_dealerid
: responsibilityCenters.defaults && ? pol.jobline.part_type !== "PAE"
(responsibilityCenters.defaults.costs[pol.jobline.part_type] || null) ? pol.jobline.part_type
: null : null
})) : responsibilityCenters.defaults &&
(responsibilityCenters.defaults.costs[pol.jobline.part_type] || null)
: null
};
})
} }
} }
}); });
@@ -221,6 +243,8 @@ 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 = [
{ {

View File

@@ -27,10 +27,6 @@ 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: (

View File

@@ -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")}
> >
<DateTimePicker isDateOnly onlyFuture /> <FormDatePicker onlyFuture />
</Form.Item> </Form.Item>
{job && job.special_coverage_policy && ( {job && job.special_coverage_policy && (
<Tag color="tomato"> <Tag color="tomato">

View File

@@ -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 }) {
} }
]} ]}
> >
<DateTimePicker isDateOnly disabled={disabled} /> <DatePickerFormItem disabled={disabled} />
</Form.Item> </Form.Item>
</LayoutFormRow> </LayoutFormRow>

View File

@@ -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 JobPartsQueueCount from "../job-parts-queue-count/job-parts-queue-count.component";
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component"; import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
import JobPartsQueueCount from "../job-parts-queue-count/job-parts-queue-count.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,13 +213,21 @@ const EstimatorToolTip = ({ metadata, cardSettings }) => {
}; };
const SubtotalTooltip = ({ metadata, cardSettings, t }) => { const SubtotalTooltip = ({ metadata, cardSettings, t }) => {
const dineroAmount = Dinero(metadata?.job_totals?.totals?.subtotal ?? Dinero()).toFormat(); const amount = metadata?.job_totals?.totals?.subtotal?.amount;
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 title={`${dineroAmount}`} kiosk={cardSettings.kiosk}> <EllipsesToolTip
{dineroAmount} title={!!amount ? `${t("production.statistics.currency_symbol")}${dineroAmount}` : null}
kiosk={cardSettings.kiosk}
>
{!!amount ? (
<span>{`${t("production.statistics.currency_symbol")}${dineroAmount}`}</span>
) : (
<span>&nbsp;</span>
)}
</EllipsesToolTip> </EllipsesToolTip>
</Col> </Col>
) )

View File

@@ -1,12 +1,8 @@
import React, { useEffect, useMemo, useRef } from "react"; import React, { useEffect, useMemo } 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 { import { QUERY_JOBS_IN_PRODUCTION, SUBSCRIPTION_JOBS_IN_PRODUCTION } from "../../graphql/jobs.queries";
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";
@@ -16,9 +12,7 @@ const mapStateToProps = createStructuredSelector({
currentUser: selectCurrentUser currentUser: selectCurrentUser
}); });
function ProductionBoardKanbanContainer({ bodyshop, currentUser, subscriptionType = "direct" }) { function ProductionBoardKanbanContainer({ bodyshop, currentUser }) {
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,
@@ -34,12 +28,9 @@ function ProductionBoardKanbanContainer({ bodyshop, currentUser, subscriptionTyp
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( const { data: updatedJobs } = useSubscription(SUBSCRIPTION_JOBS_IN_PRODUCTION, {
subscriptionType === "view" ? SUBSCRIPTION_JOBS_IN_PRODUCTION_VIEW : SUBSCRIPTION_JOBS_IN_PRODUCTION, onError: (error) => console.error(`Error subscribing to jobs in production: ${error.message}`)
{ });
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 },
@@ -49,15 +40,10 @@ function ProductionBoardKanbanContainer({ bodyshop, currentUser, subscriptionTyp
// const currentReducerData = useSelector((state) => (state.trello.lanes ? state.trello : {})); // const currentReducerData = useSelector((state) => (state.trello.lanes ? state.trello : {}));
useEffect(() => { useEffect(() => {
if (!updatedJobs) { if (updatedJobs && data) {
return; refetch().catch((err) => console.error(`Error re-fetching jobs in production: ${err.message}`));
} }
if (!fired.current) { }, [updatedJobs, data, refetch]);
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;

View File

@@ -3,7 +3,6 @@ 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",
@@ -33,21 +32,7 @@ const ProductionStatistics = ({ data, cardSettings, reducerData }) => {
}; };
const calculateTotalAmount = (items, key) => { const calculateTotalAmount = (items, key) => {
return items.reduce((acc, item) => acc.add(Dinero(item[key]?.totals?.subtotal ?? Dinero())), Dinero({ amount: 0 })); return items.reduce((acc, item) => acc + (item[key]?.totals?.subtotal?.amount || 0), 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) => {
@@ -58,6 +43,14 @@ 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);
@@ -94,15 +87,9 @@ 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 total.toFormat("$0,0.00"); return parseFloat(total.toFixed(2));
}, [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 =
@@ -131,6 +118,12 @@ 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);
@@ -198,6 +191,7 @@ 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")

View File

@@ -28,11 +28,6 @@ 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 [
@@ -431,8 +426,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(
getEmployeeName(a.employee_body, bodyshop.employees), bodyshop.employees?.find((e) => e.id === a.employee_body)?.first_name,
getEmployeeName(b.employee_body, bodyshop.employees) bodyshop.employees?.find((e) => e.id === b.employee_body)?.first_name
), ),
render: (text, record) => ( render: (text, record) => (
<ProductionListEmployeeAssignment refetch={refetch} record={record} type="employee_body" /> <ProductionListEmployeeAssignment refetch={refetch} record={record} type="employee_body" />
@@ -445,8 +440,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(
getEmployeeName(a.employee_prep, bodyshop.employees), bodyshop.employees?.find((e) => e.id === a.employee_prep)?.first_name,
getEmployeeName(b.employee_prep, bodyshop.employees) bodyshop.employees?.find((e) => e.id === b.employee_prep)?.first_name
), ),
render: (text, record) => ( render: (text, record) => (
<ProductionListEmployeeAssignment record={record} refetch={refetch} type="employee_prep" /> <ProductionListEmployeeAssignment record={record} refetch={refetch} type="employee_prep" />
@@ -465,8 +460,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(
getEmployeeName(a.employee_csr, bodyshop.employees), bodyshop.employees?.find((e) => e.id === a.employee_csr)?.first_name,
getEmployeeName(b.employee_csr, bodyshop.employees) bodyshop.employees?.find((e) => e.id === b.employee_csr)?.first_name
), ),
render: (text, record) => ( render: (text, record) => (
<ProductionListEmployeeAssignment refetch={refetch} record={record} type="employee_csr" /> <ProductionListEmployeeAssignment refetch={refetch} record={record} type="employee_csr" />
@@ -479,8 +474,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(
getEmployeeName(a.employee_refinish, bodyshop.employees), bodyshop.employees?.find((e) => e.id === a.employee_refinish)?.first_name,
getEmployeeName(b.employee_refinish, bodyshop.employees) bodyshop.employees?.find((e) => e.id === b.employee_refinish)?.first_name
), ),
render: (text, record) => ( render: (text, record) => (
<ProductionListEmployeeAssignment record={record} refetch={refetch} type="employee_refinish" /> <ProductionListEmployeeAssignment record={record} refetch={refetch} type="employee_refinish" />

View File

@@ -1,12 +1,12 @@
import { useMutation } from "@apollo/client"; import { useMutation } from "@apollo/client";
import { Button, Card, Dropdown, Space } from "antd"; import { Button, Card, Dropdown, Space, TimePicker } 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 DateTimePicker from "../form-date-time-picker/form-date-time-picker.component.jsx"; import FormDatePicker from "../form-date-picker/form-date-picker.component";
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,14 +57,22 @@ 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"}>
<DateTimePicker <FormDatePicker
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={time ? "MM/DD/YYYY hh:mm a" : "MM/DD/YYYY"} format="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>

View File

@@ -0,0 +1,93 @@
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);

View File

@@ -1,505 +0,0 @@
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>
);
}

View File

@@ -0,0 +1,172 @@
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);

View File

@@ -1,23 +1,24 @@
import { SyncOutlined } from "@ant-design/icons";
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 React, { useEffect, useMemo, useRef, useState } from "react";
import { Button, Dropdown, Input, Space, Statistic, Table } from "antd";
import { PageHeader } from "@ant-design/pro-layout";
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 { ProductionListConfigManager } from "./production-list-config-manager.component.jsx"; import ProductionListSaveConfigButton from "../production-list-save-config-button/production-list-save-config-button.component";
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,
@@ -43,7 +44,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 && bodyshop.production_config[0]?.columns.tableState) || { bodyshop.production_config[0]?.columns.tableState || {
sortedInfo: {}, sortedInfo: {},
filteredInfo: { text: "" } filteredInfo: { text: "" }
} }
@@ -269,24 +270,24 @@ export function ProductionListTable({ loading, data, refetch, bodyshop, technici
data={data} data={data}
onColumnAdd={addColumn} onColumnAdd={addColumn}
/> />
<ProductionListSaveConfigButton
<ProductionListConfigManager
columns={columns} columns={columns}
setColumns={setColumns} tableState={state}
state={state}
setState={setState}
refetch={refetch}
data={data}
bodyshop={bodyshop}
technician={technician}
currentUser={currentUser}
setHasUnsavedChanges={setHasUnsavedChanges}
hasUnsavedChanges={hasUnsavedChanges}
onSave={() => { onSave={() => {
setHasUnsavedChanges(false); setHasUnsavedChanges(false);
initialStateRef.current = state;
}} }}
/> />
<ProductionListTableViewSelect
state={state}
setState={setState}
setColumns={setColumns}
onProfileChange={() => {
initialStateRef.current = state;
setHasUnsavedChanges(false);
}}
refetch={refetch}
data={data}
/>
<Input <Input
onChange={(e) => setSearchText(e.target.value)} onChange={(e) => setSearchText(e.target.value)}
placeholder={t("general.labels.search")} placeholder={t("general.labels.search")}

View File

@@ -4,13 +4,12 @@ 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({ subscriptionType = "direct" }) { export default function ProductionListTableContainer() {
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",
@@ -18,9 +17,7 @@ export default function ProductionListTableContainer({ subscriptionType = "direc
}); });
const client = useApolloClient(); const client = useApolloClient();
const [joblist, setJoblist] = useState([]); const [joblist, setJoblist] = useState([]);
const { data: updatedJobs } = useSubscription( const { data: updatedJobs } = useSubscription(SUBSCRIPTION_JOBS_IN_PRODUCTION);
subscriptionType === "view" ? SUBSCRIPTION_JOBS_IN_PRODUCTION_VIEW : SUBSCRIPTION_JOBS_IN_PRODUCTION
);
useEffect(() => { useEffect(() => {
if (!(data && data.jobs)) return; if (!(data && data.jobs)) return;

View File

@@ -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 DateTimePicker from "../form-date-time-picker/form-date-time-picker.component.jsx"; import { FormDatePicker } from "../form-date-picker/form-date-picker.component.jsx";
export default function ReportCenterModalFiltersSortersComponent({ form, bodyshop }) { export default function ReportCenterModalFiltersSortersComponent({ form, bodyshop }) {
return ( return (
@@ -196,8 +196,7 @@ 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 (
<DateTimePicker <FormDatePicker
isDateOnly
disabled={!operator} disabled={!operator}
onChange={(date) => form.setFieldValue(fieldPath, date)} onChange={(date) => form.setFieldValue(fieldPath, date)}
/> />

View File

@@ -34,34 +34,28 @@ 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, ADPPayroll } treatments: { Enhanced_Payroll }
} = useSplitTreatments({ } = useSplitTreatments({
attributes: {}, attributes: {},
names: ["Enhanced_Payroll", "ADPPayroll"], names: ["Enhanced_Payroll"],
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 = Object.keys(Templates) const ReportsList =
.map((key) => Templates[key]) Enhanced_Payroll.treatment === "on"
.filter((temp) => { ? Object.keys(Templates)
const enhancedPayrollOn = Enhanced_Payroll.treatment === "on"; .map((key) => {
const adpPayrollOn = ADPPayroll.treatment === "on"; return Templates[key];
})
if (enhancedPayrollOn && adpPayrollOn) { .filter((temp) => temp.enhanced_payroll === undefined || temp.enhanced_payroll === true)
return temp.enhanced_payroll !== false || temp.adp_payroll !== false; : Object.keys(Templates)
} .map((key) => {
if (enhancedPayrollOn) { return Templates[key];
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;
@@ -110,7 +104,7 @@ export function ReportCenterModalComponent({ reportCenterModal, bodyshop }) {
to: values.to, to: values.to,
subject: Templates[values.key]?.subject subject: Templates[values.key]?.subject
}, },
values.sendbytext === "text" ? values.sendbytext : values.sendbyexcel === "excel" ? "x" : values.sendby === "email" ? "e" : "p", values.sendbyexcel === "excel" ? "x" : values.sendby === "email" ? "e" : "p",
id id
); );
setLoading(false); setLoading(false);
@@ -297,15 +291,7 @@ export function ReportCenterModalComponent({ reportCenterModal, bodyshop }) {
</Radio.Group> </Radio.Group>
</Form.Item> </Form.Item>
); );
if (reporttype === "text") if (reporttype !== "excel")
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>

View File

@@ -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 DateTimePicker from "../form-date-time-picker/form-date-time-picker.component.jsx"; import FormDatePicker from "../form-date-picker/form-date-picker.component";
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 }) {
} }
]} ]}
> >
<DateTimePicker isDateOnly /> <FormDatePicker />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label={t("scoreboard.fields.bodyhrs")} label={t("scoreboard.fields.bodyhrs")}

View File

@@ -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 DateTimePicker from "../form-date-time-picker/form-date-time-picker.component.jsx"; import FormDatePicker from "../form-date-picker/form-date-picker.component";
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 }) {
} }
]} ]}
> >
<DateTimePicker isDateOnly /> <FormDatePicker />
</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 }) {
}) })
]} ]}
> >
<DateTimePicker isDateOnly /> <FormDatePicker />
</Form.Item> </Form.Item>
<Space wrap> <Space wrap>

View File

@@ -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 }) {
} }
]} ]}
> >
<DateTimePicker isDateOnly /> <FormDatePicker />
</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">
<DateTimePicker isDateOnly /> <FormDatePicker />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label={t("employees.fields.user_email")} label={t("employees.fields.user_email")}

View File

@@ -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, ADPPayroll } treatments: { ClosingPeriod }
} = useSplitTreatments({ } = useSplitTreatments({
attributes: {}, attributes: {},
names: ["ClosingPeriod", "ADPPayroll"], names: ["ClosingPeriod"],
splitKey: bodyshop && bodyshop.imexshopid splitKey: bodyshop && bodyshop.imexshopid
}); });
@@ -98,6 +98,7 @@ 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"
@@ -355,22 +356,14 @@ export function ShopInfoGeneral({ form, bodyshop }) {
<Select mode="tags" /> <Select mode="tags" />
</Form.Item> </Form.Item>
{ClosingPeriod.treatment === "on" && ( {ClosingPeriod.treatment === "on" && (
<Form.Item <>
name={["accountingconfig", "ClosingPeriod"]} <Form.Item
label={t("bodyshop.fields.closingperiod")} //{t("reportcenter.labels.dates")} name={["accountingconfig", "ClosingPeriod"]}
> label={t("bodyshop.fields.closingperiod")} //{t("reportcenter.labels.dates")}
<DatePicker.RangePicker format="MM/DD/YYYY" presets={DatePickerRanges} /> >
</Form.Item> <DatePicker.RangePicker format="MM/DD/YYYY" presets={DatePickerRanges} />
)} </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>

View File

@@ -30,226 +30,219 @@ 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
<Form.Item label={t("bodyshop.fields.rbac.accounting.exportlog")}
label={t("bodyshop.fields.rbac.accounting.exportlog")} rules={[
rules={[ {
{ required: true
required: true //message: t("general.validation.required"),
//message: t("general.validation.required"), }
} ]}
]} name={["md_rbac", "accounting:exportlog"]}
name={["md_rbac", "accounting:exportlog"]} >
> <InputNumber />
<InputNumber /> </Form.Item>,
</Form.Item>, <Form.Item
<Form.Item label={t("bodyshop.fields.rbac.accounting.payables")}
label={t("bodyshop.fields.rbac.accounting.payables")} rules={[
rules={[ {
{ required: true
required: true //message: t("general.validation.required"),
//message: t("general.validation.required"), }
} ]}
]} name={["md_rbac", "accounting:payables"]}
name={["md_rbac", "accounting:payables"]} >
> <InputNumber />
<InputNumber /> </Form.Item>,
</Form.Item>, <Form.Item
<Form.Item label={t("bodyshop.fields.rbac.accounting.payments")}
label={t("bodyshop.fields.rbac.accounting.payments")} rules={[
rules={[ {
{ required: true
required: true //message: t("general.validation.required"),
//message: t("general.validation.required"), }
} ]}
]} name={["md_rbac", "accounting:payments"]}
name={["md_rbac", "accounting:payments"]} >
> <InputNumber />
<InputNumber /> </Form.Item>,
</Form.Item>, <Form.Item
<Form.Item label={t("bodyshop.fields.rbac.accounting.receivables")}
label={t("bodyshop.fields.rbac.accounting.receivables")} rules={[
rules={[ {
{ required: true
required: true //message: t("general.validation.required"),
//message: t("general.validation.required"), }
} ]}
]} name={["md_rbac", "accounting:receivables"]}
name={["md_rbac", "accounting:receivables"]} >
> <InputNumber />
<InputNumber /> </Form.Item>
</Form.Item> ]:[]}
] {...HasFeatureAccess({ featureName: "bills", bodyshop }) ? [
: []} <Form.Item
{...HasFeatureAccess({ featureName: "bills", bodyshop }) label={t("bodyshop.fields.rbac.bills.delete")}
? [ rules={[
<Form.Item {
label={t("bodyshop.fields.rbac.bills.delete")} required: true
rules={[ //message: t("general.validation.required"),
{ }
required: true ]}
//message: t("general.validation.required"), name={["md_rbac", "bills:delete"]}
} >
]} <InputNumber />
name={["md_rbac", "bills:delete"]} </Form.Item>,
> <Form.Item
<InputNumber /> label={t("bodyshop.fields.rbac.bills.enter")}
</Form.Item>, rules={[
<Form.Item {
label={t("bodyshop.fields.rbac.bills.enter")} required: true
rules={[ //message: t("general.validation.required"),
{ }
required: true ]}
//message: t("general.validation.required"), name={["md_rbac", "bills:enter"]}
} >
]} <InputNumber />
name={["md_rbac", "bills:enter"]} </Form.Item>,
> <Form.Item
<InputNumber /> label={t("bodyshop.fields.rbac.bills.list")}
</Form.Item>, rules={[
<Form.Item {
label={t("bodyshop.fields.rbac.bills.list")} required: true
rules={[ //message: t("general.validation.required"),
{ }
required: true ]}
//message: t("general.validation.required"), name={["md_rbac", "bills:list"]}
} >
]} <InputNumber />
name={["md_rbac", "bills:list"]} </Form.Item>,
> <Form.Item
<InputNumber /> label={t("bodyshop.fields.rbac.bills.reexport")}
</Form.Item>, rules={[
<Form.Item {
label={t("bodyshop.fields.rbac.bills.reexport")} required: true
rules={[ //message: t("general.validation.required"),
{ }
required: true ]}
//message: t("general.validation.required"), name={["md_rbac", "bills:reexport"]}
} >
]} <InputNumber />
name={["md_rbac", "bills:reexport"]} </Form.Item>,
> <Form.Item
<InputNumber /> label={t("bodyshop.fields.rbac.bills.view")}
</Form.Item>, rules={[
<Form.Item {
label={t("bodyshop.fields.rbac.bills.view")} required: true
rules={[ //message: t("general.validation.required"),
{ }
required: true ]}
//message: t("general.validation.required"), name={["md_rbac", "bills:view"]}
} >
]} <InputNumber />
name={["md_rbac", "bills:view"]} </Form.Item>
> ]:[]}
<InputNumber />
</Form.Item> {...HasFeatureAccess({ featureName: "courtesycars", bodyshop }) ? [
] <Form.Item
: []} label={t("bodyshop.fields.rbac.contracts.create")}
{...HasFeatureAccess({ featureName: "courtesycars", bodyshop }) rules={[
? [ {
<Form.Item required: true
label={t("bodyshop.fields.rbac.contracts.create")} //message: t("general.validation.required"),
rules={[ }
{ ]}
required: true name={["md_rbac", "contracts:create"]}
//message: t("general.validation.required"), >
} <InputNumber />
]} </Form.Item>,
name={["md_rbac", "contracts:create"]} <Form.Item
> label={t("bodyshop.fields.rbac.contracts.detail")}
<InputNumber /> rules={[
</Form.Item>, {
<Form.Item required: true
label={t("bodyshop.fields.rbac.contracts.detail")} //message: t("general.validation.required"),
rules={[ }
{ ]}
required: true name={["md_rbac", "contracts:detail"]}
//message: t("general.validation.required"), >
} <InputNumber />
]} </Form.Item>,
name={["md_rbac", "contracts:detail"]} <Form.Item
> label={t("bodyshop.fields.rbac.contracts.list")}
<InputNumber /> rules={[
</Form.Item>, {
<Form.Item required: true
label={t("bodyshop.fields.rbac.contracts.list")} //message: t("general.validation.required"),
rules={[ }
{ ]}
required: true name={["md_rbac", "contracts:list"]}
//message: t("general.validation.required"), >
} <InputNumber />
]} </Form.Item>,
name={["md_rbac", "contracts:list"]} <Form.Item
> label={t("bodyshop.fields.rbac.courtesycar.create")}
<InputNumber /> rules={[
</Form.Item>, {
<Form.Item required: true
label={t("bodyshop.fields.rbac.courtesycar.create")} //message: t("general.validation.required"),
rules={[ }
{ ]}
required: true name={["md_rbac", "courtesycar:create"]}
//message: t("general.validation.required"), >
} <InputNumber />
]} </Form.Item>,
name={["md_rbac", "courtesycar:create"]} <Form.Item
> label={t("bodyshop.fields.rbac.courtesycar.detail")}
<InputNumber /> rules={[
</Form.Item>, {
<Form.Item required: true
label={t("bodyshop.fields.rbac.courtesycar.detail")} //message: t("general.validation.required"),
rules={[ }
{ ]}
required: true name={["md_rbac", "courtesycar:detail"]}
//message: t("general.validation.required"), >
} <InputNumber />
]} </Form.Item>,
name={["md_rbac", "courtesycar:detail"]} <Form.Item
> label={t("bodyshop.fields.rbac.courtesycar.list")}
<InputNumber /> rules={[
</Form.Item>, {
<Form.Item required: true
label={t("bodyshop.fields.rbac.courtesycar.list")} //message: t("general.validation.required"),
rules={[ }
{ ]}
required: true name={["md_rbac", "courtesycar:list"]}
//message: t("general.validation.required"), >
} <InputNumber />
]} </Form.Item>
name={["md_rbac", "courtesycar:list"]} ]:[]}
> {...HasFeatureAccess({ featureName: "csi", bodyshop }) ? [
<InputNumber /> <Form.Item
</Form.Item> label={t("bodyshop.fields.rbac.csi.export")}
] rules={[
: []} {
{...HasFeatureAccess({ featureName: "csi", bodyshop }) required: true
? [ //message: t("general.validation.required"),
<Form.Item }
label={t("bodyshop.fields.rbac.csi.export")} ]}
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: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:page"]}
{ >
required: true <InputNumber />
//message: t("general.validation.required"), </Form.Item>
} ]:[]}
]}
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={[
@@ -262,18 +255,6 @@ 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={[
@@ -454,6 +435,31 @@ 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={[
@@ -516,6 +522,7 @@ 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={[
@@ -554,118 +561,128 @@ export function ShopInfoRbacComponent({ form, bodyshop }) {
<InputNumber /> <InputNumber />
</Form.Item> </Form.Item>
)} )}
{...HasFeatureAccess({ featureName: "timetickets", bodyshop }) {...HasFeatureAccess({ featureName: "timetickets", bodyshop }) ? [
? [ <Form.Item
<Form.Item label={t("bodyshop.fields.rbac.shiftclock.view")}
label={t("bodyshop.fields.rbac.shiftclock.view")} rules={[
rules={[ {
{ required: true
required: true //message: t("general.validation.required"),
//message: t("general.validation.required"), }
} ]}
]} name={["md_rbac", "shiftclock:view"]}
name={["md_rbac", "shiftclock:view"]} >
> <InputNumber />
<InputNumber /> </Form.Item>,
</Form.Item>, <Form.Item
<Form.Item label={t("bodyshop.fields.rbac.shop.config")}
label={t("bodyshop.fields.rbac.shop.config")} rules={[
rules={[ {
{ required: true
required: true //message: t("general.validation.required"),
//message: t("general.validation.required"), }
} ]}
]} name={["md_rbac", "shop:config"]}
name={["md_rbac", "shop:config"]} >
> <InputNumber />
<InputNumber /> </Form.Item>,
</Form.Item>, <Form.Item
<Form.Item label={t("bodyshop.fields.rbac.timetickets.edit")}
label={t("bodyshop.fields.rbac.timetickets.edit")} rules={[
rules={[ {
{ required: true
required: true //message: t("general.validation.required"),
//message: t("general.validation.required"), }
} ]}
]} name={["md_rbac", "timetickets:edit"]}
name={["md_rbac", "timetickets:edit"]} >
> <InputNumber />
<InputNumber /> </Form.Item>,
</Form.Item>, <Form.Item
<Form.Item label={t("bodyshop.fields.rbac.timetickets.shiftedit")}
label={t("bodyshop.fields.rbac.timetickets.shiftedit")} rules={[
rules={[ {
{ required: true
required: true //message: t("general.validation.required"),
//message: t("general.validation.required"), }
} ]}
]} name={["md_rbac", "timetickets:shiftedit"]}
name={["md_rbac", "timetickets:shiftedit"]} >
> <InputNumber />
<InputNumber /> </Form.Item>,
</Form.Item>, <Form.Item
<Form.Item label={t("bodyshop.fields.rbac.timetickets.editcommitted")}
label={t("bodyshop.fields.rbac.timetickets.editcommitted")} rules={[
rules={[ {
{ required: true
required: true //message: t("general.validation.required"),
//message: t("general.validation.required"), }
} ]}
]} name={["md_rbac", "timetickets:editcommitted"]}
name={["md_rbac", "timetickets:editcommitted"]} >
> <InputNumber />
<InputNumber /> </Form.Item>,
</Form.Item>, <Form.Item
<Form.Item label={t("bodyshop.fields.rbac.ttapprovals.view")}
label={t("bodyshop.fields.rbac.ttapprovals.view")} rules={[
rules={[ {
{ required: true
required: true //message: t("general.validation.required"),
//message: t("general.validation.required"), }
} ]}
]} name={["md_rbac", "ttapprovals:view"]}
name={["md_rbac", "ttapprovals:view"]} >
> <InputNumber />
<InputNumber /> </Form.Item>,
</Form.Item>, <Form.Item
<Form.Item label={t("bodyshop.fields.rbac.ttapprovals.approve")}
label={t("bodyshop.fields.rbac.ttapprovals.approve")} rules={[
rules={[ {
{ required: true
required: true //message: t("general.validation.required"),
//message: t("general.validation.required"), }
} ]}
]} name={["md_rbac", "ttapprovals:approve"]}
name={["md_rbac", "ttapprovals:approve"]} >
> <InputNumber />
<InputNumber /> </Form.Item>,
</Form.Item>, <Form.Item
<Form.Item label={t("bodyshop.fields.rbac.timetickets.enter")}
label={t("bodyshop.fields.rbac.timetickets.enter")} rules={[
rules={[ {
{ required: true
required: true //message: t("general.validation.required"),
//message: t("general.validation.required"), }
} ]}
]} name={["md_rbac", "timetickets:enter"]}
name={["md_rbac", "timetickets:enter"]} >
> <InputNumber />
<InputNumber /> </Form.Item>,
</Form.Item>, <Form.Item
<Form.Item label={t("bodyshop.fields.rbac.timetickets.list")}
label={t("bodyshop.fields.rbac.timetickets.list")} rules={[
rules={[ {
{ required: true
required: true //message: t("general.validation.required"),
//message: t("general.validation.required"), }
} ]}
]} name={["md_rbac", "timetickets:list"]}
name={["md_rbac", "timetickets:list"]} >
> <InputNumber />
<InputNumber /> </Form.Item>,
</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={[
@@ -740,6 +757,7 @@ 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={[

View File

@@ -1,13 +1,14 @@
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 DateTimePicker from "../form-date-time-picker/form-date-time-picker.component.jsx"; import { FormDateTimePickerEnhanced } from "../form-date-time-picker-enhanced/form-date-time-picker-enhanced.component.jsx";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
@@ -245,8 +246,7 @@ 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">
<DateTimePicker <FormDatePicker
isDateOnly
onlyFuture onlyFuture
format="MM/DD/YYYY" format="MM/DD/YYYY"
presets={generatePresets(selectedJobDetails)} presets={generatePresets(selectedJobDetails)}
@@ -278,7 +278,12 @@ export function TaskUpsertModalComponent({
} }
]} ]}
> >
<DateTimePicker onlyFuture presets={generatePresets(selectedJobDetails)} /> <FormDateTimePickerEnhanced
onlyFuture
showTime
minuteStep={15}
presets={generatePresets(selectedJobDetails)}
/>
</Form.Item> </Form.Item>
</Col> </Col>
</Row> </Row>

View File

@@ -1,4 +1,5 @@
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";
@@ -6,12 +7,10 @@ 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) => ({
@@ -19,7 +18,7 @@ const mapDispatchToProps = (dispatch) => ({
}); });
export default connect(mapStateToProps, mapDispatchToProps)(TechJobPrintTickets); export default connect(mapStateToProps, mapDispatchToProps)(TechJobPrintTickets);
export function TechJobPrintTickets({ bodyshop, technician, event, attendacePrint }) { export function TechJobPrintTickets({ technician, event, attendacePrint }) {
const { t } = useTranslation(); const { t } = useTranslation();
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
@@ -58,8 +57,7 @@ export function TechJobPrintTickets({ bodyshop, technician, event, attendacePrin
subject: subject:
attendacePrint === true ? Templates.attendance_employee.subject : Templates.timetickets_employee.subject attendacePrint === true ? Templates.attendance_employee.subject : Templates.timetickets_employee.subject
}, },
values.sendby, values.sendby // === "email" ? "e" : "p"
bodyshop
); );
} catch (error) { } catch (error) {
console.log(error); console.log(error);

View File

@@ -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 }) {
} }
]} ]}
> >
<DateTimePicker isDateOnly /> <FormDatePicker />
</Form.Item> </Form.Item>
</LayoutFormRow> </LayoutFormRow>

View File

@@ -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 { HasRbacAccess } from "../rbac-wrapper/rbac-wrapper.component"; import RbacWrapper, { 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,10 +52,6 @@ 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(
@@ -69,18 +65,6 @@ 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"
? [ ? [
@@ -181,7 +165,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.memo?.startsWith("timetickets.labels") ? t(record.memo) : record.memo) render: (text, record) => (record.clockon || record.clockoff ? t(record.memo) : record.memo)
}, },
...(Enhanced_Payroll.treatment === "on" ...(Enhanced_Payroll.treatment === "on"
? [ ? [
@@ -222,55 +206,76 @@ 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 && (
<TimeTicketEnterButton <RbacWrapper
actions={{ refetch }} action="timetickets:edit"
context={{ noauth={() => {
id: record.id, return <div />;
timeticket: record }}
}} >
disabled={isDisabled(record)} <TimeTicketEnterButton
> actions={{ refetch }}
<EditFilled /> context={{
</TimeTicketEnterButton> id: record.id,
)} 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) => {

View File

@@ -1,5 +1,4 @@
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";
@@ -8,10 +7,8 @@ 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 { import FormDatePicker from "../form-date-picker/form-date-picker.component";
default as DateTimePicker, import FormDateTimePicker from "../form-date-time-picker/form-date-time-picker.component";
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";
@@ -19,6 +16,7 @@ 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,
@@ -62,8 +60,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>
@@ -71,7 +69,13 @@ export function TimeTicketModalComponent({
}; };
const MemoInput = ({ value, ...props }) => { const MemoInput = ({ value, ...props }) => {
return <Input value={value?.startsWith("timetickets.labels") ? t(value) : value} {...props} />; return (
<Input
value={value?.startsWith("timetickets.") ? t(value) : value}
{...props}
disabled={value?.startsWith("timetickets.") || disabled}
/>
);
}; };
return ( return (
@@ -107,7 +111,7 @@ export function TimeTicketModalComponent({
} }
]} ]}
> >
<DateTimePicker isDateOnly /> <FormDatePicker />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
name="employeeid" name="employeeid"
@@ -329,9 +333,7 @@ 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 && ( {!hideTimeTickets && <TimeTicketList loading={loading} timetickets={lineTicketData.timetickets} techConsole />}
<TimeTicketList loading={loading} timetickets={jobid ? lineTicketData.timetickets : []} techConsole />
)}
</div> </div>
); );
} }

View File

@@ -39,7 +39,7 @@ export default function TimeTicketShiftActive({ timetickets, refetch, isTechCons
renderItem={(ticket) => ( renderItem={(ticket) => (
<List.Item> <List.Item>
<Card <Card
title={ticket.memo?.startsWith("timetickets.labels") ? t(ticket.memo) : ticket.memo} title={t(ticket.memo)}
actions={[ actions={[
<TechClockOffButton <TechClockOffButton
jobId={ticket.jobid} jobId={ticket.jobid}

View File

@@ -1,14 +1,13 @@
import { AlertOutlined } from "@ant-design/icons"; import { AlertOutlined } from "@ant-design/icons";
import { Alert, Button, Col, notification, Row, Space } from "antd"; import { Alert, Button, Col, Row, Space } from "antd";
import i18n from "i18next"; import i18n from "i18next";
import React, { useCallback, useEffect, useState } from "react"; import React, { useEffect } 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
@@ -20,15 +19,6 @@ 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],
@@ -41,7 +31,7 @@ export function UpdateAlert({ updateAvailable }) {
() => { () => {
r.update(); r.update();
}, },
30 * 60 * 1000 10 * 60 * 1000
); );
} }
}, },
@@ -50,43 +40,11 @@ export function UpdateAlert({ updateAvailable }) {
} }
}); });
const ReloadNewVersion = useCallback(() => {
setLoading(true);
updateServiceWorker(true);
setTimeout(() => {
window.location.reload(true);
}, 5000);
}, [updateServiceWorker]);
useEffect(() => { useEffect(() => {
if (needRefresh) { if (import.meta.env.DEV) {
start(); console.log(`SW Status => Refresh? ${needRefresh} - offlineReady? ${offlineReady}`);
setTimerStarted(true);
} }
}, [start, needRefresh, offlineReady]); }, [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;
@@ -117,10 +75,9 @@ 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 loading={loading} type="primary" onClick={() => ReloadNewVersion()}> <Button type="primary" onClick={() => updateServiceWorker(true)}>
{i18n.t("general.actions.refresh")} {`(${(timeLeft / 1000).toFixed(0)} s)`} {i18n.t("general.actions.refresh")}
</Button> </Button>
<Button onClick={() => start(300000)}>{i18n.t("general.actions.delay")}</Button>
</Space> </Space>
</Col> </Col>
</Row> </Row>

View File

@@ -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">
<DateTimePicker isDateOnly /> <FormDatePicker />
</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"]}>

View File

@@ -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,25 +33,9 @@ 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 <div className="imex-flex-row" style={{ width: "100%" }}>
style={{ <div style={{ flex: 1 }}>{label}</div>
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>
); );
@@ -61,67 +45,36 @@ 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 <div className="imex-flex-row">
style={{ <div style={{ flex: 1 }}>{o.name}</div>
display: "flex", <Space style={{ marginLeft: "1rem" }}>
alignItems: "center", <HeartOutlined style={{ color: "red" }} />
flexWrap: "nowrap", {o.phone && showPhone && <PhoneNumberFormatter>{o.phone}</PhoneNumberFormatter>}
width: "100%" {o.discount && o.discount !== 0 ? <Tag color="green">{`${o.discount * 100}%`}</Tag> : null}
}} </Space>
>
<div
style={{
flex: 1,
minWidth: 0,
overflow: "hidden",
textOverflow: "ellipsis",
whiteSpace: "nowrap"
}}
>
{o.name}
</div> </div>
<Space style={{ marginLeft: "1rem" }}> </Option>
<HeartOutlined style={{ color: "red" }} /> ))
{o.phone && showPhone && <PhoneNumberFormatter>{o.phone}</PhoneNumberFormatter>} : null}
{o.discount && o.discount !== 0 ? <Tag color="green">{`${o.discount * 100}%`}</Tag> : null} {options
</Space> ? options.map((o) => (
</div> <Option key={o.id} value={o.id} name={o.name} discount={o.discount}>
</Option> <div className="imex-flex-row" style={{ width: "100%" }}>
))} <div style={{ flex: 1 }}>{o.name}</div>
{options &&
options.map((o) => ( <Space style={{ marginLeft: "1rem" }}>
<Option key={o.id} value={o.id} name={o.name} discount={o.discount}> {o.phone && showPhone && <PhoneNumberFormatter>{o.phone}</PhoneNumberFormatter>}
<div {o.discount && o.discount !== 0 ? <Tag color="green">{`${o.discount * 100}%`}</Tag> : null}
style={{ </Space>
display: "flex",
alignItems: "center",
flexWrap: "nowrap",
width: "100%"
}}
>
<div
style={{
flex: 1,
minWidth: 0,
overflow: "hidden",
textOverflow: "ellipsis",
whiteSpace: "nowrap"
}}
>
{o.name}
</div> </div>
<Space style={{ marginLeft: "1rem" }}> </Option>
{o.phone && showPhone && <PhoneNumberFormatter>{o.phone}</PhoneNumberFormatter>} ))
{o.discount && o.discount !== 0 ? <Tag color="green">{`${o.discount * 100}%`}</Tag> : null} : null}
</Space>
</div>
</Option>
))}
</Select> </Select>
); );
}; };

View File

@@ -5,6 +5,7 @@ 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);
@@ -87,7 +88,7 @@ export const logImEXEvent = (eventName, additionalParams, stateProp = null) => {
operationName: eventName, operationName: eventName,
variables: additionalParams, variables: additionalParams,
dbevent: false, dbevent: false,
env: "master" env: checkBeta() ? "beta" : "master"
}); });
// console.log( // console.log(
// "%c[Analytics]", // "%c[Analytics]",

View File

@@ -2461,14 +2461,6 @@ 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 {

View File

@@ -571,7 +571,7 @@ export function Manage({ conflict, bodyshop }) {
return ( return (
<> <>
{import.meta.env.PROD && <ChatAffixContainer bodyshop={bodyshop} chatVisible={chatVisible} />} <ChatAffixContainer bodyshop={bodyshop} chatVisible={chatVisible} />
<Layout style={{ minHeight: "100vh" }} className="layout-container"> <Layout style={{ minHeight: "100vh" }} className="layout-container">
<UpdateAlert /> <UpdateAlert />
<HeaderContainer /> <HeaderContainer />

View File

@@ -1,26 +1,6 @@
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 function ProductionBoardComponent({ bodyshop }) { export default function ProductionBoardComponent() {
const { return <ProductionBoardKanbanContainer />;
treatments: { Production_Use_View }
} = useSplitTreatments({
attributes: {},
names: ["Production_Use_View"],
splitKey: bodyshop && bodyshop.imexshopid
});
return <ProductionBoardKanbanContainer subscriptionType={Production_Use_View.treatment} />;
} }

View File

@@ -2,31 +2,11 @@ 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";
import { connect } from "react-redux"; export default function ProductionListComponent() {
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 subscriptionType={Production_Use_View.treatment} /> <ProductionListTable />
</> </>
); );
} }

View File

@@ -3,7 +3,6 @@ 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();
@@ -21,7 +20,6 @@ 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>

View File

@@ -9,6 +9,7 @@ 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";
@@ -67,7 +68,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>

View File

@@ -36,8 +36,7 @@ export function* openChatByPhone({ payload }) {
data: { conversations } data: { conversations }
} = yield client.query({ } = yield client.query({
query: CONVERSATION_ID_BY_PHONE, query: CONVERSATION_ID_BY_PHONE,
variables: { phone: p.number }, variables: { phone: p.number }
fetchPolicy: 'no-cache'
}); });
if (conversations.length === 0) { if (conversations.length === 0) {

View File

@@ -10,7 +10,7 @@ import {
signInWithEmailAndPassword, signInWithEmailAndPassword,
signOut signOut
} from "firebase/auth"; } from "firebase/auth";
import { arrayUnion, doc, getDoc, setDoc, updateDoc } from "firebase/firestore"; import { doc, getDoc, setDoc } 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,7 +48,6 @@ 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();
@@ -178,24 +177,10 @@ 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();
const res = yield cleanAxios.get("https://api.ipify.org/?format=json"); yield setDoc(userInstanceRef, {
const udoc = yield getDoc(userInstanceRef); timestamp: new Date(),
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);

View File

@@ -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": "", "nobilllines": "This part has not yet been recieved.",
"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,7 +270,6 @@
"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}}"
}, },
@@ -285,21 +284,19 @@
}, },
"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": {
"require_actual_delivery_date": "Require Actual Delivery", "templates": "Delivery Templates",
"templates": "Delivery Templates" "require_actual_delivery_date": "Require Actual Delivery"
}, },
"dms": { "dms": {
"apcontrol": "AP Control Number", "apcontrol": "AP Control Number",
@@ -702,10 +699,7 @@
"workingdays": "Working Days" "workingdays": "Working Days"
}, },
"successes": { "successes": {
"areyousure": "Are you sure you want to continue?", "save": "Shop configuration saved successfully. "
"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.",
@@ -1135,8 +1129,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",
@@ -1144,8 +1138,6 @@
"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",
@@ -1157,12 +1149,10 @@
"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",
@@ -1170,7 +1160,8 @@
"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.",
@@ -1192,8 +1183,6 @@
"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",
@@ -1253,7 +1242,6 @@
"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",
@@ -2743,6 +2731,38 @@
} }
}, },
"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",
@@ -2757,23 +2777,27 @@
"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",
@@ -2783,7 +2807,6 @@
"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",
@@ -2792,88 +2815,47 @@
"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",
"wide": "Wide" "alerts": "Alerts"
},
"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": {
@@ -2931,8 +2913,6 @@
"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)",
@@ -3424,18 +3404,6 @@
"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"
@@ -3574,6 +3542,18 @@
"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

View File

@@ -270,7 +270,6 @@
"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": ""
}, },
@@ -285,21 +284,19 @@
}, },
"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": {
"require_actual_delivery_date": "", "templates": "",
"templates": "" "require_actual_delivery_date": ""
}, },
"dms": { "dms": {
"apcontrol": "", "apcontrol": "",
@@ -702,10 +699,7 @@
"workingdays": "" "workingdays": ""
}, },
"successes": { "successes": {
"areyousure": "", "save": ""
"defaultviewcreated": "",
"save": "",
"unsavedchanges": ""
}, },
"validation": { "validation": {
"centermustexist": "", "centermustexist": "",
@@ -1135,8 +1129,8 @@
}, },
"general": { "general": {
"actions": { "actions": {
"defaults": "",
"add": "", "add": "",
"autoupdate": "",
"calculate": "", "calculate": "",
"cancel": "", "cancel": "",
"clear": "", "clear": "",
@@ -1144,8 +1138,6 @@
"copied": "", "copied": "",
"copylink": "", "copylink": "",
"create": "", "create": "",
"defaults": "",
"delay": "",
"delete": "Effacer", "delete": "Effacer",
"deleteall": "", "deleteall": "",
"deselectall": "", "deselectall": "",
@@ -1157,12 +1149,10 @@
"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": "",
@@ -1170,7 +1160,8 @@
"submit": "", "submit": "",
"tryagain": "", "tryagain": "",
"view": "", "view": "",
"viewreleasenotes": "" "viewreleasenotes": "",
"remove_alert": ""
}, },
"errors": { "errors": {
"fcm": "", "fcm": "",
@@ -1192,8 +1183,6 @@
"clear": "", "clear": "",
"confirmpassword": "", "confirmpassword": "",
"created_at": "", "created_at": "",
"date": "",
"datetime": "",
"email": "", "email": "",
"errors": "", "errors": "",
"excel": "", "excel": "",
@@ -1253,7 +1242,6 @@
"tuesday": "", "tuesday": "",
"tvmode": "", "tvmode": "",
"unknown": "Inconnu", "unknown": "Inconnu",
"unsavedchanges": "",
"username": "", "username": "",
"view": "", "view": "",
"wednesday": "", "wednesday": "",
@@ -2743,6 +2731,38 @@
} }
}, },
"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": "",
@@ -2757,23 +2777,27 @@
"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": "",
@@ -2783,7 +2807,6 @@
"qbo_usa": "" "qbo_usa": ""
} }
}, },
"card_size": "",
"cardcolor": "", "cardcolor": "",
"cardsettings": "", "cardsettings": "",
"clm_no": "", "clm_no": "",
@@ -2792,88 +2815,47 @@
"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": "",
"wide": "" "alerts": ""
},
"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": {
@@ -2931,8 +2913,6 @@
"vendor": "" "vendor": ""
}, },
"templates": { "templates": {
"adp_payroll_flat": "",
"adp_payroll_straight": "",
"anticipated_revenue": "", "anticipated_revenue": "",
"ar_aging": "", "ar_aging": "",
"attendance_detail": "", "attendance_detail": "",
@@ -3424,18 +3404,6 @@
"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": ""
@@ -3574,6 +3542,18 @@
"validation": { "validation": {
"unique_vendor_name": "" "unique_vendor_name": ""
} }
},
"trello": {
"labels": {
"add_card": "",
"add_lane": "",
"delete_lane": "",
"lane_actions": "",
"title": "",
"description": "",
"label": "",
"cancel": ""
}
} }
} }
} }

View File

@@ -2158,32 +2158,6 @@ 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
} }
} }
: {}), : {}),

View File

@@ -1,84 +0,0 @@
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;

View File

@@ -0,0 +1,47 @@
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;

View File

@@ -1,5 +1,5 @@
version: 2 version: 2
endpoint: https://db.dev.imex.online endpoint: https://db.dev.bodyshop.app
admin_secret: Dev-BodyShopApp! admin_secret: Dev-BodyShopApp!
metadata_directory: metadata metadata_directory: metadata
actions: actions:

View File

@@ -1,27 +1,3 @@
- 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 * * * *'

View File

@@ -918,7 +918,6 @@
- bill_tax_rates - bill_tax_rates
- cdk_configuration - cdk_configuration
- cdk_dealerid - cdk_dealerid
- chatterid
- city - city
- claimscorpid - claimscorpid
- convenient_company - convenient_company
@@ -940,7 +939,6 @@
- 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
@@ -1042,7 +1040,6 @@
- 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
@@ -4243,63 +4240,6 @@
- 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
@@ -4359,35 +4299,6 @@
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

View File

@@ -1,11 +0,0 @@
-- 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;

View File

@@ -1,9 +0,0 @@
CREATE
OR REPLACE VIEW "public"."jobs_inproduction" AS
SELECT
j.id,
j.updated_at
FROM
jobs j
WHERE
j.inproduction=true;

View File

@@ -1,8 +0,0 @@
-- 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);

View File

@@ -1,6 +0,0 @@
CREATE OR REPLACE VIEW "public"."jobs_inproduction" AS
SELECT j.id,
j.updated_at,
j.shopid
FROM jobs j
WHERE (j.inproduction = true);

View File

@@ -1,8 +0,0 @@
-- 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);

View File

@@ -1,6 +0,0 @@
CREATE OR REPLACE VIEW "public"."jobs_inproduction" AS
SELECT j.id,
j.updated_at,
j.shopid
FROM jobs j
WHERE (j.inproduction = true);

View File

@@ -1,3 +0,0 @@
-- 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;

View File

@@ -1 +0,0 @@
CREATE INDEX idx_jobs_inproduction_true_cast ON jobs(inproduction) WHERE inproduction = ('true') :: boolean;

View File

@@ -1,2 +0,0 @@
CREATE INDEX "idx_jobs_inproduction_true_cast" on
"public"."jobs" using btree ("inproduction");

View File

@@ -1 +0,0 @@
DROP INDEX IF EXISTS "public"."idx_jobs_inproduction_true_cast";

128
package-lock.json generated
View File

@@ -9,9 +9,9 @@
"version": "0.2.0", "version": "0.2.0",
"license": "UNLICENSED", "license": "UNLICENSED",
"dependencies": { "dependencies": {
"@aws-sdk/client-secrets-manager": "^3.629.0", "@aws-sdk/client-secrets-manager": "^3.632.0",
"@aws-sdk/client-ses": "^3.629.0", "@aws-sdk/client-ses": "^3.632.0",
"@aws-sdk/credential-provider-node": "^3.629.0", "@aws-sdk/credential-provider-node": "^3.632.0",
"@opensearch-project/opensearch": "^2.11.0", "@opensearch-project/opensearch": "^2.11.0",
"aws4": "^1.13.1", "aws4": "^1.13.1",
"axios": "^1.7.4", "axios": "^1.7.4",
@@ -42,7 +42,7 @@
"node-mailjet": "^6.0.5", "node-mailjet": "^6.0.5",
"node-persist": "^4.0.3", "node-persist": "^4.0.3",
"nodemailer": "^6.9.14", "nodemailer": "^6.9.14",
"phone": "^3.1.49", "phone": "^3.1.50",
"recursive-diff": "^1.0.9", "recursive-diff": "^1.0.9",
"rimraf": "^6.0.1", "rimraf": "^6.0.1",
"soap": "^1.1.1", "soap": "^1.1.1",
@@ -180,24 +180,24 @@
} }
}, },
"node_modules/@aws-sdk/client-secrets-manager": { "node_modules/@aws-sdk/client-secrets-manager": {
"version": "3.629.0", "version": "3.632.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/client-secrets-manager/-/client-secrets-manager-3.629.0.tgz", "resolved": "https://registry.npmjs.org/@aws-sdk/client-secrets-manager/-/client-secrets-manager-3.632.0.tgz",
"integrity": "sha512-ZZKI9uTQ3WIdbCZK6sveccalLTWgyOZeebi+Gnwl5ztKMk4OfwZKxyhry1DXB8gMrlISniREDb3ZxUZdFNwqfQ==", "integrity": "sha512-WsQhPHHK1yPfALcP1B7nBSGDzky6vFTUEXnUdfzb5Xy2cT+JTBTS6ChtQGqqOuGHDP/3t/9soqZ+L6rUCYBb/Q==",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-browser": "5.2.0",
"@aws-crypto/sha256-js": "5.2.0", "@aws-crypto/sha256-js": "5.2.0",
"@aws-sdk/client-sso-oidc": "3.629.0", "@aws-sdk/client-sso-oidc": "3.632.0",
"@aws-sdk/client-sts": "3.629.0", "@aws-sdk/client-sts": "3.632.0",
"@aws-sdk/core": "3.629.0", "@aws-sdk/core": "3.629.0",
"@aws-sdk/credential-provider-node": "3.629.0", "@aws-sdk/credential-provider-node": "3.632.0",
"@aws-sdk/middleware-host-header": "3.620.0", "@aws-sdk/middleware-host-header": "3.620.0",
"@aws-sdk/middleware-logger": "3.609.0", "@aws-sdk/middleware-logger": "3.609.0",
"@aws-sdk/middleware-recursion-detection": "3.620.0", "@aws-sdk/middleware-recursion-detection": "3.620.0",
"@aws-sdk/middleware-user-agent": "3.620.0", "@aws-sdk/middleware-user-agent": "3.632.0",
"@aws-sdk/region-config-resolver": "3.614.0", "@aws-sdk/region-config-resolver": "3.614.0",
"@aws-sdk/types": "3.609.0", "@aws-sdk/types": "3.609.0",
"@aws-sdk/util-endpoints": "3.614.0", "@aws-sdk/util-endpoints": "3.632.0",
"@aws-sdk/util-user-agent-browser": "3.609.0", "@aws-sdk/util-user-agent-browser": "3.609.0",
"@aws-sdk/util-user-agent-node": "3.614.0", "@aws-sdk/util-user-agent-node": "3.614.0",
"@smithy/config-resolver": "^3.0.5", "@smithy/config-resolver": "^3.0.5",
@@ -245,24 +245,24 @@
} }
}, },
"node_modules/@aws-sdk/client-ses": { "node_modules/@aws-sdk/client-ses": {
"version": "3.629.0", "version": "3.632.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/client-ses/-/client-ses-3.629.0.tgz", "resolved": "https://registry.npmjs.org/@aws-sdk/client-ses/-/client-ses-3.632.0.tgz",
"integrity": "sha512-KreCdUAO/gIzWCgnPV1/dGUvLDDTdXI3fZzjjHUWFa1bE4wENjenNnWGw0qZgc8xB8pgiMdgPn7N+JvxJ7c/ZQ==", "integrity": "sha512-hi01MPJF55LEK7NB1LZrqUV7b5GyjH08EToYuekFvQf9aNoR5mqWuMEDQ/dFAowYhUa2KqCdn67HnPn0ySQxHg==",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-browser": "5.2.0",
"@aws-crypto/sha256-js": "5.2.0", "@aws-crypto/sha256-js": "5.2.0",
"@aws-sdk/client-sso-oidc": "3.629.0", "@aws-sdk/client-sso-oidc": "3.632.0",
"@aws-sdk/client-sts": "3.629.0", "@aws-sdk/client-sts": "3.632.0",
"@aws-sdk/core": "3.629.0", "@aws-sdk/core": "3.629.0",
"@aws-sdk/credential-provider-node": "3.629.0", "@aws-sdk/credential-provider-node": "3.632.0",
"@aws-sdk/middleware-host-header": "3.620.0", "@aws-sdk/middleware-host-header": "3.620.0",
"@aws-sdk/middleware-logger": "3.609.0", "@aws-sdk/middleware-logger": "3.609.0",
"@aws-sdk/middleware-recursion-detection": "3.620.0", "@aws-sdk/middleware-recursion-detection": "3.620.0",
"@aws-sdk/middleware-user-agent": "3.620.0", "@aws-sdk/middleware-user-agent": "3.632.0",
"@aws-sdk/region-config-resolver": "3.614.0", "@aws-sdk/region-config-resolver": "3.614.0",
"@aws-sdk/types": "3.609.0", "@aws-sdk/types": "3.609.0",
"@aws-sdk/util-endpoints": "3.614.0", "@aws-sdk/util-endpoints": "3.632.0",
"@aws-sdk/util-user-agent-browser": "3.609.0", "@aws-sdk/util-user-agent-browser": "3.609.0",
"@aws-sdk/util-user-agent-node": "3.614.0", "@aws-sdk/util-user-agent-node": "3.614.0",
"@smithy/config-resolver": "^3.0.5", "@smithy/config-resolver": "^3.0.5",
@@ -298,9 +298,9 @@
} }
}, },
"node_modules/@aws-sdk/client-sso": { "node_modules/@aws-sdk/client-sso": {
"version": "3.629.0", "version": "3.632.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.629.0.tgz", "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.632.0.tgz",
"integrity": "sha512-2w8xU4O0Grca5HmT2dXZ5fF0g39RxODtmoqHJDsK5DSt750LqDG4w3ktmBvQs3+SrpkkJOjlX5v/hb2PCxVbww==", "integrity": "sha512-iYWHiKBz44m3chCFvtvHnvCpL2rALzyr1e6tOZV3dLlOKtQtDUlPy6OtnXDu4y+wyJCniy8ivG3+LAe4klzn1Q==",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-browser": "5.2.0",
@@ -309,10 +309,10 @@
"@aws-sdk/middleware-host-header": "3.620.0", "@aws-sdk/middleware-host-header": "3.620.0",
"@aws-sdk/middleware-logger": "3.609.0", "@aws-sdk/middleware-logger": "3.609.0",
"@aws-sdk/middleware-recursion-detection": "3.620.0", "@aws-sdk/middleware-recursion-detection": "3.620.0",
"@aws-sdk/middleware-user-agent": "3.620.0", "@aws-sdk/middleware-user-agent": "3.632.0",
"@aws-sdk/region-config-resolver": "3.614.0", "@aws-sdk/region-config-resolver": "3.614.0",
"@aws-sdk/types": "3.609.0", "@aws-sdk/types": "3.609.0",
"@aws-sdk/util-endpoints": "3.614.0", "@aws-sdk/util-endpoints": "3.632.0",
"@aws-sdk/util-user-agent-browser": "3.609.0", "@aws-sdk/util-user-agent-browser": "3.609.0",
"@aws-sdk/util-user-agent-node": "3.614.0", "@aws-sdk/util-user-agent-node": "3.614.0",
"@smithy/config-resolver": "^3.0.5", "@smithy/config-resolver": "^3.0.5",
@@ -347,22 +347,22 @@
} }
}, },
"node_modules/@aws-sdk/client-sso-oidc": { "node_modules/@aws-sdk/client-sso-oidc": {
"version": "3.629.0", "version": "3.632.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.629.0.tgz", "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.632.0.tgz",
"integrity": "sha512-3if0LauNJPqubGYf8vnlkp+B3yAeKRuRNxfNbHlE6l510xWGcKK/ZsEmiFmfePzKKSRrDh/cxMFMScgOrXptNg==", "integrity": "sha512-Oh1fIWaoZluihOCb/zDEpRTi+6an82fgJz7fyRBugyLhEtDjmvpCQ3oKjzaOhoN+4EvXAm1ZS/ZgpvXBlIRTgw==",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-browser": "5.2.0",
"@aws-crypto/sha256-js": "5.2.0", "@aws-crypto/sha256-js": "5.2.0",
"@aws-sdk/core": "3.629.0", "@aws-sdk/core": "3.629.0",
"@aws-sdk/credential-provider-node": "3.629.0", "@aws-sdk/credential-provider-node": "3.632.0",
"@aws-sdk/middleware-host-header": "3.620.0", "@aws-sdk/middleware-host-header": "3.620.0",
"@aws-sdk/middleware-logger": "3.609.0", "@aws-sdk/middleware-logger": "3.609.0",
"@aws-sdk/middleware-recursion-detection": "3.620.0", "@aws-sdk/middleware-recursion-detection": "3.620.0",
"@aws-sdk/middleware-user-agent": "3.620.0", "@aws-sdk/middleware-user-agent": "3.632.0",
"@aws-sdk/region-config-resolver": "3.614.0", "@aws-sdk/region-config-resolver": "3.614.0",
"@aws-sdk/types": "3.609.0", "@aws-sdk/types": "3.609.0",
"@aws-sdk/util-endpoints": "3.614.0", "@aws-sdk/util-endpoints": "3.632.0",
"@aws-sdk/util-user-agent-browser": "3.609.0", "@aws-sdk/util-user-agent-browser": "3.609.0",
"@aws-sdk/util-user-agent-node": "3.614.0", "@aws-sdk/util-user-agent-node": "3.614.0",
"@smithy/config-resolver": "^3.0.5", "@smithy/config-resolver": "^3.0.5",
@@ -396,27 +396,27 @@
"node": ">=16.0.0" "node": ">=16.0.0"
}, },
"peerDependencies": { "peerDependencies": {
"@aws-sdk/client-sts": "^3.629.0" "@aws-sdk/client-sts": "^3.632.0"
} }
}, },
"node_modules/@aws-sdk/client-sts": { "node_modules/@aws-sdk/client-sts": {
"version": "3.629.0", "version": "3.632.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.629.0.tgz", "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.632.0.tgz",
"integrity": "sha512-RjOs371YwnSVGxhPjuluJKaxl4gcPYTAky0nPjwBime0i9/iS9nI8R8l5j7k7ec9tpFWjBPvNnThCU07pvjdzw==", "integrity": "sha512-Ss5cBH09icpTvT+jtGGuQlRdwtO7RyE9BF4ZV/CEPATdd9whtJt4Qxdya8BUnkWR7h5HHTrQHqai3YVYjku41A==",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-browser": "5.2.0",
"@aws-crypto/sha256-js": "5.2.0", "@aws-crypto/sha256-js": "5.2.0",
"@aws-sdk/client-sso-oidc": "3.629.0", "@aws-sdk/client-sso-oidc": "3.632.0",
"@aws-sdk/core": "3.629.0", "@aws-sdk/core": "3.629.0",
"@aws-sdk/credential-provider-node": "3.629.0", "@aws-sdk/credential-provider-node": "3.632.0",
"@aws-sdk/middleware-host-header": "3.620.0", "@aws-sdk/middleware-host-header": "3.620.0",
"@aws-sdk/middleware-logger": "3.609.0", "@aws-sdk/middleware-logger": "3.609.0",
"@aws-sdk/middleware-recursion-detection": "3.620.0", "@aws-sdk/middleware-recursion-detection": "3.620.0",
"@aws-sdk/middleware-user-agent": "3.620.0", "@aws-sdk/middleware-user-agent": "3.632.0",
"@aws-sdk/region-config-resolver": "3.614.0", "@aws-sdk/region-config-resolver": "3.614.0",
"@aws-sdk/types": "3.609.0", "@aws-sdk/types": "3.609.0",
"@aws-sdk/util-endpoints": "3.614.0", "@aws-sdk/util-endpoints": "3.632.0",
"@aws-sdk/util-user-agent-browser": "3.609.0", "@aws-sdk/util-user-agent-browser": "3.609.0",
"@aws-sdk/util-user-agent-node": "3.614.0", "@aws-sdk/util-user-agent-node": "3.614.0",
"@smithy/config-resolver": "^3.0.5", "@smithy/config-resolver": "^3.0.5",
@@ -507,15 +507,15 @@
} }
}, },
"node_modules/@aws-sdk/credential-provider-ini": { "node_modules/@aws-sdk/credential-provider-ini": {
"version": "3.629.0", "version": "3.632.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.629.0.tgz", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.632.0.tgz",
"integrity": "sha512-r9fI7BABARvVDp77DBUImQzYdvarAIdhbvpCEZib0rlpvfWu3zxE9KZcapCAAi0MPjxeDfb7RMehFQIkAP7mYw==", "integrity": "sha512-m6epoW41xa1ajU5OiHcmQHoGVtrbXBaRBOUhlCLZmcaqMLYsboM4iD/WZP8aatKEON5tTnVXh/4StV8D/+wemw==",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@aws-sdk/credential-provider-env": "3.620.1", "@aws-sdk/credential-provider-env": "3.620.1",
"@aws-sdk/credential-provider-http": "3.622.0", "@aws-sdk/credential-provider-http": "3.622.0",
"@aws-sdk/credential-provider-process": "3.620.1", "@aws-sdk/credential-provider-process": "3.620.1",
"@aws-sdk/credential-provider-sso": "3.629.0", "@aws-sdk/credential-provider-sso": "3.632.0",
"@aws-sdk/credential-provider-web-identity": "3.621.0", "@aws-sdk/credential-provider-web-identity": "3.621.0",
"@aws-sdk/types": "3.609.0", "@aws-sdk/types": "3.609.0",
"@smithy/credential-provider-imds": "^3.2.0", "@smithy/credential-provider-imds": "^3.2.0",
@@ -528,20 +528,20 @@
"node": ">=16.0.0" "node": ">=16.0.0"
}, },
"peerDependencies": { "peerDependencies": {
"@aws-sdk/client-sts": "^3.629.0" "@aws-sdk/client-sts": "^3.632.0"
} }
}, },
"node_modules/@aws-sdk/credential-provider-node": { "node_modules/@aws-sdk/credential-provider-node": {
"version": "3.629.0", "version": "3.632.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.629.0.tgz", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.632.0.tgz",
"integrity": "sha512-868hnVOLlXOBHk91Rl0jZIRgr/M4WJCa0nOrW9A9yidsQxuZp9P0vshDmm4hMvNZadmPIfo0Rra2MpA4RELoCw==", "integrity": "sha512-cL8fuJWm/xQBO4XJPkeuZzl3XinIn9EExWgzpG48NRMKR5us1RI/ucv7xFbBBaG+r/sDR2HpYBIA3lVIpm1H3Q==",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@aws-sdk/credential-provider-env": "3.620.1", "@aws-sdk/credential-provider-env": "3.620.1",
"@aws-sdk/credential-provider-http": "3.622.0", "@aws-sdk/credential-provider-http": "3.622.0",
"@aws-sdk/credential-provider-ini": "3.629.0", "@aws-sdk/credential-provider-ini": "3.632.0",
"@aws-sdk/credential-provider-process": "3.620.1", "@aws-sdk/credential-provider-process": "3.620.1",
"@aws-sdk/credential-provider-sso": "3.629.0", "@aws-sdk/credential-provider-sso": "3.632.0",
"@aws-sdk/credential-provider-web-identity": "3.621.0", "@aws-sdk/credential-provider-web-identity": "3.621.0",
"@aws-sdk/types": "3.609.0", "@aws-sdk/types": "3.609.0",
"@smithy/credential-provider-imds": "^3.2.0", "@smithy/credential-provider-imds": "^3.2.0",
@@ -571,12 +571,12 @@
} }
}, },
"node_modules/@aws-sdk/credential-provider-sso": { "node_modules/@aws-sdk/credential-provider-sso": {
"version": "3.629.0", "version": "3.632.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.629.0.tgz", "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.632.0.tgz",
"integrity": "sha512-Lf4XOuj6jamxgGZGrVojERh5S+NS2t2S4CUOnAu6tJ5U0GPlpjhINUKlcVxJBpsIXudMGW1nkumAd3+kazCPig==", "integrity": "sha512-P/4wB6j7ym5QCPTL2xlMfvf2NcXSh+z0jmsZP4WW/tVwab4hvgabPPbLeEZDSWZ0BpgtxKGvRq0GSHuGeirQbA==",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@aws-sdk/client-sso": "3.629.0", "@aws-sdk/client-sso": "3.632.0",
"@aws-sdk/token-providers": "3.614.0", "@aws-sdk/token-providers": "3.614.0",
"@aws-sdk/types": "3.609.0", "@aws-sdk/types": "3.609.0",
"@smithy/property-provider": "^3.1.3", "@smithy/property-provider": "^3.1.3",
@@ -650,13 +650,13 @@
} }
}, },
"node_modules/@aws-sdk/middleware-user-agent": { "node_modules/@aws-sdk/middleware-user-agent": {
"version": "3.620.0", "version": "3.632.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.620.0.tgz", "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.632.0.tgz",
"integrity": "sha512-bvS6etn+KsuL32ubY5D3xNof1qkenpbJXf/ugGXbg0n98DvDFQ/F+SMLxHgbnER5dsKYchNnhmtI6/FC3HFu/A==", "integrity": "sha512-yY/sFsHKwG9yzSf/DTclqWJaGPI2gPBJDCGBujSqTG1zlS7Ot4fqi91DZ6088BFWzbOorDzJFcAhAEFzc6LuQg==",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@aws-sdk/types": "3.609.0", "@aws-sdk/types": "3.609.0",
"@aws-sdk/util-endpoints": "3.614.0", "@aws-sdk/util-endpoints": "3.632.0",
"@smithy/protocol-http": "^4.1.0", "@smithy/protocol-http": "^4.1.0",
"@smithy/types": "^3.3.0", "@smithy/types": "^3.3.0",
"tslib": "^2.6.2" "tslib": "^2.6.2"
@@ -713,9 +713,10 @@
} }
}, },
"node_modules/@aws-sdk/util-endpoints": { "node_modules/@aws-sdk/util-endpoints": {
"version": "3.614.0", "version": "3.632.0",
"resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.614.0.tgz", "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.632.0.tgz",
"integrity": "sha512-wK2cdrXHH4oz4IomV/yrGkftU9A+ITB6nFL+rxxyO78is2ifHJpFdV4aqk4LSkXYPi6CXWNru/Dqc7yiKXgJPw==", "integrity": "sha512-LlYMU8pAbcEQphOpE6xaNLJ8kPGhklZZTVzZVpVW477NaaGgoGTMYNXTABYHcxeF5E2lLrxql9OmVpvr8GWN8Q==",
"license": "Apache-2.0",
"dependencies": { "dependencies": {
"@aws-sdk/types": "3.609.0", "@aws-sdk/types": "3.609.0",
"@smithy/types": "^3.3.0", "@smithy/types": "^3.3.0",
@@ -5797,9 +5798,10 @@
"integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ=="
}, },
"node_modules/phone": { "node_modules/phone": {
"version": "3.1.49", "version": "3.1.50",
"resolved": "https://registry.npmjs.org/phone/-/phone-3.1.49.tgz", "resolved": "https://registry.npmjs.org/phone/-/phone-3.1.50.tgz",
"integrity": "sha512-S+rHWXSQrllK5eQwz0sDbwfxQ2PzennWPgsP/jdpEPH3k7P5IBJZYjvYfU8e/RF5AwKCgOtzbTGTGJcBSLJVVw==", "integrity": "sha512-TRmb2bX3sX+rrOrc8FRd8hmy4exoH2Lu3vjBP/dLgwwci1lv7DbjJ2iHMe7X4Hm8Pa0rJcfqTbq/O1vjU4NgxQ==",
"license": "MIT",
"engines": { "engines": {
"node": ">=12" "node": ">=12"
} }

View File

@@ -19,9 +19,9 @@
"makeitpretty": "prettier --write \"**/*.{css,js,json,jsx,scss}\"" "makeitpretty": "prettier --write \"**/*.{css,js,json,jsx,scss}\""
}, },
"dependencies": { "dependencies": {
"@aws-sdk/client-secrets-manager": "^3.629.0", "@aws-sdk/client-secrets-manager": "^3.632.0",
"@aws-sdk/client-ses": "^3.629.0", "@aws-sdk/client-ses": "^3.632.0",
"@aws-sdk/credential-provider-node": "^3.629.0", "@aws-sdk/credential-provider-node": "^3.632.0",
"@opensearch-project/opensearch": "^2.11.0", "@opensearch-project/opensearch": "^2.11.0",
"aws4": "^1.13.1", "aws4": "^1.13.1",
"axios": "^1.7.4", "axios": "^1.7.4",
@@ -52,7 +52,7 @@
"node-mailjet": "^6.0.5", "node-mailjet": "^6.0.5",
"node-persist": "^4.0.3", "node-persist": "^4.0.3",
"nodemailer": "^6.9.14", "nodemailer": "^6.9.14",
"phone": "^3.1.49", "phone": "^3.1.50",
"recursive-diff": "^1.0.9", "recursive-diff": "^1.0.9",
"rimraf": "^6.0.1", "rimraf": "^6.0.1",
"soap": "^1.1.1", "soap": "^1.1.1",

View File

@@ -94,10 +94,7 @@ exports.default = async (req, res) => {
ret.push({ ret.push({
billid: bill.id, billid: bill.id,
success: false, success: false,
errorMessage: errorMessage: (error && error.authResponse && error.authResponse.body) || (error && error.message)
(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.
@@ -212,14 +209,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_itc.accountdesc] value: accounts[bodyshop.md_responsibility_centers.taxes.federal.accountdesc]
} }
}, },
Amount: Dinero({ Amount: Dinero({
amount: Math.round( amount: Math.round(
bill.billlines.reduce((acc, val) => { bill.billlines.reduce((acc, val) => {
return acc + val.applicable_taxes?.federal ? (val.actual_cost * val.quantity ?? 0) : 0; return acc + val.actual_cost * val.quantity;
}, 0) * 100 }, 0) * 100
) )
}) })
@@ -277,8 +274,6 @@ 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;

View File

@@ -179,11 +179,7 @@ exports.default = async (req, res) => {
ret.push({ ret.push({
jobid: job.id, jobid: job.id,
success: false, success: false,
errorMessage: errorMessage: (error && error.authResponse && error.authResponse.body) || (error && error.message)
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, {
@@ -258,6 +254,7 @@ 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(),
@@ -578,9 +575,7 @@ 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: "InsertInvoice", method: "InsertOwner"
validationError: JSON.stringify(error?.response?.data),
accountmeta: JSON.stringify({ items, taxCodes, classes })
}); });
throw error; throw error;
} }

View File

@@ -31,12 +31,6 @@ 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);

View File

@@ -31,12 +31,6 @@ 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);

View File

@@ -31,12 +31,6 @@ 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"];
@@ -62,8 +56,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("day") : moment().subtract(5, "days").startOf("day"), start: start ? moment(start).startOf("hours") : moment().subtract(2, "hours").startOf("hour"),
...(end && { end: moment(end).endOf("day") }) ...(end && { end: moment(end).endOf("hours") })
}); });
const kaizenObject = { const kaizenObject = {
@@ -182,19 +176,24 @@ 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))}`
});
} }
}; };

View File

@@ -965,17 +965,22 @@ function CalculateTaxesTotals(job, otherTotals) {
} }
}); });
if (job.adjustment_bottom_line && job.adjustment_bottom_line !== 0) { if (job.adjustment_bottom_line) {
for (let tyCounter = 1; tyCounter <= 5; tyCounter++) { const subtotal_before_adjustment = subtotal.add(Dinero({ amount: Math.round(job.adjustment_bottom_line * -100) }));
if (IsTrueOrYes(pfp["PAN"][`prt_tx_in${tyCounter}`])) { const percent_of_adjustment =
//This amount is taxable for this type. Math.round(
taxableAmountsByTier[`ty${tyCounter}Tax`] = taxableAmountsByTier[`ty${tyCounter}Tax`].add( subtotal_before_adjustment.toUnit() /
Dinero({ (job.adjustment_bottom_line > 0 ? job.adjustment_bottom_line : job.adjustment_bottom_line * -1)
amount: Math.round(job.adjustment_bottom_line * 100) ) / 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;