Compare commits
170 Commits
feature/IO
...
feature/IO
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cafca35500 | ||
|
|
6e61159608 | ||
|
|
3c85de3e34 | ||
|
|
1a4c9faab1 | ||
|
|
bfbf34e11d | ||
|
|
646754732d | ||
|
|
439d9e7b74 | ||
|
|
464f7044f0 | ||
|
|
7cde2f64af | ||
|
|
f674fff930 | ||
|
|
efc1157653 | ||
|
|
0677712d6e | ||
|
|
2e106a5d07 | ||
|
|
da7b97042e | ||
|
|
f018a2b2a6 | ||
|
|
c3f7d7bad2 | ||
|
|
70d857bfec | ||
|
|
f3265901b6 | ||
|
|
7c8ac50426 | ||
|
|
8ad39fe855 | ||
|
|
13b6218c43 | ||
|
|
bece3278f4 | ||
|
|
4c0a1960ad | ||
|
|
47324422a6 | ||
|
|
3b1da6901d | ||
|
|
fc6ec54233 | ||
|
|
64928d0849 | ||
|
|
56a580b1e7 | ||
|
|
f7af3b407b | ||
|
|
9a0674f5d7 | ||
|
|
cc30ea658e | ||
|
|
59869def31 | ||
|
|
a5d3f2caf1 | ||
|
|
4ad87a522c | ||
|
|
453812222b | ||
|
|
145cf7cc93 | ||
|
|
c09e22ed96 | ||
|
|
cdb2d4d2d6 | ||
|
|
29f0031c1e | ||
|
|
e8099e130a | ||
|
|
1cbca1ddf0 | ||
|
|
eeed004fe2 | ||
|
|
5a180b86fb | ||
|
|
e3059b41ae | ||
|
|
1a5c71048c | ||
|
|
fc4e97c9b5 | ||
|
|
3cd3d7414d | ||
|
|
1bb2212e4a | ||
|
|
a088f27f1d | ||
|
|
0e9ad1258d | ||
|
|
2a33f462a3 | ||
|
|
cbc164dbeb | ||
|
|
0bfc7033a9 | ||
|
|
2ec0d90a58 | ||
|
|
6382fdf19c | ||
|
|
9287e6608d | ||
|
|
0fcee5b25e | ||
|
|
d221763064 | ||
|
|
b39a5b755e | ||
|
|
30cb4ef562 | ||
|
|
449330441a | ||
|
|
fcab5e6ef2 | ||
|
|
0212b837ea | ||
|
|
e7438a099e | ||
|
|
b3303e3c38 | ||
|
|
c69c86d193 | ||
|
|
73ec8b8a70 | ||
|
|
af09796df8 | ||
|
|
954504de8d | ||
|
|
0aba040338 | ||
|
|
c3bfe87674 | ||
|
|
9aa1279144 | ||
|
|
4e6c45b195 | ||
|
|
4fdb939bd2 | ||
|
|
062a1dcc72 | ||
|
|
7b420b1855 | ||
|
|
40f61bbc8f | ||
|
|
f5d821c394 | ||
|
|
3958ec9189 | ||
|
|
1e4f52e541 | ||
|
|
5cc5cb444e | ||
|
|
4acf0c59ca | ||
|
|
2858a5e871 | ||
|
|
24496d3ee1 | ||
|
|
0a5df69b12 | ||
|
|
80efea02c6 | ||
|
|
9f5c282b41 | ||
|
|
b2602c3385 | ||
|
|
0e584af424 | ||
|
|
cdc3de2a33 | ||
|
|
3bfa556b02 | ||
|
|
44cb7577e2 | ||
|
|
46d2b08477 | ||
|
|
0193ff9e65 | ||
|
|
fd9a51209f | ||
|
|
d0a7b87e04 | ||
|
|
799b24c90e | ||
|
|
3e1a8c87d1 | ||
|
|
c886d874de | ||
|
|
4dfb020089 | ||
|
|
bc6f05acbc | ||
|
|
2701bbd501 | ||
|
|
1f2040d97c | ||
|
|
43963a3e91 | ||
|
|
4287311adb | ||
|
|
d0e8589a76 | ||
|
|
c4bab72947 | ||
|
|
aa4b4998fa | ||
|
|
ed4566e00f | ||
|
|
5c2cdfe16c | ||
|
|
12c75357b5 | ||
|
|
d40f3ee45a | ||
|
|
96a0def846 | ||
|
|
1fd595d0de | ||
|
|
52cf4f3d1f | ||
|
|
4d9be1d232 | ||
|
|
fb2bc20b4f | ||
|
|
744593e96a | ||
|
|
1e9308be9b | ||
|
|
411605e121 | ||
|
|
1da8d6abb3 | ||
|
|
cdcef798df | ||
|
|
f7207a9f3f | ||
|
|
7a54b55bd4 | ||
|
|
991dfc2ad5 | ||
|
|
718c8291a8 | ||
|
|
f1e84c348b | ||
|
|
2a2d399a98 | ||
|
|
5f513a8bef | ||
|
|
4b96d5a707 | ||
|
|
220f3d4410 | ||
|
|
841f62bd84 | ||
|
|
f3f16b78d5 | ||
|
|
91e2e7931b | ||
|
|
1e855799f8 | ||
|
|
3c6faf8473 | ||
|
|
c994eaaa8e | ||
|
|
517d8f4163 | ||
|
|
9deb2964a5 | ||
|
|
9cf9f8b844 | ||
|
|
ad46ea74c0 | ||
|
|
2a28855e4b | ||
|
|
8d25f60097 | ||
|
|
982a51f16e | ||
|
|
68d02648d7 | ||
|
|
6e8122849a | ||
|
|
b04ae84941 | ||
|
|
932979d5fb | ||
|
|
f7ef32c58d | ||
|
|
f7108b4b8c | ||
|
|
882038a794 | ||
|
|
aec23fe46b | ||
|
|
89d5b1cfe4 | ||
|
|
35ac0b0c6a | ||
|
|
2a2a0f8961 | ||
|
|
d9902b9744 | ||
|
|
f82478a362 | ||
|
|
bb3d3fbe72 | ||
|
|
4fa0593bb5 | ||
|
|
41517ca7d4 | ||
|
|
35c9f649ad | ||
|
|
ad2f2e55a5 | ||
|
|
41c446ddb3 | ||
|
|
7d6aa8489d | ||
|
|
63f1e0f07c | ||
|
|
98f4423624 | ||
|
|
1ac4cbb59f | ||
|
|
24ebfbfbf5 | ||
|
|
cc9979ff4b | ||
|
|
ad1ce7b220 |
@@ -5,6 +5,7 @@ orbs:
|
||||
aws-s3: circleci/aws-s3@4.0.0
|
||||
aws-cli: circleci/aws-cli@4.0
|
||||
eb: circleci/aws-elastic-beanstalk@2.0.1
|
||||
jira: circleci/jira@2.1.0
|
||||
jobs:
|
||||
imex-api-deploy:
|
||||
docker:
|
||||
@@ -18,6 +19,12 @@ jobs:
|
||||
eb status --verbose
|
||||
eb deploy
|
||||
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:
|
||||
docker:
|
||||
@@ -33,11 +40,16 @@ jobs:
|
||||
- run:
|
||||
name: Execute migration
|
||||
command: |
|
||||
npm install hasura-cli -g
|
||||
curl -L https://github.com/hasura/graphql-engine/raw/stable/cli/get.sh | bash
|
||||
hasura migrate apply --endpoint https://db.imex.online/ --admin-secret << parameters.secret >>
|
||||
hasura metadata apply --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:
|
||||
docker:
|
||||
- image: cimg/node:18.18.2
|
||||
@@ -62,6 +74,7 @@ jobs:
|
||||
to: "s3://imex-online-production/"
|
||||
arguments: "--exclude '*.map'"
|
||||
|
||||
|
||||
imex-app-beta-build:
|
||||
docker:
|
||||
- image: cimg/node:18.18.2
|
||||
@@ -86,6 +99,12 @@ jobs:
|
||||
from: dist
|
||||
to: "s3://imex-online-beta/"
|
||||
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:
|
||||
docker:
|
||||
@@ -99,7 +118,12 @@ jobs:
|
||||
eb status --verbose
|
||||
eb deploy
|
||||
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:
|
||||
docker:
|
||||
- image: cimg/node:18.18.2
|
||||
@@ -114,11 +138,16 @@ jobs:
|
||||
- run:
|
||||
name: Execute migration
|
||||
command: |
|
||||
npm install hasura-cli -g
|
||||
curl -L https://github.com/hasura/graphql-engine/raw/stable/cli/get.sh | bash
|
||||
hasura migrate apply --endpoint https://db.romeonline.io/ --admin-secret << parameters.secret >>
|
||||
hasura metadata apply --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:
|
||||
docker:
|
||||
- image: cimg/node:18.18.2
|
||||
@@ -143,6 +172,12 @@ jobs:
|
||||
from: dist
|
||||
to: "s3://rome-online-production/"
|
||||
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:
|
||||
docker:
|
||||
@@ -168,6 +203,12 @@ jobs:
|
||||
from: dist
|
||||
to: "s3://promanager-production/"
|
||||
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:
|
||||
docker:
|
||||
@@ -183,10 +224,18 @@ jobs:
|
||||
- run:
|
||||
name: Execute migration
|
||||
command: |
|
||||
npm install hasura-cli -g
|
||||
curl -L https://github.com/hasura/graphql-engine/raw/stable/cli/get.sh | bash
|
||||
hasura migrate apply --endpoint https://db.test.romeonline.io/ --admin-secret << parameters.secret >>
|
||||
sleep 5
|
||||
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 >>
|
||||
- jira/notify:
|
||||
environment: Test (Rome) - Hasura
|
||||
environment_type: testing
|
||||
pipeline_id: << pipeline.id >>
|
||||
job_type: deployment
|
||||
pipeline_number: << pipeline.number >>
|
||||
|
||||
test-rome-app-build:
|
||||
docker:
|
||||
@@ -212,6 +261,12 @@ jobs:
|
||||
from: dist
|
||||
to: "s3://rome-online-test/"
|
||||
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:
|
||||
docker:
|
||||
@@ -237,6 +292,12 @@ jobs:
|
||||
from: dist
|
||||
to: "s3://promanager-testing/"
|
||||
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:
|
||||
docker:
|
||||
@@ -252,10 +313,18 @@ jobs:
|
||||
- run:
|
||||
name: Execute migration
|
||||
command: |
|
||||
npm install hasura-cli -g
|
||||
curl -L https://github.com/hasura/graphql-engine/raw/stable/cli/get.sh | bash
|
||||
hasura migrate apply --endpoint https://db.test.bodyshop.app/ --admin-secret << parameters.secret >>
|
||||
sleep 15
|
||||
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 >>
|
||||
- jira/notify:
|
||||
environment: Test (ImEX) - Hasura
|
||||
environment_type: testing
|
||||
pipeline_id: << pipeline.id >>
|
||||
job_type: deployment
|
||||
pipeline_number: << pipeline.number >>
|
||||
|
||||
imex-test-app-build:
|
||||
docker:
|
||||
@@ -302,7 +371,12 @@ jobs:
|
||||
from: dist
|
||||
to: "s3://imex-online-test-beta/"
|
||||
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:
|
||||
docker:
|
||||
@@ -353,7 +427,7 @@ workflows:
|
||||
secret: ${HASURA_PROD_SECRET}
|
||||
filters:
|
||||
branches:
|
||||
only: master
|
||||
only: master-AIO
|
||||
- rome-api-deploy:
|
||||
filters:
|
||||
branches:
|
||||
@@ -363,7 +437,7 @@ workflows:
|
||||
branches:
|
||||
only: master-AIO
|
||||
- rome-hasura-migrate:
|
||||
secret: ${HASURA_PROD_SECRET}
|
||||
secret: ${HASURA_ROME_PROD_SECRET}
|
||||
filters:
|
||||
branches:
|
||||
only: master-AIO
|
||||
|
||||
59
_reference/prHelper.html
Normal file
59
_reference/prHelper.html
Normal file
@@ -0,0 +1,59 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>IMEX IO Extractor</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
padding: 20px;
|
||||
}
|
||||
textarea {
|
||||
width: 100%;
|
||||
height: 200px;
|
||||
}
|
||||
.output-box {
|
||||
margin-top: 20px;
|
||||
padding: 10px;
|
||||
border: 1px solid #ccc;
|
||||
background-color: #f9f9f9;
|
||||
min-height: 40px;
|
||||
}
|
||||
.copy-button {
|
||||
margin-top: 10px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>IMEX IO Extractor</h1>
|
||||
<textarea id="inputText" placeholder="Paste your text here..."></textarea>
|
||||
<br>
|
||||
<button onclick="extractIO()">Extract</button>
|
||||
|
||||
<div class="output-box" id="outputBox" contenteditable="true"></div>
|
||||
<button class="copy-button" onclick="copyToClipboard()">Copy to Clipboard</button>
|
||||
|
||||
<script>
|
||||
function extractIO() {
|
||||
const inputText = document.getElementById('inputText').value;
|
||||
const ioNumbers = [...new Set(inputText.match(/IO-\d{4}/g))] // Extract unique IO-#### matches
|
||||
.map(io => ({ io, num: parseInt(io.split('-')[1]) })) // Extract number part for sorting
|
||||
.sort((a, b) => a.num - b.num) // Sort by the number
|
||||
.map(item => item.io); // Extract sorted IO-####
|
||||
|
||||
document.getElementById('outputBox').innerText = ioNumbers.join(', '); // Display horizontally
|
||||
}
|
||||
|
||||
function copyToClipboard() {
|
||||
const outputBox = document.getElementById('outputBox');
|
||||
const range = document.createRange();
|
||||
range.selectNodeContents(outputBox);
|
||||
const selection = window.getSelection();
|
||||
selection.removeAllRanges();
|
||||
selection.addRange(range);
|
||||
document.execCommand('copy');
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,5 @@
|
||||
VITE_APP_GRAPHQL_ENDPOINT=https://db.dev.bodyshop.app/v1/graphql
|
||||
VITE_APP_GRAPHQL_ENDPOINT_WS=wss://db.dev.bodyshop.app/v1/graphql
|
||||
VITE_APP_GRAPHQL_ENDPOINT=https://db.dev.imex.online/v1/graphql
|
||||
VITE_APP_GRAPHQL_ENDPOINT_WS=wss://db.dev.imex.online/v1/graphql
|
||||
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_CLOUDINARY_ENDPOINT_API=https://api.cloudinary.com/v1_1/io-test
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
VITE_APP_GRAPHQL_ENDPOINT=https://db.dev.bodyshop.app/v1/graphql
|
||||
VITE_APP_GRAPHQL_ENDPOINT_WS=wss://db.dev.bodyshop.app/v1/graphql
|
||||
VITE_APP_GRAPHQL_ENDPOINT=https://db.dev.imex.online/v1/graphql
|
||||
VITE_APP_GRAPHQL_ENDPOINT_WS=wss://db.dev.imex.online/v1/graphql
|
||||
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_CLOUDINARY_ENDPOINT_API=https://api.cloudinary.com/v1_1/io-test
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
VITE_APP_GRAPHQL_ENDPOINT=https://db.dev.bodyshop.app/v1/graphql
|
||||
VITE_APP_GRAPHQL_ENDPOINT_WS=wss://db.dev.bodyshop.app/v1/graphql
|
||||
VITE_APP_GRAPHQL_ENDPOINT=https://db.dev.imex.online/v1/graphql
|
||||
VITE_APP_GRAPHQL_ENDPOINT_WS=wss://db.dev.imex.online/v1/graphql
|
||||
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=https://res.cloudinary.com/io-test
|
||||
VITE_APP_CLOUDINARY_API_KEY=957865933348715
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<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') { %>
|
||||
<link rel="icon" href="/favicon.png"/>
|
||||
<% } %> <% if (env.VITE_APP_INSTANCE === 'ROME') { %>
|
||||
|
||||
5
client/package-lock.json
generated
5
client/package-lock.json
generated
@@ -7,6 +7,7 @@
|
||||
"": {
|
||||
"name": "bodyshop",
|
||||
"version": "0.2.1",
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"@ant-design/pro-layout": "^7.19.12",
|
||||
"@apollo/client": "^3.11.4",
|
||||
@@ -109,7 +110,8 @@
|
||||
"vite-plugin-legacy": "^2.1.0",
|
||||
"vite-plugin-node-polyfills": "^0.22.0",
|
||||
"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": {
|
||||
"node": ">=18.18.2"
|
||||
@@ -18429,6 +18431,7 @@
|
||||
"resolved": "https://registry.npmjs.org/workbox-window/-/workbox-window-7.1.0.tgz",
|
||||
"integrity": "sha512-ZHeROyqR+AS5UPzholQRDttLFqGMwP0Np8MKWAdyxsDETxq3qOAyXvqessc3GniohG6e0mAqSQyKOHmT8zPF7g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/trusted-types": "^2.0.2",
|
||||
"workbox-core": "7.1.0"
|
||||
|
||||
@@ -84,6 +84,7 @@
|
||||
"web-vitals": "^3.5.2"
|
||||
},
|
||||
"scripts": {
|
||||
"postinstall": "echo 'when updating react-big-calendar, remember to check to localizer in the calendar wrapper'",
|
||||
"analyze": "source-map-explorer 'build/static/js/*.js'",
|
||||
"start": "vite",
|
||||
"build": "dotenvx run --env-file=.env.development.imex -- vite build",
|
||||
@@ -153,6 +154,7 @@
|
||||
"vite-plugin-legacy": "^2.1.0",
|
||||
"vite-plugin-node-polyfills": "^0.22.0",
|
||||
"vite-plugin-pwa": "^0.20.1",
|
||||
"vite-plugin-style-import": "^2.0.0"
|
||||
"vite-plugin-style-import": "^2.0.0",
|
||||
"workbox-window": "^7.1.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,6 @@ import { checkUserSession } from "../redux/user/user.actions";
|
||||
import { selectBodyshop, selectCurrentEula, selectCurrentUser } from "../redux/user/user.selectors";
|
||||
import PrivateRoute from "../components/PrivateRoute";
|
||||
import "./App.styles.scss";
|
||||
import handleBeta from "../utils/handleBeta";
|
||||
import Eula from "../components/eula/eula.component";
|
||||
import InstanceRenderMgr from "../utils/instanceRenderMgr";
|
||||
import ProductFruitsWrapper from "./ProductFruitsWrapper.jsx";
|
||||
@@ -108,8 +107,6 @@ export function App({ bodyshop, checkUserSession, currentUser, online, setOnline
|
||||
return <LoadingSpinner message={t("general.labels.loggingin")} />;
|
||||
}
|
||||
|
||||
handleBeta();
|
||||
|
||||
if (!online) {
|
||||
return (
|
||||
<Result
|
||||
|
||||
@@ -98,7 +98,7 @@ export function BillDetailEditcontainer({ setPartsOrderContext, insertAuditTrail
|
||||
});
|
||||
|
||||
billlines.forEach((billline) => {
|
||||
const { deductedfromlbr, inventories, jobline, ...il } = billline;
|
||||
const { deductedfromlbr, inventories, jobline, original_actual_price, create_ppc, ...il } = billline;
|
||||
delete il.__typename;
|
||||
|
||||
if (il.id) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { DeleteFilled } from "@ant-design/icons";
|
||||
import { DeleteFilled, CopyFilled } from "@ant-design/icons";
|
||||
import { useLazyQuery, useMutation } from "@apollo/client";
|
||||
import { Button, Card, Col, Form, Input, Row, Space, Spin, Statistic, notification } from "antd";
|
||||
import { Button, Card, Col, Form, Input, Row, Space, Spin, Statistic, message, notification } from "antd";
|
||||
import axios from "axios";
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
@@ -14,10 +14,12 @@ import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
||||
import CurrencyFormItemComponent from "../form-items-formatted/currency-form-item.component";
|
||||
import JobSearchSelectComponent from "../job-search-select/job-search-select.component";
|
||||
import { getCurrentUser } from "../../firebase/firebase.utils";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
cardPaymentModal: selectCardPayment,
|
||||
bodyshop: selectBodyshop
|
||||
bodyshop: selectBodyshop,
|
||||
currentUser: getCurrentUser
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
@@ -25,11 +27,17 @@ const mapDispatchToProps = (dispatch) => ({
|
||||
toggleModalVisible: () => dispatch(toggleModalVisible("cardPayment"))
|
||||
});
|
||||
|
||||
const CardPaymentModalComponent = ({ bodyshop, cardPaymentModal, toggleModalVisible, insertAuditTrail }) => {
|
||||
const CardPaymentModalComponent = ({
|
||||
bodyshop,
|
||||
currentUser,
|
||||
cardPaymentModal,
|
||||
toggleModalVisible,
|
||||
insertAuditTrail
|
||||
}) => {
|
||||
const { context, actions } = cardPaymentModal;
|
||||
|
||||
const [form] = Form.useForm();
|
||||
|
||||
const [paymentLink, setPaymentLink] = useState();
|
||||
const [loading, setLoading] = useState(false);
|
||||
// const [insertPayment] = useMutation(INSERT_NEW_PAYMENT);
|
||||
const [insertPaymentResponse] = useMutation(INSERT_PAYMENT_RESPONSE);
|
||||
@@ -37,7 +45,7 @@ const CardPaymentModalComponent = ({ bodyshop, cardPaymentModal, toggleModalVisi
|
||||
|
||||
const [, { data, refetch, queryLoading }] = useLazyQuery(QUERY_RO_AND_OWNER_BY_JOB_PKS, {
|
||||
variables: { jobids: [context.jobid] },
|
||||
skip: true
|
||||
skip: !context?.jobid
|
||||
});
|
||||
|
||||
//Initialize the intellipay window.
|
||||
@@ -51,8 +59,7 @@ const CardPaymentModalComponent = ({ bodyshop, cardPaymentModal, toggleModalVisi
|
||||
//2024-04-25: Nothing is going to happen here anymore. We'll completely rely on the callback.
|
||||
//Add a slight delay to allow the refetch to properly get the data.
|
||||
setTimeout(() => {
|
||||
if (actions && actions.refetch && typeof actions.refetch === "function")
|
||||
actions.refetch();
|
||||
if (actions && actions.refetch && typeof actions.refetch === "function") actions.refetch();
|
||||
setLoading(false);
|
||||
toggleModalVisible();
|
||||
}, 750);
|
||||
@@ -86,7 +93,6 @@ const CardPaymentModalComponent = ({ bodyshop, cardPaymentModal, toggleModalVisi
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
const handleIntelliPayCharge = async () => {
|
||||
setLoading(true);
|
||||
//Validate
|
||||
@@ -101,7 +107,7 @@ const CardPaymentModalComponent = ({ bodyshop, cardPaymentModal, toggleModalVisi
|
||||
const response = await axios.post("/intellipay/lightbox_credentials", {
|
||||
bodyshop,
|
||||
refresh: !!window.intellipay,
|
||||
paymentSplitMeta: form.getFieldsValue(),
|
||||
paymentSplitMeta: form.getFieldsValue()
|
||||
});
|
||||
|
||||
if (window.intellipay) {
|
||||
@@ -126,6 +132,42 @@ const CardPaymentModalComponent = ({ bodyshop, cardPaymentModal, toggleModalVisi
|
||||
}
|
||||
};
|
||||
|
||||
const handleIntelliPayChargeShortLink = async () => {
|
||||
setLoading(true);
|
||||
//Validate
|
||||
try {
|
||||
await form.validateFields();
|
||||
} catch (error) {
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const { payments } = form.getFieldsValue();
|
||||
const response = await axios.post("/intellipay/generate_payment_url", {
|
||||
bodyshop,
|
||||
amount: payments?.reduce((acc, val) => {
|
||||
return acc + (val?.amount || 0);
|
||||
}, 0),
|
||||
account: payments && data && data.jobs.length > 0 ? data.jobs.map((j) => j.ro_number).join(", ") : null,
|
||||
comment: btoa(JSON.stringify({ payments, userEmail: currentUser.email })),
|
||||
paymentSplitMeta: form.getFieldsValue()
|
||||
});
|
||||
if (response.data) {
|
||||
setPaymentLink(response.data?.shorUrl);
|
||||
navigator.clipboard.writeText(response.data?.shorUrl);
|
||||
message.success(t("general.actions.copied"));
|
||||
}
|
||||
setLoading(false);
|
||||
} catch (error) {
|
||||
notification.open({
|
||||
type: "error",
|
||||
message: t("job_payments.notifications.error.openingip")
|
||||
});
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Card title="Card Payment">
|
||||
<Spin spinning={loading}>
|
||||
@@ -202,16 +244,14 @@ const CardPaymentModalComponent = ({ bodyshop, cardPaymentModal, toggleModalVisi
|
||||
|
||||
<Form.Item
|
||||
shouldUpdate={(prevValues, curValues) =>
|
||||
prevValues.payments?.map((p) => p?.jobid).join() !== curValues.payments?.map((p) => p?.jobid).join()
|
||||
prevValues.payments?.map((p) => p?.jobid + p?.amount).join() !==
|
||||
curValues.payments?.map((p) => p?.jobid + p?.amount).join()
|
||||
}
|
||||
>
|
||||
{() => {
|
||||
//If all of the job ids have been fileld in, then query and update the IP field.
|
||||
const { payments } = form.getFieldsValue();
|
||||
if (
|
||||
payments?.length > 0 &&
|
||||
payments?.filter((p) => p?.jobid).length === payments?.length
|
||||
) {
|
||||
if (payments?.length > 0 && payments?.filter((p) => p?.jobid).length === payments?.length) {
|
||||
refetch({ jobids: payments.map((p) => p.jobid) });
|
||||
}
|
||||
return (
|
||||
@@ -246,7 +286,6 @@ const CardPaymentModalComponent = ({ bodyshop, cardPaymentModal, toggleModalVisi
|
||||
const totalAmountToCharge = payments?.reduce((acc, val) => {
|
||||
return acc + (val?.amount || 0);
|
||||
}, 0);
|
||||
|
||||
return (
|
||||
<Space style={{ float: "right" }}>
|
||||
<Statistic title="Amount To Charge" value={totalAmountToCharge} precision={2} />
|
||||
@@ -273,11 +312,36 @@ const CardPaymentModalComponent = ({ bodyshop, cardPaymentModal, toggleModalVisi
|
||||
>
|
||||
{t("job_payments.buttons.proceedtopayment")}
|
||||
</Button>
|
||||
<Space direction="vertical" align="center">
|
||||
<Button
|
||||
type="primary"
|
||||
// data-ipayname="submit"
|
||||
className="ipayfield"
|
||||
loading={queryLoading || loading}
|
||||
disabled={!(totalAmountToCharge > 0)}
|
||||
onClick={handleIntelliPayChargeShortLink}
|
||||
>
|
||||
{t("job_payments.buttons.create_short_link")}
|
||||
</Button>
|
||||
</Space>
|
||||
</Space>
|
||||
);
|
||||
}}
|
||||
</Form.Item>
|
||||
</Form>
|
||||
{paymentLink && (
|
||||
<Space
|
||||
style={{ cursor: "pointer", float: "right" }}
|
||||
align="end"
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(paymentLink);
|
||||
message.success(t("general.actions.copied"));
|
||||
}}
|
||||
>
|
||||
<div>{paymentLink}</div>
|
||||
<CopyFilled />
|
||||
</Space>
|
||||
)}
|
||||
</Spin>
|
||||
</Card>
|
||||
);
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
import React, { useCallback, useState } from "react";
|
||||
import { DatePicker } from "antd";
|
||||
import dayjs from "../../utils/day";
|
||||
import { formats, shorthandFormats } from "./formats.js";
|
||||
import PropTypes from "prop-types";
|
||||
import React, { useCallback, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import dayjs from "../../utils/day";
|
||||
import { fuzzyMatchDate } from "./formats.js";
|
||||
|
||||
const DateTimePicker = ({ value, onChange, onBlur, id, onlyFuture, onlyToday, isDateOnly = false, ...restProps }) => {
|
||||
const [isManualInput, setIsManualInput] = useState(false);
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleChange = useCallback(
|
||||
(newDate) => {
|
||||
if (newDate === null && onChange) {
|
||||
onChange(null);
|
||||
} else if (newDate && onChange) {
|
||||
onChange(newDate);
|
||||
if (onChange) {
|
||||
onChange(newDate || null);
|
||||
}
|
||||
setIsManualInput(false);
|
||||
},
|
||||
@@ -21,57 +21,24 @@ const DateTimePicker = ({ value, onChange, onBlur, id, onlyFuture, onlyToday, is
|
||||
|
||||
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;
|
||||
const v = e?.target?.value;
|
||||
|
||||
if (!v) return;
|
||||
|
||||
const upperV = v.toUpperCase();
|
||||
let parsedDate = isDateOnly ? fuzzyMatchDate(v)?.startOf("day") : fuzzyMatchDate(v);
|
||||
|
||||
let _a;
|
||||
|
||||
for (const format of shorthandFormats) {
|
||||
_a = dayjs(upperV, format);
|
||||
if (_a.isValid()) break;
|
||||
}
|
||||
|
||||
if (!_a || !_a.isValid()) {
|
||||
for (const format of formats) {
|
||||
_a = dayjs(upperV, format);
|
||||
if (_a.isValid()) break;
|
||||
}
|
||||
}
|
||||
|
||||
if (_a && _a.isValid()) {
|
||||
if (isDateOnly) {
|
||||
_a = _a.startOf("day");
|
||||
}
|
||||
|
||||
if (value && value.isValid && value.isValid()) {
|
||||
_a.set({
|
||||
hours: value.hours(),
|
||||
minutes: value.minutes(),
|
||||
seconds: value.seconds(),
|
||||
milliseconds: value.milliseconds()
|
||||
});
|
||||
}
|
||||
|
||||
if (onlyFuture) {
|
||||
if (dayjs().subtract(1, "day").isBefore(_a)) {
|
||||
onChange(_a);
|
||||
} else {
|
||||
onChange(dayjs().startOf("day"));
|
||||
}
|
||||
} else {
|
||||
onChange(_a);
|
||||
}
|
||||
if (parsedDate && onChange) {
|
||||
onChange(parsedDate);
|
||||
}
|
||||
},
|
||||
[isManualInput, isDateOnly, onlyFuture, onChange, value]
|
||||
[isManualInput, isDateOnly, onChange]
|
||||
);
|
||||
|
||||
const handleKeyDown = useCallback(
|
||||
@@ -79,6 +46,7 @@ const DateTimePicker = ({ value, onChange, onBlur, id, onlyFuture, onlyToday, is
|
||||
setIsManualInput(true);
|
||||
|
||||
if (e.key.toLowerCase() === "t" && onChange) {
|
||||
e.preventDefault();
|
||||
setIsManualInput(false);
|
||||
onChange(dayjs());
|
||||
} else if (e.key.toLowerCase() === "enter") {
|
||||
@@ -115,6 +83,7 @@ const DateTimePicker = ({ value, onChange, onBlur, id, onlyFuture, onlyToday, is
|
||||
format={isDateOnly ? "MM/DD/YYYY" : "MM/DD/YYYY hh:mm a"}
|
||||
value={value ? dayjs(value) : null}
|
||||
onChange={handleChange}
|
||||
placeholder={isDateOnly ? t("general.labels.date") : t("general.labels.datetime")}
|
||||
onBlur={onBlur || handleBlur}
|
||||
disabledDate={handleDisabledDate}
|
||||
{...restProps}
|
||||
|
||||
@@ -1,93 +1,63 @@
|
||||
export const shorthandFormats = [
|
||||
"M/D/YY hA",
|
||||
"M/D/YY h:mmA",
|
||||
"M/D/YYYY hA",
|
||||
"M/D/YYYY h:mmA",
|
||||
"M/D/YY ha",
|
||||
"M/D/YY h:mma",
|
||||
"M/D/YYYY ha",
|
||||
"M/D/YYYY h:mma"
|
||||
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"
|
||||
];
|
||||
|
||||
export 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",
|
||||
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",
|
||||
"D/MM/YY",
|
||||
"D/MM/YYYY",
|
||||
"DD/M/YY",
|
||||
"DD/M/YYYY",
|
||||
"D/M/YY",
|
||||
"D/M/YYYY",
|
||||
"MMDDYY hh:mm A",
|
||||
"MMDDYYYY hh:mm A",
|
||||
"MM/DD/YY hh:mm A",
|
||||
"MM/DD/YYYY hh:mm A",
|
||||
"M/DD/YY hh:mm A",
|
||||
"M/DD/YYYY hh:mm A",
|
||||
"MM/D/YY hh:mm A",
|
||||
"MM/D/YYYY hh:mm A",
|
||||
"M/D/YY hh:mm A",
|
||||
"M/D/YYYY hh:mm A",
|
||||
"D/MM/YY hh:mm A",
|
||||
"D/MM/YYYY hh:mm A",
|
||||
"DD/M/YY hh:mm A",
|
||||
"DD/M/YYYY hh:mm A",
|
||||
"D/M/YY hh:mm A",
|
||||
"D/M/YYYY hh:mm A",
|
||||
"MMDDYY hh:mm:ss A",
|
||||
"MMDDYYYY hh:mm:ss A",
|
||||
"MM/DD/YY hh:mm:ss A",
|
||||
"MM/DD/YYYY hh:mm:ss A",
|
||||
"M/DD/YY hh:mm:ss A",
|
||||
"M/DD/YYYY hh:mm:ss A",
|
||||
"MM/D/YY hh:mm:ss A",
|
||||
"MM/D/YYYY hh:mm:ss A",
|
||||
"M/D/YY hh:mm:ss A",
|
||||
"M/D/YYYY hh:mm:ss A",
|
||||
"D/MM/YY hh:mm:ss A",
|
||||
"D/MM/YYYY hh:mm:ss A",
|
||||
"DD/M/YY hh:mm:ss A",
|
||||
"DD/M/YYYY hh:mm:ss A",
|
||||
"D/M/YY hh:mm:ss A",
|
||||
"D/M/YYYY hh:mm:ss A",
|
||||
"MMDDYY HH:mm",
|
||||
"MMDDYYYY HH:mm",
|
||||
"MM/DD/YY HH:mm",
|
||||
"MM/DD/YYYY HH:mm",
|
||||
"M/DD/YY HH:mm",
|
||||
"M/DD/YYYY HH:mm",
|
||||
"MM/D/YY HH:mm",
|
||||
"MM/D/YYYY HH:mm",
|
||||
"M/D/YY HH:mm",
|
||||
"M/D/YYYY HH:mm",
|
||||
"D/MM/YY HH:mm",
|
||||
"D/MM/YYYY HH:mm",
|
||||
"DD/M/YY HH:mm",
|
||||
"DD/M/YYYY HH:mm",
|
||||
"D/M/YY HH:mm",
|
||||
"D/M/YYYY HH:mm",
|
||||
"MMDDYY HH:mm:ss",
|
||||
"MMDDYYYY HH:mm:ss",
|
||||
"MM/DD/YY HH:mm:ss",
|
||||
"MM/DD/YYYY HH:mm:ss",
|
||||
"M/DD/YY HH:mm:ss",
|
||||
"M/DD/YYYY HH:mm:ss",
|
||||
"MM/D/YY HH:mm:ss",
|
||||
"MM/D/YYYY HH:mm:ss",
|
||||
"M/D/YY HH:mm:ss",
|
||||
"M/D/YYYY HH:mm:ss",
|
||||
"D/MM/YY HH:mm:ss",
|
||||
"D/MM/YYYY HH:mm:ss",
|
||||
"DD/M/YY HH:mm:ss",
|
||||
"DD/M/YYYY HH:mm:ss",
|
||||
"D/M/YY HH:mm:ss",
|
||||
"D/M/YYYY HH:mm:ss"
|
||||
"MM/D/YYYY",
|
||||
"M/DD/YYYY",
|
||||
"MM/DD/YYYY",
|
||||
"M/D/YY",
|
||||
"MM/D/YY",
|
||||
"M/DD/YY",
|
||||
"MM/DD/YY",
|
||||
"MMDDYYYY",
|
||||
"MMDDYY"
|
||||
];
|
||||
|
||||
const sanitizeInput = (input) =>
|
||||
input
|
||||
.trim()
|
||||
.toUpperCase()
|
||||
.replace(/\s*(am|pm)\s*/i, " $1")
|
||||
.replaceAll(".", "/")
|
||||
.replaceAll("-", "/");
|
||||
|
||||
export const fuzzyMatchDate = (dateString) => {
|
||||
const sanitizedInput = sanitizeInput(dateString);
|
||||
|
||||
for (const format of dateFormats) {
|
||||
const parsedDate = dayjs(sanitizedInput, format, true);
|
||||
if (parsedDate.isValid()) {
|
||||
return parsedDate;
|
||||
}
|
||||
}
|
||||
|
||||
for (const format of dateTimeFormats) {
|
||||
const parsedDateTime = dayjs(sanitizedInput, format, true);
|
||||
if (parsedDateTime.isValid()) {
|
||||
return parsedDateTime; // Return the dayjs object
|
||||
}
|
||||
}
|
||||
|
||||
return null; // If no matching format is found
|
||||
};
|
||||
|
||||
@@ -13,7 +13,6 @@ import Icon, {
|
||||
FileFilled,
|
||||
HomeFilled,
|
||||
ImportOutlined,
|
||||
InfoCircleOutlined,
|
||||
LineChartOutlined,
|
||||
PaperClipOutlined,
|
||||
PhoneOutlined,
|
||||
@@ -27,8 +26,8 @@ import Icon, {
|
||||
UserOutlined
|
||||
} from "@ant-design/icons";
|
||||
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
||||
import { Layout, Menu, Switch, Tooltip } from "antd";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { Layout, Menu } from "antd";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { BsKanban } from "react-icons/bs";
|
||||
import { FaCalendarAlt, FaCarCrash, FaCreditCard, FaFileInvoiceDollar, FaTasks } from "react-icons/fa";
|
||||
@@ -43,7 +42,6 @@ import { selectRecentItems, selectSelectedHeader } from "../../redux/application
|
||||
import { setModalContext } from "../../redux/modals/modals.actions";
|
||||
import { signOutStart } from "../../redux/user/user.actions";
|
||||
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors";
|
||||
import { checkBeta, handleBeta, setBeta } from "../../utils/handleBeta";
|
||||
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
||||
import { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component";
|
||||
|
||||
@@ -115,20 +113,22 @@ function Header({
|
||||
names: ["ImEXPay", "DmsAp", "Simple_Inventory"],
|
||||
splitKey: bodyshop && bodyshop.imexshopid
|
||||
});
|
||||
const [betaSwitch, setBetaSwitch] = useState(false);
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
useEffect(() => {
|
||||
const isBeta = checkBeta();
|
||||
setBetaSwitch(isBeta);
|
||||
}, []);
|
||||
|
||||
const betaSwitchChange = (checked) => {
|
||||
setBeta(checked);
|
||||
setBetaSwitch(checked);
|
||||
handleBeta();
|
||||
const deleteBetaCookie = () => {
|
||||
const cookieExists = document.cookie.split("; ").some((row) => row.startsWith(`betaSwitchImex=`));
|
||||
if (cookieExists) {
|
||||
const domain = window.location.hostname.split(".").slice(-2).join(".");
|
||||
document.cookie = `betaSwitchImex=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/; domain=.${domain}`;
|
||||
console.log(`betaSwitchImex cookie deleted`);
|
||||
} else {
|
||||
console.log(`betaSwitchImex cookie does not exist`);
|
||||
}
|
||||
};
|
||||
|
||||
deleteBetaCookie();
|
||||
|
||||
const accountingChildren = [];
|
||||
|
||||
if (
|
||||
@@ -695,31 +695,6 @@ function Header({
|
||||
}
|
||||
];
|
||||
|
||||
InstanceRenderManager({
|
||||
executeFunction: true,
|
||||
args: [],
|
||||
imex: () => {
|
||||
menuItems.push({
|
||||
key: "beta-switch",
|
||||
id: "header-beta-switch",
|
||||
style: { marginLeft: "auto" },
|
||||
label: (
|
||||
<Tooltip
|
||||
title={`A more modern ${InstanceRenderManager({
|
||||
imex: t("titles.imexonline"),
|
||||
rome: t("titles.romeonline"),
|
||||
promanager: t("titles.promanager")
|
||||
})} is ready for you to try! You can switch back at any time.`}
|
||||
>
|
||||
<InfoCircleOutlined />
|
||||
<span style={{ marginRight: 8 }}>Try the new app</span>
|
||||
<Switch checked={betaSwitch} onChange={betaSwitchChange} />
|
||||
</Tooltip>
|
||||
)
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<Layout.Header>
|
||||
<Menu
|
||||
|
||||
@@ -141,10 +141,14 @@ export function JobTotalsTableTotals({ bodyshop, job }) {
|
||||
key: t("jobs.fields.ded_amt"),
|
||||
total: job.job_totals.totals.custPayable.deductible
|
||||
},
|
||||
// {
|
||||
// key: t("jobs.fields.federal_tax_payable"),
|
||||
// total: job.job_totals.totals.custPayable.federal_tax,
|
||||
// },
|
||||
...(InstanceRenderManager({
|
||||
imex: [{
|
||||
key: t("jobs.fields.federal_tax_payable"),
|
||||
total: job.job_totals.totals.custPayable.federal_tax
|
||||
}],
|
||||
rome: [],
|
||||
promanager: "USE_ROME"
|
||||
})),
|
||||
{
|
||||
key: t("jobs.fields.other_amount_payable"),
|
||||
total: job.job_totals.totals.custPayable.other_customer_amount
|
||||
|
||||
@@ -250,8 +250,8 @@ export function JobsList({ bodyshop }) {
|
||||
},
|
||||
{
|
||||
title: t("jobs.labels.estimator"),
|
||||
dataIndex: "jobs.labels.estimator",
|
||||
key: "jobs.labels.estimator",
|
||||
dataIndex: "estimator",
|
||||
key: "estimator",
|
||||
ellipsis: true,
|
||||
responsive: ["xl"],
|
||||
sorter: (a, b) =>
|
||||
|
||||
@@ -27,6 +27,10 @@ export default function PartsOrderModalPriceChange({ form, field }) {
|
||||
key: "25",
|
||||
label: t("parts_orders.labels.discount", { percent: "25%" })
|
||||
},
|
||||
{
|
||||
key: "40",
|
||||
label: t("parts_orders.labels.discount", { percent: "40%" })
|
||||
},
|
||||
{
|
||||
key: "custom",
|
||||
label: (
|
||||
|
||||
@@ -8,11 +8,12 @@ import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { openChatByPhone, setMessage } from "../../redux/messaging/messaging.actions";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors";
|
||||
import CurrencyFormItemComponent from "../form-items-formatted/currency-form-item.component";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop
|
||||
bodyshop: selectBodyshop,
|
||||
currentUser: selectCurrentUser
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
openChatByPhone: (phone) => dispatch(openChatByPhone(phone)),
|
||||
@@ -20,7 +21,7 @@ const mapDispatchToProps = (dispatch) => ({
|
||||
});
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(PaymentsGenerateLink);
|
||||
|
||||
export function PaymentsGenerateLink({ bodyshop, callback, job, openChatByPhone, setMessage }) {
|
||||
export function PaymentsGenerateLink({ bodyshop, currentUser, callback, job, openChatByPhone, setMessage }) {
|
||||
const { t } = useTranslation();
|
||||
const [form] = Form.useForm();
|
||||
|
||||
@@ -30,29 +31,35 @@ export function PaymentsGenerateLink({ bodyshop, callback, job, openChatByPhone,
|
||||
|
||||
const handleFinish = async ({ amount }) => {
|
||||
setLoading(true);
|
||||
|
||||
const p = parsePhoneNumber(job.ownr_ph1, "CA");
|
||||
let p;
|
||||
try {
|
||||
p = parsePhoneNumber(job.ownr_ph1 || "", "CA");
|
||||
} catch (error) {
|
||||
console.log("Unable to parse phone number");
|
||||
}
|
||||
setLoading(true);
|
||||
const response = await axios.post("/intellipay/generate_payment_url", {
|
||||
bodyshop,
|
||||
amount: amount,
|
||||
account: job.ro_number,
|
||||
invoice: job.id
|
||||
comment: btoa(JSON.stringify({ payments: [{ jobid: job.id, amount }], userEmail: currentUser.email }))
|
||||
});
|
||||
setLoading(false);
|
||||
setPaymentLink(response.data.shorUrl);
|
||||
|
||||
openChatByPhone({
|
||||
phone_num: p.formatInternational(),
|
||||
jobid: job.id
|
||||
});
|
||||
setMessage(
|
||||
t("payments.labels.smspaymentreminder", {
|
||||
shopname: bodyshop.shopname,
|
||||
amount: amount,
|
||||
payment_link: response.data.shorUrl
|
||||
})
|
||||
);
|
||||
if (p) {
|
||||
openChatByPhone({
|
||||
phone_num: p.formatInternational(),
|
||||
jobid: job.id
|
||||
});
|
||||
setMessage(
|
||||
t("payments.labels.smspaymentreminder", {
|
||||
shopname: bodyshop.shopname,
|
||||
amount: amount,
|
||||
payment_link: response.data.shorUrl
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
//Add in confirmation & errors.
|
||||
if (callback) callback();
|
||||
|
||||
@@ -6,11 +6,11 @@ import {
|
||||
PauseCircleOutlined
|
||||
} from "@ant-design/icons";
|
||||
import { Card, Col, Row, Space, Tooltip } from "antd";
|
||||
import Dinero from "dinero.js";
|
||||
import React, { useMemo } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Link } from "react-router-dom";
|
||||
import { DateTimeFormatter } from "../../utils/DateFormatter";
|
||||
import Dinero from "dinero.js";
|
||||
|
||||
import ProductionAlert from "../production-list-columns/production-list-columns.alert.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 OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
|
||||
import JobPartsQueueCount from "../job-parts-queue-count/job-parts-queue-count.component";
|
||||
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
|
||||
|
||||
const cardColor = (ssbuckets, totalHrs) => {
|
||||
const bucket = ssbuckets.find((bucket) => bucket.gte <= totalHrs && (!bucket.lt || bucket.lt > totalHrs));
|
||||
@@ -213,21 +213,13 @@ const EstimatorToolTip = ({ metadata, cardSettings }) => {
|
||||
};
|
||||
|
||||
const SubtotalTooltip = ({ metadata, cardSettings, t }) => {
|
||||
const amount = metadata?.job_totals?.totals?.subtotal?.amount;
|
||||
const dineroAmount = amount ? Dinero({ amount: parseInt(amount * 100) }).toFormat("0,0.00") : null;
|
||||
const dineroAmount = Dinero(metadata?.job_totals?.totals?.subtotal ?? Dinero()).toFormat();
|
||||
|
||||
return (
|
||||
cardSettings?.subtotal && (
|
||||
<Col span={cardSettings.compact ? 24 : 12}>
|
||||
<EllipsesToolTip
|
||||
title={!!amount ? `${t("production.statistics.currency_symbol")}${dineroAmount}` : null}
|
||||
kiosk={cardSettings.kiosk}
|
||||
>
|
||||
{!!amount ? (
|
||||
<span>{`${t("production.statistics.currency_symbol")}${dineroAmount}`}</span>
|
||||
) : (
|
||||
<span> </span>
|
||||
)}
|
||||
<EllipsesToolTip title={`${dineroAmount}`} kiosk={cardSettings.kiosk}>
|
||||
{dineroAmount}
|
||||
</EllipsesToolTip>
|
||||
</Col>
|
||||
)
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
import React, { useEffect, useMemo } from "react";
|
||||
import React, { useEffect, useMemo, useRef } from "react";
|
||||
import { useQuery, useSubscription } from "@apollo/client";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { QUERY_JOBS_IN_PRODUCTION, SUBSCRIPTION_JOBS_IN_PRODUCTION } from "../../graphql/jobs.queries";
|
||||
import {
|
||||
QUERY_JOBS_IN_PRODUCTION,
|
||||
SUBSCRIPTION_JOBS_IN_PRODUCTION,
|
||||
SUBSCRIPTION_JOBS_IN_PRODUCTION_VIEW
|
||||
} from "../../graphql/jobs.queries";
|
||||
import { QUERY_KANBAN_SETTINGS } from "../../graphql/user.queries";
|
||||
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors";
|
||||
import ProductionBoardKanbanComponent from "./production-board-kanban.component";
|
||||
@@ -12,7 +16,9 @@ const mapStateToProps = createStructuredSelector({
|
||||
currentUser: selectCurrentUser
|
||||
});
|
||||
|
||||
function ProductionBoardKanbanContainer({ bodyshop, currentUser }) {
|
||||
function ProductionBoardKanbanContainer({ bodyshop, currentUser, subscriptionType = "direct" }) {
|
||||
const fired = useRef(false); // useRef to keep track of whether the subscription fired
|
||||
|
||||
const combinedStatuses = useMemo(
|
||||
() => [
|
||||
...bodyshop.md_ro_statuses.production_statuses,
|
||||
@@ -28,9 +34,12 @@ function ProductionBoardKanbanContainer({ bodyshop, currentUser }) {
|
||||
onError: (error) => console.error(`Error fetching jobs in production: ${error.message}`)
|
||||
});
|
||||
|
||||
const { data: updatedJobs } = useSubscription(SUBSCRIPTION_JOBS_IN_PRODUCTION, {
|
||||
onError: (error) => console.error(`Error subscribing to jobs in production: ${error.message}`)
|
||||
});
|
||||
const { data: updatedJobs } = useSubscription(
|
||||
subscriptionType === "view" ? SUBSCRIPTION_JOBS_IN_PRODUCTION_VIEW : SUBSCRIPTION_JOBS_IN_PRODUCTION,
|
||||
{
|
||||
onError: (error) => console.error(`Error subscribing to jobs in production: ${error.message}`)
|
||||
}
|
||||
);
|
||||
|
||||
const { loading: associationSettingsLoading, data: associationSettings } = useQuery(QUERY_KANBAN_SETTINGS, {
|
||||
variables: { email: currentUser.email },
|
||||
@@ -40,10 +49,15 @@ function ProductionBoardKanbanContainer({ bodyshop, currentUser }) {
|
||||
// const currentReducerData = useSelector((state) => (state.trello.lanes ? state.trello : {}));
|
||||
|
||||
useEffect(() => {
|
||||
if (updatedJobs && data) {
|
||||
refetch().catch((err) => console.error(`Error re-fetching jobs in production: ${err.message}`));
|
||||
if (!updatedJobs) {
|
||||
return;
|
||||
}
|
||||
}, [updatedJobs, data, refetch]);
|
||||
if (!fired.current) {
|
||||
fired.current = true;
|
||||
return;
|
||||
}
|
||||
refetch().catch((err) => console.error(`Error re-fetching jobs in production: ${err.message}`));
|
||||
}, [updatedJobs, refetch]);
|
||||
|
||||
const filteredAssociationSettings = useMemo(() => {
|
||||
return associationSettings?.associations[0] || null;
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Card, Statistic } from "antd";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import PropTypes from "prop-types";
|
||||
import { defaultKanbanSettings, statisticsItems } from "./settings/defaultKanbanSettings.js";
|
||||
import Dinero from "dinero.js";
|
||||
|
||||
export const StatisticType = {
|
||||
HOURS: "hours",
|
||||
@@ -32,7 +33,21 @@ const ProductionStatistics = ({ data, cardSettings, reducerData }) => {
|
||||
};
|
||||
|
||||
const calculateTotalAmount = (items, key) => {
|
||||
return items.reduce((acc, item) => acc + (item[key]?.totals?.subtotal?.amount || 0), 0);
|
||||
return items.reduce((acc, item) => acc.add(Dinero(item[key]?.totals?.subtotal ?? Dinero())), Dinero({ amount: 0 }));
|
||||
};
|
||||
|
||||
const calculateReducerTotalAmount = (lanes, key) => {
|
||||
return lanes.reduce(
|
||||
(acc, lane) => {
|
||||
return acc.add(
|
||||
lane.cards.reduce(
|
||||
(laneAcc, card) => laneAcc.add(Dinero(card.metadata[key]?.totals?.subtotal ?? Dinero())),
|
||||
Dinero({ amount: 0 })
|
||||
)
|
||||
);
|
||||
},
|
||||
Dinero({ amount: 0 })
|
||||
);
|
||||
};
|
||||
|
||||
const calculateReducerTotal = (lanes, key, subKey) => {
|
||||
@@ -43,14 +58,6 @@ const ProductionStatistics = ({ data, cardSettings, reducerData }) => {
|
||||
}, 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) => {
|
||||
if (type === StatisticType.JOBS) {
|
||||
return value.toFixed(0);
|
||||
@@ -87,9 +94,15 @@ const ProductionStatistics = ({ data, cardSettings, reducerData }) => {
|
||||
const totalAmountInProduction = useMemo(() => {
|
||||
if (!cardSettings.totalAmountInProduction) return null;
|
||||
const total = calculateTotalAmount(data, "job_totals");
|
||||
return parseFloat(total.toFixed(2));
|
||||
return total.toFormat("$0,0.00");
|
||||
}, [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(() => {
|
||||
if (!reducerData || !cardSettings.totalHrsOnBoard) return null;
|
||||
const total =
|
||||
@@ -118,12 +131,6 @@ const ProductionStatistics = ({ data, cardSettings, reducerData }) => {
|
||||
[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(() => {
|
||||
if (!data || !cardSettings.tasksInProduction) return null;
|
||||
return data.reduce((acc, item) => acc + (item.tasks_aggregate?.aggregate?.count || 0), 0);
|
||||
@@ -191,7 +198,6 @@ const ProductionStatistics = ({ data, cardSettings, reducerData }) => {
|
||||
<Statistic
|
||||
title={t(`production.statistics.${stat.label}`)}
|
||||
value={formatValue(stat.value, stat.type)}
|
||||
prefix={stat.type === StatisticType.AMOUNT ? t("production.statistics.currency_symbol") : undefined}
|
||||
suffix={
|
||||
stat.type === StatisticType.HOURS
|
||||
? t("production.statistics.hours")
|
||||
|
||||
@@ -28,6 +28,11 @@ import ProductionListColumnCategory from "./production-list-columns.status.categ
|
||||
import ProductionListColumnStatus from "./production-list-columns.status.component";
|
||||
import 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 { Enhanced_Payroll } = treatments;
|
||||
return [
|
||||
@@ -293,6 +298,16 @@ const r = ({ technician, state, activeStatuses, data, bodyshop, refetch, treatme
|
||||
ellipsis: true,
|
||||
sorter: (a, b) => statusSort(a.status, b.status, activeStatuses),
|
||||
sortOrder: state.sortedInfo.columnKey === "status" && state.sortedInfo.order,
|
||||
filters:
|
||||
activeStatuses
|
||||
?.map((s) => {
|
||||
return {
|
||||
text: s || "No Status*",
|
||||
value: [s]
|
||||
};
|
||||
})
|
||||
.sort((a, b) => statusSort(a.text, b.text, activeStatuses)) || [],
|
||||
onFilter: (value, record) => value.includes(record.status),
|
||||
render: (text, record) => <ProductionListColumnStatus record={record} />
|
||||
},
|
||||
{
|
||||
@@ -426,8 +441,8 @@ const r = ({ technician, state, activeStatuses, data, bodyshop, refetch, treatme
|
||||
sortOrder: state.sortedInfo.columnKey === "employee_body" && state.sortedInfo.order,
|
||||
sorter: (a, b) =>
|
||||
alphaSort(
|
||||
bodyshop.employees?.find((e) => e.id === a.employee_body)?.first_name,
|
||||
bodyshop.employees?.find((e) => e.id === b.employee_body)?.first_name
|
||||
getEmployeeName(a.employee_body, bodyshop.employees),
|
||||
getEmployeeName(b.employee_body, bodyshop.employees)
|
||||
),
|
||||
render: (text, record) => (
|
||||
<ProductionListEmployeeAssignment refetch={refetch} record={record} type="employee_body" />
|
||||
@@ -440,8 +455,8 @@ const r = ({ technician, state, activeStatuses, data, bodyshop, refetch, treatme
|
||||
sortOrder: state.sortedInfo.columnKey === "employee_prep" && state.sortedInfo.order,
|
||||
sorter: (a, b) =>
|
||||
alphaSort(
|
||||
bodyshop.employees?.find((e) => e.id === a.employee_prep)?.first_name,
|
||||
bodyshop.employees?.find((e) => e.id === b.employee_prep)?.first_name
|
||||
getEmployeeName(a.employee_prep, bodyshop.employees),
|
||||
getEmployeeName(b.employee_prep, bodyshop.employees)
|
||||
),
|
||||
render: (text, record) => (
|
||||
<ProductionListEmployeeAssignment record={record} refetch={refetch} type="employee_prep" />
|
||||
@@ -460,8 +475,8 @@ const r = ({ technician, state, activeStatuses, data, bodyshop, refetch, treatme
|
||||
sortOrder: state.sortedInfo.columnKey === "employee_csr" && state.sortedInfo.order,
|
||||
sorter: (a, b) =>
|
||||
alphaSort(
|
||||
bodyshop.employees?.find((e) => e.id === a.employee_csr)?.first_name,
|
||||
bodyshop.employees?.find((e) => e.id === b.employee_csr)?.first_name
|
||||
getEmployeeName(a.employee_csr, bodyshop.employees),
|
||||
getEmployeeName(b.employee_csr, bodyshop.employees)
|
||||
),
|
||||
render: (text, record) => (
|
||||
<ProductionListEmployeeAssignment refetch={refetch} record={record} type="employee_csr" />
|
||||
@@ -474,8 +489,8 @@ const r = ({ technician, state, activeStatuses, data, bodyshop, refetch, treatme
|
||||
sortOrder: state.sortedInfo.columnKey === "employee_refinish" && state.sortedInfo.order,
|
||||
sorter: (a, b) =>
|
||||
alphaSort(
|
||||
bodyshop.employees?.find((e) => e.id === a.employee_refinish)?.first_name,
|
||||
bodyshop.employees?.find((e) => e.id === b.employee_refinish)?.first_name
|
||||
getEmployeeName(a.employee_refinish, bodyshop.employees),
|
||||
getEmployeeName(b.employee_refinish, bodyshop.employees)
|
||||
),
|
||||
render: (text, record) => (
|
||||
<ProductionListEmployeeAssignment record={record} refetch={refetch} type="employee_refinish" />
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
import React, { useEffect, useMemo, useRef, useState } from "react";
|
||||
import { Button, Dropdown, Input, Space, Statistic, Table } from "antd";
|
||||
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 ReactDragListView from "react-drag-listview";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectTechnician } from "../../redux/tech/tech.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 ProductionListColumns from "../production-list-columns/production-list-columns.data";
|
||||
import ProductionListDetail from "../production-list-detail/production-list-detail.component";
|
||||
import { ProductionListConfigManager } from "./production-list-config-manager.component.jsx";
|
||||
import ProductionListPrint from "./production-list-print.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";
|
||||
import { ProductionListConfigManager } from "./production-list-config-manager.component.jsx";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
@@ -43,7 +43,7 @@ export function ProductionListTable({ loading, data, refetch, bodyshop, technici
|
||||
const initialStateRef = useRef(
|
||||
(bodyshop.production_config &&
|
||||
bodyshop.production_config.find((p) => p.name === defaultView)?.columns.tableState) ||
|
||||
bodyshop.production_config[0]?.columns.tableState || {
|
||||
(bodyshop.production_config && bodyshop.production_config[0]?.columns.tableState) || {
|
||||
sortedInfo: {},
|
||||
filteredInfo: { text: "" }
|
||||
}
|
||||
|
||||
@@ -4,12 +4,13 @@ import {
|
||||
QUERY_EXACT_JOB_IN_PRODUCTION,
|
||||
QUERY_EXACT_JOBS_IN_PRODUCTION,
|
||||
QUERY_JOBS_IN_PRODUCTION,
|
||||
SUBSCRIPTION_JOBS_IN_PRODUCTION
|
||||
SUBSCRIPTION_JOBS_IN_PRODUCTION,
|
||||
SUBSCRIPTION_JOBS_IN_PRODUCTION_VIEW
|
||||
} from "../../graphql/jobs.queries";
|
||||
import ProductionListTable from "./production-list-table.component";
|
||||
import _ from "lodash";
|
||||
|
||||
export default function ProductionListTableContainer() {
|
||||
export default function ProductionListTableContainer({ subscriptionType = "direct" }) {
|
||||
const { refetch, loading, data } = useQuery(QUERY_JOBS_IN_PRODUCTION, {
|
||||
pollInterval: 3600000,
|
||||
fetchPolicy: "network-only",
|
||||
@@ -17,7 +18,9 @@ export default function ProductionListTableContainer() {
|
||||
});
|
||||
const client = useApolloClient();
|
||||
const [joblist, setJoblist] = useState([]);
|
||||
const { data: updatedJobs } = useSubscription(SUBSCRIPTION_JOBS_IN_PRODUCTION);
|
||||
const { data: updatedJobs } = useSubscription(
|
||||
subscriptionType === "view" ? SUBSCRIPTION_JOBS_IN_PRODUCTION_VIEW : SUBSCRIPTION_JOBS_IN_PRODUCTION
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (!(data && data.jobs)) return;
|
||||
|
||||
@@ -34,28 +34,34 @@ export function ReportCenterModalComponent({ reportCenterModal, bodyshop }) {
|
||||
const [form] = Form.useForm();
|
||||
const [search, setSearch] = useState("");
|
||||
const {
|
||||
treatments: { Enhanced_Payroll }
|
||||
treatments: { Enhanced_Payroll, ADPPayroll }
|
||||
} = useSplitTreatments({
|
||||
attributes: {},
|
||||
names: ["Enhanced_Payroll"],
|
||||
names: ["Enhanced_Payroll", "ADPPayroll"],
|
||||
splitKey: bodyshop.imexshopid
|
||||
});
|
||||
|
||||
const [loading, setLoading] = useState(false);
|
||||
const { t } = useTranslation();
|
||||
const Templates = TemplateList("report_center");
|
||||
const ReportsList =
|
||||
Enhanced_Payroll.treatment === "on"
|
||||
? Object.keys(Templates)
|
||||
.map((key) => {
|
||||
return Templates[key];
|
||||
})
|
||||
.filter((temp) => temp.enhanced_payroll === undefined || temp.enhanced_payroll === true)
|
||||
: Object.keys(Templates)
|
||||
.map((key) => {
|
||||
return Templates[key];
|
||||
})
|
||||
.filter((temp) => temp.enhanced_payroll === undefined || temp.enhanced_payroll === false);
|
||||
const ReportsList = Object.keys(Templates)
|
||||
.map((key) => Templates[key])
|
||||
.filter((temp) => {
|
||||
const enhancedPayrollOn = Enhanced_Payroll.treatment === "on";
|
||||
const adpPayrollOn = ADPPayroll.treatment === "on";
|
||||
|
||||
if (enhancedPayrollOn && adpPayrollOn) {
|
||||
return temp.enhanced_payroll !== false || temp.adp_payroll !== false;
|
||||
}
|
||||
if (enhancedPayrollOn) {
|
||||
return temp.enhanced_payroll !== false && temp.adp_payroll !== true;
|
||||
}
|
||||
if (adpPayrollOn) {
|
||||
return temp.adp_payroll !== false && temp.enhanced_payroll !== true;
|
||||
}
|
||||
|
||||
return temp.enhanced_payroll !== true && temp.adp_payroll !== true;
|
||||
});
|
||||
|
||||
const { open } = reportCenterModal;
|
||||
|
||||
@@ -104,7 +110,7 @@ export function ReportCenterModalComponent({ reportCenterModal, bodyshop }) {
|
||||
to: values.to,
|
||||
subject: Templates[values.key]?.subject
|
||||
},
|
||||
values.sendbyexcel === "excel" ? "x" : values.sendby === "email" ? "e" : "p",
|
||||
values.sendbytext === "text" ? values.sendbytext : values.sendbyexcel === "excel" ? "x" : values.sendby === "email" ? "e" : "p",
|
||||
id
|
||||
);
|
||||
setLoading(false);
|
||||
@@ -291,7 +297,15 @@ export function ReportCenterModalComponent({ reportCenterModal, bodyshop }) {
|
||||
</Radio.Group>
|
||||
</Form.Item>
|
||||
);
|
||||
if (reporttype !== "excel")
|
||||
if (reporttype === "text")
|
||||
return (
|
||||
<Form.Item label={t("general.labels.sendby")} name="sendbytext" initialValue="text">
|
||||
<Radio.Group>
|
||||
<Radio value="text">{t("general.labels.text")}</Radio>
|
||||
</Radio.Group>
|
||||
</Form.Item>
|
||||
);
|
||||
if (reporttype !== "excel" || reporttype !== "text")
|
||||
return (
|
||||
<Form.Item label={t("general.labels.sendby")} name="sendby" initialValue="print">
|
||||
<Radio.Group>
|
||||
|
||||
505
client/src/components/schedule-calendar-wrapper/localizer.js
Normal file
505
client/src/components/schedule-calendar-wrapper/localizer.js
Normal file
@@ -0,0 +1,505 @@
|
||||
import isBetween from "dayjs/plugin/isBetween";
|
||||
import isSameOrAfter from "dayjs/plugin/isSameOrAfter";
|
||||
import isSameOrBefore from "dayjs/plugin/isSameOrBefore";
|
||||
import localeData from "dayjs/plugin/localeData";
|
||||
import localizedFormat from "dayjs/plugin/localizedFormat";
|
||||
import minMax from "dayjs/plugin/minMax";
|
||||
import utc from "dayjs/plugin/utc";
|
||||
import { DateLocalizer } from "react-big-calendar";
|
||||
|
||||
function arrayWithHoles(arr) {
|
||||
if (Array.isArray(arr)) return arr;
|
||||
}
|
||||
|
||||
function iterableToArrayLimit(arr, i) {
|
||||
if (typeof Symbol === "undefined" || !(Symbol.iterator in Object(arr))) return;
|
||||
var _arr = [];
|
||||
var _n = true;
|
||||
var _d = false;
|
||||
var _e = undefined;
|
||||
try {
|
||||
for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) {
|
||||
_arr.push(_s.value);
|
||||
if (i && _arr.length === i) break;
|
||||
}
|
||||
} catch (err) {
|
||||
_d = true;
|
||||
_e = err;
|
||||
} finally {
|
||||
try {
|
||||
if (!_n && _i["return"] != null) _i["return"]();
|
||||
} finally {
|
||||
if (_d) throw _e;
|
||||
}
|
||||
}
|
||||
return _arr;
|
||||
}
|
||||
|
||||
function unsupportedIterableToArray(o, minLen) {
|
||||
if (!o) return;
|
||||
if (typeof o === "string") return arrayLikeToArray(o, minLen);
|
||||
var n = Object.prototype.toString.call(o).slice(8, -1);
|
||||
if (n === "Object" && o.constructor) n = o.constructor.name;
|
||||
if (n === "Map" || n === "Set") return Array.from(o);
|
||||
if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return arrayLikeToArray(o, minLen);
|
||||
}
|
||||
|
||||
function arrayLikeToArray(arr, len) {
|
||||
if (len == null || len > arr.length) len = arr.length;
|
||||
for (var i = 0, arr2 = new Array(len); i < len; i++) {
|
||||
arr2[i] = arr[i];
|
||||
}
|
||||
return arr2;
|
||||
}
|
||||
|
||||
function nonIterableRest() {
|
||||
throw new TypeError(
|
||||
"Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."
|
||||
);
|
||||
}
|
||||
|
||||
function _slicedToArray(arr, i) {
|
||||
return arrayWithHoles(arr) || iterableToArrayLimit(arr, i) || unsupportedIterableToArray(arr, i) || nonIterableRest();
|
||||
}
|
||||
|
||||
function fixUnit(unit) {
|
||||
var datePart = unit ? unit.toLowerCase() : unit;
|
||||
if (datePart === "FullYear") {
|
||||
datePart = "year";
|
||||
} else if (!datePart) {
|
||||
datePart = undefined;
|
||||
}
|
||||
return datePart;
|
||||
}
|
||||
|
||||
var timeRangeFormat = function timeRangeFormat(_ref3, culture, local) {
|
||||
var start = _ref3.start,
|
||||
end = _ref3.end;
|
||||
return local.format(start, "LT", culture) + " – " + local.format(end, "LT", culture);
|
||||
};
|
||||
var timeRangeStartFormat = function timeRangeStartFormat(_ref4, culture, local) {
|
||||
var start = _ref4.start;
|
||||
return local.format(start, "LT", culture) + " – ";
|
||||
};
|
||||
var timeRangeEndFormat = function timeRangeEndFormat(_ref5, culture, local) {
|
||||
var end = _ref5.end;
|
||||
return " – " + local.format(end, "LT", culture);
|
||||
};
|
||||
var weekRangeFormat = function weekRangeFormat(_ref, culture, local) {
|
||||
var start = _ref.start,
|
||||
end = _ref.end;
|
||||
return (
|
||||
local.format(start, "MMMM DD", culture) +
|
||||
" – " +
|
||||
// updated to use this localizer 'eq()' method
|
||||
local.format(end, local.eq(start, end, "month") ? "DD" : "MMMM DD", culture)
|
||||
);
|
||||
};
|
||||
var dateRangeFormat = function dateRangeFormat(_ref2, culture, local) {
|
||||
var start = _ref2.start,
|
||||
end = _ref2.end;
|
||||
return local.format(start, "L", culture) + " – " + local.format(end, "L", culture);
|
||||
};
|
||||
|
||||
var formats = {
|
||||
dateFormat: "DD",
|
||||
dayFormat: "DD ddd",
|
||||
weekdayFormat: "ddd",
|
||||
selectRangeFormat: timeRangeFormat,
|
||||
eventTimeRangeFormat: timeRangeFormat,
|
||||
eventTimeRangeStartFormat: timeRangeStartFormat,
|
||||
eventTimeRangeEndFormat: timeRangeEndFormat,
|
||||
timeGutterFormat: "LT",
|
||||
monthHeaderFormat: "MMMM YYYY",
|
||||
dayHeaderFormat: "dddd MMM DD",
|
||||
dayRangeHeaderFormat: weekRangeFormat,
|
||||
agendaHeaderFormat: dateRangeFormat,
|
||||
agendaDateFormat: "ddd MMM DD",
|
||||
agendaTimeFormat: "LT",
|
||||
agendaTimeRangeFormat: timeRangeFormat
|
||||
};
|
||||
|
||||
const localizer = (dayjsLib) => {
|
||||
// load dayjs plugins
|
||||
dayjsLib.extend(isBetween);
|
||||
dayjsLib.extend(isSameOrAfter);
|
||||
dayjsLib.extend(isSameOrBefore);
|
||||
dayjsLib.extend(localeData);
|
||||
dayjsLib.extend(localizedFormat);
|
||||
dayjsLib.extend(minMax);
|
||||
dayjsLib.extend(utc);
|
||||
var locale = function locale(dj, c) {
|
||||
return c ? dj.locale(c) : dj;
|
||||
};
|
||||
|
||||
// if the timezone plugin is loaded,
|
||||
// then use the timezone aware version
|
||||
|
||||
//TODO This was the issue entirely...
|
||||
// var dayjs = dayjsLib.tz ? dayjsLib.tz : dayjsLib;
|
||||
var dayjs = dayjsLib;
|
||||
|
||||
function getTimezoneOffset(date) {
|
||||
// ensures this gets cast to timezone
|
||||
return dayjs(date).toDate().getTimezoneOffset();
|
||||
}
|
||||
|
||||
function getDstOffset(start, end) {
|
||||
var _st$tz$$x$$timezone;
|
||||
// convert to dayjs, in case
|
||||
var st = dayjs(start);
|
||||
var ed = dayjs(end);
|
||||
// if not using the dayjs timezone plugin
|
||||
if (!dayjs.tz) {
|
||||
return st.toDate().getTimezoneOffset() - ed.toDate().getTimezoneOffset();
|
||||
}
|
||||
/**
|
||||
* If a default timezone has been applied, then
|
||||
* use this to get the proper timezone offset, otherwise default
|
||||
* the timezone to the browser local
|
||||
*/
|
||||
var tzName =
|
||||
(_st$tz$$x$$timezone = st.tz().$x.$timezone) !== null && _st$tz$$x$$timezone !== void 0
|
||||
? _st$tz$$x$$timezone
|
||||
: dayjsLib.tz.guess();
|
||||
// invert offsets to be inline with moment.js
|
||||
var startOffset = -dayjs.tz(+st, tzName).utcOffset();
|
||||
var endOffset = -dayjs.tz(+ed, tzName).utcOffset();
|
||||
return startOffset - endOffset;
|
||||
}
|
||||
|
||||
function getDayStartDstOffset(start) {
|
||||
var dayStart = dayjs(start).startOf("day");
|
||||
return getDstOffset(dayStart, start);
|
||||
}
|
||||
|
||||
/*** BEGIN localized date arithmetic methods with dayjs ***/
|
||||
function defineComparators(a, b, unit) {
|
||||
var datePart = fixUnit(unit);
|
||||
var dtA = datePart ? dayjs(a).startOf(datePart) : dayjs(a);
|
||||
var dtB = datePart ? dayjs(b).startOf(datePart) : dayjs(b);
|
||||
return [dtA, dtB, datePart];
|
||||
}
|
||||
|
||||
function startOf() {
|
||||
var date = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
|
||||
var unit = arguments.length > 1 ? arguments[1] : undefined;
|
||||
var datePart = fixUnit(unit);
|
||||
if (datePart) {
|
||||
return dayjs(date).startOf(datePart).toDate();
|
||||
}
|
||||
return dayjs(date).toDate();
|
||||
}
|
||||
|
||||
function endOf() {
|
||||
var date = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
|
||||
var unit = arguments.length > 1 ? arguments[1] : undefined;
|
||||
var datePart = fixUnit(unit);
|
||||
if (datePart) {
|
||||
return dayjs(date).endOf(datePart).toDate();
|
||||
}
|
||||
return dayjs(date).toDate();
|
||||
}
|
||||
|
||||
// dayjs comparison operations *always* convert both sides to dayjs objects
|
||||
// prior to running the comparisons
|
||||
function eq(a, b, unit) {
|
||||
var _defineComparators = defineComparators(a, b, unit),
|
||||
_defineComparators2 = _slicedToArray(_defineComparators, 3),
|
||||
dtA = _defineComparators2[0],
|
||||
dtB = _defineComparators2[1],
|
||||
datePart = _defineComparators2[2];
|
||||
return dtA.isSame(dtB, datePart);
|
||||
}
|
||||
|
||||
function neq(a, b, unit) {
|
||||
return !eq(a, b, unit);
|
||||
}
|
||||
|
||||
function gt(a, b, unit) {
|
||||
var _defineComparators3 = defineComparators(a, b, unit),
|
||||
_defineComparators4 = _slicedToArray(_defineComparators3, 3),
|
||||
dtA = _defineComparators4[0],
|
||||
dtB = _defineComparators4[1],
|
||||
datePart = _defineComparators4[2];
|
||||
return dtA.isAfter(dtB, datePart);
|
||||
}
|
||||
|
||||
function lt(a, b, unit) {
|
||||
var _defineComparators5 = defineComparators(a, b, unit),
|
||||
_defineComparators6 = _slicedToArray(_defineComparators5, 3),
|
||||
dtA = _defineComparators6[0],
|
||||
dtB = _defineComparators6[1],
|
||||
datePart = _defineComparators6[2];
|
||||
return dtA.isBefore(dtB, datePart);
|
||||
}
|
||||
|
||||
function gte(a, b, unit) {
|
||||
var _defineComparators7 = defineComparators(a, b, unit),
|
||||
_defineComparators8 = _slicedToArray(_defineComparators7, 3),
|
||||
dtA = _defineComparators8[0],
|
||||
dtB = _defineComparators8[1],
|
||||
datePart = _defineComparators8[2];
|
||||
return dtA.isSameOrBefore(dtB, datePart);
|
||||
}
|
||||
|
||||
function lte(a, b, unit) {
|
||||
var _defineComparators9 = defineComparators(a, b, unit),
|
||||
_defineComparators10 = _slicedToArray(_defineComparators9, 3),
|
||||
dtA = _defineComparators10[0],
|
||||
dtB = _defineComparators10[1],
|
||||
datePart = _defineComparators10[2];
|
||||
return dtA.isSameOrBefore(dtB, datePart);
|
||||
}
|
||||
|
||||
function inRange(day, min, max) {
|
||||
var unit = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : "day";
|
||||
var datePart = fixUnit(unit);
|
||||
var djDay = dayjs(day);
|
||||
var djMin = dayjs(min);
|
||||
var djMax = dayjs(max);
|
||||
return djDay.isBetween(djMin, djMax, datePart, "[]");
|
||||
}
|
||||
|
||||
function min(dateA, dateB) {
|
||||
var dtA = dayjs(dateA);
|
||||
var dtB = dayjs(dateB);
|
||||
var minDt = dayjsLib.min(dtA, dtB);
|
||||
return minDt.toDate();
|
||||
}
|
||||
|
||||
function max(dateA, dateB) {
|
||||
var dtA = dayjs(dateA);
|
||||
var dtB = dayjs(dateB);
|
||||
var maxDt = dayjsLib.max(dtA, dtB);
|
||||
return maxDt.toDate();
|
||||
}
|
||||
|
||||
function merge(date, time) {
|
||||
if (!date && !time) return null;
|
||||
var tm = dayjs(time).format("HH:mm:ss");
|
||||
var dt = dayjs(date).startOf("day").format("MM/DD/YYYY");
|
||||
// We do it this way to avoid issues when timezone switching
|
||||
return dayjsLib("".concat(dt, " ").concat(tm), "MM/DD/YYYY HH:mm:ss").toDate();
|
||||
}
|
||||
|
||||
function add(date, adder, unit) {
|
||||
var datePart = fixUnit(unit);
|
||||
return dayjs(date).add(adder, datePart).toDate();
|
||||
}
|
||||
|
||||
function range(start, end) {
|
||||
var unit = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : "day";
|
||||
var datePart = fixUnit(unit);
|
||||
// because the add method will put these in tz, we have to start that way
|
||||
var current = dayjs(start).toDate();
|
||||
var days = [];
|
||||
while (lte(current, end)) {
|
||||
days.push(current);
|
||||
current = add(current, 1, datePart);
|
||||
}
|
||||
return days;
|
||||
}
|
||||
|
||||
function ceil(date, unit) {
|
||||
var datePart = fixUnit(unit);
|
||||
var floor = startOf(date, datePart);
|
||||
return eq(floor, date) ? floor : add(floor, 1, datePart);
|
||||
}
|
||||
|
||||
function diff(a, b) {
|
||||
var unit = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : "day";
|
||||
var datePart = fixUnit(unit);
|
||||
// don't use 'defineComparators' here, as we don't want to mutate the values
|
||||
var dtA = dayjs(a);
|
||||
var dtB = dayjs(b);
|
||||
return dtB.diff(dtA, datePart);
|
||||
}
|
||||
|
||||
function minutes(date) {
|
||||
var dt = dayjs(date);
|
||||
return dt.minutes();
|
||||
}
|
||||
|
||||
function firstOfWeek(culture) {
|
||||
var data = culture ? dayjsLib.localeData(culture) : dayjsLib.localeData();
|
||||
return data ? data.firstDayOfWeek() : 0;
|
||||
}
|
||||
|
||||
function firstVisibleDay(date) {
|
||||
return dayjs(date).startOf("month").startOf("week").toDate();
|
||||
}
|
||||
|
||||
function lastVisibleDay(date) {
|
||||
return dayjs(date).endOf("month").endOf("week").toDate();
|
||||
}
|
||||
|
||||
function visibleDays(date) {
|
||||
var current = firstVisibleDay(date);
|
||||
var last = lastVisibleDay(date);
|
||||
var days = [];
|
||||
while (lte(current, last)) {
|
||||
days.push(current);
|
||||
current = add(current, 1, "d");
|
||||
}
|
||||
return days;
|
||||
}
|
||||
|
||||
/*** END localized date arithmetic methods with dayjs ***/
|
||||
|
||||
/**
|
||||
* Moved from TimeSlots.js, this method overrides the method of the same name
|
||||
* in the localizer.js, using dayjs to construct the js Date
|
||||
* @param {Date} dt - date to start with
|
||||
* @param {Number} minutesFromMidnight
|
||||
* @param {Number} offset
|
||||
* @returns {Date}
|
||||
*/
|
||||
function getSlotDate(dt, minutesFromMidnight, offset) {
|
||||
return dayjs(dt)
|
||||
.startOf("day")
|
||||
.minute(minutesFromMidnight + offset)
|
||||
.toDate();
|
||||
}
|
||||
|
||||
// dayjs will automatically handle DST differences in it's calculations
|
||||
function getTotalMin(start, end) {
|
||||
return diff(start, end, "minutes");
|
||||
}
|
||||
|
||||
function getMinutesFromMidnight(start) {
|
||||
var dayStart = dayjs(start).startOf("day");
|
||||
var day = dayjs(start);
|
||||
return day.diff(dayStart, "minutes") + getDayStartDstOffset(start);
|
||||
}
|
||||
|
||||
// These two are used by DateSlotMetrics
|
||||
function continuesPrior(start, first) {
|
||||
var djStart = dayjs(start);
|
||||
var djFirst = dayjs(first);
|
||||
return djStart.isBefore(djFirst, "day");
|
||||
}
|
||||
|
||||
function continuesAfter(start, end, last) {
|
||||
var djEnd = dayjs(end);
|
||||
var djLast = dayjs(last);
|
||||
return djEnd.isSameOrAfter(djLast, "minutes");
|
||||
}
|
||||
|
||||
function daySpan(start, end) {
|
||||
var startDay = dayjs(start);
|
||||
var endDay = dayjs(end);
|
||||
return endDay.diff(startDay, "day");
|
||||
}
|
||||
|
||||
// These two are used by eventLevels
|
||||
function sortEvents(_ref6) {
|
||||
var _ref6$evtA = _ref6.evtA,
|
||||
aStart = _ref6$evtA.start,
|
||||
aEnd = _ref6$evtA.end,
|
||||
aAllDay = _ref6$evtA.allDay,
|
||||
_ref6$evtB = _ref6.evtB,
|
||||
bStart = _ref6$evtB.start,
|
||||
bEnd = _ref6$evtB.end,
|
||||
bAllDay = _ref6$evtB.allDay;
|
||||
var startSort = +startOf(aStart, "day") - +startOf(bStart, "day");
|
||||
var durA = daySpan(aStart, aEnd);
|
||||
var durB = daySpan(bStart, bEnd);
|
||||
return (
|
||||
startSort ||
|
||||
// sort by start Day first
|
||||
durB - durA ||
|
||||
// events spanning multiple days go first
|
||||
!!bAllDay - !!aAllDay ||
|
||||
// then allDay single day events
|
||||
+aStart - +bStart ||
|
||||
// then sort by start time *don't need dayjs conversion here
|
||||
+aEnd - +bEnd // then sort by end time *don't need dayjs conversion here either
|
||||
);
|
||||
}
|
||||
|
||||
function inEventRange(_ref7) {
|
||||
var _ref7$event = _ref7.event,
|
||||
start = _ref7$event.start,
|
||||
end = _ref7$event.end,
|
||||
_ref7$range = _ref7.range,
|
||||
rangeStart = _ref7$range.start,
|
||||
rangeEnd = _ref7$range.end;
|
||||
var startOfDay = dayjs(start).startOf("day");
|
||||
var eEnd = dayjs(end);
|
||||
var rStart = dayjs(rangeStart);
|
||||
var rEnd = dayjs(rangeEnd);
|
||||
var startsBeforeEnd = startOfDay.isSameOrBefore(rEnd, "day");
|
||||
// when the event is zero duration we need to handle a bit differently
|
||||
var sameMin = !startOfDay.isSame(eEnd, "minutes");
|
||||
var endsAfterStart = sameMin ? eEnd.isAfter(rStart, "minutes") : eEnd.isSameOrAfter(rStart, "minutes");
|
||||
return startsBeforeEnd && endsAfterStart;
|
||||
}
|
||||
|
||||
function isSameDate(date1, date2) {
|
||||
var dt = dayjs(date1);
|
||||
var dt2 = dayjs(date2);
|
||||
return dt.isSame(dt2, "day");
|
||||
}
|
||||
|
||||
/**
|
||||
* This method, called once in the localizer constructor, is used by eventLevels
|
||||
* 'eventSegments()' to assist in determining the 'span' of the event in the display,
|
||||
* specifically when using a timezone that is greater than the browser native timezone.
|
||||
* @returns number
|
||||
*/
|
||||
function browserTZOffset() {
|
||||
/**
|
||||
* Date.prototype.getTimezoneOffset horrifically flips the positive/negative from
|
||||
* what you see in it's string, so we have to jump through some hoops to get a value
|
||||
* we can actually compare.
|
||||
*/
|
||||
var dt = new Date();
|
||||
var neg = /-/.test(dt.toString()) ? "-" : "";
|
||||
var dtOffset = dt.getTimezoneOffset();
|
||||
var comparator = Number("".concat(neg).concat(Math.abs(dtOffset)));
|
||||
// dayjs correctly provides positive/negative offset, as expected
|
||||
var mtOffset = dayjs().utcOffset();
|
||||
return mtOffset > comparator ? 1 : 0;
|
||||
}
|
||||
|
||||
return new DateLocalizer({
|
||||
formats: formats,
|
||||
firstOfWeek: firstOfWeek,
|
||||
firstVisibleDay: firstVisibleDay,
|
||||
lastVisibleDay: lastVisibleDay,
|
||||
visibleDays: visibleDays,
|
||||
format: function format(value, _format, culture) {
|
||||
return locale(dayjs(value), culture).format(_format);
|
||||
},
|
||||
lt: lt,
|
||||
lte: lte,
|
||||
gt: gt,
|
||||
gte: gte,
|
||||
eq: eq,
|
||||
neq: neq,
|
||||
merge: merge,
|
||||
inRange: inRange,
|
||||
startOf: startOf,
|
||||
endOf: endOf,
|
||||
range: range,
|
||||
add: add,
|
||||
diff: diff,
|
||||
ceil: ceil,
|
||||
min: min,
|
||||
max: max,
|
||||
minutes: minutes,
|
||||
getSlotDate: getSlotDate,
|
||||
getTimezoneOffset: getTimezoneOffset,
|
||||
getDstOffset: getDstOffset,
|
||||
getTotalMin: getTotalMin,
|
||||
getMinutesFromMidnight: getMinutesFromMidnight,
|
||||
continuesPrior: continuesPrior,
|
||||
continuesAfter: continuesAfter,
|
||||
sortEvents: sortEvents,
|
||||
inEventRange: inEventRange,
|
||||
isSameDate: isSameDate,
|
||||
browserTZOffset: browserTZOffset
|
||||
});
|
||||
};
|
||||
export default localizer;
|
||||
@@ -1,7 +1,7 @@
|
||||
import dayjs from "../../utils/day";
|
||||
import queryString from "query-string";
|
||||
import React from "react";
|
||||
import { Calendar, dayjsLocalizer } from "react-big-calendar";
|
||||
import { Calendar } from "react-big-calendar";
|
||||
import { connect } from "react-redux";
|
||||
import { Link, useLocation, useNavigate } from "react-router-dom";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
@@ -14,12 +14,13 @@ import { selectProblemJobs } from "../../redux/application/application.selectors
|
||||
import { Alert, Collapse, Space } from "antd";
|
||||
import { Trans, useTranslation } from "react-i18next";
|
||||
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
||||
import local from "./localizer";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
problemJobs: selectProblemJobs
|
||||
});
|
||||
const localizer = dayjsLocalizer(dayjs);
|
||||
const localizer = local(dayjs);
|
||||
|
||||
export function ScheduleCalendarWrapperComponent({
|
||||
bodyshop,
|
||||
|
||||
@@ -4,7 +4,7 @@ export const CalculateWorkingDaysThisMonth = () => dayjs().endOf("month").busine
|
||||
|
||||
export const CalculateWorkingDaysInPeriod = (start, end) => dayjs(end).businessDiff(dayjs(start));
|
||||
|
||||
export const CalculateWorkingDaysAsOfToday = () => dayjs().businessDaysInMonth().length;
|
||||
export const CalculateWorkingDaysAsOfToday = () => dayjs().endOf("day").businessDiff(dayjs().startOf("month"));
|
||||
|
||||
export const CalculateWorkingDaysLastMonth = () =>
|
||||
dayjs().subtract(1, "month").endOf("month").businessDaysInMonth().length;
|
||||
|
||||
@@ -20,6 +20,7 @@ import ShopInfoTaskPresets from "./shop-info.task-presets.component";
|
||||
import queryString from "query-string";
|
||||
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
||||
import ShopInfoRoGuard from "./shop-info.roguard.component";
|
||||
import ShopInfoIntellipay from "./shop-intellipay-config.component";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop
|
||||
@@ -135,6 +136,17 @@ export function ShopInfoComponent({ bodyshop, form, saveLoading }) {
|
||||
],
|
||||
rome: "USE_IMEX",
|
||||
promanager: []
|
||||
}),
|
||||
...InstanceRenderManager({
|
||||
imex: [],
|
||||
rome: [
|
||||
{
|
||||
key: "intellipay",
|
||||
label: t("bodyshop.labels.intellipay"),
|
||||
children: <ShopInfoIntellipay form={form} />
|
||||
}
|
||||
],
|
||||
promanager: []
|
||||
})
|
||||
];
|
||||
return (
|
||||
|
||||
@@ -7,13 +7,13 @@ import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
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 FormItemEmail from "../form-items-formatted/email-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 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
|
||||
const timeZonesList = Intl.supportedValuesOf("timeZone");
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
@@ -28,10 +28,10 @@ export function ShopInfoGeneral({ form, bodyshop }) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const {
|
||||
treatments: { ClosingPeriod }
|
||||
treatments: { ClosingPeriod, ADPPayroll }
|
||||
} = useSplitTreatments({
|
||||
attributes: {},
|
||||
names: ["ClosingPeriod"],
|
||||
names: ["ClosingPeriod", "ADPPayroll"],
|
||||
splitKey: bodyshop && bodyshop.imexshopid
|
||||
});
|
||||
|
||||
@@ -98,7 +98,6 @@ export function ShopInfoGeneral({ form, bodyshop }) {
|
||||
<Form.Item label={t("bodyshop.fields.email")} name="email">
|
||||
<Input />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.phone")}
|
||||
name="phone"
|
||||
@@ -356,14 +355,22 @@ export function ShopInfoGeneral({ form, bodyshop }) {
|
||||
<Select mode="tags" />
|
||||
</Form.Item>
|
||||
{ClosingPeriod.treatment === "on" && (
|
||||
<>
|
||||
<Form.Item
|
||||
name={["accountingconfig", "ClosingPeriod"]}
|
||||
label={t("bodyshop.fields.closingperiod")} //{t("reportcenter.labels.dates")}
|
||||
>
|
||||
<DatePicker.RangePicker format="MM/DD/YYYY" presets={DatePickerRanges} />
|
||||
</Form.Item>
|
||||
</>
|
||||
<Form.Item
|
||||
name={["accountingconfig", "ClosingPeriod"]}
|
||||
label={t("bodyshop.fields.closingperiod")} //{t("reportcenter.labels.dates")}
|
||||
>
|
||||
<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>
|
||||
</FeatureWrapper>
|
||||
|
||||
@@ -30,219 +30,226 @@ export function ShopInfoRbacComponent({ form, bodyshop }) {
|
||||
return (
|
||||
<RbacWrapper action="shop:rbac">
|
||||
<LayoutFormRow>
|
||||
{...HasFeatureAccess({ featureName: "export", bodyshop }) ? [
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.accounting.exportlog")}
|
||||
rules={[
|
||||
{
|
||||
required: true
|
||||
//message: t("general.validation.required"),
|
||||
}
|
||||
]}
|
||||
name={["md_rbac", "accounting:exportlog"]}
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>,
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.accounting.payables")}
|
||||
rules={[
|
||||
{
|
||||
required: true
|
||||
//message: t("general.validation.required"),
|
||||
}
|
||||
]}
|
||||
name={["md_rbac", "accounting:payables"]}
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>,
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.accounting.payments")}
|
||||
rules={[
|
||||
{
|
||||
required: true
|
||||
//message: t("general.validation.required"),
|
||||
}
|
||||
]}
|
||||
name={["md_rbac", "accounting:payments"]}
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>,
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.accounting.receivables")}
|
||||
rules={[
|
||||
{
|
||||
required: true
|
||||
//message: t("general.validation.required"),
|
||||
}
|
||||
]}
|
||||
name={["md_rbac", "accounting:receivables"]}
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>
|
||||
]:[]}
|
||||
{...HasFeatureAccess({ featureName: "bills", bodyshop }) ? [
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.bills.delete")}
|
||||
rules={[
|
||||
{
|
||||
required: true
|
||||
//message: t("general.validation.required"),
|
||||
}
|
||||
]}
|
||||
name={["md_rbac", "bills:delete"]}
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>,
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.bills.enter")}
|
||||
rules={[
|
||||
{
|
||||
required: true
|
||||
//message: t("general.validation.required"),
|
||||
}
|
||||
]}
|
||||
name={["md_rbac", "bills:enter"]}
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>,
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.bills.list")}
|
||||
rules={[
|
||||
{
|
||||
required: true
|
||||
//message: t("general.validation.required"),
|
||||
}
|
||||
]}
|
||||
name={["md_rbac", "bills:list"]}
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>,
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.bills.reexport")}
|
||||
rules={[
|
||||
{
|
||||
required: true
|
||||
//message: t("general.validation.required"),
|
||||
}
|
||||
]}
|
||||
name={["md_rbac", "bills:reexport"]}
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>,
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.bills.view")}
|
||||
rules={[
|
||||
{
|
||||
required: true
|
||||
//message: t("general.validation.required"),
|
||||
}
|
||||
]}
|
||||
name={["md_rbac", "bills:view"]}
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>
|
||||
]:[]}
|
||||
|
||||
{...HasFeatureAccess({ featureName: "courtesycars", bodyshop }) ? [
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.contracts.create")}
|
||||
rules={[
|
||||
{
|
||||
required: true
|
||||
//message: t("general.validation.required"),
|
||||
}
|
||||
]}
|
||||
name={["md_rbac", "contracts:create"]}
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>,
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.contracts.detail")}
|
||||
rules={[
|
||||
{
|
||||
required: true
|
||||
//message: t("general.validation.required"),
|
||||
}
|
||||
]}
|
||||
name={["md_rbac", "contracts:detail"]}
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>,
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.contracts.list")}
|
||||
rules={[
|
||||
{
|
||||
required: true
|
||||
//message: t("general.validation.required"),
|
||||
}
|
||||
]}
|
||||
name={["md_rbac", "contracts:list"]}
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>,
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.courtesycar.create")}
|
||||
rules={[
|
||||
{
|
||||
required: true
|
||||
//message: t("general.validation.required"),
|
||||
}
|
||||
]}
|
||||
name={["md_rbac", "courtesycar:create"]}
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>,
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.courtesycar.detail")}
|
||||
rules={[
|
||||
{
|
||||
required: true
|
||||
//message: t("general.validation.required"),
|
||||
}
|
||||
]}
|
||||
name={["md_rbac", "courtesycar:detail"]}
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>,
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.courtesycar.list")}
|
||||
rules={[
|
||||
{
|
||||
required: true
|
||||
//message: t("general.validation.required"),
|
||||
}
|
||||
]}
|
||||
name={["md_rbac", "courtesycar:list"]}
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>
|
||||
]:[]}
|
||||
{...HasFeatureAccess({ featureName: "csi", bodyshop }) ? [
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.csi.export")}
|
||||
rules={[
|
||||
{
|
||||
required: true
|
||||
//message: t("general.validation.required"),
|
||||
}
|
||||
]}
|
||||
name={["md_rbac", "csi:export"]}
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>,
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.csi.page")}
|
||||
rules={[
|
||||
{
|
||||
required: true
|
||||
//message: t("general.validation.required"),
|
||||
}
|
||||
]}
|
||||
name={["md_rbac", "csi:page"]}
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>
|
||||
]:[]}
|
||||
{...HasFeatureAccess({ featureName: "export", bodyshop })
|
||||
? [
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.accounting.exportlog")}
|
||||
rules={[
|
||||
{
|
||||
required: true
|
||||
//message: t("general.validation.required"),
|
||||
}
|
||||
]}
|
||||
name={["md_rbac", "accounting:exportlog"]}
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>,
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.accounting.payables")}
|
||||
rules={[
|
||||
{
|
||||
required: true
|
||||
//message: t("general.validation.required"),
|
||||
}
|
||||
]}
|
||||
name={["md_rbac", "accounting:payables"]}
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>,
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.accounting.payments")}
|
||||
rules={[
|
||||
{
|
||||
required: true
|
||||
//message: t("general.validation.required"),
|
||||
}
|
||||
]}
|
||||
name={["md_rbac", "accounting:payments"]}
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>,
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.accounting.receivables")}
|
||||
rules={[
|
||||
{
|
||||
required: true
|
||||
//message: t("general.validation.required"),
|
||||
}
|
||||
]}
|
||||
name={["md_rbac", "accounting:receivables"]}
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>
|
||||
]
|
||||
: []}
|
||||
{...HasFeatureAccess({ featureName: "bills", bodyshop })
|
||||
? [
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.bills.delete")}
|
||||
rules={[
|
||||
{
|
||||
required: true
|
||||
//message: t("general.validation.required"),
|
||||
}
|
||||
]}
|
||||
name={["md_rbac", "bills:delete"]}
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>,
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.bills.enter")}
|
||||
rules={[
|
||||
{
|
||||
required: true
|
||||
//message: t("general.validation.required"),
|
||||
}
|
||||
]}
|
||||
name={["md_rbac", "bills:enter"]}
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>,
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.bills.list")}
|
||||
rules={[
|
||||
{
|
||||
required: true
|
||||
//message: t("general.validation.required"),
|
||||
}
|
||||
]}
|
||||
name={["md_rbac", "bills:list"]}
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>,
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.bills.reexport")}
|
||||
rules={[
|
||||
{
|
||||
required: true
|
||||
//message: t("general.validation.required"),
|
||||
}
|
||||
]}
|
||||
name={["md_rbac", "bills:reexport"]}
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>,
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.bills.view")}
|
||||
rules={[
|
||||
{
|
||||
required: true
|
||||
//message: t("general.validation.required"),
|
||||
}
|
||||
]}
|
||||
name={["md_rbac", "bills:view"]}
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>
|
||||
]
|
||||
: []}
|
||||
{...HasFeatureAccess({ featureName: "courtesycars", bodyshop })
|
||||
? [
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.contracts.create")}
|
||||
rules={[
|
||||
{
|
||||
required: true
|
||||
//message: t("general.validation.required"),
|
||||
}
|
||||
]}
|
||||
name={["md_rbac", "contracts:create"]}
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>,
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.contracts.detail")}
|
||||
rules={[
|
||||
{
|
||||
required: true
|
||||
//message: t("general.validation.required"),
|
||||
}
|
||||
]}
|
||||
name={["md_rbac", "contracts:detail"]}
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>,
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.contracts.list")}
|
||||
rules={[
|
||||
{
|
||||
required: true
|
||||
//message: t("general.validation.required"),
|
||||
}
|
||||
]}
|
||||
name={["md_rbac", "contracts:list"]}
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>,
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.courtesycar.create")}
|
||||
rules={[
|
||||
{
|
||||
required: true
|
||||
//message: t("general.validation.required"),
|
||||
}
|
||||
]}
|
||||
name={["md_rbac", "courtesycar:create"]}
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>,
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.courtesycar.detail")}
|
||||
rules={[
|
||||
{
|
||||
required: true
|
||||
//message: t("general.validation.required"),
|
||||
}
|
||||
]}
|
||||
name={["md_rbac", "courtesycar:detail"]}
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>,
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.courtesycar.list")}
|
||||
rules={[
|
||||
{
|
||||
required: true
|
||||
//message: t("general.validation.required"),
|
||||
}
|
||||
]}
|
||||
name={["md_rbac", "courtesycar:list"]}
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>
|
||||
]
|
||||
: []}
|
||||
{...HasFeatureAccess({ featureName: "csi", bodyshop })
|
||||
? [
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.csi.export")}
|
||||
rules={[
|
||||
{
|
||||
required: true
|
||||
//message: t("general.validation.required"),
|
||||
}
|
||||
]}
|
||||
name={["md_rbac", "csi:export"]}
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>,
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.csi.page")}
|
||||
rules={[
|
||||
{
|
||||
required: true
|
||||
//message: t("general.validation.required"),
|
||||
}
|
||||
]}
|
||||
name={["md_rbac", "csi:page"]}
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>
|
||||
]
|
||||
: []}
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.employees.page")}
|
||||
rules={[
|
||||
@@ -255,6 +262,18 @@ export function ShopInfoRbacComponent({ form, bodyshop }) {
|
||||
>
|
||||
<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
|
||||
label={t("bodyshop.fields.rbac.jobs.admin")}
|
||||
rules={[
|
||||
@@ -435,31 +454,6 @@ export function ShopInfoRbacComponent({ form, bodyshop }) {
|
||||
>
|
||||
<InputNumber />
|
||||
</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
|
||||
label={t("bodyshop.fields.rbac.payments.enter")}
|
||||
rules={[
|
||||
@@ -522,7 +516,6 @@ export function ShopInfoRbacComponent({ form, bodyshop }) {
|
||||
<InputNumber />
|
||||
</Form.Item>
|
||||
)}
|
||||
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.production.list")}
|
||||
rules={[
|
||||
@@ -561,128 +554,118 @@ export function ShopInfoRbacComponent({ form, bodyshop }) {
|
||||
<InputNumber />
|
||||
</Form.Item>
|
||||
)}
|
||||
{...HasFeatureAccess({ featureName: "timetickets", bodyshop }) ? [
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.shiftclock.view")}
|
||||
rules={[
|
||||
{
|
||||
required: true
|
||||
//message: t("general.validation.required"),
|
||||
}
|
||||
]}
|
||||
name={["md_rbac", "shiftclock:view"]}
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>,
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.shop.config")}
|
||||
rules={[
|
||||
{
|
||||
required: true
|
||||
//message: t("general.validation.required"),
|
||||
}
|
||||
]}
|
||||
name={["md_rbac", "shop:config"]}
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>,
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.timetickets.edit")}
|
||||
rules={[
|
||||
{
|
||||
required: true
|
||||
//message: t("general.validation.required"),
|
||||
}
|
||||
]}
|
||||
name={["md_rbac", "timetickets:edit"]}
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>,
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.timetickets.shiftedit")}
|
||||
rules={[
|
||||
{
|
||||
required: true
|
||||
//message: t("general.validation.required"),
|
||||
}
|
||||
]}
|
||||
name={["md_rbac", "timetickets:shiftedit"]}
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>,
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.timetickets.editcommitted")}
|
||||
rules={[
|
||||
{
|
||||
required: true
|
||||
//message: t("general.validation.required"),
|
||||
}
|
||||
]}
|
||||
name={["md_rbac", "timetickets:editcommitted"]}
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>,
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.ttapprovals.view")}
|
||||
rules={[
|
||||
{
|
||||
required: true
|
||||
//message: t("general.validation.required"),
|
||||
}
|
||||
]}
|
||||
name={["md_rbac", "ttapprovals:view"]}
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>,
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.ttapprovals.approve")}
|
||||
rules={[
|
||||
{
|
||||
required: true
|
||||
//message: t("general.validation.required"),
|
||||
}
|
||||
]}
|
||||
name={["md_rbac", "ttapprovals:approve"]}
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>,
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.timetickets.enter")}
|
||||
rules={[
|
||||
{
|
||||
required: true
|
||||
//message: t("general.validation.required"),
|
||||
}
|
||||
]}
|
||||
name={["md_rbac", "timetickets:enter"]}
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>,
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.timetickets.list")}
|
||||
rules={[
|
||||
{
|
||||
required: true
|
||||
//message: t("general.validation.required"),
|
||||
}
|
||||
]}
|
||||
name={["md_rbac", "timetickets:list"]}
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>,
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.timetickets.shiftedit")}
|
||||
rules={[
|
||||
{
|
||||
required: true
|
||||
//message: t("general.validation.required"),
|
||||
}
|
||||
]}
|
||||
name={["md_rbac", "timetickets:shiftedit"]}
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>
|
||||
]:[]}
|
||||
{...HasFeatureAccess({ featureName: "timetickets", bodyshop })
|
||||
? [
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.shiftclock.view")}
|
||||
rules={[
|
||||
{
|
||||
required: true
|
||||
//message: t("general.validation.required"),
|
||||
}
|
||||
]}
|
||||
name={["md_rbac", "shiftclock:view"]}
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>,
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.shop.config")}
|
||||
rules={[
|
||||
{
|
||||
required: true
|
||||
//message: t("general.validation.required"),
|
||||
}
|
||||
]}
|
||||
name={["md_rbac", "shop:config"]}
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>,
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.timetickets.edit")}
|
||||
rules={[
|
||||
{
|
||||
required: true
|
||||
//message: t("general.validation.required"),
|
||||
}
|
||||
]}
|
||||
name={["md_rbac", "timetickets:edit"]}
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>,
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.timetickets.shiftedit")}
|
||||
rules={[
|
||||
{
|
||||
required: true
|
||||
//message: t("general.validation.required"),
|
||||
}
|
||||
]}
|
||||
name={["md_rbac", "timetickets:shiftedit"]}
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>,
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.timetickets.editcommitted")}
|
||||
rules={[
|
||||
{
|
||||
required: true
|
||||
//message: t("general.validation.required"),
|
||||
}
|
||||
]}
|
||||
name={["md_rbac", "timetickets:editcommitted"]}
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>,
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.ttapprovals.view")}
|
||||
rules={[
|
||||
{
|
||||
required: true
|
||||
//message: t("general.validation.required"),
|
||||
}
|
||||
]}
|
||||
name={["md_rbac", "ttapprovals:view"]}
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>,
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.ttapprovals.approve")}
|
||||
rules={[
|
||||
{
|
||||
required: true
|
||||
//message: t("general.validation.required"),
|
||||
}
|
||||
]}
|
||||
name={["md_rbac", "ttapprovals:approve"]}
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>,
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.timetickets.enter")}
|
||||
rules={[
|
||||
{
|
||||
required: true
|
||||
//message: t("general.validation.required"),
|
||||
}
|
||||
]}
|
||||
name={["md_rbac", "timetickets:enter"]}
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>,
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.timetickets.list")}
|
||||
rules={[
|
||||
{
|
||||
required: true
|
||||
//message: t("general.validation.required"),
|
||||
}
|
||||
]}
|
||||
name={["md_rbac", "timetickets:list"]}
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>
|
||||
]
|
||||
: []}
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.shop.vendors")}
|
||||
rules={[
|
||||
@@ -757,7 +740,6 @@ export function ShopInfoRbacComponent({ form, bodyshop }) {
|
||||
<InputNumber />
|
||||
</Form.Item>
|
||||
)}
|
||||
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.users.editaccess")}
|
||||
rules={[
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
import { Alert, Form, InputNumber, Switch } from "antd";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
||||
|
||||
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)(ShopInfoIntellipay);
|
||||
|
||||
export function ShopInfoIntellipay({ bodyshop, form }) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Form.Item dependencies={[["intellipay_config", "enable_cash_discount"]]}>
|
||||
{() => {
|
||||
const { intellipay_config } = form.getFieldsValue();
|
||||
|
||||
if (intellipay_config?.enable_cash_discount)
|
||||
return <Alert message={t("bodyshop.labels.intellipay_cash_discount")} />;
|
||||
}}
|
||||
</Form.Item>
|
||||
|
||||
<LayoutFormRow noDivider>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.intellipay_config.enable_cash_discount")}
|
||||
valuePropName="checked"
|
||||
name={["intellipay_config", "enable_cash_discount"]}
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.intellipay_config.cash_discount_percentage")}
|
||||
valuePropName="checked"
|
||||
dependencies={[["intellipay_config", "enable_cash_discount"]]}
|
||||
name={["intellipay_config", "cash_discount_percentage"]}
|
||||
rules={[
|
||||
({ getFieldsValue }) => ({ required: form.getFieldValue(["intellipay_config", "enable_cash_discount"]) })
|
||||
]}
|
||||
>
|
||||
<InputNumber min={0} max={100} precision={1} suffix='%'/>
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
import { Button, Card, DatePicker, Form, Popover, Radio, Space } from "antd";
|
||||
import dayjs from "../../utils/day";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
@@ -7,10 +6,12 @@ import { createStructuredSelector } from "reselect";
|
||||
import { logImEXEvent } from "../../firebase/firebase.utils";
|
||||
import { selectTechnician } from "../../redux/tech/tech.selectors";
|
||||
import DatePIckerRanges from "../../utils/DatePickerRanges";
|
||||
import dayjs from "../../utils/day";
|
||||
import { GenerateDocument } from "../../utils/RenderTemplate";
|
||||
import { TemplateList } from "../../utils/TemplateConstants";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectTechnician,
|
||||
technician: selectTechnician
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
@@ -18,7 +19,7 @@ const mapDispatchToProps = (dispatch) => ({
|
||||
});
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(TechJobPrintTickets);
|
||||
|
||||
export function TechJobPrintTickets({ technician, event, attendacePrint }) {
|
||||
export function TechJobPrintTickets({ bodyshop, technician, event, attendacePrint }) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const [loading, setLoading] = useState(false);
|
||||
@@ -57,7 +58,8 @@ export function TechJobPrintTickets({ technician, event, attendacePrint }) {
|
||||
subject:
|
||||
attendacePrint === true ? Templates.attendance_employee.subject : Templates.timetickets_employee.subject
|
||||
},
|
||||
values.sendby // === "email" ? "e" : "p"
|
||||
values.sendby,
|
||||
bodyshop
|
||||
);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { EditFilled, SyncOutlined } from "@ant-design/icons";
|
||||
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
||||
import { Button, Card, Checkbox, Space, Table } from "antd";
|
||||
import dayjs from "../../utils/day";
|
||||
import React, { useMemo, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
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 { DateFormatter, DateTimeFormatter } from "../../utils/DateFormatter";
|
||||
import { onlyUnique } from "../../utils/arrayHelper";
|
||||
import dayjs from "../../utils/day";
|
||||
import { alphaSort, dateSort } from "../../utils/sorters";
|
||||
import RbacWrapper, { HasRbacAccess } from "../rbac-wrapper/rbac-wrapper.component";
|
||||
import { HasRbacAccess } from "../rbac-wrapper/rbac-wrapper.component";
|
||||
import TimeTicketEnterButton from "../time-ticket-enter-button/time-ticket-enter-button.component";
|
||||
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
@@ -52,6 +52,10 @@ export function TimeTicketList({
|
||||
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(() => {
|
||||
if (timetickets)
|
||||
return timetickets.reduce(
|
||||
@@ -65,6 +69,18 @@ export function TimeTicketList({
|
||||
return { productivehrs: 0, actualhrs: 0 };
|
||||
}, [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 = [
|
||||
...(Enhanced_Payroll.treatment === "on"
|
||||
? [
|
||||
@@ -165,7 +181,7 @@ export function TimeTicketList({
|
||||
key: "memo",
|
||||
sorter: (a, b) => alphaSort(a.memo, b.memo),
|
||||
sortOrder: state.sortedInfo.columnKey === "memo" && state.sortedInfo.order,
|
||||
render: (text, record) => (record.clockon || record.clockoff ? t(record.memo) : record.memo)
|
||||
render: (text, record) => (record.memo?.startsWith("timetickets.labels") ? t(record.memo) : record.memo)
|
||||
},
|
||||
...(Enhanced_Payroll.treatment === "on"
|
||||
? [
|
||||
@@ -206,76 +222,55 @@ export function TimeTicketList({
|
||||
return null;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
}
|
||||
]),
|
||||
{
|
||||
title: t("timetickets.fields.created_by"),
|
||||
dataIndex: "created_by",
|
||||
key: "created_by",
|
||||
sorter: (a, b) => alphaSort(a.created_by, b.created_by),
|
||||
sortOrder: state.sortedInfo.columnKey === "created_by" && state.sortedInfo.order,
|
||||
render: (text, record) => record.created_by
|
||||
},
|
||||
// {
|
||||
// title: "Pay",
|
||||
// dataIndex: "pay",
|
||||
// key: "pay",
|
||||
// render: (text, record) =>
|
||||
// Dinero({ amount: Math.round(record.rate * 100) })
|
||||
// .multiply(record.flat_rate ? record.productivehrs : record.actualhrs)
|
||||
// .toFormat("$0.00"),
|
||||
// },
|
||||
{
|
||||
title: t("general.labels.actions"),
|
||||
dataIndex: "actions",
|
||||
key: "actions",
|
||||
render: (text, record) => (
|
||||
<Space wrap>
|
||||
{techConsole && (
|
||||
<TimeTicketEnterButton
|
||||
actions={{ refetch }}
|
||||
context={{ id: record.id, timeticket: record }}
|
||||
disabled={!record.job || disabled}
|
||||
>
|
||||
<EditFilled />
|
||||
</TimeTicketEnterButton>
|
||||
)}
|
||||
{!techConsole && (
|
||||
<RbacWrapper
|
||||
action="timetickets:edit"
|
||||
noauth={() => {
|
||||
return <div />;
|
||||
}}
|
||||
>
|
||||
<TimeTicketEnterButton
|
||||
actions={{ refetch }}
|
||||
context={{
|
||||
id: record.id,
|
||||
timeticket: record
|
||||
}}
|
||||
disabled={
|
||||
HasRbacAccess({
|
||||
bodyshop,
|
||||
authLevel: authLevel,
|
||||
action: "timetickets:editcommitted"
|
||||
}) &&
|
||||
HasRbacAccess({
|
||||
bodyshop,
|
||||
authLevel: authLevel,
|
||||
action: "timetickets:shiftedit"
|
||||
})
|
||||
? disabled
|
||||
: !record.jobid
|
||||
}
|
||||
>
|
||||
<EditFilled />
|
||||
</TimeTicketEnterButton>
|
||||
</RbacWrapper>
|
||||
)}
|
||||
</Space>
|
||||
)
|
||||
}
|
||||
{
|
||||
title: t("timetickets.fields.created_by"),
|
||||
dataIndex: "created_by",
|
||||
key: "created_by",
|
||||
sorter: (a, b) => alphaSort(a.created_by, b.created_by),
|
||||
sortOrder: state.sortedInfo.columnKey === "created_by" && state.sortedInfo.order,
|
||||
render: (text, record) => record.created_by
|
||||
},
|
||||
// {
|
||||
// title: "Pay",
|
||||
// dataIndex: "pay",
|
||||
// key: "pay",
|
||||
// render: (text, record) =>
|
||||
// Dinero({ amount: Math.round(record.rate * 100) })
|
||||
// .multiply(record.flat_rate ? record.productivehrs : record.actualhrs)
|
||||
// .toFormat("$0.00"),
|
||||
// },
|
||||
{
|
||||
title: t("general.labels.actions"),
|
||||
dataIndex: "actions",
|
||||
key: "actions",
|
||||
render: (text, record) => (
|
||||
<Space wrap>
|
||||
{techConsole && (
|
||||
<TimeTicketEnterButton
|
||||
actions={{ refetch }}
|
||||
context={{ id: record.id, timeticket: record }}
|
||||
disabled={!record.job || disabled}
|
||||
>
|
||||
<EditFilled />
|
||||
</TimeTicketEnterButton>
|
||||
)}
|
||||
{!techConsole && (
|
||||
<TimeTicketEnterButton
|
||||
actions={{ refetch }}
|
||||
context={{
|
||||
id: record.id,
|
||||
timeticket: record
|
||||
}}
|
||||
disabled={isDisabled(record)}
|
||||
>
|
||||
<EditFilled />
|
||||
</TimeTicketEnterButton>
|
||||
)}
|
||||
</Space>
|
||||
)
|
||||
}
|
||||
];
|
||||
|
||||
const handleTableChange = (pagination, filters, sorter) => {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useLazyQuery } from "@apollo/client";
|
||||
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
||||
import { Form, Input, InputNumber, Select, Switch } from "antd";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
@@ -7,8 +8,10 @@ import { createStructuredSelector } from "reselect";
|
||||
import { GET_LINE_TICKET_BY_PK } from "../../graphql/jobs-lines.queries";
|
||||
import { selectAuthLevel, selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import EmployeeSearchSelect from "../employee-search-select/employee-search-select.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 {
|
||||
default as DateTimePicker,
|
||||
default as FormDateTimePicker
|
||||
} from "../form-date-time-picker/form-date-time-picker.component";
|
||||
import JobSearchSelect from "../job-search-select/job-search-select.component";
|
||||
import LaborAllocationsTable from "../labor-allocations-table/labor-allocations-table.component";
|
||||
import { CalculateAllocationsTotals } from "../labor-allocations-table/labor-allocations-table.utility";
|
||||
@@ -16,7 +19,6 @@ import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
||||
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component";
|
||||
import { HasRbacAccess } from "../rbac-wrapper/rbac-wrapper.component";
|
||||
import TimeTicketList from "../time-ticket-list/time-ticket-list.component";
|
||||
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
@@ -69,13 +71,7 @@ export function TimeTicketModalComponent({
|
||||
};
|
||||
|
||||
const MemoInput = ({ value, ...props }) => {
|
||||
return (
|
||||
<Input
|
||||
value={value?.startsWith("timetickets.") ? t(value) : value}
|
||||
{...props}
|
||||
disabled={value?.startsWith("timetickets.") || disabled}
|
||||
/>
|
||||
);
|
||||
return <Input value={value?.startsWith("timetickets.labels") ? t(value) : value} {...props} />;
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -333,7 +329,9 @@ export function LaborAllocationContainer({ jobid, loading, lineTicketData, hideT
|
||||
timetickets={lineTicketData.timetickets}
|
||||
adjustments={lineTicketData.jobs_by_pk.lbr_adjustments}
|
||||
/>
|
||||
{!hideTimeTickets && <TimeTicketList loading={loading} timetickets={lineTicketData.timetickets} techConsole />}
|
||||
{!hideTimeTickets && (
|
||||
<TimeTicketList loading={loading} timetickets={jobid ? lineTicketData.timetickets : []} techConsole />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ export default function TimeTicketShiftActive({ timetickets, refetch, isTechCons
|
||||
renderItem={(ticket) => (
|
||||
<List.Item>
|
||||
<Card
|
||||
title={t(ticket.memo)}
|
||||
title={ticket.memo?.startsWith("timetickets.labels") ? t(ticket.memo) : ticket.memo}
|
||||
actions={[
|
||||
<TechClockOffButton
|
||||
jobId={ticket.jobid}
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import { AlertOutlined } from "@ant-design/icons";
|
||||
import { Alert, Button, Col, Row, Space } from "antd";
|
||||
import { Alert, Button, Col, notification, Row, Space } from "antd";
|
||||
import i18n from "i18next";
|
||||
import React, { useEffect } from "react";
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectUpdateAvailable } from "../../redux/application/application.selectors";
|
||||
import { useRegisterSW } from "virtual:pwa-register/react";
|
||||
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
||||
import useCountDown from "../../utils/countdownHook";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
updateAvailable: selectUpdateAvailable
|
||||
@@ -19,6 +20,15 @@ const mapDispatchToProps = (dispatch) => ({
|
||||
|
||||
export function UpdateAlert({ updateAvailable }) {
|
||||
const { t } = useTranslation();
|
||||
const [timerStarted, setTimerStarted] = useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [
|
||||
timeLeft,
|
||||
{
|
||||
start //pause, resume, reset
|
||||
}
|
||||
] = useCountDown(180000, 1000);
|
||||
|
||||
const {
|
||||
offlineReady: [offlineReady],
|
||||
needRefresh: [needRefresh],
|
||||
@@ -31,7 +41,7 @@ export function UpdateAlert({ updateAvailable }) {
|
||||
() => {
|
||||
r.update();
|
||||
},
|
||||
10 * 60 * 1000
|
||||
30 * 60 * 1000
|
||||
);
|
||||
}
|
||||
},
|
||||
@@ -40,11 +50,43 @@ export function UpdateAlert({ updateAvailable }) {
|
||||
}
|
||||
});
|
||||
|
||||
const ReloadNewVersion = useCallback(() => {
|
||||
setLoading(true);
|
||||
updateServiceWorker(true);
|
||||
setTimeout(() => {
|
||||
window.location.reload(true);
|
||||
}, 5000);
|
||||
}, [updateServiceWorker]);
|
||||
|
||||
useEffect(() => {
|
||||
if (import.meta.env.DEV) {
|
||||
console.log(`SW Status => Refresh? ${needRefresh} - offlineReady? ${offlineReady}`);
|
||||
if (needRefresh) {
|
||||
start();
|
||||
setTimerStarted(true);
|
||||
}
|
||||
}, [needRefresh, offlineReady]);
|
||||
}, [start, needRefresh, offlineReady]);
|
||||
|
||||
useEffect(() => {
|
||||
if (needRefresh && timerStarted && timeLeft < 60000) {
|
||||
notification.open({
|
||||
type: "warning",
|
||||
closable: false,
|
||||
duration: 65000,
|
||||
key: "autoupdate",
|
||||
message: t("general.actions.autoupdate", {
|
||||
time: (timeLeft / 1000).toFixed(0),
|
||||
app: InstanceRenderManager({
|
||||
imex: "$t(titles.imexonline)",
|
||||
rome: "$t(titles.romeonline)",
|
||||
promanager: "$t(titles.promanager)"
|
||||
})
|
||||
}),
|
||||
placement: "bottomRight"
|
||||
});
|
||||
}
|
||||
if (needRefresh && timerStarted && timeLeft <= 0) {
|
||||
ReloadNewVersion();
|
||||
}
|
||||
}, [timeLeft, t, needRefresh, ReloadNewVersion, timerStarted]);
|
||||
|
||||
if (!needRefresh) return null;
|
||||
|
||||
@@ -75,9 +117,10 @@ export function UpdateAlert({ updateAvailable }) {
|
||||
<Button onClick={() => window.open("https://imex-online.noticeable.news/", "_blank")}>
|
||||
{i18n.t("general.actions.viewreleasenotes")}
|
||||
</Button>
|
||||
<Button type="primary" onClick={() => updateServiceWorker(true)}>
|
||||
{i18n.t("general.actions.refresh")}
|
||||
<Button loading={loading} type="primary" onClick={() => ReloadNewVersion()}>
|
||||
{i18n.t("general.actions.refresh")} {`(${(timeLeft / 1000).toFixed(0)} s)`}
|
||||
</Button>
|
||||
<Button onClick={() => start(300000)}>{i18n.t("general.actions.delay")}</Button>
|
||||
</Space>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
@@ -5,7 +5,7 @@ import PhoneNumberFormatter from "../../utils/PhoneFormatter";
|
||||
|
||||
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 [option, setOption] = useState(value);
|
||||
@@ -33,9 +33,25 @@ const VendorSearchSelect = ({ value, onChange, options, onSelect, disabled, pref
|
||||
if (!value || !options) return label;
|
||||
const discount = options?.find((o) => o.id === value)?.discount;
|
||||
return (
|
||||
<div className="imex-flex-row" style={{ width: "100%" }}>
|
||||
<div style={{ flex: 1 }}>{label}</div>
|
||||
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
flexWrap: "nowrap",
|
||||
width: "100%"
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
flex: 1,
|
||||
minWidth: 0,
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
whiteSpace: "nowrap"
|
||||
}}
|
||||
>
|
||||
{label}
|
||||
</div>
|
||||
{discount && discount !== 0 ? <Tag color="green">{`${discount * 100}%`}</Tag> : null}
|
||||
</div>
|
||||
);
|
||||
@@ -45,36 +61,67 @@ const VendorSearchSelect = ({ value, onChange, options, onSelect, disabled, pref
|
||||
optionFilterProp="name"
|
||||
onSelect={onSelect}
|
||||
disabled={disabled || false}
|
||||
optionLabelProp={"name"}
|
||||
optionLabelProp="name"
|
||||
>
|
||||
{favorites
|
||||
? favorites.map((o) => (
|
||||
<Option key={`favorite-${o.id}`} value={o.id} name={o.name} discount={o.discount}>
|
||||
<div className="imex-flex-row">
|
||||
<div style={{ flex: 1 }}>{o.name}</div>
|
||||
<Space style={{ marginLeft: "1rem" }}>
|
||||
<HeartOutlined style={{ color: "red" }} />
|
||||
{o.phone && showPhone && <PhoneNumberFormatter>{o.phone}</PhoneNumberFormatter>}
|
||||
{o.discount && o.discount !== 0 ? <Tag color="green">{`${o.discount * 100}%`}</Tag> : null}
|
||||
</Space>
|
||||
{favorites &&
|
||||
favorites.map((o) => (
|
||||
<Option key={`favorite-${o.id}`} value={o.id} name={o.name} discount={o.discount}>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
flexWrap: "nowrap",
|
||||
width: "100%"
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
flex: 1,
|
||||
minWidth: 0,
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
whiteSpace: "nowrap"
|
||||
}}
|
||||
>
|
||||
{o.name}
|
||||
</div>
|
||||
</Option>
|
||||
))
|
||||
: null}
|
||||
{options
|
||||
? options.map((o) => (
|
||||
<Option key={o.id} value={o.id} name={o.name} discount={o.discount}>
|
||||
<div className="imex-flex-row" style={{ width: "100%" }}>
|
||||
<div style={{ flex: 1 }}>{o.name}</div>
|
||||
|
||||
<Space style={{ marginLeft: "1rem" }}>
|
||||
{o.phone && showPhone && <PhoneNumberFormatter>{o.phone}</PhoneNumberFormatter>}
|
||||
{o.discount && o.discount !== 0 ? <Tag color="green">{`${o.discount * 100}%`}</Tag> : null}
|
||||
</Space>
|
||||
<Space style={{ marginLeft: "1rem" }}>
|
||||
<HeartOutlined style={{ color: "red" }} />
|
||||
{o.phone && showPhone && <PhoneNumberFormatter>{o.phone}</PhoneNumberFormatter>}
|
||||
{o.discount && o.discount !== 0 ? <Tag color="green">{`${o.discount * 100}%`}</Tag> : null}
|
||||
</Space>
|
||||
</div>
|
||||
</Option>
|
||||
))}
|
||||
{options &&
|
||||
options.map((o) => (
|
||||
<Option key={o.id} value={o.id} name={o.name} discount={o.discount}>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
flexWrap: "nowrap",
|
||||
width: "100%"
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
flex: 1,
|
||||
minWidth: 0,
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
whiteSpace: "nowrap"
|
||||
}}
|
||||
>
|
||||
{o.name}
|
||||
</div>
|
||||
</Option>
|
||||
))
|
||||
: null}
|
||||
<Space style={{ marginLeft: "1rem" }}>
|
||||
{o.phone && showPhone && <PhoneNumberFormatter>{o.phone}</PhoneNumberFormatter>}
|
||||
{o.discount && o.discount !== 0 ? <Tag color="green">{`${o.discount * 100}%`}</Tag> : null}
|
||||
</Space>
|
||||
</div>
|
||||
</Option>
|
||||
))}
|
||||
</Select>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -5,7 +5,6 @@ import { getFirestore } from "firebase/firestore";
|
||||
import { getMessaging, getToken, onMessage } from "firebase/messaging";
|
||||
import { store } from "../redux/store";
|
||||
import axios from "axios";
|
||||
import { checkBeta } from "../utils/handleBeta";
|
||||
|
||||
const config = JSON.parse(import.meta.env.VITE_APP_FIREBASE_CONFIG);
|
||||
initializeApp(config);
|
||||
@@ -88,7 +87,7 @@ export const logImEXEvent = (eventName, additionalParams, stateProp = null) => {
|
||||
operationName: eventName,
|
||||
variables: additionalParams,
|
||||
dbevent: false,
|
||||
env: checkBeta() ? "beta" : "master"
|
||||
env: `master-AIO|${import.meta.env.VITE_APP_GIT_SHA_DATE}`
|
||||
});
|
||||
// console.log(
|
||||
// "%c[Analytics]",
|
||||
|
||||
@@ -138,7 +138,8 @@ export const QUERY_BODYSHOP = gql`
|
||||
tt_enforce_hours_for_tech_console
|
||||
md_tasks_presets
|
||||
use_paint_scale_data
|
||||
md_ro_guard
|
||||
intellipay_config
|
||||
md_ro_guard
|
||||
employee_teams(order_by: { name: asc }, where: { active: { _eq: true } }) {
|
||||
id
|
||||
name
|
||||
@@ -266,7 +267,8 @@ export const UPDATE_SHOP = gql`
|
||||
enforce_conversion_category
|
||||
tt_enforce_hours_for_tech_console
|
||||
md_tasks_presets
|
||||
md_ro_guard
|
||||
intellipay_config
|
||||
md_ro_guard
|
||||
employee_teams(order_by: { name: asc }, where: { active: { _eq: true } }) {
|
||||
id
|
||||
name
|
||||
|
||||
@@ -2461,6 +2461,14 @@ export const SUBSCRIPTION_JOBS_IN_PRODUCTION = gql`
|
||||
}
|
||||
}
|
||||
`;
|
||||
export const SUBSCRIPTION_JOBS_IN_PRODUCTION_VIEW = gql`
|
||||
subscription SUBSCRIPTION_JOBS_IN_PRODUCTION_VIEW {
|
||||
jobs: jobs_inproduction {
|
||||
id
|
||||
updated_at
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const QUERY_JOBS_IN_PRODUCTION = gql`
|
||||
query QUERY_JOBS_IN_PRODUCTION {
|
||||
|
||||
@@ -1,6 +1,26 @@
|
||||
import React from "react";
|
||||
import ProductionBoardKanbanContainer from "../../components/production-board-kanban/production-board-kanban.container";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
//currentUser: selectCurrentUser
|
||||
bodyshop: selectBodyshop
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
});
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(ProductionBoardComponent);
|
||||
|
||||
export default function ProductionBoardComponent() {
|
||||
return <ProductionBoardKanbanContainer />;
|
||||
export function ProductionBoardComponent({ bodyshop }) {
|
||||
const {
|
||||
treatments: { Production_Use_View }
|
||||
} = useSplitTreatments({
|
||||
attributes: {},
|
||||
names: ["Production_Use_View"],
|
||||
splitKey: bodyshop && bodyshop.imexshopid
|
||||
});
|
||||
|
||||
return <ProductionBoardKanbanContainer subscriptionType={Production_Use_View.treatment} />;
|
||||
}
|
||||
|
||||
@@ -2,11 +2,31 @@ import React from "react";
|
||||
import NoteUpsertModal from "../../components/note-upsert-modal/note-upsert-modal.container";
|
||||
import ProductionListTable from "../../components/production-list-table/production-list-table.container";
|
||||
|
||||
export default function ProductionListComponent() {
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
});
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(ProductionListComponent);
|
||||
|
||||
export function ProductionListComponent({ bodyshop }) {
|
||||
const {
|
||||
treatments: { Production_Use_View }
|
||||
} = useSplitTreatments({
|
||||
attributes: {},
|
||||
names: ["Production_Use_View"],
|
||||
splitKey: bodyshop && bodyshop.imexshopid
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<NoteUpsertModal />
|
||||
<ProductionListTable />
|
||||
<ProductionListTable subscriptionType={Production_Use_View.treatment} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
import { Tabs } from "antd";
|
||||
import React, { useEffect } from "react";
|
||||
import { useLocation, useNavigate } from "react-router-dom";
|
||||
import queryString from "query-string";
|
||||
import React, { useEffect } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { useLocation, useNavigate } from "react-router-dom";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
|
||||
import ShopCsiConfig from "../../components/shop-csi-config/shop-csi-config.component";
|
||||
import ShopEmployeesContainer from "../../components/shop-employees/shop-employees.container";
|
||||
import ShopInfoContainer from "../../components/shop-info/shop-info.container";
|
||||
import ShopCsiConfig from "../../components/shop-csi-config/shop-csi-config.component";
|
||||
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
|
||||
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import ShopInfoUsersComponent from "../../components/shop-users/shop-users.component";
|
||||
import { setBreadcrumbs, setSelectedHeader } from "../../redux/application/application.actions";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import ShopInfoUsersComponent from "../../components/shop-users/shop-users.component";
|
||||
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
||||
|
||||
import ShopTeamsContainer from "../../components/shop-teams/shop-teams.container";
|
||||
import { HasFeatureAccess } from "../../components/feature-wrapper/feature-wrapper.component";
|
||||
import ShopTeamsContainer from "../../components/shop-teams/shop-teams.container";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop
|
||||
|
||||
@@ -3,6 +3,7 @@ import { useTranslation } from "react-i18next";
|
||||
import RbacWrapperComponent from "../../components/rbac-wrapper/rbac-wrapper.component";
|
||||
import TechLookupJobsList from "../../components/tech-lookup-jobs-list/tech-lookup-jobs-list.component";
|
||||
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
||||
import TechLookupJobsDrawer from "../../components/tech-lookup-jobs-drawer/tech-lookup-jobs-drawer.component";
|
||||
|
||||
export default function TechLookupContainer() {
|
||||
const { t } = useTranslation();
|
||||
@@ -20,6 +21,7 @@ export default function TechLookupContainer() {
|
||||
return (
|
||||
<div>
|
||||
<RbacWrapperComponent action="jobs:list-active">
|
||||
<TechLookupJobsDrawer />
|
||||
<TechLookupJobsList />
|
||||
</RbacWrapperComponent>
|
||||
</div>
|
||||
|
||||
@@ -9,7 +9,6 @@ import ErrorBoundary from "../../components/error-boundary/error-boundary.compon
|
||||
import FeatureWrapper from "../../components/feature-wrapper/feature-wrapper.component";
|
||||
import LoadingSpinner from "../../components/loading-spinner/loading-spinner.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 UpdateAlert from "../../components/update-alert/update-alert.component";
|
||||
import { selectTechnician } from "../../redux/tech/tech.selectors";
|
||||
@@ -68,7 +67,7 @@ export function TechPage({ technician }) {
|
||||
<Layout>
|
||||
<UpdateAlert />
|
||||
<TechHeader />
|
||||
<TechLookupJobsDrawer />
|
||||
|
||||
<TaskUpsertModalContainer />
|
||||
<Content className="tech-content-container">
|
||||
<ErrorBoundary>
|
||||
|
||||
@@ -36,7 +36,8 @@ export function* openChatByPhone({ payload }) {
|
||||
data: { conversations }
|
||||
} = yield client.query({
|
||||
query: CONVERSATION_ID_BY_PHONE,
|
||||
variables: { phone: p.number }
|
||||
variables: { phone: p.number },
|
||||
fetchPolicy: 'no-cache'
|
||||
});
|
||||
|
||||
if (conversations.length === 0) {
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
signInWithEmailAndPassword,
|
||||
signOut
|
||||
} from "firebase/auth";
|
||||
import { doc, getDoc, setDoc } from "firebase/firestore";
|
||||
import { arrayUnion, doc, getDoc, setDoc, updateDoc } from "firebase/firestore";
|
||||
import { getToken } from "firebase/messaging";
|
||||
import i18next from "i18next";
|
||||
import LogRocket from "logrocket";
|
||||
@@ -48,6 +48,7 @@ import {
|
||||
validatePasswordResetSuccess
|
||||
} from "./user.actions";
|
||||
import UserActionTypes from "./user.types";
|
||||
import cleanAxios from "../../utils/CleanAxios";
|
||||
|
||||
const fpPromise = FingerprintJS.load();
|
||||
|
||||
@@ -177,10 +178,24 @@ export function* setInstanceIdSaga({ payload: uid }) {
|
||||
// Get the visitor identifier when you need it.
|
||||
const fp = yield fpPromise;
|
||||
const result = yield fp.get();
|
||||
yield setDoc(userInstanceRef, {
|
||||
timestamp: new Date(),
|
||||
fingerprint: result.visitorId
|
||||
});
|
||||
const res = yield cleanAxios.get("https://api.ipify.org/?format=json");
|
||||
const udoc = yield getDoc(userInstanceRef);
|
||||
|
||||
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 delay(5 * 60 * 1000);
|
||||
|
||||
@@ -230,7 +230,7 @@
|
||||
"markexported": "Mark Exported",
|
||||
"markforreexport": "Mark for Re-export",
|
||||
"new": "New Bill",
|
||||
"nobilllines": "This part has not yet been recieved.",
|
||||
"nobilllines": "",
|
||||
"noneselected": "No bill selected.",
|
||||
"onlycmforinvoiced": "Only credit memos can be entered for any Job that has been invoiced, exported, or voided.",
|
||||
"printlabels": "Print Labels",
|
||||
@@ -270,9 +270,9 @@
|
||||
"testrender": "Test Render"
|
||||
},
|
||||
"errors": {
|
||||
"creatingdefaultview": "Error creating default view.",
|
||||
"loading": "Unable to load shop details. Please call technical support.",
|
||||
"saving": "Error encountered while saving. {{message}}",
|
||||
"creatingdefaultview": "Error creating default view."
|
||||
"saving": "Error encountered while saving. {{message}}"
|
||||
},
|
||||
"fields": {
|
||||
"ReceivableCustomField": "QBO Receivable Custom Field {{number}}",
|
||||
@@ -285,19 +285,21 @@
|
||||
},
|
||||
"appt_length": "Default Appointment Length",
|
||||
"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_federal_tax_rate": "Bills - Federal Tax Rate %",
|
||||
"bill_local_tax_rate": "Bill - Local Tax Rate %",
|
||||
"bill_state_tax_rate": "Bill - Provincial/State Tax Rate %",
|
||||
"city": "City",
|
||||
"closingperiod": "Closing Period",
|
||||
"companycode": "ADP Company Code",
|
||||
"country": "Country",
|
||||
"dailybodytarget": "Scoreboard - Daily Body Target",
|
||||
"dailypainttarget": "Scoreboard - Daily Paint Target",
|
||||
"default_adjustment_rate": "Default Labor Deduction Adjustment Rate",
|
||||
"deliver": {
|
||||
"templates": "Delivery Templates",
|
||||
"require_actual_delivery_date": "Require Actual Delivery"
|
||||
"require_actual_delivery_date": "Require Actual Delivery",
|
||||
"templates": "Delivery Templates"
|
||||
},
|
||||
"dms": {
|
||||
"apcontrol": "AP Control Number",
|
||||
@@ -330,6 +332,10 @@
|
||||
"next_contact_hours": "Automatic Next Contact Date - Hours from Intake",
|
||||
"templates": "Intake Templates"
|
||||
},
|
||||
"intellipay_config": {
|
||||
"cash_discount_percentage": "Cash Discount %",
|
||||
"enable_cash_discount": "Enable Cash Discounting"
|
||||
},
|
||||
"invoice_federal_tax_rate": "Invoices - Federal Tax Rate",
|
||||
"invoice_local_tax_rate": "Invoices - Local Tax Rate",
|
||||
"invoice_state_tax_rate": "Invoices - State Tax Rate",
|
||||
@@ -661,6 +667,8 @@
|
||||
"filehandlers": "Adjusters",
|
||||
"insurancecos": "Insurance Companies",
|
||||
"intakechecklist": "Intake Checklist",
|
||||
"intellipay": "IntelliPay",
|
||||
"intellipay_cash_discount": "Please ensure that cash discounting has been enabled on your merchant account. Reach out to IntelliPay Support if you need assistance. ",
|
||||
"jobstatuses": "Job Statuses",
|
||||
"laborrates": "Labor Rates",
|
||||
"licensing": "Licensing",
|
||||
@@ -700,10 +708,10 @@
|
||||
"workingdays": "Working Days"
|
||||
},
|
||||
"successes": {
|
||||
"save": "Shop configuration saved successfully. ",
|
||||
"unsavedchanges": "Unsaved changes will be lost. Are you sure you want to continue?",
|
||||
"areyousure": "Are you sure you want to continue?",
|
||||
"defaultviewcreated": "Default view created 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": {
|
||||
"centermustexist": "The chosen responsibility center does not exist.",
|
||||
@@ -1133,8 +1141,8 @@
|
||||
},
|
||||
"general": {
|
||||
"actions": {
|
||||
"defaults": "Defaults",
|
||||
"add": "Add",
|
||||
"autoupdate": "{{app}} will automatically update in {{time}} seconds. Please save all changes.",
|
||||
"calculate": "Calculate",
|
||||
"cancel": "Cancel",
|
||||
"clear": "Clear",
|
||||
@@ -1142,6 +1150,8 @@
|
||||
"copied": "Copied!",
|
||||
"copylink": "Copy Link",
|
||||
"create": "Create",
|
||||
"defaults": "Defaults",
|
||||
"delay": "Delay Update (5 mins)",
|
||||
"delete": "Delete",
|
||||
"deleteall": "Delete All",
|
||||
"deselectall": "Deselect All",
|
||||
@@ -1153,10 +1163,12 @@
|
||||
"print": "Print",
|
||||
"refresh": "Refresh",
|
||||
"remove": "Remove",
|
||||
"remove_alert": "Are you sure you want to dismiss the alert?",
|
||||
"reset": "Reset your changes.",
|
||||
"resetpassword": "Reset Password",
|
||||
"save": "Save",
|
||||
"saveandnew": "Save and New",
|
||||
"saveas": "Save As",
|
||||
"selectall": "Select All",
|
||||
"send": "Send",
|
||||
"sendbysms": "Send by SMS",
|
||||
@@ -1164,9 +1176,7 @@
|
||||
"submit": "Submit",
|
||||
"tryagain": "Try Again",
|
||||
"view": "View",
|
||||
"viewreleasenotes": "See What's Changed",
|
||||
"remove_alert": "Are you sure you want to dismiss the alert?",
|
||||
"saveas": "Save As"
|
||||
"viewreleasenotes": "See What's Changed"
|
||||
},
|
||||
"errors": {
|
||||
"fcm": "You must allow notification permissions to have real time messaging. Click to try again.",
|
||||
@@ -1181,7 +1191,6 @@
|
||||
"vehicle": "Vehicle"
|
||||
},
|
||||
"labels": {
|
||||
"unsavedchanges": "Unsaved changes.",
|
||||
"actions": "Actions",
|
||||
"areyousure": "Are you sure?",
|
||||
"barcode": "Barcode",
|
||||
@@ -1189,6 +1198,8 @@
|
||||
"clear": "Clear",
|
||||
"confirmpassword": "Confirm Password",
|
||||
"created_at": "Created At",
|
||||
"date": "Select Date",
|
||||
"datetime": "Select Date & Time",
|
||||
"email": "Email",
|
||||
"errors": "Errors",
|
||||
"excel": "Excel",
|
||||
@@ -1248,6 +1259,7 @@
|
||||
"tuesday": "Tuesday",
|
||||
"tvmode": "TV Mode",
|
||||
"unknown": "Unknown",
|
||||
"unsavedchanges": "Unsaved changes.",
|
||||
"username": "Username",
|
||||
"view": "View",
|
||||
"wednesday": "Wednesday",
|
||||
@@ -1361,6 +1373,7 @@
|
||||
},
|
||||
"job_payments": {
|
||||
"buttons": {
|
||||
"create_short_link": "Generate Short Link",
|
||||
"goback": "Go Back",
|
||||
"proceedtopayment": "Proceed to Payment",
|
||||
"refundpayment": "Refund Payment"
|
||||
@@ -2737,41 +2750,6 @@
|
||||
}
|
||||
},
|
||||
"production": {
|
||||
"constants":{
|
||||
"main_profile": "Default"
|
||||
},
|
||||
"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": {
|
||||
"addcolumns": "Add Columns",
|
||||
"bodypriority-clear": "Clear Body Priority",
|
||||
@@ -2786,29 +2764,23 @@
|
||||
"suspend": "Suspend",
|
||||
"unsuspend": "Unsuspend"
|
||||
},
|
||||
"constants": {
|
||||
"main_profile": "Default"
|
||||
},
|
||||
"errors": {
|
||||
"boardupdate": "Error encountered updating Job. {{message}}",
|
||||
"removing": "Error removing from production board. {{error}}",
|
||||
"settings": "Error saving board settings: {{error}}",
|
||||
"name_exists": "A Profile with this name already exists. Please choose a different name.",
|
||||
"name_required": "Profile name is required."
|
||||
"name_required": "Profile name is required.",
|
||||
"removing": "Error removing from production board. {{error}}",
|
||||
"settings": "Error saving board settings: {{error}}"
|
||||
},
|
||||
"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",
|
||||
"addnewprofile": "Add New Profile",
|
||||
"alert": "Alert",
|
||||
"tasks": "Tasks",
|
||||
"alertoff": "Remove alert from Job",
|
||||
"alerton": "Add alert to Job",
|
||||
"alerts": "Alerts",
|
||||
"ats": "Alternative Transportation",
|
||||
"bodyhours": "B",
|
||||
"bodypriority": "B/P",
|
||||
@@ -2818,6 +2790,7 @@
|
||||
"qbo_usa": "QBO USA"
|
||||
}
|
||||
},
|
||||
"card_size": "Card Size",
|
||||
"cardcolor": "Colored Cards",
|
||||
"cardsettings": "Card Settings",
|
||||
"clm_no": "Claim Number",
|
||||
@@ -2826,48 +2799,88 @@
|
||||
"detailpriority": "D/P",
|
||||
"employeeassignments": "Employee Assignments",
|
||||
"employeesearch": "Employee Search",
|
||||
"estimator": "Estimator",
|
||||
"horizontal": "Horizontal",
|
||||
"ins_co_nm": "Insurance Company Name",
|
||||
"jobdetail": "Job Details",
|
||||
"kiosk_mode": "Kiosk Mode",
|
||||
"laborhrs": "Labor Hours",
|
||||
"legend": "Legend:",
|
||||
"model_info": "Vehicle Info",
|
||||
"note": "Production Note",
|
||||
"off": "Off",
|
||||
"on": "On",
|
||||
"orientation": "Board Orientation",
|
||||
"ownr_nm": "Customer Name",
|
||||
"paintpriority": "P/P",
|
||||
"partsstatus": "Parts Status",
|
||||
"estimator": "Estimator",
|
||||
"subtotal": "Subtotal",
|
||||
"production_note": "Production Note",
|
||||
"refinishhours": "R",
|
||||
"scheduled_completion": "Scheduled Completion",
|
||||
"selectview": "Select a View",
|
||||
"stickyheader": "Sticky Header (BETA)",
|
||||
"sublets": "Sublets",
|
||||
"subtotal": "Subtotal",
|
||||
"tall": "Tall",
|
||||
"tasks": "Tasks",
|
||||
"totalhours": "Total Hrs ",
|
||||
"touchtime": "T/T",
|
||||
"vertical": "Vertical",
|
||||
"viewname": "View Name",
|
||||
"alerts": "Alerts",
|
||||
"addnewprofile": "Add New Profile"
|
||||
"wide": "Wide"
|
||||
},
|
||||
"options": {
|
||||
"horizontal": "Horizontal",
|
||||
"large": "Large",
|
||||
"medium": "Medium",
|
||||
"small": "Small",
|
||||
"vertical": "Vertical"
|
||||
},
|
||||
"settings": {
|
||||
"board_settings": "Board Settings",
|
||||
"filters": {
|
||||
"md_estimators": "Estimators",
|
||||
"md_ins_cos": "Insurance Companies"
|
||||
},
|
||||
"filters_title": "Filters",
|
||||
"information": "Information",
|
||||
"layout": "Layout",
|
||||
"statistics": {
|
||||
"jobs_in_production": "Jobs in Production",
|
||||
"tasks_in_production": "Tasks in Production",
|
||||
"tasks_on_board": "Tasks on Board",
|
||||
"total_amount_in_production": "Dollars in Production",
|
||||
"total_amount_on_board": "Dollars on Board",
|
||||
"total_hours_in_production": "Hours in Production",
|
||||
"total_hours_on_board": "Hours on Board",
|
||||
"total_jobs_on_board": "Jobs on Board",
|
||||
"total_lab_in_production": "Body Hours in Production",
|
||||
"total_lab_on_board": "Body Hours on Board",
|
||||
"total_lar_in_production": "Refinish Hours in Production",
|
||||
"total_lar_on_board": "Refinish Hours on Board"
|
||||
},
|
||||
"statistics_title": "Statistics"
|
||||
},
|
||||
"statistics": {
|
||||
"currency_symbol": "$",
|
||||
"hours": "Hours",
|
||||
"jobs": "Jobs",
|
||||
"jobs_in_production": "Jobs in Production",
|
||||
"tasks": "Tasks",
|
||||
"tasks_in_production": "Tasks in Production",
|
||||
"tasks_on_board": "Tasks on Board",
|
||||
"total_amount_in_production": "Dollars in Production",
|
||||
"total_amount_on_board": "Dollars on Board",
|
||||
"total_hours_in_production": "Hours in Production",
|
||||
"total_hours_on_board": "Hours on Board",
|
||||
"total_jobs_on_board": "Jobs on Board",
|
||||
"total_lab_in_production": "Body Hours in Production",
|
||||
"total_lab_on_board": "Body Hours on Board",
|
||||
"total_lar_in_production": "Refinish Hours in Production",
|
||||
"total_lar_on_board": "Refinish Hours on Board"
|
||||
},
|
||||
"successes": {
|
||||
"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": {
|
||||
@@ -2925,6 +2938,8 @@
|
||||
"vendor": "Vendor"
|
||||
},
|
||||
"templates": {
|
||||
"adp_payroll_flat": "ADP Payroll - Flat Rate",
|
||||
"adp_payroll_straight": "ADP Payroll - Straight Time",
|
||||
"anticipated_revenue": "Anticipated Revenue",
|
||||
"ar_aging": "AR Aging",
|
||||
"attendance_detail": "Attendance (All Employees)",
|
||||
@@ -3416,6 +3431,18 @@
|
||||
"vehicledetail": "Vehicle Details {{vehicle}} | {{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": {
|
||||
"actions": {
|
||||
"approveselected": "Approve Selected"
|
||||
@@ -3554,18 +3581,6 @@
|
||||
"validation": {
|
||||
"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
File diff suppressed because it is too large
Load Diff
@@ -2158,6 +2158,32 @@ export const TemplateList = (type, context) => {
|
||||
field: i18n.t("tasks.fields.created_at")
|
||||
},
|
||||
group: "jobs"
|
||||
},
|
||||
adp_payroll_flat: {
|
||||
title: i18n.t("reportcenter.templates.adp_payroll_flat"),
|
||||
subject: i18n.t("reportcenter.templates.adp_payroll_flat"),
|
||||
key: "adp_payroll_flat",
|
||||
reporttype: "text",
|
||||
disabled: false,
|
||||
rangeFilter: {
|
||||
object: i18n.t("reportcenter.labels.objects.timetickets"),
|
||||
field: i18n.t("timetickets.fields.committed_at")
|
||||
},
|
||||
group: "payroll",
|
||||
adp_payroll: true
|
||||
},
|
||||
adp_payroll_straight: {
|
||||
title: i18n.t("reportcenter.templates.adp_payroll_straight"),
|
||||
subject: i18n.t("reportcenter.templates.adp_payroll_straight"),
|
||||
key: "adp_payroll_straight",
|
||||
reporttype: "text",
|
||||
disabled: false,
|
||||
rangeFilter: {
|
||||
object: i18n.t("reportcenter.labels.objects.timetickets"),
|
||||
field: i18n.t("timetickets.fields.date")
|
||||
},
|
||||
group: "payroll",
|
||||
adp_payroll: true
|
||||
}
|
||||
}
|
||||
: {}),
|
||||
|
||||
84
client/src/utils/countdownHook.js
Normal file
84
client/src/utils/countdownHook.js
Normal file
@@ -0,0 +1,84 @@
|
||||
import React from "react";
|
||||
|
||||
const useCountDown = (timeToCount = 60 * 1000, interval = 1000) => {
|
||||
const [timeLeft, setTimeLeft] = React.useState(0);
|
||||
const timer = React.useRef({});
|
||||
|
||||
const run = (ts) => {
|
||||
if (!timer.current.started) {
|
||||
timer.current.started = ts;
|
||||
timer.current.lastInterval = ts;
|
||||
}
|
||||
|
||||
const localInterval = Math.min(interval, timer.current.timeLeft || Infinity);
|
||||
if (ts - timer.current.lastInterval >= localInterval) {
|
||||
timer.current.lastInterval += localInterval;
|
||||
setTimeLeft((timeLeft) => {
|
||||
timer.current.timeLeft = timeLeft - localInterval;
|
||||
return timer.current.timeLeft;
|
||||
});
|
||||
}
|
||||
|
||||
if (ts - timer.current.started < timer.current.timeToCount) {
|
||||
timer.current.requestId = window.requestAnimationFrame(run);
|
||||
} else {
|
||||
timer.current = {};
|
||||
setTimeLeft(0);
|
||||
}
|
||||
};
|
||||
|
||||
const start = React.useCallback(
|
||||
(ttc) => {
|
||||
window.cancelAnimationFrame(timer.current.requestId);
|
||||
|
||||
const newTimeToCount = ttc !== undefined ? ttc : timeToCount;
|
||||
timer.current.started = null;
|
||||
timer.current.lastInterval = null;
|
||||
timer.current.timeToCount = newTimeToCount;
|
||||
timer.current.requestId = window.requestAnimationFrame(run);
|
||||
|
||||
setTimeLeft(newTimeToCount);
|
||||
},
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[]
|
||||
);
|
||||
|
||||
const pause = React.useCallback(() => {
|
||||
window.cancelAnimationFrame(timer.current.requestId);
|
||||
timer.current.started = null;
|
||||
timer.current.lastInterval = null;
|
||||
timer.current.timeToCount = timer.current.timeLeft;
|
||||
}, []);
|
||||
|
||||
const resume = React.useCallback(
|
||||
() => {
|
||||
if (!timer.current.started && timer.current.timeLeft > 0) {
|
||||
window.cancelAnimationFrame(timer.current.requestId);
|
||||
timer.current.requestId = window.requestAnimationFrame(run);
|
||||
}
|
||||
},
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[]
|
||||
);
|
||||
|
||||
const reset = React.useCallback(() => {
|
||||
if (timer.current.timeLeft) {
|
||||
window.cancelAnimationFrame(timer.current.requestId);
|
||||
timer.current = {};
|
||||
setTimeLeft(0);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const actions = React.useMemo(
|
||||
() => ({ start, pause, resume, reset }), // eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[]
|
||||
);
|
||||
|
||||
React.useEffect(() => {
|
||||
return () => window.cancelAnimationFrame(timer.current.requestId);
|
||||
}, []);
|
||||
|
||||
return [timeLeft, actions];
|
||||
};
|
||||
|
||||
export default useCountDown;
|
||||
@@ -1,47 +0,0 @@
|
||||
export const BETA_KEY = "betaSwitchImex";
|
||||
|
||||
export const checkBeta = () => {
|
||||
const cookie = document.cookie.split("; ").find((row) => row.startsWith(BETA_KEY));
|
||||
return cookie ? cookie.split("=")[1] === "true" : false;
|
||||
};
|
||||
|
||||
export const setBeta = (value) => {
|
||||
const domain = window.location.hostname.split(".").slice(-2).join(".");
|
||||
document.cookie = `${BETA_KEY}=${value}; path=/; domain=.${domain}`;
|
||||
};
|
||||
|
||||
export const handleBeta = () => {
|
||||
if (window.location.hostname.startsWith("localhost")) {
|
||||
console.log("Not on beta or test, so no need to handle beta.");
|
||||
return;
|
||||
}
|
||||
|
||||
const isBeta = checkBeta();
|
||||
const currentHostName = window.location.hostname;
|
||||
|
||||
// Determine if the host name starts with "beta" or "www.beta"
|
||||
const isBetaHost = currentHostName.startsWith("beta.");
|
||||
const isBetaHostWithWWW = currentHostName.startsWith("www.beta.");
|
||||
|
||||
if (isBeta) {
|
||||
// If beta is on and we are not on a beta domain, redirect to the beta version
|
||||
if (!isBetaHost && !isBetaHostWithWWW) {
|
||||
const newHostName = currentHostName.startsWith("www.")
|
||||
? `www.beta.${currentHostName.replace(/^www\./, "")}`
|
||||
: `beta.${currentHostName}`;
|
||||
const href = `${window.location.protocol}//${newHostName}${window.location.pathname}${window.location.search}${window.location.hash}`;
|
||||
window.location.replace(href);
|
||||
}
|
||||
// Otherwise, if beta is on and we're already on a beta domain, stay there
|
||||
} else {
|
||||
// If beta is off and we are on a beta domain, redirect to the non-beta version
|
||||
if (isBetaHost || isBetaHostWithWWW) {
|
||||
const newHostName = currentHostName.replace(/^www\.beta\./, "www.").replace(/^beta\./, "");
|
||||
const href = `${window.location.protocol}//${newHostName}${window.location.pathname}${window.location.search}${window.location.hash}`;
|
||||
window.location.replace(href);
|
||||
}
|
||||
// Otherwise, if beta is off and we're not on a beta domain, stay there
|
||||
}
|
||||
};
|
||||
|
||||
export default handleBeta;
|
||||
@@ -1,5 +1,5 @@
|
||||
version: 2
|
||||
endpoint: https://db.dev.bodyshop.app
|
||||
endpoint: https://db.dev.imex.online
|
||||
admin_secret: Dev-BodyShopApp!
|
||||
metadata_directory: metadata
|
||||
actions:
|
||||
|
||||
@@ -1,3 +1,27 @@
|
||||
- name: AutoHouse Data Pump
|
||||
webhook: '{{HASURA_API_URL}}/data/ah'
|
||||
schedule: 0 6 * * *
|
||||
include_in_metadata: true
|
||||
payload: {}
|
||||
headers:
|
||||
- name: x-imex-auth
|
||||
value_from_env: DATAPUMP_AUTH
|
||||
- name: Claimscorp Data Pump
|
||||
webhook: '{{HASURA_API_URL}}/data/cc'
|
||||
schedule: 30 6 * * *
|
||||
include_in_metadata: true
|
||||
payload: {}
|
||||
headers:
|
||||
- name: x-imex-auth
|
||||
value_from_env: DATAPUMP_AUTH
|
||||
- name: Kaizen Data Pump
|
||||
webhook: '{{HASURA_API_URL}}/data/kaizen'
|
||||
schedule: 30 5 * * *
|
||||
include_in_metadata: true
|
||||
payload: {}
|
||||
headers:
|
||||
- name: x-imex-auth
|
||||
value_from_env: DATAPUMP_AUTH
|
||||
- name: Task Reminders
|
||||
webhook: '{{HASURA_API_URL}}/tasks-remind-handler'
|
||||
schedule: '*/15 * * * *'
|
||||
|
||||
@@ -918,6 +918,7 @@
|
||||
- bill_tax_rates
|
||||
- cdk_configuration
|
||||
- cdk_dealerid
|
||||
- chatterid
|
||||
- city
|
||||
- claimscorpid
|
||||
- convenient_company
|
||||
@@ -939,6 +940,7 @@
|
||||
- inhousevendorid
|
||||
- insurance_vendor_id
|
||||
- intakechecklist
|
||||
- intellipay_config
|
||||
- jc_hourly_rates
|
||||
- jobsizelimit
|
||||
- last_name_first
|
||||
@@ -1040,6 +1042,7 @@
|
||||
- inhousevendorid
|
||||
- insurance_vendor_id
|
||||
- intakechecklist
|
||||
- intellipay_config
|
||||
- jc_hourly_rates
|
||||
- last_name_first
|
||||
- localmediaserverhttp
|
||||
@@ -4240,6 +4243,63 @@
|
||||
- active:
|
||||
_eq: true
|
||||
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
|
||||
definition:
|
||||
enable_manual: true
|
||||
@@ -4299,6 +4359,35 @@
|
||||
template_engine: Kriti
|
||||
url: '{{$base_url}}/opensearch'
|
||||
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:
|
||||
name: masterdata
|
||||
schema: public
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
-- Could not auto-generate a down migration.
|
||||
-- Please write an appropriate down migration for the SQL below:
|
||||
-- alter table "public"."bodyshops" add column "intellipay_config" jsonb
|
||||
-- not null default jsonb_build_object();
|
||||
@@ -0,0 +1,2 @@
|
||||
alter table "public"."bodyshops" add column "intellipay_config" jsonb
|
||||
not null default jsonb_build_object();
|
||||
@@ -0,0 +1,4 @@
|
||||
-- Could not auto-generate a down migration.
|
||||
-- Please write an appropriate down migration for the SQL below:
|
||||
-- alter table "public"."bodyshops" add column "chatterid" text
|
||||
-- null;
|
||||
@@ -0,0 +1,2 @@
|
||||
alter table "public"."bodyshops" add column "chatterid" text
|
||||
null;
|
||||
@@ -0,0 +1,2 @@
|
||||
CREATE INDEX "courtesycars_idx_fleet" on
|
||||
"public"."courtesycars" using btree ("fleetnumber");
|
||||
@@ -0,0 +1 @@
|
||||
DROP INDEX IF EXISTS "public"."courtesycars_idx_fleet";
|
||||
@@ -0,0 +1,2 @@
|
||||
CREATE INDEX "idx_jobs_ownrfn" on
|
||||
"public"."jobs" using gin ("ownr_fn");
|
||||
@@ -0,0 +1 @@
|
||||
DROP INDEX IF EXISTS "public"."idx_jobs_ownrfn";
|
||||
@@ -0,0 +1,2 @@
|
||||
CREATE INDEX "idx_jobs_ownrln" on
|
||||
"public"."jobs" using gin ("ownr_ln");
|
||||
@@ -0,0 +1 @@
|
||||
DROP INDEX IF EXISTS "public"."idx_jobs_ownrln";
|
||||
@@ -0,0 +1,2 @@
|
||||
CREATE INDEX "jobs_idx_iouparent" on
|
||||
"public"."jobs" using btree ("iouparent");
|
||||
@@ -0,0 +1 @@
|
||||
DROP INDEX IF EXISTS "public"."jobs_idx_iouparent";
|
||||
@@ -0,0 +1,2 @@
|
||||
CREATE INDEX "idx_jobs_ronumber" on
|
||||
"public"."jobs" using gin ("ro_number");
|
||||
@@ -0,0 +1 @@
|
||||
DROP INDEX IF EXISTS "public"."idx_jobs_ronumber";
|
||||
@@ -0,0 +1,2 @@
|
||||
CREATE INDEX "idx_jobs_clmno" on
|
||||
"public"."jobs" using gin ("clm_no");
|
||||
@@ -0,0 +1 @@
|
||||
DROP INDEX IF EXISTS "public"."idx_jobs_clmno";
|
||||
@@ -0,0 +1,2 @@
|
||||
CREATE INDEX "idx_jobs_vmodeldesc" on
|
||||
"public"."jobs" using gin ("v_model_desc");
|
||||
@@ -0,0 +1 @@
|
||||
DROP INDEX IF EXISTS "public"."idx_jobs_vmodeldesc";
|
||||
@@ -0,0 +1,2 @@
|
||||
CREATE INDEX "idx_jobs_vmakedesc" on
|
||||
"public"."jobs" using gin ("v_make_desc");
|
||||
@@ -0,0 +1 @@
|
||||
DROP INDEX IF EXISTS "public"."idx_jobs_vmakedesc";
|
||||
@@ -0,0 +1,2 @@
|
||||
CREATE INDEX "idx_jobs_plateno" on
|
||||
"public"."jobs" using gin ("plate_no");
|
||||
@@ -0,0 +1 @@
|
||||
DROP INDEX IF EXISTS "public"."idx_jobs_plateno";
|
||||
11
hasura/migrations/1726773072213_run_sql_migration/down.sql
Normal file
11
hasura/migrations/1726773072213_run_sql_migration/down.sql
Normal file
@@ -0,0 +1,11 @@
|
||||
-- Could not auto-generate a down migration.
|
||||
-- Please write an appropriate down migration for the SQL below:
|
||||
-- CREATE
|
||||
-- OR REPLACE VIEW "public"."jobs_inproduction" AS
|
||||
-- SELECT
|
||||
-- j.id,
|
||||
-- j.updated_at
|
||||
-- FROM
|
||||
-- jobs j
|
||||
-- WHERE
|
||||
-- j.inproduction=true;
|
||||
9
hasura/migrations/1726773072213_run_sql_migration/up.sql
Normal file
9
hasura/migrations/1726773072213_run_sql_migration/up.sql
Normal file
@@ -0,0 +1,9 @@
|
||||
CREATE
|
||||
OR REPLACE VIEW "public"."jobs_inproduction" AS
|
||||
SELECT
|
||||
j.id,
|
||||
j.updated_at
|
||||
FROM
|
||||
jobs j
|
||||
WHERE
|
||||
j.inproduction=true;
|
||||
@@ -0,0 +1,8 @@
|
||||
-- Could not auto-generate a down migration.
|
||||
-- Please write an appropriate down migration for the SQL below:
|
||||
-- CREATE OR REPLACE VIEW "public"."jobs_inproduction" AS
|
||||
-- SELECT j.id,
|
||||
-- j.updated_at,
|
||||
-- j.shopid
|
||||
-- FROM jobs j
|
||||
-- WHERE (j.inproduction = true);
|
||||
6
hasura/migrations/1726773316245_run_sql_migration/up.sql
Normal file
6
hasura/migrations/1726773316245_run_sql_migration/up.sql
Normal file
@@ -0,0 +1,6 @@
|
||||
CREATE OR REPLACE VIEW "public"."jobs_inproduction" AS
|
||||
SELECT j.id,
|
||||
j.updated_at,
|
||||
j.shopid
|
||||
FROM jobs j
|
||||
WHERE (j.inproduction = true);
|
||||
@@ -0,0 +1,8 @@
|
||||
-- Could not auto-generate a down migration.
|
||||
-- Please write an appropriate down migration for the SQL below:
|
||||
-- CREATE OR REPLACE VIEW "public"."jobs_inproduction" AS
|
||||
-- SELECT j.id,
|
||||
-- j.updated_at,
|
||||
-- j.shopid
|
||||
-- FROM jobs j
|
||||
-- WHERE (j.inproduction = true);
|
||||
6
hasura/migrations/1726773326353_run_sql_migration/up.sql
Normal file
6
hasura/migrations/1726773326353_run_sql_migration/up.sql
Normal file
@@ -0,0 +1,6 @@
|
||||
CREATE OR REPLACE VIEW "public"."jobs_inproduction" AS
|
||||
SELECT j.id,
|
||||
j.updated_at,
|
||||
j.shopid
|
||||
FROM jobs j
|
||||
WHERE (j.inproduction = true);
|
||||
@@ -0,0 +1,3 @@
|
||||
-- Could not auto-generate a down migration.
|
||||
-- Please write an appropriate down migration for the SQL below:
|
||||
-- CREATE INDEX idx_jobs_inproduction_true_cast ON jobs(inproduction) WHERE inproduction = ('true') :: boolean;
|
||||
1
hasura/migrations/1726775434726_run_sql_migration/up.sql
Normal file
1
hasura/migrations/1726775434726_run_sql_migration/up.sql
Normal file
@@ -0,0 +1 @@
|
||||
CREATE INDEX idx_jobs_inproduction_true_cast ON jobs(inproduction) WHERE inproduction = ('true') :: boolean;
|
||||
@@ -0,0 +1,2 @@
|
||||
CREATE INDEX "idx_jobs_inproduction_true_cast" on
|
||||
"public"."jobs" using btree ("inproduction");
|
||||
@@ -0,0 +1 @@
|
||||
DROP INDEX IF EXISTS "public"."idx_jobs_inproduction_true_cast";
|
||||
@@ -0,0 +1,3 @@
|
||||
-- Could not auto-generate a down migration.
|
||||
-- Please write an appropriate down migration for the SQL below:
|
||||
-- CREATE INDEX idx_jobs_created_at_desc ON jobs (created_at DESC);
|
||||
1
hasura/migrations/1726868398933_run_sql_migration/up.sql
Normal file
1
hasura/migrations/1726868398933_run_sql_migration/up.sql
Normal file
@@ -0,0 +1 @@
|
||||
CREATE INDEX idx_jobs_created_at_desc ON jobs (created_at DESC);
|
||||
@@ -0,0 +1 @@
|
||||
DROP INDEX IF EXISTS "public"."idx_jobs_vehicleid";
|
||||
@@ -0,0 +1,2 @@
|
||||
CREATE INDEX "idx_jobs_vehicleid" on
|
||||
"public"."jobs" using btree ("vehicleid");
|
||||
34
hasura/migrations/1726871373784_run_sql_migration/down.sql
Normal file
34
hasura/migrations/1726871373784_run_sql_migration/down.sql
Normal file
@@ -0,0 +1,34 @@
|
||||
-- Could not auto-generate a down migration.
|
||||
-- Please write an appropriate down migration for the SQL below:
|
||||
-- CREATE OR REPLACE FUNCTION public.search_exportlog(search text)
|
||||
-- RETURNS SETOF exportlog
|
||||
-- LANGUAGE plpgsql
|
||||
-- STABLE
|
||||
-- AS $function$ BEGIN IF search = '' THEN RETURN query
|
||||
-- SELECT
|
||||
-- *
|
||||
-- FROM
|
||||
-- exportlog e;
|
||||
-- ELSE RETURN query
|
||||
-- SELECT
|
||||
-- e.*
|
||||
-- FROM
|
||||
-- exportlog e
|
||||
-- LEFT JOIN jobs j on j.id = e.jobid
|
||||
-- LEFT JOIN payments p
|
||||
-- ON p.id = e.paymentid
|
||||
-- LEFT JOIN bills b
|
||||
-- ON e.billid = b.id
|
||||
-- WHERE
|
||||
-- (
|
||||
-- j.ro_number ILIKE '%' || search
|
||||
-- OR b.invoice_number ILIKE '%' || search
|
||||
-- OR p.paymentnum ILIKE '%' || search
|
||||
-- OR e.useremail ILIKE '%' || search
|
||||
-- )
|
||||
-- AND (e.jobid = j.id
|
||||
-- or e.paymentid = p.id
|
||||
-- or e.billid = b.id)
|
||||
-- ;
|
||||
-- END IF;
|
||||
-- END $function$;
|
||||
32
hasura/migrations/1726871373784_run_sql_migration/up.sql
Normal file
32
hasura/migrations/1726871373784_run_sql_migration/up.sql
Normal file
@@ -0,0 +1,32 @@
|
||||
CREATE OR REPLACE FUNCTION public.search_exportlog(search text)
|
||||
RETURNS SETOF exportlog
|
||||
LANGUAGE plpgsql
|
||||
STABLE
|
||||
AS $function$ BEGIN IF search = '' THEN RETURN query
|
||||
SELECT
|
||||
*
|
||||
FROM
|
||||
exportlog e;
|
||||
ELSE RETURN query
|
||||
SELECT
|
||||
e.*
|
||||
FROM
|
||||
exportlog e
|
||||
LEFT JOIN jobs j on j.id = e.jobid
|
||||
LEFT JOIN payments p
|
||||
ON p.id = e.paymentid
|
||||
LEFT JOIN bills b
|
||||
ON e.billid = b.id
|
||||
WHERE
|
||||
(
|
||||
j.ro_number ILIKE '%' || search
|
||||
OR b.invoice_number ILIKE '%' || search
|
||||
OR p.paymentnum ILIKE '%' || search
|
||||
OR e.useremail ILIKE '%' || search
|
||||
)
|
||||
AND (e.jobid = j.id
|
||||
or e.paymentid = p.id
|
||||
or e.billid = b.id)
|
||||
;
|
||||
END IF;
|
||||
END $function$;
|
||||
34
hasura/migrations/1726871384601_run_sql_migration/down.sql
Normal file
34
hasura/migrations/1726871384601_run_sql_migration/down.sql
Normal file
@@ -0,0 +1,34 @@
|
||||
-- Could not auto-generate a down migration.
|
||||
-- Please write an appropriate down migration for the SQL below:
|
||||
-- CREATE OR REPLACE FUNCTION public.search_exportlog(search text)
|
||||
-- RETURNS SETOF exportlog
|
||||
-- LANGUAGE plpgsql
|
||||
-- STABLE
|
||||
-- AS $function$ BEGIN IF search = '' THEN RETURN query
|
||||
-- SELECT
|
||||
-- *
|
||||
-- FROM
|
||||
-- exportlog e;
|
||||
-- ELSE RETURN query
|
||||
-- SELECT
|
||||
-- e.*
|
||||
-- FROM
|
||||
-- exportlog e
|
||||
-- LEFT JOIN jobs j on j.id = e.jobid
|
||||
-- LEFT JOIN payments p
|
||||
-- ON p.id = e.paymentid
|
||||
-- LEFT JOIN bills b
|
||||
-- ON e.billid = b.id
|
||||
-- WHERE
|
||||
-- (
|
||||
-- j.ro_number ILIKE '%' || search
|
||||
-- OR b.invoice_number ILIKE '%' || search
|
||||
-- OR p.paymentnum ILIKE '%' || search
|
||||
-- OR e.useremail ILIKE '%' || search
|
||||
-- )
|
||||
-- AND (e.jobid = j.id
|
||||
-- or e.paymentid = p.id
|
||||
-- or e.billid = b.id)
|
||||
-- ;
|
||||
-- END IF;
|
||||
-- END $function$;
|
||||
32
hasura/migrations/1726871384601_run_sql_migration/up.sql
Normal file
32
hasura/migrations/1726871384601_run_sql_migration/up.sql
Normal file
@@ -0,0 +1,32 @@
|
||||
CREATE OR REPLACE FUNCTION public.search_exportlog(search text)
|
||||
RETURNS SETOF exportlog
|
||||
LANGUAGE plpgsql
|
||||
STABLE
|
||||
AS $function$ BEGIN IF search = '' THEN RETURN query
|
||||
SELECT
|
||||
*
|
||||
FROM
|
||||
exportlog e;
|
||||
ELSE RETURN query
|
||||
SELECT
|
||||
e.*
|
||||
FROM
|
||||
exportlog e
|
||||
LEFT JOIN jobs j on j.id = e.jobid
|
||||
LEFT JOIN payments p
|
||||
ON p.id = e.paymentid
|
||||
LEFT JOIN bills b
|
||||
ON e.billid = b.id
|
||||
WHERE
|
||||
(
|
||||
j.ro_number ILIKE '%' || search
|
||||
OR b.invoice_number ILIKE '%' || search
|
||||
OR p.paymentnum ILIKE '%' || search
|
||||
OR e.useremail ILIKE '%' || search
|
||||
)
|
||||
AND (e.jobid = j.id
|
||||
or e.paymentid = p.id
|
||||
or e.billid = b.id)
|
||||
;
|
||||
END IF;
|
||||
END $function$;
|
||||
@@ -0,0 +1 @@
|
||||
DROP INDEX IF EXISTS "public"."idx_bill_invoice_number";
|
||||
@@ -0,0 +1,2 @@
|
||||
CREATE INDEX "idx_bill_invoice_number" on
|
||||
"public"."bills" using btree ("invoice_number");
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user