Compare commits
339 Commits
feature/IO
...
feature/IO
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4a16df36dd | ||
|
|
7ba3ed2b89 | ||
|
|
2cc0b7d741 | ||
|
|
97693fbcff | ||
|
|
5e94b1a71e | ||
|
|
970275e62a | ||
|
|
aa076da255 | ||
|
|
3dd154de79 | ||
|
|
72a2366abe | ||
|
|
36f517e8e1 | ||
|
|
981fb57d36 | ||
|
|
a059c2b5a8 | ||
|
|
207bb39672 | ||
|
|
16e34e4ed9 | ||
|
|
cd04f2b2b2 | ||
|
|
26e164b4d1 | ||
|
|
ae6b2fe4f5 | ||
|
|
4ca686126a | ||
|
|
c254a8abfe | ||
|
|
f864e40a90 | ||
|
|
802f70dde8 | ||
|
|
6cb4fd6b93 | ||
|
|
70ca6edcb2 | ||
|
|
9dcc861740 | ||
|
|
fecb7fb24b | ||
|
|
c5b94db8af | ||
|
|
6690c9c692 | ||
|
|
506edcb3c6 | ||
|
|
c45e53e38b | ||
|
|
d8d8a4701e | ||
|
|
970c4ee9e1 | ||
|
|
c2386f43ef | ||
|
|
0fd3e75862 | ||
|
|
fcd3234fd9 | ||
|
|
2afa810e6c | ||
|
|
ff33b924b2 | ||
|
|
84bb25985b | ||
|
|
e38a58550f | ||
|
|
25ef4c6228 | ||
|
|
277fbeebc8 | ||
|
|
399df78957 | ||
|
|
294325343b | ||
|
|
ed17eec948 | ||
|
|
f87c95079c | ||
|
|
327149ffc9 | ||
|
|
f81b21b933 | ||
|
|
a559b56983 | ||
|
|
6a9030b653 | ||
|
|
fc7da187f4 | ||
|
|
f593f83ec1 | ||
|
|
5752f123ac | ||
|
|
6396d68584 | ||
|
|
bfd29f25dd | ||
|
|
e75e35e4ee | ||
|
|
d7ddbf7e8d | ||
|
|
1b0198af63 | ||
|
|
ace16ba873 | ||
|
|
f57a4bd948 | ||
|
|
fef4393f9c | ||
|
|
47adb6d40a | ||
|
|
4940b10910 | ||
|
|
9c8e241ef7 | ||
|
|
c3d6b98c89 | ||
|
|
4b49654083 | ||
|
|
78223078f4 | ||
|
|
ca4b78d44c | ||
|
|
d7bfc789e2 | ||
|
|
b54d0ed62a | ||
|
|
8386443cb0 | ||
|
|
7959dc67ce | ||
|
|
0d2cdec75c | ||
|
|
a4116b6c28 | ||
|
|
269ef25ece | ||
|
|
575fbd5357 | ||
|
|
2ad887fb82 | ||
|
|
40a1a86f72 | ||
|
|
95d43d936c | ||
|
|
9f56568680 | ||
|
|
3428940c72 | ||
|
|
ea604a5e64 | ||
|
|
5b76473cbc | ||
|
|
db5359e086 | ||
|
|
35046f11c2 | ||
|
|
f4c4005a2a | ||
|
|
69d8d27ad3 | ||
|
|
755acd24f0 | ||
|
|
079d6cfee6 | ||
|
|
3e3b3c269a | ||
|
|
39f1af7d4b | ||
|
|
1ea4d616d7 | ||
|
|
fdf0ecf6f6 | ||
|
|
e5f7285253 | ||
|
|
e46c304f7c | ||
|
|
1aa570db90 | ||
|
|
1c2be3c890 | ||
|
|
05e4eacf34 | ||
|
|
e1417b03f9 | ||
|
|
7b99de8046 | ||
|
|
6dc03d7f67 | ||
|
|
2a5e6a51aa | ||
|
|
885477fa68 | ||
|
|
c7db792793 | ||
|
|
31be51ef79 | ||
|
|
e4066c1570 | ||
|
|
ec480f2cb1 | ||
|
|
f5b30c9376 | ||
|
|
412508fbcf | ||
|
|
dbfd9bce54 | ||
|
|
52e0558c79 | ||
|
|
562d0b8641 | ||
|
|
d48d6cdd91 | ||
|
|
9363790541 | ||
|
|
2bceba948d | ||
|
|
168d4246af | ||
|
|
cdf6050ec2 | ||
|
|
f451155689 | ||
|
|
9f06e19346 | ||
|
|
f4be3e9668 | ||
|
|
ee613db0cb | ||
|
|
55b892a74e | ||
|
|
16754d9657 | ||
|
|
33db67122c | ||
|
|
b9b88b0e23 | ||
|
|
992ad71910 | ||
|
|
51d1f926c2 | ||
|
|
c70447f337 | ||
|
|
dc367e1a30 | ||
|
|
ee7997ffbc | ||
|
|
2dbb5adbbb | ||
|
|
cc9f342575 | ||
|
|
252262f4a7 | ||
|
|
f77a16648f | ||
|
|
d6e3c54b68 | ||
|
|
7294e96a77 | ||
|
|
a4f4f45251 | ||
|
|
acc91abc0c | ||
|
|
18d5176cb9 | ||
|
|
d6d6ced7a4 | ||
|
|
52809cc849 | ||
|
|
c525b7ea3f | ||
|
|
e799417aaf | ||
|
|
ef077c2d48 | ||
|
|
e78b114544 | ||
|
|
df170ddd27 | ||
|
|
17928741e3 | ||
|
|
a6b825ffdf | ||
|
|
0d3bc0e95f | ||
|
|
0f62a2d73d | ||
|
|
bcdd32f92f | ||
|
|
6865fcbffc | ||
|
|
8e623c71a9 | ||
|
|
1e06502464 | ||
|
|
f2914868e2 | ||
|
|
561f0313f9 | ||
|
|
1e27c4e1ae | ||
|
|
46676ba8eb | ||
|
|
02a49efbea | ||
|
|
5f2a5e1025 | ||
|
|
42552e4f4f | ||
|
|
f2af78f056 | ||
|
|
269d55bde3 | ||
|
|
71f3dbbeb4 | ||
|
|
73dfb74ed7 | ||
|
|
7dd6baef33 | ||
|
|
519b532091 | ||
|
|
4fb9c37c0d | ||
|
|
c9b63be29f | ||
|
|
84bc735dce | ||
|
|
a44b9417a1 | ||
|
|
6b157ed43c | ||
|
|
9050276ea7 | ||
|
|
683293d042 | ||
|
|
70d009ab49 | ||
|
|
dbc2d10d6d | ||
|
|
059b854db9 | ||
|
|
13569a1785 | ||
|
|
6fd70b165b | ||
|
|
11d94cf286 | ||
|
|
c98a48ea14 | ||
|
|
b8fa80419b | ||
|
|
2a8846297f | ||
|
|
fb322f760f | ||
|
|
0b9f718106 | ||
|
|
57b3b3a9cd | ||
|
|
6513432993 | ||
|
|
80a564d4b6 | ||
|
|
132ecffc40 | ||
|
|
cdf02a8eac | ||
|
|
7d22dcab7c | ||
|
|
45c943a78f | ||
|
|
cf82a8013f | ||
|
|
25d145d864 | ||
|
|
7822e3f90e | ||
|
|
a4a612fbe4 | ||
|
|
07da472e82 | ||
|
|
e9096632a4 | ||
|
|
6475a8bdce | ||
|
|
535f92b7d2 | ||
|
|
00b91616f5 | ||
|
|
b8c34762ed | ||
|
|
9f92347936 | ||
|
|
b657a893ad | ||
|
|
387670212a | ||
|
|
b8e42544ae | ||
|
|
bcea47c2c6 | ||
|
|
b38aaba56c | ||
|
|
8099607d90 | ||
|
|
2d9412e4e8 | ||
|
|
069d508528 | ||
|
|
d68ce67e4f | ||
|
|
a9d38e743f | ||
|
|
878e81dc8f | ||
|
|
5ccf74f99c | ||
|
|
6c823f4914 | ||
|
|
de35155ffe | ||
|
|
470eb19a2f | ||
|
|
947bc33946 | ||
|
|
8bcaabfb57 | ||
|
|
a1f7e7b755 | ||
|
|
c8f8a86a98 | ||
|
|
bb205af019 | ||
|
|
443c6046f9 | ||
|
|
1620b94a7b | ||
|
|
81f94eac6c | ||
|
|
ffada75d9e | ||
|
|
34d773bcd8 | ||
|
|
ec7509670d | ||
|
|
eceac11af2 | ||
|
|
dd64598850 | ||
|
|
650ace6be6 | ||
|
|
1c71a5c5e0 | ||
|
|
9c699a634b | ||
|
|
a47d17bbf5 | ||
|
|
164f67d6ce | ||
|
|
465b9e7177 | ||
|
|
f5a914c318 | ||
|
|
de486d2e73 | ||
|
|
d7f946ec2a | ||
|
|
4d1480bb61 | ||
|
|
25a49473f9 | ||
|
|
8e86e7fba5 | ||
|
|
f9b380a0d4 | ||
|
|
f664a56b16 | ||
|
|
0acfd3c4b1 | ||
|
|
bfc4cb1ad9 | ||
|
|
66dd1a9a2b | ||
|
|
1f42be2e54 | ||
|
|
30d344af6b | ||
|
|
3ca989fd8c | ||
|
|
73c38b3ae4 | ||
|
|
08749b0c92 | ||
|
|
ea73af371e | ||
|
|
ce2086a480 | ||
|
|
63f7106d2b | ||
|
|
8cce6ea6e3 | ||
|
|
77c486b4c9 | ||
|
|
7c8f276bb0 | ||
|
|
e991586254 | ||
|
|
d61ab796a7 | ||
|
|
e634369975 | ||
|
|
453236cf3a | ||
|
|
3530476b07 | ||
|
|
e75d8d1874 | ||
|
|
719fa6a67d | ||
|
|
148cd43c5d | ||
|
|
3d753a2d19 | ||
|
|
693d02de87 | ||
|
|
80b4ef3ae8 | ||
|
|
6b9269eb2d | ||
|
|
15c9529885 | ||
|
|
512cd70d13 | ||
|
|
e5599ff4c4 | ||
|
|
32041ee3fc | ||
|
|
07bf84ed69 | ||
|
|
0c0449aa17 | ||
|
|
26cb527d37 | ||
|
|
33c282051b | ||
|
|
df0f8ef9dc | ||
|
|
e23f13a1b3 | ||
|
|
f5b8bf1d74 | ||
|
|
61766017ea | ||
|
|
706984a53b | ||
|
|
e137feca20 | ||
|
|
9e66d7c929 | ||
|
|
9605bf5c21 | ||
|
|
c2cc7b1e9e | ||
|
|
fe55eccbf9 | ||
|
|
47e17dc78a | ||
|
|
88a71dd647 | ||
|
|
ce0b3a8635 | ||
|
|
bd3d86a6dd | ||
|
|
c3b395c99e | ||
|
|
eb4e5d9576 | ||
|
|
19a03ec080 | ||
|
|
33d5d9b462 | ||
|
|
b5a371d0cf | ||
|
|
b61fd17879 | ||
|
|
6722f8b1e5 | ||
|
|
69ff75157d | ||
|
|
7fad968ad2 | ||
|
|
7c619f5439 | ||
|
|
3b35f38ad5 | ||
|
|
096017c3d6 | ||
|
|
4ff2ab1bc8 | ||
|
|
ef698529d7 | ||
|
|
167d5bd89a | ||
|
|
c328a55453 | ||
|
|
83a976e98f | ||
|
|
d004133ad6 | ||
|
|
1f5c1b9658 | ||
|
|
56dfd174dd | ||
|
|
532cd4937b | ||
|
|
91bc73baf2 | ||
|
|
ab031c01de | ||
|
|
e51f72ff98 | ||
|
|
3eb010285d | ||
|
|
2b172f9999 | ||
|
|
0803f5af35 | ||
|
|
69ac2f0a6c | ||
|
|
0c842e0e15 | ||
|
|
d94678d4f4 | ||
|
|
90814f41a2 | ||
|
|
282dbd0913 | ||
|
|
1343b68cc6 | ||
|
|
ae07f71e76 | ||
|
|
54dc9c8587 | ||
|
|
9f9fa3b952 | ||
|
|
cc7c98336f | ||
|
|
dc22b96bed | ||
|
|
ae9e9f4b72 | ||
|
|
301c680bff | ||
|
|
595159f24d | ||
|
|
3bf1ec25c1 | ||
|
|
40c801592d | ||
|
|
9012e4deec | ||
|
|
ab2323e5c1 | ||
|
|
f31ae9ac6d | ||
|
|
27c24619c3 | ||
|
|
38aef71269 |
@@ -9,10 +9,10 @@ const config = {
|
||||
arrowParens: "always",
|
||||
jsxSingleQuote: false,
|
||||
bracketSameLine: false,
|
||||
endOfLine: "lf",
|
||||
importOrder: ["^@core/(.*)$", "^@server/(.*)$", "^@ui/(.*)$", "^[./]"],
|
||||
importOrderSeparation: true,
|
||||
importOrderSortSpecifiers: true
|
||||
endOfLine: "lf"
|
||||
// importOrder: ["^@core/(.*)$", "^@server/(.*)$", "^@ui/(.*)$", "^[./]"],
|
||||
// importOrderSeparation: true,
|
||||
// importOrderSortSpecifiers: true
|
||||
};
|
||||
|
||||
module.exports = config;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
||||
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_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":"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
|
||||
|
||||
8
client/.eslintrc
Normal file
8
client/.eslintrc
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": [
|
||||
"react-app"
|
||||
],
|
||||
"rules": {
|
||||
"no-useless-rename": "off"
|
||||
}
|
||||
}
|
||||
39
client/package-lock.json
generated
39
client/package-lock.json
generated
@@ -96,6 +96,8 @@
|
||||
"browserslist-to-esbuild": "^2.1.1",
|
||||
"cross-env": "^7.0.3",
|
||||
"cypress": "^13.6.6",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-config-react-app": "^7.0.1",
|
||||
"eslint-plugin-cypress": "^2.15.1",
|
||||
"memfs": "^4.6.0",
|
||||
"os-browserify": "^0.3.0",
|
||||
@@ -104,6 +106,7 @@
|
||||
"source-map-explorer": "^2.5.3",
|
||||
"vite": "^5.0.11",
|
||||
"vite-plugin-babel": "^1.2.0",
|
||||
"vite-plugin-eslint": "^1.8.1",
|
||||
"vite-plugin-legacy": "^2.1.0",
|
||||
"vite-plugin-node-polyfills": "^0.19.0",
|
||||
"vite-plugin-pwa": "^0.19.0",
|
||||
@@ -11934,7 +11937,8 @@
|
||||
},
|
||||
"node_modules/eslint": {
|
||||
"version": "8.57.0",
|
||||
"license": "MIT",
|
||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz",
|
||||
"integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==",
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.2.0",
|
||||
"@eslint-community/regexpp": "^4.6.1",
|
||||
@@ -11987,7 +11991,8 @@
|
||||
},
|
||||
"node_modules/eslint-config-react-app": {
|
||||
"version": "7.0.1",
|
||||
"license": "MIT",
|
||||
"resolved": "https://registry.npmjs.org/eslint-config-react-app/-/eslint-config-react-app-7.0.1.tgz",
|
||||
"integrity": "sha512-K6rNzvkIeHaTd8m/QEh1Zko0KI7BACWkkneSs6s9cKZC/J27X3eZR6Upt1jkmZ/4FK+XUOPPxMEN7+lbUXfSlA==",
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.16.0",
|
||||
"@babel/eslint-parser": "^7.16.3",
|
||||
@@ -26439,6 +26444,36 @@
|
||||
"vite": ">=5.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/vite-plugin-eslint": {
|
||||
"version": "1.8.1",
|
||||
"resolved": "https://registry.npmjs.org/vite-plugin-eslint/-/vite-plugin-eslint-1.8.1.tgz",
|
||||
"integrity": "sha512-PqdMf3Y2fLO9FsNPmMX+//2BF5SF8nEWspZdgl4kSt7UvHDRHVVfHvxsD7ULYzZrJDGRxR81Nq7TOFgwMnUang==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@rollup/pluginutils": "^4.2.1",
|
||||
"@types/eslint": "^8.4.5",
|
||||
"rollup": "^2.77.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"eslint": ">=7",
|
||||
"vite": ">=2"
|
||||
}
|
||||
},
|
||||
"node_modules/vite-plugin-eslint/node_modules/rollup": {
|
||||
"version": "2.79.1",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.1.tgz",
|
||||
"integrity": "sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"rollup": "dist/bin/rollup"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"fsevents": "~2.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/vite-plugin-legacy": {
|
||||
"version": "2.1.0",
|
||||
"dev": true,
|
||||
|
||||
@@ -140,6 +140,8 @@
|
||||
"browserslist-to-esbuild": "^2.1.1",
|
||||
"cross-env": "^7.0.3",
|
||||
"cypress": "^13.6.6",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-config-react-app": "^7.0.1",
|
||||
"eslint-plugin-cypress": "^2.15.1",
|
||||
"memfs": "^4.6.0",
|
||||
"os-browserify": "^0.3.0",
|
||||
@@ -148,6 +150,7 @@
|
||||
"source-map-explorer": "^2.5.3",
|
||||
"vite": "^5.0.11",
|
||||
"vite-plugin-babel": "^1.2.0",
|
||||
"vite-plugin-eslint": "^1.8.1",
|
||||
"vite-plugin-legacy": "^2.1.0",
|
||||
"vite-plugin-node-polyfills": "^0.19.0",
|
||||
"vite-plugin-pwa": "^0.19.0",
|
||||
|
||||
56
client/public/firebase-messaging-sw.js
Normal file
56
client/public/firebase-messaging-sw.js
Normal file
@@ -0,0 +1,56 @@
|
||||
// Scripts for firebase and firebase messaging
|
||||
importScripts("https://www.gstatic.com/firebasejs/8.2.0/firebase-app.js");
|
||||
importScripts("https://www.gstatic.com/firebasejs/8.2.0/firebase-messaging.js");
|
||||
|
||||
// Initialize the Firebase app in the service worker by passing the generated config
|
||||
let firebaseConfig;
|
||||
switch (this.location.hostname) {
|
||||
case "localhost":
|
||||
firebaseConfig = {
|
||||
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",
|
||||
};
|
||||
break;
|
||||
case "test.imex.online":
|
||||
firebaseConfig = {
|
||||
apiKey: "AIzaSyBw7_GTy7GtQyfkIRPVrWHEGKfcqeyXw0c",
|
||||
authDomain: "imex-test.firebaseapp.com",
|
||||
projectId: "imex-test",
|
||||
storageBucket: "imex-test.appspot.com",
|
||||
messagingSenderId: "991923618608",
|
||||
appId: "1:991923618608:web:633437569cdad78299bef5",
|
||||
// measurementId: "${config.measurementId}",
|
||||
};
|
||||
break;
|
||||
case "imex.online":
|
||||
default:
|
||||
firebaseConfig = {
|
||||
apiKey: "AIzaSyDSezy-jGJreo7ulgpLdlpOwAOrgcaEkhU",
|
||||
authDomain: "imex-prod.firebaseapp.com",
|
||||
databaseURL: "https://imex-prod.firebaseio.com",
|
||||
projectId: "imex-prod",
|
||||
storageBucket: "imex-prod.appspot.com",
|
||||
messagingSenderId: "253497221485",
|
||||
appId: "1:253497221485:web:3c81c483b94db84b227a64",
|
||||
measurementId: "G-NTWBKG2L0M",
|
||||
};
|
||||
}
|
||||
|
||||
firebase.initializeApp(firebaseConfig);
|
||||
|
||||
// Retrieve firebase messaging
|
||||
const messaging = firebase.messaging();
|
||||
|
||||
messaging.onBackgroundMessage(function (payload) {
|
||||
// Customize notification here
|
||||
const channel = new BroadcastChannel("imex-sw-messages");
|
||||
channel.postMessage(payload);
|
||||
|
||||
//self.registration.showNotification(notificationTitle, notificationOptions);
|
||||
});
|
||||
@@ -7,9 +7,7 @@ import { connect } from "react-redux";
|
||||
import { Route, Routes } from "react-router-dom";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import DocumentEditorContainer from "../components/document-editor/document-editor.container";
|
||||
import ErrorBoundary from "../components/error-boundary/error-boundary.component";
|
||||
|
||||
//Component Imports
|
||||
import ErrorBoundary from "../components/error-boundary/error-boundary.component"; // Component Imports
|
||||
import LoadingSpinner from "../components/loading-spinner/loading-spinner.component";
|
||||
import DisclaimerPage from "../pages/disclaimer/disclaimer.page";
|
||||
import LandingPage from "../pages/landing/landing.page";
|
||||
@@ -23,20 +21,21 @@ import "./App.styles.scss";
|
||||
import handleBeta from "../utils/betaHandler";
|
||||
import Eula from "../components/eula/eula.component";
|
||||
import InstanceRenderMgr from "../utils/instanceRenderMgr";
|
||||
import { ProductFruits } from "react-product-fruits";
|
||||
import ProductFruitsWrapper from "./ProductFruitsWrapper.jsx";
|
||||
|
||||
const ResetPassword = lazy(() => import("../pages/reset-password/reset-password.component"));
|
||||
const ManagePage = lazy(() => import("../pages/manage/manage.page.container"));
|
||||
const SignInPage = lazy(() => import("../pages/sign-in/sign-in.page"));
|
||||
|
||||
const CsiPage = lazy(() => import("../pages/csi/csi.container.page"));
|
||||
const MobilePaymentContainer = lazy(() => import("../pages/mobile-payment/mobile-payment.container"));
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
currentUser: selectCurrentUser,
|
||||
online: selectOnline,
|
||||
bodyshop: selectBodyshop,
|
||||
currentEula: selectCurrentEula
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
checkUserSession: () => dispatch(checkUserSession()),
|
||||
setOnline: (isOnline) => dispatch(setOnline(isOnline))
|
||||
@@ -60,11 +59,11 @@ export function App({ bodyshop, checkUserSession, currentUser, online, setOnline
|
||||
|
||||
// Associate event listeners, memoize to prevent multiple listeners being added
|
||||
useEffect(() => {
|
||||
const offlineListener = (e) => {
|
||||
const offlineListener = () => {
|
||||
setOnline(false);
|
||||
};
|
||||
|
||||
const onlineListener = (e) => {
|
||||
const onlineListener = () => {
|
||||
setOnline(true);
|
||||
};
|
||||
|
||||
@@ -98,7 +97,7 @@ export function App({ bodyshop, checkUserSession, currentUser, online, setOnline
|
||||
InstanceRenderMgr({
|
||||
imex: "gvfvfw/bodyshopapp",
|
||||
rome: "rome-online/rome-online",
|
||||
promanager: "" //TODO:AIO Add in log rocket for promanager instances.
|
||||
promanager: "" // TODO: AIO Add in log rocket for promanager instances.
|
||||
})
|
||||
);
|
||||
}
|
||||
@@ -111,24 +110,20 @@ export function App({ bodyshop, checkUserSession, currentUser, online, setOnline
|
||||
|
||||
handleBeta();
|
||||
|
||||
if (!online)
|
||||
if (!online) {
|
||||
return (
|
||||
<Result
|
||||
status="warning"
|
||||
title={t("general.labels.nointernet")}
|
||||
subTitle={t("general.labels.nointernet_sub")}
|
||||
extra={
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={() => {
|
||||
window.location.reload();
|
||||
}}
|
||||
>
|
||||
<Button type="primary" onClick={() => window.location.reload()}>
|
||||
{t("general.actions.refresh")}
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (currentEula && !currentUser.eulaIsAccepted) {
|
||||
return <Eula />;
|
||||
@@ -147,18 +142,13 @@ export function App({ bodyshop, checkUserSession, currentUser, online, setOnline
|
||||
/>
|
||||
}
|
||||
>
|
||||
<ProductFruits
|
||||
<ProductFruitsWrapper
|
||||
currentUser={currentUser}
|
||||
workspaceCode={InstanceRenderMgr({
|
||||
imex: null,
|
||||
rome: null,
|
||||
rome: "9BkbEseqNqxw8jUH",
|
||||
promanager: "aoJoEifvezYI0Z0P"
|
||||
})}
|
||||
debug
|
||||
language="en"
|
||||
user={{
|
||||
email: currentUser.email,
|
||||
username: currentUser.email
|
||||
}}
|
||||
/>
|
||||
|
||||
<Routes>
|
||||
|
||||
32
client/src/App/ProductFruitsWrapper.jsx
Normal file
32
client/src/App/ProductFruitsWrapper.jsx
Normal file
@@ -0,0 +1,32 @@
|
||||
import React from "react";
|
||||
import { ProductFruits } from "react-product-fruits";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
const ProductFruitsWrapper = React.memo(({ currentUser, workspaceCode }) => {
|
||||
return (
|
||||
workspaceCode &&
|
||||
currentUser?.authorized === true &&
|
||||
currentUser?.email && (
|
||||
<ProductFruits
|
||||
lifeCycle="unmount"
|
||||
workspaceCode={workspaceCode}
|
||||
debug
|
||||
language="en"
|
||||
user={{
|
||||
email: currentUser.email,
|
||||
username: currentUser.email
|
||||
}}
|
||||
/>
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
export default ProductFruitsWrapper;
|
||||
|
||||
ProductFruitsWrapper.propTypes = {
|
||||
currentUser: PropTypes.shape({
|
||||
authorized: PropTypes.bool,
|
||||
email: PropTypes.string
|
||||
}).isRequired,
|
||||
workspaceCode: PropTypes.string.isRequired
|
||||
};
|
||||
@@ -34,6 +34,7 @@ export function BillDetailEditReturn({ setPartsOrderContext, insertAuditTrail, b
|
||||
actions: {},
|
||||
context: {
|
||||
jobId: data.bills_by_pk.jobid,
|
||||
job: data.bills_by_pk.job,
|
||||
vendorId: data.bills_by_pk.vendorid,
|
||||
returnFromBill: data.bills_by_pk.id,
|
||||
invoiceNumber: data.bills_by_pk.invoice_number,
|
||||
|
||||
@@ -4,7 +4,6 @@ import { useTranslation } from "react-i18next";
|
||||
import InstanceRenderMgr from "../../utils/instanceRenderMgr";
|
||||
|
||||
//To be used as a form element only.
|
||||
const { Option } = Select;
|
||||
const BillLineSearchSelect = ({ options, disabled, allowRemoved, ...restProps }, ref) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
@@ -25,31 +24,27 @@ const BillLineSearchSelect = ({ options, disabled, allowRemoved, ...restProps },
|
||||
);
|
||||
}}
|
||||
notFoundContent={"Removed."}
|
||||
{...restProps}
|
||||
>
|
||||
<Select.Option key={null} value={"noline"} cost={0} line_desc={""}>
|
||||
{t("billlines.labels.other")}
|
||||
</Select.Option>
|
||||
{options
|
||||
? options.map((item) => (
|
||||
<Option
|
||||
disabled={allowRemoved ? false : item.removed}
|
||||
key={item.id}
|
||||
value={item.id}
|
||||
cost={item.act_price ? item.act_price : 0}
|
||||
part_type={item.part_type}
|
||||
line_desc={item.line_desc}
|
||||
part_qty={item.part_qty}
|
||||
oem_partno={item.oem_partno}
|
||||
alt_partno={item.alt_partno}
|
||||
act_price={item.act_price}
|
||||
style={{
|
||||
...(item.removed ? { textDecoration: "line-through" } : {})
|
||||
}}
|
||||
name={`${item.removed ? `(REMOVED) ` : ""}${item.line_desc}${
|
||||
item.oem_partno ? ` - ${item.oem_partno}` : ""
|
||||
}${item.alt_partno ? ` (${item.alt_partno})` : ""}`.trim()}
|
||||
>
|
||||
options={[
|
||||
{ value: "noline", label: t("billlines.labels.other"), name: t("billlines.labels.other") },
|
||||
...options.map((item) => ({
|
||||
disabled: allowRemoved ? false : item.removed,
|
||||
key: item.id,
|
||||
value: item.id,
|
||||
cost: item.act_price ? item.act_price : 0,
|
||||
part_type: item.part_type,
|
||||
line_desc: item.line_desc,
|
||||
part_qty: item.part_qty,
|
||||
oem_partno: item.oem_partno,
|
||||
alt_partno: item.alt_partno,
|
||||
act_price: item.act_price,
|
||||
style: {
|
||||
...(item.removed ? { textDecoration: "line-through" } : {})
|
||||
},
|
||||
name: `${item.removed ? `(REMOVED) ` : ""}${item.line_desc}${
|
||||
item.oem_partno ? ` - ${item.oem_partno}` : ""
|
||||
}${item.alt_partno ? ` (${item.alt_partno})` : ""}`.trim(),
|
||||
label: (
|
||||
<>
|
||||
<span>
|
||||
{`${item.removed ? `(REMOVED) ` : ""}${item.line_desc}${
|
||||
item.oem_partno ? ` - ${item.oem_partno}` : ""
|
||||
@@ -60,14 +55,15 @@ const BillLineSearchSelect = ({ options, disabled, allowRemoved, ...restProps },
|
||||
<span style={{ float: "right", paddingleft: "1rem" }}>{`${item.mod_lb_hrs} units`}</span>
|
||||
)
|
||||
})}
|
||||
|
||||
<span style={{ float: "right", paddingleft: "1rem" }}>
|
||||
{item.act_price ? `$${item.act_price && item.act_price.toFixed(2)}` : ``}
|
||||
</span>
|
||||
</Option>
|
||||
))
|
||||
: null}
|
||||
</Select>
|
||||
</>
|
||||
)
|
||||
}))
|
||||
]}
|
||||
{...restProps}
|
||||
></Select>
|
||||
);
|
||||
};
|
||||
export default forwardRef(BillLineSearchSelect);
|
||||
|
||||
@@ -14,6 +14,7 @@ import { TemplateList } from "../../utils/TemplateConstants";
|
||||
import BillDeleteButton from "../bill-delete-button/bill-delete-button.component";
|
||||
import BillDetailEditReturnComponent from "../bill-detail-edit/bill-detail-edit-return.component";
|
||||
import PrintWrapperComponent from "../print-wrapper/print-wrapper.component";
|
||||
import { FaTasks } from "react-icons/fa";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
jobRO: selectJobReadOnly,
|
||||
@@ -21,9 +22,21 @@ const mapStateToProps = createStructuredSelector({
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
setPartsOrderContext: (context) => dispatch(setModalContext({ context: context, modal: "partsOrder" })),
|
||||
setBillEnterContext: (context) => dispatch(setModalContext({ context: context, modal: "billEnter" })),
|
||||
setReconciliationContext: (context) => dispatch(setModalContext({ context: context, modal: "reconciliation" }))
|
||||
setBillEnterContext: (context) =>
|
||||
dispatch(
|
||||
setModalContext({
|
||||
context: context,
|
||||
modal: "billEnter"
|
||||
})
|
||||
),
|
||||
setReconciliationContext: (context) =>
|
||||
dispatch(
|
||||
setModalContext({
|
||||
context: context,
|
||||
modal: "reconciliation"
|
||||
})
|
||||
),
|
||||
setTaskUpsertContext: (context) => dispatch(setModalContext({ context, modal: "taskUpsert" }))
|
||||
});
|
||||
|
||||
export function BillsListTableComponent({
|
||||
@@ -32,9 +45,9 @@ export function BillsListTableComponent({
|
||||
job,
|
||||
billsQuery,
|
||||
handleOnRowClick,
|
||||
setPartsOrderContext,
|
||||
setBillEnterContext,
|
||||
setReconciliationContext
|
||||
setReconciliationContext,
|
||||
setTaskUpsertContext
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
@@ -48,6 +61,7 @@ export function BillsListTableComponent({
|
||||
const Templates = TemplateList("bill");
|
||||
const bills = billsQuery.data ? billsQuery.data.bills : [];
|
||||
const { refetch } = billsQuery;
|
||||
|
||||
const recordActions = (record, showView = false) => (
|
||||
<Space wrap>
|
||||
{showView && (
|
||||
@@ -55,9 +69,22 @@ export function BillsListTableComponent({
|
||||
<EditFilled />
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
title={t("tasks.buttons.create")}
|
||||
onClick={() => {
|
||||
setTaskUpsertContext({
|
||||
context: {
|
||||
jobid: job.id,
|
||||
billid: record.id
|
||||
}
|
||||
});
|
||||
}}
|
||||
>
|
||||
<FaTasks />
|
||||
</Button>
|
||||
<BillDeleteButton bill={record} jobid={job.id} />
|
||||
<BillDetailEditReturnComponent
|
||||
data={{ bills_by_pk: { ...record, jobid: job.id } }}
|
||||
data={{ bills_by_pk: { ...record, jobid: job.id, job: job } }}
|
||||
disabled={record.is_credit_memo || record.vendorid === bodyshop.inhousevendorid || jobRO}
|
||||
/>
|
||||
|
||||
|
||||
@@ -1,14 +1,12 @@
|
||||
import { DeleteFilled } from "@ant-design/icons";
|
||||
import { useLazyQuery, useMutation } from "@apollo/client";
|
||||
import { Button, Card, Col, Form, Input, notification, Row, Space, Spin, Statistic } from "antd";
|
||||
import { Button, Card, Col, Form, Input, Row, Space, Spin, Statistic, notification } from "antd";
|
||||
import axios from "axios";
|
||||
import dayjs from "../../utils/day";
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { INSERT_PAYMENT_RESPONSE, QUERY_RO_AND_OWNER_BY_JOB_PKS } from "../../graphql/payment_response.queries";
|
||||
import { INSERT_NEW_PAYMENT } from "../../graphql/payments.queries";
|
||||
import { insertAuditTrail } from "../../redux/application/application.actions";
|
||||
import { toggleModalVisible } from "../../redux/modals/modals.actions";
|
||||
import { selectCardPayment } from "../../redux/modals/modals.selectors";
|
||||
@@ -28,12 +26,12 @@ const mapDispatchToProps = (dispatch) => ({
|
||||
});
|
||||
|
||||
const CardPaymentModalComponent = ({ bodyshop, cardPaymentModal, toggleModalVisible, insertAuditTrail }) => {
|
||||
const { context } = cardPaymentModal;
|
||||
const { context, actions } = cardPaymentModal;
|
||||
|
||||
const [form] = Form.useForm();
|
||||
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [insertPayment] = useMutation(INSERT_NEW_PAYMENT);
|
||||
// const [insertPayment] = useMutation(INSERT_NEW_PAYMENT);
|
||||
const [insertPaymentResponse] = useMutation(INSERT_PAYMENT_RESPONSE);
|
||||
const { t } = useTranslation();
|
||||
|
||||
@@ -42,7 +40,6 @@ const CardPaymentModalComponent = ({ bodyshop, cardPaymentModal, toggleModalVisi
|
||||
skip: true
|
||||
});
|
||||
|
||||
console.log("🚀 ~ file: card-payment-modal.component..jsx:61 ~ data:", data);
|
||||
//Initialize the intellipay window.
|
||||
const SetIntellipayCallbackFunctions = () => {
|
||||
console.log("*** Set IntelliPay callback functions.");
|
||||
@@ -51,16 +48,20 @@ const CardPaymentModalComponent = ({ bodyshop, cardPaymentModal, toggleModalVisi
|
||||
});
|
||||
|
||||
window.intellipay.runOnApproval(async function (response) {
|
||||
console.warn("*** Running On Approval Script ***");
|
||||
form.setFieldValue("paymentResponse", response);
|
||||
form.submit();
|
||||
//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();
|
||||
setLoading(false);
|
||||
toggleModalVisible();
|
||||
}, 750);
|
||||
});
|
||||
|
||||
window.intellipay.runOnNonApproval(async function (response) {
|
||||
// Mutate unsuccessful payment
|
||||
|
||||
const { payments } = form.getFieldsValue();
|
||||
|
||||
await insertPaymentResponse({
|
||||
variables: {
|
||||
paymentResponse: payments.map((payment) => ({
|
||||
@@ -85,50 +86,9 @@ const CardPaymentModalComponent = ({ bodyshop, cardPaymentModal, toggleModalVisi
|
||||
});
|
||||
};
|
||||
|
||||
const handleFinish = async (values) => {
|
||||
try {
|
||||
await insertPayment({
|
||||
variables: {
|
||||
paymentInput: values.payments.map((payment) => ({
|
||||
amount: payment.amount,
|
||||
transactionid: (values.paymentResponse.paymentid || "").toString(),
|
||||
payer: t("payments.labels.customer"),
|
||||
type: values.paymentResponse.cardbrand,
|
||||
jobid: payment.jobid,
|
||||
date: dayjs(Date.now()),
|
||||
payment_responses: {
|
||||
data: [
|
||||
{
|
||||
amount: payment.amount,
|
||||
bodyshopid: bodyshop.id,
|
||||
|
||||
jobid: payment.jobid,
|
||||
declinereason: values.paymentResponse.declinereason,
|
||||
ext_paymentid: values.paymentResponse.paymentid.toString(),
|
||||
successful: true,
|
||||
response: values.paymentResponse
|
||||
}
|
||||
]
|
||||
}
|
||||
}))
|
||||
},
|
||||
refetchQueries: ["GET_JOB_BY_PK"]
|
||||
});
|
||||
toggleModalVisible();
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
notification.open({
|
||||
type: "error",
|
||||
message: t("payments.errors.inserting", { error: error.message })
|
||||
});
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleIntelliPayCharge = async () => {
|
||||
setLoading(true);
|
||||
|
||||
//Validate
|
||||
try {
|
||||
await form.validateFields();
|
||||
@@ -140,7 +100,8 @@ const CardPaymentModalComponent = ({ bodyshop, cardPaymentModal, toggleModalVisi
|
||||
try {
|
||||
const response = await axios.post("/intellipay/lightbox_credentials", {
|
||||
bodyshop,
|
||||
refresh: !!window.intellipay
|
||||
refresh: !!window.intellipay,
|
||||
paymentSplitMeta: form.getFieldsValue(),
|
||||
});
|
||||
|
||||
if (window.intellipay) {
|
||||
@@ -169,7 +130,6 @@ const CardPaymentModalComponent = ({ bodyshop, cardPaymentModal, toggleModalVisi
|
||||
<Card title="Card Payment">
|
||||
<Spin spinning={loading}>
|
||||
<Form
|
||||
onFinish={handleFinish}
|
||||
form={form}
|
||||
layout="vertical"
|
||||
initialValues={{
|
||||
@@ -246,18 +206,14 @@ const CardPaymentModalComponent = ({ bodyshop, cardPaymentModal, toggleModalVisi
|
||||
}
|
||||
>
|
||||
{() => {
|
||||
console.log("Updating the owner info section.");
|
||||
//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) {
|
||||
console.log("**Calling refetch.");
|
||||
if (
|
||||
payments?.length > 0 &&
|
||||
payments?.filter((p) => p?.jobid).length === payments?.length
|
||||
) {
|
||||
refetch({ jobids: payments.map((p) => p.jobid) });
|
||||
}
|
||||
console.log(
|
||||
"Acc info",
|
||||
data,
|
||||
payments && data && data.jobs.length > 0 ? data.jobs.map((j) => j.ro_number).join(", ") : null
|
||||
);
|
||||
return (
|
||||
<>
|
||||
<Input
|
||||
@@ -300,6 +256,13 @@ const CardPaymentModalComponent = ({ bodyshop, cardPaymentModal, toggleModalVisi
|
||||
type="hidden"
|
||||
value={totalAmountToCharge?.toFixed(2)}
|
||||
/>
|
||||
<Input
|
||||
className="ipayfield"
|
||||
data-ipayname="comment"
|
||||
type="hidden"
|
||||
value={btoa(JSON.stringify(payments))}
|
||||
hidden
|
||||
/>
|
||||
<Button
|
||||
type="primary"
|
||||
// data-ipayname="submit"
|
||||
@@ -314,11 +277,6 @@ const CardPaymentModalComponent = ({ bodyshop, cardPaymentModal, toggleModalVisi
|
||||
);
|
||||
}}
|
||||
</Form.Item>
|
||||
|
||||
{/* Lightbox payment response when it is completed */}
|
||||
<Form.Item name="paymentResponse" hidden>
|
||||
<Input type="hidden" />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Spin>
|
||||
</Card>
|
||||
|
||||
@@ -28,6 +28,7 @@ const CourtesyCarStatusComponent = ({ value, onChange }, ref) => {
|
||||
<Option value="courtesycars.status.out">{t("courtesycars.status.out")}</Option>
|
||||
<Option value="courtesycars.status.sold">{t("courtesycars.status.sold")}</Option>
|
||||
<Option value="courtesycars.status.leasereturn">{t("courtesycars.status.leasereturn")}</Option>
|
||||
<Option value="courtesycars.status.unavailable">{t("courtesycars.status.unavailable")}</Option>
|
||||
</Select>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -61,7 +61,11 @@ export default function CourtesyCarsList({ loading, courtesycars, refetch }) {
|
||||
{
|
||||
text: t("courtesycars.status.leasereturn"),
|
||||
value: "courtesycars.status.leasereturn"
|
||||
}
|
||||
},
|
||||
{
|
||||
text: t("courtesycars.status.unavailable"),
|
||||
value: "courtesycars.status.unavailable",
|
||||
},
|
||||
],
|
||||
onFilter: (value, record) => record.status === value,
|
||||
sortOrder: state.sortedInfo.columnKey === "status" && state.sortedInfo.order,
|
||||
|
||||
@@ -14,7 +14,6 @@ import {
|
||||
Typography
|
||||
} from "antd";
|
||||
import Dinero from "dinero.js";
|
||||
import dayjs from "../../utils/day";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
@@ -22,6 +21,7 @@ import { createStructuredSelector } from "reselect";
|
||||
import { determineDmsType } from "../../pages/dms/dms.container";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import i18n from "../../translations/i18n";
|
||||
import dayjs from "../../utils/day";
|
||||
import DmsCdkMakes from "../dms-cdk-makes/dms-cdk-makes.component";
|
||||
import DmsCdkMakesRefetch from "../dms-cdk-makes/dms-cdk-makes.refetch.component";
|
||||
import FormDatePicker from "../form-date-picker/form-date-picker.component";
|
||||
@@ -89,7 +89,7 @@ export function DmsPostForm({ bodyshop, socket, job, logsRef }) {
|
||||
job.area_of_damage && job.area_of_damage.impact1
|
||||
? " " +
|
||||
t("jobs.labels.dms.damageto", {
|
||||
area_of_damage: (job.area_of_damage && job.area_of_damage.impact1) || "UNKNOWN"
|
||||
area_of_damage: (job.area_of_damage && job.area_of_damage.impact1.padStart(2, "0")) || "UNKNOWN"
|
||||
})
|
||||
: ""
|
||||
}`.slice(0, 239),
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
import { Select, Space, Tag } from "antd";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const { Option } = Select;
|
||||
//To be used as a form element only.
|
||||
|
||||
const EmployeeSearchSelectEmail = ({ options, ...props }) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Select
|
||||
showSearch
|
||||
// value={option}
|
||||
style={{
|
||||
width: 400
|
||||
}}
|
||||
optionFilterProp="search"
|
||||
{...props}
|
||||
>
|
||||
{options
|
||||
? options.map((o) => (
|
||||
<Option key={o.id} value={o.user_email} search={`${o.employee_number} ${o.first_name} ${o.last_name}`}>
|
||||
<Space>
|
||||
{`${o.employee_number} ${o.first_name} ${o.last_name}`}
|
||||
|
||||
<Tag color="green">
|
||||
{o.flat_rate ? t("timetickets.labels.flat_rate") : t("timetickets.labels.straight_time")}
|
||||
</Tag>
|
||||
</Space>
|
||||
</Option>
|
||||
))
|
||||
: null}
|
||||
</Select>
|
||||
);
|
||||
};
|
||||
export default EmployeeSearchSelectEmail;
|
||||
@@ -0,0 +1,48 @@
|
||||
import { DatePicker } from "antd";
|
||||
import dayjs from "../../utils/day.js";
|
||||
import React, { useRef } from "react";
|
||||
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors.js";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
});
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(FormDateTimePickerEnhanced);
|
||||
|
||||
const dateFormat = "MM/DD/YYYY h:mm a";
|
||||
|
||||
export function FormDateTimePickerEnhanced({
|
||||
bodyshop,
|
||||
value,
|
||||
onBlur,
|
||||
onlyFuture,
|
||||
onlyToday,
|
||||
isDateOnly = true,
|
||||
...restProps
|
||||
}) {
|
||||
const ref = useRef();
|
||||
return (
|
||||
<div>
|
||||
<DatePicker
|
||||
ref={ref}
|
||||
value={value ? dayjs(value) : null}
|
||||
format={dateFormat}
|
||||
onBlur={onBlur}
|
||||
showToday={false}
|
||||
disabledDate={(d) => {
|
||||
if (onlyToday) {
|
||||
return !dayjs().isSame(d, "day");
|
||||
} else if (onlyFuture) {
|
||||
return dayjs().subtract(1, "day").isAfter(d);
|
||||
}
|
||||
}}
|
||||
{...restProps}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { forwardRef } from "react";
|
||||
//import DatePicker from "react-datepicker";
|
||||
//import "react-datepicker/src/stylesheets/datepicker.scss";
|
||||
import { TimePicker } from "antd";
|
||||
import { Space, TimePicker } from "antd";
|
||||
import dayjs from "../../utils/day";
|
||||
import FormDatePicker from "../form-date-picker/form-date-picker.component";
|
||||
//To be used as a form element only.
|
||||
@@ -14,7 +14,7 @@ const DateTimePicker = ({ value, onChange, onBlur, id, onlyFuture, ...restProps
|
||||
// };
|
||||
|
||||
return (
|
||||
<div id={id}>
|
||||
<Space direction="vertical" style={{ width: "100%" }} id={id}>
|
||||
<FormDatePicker
|
||||
{...restProps}
|
||||
{...(onlyFuture && {
|
||||
@@ -39,7 +39,7 @@ const DateTimePicker = ({ value, onChange, onBlur, id, onlyFuture, ...restProps
|
||||
format="hh:mm a"
|
||||
{...restProps}
|
||||
/>
|
||||
</div>
|
||||
</Space>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -3,14 +3,13 @@ import axios from "axios";
|
||||
import _ from "lodash";
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Link, useNavigate } from "react-router-dom";
|
||||
import { Link } from "react-router-dom";
|
||||
import PhoneNumberFormatter from "../../utils/PhoneFormatter";
|
||||
import OwnerNameDisplay, { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component";
|
||||
import VehicleVinDisplay from "../vehicle-vin-display/vehicle-vin-display.component";
|
||||
|
||||
export default function GlobalSearchOs() {
|
||||
const { t } = useTranslation();
|
||||
const history = useNavigate();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [data, setData] = useState(false);
|
||||
|
||||
@@ -178,15 +177,7 @@ export default function GlobalSearchOs() {
|
||||
};
|
||||
|
||||
return (
|
||||
<AutoComplete
|
||||
options={data}
|
||||
onSearch={handleSearch}
|
||||
defaultActiveFirstOption
|
||||
onSelect={(val, opt) => {
|
||||
history(opt.label.props.to);
|
||||
}}
|
||||
onClear={() => setData([])}
|
||||
>
|
||||
<AutoComplete options={data} onSearch={handleSearch} defaultActiveFirstOption onClear={() => setData([])}>
|
||||
<Input.Search
|
||||
size="large"
|
||||
placeholder={t("general.labels.globalsearch")}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { AutoComplete, Divider, Input, Space } from "antd";
|
||||
import _ from "lodash";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Link, useNavigate } from "react-router-dom";
|
||||
import { Link } from "react-router-dom";
|
||||
import { GLOBAL_SEARCH_QUERY } from "../../graphql/search.queries";
|
||||
import PhoneNumberFormatter from "../../utils/PhoneFormatter";
|
||||
import AlertComponent from "../alert/alert.component";
|
||||
@@ -12,7 +12,6 @@ import VehicleVinDisplay from "../vehicle-vin-display/vehicle-vin-display.compon
|
||||
|
||||
export default function GlobalSearch() {
|
||||
const { t } = useTranslation();
|
||||
const history = useNavigate();
|
||||
const [callSearch, { loading, error, data }] = useLazyQuery(GLOBAL_SEARCH_QUERY);
|
||||
|
||||
const executeSearch = (v) => {
|
||||
@@ -157,14 +156,7 @@ export default function GlobalSearch() {
|
||||
if (error) return <AlertComponent message={error.message} type="error" />;
|
||||
|
||||
return (
|
||||
<AutoComplete
|
||||
options={options}
|
||||
onSearch={handleSearch}
|
||||
defaultActiveFirstOption
|
||||
onSelect={(val, opt) => {
|
||||
history(opt.label.props.to);
|
||||
}}
|
||||
>
|
||||
<AutoComplete options={options} onSearch={handleSearch} defaultActiveFirstOption>
|
||||
<Input.Search
|
||||
size="large"
|
||||
placeholder={t("general.labels.globalsearch")}
|
||||
|
||||
@@ -17,6 +17,7 @@ import Icon, {
|
||||
LineChartOutlined,
|
||||
PaperClipOutlined,
|
||||
PhoneOutlined,
|
||||
PlusCircleOutlined,
|
||||
QuestionCircleFilled,
|
||||
ScheduleOutlined,
|
||||
SettingOutlined,
|
||||
@@ -30,7 +31,8 @@ import { Layout, Menu, Switch, Tooltip } from "antd";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { BsKanban } from "react-icons/bs";
|
||||
import { FaCalendarAlt, FaCarCrash, FaCreditCard, FaFileInvoiceDollar } from "react-icons/fa";
|
||||
import { FaCalendarAlt, FaCarCrash, FaCreditCard, FaFileInvoiceDollar, FaTasks } from "react-icons/fa";
|
||||
import { FiLogOut } from "react-icons/fi";
|
||||
import { GiPayMoney, GiPlayerTime, GiSettingsKnobs } from "react-icons/gi";
|
||||
import { IoBusinessOutline } from "react-icons/io5";
|
||||
import { RiSurveyLine } from "react-icons/ri";
|
||||
@@ -41,7 +43,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 { FiLogOut } from "react-icons/fi";
|
||||
import { checkBeta, handleBeta, setBeta } from "../../utils/betaHandler";
|
||||
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
||||
import { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component";
|
||||
@@ -54,12 +55,43 @@ const mapStateToProps = createStructuredSelector({
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
setBillEnterContext: (context) => dispatch(setModalContext({ context: context, modal: "billEnter" })),
|
||||
setTimeTicketContext: (context) => dispatch(setModalContext({ context: context, modal: "timeTicket" })),
|
||||
setBillEnterContext: (context) =>
|
||||
dispatch(
|
||||
setModalContext({
|
||||
context: context,
|
||||
modal: "billEnter"
|
||||
})
|
||||
),
|
||||
setTimeTicketContext: (context) =>
|
||||
dispatch(
|
||||
setModalContext({
|
||||
context: context,
|
||||
modal: "timeTicket"
|
||||
})
|
||||
),
|
||||
setPaymentContext: (context) => dispatch(setModalContext({ context: context, modal: "payment" })),
|
||||
setReportCenterContext: (context) => dispatch(setModalContext({ context: context, modal: "reportCenter" })),
|
||||
setReportCenterContext: (context) =>
|
||||
dispatch(
|
||||
setModalContext({
|
||||
context: context,
|
||||
modal: "reportCenter"
|
||||
})
|
||||
),
|
||||
signOutStart: () => dispatch(signOutStart()),
|
||||
setCardPaymentContext: (context) => dispatch(setModalContext({ context: context, modal: "cardPayment" }))
|
||||
setCardPaymentContext: (context) =>
|
||||
dispatch(
|
||||
setModalContext({
|
||||
context: context,
|
||||
modal: "cardPayment"
|
||||
})
|
||||
),
|
||||
setTaskUpsertContext: (context) =>
|
||||
dispatch(
|
||||
setModalContext({
|
||||
context: context,
|
||||
modal: "taskUpsert"
|
||||
})
|
||||
)
|
||||
});
|
||||
|
||||
function Header({
|
||||
@@ -73,7 +105,8 @@ function Header({
|
||||
setPaymentContext,
|
||||
setReportCenterContext,
|
||||
recentItems,
|
||||
setCardPaymentContext
|
||||
setCardPaymentContext,
|
||||
setTaskUpsertContext
|
||||
}) {
|
||||
const {
|
||||
treatments: { ImEXPay, DmsAp, Simple_Inventory }
|
||||
@@ -108,11 +141,13 @@ function Header({
|
||||
accountingChildren.push(
|
||||
{
|
||||
key: "bills",
|
||||
id: "header-accounting-bills",
|
||||
icon: <Icon component={FaFileInvoiceDollar} />,
|
||||
label: <Link to="/manage/bills">{t("menus.header.bills")}</Link>
|
||||
},
|
||||
{
|
||||
key: "enterbills",
|
||||
id: "header-accounting-enterbills",
|
||||
icon: <Icon component={GiPayMoney} />,
|
||||
label: t("menus.header.enterbills"),
|
||||
onClick: () => {
|
||||
@@ -132,6 +167,7 @@ function Header({
|
||||
},
|
||||
{
|
||||
key: "inventory",
|
||||
id: "header-accounting-inventory",
|
||||
icon: <Icon component={FaFileInvoiceDollar} />,
|
||||
label: <Link to="/manage/inventory">{t("menus.header.inventory")}</Link>
|
||||
}
|
||||
@@ -150,11 +186,13 @@ function Header({
|
||||
},
|
||||
{
|
||||
key: "allpayments",
|
||||
id: "header-accounting-allpayments",
|
||||
icon: <BankFilled />,
|
||||
label: <Link to="/manage/payments">{t("menus.header.allpayments")}</Link>
|
||||
},
|
||||
{
|
||||
key: "enterpayments",
|
||||
id: "header-accounting-enterpayments",
|
||||
icon: <Icon component={FaCreditCard} />,
|
||||
label: t("menus.header.enterpayment"),
|
||||
onClick: () => {
|
||||
@@ -170,6 +208,7 @@ function Header({
|
||||
if (ImEXPay.treatment === "on") {
|
||||
accountingChildren.push({
|
||||
key: "entercardpayments",
|
||||
id: "header-accounting-entercardpayments",
|
||||
icon: <Icon component={FaCreditCard} />,
|
||||
label: t("menus.header.entercardpayment"),
|
||||
onClick: () => {
|
||||
@@ -194,6 +233,7 @@ function Header({
|
||||
},
|
||||
{
|
||||
key: "timetickets",
|
||||
id: "header-accounting-timetickets",
|
||||
icon: <FieldTimeOutlined />,
|
||||
label: <Link to="/manage/timetickets">{t("menus.header.timetickets")}</Link>
|
||||
}
|
||||
@@ -202,6 +242,7 @@ function Header({
|
||||
if (bodyshop?.md_tasks_presets?.use_approvals) {
|
||||
accountingChildren.push({
|
||||
key: "ttapprovals",
|
||||
id: "header-accounting-ttapprovals",
|
||||
icon: <FieldTimeOutlined />,
|
||||
label: <Link to="/manage/ttapprovals">{t("menus.header.ttapprovals")}</Link>
|
||||
});
|
||||
@@ -211,6 +252,7 @@ function Header({
|
||||
key: "entertimetickets",
|
||||
icon: <Icon component={GiPlayerTime} />,
|
||||
label: t("menus.header.entertimeticket"),
|
||||
id: "header-accounting-entertimetickets",
|
||||
onClick: () => {
|
||||
setTimeTicketContext({
|
||||
actions: {},
|
||||
@@ -231,6 +273,7 @@ function Header({
|
||||
const accountingExportChildren = [
|
||||
{
|
||||
key: "receivables",
|
||||
id: "header-accounting-receivables",
|
||||
label: <Link to="/manage/accounting/receivables">{t("menus.header.accounting-receivables")}</Link>
|
||||
}
|
||||
];
|
||||
@@ -238,6 +281,7 @@ function Header({
|
||||
if (!((bodyshop && bodyshop.cdk_dealerid) || (bodyshop && bodyshop.pbs_serialnumber)) || DmsAp.treatment === "on") {
|
||||
accountingExportChildren.push({
|
||||
key: "payables",
|
||||
id: "header-accounting-payables",
|
||||
label: <Link to="/manage/accounting/payables">{t("menus.header.accounting-payables")}</Link>
|
||||
});
|
||||
}
|
||||
@@ -245,6 +289,7 @@ function Header({
|
||||
if (!((bodyshop && bodyshop.cdk_dealerid) || (bodyshop && bodyshop.pbs_serialnumber))) {
|
||||
accountingExportChildren.push({
|
||||
key: "payments",
|
||||
id: "header-accounting-payments",
|
||||
label: <Link to="/manage/accounting/payments">{t("menus.header.accounting-payments")}</Link>
|
||||
});
|
||||
}
|
||||
@@ -255,6 +300,7 @@ function Header({
|
||||
},
|
||||
{
|
||||
key: "exportlogs",
|
||||
id: "header-accounting-exportlogs",
|
||||
label: <Link to="/manage/accounting/exportlogs">{t("menus.header.export-logs")}</Link>
|
||||
}
|
||||
);
|
||||
@@ -268,6 +314,7 @@ function Header({
|
||||
) {
|
||||
accountingChildren.push({
|
||||
key: "accountingexport",
|
||||
id: "header-accounting-export",
|
||||
icon: <ExportOutlined />,
|
||||
label: t("menus.header.export"),
|
||||
children: accountingExportChildren
|
||||
@@ -278,10 +325,12 @@ function Header({
|
||||
{
|
||||
key: "home",
|
||||
icon: <HomeFilled />,
|
||||
id: "header-home",
|
||||
label: <Link to="/manage/">{t("menus.header.home")}</Link>
|
||||
},
|
||||
{
|
||||
key: "schedule",
|
||||
id: "header-schedule",
|
||||
icon: <Icon component={FaCalendarAlt} />,
|
||||
label: <Link to="/manage/schedule">{t("menus.header.schedule")}</Link>
|
||||
},
|
||||
@@ -293,16 +342,19 @@ function Header({
|
||||
children: [
|
||||
{
|
||||
key: "activejobs",
|
||||
id: "header-active-jobs",
|
||||
icon: <FileFilled />,
|
||||
label: <Link to="/manage/jobs">{t("menus.header.activejobs")}</Link>
|
||||
},
|
||||
{
|
||||
key: "readyjobs",
|
||||
id: "header-ready-jobs",
|
||||
icon: <CheckCircleOutlined />,
|
||||
label: <Link to="/manage/jobs/ready">{t("menus.header.readyjobs")}</Link>
|
||||
},
|
||||
{
|
||||
key: "parts-queue",
|
||||
id: "header-parts-queue",
|
||||
icon: <ToolFilled />,
|
||||
label: <Link to="/manage/partsqueue">{t("menus.header.parts-queue")}</Link>
|
||||
},
|
||||
@@ -314,6 +366,7 @@ function Header({
|
||||
},
|
||||
{
|
||||
key: "newjob",
|
||||
id: "header-new-job",
|
||||
icon: <FileAddOutlined />,
|
||||
label: <Link to="/manage/jobs/new">{t("menus.header.newjob")}</Link>
|
||||
},
|
||||
@@ -322,6 +375,7 @@ function Header({
|
||||
},
|
||||
{
|
||||
key: "alljobs",
|
||||
id: "header-all-jobs",
|
||||
icon: <UnorderedListOutlined />,
|
||||
label: <Link to="/manage/jobs/all">{t("menus.header.alljobs")}</Link>
|
||||
},
|
||||
@@ -330,6 +384,7 @@ function Header({
|
||||
},
|
||||
{
|
||||
key: "productionlist",
|
||||
id: "header-production-list",
|
||||
icon: <ScheduleOutlined />,
|
||||
label: <Link to="/manage/production/list">{t("menus.header.productionlist")}</Link>
|
||||
},
|
||||
@@ -341,6 +396,7 @@ function Header({
|
||||
? [
|
||||
{
|
||||
key: "productionboard",
|
||||
id: "header-production-board",
|
||||
icon: <Icon component={BsKanban} />,
|
||||
label: <Link to="/manage/production/board">{t("menus.header.productionboard")}</Link>
|
||||
}
|
||||
@@ -358,6 +414,7 @@ function Header({
|
||||
},
|
||||
{
|
||||
key: "scoreboard",
|
||||
id: "header-scoreboard",
|
||||
icon: <LineChartOutlined />,
|
||||
label: <Link to="/manage/scoreboard">{t("menus.header.scoreboard")}</Link>
|
||||
}
|
||||
@@ -368,15 +425,18 @@ function Header({
|
||||
{
|
||||
key: "customers",
|
||||
icon: <UserOutlined />,
|
||||
id: "header-customers",
|
||||
label: t("menus.header.customers"),
|
||||
children: [
|
||||
{
|
||||
key: "owners",
|
||||
id: "header-owners",
|
||||
icon: <TeamOutlined />,
|
||||
label: <Link to="/manage/owners">{t("menus.header.owners")}</Link>
|
||||
},
|
||||
{
|
||||
key: "vehicles",
|
||||
id: "header-vehicles",
|
||||
icon: <CarFilled />,
|
||||
label: <Link to="/manage/vehicles">{t("menus.header.vehicles")}</Link>
|
||||
}
|
||||
@@ -390,21 +450,25 @@ function Header({
|
||||
? [
|
||||
{
|
||||
key: "ccs",
|
||||
id: "header-css",
|
||||
icon: <CarFilled />,
|
||||
label: t("menus.header.courtesycars"),
|
||||
children: [
|
||||
{
|
||||
key: "courtesycarsall",
|
||||
id: "header-courtesycars-all",
|
||||
icon: <CarFilled />,
|
||||
label: <Link to="/manage/courtesycars">{t("menus.header.courtesycars-all")}</Link>
|
||||
},
|
||||
{
|
||||
key: "contracts",
|
||||
id: "header-contracts",
|
||||
icon: <FileFilled />,
|
||||
label: <Link to="/manage/courtesycars/contracts">{t("menus.header.courtesycars-contracts")}</Link>
|
||||
},
|
||||
{
|
||||
key: "newcontract",
|
||||
id: "header-newcontract",
|
||||
icon: <FileAddFilled />,
|
||||
label: <Link to="/manage/courtesycars/contracts/new">{t("menus.header.courtesycars-newcontract")}</Link>
|
||||
}
|
||||
@@ -417,6 +481,7 @@ function Header({
|
||||
? [
|
||||
{
|
||||
key: "accounting",
|
||||
id: "header-accounting",
|
||||
icon: <DollarCircleFilled />,
|
||||
label: t("menus.header.accounting"),
|
||||
children: accountingChildren
|
||||
@@ -425,6 +490,7 @@ function Header({
|
||||
: []),
|
||||
{
|
||||
key: "phonebook",
|
||||
id: "header-phonebook",
|
||||
icon: <PhoneOutlined />,
|
||||
label: <Link to="/manage/phonebook">{t("menus.header.phonebook")}</Link>
|
||||
},
|
||||
@@ -436,28 +502,62 @@ function Header({
|
||||
? [
|
||||
{
|
||||
key: "temporarydocs",
|
||||
id: "header-temporarydocs",
|
||||
icon: <PaperClipOutlined />,
|
||||
label: <Link to="/manage/temporarydocs">{t("menus.header.temporarydocs")}</Link>
|
||||
}
|
||||
]
|
||||
: []),
|
||||
{
|
||||
key: "tasks",
|
||||
id: "tasks",
|
||||
icon: <FaTasks />,
|
||||
label: t("menus.header.tasks"),
|
||||
children: [
|
||||
{
|
||||
key: "createTask",
|
||||
icon: <PlusCircleOutlined />,
|
||||
label: t("menus.header.create_task"),
|
||||
onClick: () => {
|
||||
setTaskUpsertContext({
|
||||
actions: {},
|
||||
context: {}
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
key: "mytasks",
|
||||
icon: <FaTasks />,
|
||||
label: <Link to="/manage/tasks/mytasks">{t("menus.header.my_tasks")}</Link>
|
||||
},
|
||||
{
|
||||
key: "all_tasks",
|
||||
icon: <FaTasks />,
|
||||
label: <Link to="/manage/tasks/alltasks">{t("menus.header.all_tasks")}</Link>
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
key: "shopsubmenu",
|
||||
id: "header-shopsubmenu",
|
||||
icon: <SettingOutlined />,
|
||||
label: t("menus.header.shop"),
|
||||
children: [
|
||||
{
|
||||
key: "shop",
|
||||
id: "header-shop",
|
||||
icon: <Icon component={GiSettingsKnobs} />,
|
||||
label: <Link to="/manage/shop?tab=info">{t("menus.header.shop_config")}</Link>
|
||||
},
|
||||
{
|
||||
key: "dashboard",
|
||||
id: "header-dashboard",
|
||||
icon: <DashboardFilled />,
|
||||
label: <Link to="/manage/dashboard">{t("menus.header.dashboard")}</Link>
|
||||
},
|
||||
{
|
||||
key: "reportcenter",
|
||||
id: "header-reportcenter",
|
||||
icon: <BarChartOutlined />,
|
||||
label: t("menus.header.reportcenter"),
|
||||
onClick: () => {
|
||||
@@ -469,6 +569,7 @@ function Header({
|
||||
},
|
||||
{
|
||||
key: "shop-vendors",
|
||||
id: "header-shop-vendors",
|
||||
icon: <Icon component={IoBusinessOutline} />,
|
||||
label: <Link to="/manage/shop/vendors">{t("menus.header.shop_vendors")}</Link>
|
||||
},
|
||||
@@ -480,6 +581,7 @@ function Header({
|
||||
? [
|
||||
{
|
||||
key: "shop-csi",
|
||||
id: "header-shop-csi",
|
||||
icon: <Icon component={RiSurveyLine} />,
|
||||
label: <Link to="/manage/shop/csi">{t("menus.header.shop_csi")}</Link>
|
||||
}
|
||||
@@ -493,6 +595,7 @@ function Header({
|
||||
children: [
|
||||
{
|
||||
key: "signout",
|
||||
id: "header-signout",
|
||||
icon: <Icon component={FiLogOut} />,
|
||||
danger: true,
|
||||
label: t("user.actions.signout"),
|
||||
@@ -500,6 +603,7 @@ function Header({
|
||||
},
|
||||
{
|
||||
key: "help",
|
||||
id: "header-help",
|
||||
icon: <Icon component={QuestionCircleFilled} />,
|
||||
label: t("menus.header.help"),
|
||||
onClick: () => {
|
||||
@@ -514,14 +618,22 @@ function Header({
|
||||
);
|
||||
}
|
||||
},
|
||||
// {
|
||||
// key: 'rescue',
|
||||
// icon: <Icon component={CarFilled}/>,
|
||||
// label: t("menus.header.rescueme"),
|
||||
// onClick: () => {
|
||||
// window.open("https://imexrescue.com/", "_blank");
|
||||
// }
|
||||
// },
|
||||
...(InstanceRenderManager({
|
||||
imex: true,
|
||||
rome: false,
|
||||
promanager: false
|
||||
})
|
||||
? [
|
||||
{
|
||||
key: "rescue",
|
||||
icon: <Icon component={CarFilled} />,
|
||||
label: t("menus.header.rescueme"),
|
||||
onClick: () => {
|
||||
window.open("https://imexrescue.com/", "_blank");
|
||||
}
|
||||
}
|
||||
]
|
||||
: []),
|
||||
|
||||
...(InstanceRenderManager({
|
||||
imex: true,
|
||||
@@ -531,6 +643,7 @@ function Header({
|
||||
? [
|
||||
{
|
||||
key: "shiftclock",
|
||||
id: "header-shiftclock",
|
||||
icon: <Icon component={GiPlayerTime} />,
|
||||
label: <Link to="/manage/shiftclock">{t("menus.header.shiftclock")}</Link>
|
||||
}
|
||||
@@ -538,6 +651,7 @@ function Header({
|
||||
: []),
|
||||
{
|
||||
key: "profile",
|
||||
id: "header-profile",
|
||||
icon: <UserOutlined />,
|
||||
label: <Link to="/manage/profile">{t("menus.currentuser.profile")}</Link>
|
||||
}
|
||||
@@ -573,6 +687,7 @@ function Header({
|
||||
{
|
||||
key: "recent",
|
||||
icon: <ClockCircleFilled />,
|
||||
id: "header-recent",
|
||||
children: recentItems.map((i, idx) => ({
|
||||
key: idx,
|
||||
label: <Link to={i.url}>{i.label}</Link>
|
||||
@@ -586,6 +701,7 @@ function Header({
|
||||
imex: () => {
|
||||
menuItems.push({
|
||||
key: "beta-switch",
|
||||
id: "header-beta-switch",
|
||||
style: { marginLeft: "auto" },
|
||||
label: (
|
||||
<Tooltip
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { Alert, Card, Col, Row, Space, Statistic, Tooltip, Typography } from 'antd';
|
||||
import Dinero from 'dinero.js';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import InstanceRenderManager from '../../utils/instanceRenderMgr';
|
||||
import AlertComponent from '../alert/alert.component';
|
||||
import LoadingSkeleton from '../loading-skeleton/loading-skeleton.component';
|
||||
import './job-bills-total.styles.scss';
|
||||
import { Alert, Card, Col, Row, Space, Statistic, Tooltip, Typography } from "antd";
|
||||
import Dinero from "dinero.js";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
||||
import AlertComponent from "../alert/alert.component";
|
||||
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component";
|
||||
import "./job-bills-total.styles.scss";
|
||||
|
||||
export default function JobBillsTotalComponent({
|
||||
loading,
|
||||
@@ -13,16 +13,16 @@ export default function JobBillsTotalComponent({
|
||||
partsOrders,
|
||||
jobTotals,
|
||||
showWarning,
|
||||
warningCallback,
|
||||
warningCallback
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
if (loading) return <LoadingSkeleton />;
|
||||
if (!!!jobTotals) {
|
||||
if (showWarning && warningCallback && typeof warningCallback === 'function') {
|
||||
warningCallback({ key: 'bills', warning: t('jobs.errors.nofinancial') });
|
||||
if (showWarning && warningCallback && typeof warningCallback === "function") {
|
||||
warningCallback({ key: "bills", warning: t("jobs.errors.nofinancial") });
|
||||
}
|
||||
return <AlertComponent type="error" message={t('jobs.errors.nofinancial')} />;
|
||||
return <AlertComponent type="error" message={t("jobs.errors.nofinancial")} />;
|
||||
}
|
||||
|
||||
const totals = jobTotals;
|
||||
@@ -109,64 +109,60 @@ export default function JobBillsTotalComponent({
|
||||
const discrepWithCms = discrepWithLbrAdj.add(totalReturns);
|
||||
const calculatedCreditsNotReceived = totalReturns.subtract(billCms); //billCms is tracked as a negative number.
|
||||
|
||||
if (showWarning && warningCallback && typeof warningCallback === 'function') {
|
||||
if (
|
||||
discrepWithCms.getAmount() !== 0 ||
|
||||
discrepWithLbrAdj.getAmount() !== 0 ||
|
||||
discrepancy.getAmount() !== 0
|
||||
) {
|
||||
if (showWarning && warningCallback && typeof warningCallback === "function") {
|
||||
if (discrepWithCms.getAmount() !== 0) {
|
||||
warningCallback({
|
||||
key: 'bills',
|
||||
warning: t('jobs.labels.outstanding_reconciliation_discrep'),
|
||||
key: "bills",
|
||||
warning: t("jobs.labels.outstanding_reconciliation_discrep")
|
||||
});
|
||||
}
|
||||
if (calculatedCreditsNotReceived.getAmount() > 0) {
|
||||
warningCallback({ key: 'cm', warning: t('jobs.labels.outstanding_credit_memos') });
|
||||
warningCallback({ key: "cm", warning: t("jobs.labels.outstanding_credit_memos") });
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Row gutter={[16, 16]}>
|
||||
<Col md={24} lg={18}>
|
||||
<Card title={t('jobs.labels.jobtotals')} style={{ height: '100%' }}>
|
||||
<Card title={t("jobs.labels.jobtotals")} style={{ height: "100%" }}>
|
||||
<Space wrap size="large">
|
||||
<Tooltip
|
||||
title={
|
||||
<div
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: t('jobs.labels.plitooltips.partstotal'),
|
||||
__html: t("jobs.labels.plitooltips.partstotal")
|
||||
}}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Statistic title={t('jobs.labels.rosaletotal')} value={totalPartsSublet.toFormat()} />
|
||||
<Statistic title={t("jobs.labels.rosaletotal")} value={totalPartsSublet.toFormat()} />
|
||||
</Tooltip>
|
||||
<Typography.Title>-</Typography.Title>
|
||||
<Tooltip
|
||||
title={
|
||||
<div
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: t('jobs.labels.plitooltips.billtotal'),
|
||||
__html: t("jobs.labels.plitooltips.billtotal")
|
||||
}}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Statistic title={t('bills.labels.retailtotal')} value={billTotals.toFormat()} />
|
||||
<Statistic title={t("bills.labels.retailtotal")} value={billTotals.toFormat()} />
|
||||
</Tooltip>
|
||||
<Typography.Title>=</Typography.Title>
|
||||
<Tooltip
|
||||
title={
|
||||
<div
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: t('jobs.labels.plitooltips.discrep1'),
|
||||
__html: t("jobs.labels.plitooltips.discrep1")
|
||||
}}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Statistic
|
||||
title={t('bills.labels.discrepancy')}
|
||||
title={t("bills.labels.discrepancy")}
|
||||
valueStyle={{
|
||||
color: discrepancy.getAmount() === 0 ? 'green' : 'red',
|
||||
color: discrepancy.getAmount() === 0 ? "green" : "red"
|
||||
}}
|
||||
value={discrepancy.toFormat()}
|
||||
/>
|
||||
@@ -176,27 +172,27 @@ export default function JobBillsTotalComponent({
|
||||
title={
|
||||
<div
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: t('jobs.labels.plitooltips.laboradj'),
|
||||
__html: t("jobs.labels.plitooltips.laboradj")
|
||||
}}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Statistic title={t('bills.labels.dedfromlbr')} value={lbrAdjustments.toFormat()} />
|
||||
<Statistic title={t("bills.labels.dedfromlbr")} value={lbrAdjustments.toFormat()} />
|
||||
</Tooltip>
|
||||
<Typography.Title>=</Typography.Title>
|
||||
<Tooltip
|
||||
title={
|
||||
<div
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: t('jobs.labels.plitooltips.discrep2'),
|
||||
__html: t("jobs.labels.plitooltips.discrep2")
|
||||
}}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Statistic
|
||||
title={t('bills.labels.discrepancy')}
|
||||
title={t("bills.labels.discrepancy")}
|
||||
valueStyle={{
|
||||
color: discrepWithLbrAdj.getAmount() === 0 ? 'green' : 'red',
|
||||
color: discrepWithLbrAdj.getAmount() === 0 ? "green" : "red"
|
||||
}}
|
||||
value={discrepWithLbrAdj.toFormat()}
|
||||
/>
|
||||
@@ -206,27 +202,27 @@ export default function JobBillsTotalComponent({
|
||||
title={
|
||||
<div
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: t('jobs.labels.plitooltips.creditmemos'),
|
||||
__html: t("jobs.labels.plitooltips.creditmemos")
|
||||
}}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Statistic title={t('bills.labels.totalreturns')} value={totalReturns.toFormat()} />
|
||||
<Statistic title={t("bills.labels.totalreturns")} value={totalReturns.toFormat()} />
|
||||
</Tooltip>
|
||||
<Typography.Title>=</Typography.Title>
|
||||
<Tooltip
|
||||
title={
|
||||
<div
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: t('jobs.labels.plitooltips.discrep3'),
|
||||
__html: t("jobs.labels.plitooltips.discrep3")
|
||||
}}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Statistic
|
||||
title={t('bills.labels.discrepancy')}
|
||||
title={t("bills.labels.discrepancy")}
|
||||
valueStyle={{
|
||||
color: discrepWithCms.getAmount() === 0 ? 'green' : 'red',
|
||||
color: discrepWithCms.getAmount() === 0 ? "green" : "red"
|
||||
}}
|
||||
value={discrepWithCms.toFormat()}
|
||||
/>
|
||||
@@ -238,40 +234,40 @@ export default function JobBillsTotalComponent({
|
||||
discrepWithLbrAdj.getAmount() !== 0 ||
|
||||
discrepancy.getAmount() !== 0) && (
|
||||
<Alert
|
||||
style={{ margin: '8px 0px' }}
|
||||
style={{ margin: "8px 0px" }}
|
||||
type="warning"
|
||||
message={t('jobs.labels.outstanding_reconciliation_discrep')}
|
||||
message={t("jobs.labels.outstanding_reconciliation_discrep")}
|
||||
/>
|
||||
)}
|
||||
</Card>
|
||||
</Col>
|
||||
<Col md={24} lg={6}>
|
||||
<Card title={t('jobs.labels.returntotals')} style={{ height: '100%' }}>
|
||||
<Card title={t("jobs.labels.returntotals")} style={{ height: "100%" }}>
|
||||
<Space wrap>
|
||||
<Tooltip
|
||||
title={
|
||||
<div
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: t('jobs.labels.plitooltips.totalreturns'),
|
||||
__html: t("jobs.labels.plitooltips.totalreturns")
|
||||
}}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Statistic title={t('bills.labels.totalreturns')} value={totalReturns.toFormat()} />
|
||||
<Statistic title={t("bills.labels.totalreturns")} value={totalReturns.toFormat()} />
|
||||
</Tooltip>
|
||||
<Tooltip
|
||||
title={
|
||||
<div
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: t('jobs.labels.plitooltips.calculatedcreditsnotreceived'),
|
||||
__html: t("jobs.labels.plitooltips.calculatedcreditsnotreceived")
|
||||
}}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Statistic
|
||||
title={t('bills.labels.calculatedcreditsnotreceived')}
|
||||
title={t("bills.labels.calculatedcreditsnotreceived")}
|
||||
valueStyle={{
|
||||
color: calculatedCreditsNotReceived.getAmount() <= 0 ? 'green' : 'red',
|
||||
color: calculatedCreditsNotReceived.getAmount() <= 0 ? "green" : "red"
|
||||
}}
|
||||
value={
|
||||
calculatedCreditsNotReceived.getAmount() >= 0
|
||||
@@ -284,15 +280,15 @@ export default function JobBillsTotalComponent({
|
||||
title={
|
||||
<div
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: t('jobs.labels.plitooltips.creditsnotreceived'),
|
||||
__html: t("jobs.labels.plitooltips.creditsnotreceived")
|
||||
}}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Statistic
|
||||
title={t('bills.labels.creditsnotreceived')}
|
||||
title={t("bills.labels.creditsnotreceived")}
|
||||
valueStyle={{
|
||||
color: totalReturnsMarkedNotReceived.getAmount() <= 0 ? 'green' : 'red',
|
||||
color: totalReturnsMarkedNotReceived.getAmount() <= 0 ? "green" : "red"
|
||||
}}
|
||||
value={
|
||||
totalReturnsMarkedNotReceived.getAmount() >= 0
|
||||
@@ -303,11 +299,7 @@ export default function JobBillsTotalComponent({
|
||||
</Tooltip>
|
||||
</Space>
|
||||
{showWarning && calculatedCreditsNotReceived.getAmount() > 0 && (
|
||||
<Alert
|
||||
style={{ margin: '8px 0px' }}
|
||||
type="warning"
|
||||
message={t('jobs.labels.outstanding_credit_memos')}
|
||||
/>
|
||||
<Alert style={{ margin: "8px 0px" }} type="warning" message={t("jobs.labels.outstanding_credit_memos")} />
|
||||
)}
|
||||
</Card>
|
||||
</Col>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { useMutation } from "@apollo/client";
|
||||
import { Button, Card, Form, Input, notification, Switch } from "antd";
|
||||
import dayjs from "../../../../utils/day";
|
||||
import queryString from "query-string";
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
@@ -14,6 +13,7 @@ import { UPDATE_OWNER } from "../../../../graphql/owners.queries";
|
||||
import { insertAuditTrail } from "../../../../redux/application/application.actions";
|
||||
import { selectBodyshop, selectCurrentUser } from "../../../../redux/user/user.selectors";
|
||||
import AuditTrailMapping from "../../../../utils/AuditTrailMappings";
|
||||
import dayjs from "../../../../utils/day";
|
||||
import ConfigFormComponents from "../../../config-form-components/config-form-components.component";
|
||||
import DateTimePicker from "../../../form-date-time-picker/form-date-time-picker.component";
|
||||
|
||||
@@ -275,7 +275,19 @@ export function JobChecklistForm({ insertAuditTrail, formItems, bodyshop, curren
|
||||
>
|
||||
<DateTimePicker disabled={readOnly} />
|
||||
</Form.Item>
|
||||
<Form.Item name="actual_delivery" label={t("jobs.fields.actual_delivery")} disabled={readOnly}>
|
||||
<Form.Item
|
||||
name="actual_delivery"
|
||||
label={t("jobs.fields.actual_delivery")}
|
||||
rules={[
|
||||
{
|
||||
required: bodyshop.deliverchecklist.actual_delivery
|
||||
? bodyshop.deliverchecklist.actual_delivery
|
||||
: false
|
||||
//message: t("general.validation.required"),
|
||||
}
|
||||
]}
|
||||
disabled={readOnly}
|
||||
>
|
||||
<DateTimePicker disabled={readOnly} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
import React from 'react';
|
||||
import React from "react";
|
||||
|
||||
import { useQuery } from '@apollo/client';
|
||||
import { useSplitTreatments } from '@splitsoftware/splitio-react';
|
||||
import { connect } from 'react-redux';
|
||||
import { createStructuredSelector } from 'reselect';
|
||||
import { GET_LINE_TICKET_BY_PK } from '../../graphql/jobs-lines.queries';
|
||||
import { selectJobReadOnly } from '../../redux/application/application.selectors';
|
||||
import { selectBodyshop } from '../../redux/user/user.selectors';
|
||||
import AlertComponent from '../alert/alert.component';
|
||||
import LaborAllocationsTableComponent from '../labor-allocations-table/labor-allocations-table.component';
|
||||
import PayrollLaborAllocationsTable from '../labor-allocations-table/labor-allocations-table.payroll.component';
|
||||
import LoadingSkeleton from '../loading-skeleton/loading-skeleton.component';
|
||||
import { useQuery } from "@apollo/client";
|
||||
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { GET_LINE_TICKET_BY_PK } from "../../graphql/jobs-lines.queries";
|
||||
import { selectJobReadOnly } from "../../redux/application/application.selectors";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import AlertComponent from "../alert/alert.component";
|
||||
import LaborAllocationsTableComponent from "../labor-allocations-table/labor-allocations-table.component";
|
||||
import PayrollLaborAllocationsTable from "../labor-allocations-table/labor-allocations-table.payroll.component";
|
||||
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component";
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
//currentUser: selectCurrentUser
|
||||
bodyshop: selectBodyshop,
|
||||
jobRO: selectJobReadOnly,
|
||||
jobRO: selectJobReadOnly
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
@@ -24,21 +24,21 @@ export default connect(mapStateToProps, mapDispatchToProps)(JobCloseRoGuardLabor
|
||||
export function JobCloseRoGuardLabor({ job, jobRO, bodyshop, form, warningCallback }) {
|
||||
const { loading, error, data, refetch } = useQuery(GET_LINE_TICKET_BY_PK, {
|
||||
variables: { id: job.id },
|
||||
fetchPolicy: 'network-only',
|
||||
nextFetchPolicy: 'network-only',
|
||||
fetchPolicy: "network-only",
|
||||
nextFetchPolicy: "network-only"
|
||||
});
|
||||
const {
|
||||
treatments: { Enhanced_Payroll },
|
||||
treatments: { Enhanced_Payroll }
|
||||
} = useSplitTreatments({
|
||||
attributes: {},
|
||||
names: ['Enhanced_Payroll'],
|
||||
splitKey: bodyshop.imexshopid,
|
||||
names: ["Enhanced_Payroll"],
|
||||
splitKey: bodyshop.imexshopid
|
||||
});
|
||||
|
||||
if (loading) return <LoadingSkeleton />;
|
||||
if (error) return <AlertComponent message={error.message} type="error" />;
|
||||
|
||||
return Enhanced_Payroll.treatment === 'on' ? (
|
||||
return Enhanced_Payroll.treatment === "on" ? (
|
||||
<PayrollLaborAllocationsTable
|
||||
jobId={job.id}
|
||||
timetickets={data ? data.timetickets : []}
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import React, { useEffect } from "react";
|
||||
|
||||
import { Alert, Card } from 'antd';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { connect } from 'react-redux';
|
||||
import { createStructuredSelector } from 'reselect';
|
||||
import { selectJobReadOnly } from '../../redux/application/application.selectors';
|
||||
import { selectBodyshop } from '../../redux/user/user.selectors';
|
||||
import { useMemo } from 'react';
|
||||
import Dinero from 'dinero.js';
|
||||
import DataLabel from '../data-label/data-label.component';
|
||||
import { Alert, Card } from "antd";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectJobReadOnly } from "../../redux/application/application.selectors";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import { useMemo } from "react";
|
||||
import Dinero from "dinero.js";
|
||||
import DataLabel from "../data-label/data-label.component";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
//currentUser: selectCurrentUser
|
||||
bodyshop: selectBodyshop,
|
||||
jobRO: selectJobReadOnly,
|
||||
jobRO: selectJobReadOnly
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
@@ -41,25 +41,21 @@ export function JobCloseRoGuardProfit({ job, jobRO, bodyshop, form, warningCallb
|
||||
|
||||
useEffect(() => {
|
||||
if (balance.getAmount() !== 0) {
|
||||
warningCallback({ key: 'ar', warning: t('jobs.labels.outstanding_ar') });
|
||||
warningCallback({ key: "ar", warning: t("jobs.labels.outstanding_ar") });
|
||||
}
|
||||
}, [balance, t, warningCallback]);
|
||||
|
||||
return (
|
||||
<Card title={t('jobs.labels.accountsreceivable')} style={{ height: '100%' }}>
|
||||
<DataLabel label={t('payments.labels.totalpayments')}>{total.toFormat()}</DataLabel>
|
||||
<Card title={t("jobs.labels.accountsreceivable")} style={{ height: "100%" }}>
|
||||
<DataLabel label={t("payments.labels.totalpayments")}>{total.toFormat()}</DataLabel>
|
||||
<DataLabel
|
||||
valueStyle={{ color: balance.getAmount() !== 0 ? 'red' : 'green' }}
|
||||
label={t('payments.labels.balance')}
|
||||
valueStyle={{ color: balance.getAmount() !== 0 ? "red" : "green" }}
|
||||
label={t("payments.labels.balance")}
|
||||
>
|
||||
{balance.toFormat()}
|
||||
</DataLabel>
|
||||
{balance.getAmount() !== 0 && (
|
||||
<Alert
|
||||
style={{ margin: '8px 0px' }}
|
||||
type="warning"
|
||||
message={t('jobs.labels.outstanding_ar')}
|
||||
/>
|
||||
<Alert style={{ margin: "8px 0px" }} type="warning" message={t("jobs.labels.outstanding_ar")} />
|
||||
)}
|
||||
</Card>
|
||||
);
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
import React from 'react';
|
||||
import React from "react";
|
||||
|
||||
import { useQuery } from '@apollo/client';
|
||||
import { connect } from 'react-redux';
|
||||
import { createStructuredSelector } from 'reselect';
|
||||
import { QUERY_BILLS_BY_JOBID } from '../../graphql/bills.queries';
|
||||
import { selectJobReadOnly } from '../../redux/application/application.selectors';
|
||||
import { selectBodyshop } from '../../redux/user/user.selectors';
|
||||
import AlertComponent from '../alert/alert.component';
|
||||
import JobBillsTotalComponent from '../job-bills-total/job-bills-total.component';
|
||||
import { useQuery } from "@apollo/client";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { QUERY_BILLS_BY_JOBID } from "../../graphql/bills.queries";
|
||||
import { selectJobReadOnly } from "../../redux/application/application.selectors";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import AlertComponent from "../alert/alert.component";
|
||||
import JobBillsTotalComponent from "../job-bills-total/job-bills-total.component";
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
//currentUser: selectCurrentUser
|
||||
bodyshop: selectBodyshop,
|
||||
jobRO: selectJobReadOnly,
|
||||
jobRO: selectJobReadOnly
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
@@ -21,8 +21,8 @@ export default connect(mapStateToProps, mapDispatchToProps)(JobCloseRoGuardBills
|
||||
export function JobCloseRoGuardBills({ job, jobRO, bodyshop, form, warningCallback }) {
|
||||
const { loading, error, data } = useQuery(QUERY_BILLS_BY_JOBID, {
|
||||
variables: { jobid: job.id },
|
||||
fetchPolicy: 'network-only',
|
||||
nextFetchPolicy: 'network-only',
|
||||
fetchPolicy: "network-only",
|
||||
nextFetchPolicy: "network-only"
|
||||
});
|
||||
|
||||
if (error) return <AlertComponent message={error.message} type="error" />;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useCallback, useState } from "react";
|
||||
import { LockOutlined } from "@ant-design/icons";
|
||||
import { Badge, Card, Col, Collapse, Form, Input, Row, Space, Tooltip } from "antd";
|
||||
import React, { useCallback, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
@@ -12,9 +12,9 @@ import JobCloseRoGuardBills from "./job-close-ro-guard.bills";
|
||||
import JobCloseRoGuardPpd from "./job-close-ro-guard.ppd";
|
||||
import JobCloseRoGuardProfit from "./job-close-ro-guard.profit";
|
||||
import "./job-close-ro-guard.styles.scss";
|
||||
import JobCloseRoGuardSublet from "./job-close-ro-guard.sublet";
|
||||
import JobCloseRoGuardTtLifecycle from "./job-close-ro-guard.tt-lifecycle";
|
||||
// import JobCloseRoGuardSublet from "./job-close-ro-guard.sublet";
|
||||
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
||||
import JobCloseRoGuardTtLifecycle from "./job-close-ro-guard.tt-lifecycle";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
//currentUser: selectCurrentUser
|
||||
@@ -76,20 +76,21 @@ export function JobCloseRoGuardContainer({ job, jobRO, bodyshop, form }) {
|
||||
<Col className="ro-guard-col-multiple" md={24} lg={6}>
|
||||
<Row gutter={[32, 32]} style={{ height: "100%" }}>
|
||||
<Col span={24}>
|
||||
<JobCloseRoGuardProfit
|
||||
job={job}
|
||||
form={form} //warningCallback={warningCallback}
|
||||
/>
|
||||
<JobCloseRoGuardProfit job={job} form={form} warningCallback={warningCallback} />
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<JobCloseRoGuardAr job={job} form={form} warningCallback={warningCallback} />
|
||||
</Col>
|
||||
</Row>
|
||||
</Col>
|
||||
<Col className="ro-guard-col-50" md={24} lg={8}>
|
||||
<JobCloseRoGuardSublet job={job} warningCallback={warningCallback} />
|
||||
{InstanceRenderManager({ rome: <JobCloseRoGuardPpd job={job} warningCallback={warningCallback} /> })}
|
||||
</Col>
|
||||
{InstanceRenderManager({
|
||||
rome: (
|
||||
<Col md={24} lg={8}>
|
||||
{/* <JobCloseRoGuardSublet job={job} warningCallback={warningCallback} /> */}
|
||||
<JobCloseRoGuardPpd job={job} warningCallback={warningCallback} />
|
||||
</Col>
|
||||
)
|
||||
})}
|
||||
<Col className="ro-guard-col" md={24} lg={10}>
|
||||
<JobCloseRoGuardLabor job={job} form={form} warningCallback={warningCallback} />
|
||||
</Col>
|
||||
|
||||
@@ -1,20 +1,19 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import React, { useEffect, useState } from "react";
|
||||
|
||||
import { LockOutlined } from '@ant-design/icons';
|
||||
import { Card, Form, Input } from 'antd';
|
||||
import axios from 'axios';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { connect } from 'react-redux';
|
||||
import { createStructuredSelector } from 'reselect';
|
||||
import { selectJobReadOnly } from '../../redux/application/application.selectors';
|
||||
import { selectBodyshop } from '../../redux/user/user.selectors';
|
||||
import JobCostingStatistics from '../job-costing-statistics/job-costing-statistics.component';
|
||||
import LoadingSkeleton from '../loading-skeleton/loading-skeleton.component';
|
||||
import { Alert, Card } from "antd";
|
||||
import axios from "axios";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectJobReadOnly } from "../../redux/application/application.selectors";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import JobCostingStatistics from "../job-costing-statistics/job-costing-statistics.component";
|
||||
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
//currentUser: selectCurrentUser
|
||||
bodyshop: selectBodyshop,
|
||||
jobRO: selectJobReadOnly,
|
||||
jobRO: selectJobReadOnly
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
@@ -31,7 +30,7 @@ export function JobCloseRoGuardProfit({ job, jobRO, bodyshop, form, warningCallb
|
||||
try {
|
||||
if (job.id) {
|
||||
setLoading(true);
|
||||
const { data } = await axios.post('/job/costing', { jobid: job.id });
|
||||
const { data } = await axios.post("/job/costing", { jobid: job.id });
|
||||
setCostingData(data);
|
||||
}
|
||||
} catch (error) {}
|
||||
@@ -45,16 +44,19 @@ export function JobCloseRoGuardProfit({ job, jobRO, bodyshop, form, warningCallb
|
||||
parseFloat(costingData?.summaryData.gppercent) < bodyshop?.md_ro_guard?.totalgppercent_minimum;
|
||||
|
||||
useEffect(() => {
|
||||
if (enforceProfitPassword && typeof warningCallback === 'function') {
|
||||
warningCallback({ key: 'profit', warning: t('jobs.labels.profitbypassrequired') });
|
||||
if (enforceProfitPassword && typeof warningCallback === "function") {
|
||||
warningCallback({ key: "profit", warning: t("jobs.labels.profitbypassrequired") });
|
||||
}
|
||||
}, [enforceProfitPassword, t, warningCallback]);
|
||||
|
||||
if (loading || !costingData) return <LoadingSkeleton />;
|
||||
|
||||
return (
|
||||
<Card title={t('jobs.labels.profits')} style={{ height: '100%' }}>
|
||||
<Card title={t("jobs.labels.profits")} style={{ height: "100%" }}>
|
||||
<JobCostingStatistics summaryData={costingData?.summaryData} onlyGP />
|
||||
{enforceProfitPassword && (
|
||||
<Alert style={{ margin: "8px 0px" }} type="warning" message={t("jobs.labels.profitbypassrequired")} />
|
||||
)}
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import React, { useEffect } from "react";
|
||||
|
||||
import { Alert, Card, Table } from 'antd';
|
||||
import { t } from 'i18next';
|
||||
import { connect } from 'react-redux';
|
||||
import { createStructuredSelector } from 'reselect';
|
||||
import { selectJobReadOnly } from '../../redux/application/application.selectors';
|
||||
import { selectBodyshop } from '../../redux/user/user.selectors';
|
||||
import CurrencyFormatter from '../../utils/CurrencyFormatter';
|
||||
import { alphaSort } from '../../utils/sorters';
|
||||
import { Alert, Card, Table } from "antd";
|
||||
import { t } from "i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectJobReadOnly } from "../../redux/application/application.selectors";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||
import { alphaSort } from "../../utils/sorters";
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
//currentUser: selectCurrentUser
|
||||
bodyshop: selectBodyshop,
|
||||
jobRO: selectJobReadOnly,
|
||||
jobRO: selectJobReadOnly
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
@@ -20,69 +20,56 @@ export default connect(mapStateToProps, mapDispatchToProps)(JobCloseRGuardSublet
|
||||
|
||||
export function JobCloseRGuardSublet({ job, jobRO, bodyshop, form, warningCallback }) {
|
||||
const subletsNotDone = job?.joblines.filter(
|
||||
(j) =>
|
||||
(j.part_type === 'PAS' || j.part_type === 'PASL') &&
|
||||
(!j.sublet_completed || !j.sublet_ignored)
|
||||
(j) => (j.part_type === "PAS" || j.part_type === "PASL") && (!j.sublet_completed || !j.sublet_ignored)
|
||||
);
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: t('joblines.fields.line_desc'),
|
||||
dataIndex: 'line_desc',
|
||||
fixed: 'left',
|
||||
key: 'line_desc',
|
||||
title: t("joblines.fields.line_desc"),
|
||||
dataIndex: "line_desc",
|
||||
fixed: "left",
|
||||
key: "line_desc",
|
||||
sorter: (a, b) => alphaSort(a.line_desc, b.line_desc),
|
||||
onCell: (record) => ({
|
||||
className: record.manual_line && 'job-line-manual',
|
||||
className: record.manual_line && "job-line-manual",
|
||||
style: {
|
||||
...(record.critical ? { boxShadow: ' -.5em 0 0 #FFC107' } : {}),
|
||||
},
|
||||
...(record.critical ? { boxShadow: " -.5em 0 0 #FFC107" } : {})
|
||||
}
|
||||
}),
|
||||
ellipsis: true,
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
title: t('joblines.fields.act_price'),
|
||||
dataIndex: 'act_price',
|
||||
key: 'act_price',
|
||||
title: t("joblines.fields.act_price"),
|
||||
dataIndex: "act_price",
|
||||
key: "act_price",
|
||||
sorter: (a, b) => a.act_price - b.act_price,
|
||||
|
||||
ellipsis: true,
|
||||
render: (text, record) => <CurrencyFormatter>{record.act_price}</CurrencyFormatter>,
|
||||
render: (text, record) => <CurrencyFormatter>{record.act_price}</CurrencyFormatter>
|
||||
},
|
||||
{
|
||||
title: t('joblines.fields.part_qty'),
|
||||
dataIndex: 'part_qty',
|
||||
key: 'part_qty',
|
||||
title: t("joblines.fields.part_qty"),
|
||||
dataIndex: "part_qty",
|
||||
key: "part_qty"
|
||||
},
|
||||
{
|
||||
title: t('joblines.fields.notes'),
|
||||
dataIndex: 'notes',
|
||||
key: 'notes',
|
||||
},
|
||||
title: t("joblines.fields.notes"),
|
||||
dataIndex: "notes",
|
||||
key: "notes"
|
||||
}
|
||||
];
|
||||
|
||||
useEffect(() => {
|
||||
if (subletsNotDone.length > 0) {
|
||||
warningCallback({ key: 'sublet', warning: t('jobs.labels.outstanding_sublets') });
|
||||
warningCallback({ key: "sublet", warning: t("jobs.labels.outstanding_sublets") });
|
||||
}
|
||||
}, [subletsNotDone.length, warningCallback]);
|
||||
|
||||
return (
|
||||
<Card title={t('jobs.labels.subletsnotcompleted')}>
|
||||
<Table
|
||||
dataSource={subletsNotDone}
|
||||
columns={columns}
|
||||
pagination={false}
|
||||
rowKey="id"
|
||||
bordered
|
||||
size="small"
|
||||
/>
|
||||
<Card title={t("jobs.labels.subletsnotcompleted")}>
|
||||
<Table dataSource={subletsNotDone} columns={columns} pagination={false} rowKey="id" bordered size="small" />
|
||||
{subletsNotDone.length > 0 && (
|
||||
<Alert
|
||||
style={{ margin: '8px 0px' }}
|
||||
type="warning"
|
||||
message={t('jobs.labels.outstanding_sublets')}
|
||||
/>
|
||||
<Alert style={{ margin: "8px 0px" }} type="warning" message={t("jobs.labels.outstanding_sublets")} />
|
||||
)}
|
||||
</Card>
|
||||
);
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import React from 'react';
|
||||
import React from "react";
|
||||
|
||||
import { connect } from 'react-redux';
|
||||
import { createStructuredSelector } from 'reselect';
|
||||
import { selectJobReadOnly } from '../../redux/application/application.selectors';
|
||||
import { selectBodyshop } from '../../redux/user/user.selectors';
|
||||
import JobLifecycleComponent from '../job-lifecycle/job-lifecycle.component';
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectJobReadOnly } from "../../redux/application/application.selectors";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import JobLifecycleComponent from "../job-lifecycle/job-lifecycle.component";
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
//currentUser: selectCurrentUser
|
||||
bodyshop: selectBodyshop,
|
||||
jobRO: selectJobReadOnly,
|
||||
jobRO: selectJobReadOnly
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
|
||||
@@ -1,14 +1,21 @@
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import CardTemplate from "./job-detail-cards.template.component";
|
||||
import Car from "../job-damage-visual/job-damage-visual.component";
|
||||
import CardTemplate from "./job-detail-cards.template.component";
|
||||
|
||||
export default function JobDetailCardsDamageComponent({ loading, data }) {
|
||||
const { t } = useTranslation();
|
||||
const { area_of_damage } = data;
|
||||
return (
|
||||
<CardTemplate loading={loading} title={t("jobs.labels.cards.damage")}>
|
||||
{area_of_damage ? <Car dmg1={area_of_damage.impact1} dmg2={area_of_damage.impact2} /> : t("jobs.errors.nodamage")}
|
||||
{area_of_damage ? (
|
||||
<Car
|
||||
dmg1={area_of_damage.impact1 && area_of_damage.impact1.padStart(2, "0")}
|
||||
dmg2={area_of_damage.impact2 && area_of_damage.impact2.padStart(2, "0")}
|
||||
/>
|
||||
) : (
|
||||
t("jobs.errors.nodamage")
|
||||
)}
|
||||
</CardTemplate>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -26,6 +26,16 @@ export function JobDetailCardsPartsComponent({ loading, data, jobRO }) {
|
||||
const { t } = useTranslation();
|
||||
const { joblines_status } = data;
|
||||
|
||||
const filteredJobLines = data.joblines.filter(
|
||||
(j) =>
|
||||
j.part_type !== null &&
|
||||
j.part_type !== "PAE" &&
|
||||
j.part_type !== "PAS" &&
|
||||
j.part_type !== "PASL" &&
|
||||
j.part_qty !== 0 &&
|
||||
j.act_price !== 0
|
||||
);
|
||||
//TODO: Correct jobline_statuses view by including the part_qty !== 0 and act_price !== 0
|
||||
const columns = [
|
||||
{
|
||||
title: t("joblines.fields.line_desc"),
|
||||
@@ -95,7 +105,7 @@ export function JobDetailCardsPartsComponent({ loading, data, jobRO }) {
|
||||
<div>
|
||||
<CardTemplate loading={loading} title={t("jobs.labels.cards.parts")}>
|
||||
<PartsStatusPie joblines_status={joblines_status} />
|
||||
<Table key="id" columns={columns} dataSource={data ? data.joblines : []} />
|
||||
<Table key="id" columns={columns} dataSource={filteredJobLines ? filteredJobLines : []} />
|
||||
</CardTemplate>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useQuery } from "@apollo/client";
|
||||
import { Col, Divider, Row, Skeleton, Space, Timeline, Typography } from "antd";
|
||||
import { Col, Row, Skeleton, Space, Timeline, Typography } from "antd";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Link } from "react-router-dom";
|
||||
@@ -7,17 +7,19 @@ import { GET_JOB_LINE_ORDERS } from "../../graphql/jobs.queries";
|
||||
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||
import { DateFormatter } from "../../utils/DateFormatter";
|
||||
import AlertComponent from "../alert/alert.component";
|
||||
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import { QUERY_JOBLINE_TASKS_PAGINATED } from "../../graphql/tasks.queries.js";
|
||||
import TaskListContainer from "../task-list/task-list.container.jsx";
|
||||
import FeatureWrapper from "../feature-wrapper/feature-wrapper.component.jsx";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({});
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(JobLinesExpander);
|
||||
|
||||
export function JobLinesExpander({ jobline, jobid, bodyshop }) {
|
||||
@@ -43,13 +45,25 @@ export function JobLinesExpander({ jobline, jobid, bodyshop }) {
|
||||
? data.parts_order_lines.map((line) => ({
|
||||
key: line.id,
|
||||
children: (
|
||||
<Space split={<Divider type="vertical" />} wrap>
|
||||
<Link to={`/manage/jobs/${jobid}?partsorderid=${line.parts_order.id}`}>
|
||||
{line.parts_order.order_number}
|
||||
</Link>
|
||||
<DateFormatter>{line.parts_order.order_date}</DateFormatter>
|
||||
{line.parts_order.vendor.name}
|
||||
</Space>
|
||||
<Row wrap>
|
||||
<Col span={4}>
|
||||
<Link to={`/manage/jobs/${jobid}?partsorderid=${line.parts_order.id}`}>
|
||||
{line.parts_order.order_number}
|
||||
</Link>
|
||||
</Col>
|
||||
<Col span={4}>
|
||||
<DateFormatter>{line.parts_order.order_date}</DateFormatter>
|
||||
</Col>
|
||||
<Col span={4}>{line.parts_order.vendor.name}</Col>
|
||||
{line.backordered_eta ? (
|
||||
<Col span={4}>
|
||||
<span>
|
||||
{`${t("parts_orders.fields.backordered_eta")}: `}
|
||||
<DateFormatter>{line.backordered_eta}</DateFormatter>
|
||||
</span>
|
||||
</Col>
|
||||
) : null}
|
||||
</Row>
|
||||
)
|
||||
}))
|
||||
: [
|
||||
@@ -61,48 +75,6 @@ export function JobLinesExpander({ jobline, jobid, bodyshop }) {
|
||||
}
|
||||
/>{" "}
|
||||
</Col>
|
||||
<Col md={24} lg={8}>
|
||||
<Typography.Title level={4}>{t("bills.labels.bills")}</Typography.Title>
|
||||
<Timeline
|
||||
items={
|
||||
data.billlines.length > 0
|
||||
? data.billlines.map((line) => ({
|
||||
key: line.id,
|
||||
children: (
|
||||
<Row wrap>
|
||||
<Col span={4}>
|
||||
<Link to={`/manage/jobs/${jobid}?tab=partssublet&billid=${line.bill.id}`}>
|
||||
{line.bill.invoice_number}
|
||||
</Link>
|
||||
</Col>
|
||||
<Col span={4}>
|
||||
<span>
|
||||
{`${t("billlines.fields.actual_price")}: `}
|
||||
<CurrencyFormatter>{line.actual_price}</CurrencyFormatter>
|
||||
</span>
|
||||
</Col>
|
||||
<Col span={4}>
|
||||
<span>
|
||||
{`${t("billlines.fields.actual_cost")}: `}
|
||||
<CurrencyFormatter>{line.actual_cost}</CurrencyFormatter>
|
||||
</span>
|
||||
</Col>
|
||||
<Col span={4}>
|
||||
<DateFormatter>{line.bill.date}</DateFormatter>
|
||||
</Col>
|
||||
<Col span={4}> {line.bill.vendor.name}</Col>
|
||||
</Row>
|
||||
)
|
||||
}))
|
||||
: [
|
||||
{
|
||||
key: "no-orders",
|
||||
children: t("bills.labels.nobilllines")
|
||||
}
|
||||
]
|
||||
}
|
||||
/>
|
||||
</Col>
|
||||
<Col md={24} lg={8}>
|
||||
<Typography.Title level={4}>{t("parts_dispatch.labels.parts_dispatch")}</Typography.Title>
|
||||
<Timeline
|
||||
@@ -111,23 +83,84 @@ export function JobLinesExpander({ jobline, jobid, bodyshop }) {
|
||||
? data.parts_dispatch_lines.map((line) => ({
|
||||
key: line.id,
|
||||
children: (
|
||||
<Space split={<Divider type="vertical" />} wrap>
|
||||
<Link to={`/manage/jobs/${jobid}?partsorderid=${line.id}`}>{line.parts_dispatch.number}</Link>
|
||||
{bodyshop.employees.find((e) => e.id === line.parts_dispatch.employeeid)?.first_name}
|
||||
<Space>
|
||||
{t("parts_dispatch_lines.fields.accepted_at")}
|
||||
<DateFormatter>{line.accepted_at}</DateFormatter>
|
||||
</Space>
|
||||
</Space>
|
||||
<Row>
|
||||
<Col span={8}>
|
||||
<Link to={`/manage/jobs/${jobid}?partsorderid=${line.id}`}>{line.parts_dispatch.number}</Link>
|
||||
</Col>
|
||||
<Col span={8}>
|
||||
{bodyshop.employees.find((e) => e.id === line.parts_dispatch.employeeid)?.first_name}
|
||||
</Col>
|
||||
<Col span={8}>
|
||||
<Space>
|
||||
{t("parts_dispatch_lines.fields.accepted_at")}
|
||||
<DateFormatter>{line.accepted_at}</DateFormatter>
|
||||
</Space>
|
||||
</Col>
|
||||
</Row>
|
||||
)
|
||||
}))
|
||||
: {
|
||||
key: "dispatch-lines",
|
||||
children: t("parts_orders.labels.notyetordered")
|
||||
}
|
||||
: [
|
||||
{
|
||||
key: "dispatch-lines",
|
||||
children: t("parts_dispatch.labels.notyetdispatched")
|
||||
}
|
||||
]
|
||||
}
|
||||
/>
|
||||
</Col>
|
||||
<FeatureWrapper featureName="bills" noauth={() => null}>
|
||||
<Col md={24} lg={8}>
|
||||
<Typography.Title level={4}>{t("bills.labels.bills")}</Typography.Title>
|
||||
<Timeline
|
||||
items={
|
||||
data.billlines.length > 0
|
||||
? data.billlines.map((line) => ({
|
||||
key: line.id,
|
||||
children: (
|
||||
<Row wrap>
|
||||
<Col span={4}>
|
||||
<Link to={`/manage/jobs/${jobid}?tab=partssublet&billid=${line.bill.id}`}>
|
||||
{line.bill.invoice_number}
|
||||
</Link>
|
||||
</Col>
|
||||
<Col span={4}>
|
||||
<span>
|
||||
{`${t("billlines.fields.actual_price")}: `}
|
||||
<CurrencyFormatter>{line.actual_price}</CurrencyFormatter>
|
||||
</span>
|
||||
</Col>
|
||||
<Col span={4}>
|
||||
<span>
|
||||
{`${t("billlines.fields.actual_cost")}: `}
|
||||
<CurrencyFormatter>{line.actual_cost}</CurrencyFormatter>
|
||||
</span>
|
||||
</Col>
|
||||
<Col span={4}>
|
||||
<DateFormatter>{line.bill.date}</DateFormatter>
|
||||
</Col>
|
||||
<Col span={4}> {line.bill.vendor.name}</Col>
|
||||
</Row>
|
||||
)
|
||||
}))
|
||||
: [
|
||||
{
|
||||
key: "no-orders",
|
||||
children: t("bills.labels.nobilllines")
|
||||
}
|
||||
]
|
||||
}
|
||||
/>
|
||||
</Col>
|
||||
</FeatureWrapper>
|
||||
<Col md={24} lg={24}>
|
||||
<TaskListContainer
|
||||
parentJobId={jobid}
|
||||
relationshipType={"joblineid"}
|
||||
relationshipId={jobline.id}
|
||||
query={{ QUERY_JOBLINE_TASKS_PAGINATED }}
|
||||
titleTranslation="tasks.titles.job_tasks"
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -42,6 +42,8 @@ import JobSendPartPriceChangeComponent from "../job-send-parts-price-change/job-
|
||||
import PartsOrderModalContainer from "../parts-order-modal/parts-order-modal.container";
|
||||
import JobLinesExpander from "./job-lines-expander.component";
|
||||
import JobLinesPartPriceChange from "./job-lines-part-price-change.component";
|
||||
import { FaTasks } from "react-icons/fa";
|
||||
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
@@ -52,7 +54,8 @@ const mapStateToProps = createStructuredSelector({
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
setJobLineEditContext: (context) => dispatch(setModalContext({ context: context, modal: "jobLineEdit" })),
|
||||
setPartsOrderContext: (context) => dispatch(setModalContext({ context: context, modal: "partsOrder" })),
|
||||
setBillEnterContext: (context) => dispatch(setModalContext({ context: context, modal: "billEnter" }))
|
||||
setBillEnterContext: (context) => dispatch(setModalContext({ context: context, modal: "billEnter" })),
|
||||
setTaskUpsertContext: (context) => dispatch(setModalContext({ context, modal: "taskUpsert" }))
|
||||
});
|
||||
|
||||
export function JobLinesComponent({
|
||||
@@ -67,7 +70,8 @@ export function JobLinesComponent({
|
||||
job,
|
||||
setJobLineEditContext,
|
||||
form,
|
||||
setBillEnterContext
|
||||
setBillEnterContext,
|
||||
setTaskUpsertContext
|
||||
}) {
|
||||
const [deleteJobLine] = useMutation(DELETE_JOB_LINE_BY_PK);
|
||||
const {
|
||||
@@ -331,6 +335,24 @@ export function JobLinesComponent({
|
||||
>
|
||||
<EditFilled />
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
|
||||
<Button
|
||||
title={t("tasks.buttons.create")}
|
||||
onClick={() => {
|
||||
setTaskUpsertContext({
|
||||
context: {
|
||||
jobid: job.id,
|
||||
joblineid: record.id
|
||||
}
|
||||
});
|
||||
}}
|
||||
>
|
||||
<FaTasks />
|
||||
</Button>
|
||||
{(record.manual_line || jobIsPrivate) && (
|
||||
<>
|
||||
<Button
|
||||
disabled={jobRO}
|
||||
onClick={async () => {
|
||||
@@ -452,7 +474,7 @@ export function JobLinesComponent({
|
||||
vendorid: bodyshop.inhousevendorid,
|
||||
invoice_number: "ih",
|
||||
isinhouse: true,
|
||||
date: new dayjs(),
|
||||
date: dayjs(),
|
||||
total: 0,
|
||||
billlines: selectedLines.map((p) => {
|
||||
return {
|
||||
@@ -531,7 +553,7 @@ export function JobLinesComponent({
|
||||
>
|
||||
{t("joblines.actions.new")}
|
||||
</Button>
|
||||
{bodyshop.region_config.toLowerCase().startsWith("us") && <JobSendPartPriceChangeComponent job={job} />}
|
||||
{InstanceRenderManager({ rome: <JobSendPartPriceChangeComponent job={job} /> })}
|
||||
<JobCreateIOU job={job} selectedJobLines={selectedLines} />
|
||||
<Input.Search
|
||||
placeholder={t("general.labels.search")}
|
||||
|
||||
@@ -154,7 +154,7 @@ export function JobLineConvertToLabor({ children, jobline, job, insertAuditTrail
|
||||
setLoading(true);
|
||||
|
||||
form.setFieldsValue({
|
||||
// date: new dayjs(),
|
||||
// date: dayjs(),
|
||||
// bodyhrs: Math.round(v.bodyhrs * 10) / 10,
|
||||
// painthrs: Math.round(v.painthrs * 10) / 10,
|
||||
});
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { LoadingOutlined } from "@ant-design/icons";
|
||||
import { useLazyQuery } from "@apollo/client";
|
||||
import { Select, Space, Spin, Tag } from "antd";
|
||||
import _ from "lodash";
|
||||
@@ -6,8 +7,6 @@ import { useTranslation } from "react-i18next";
|
||||
import { SEARCH_JOBS_BY_ID_FOR_AUTOCOMPLETE, SEARCH_JOBS_FOR_AUTOCOMPLETE } from "../../graphql/jobs.queries";
|
||||
import AlertComponent from "../alert/alert.component";
|
||||
import { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component";
|
||||
import { SearchOutlined } from "@ant-design/icons";
|
||||
import { LoadingOutlined } from "@ant-design/icons";
|
||||
|
||||
const { Option } = Select;
|
||||
|
||||
@@ -75,7 +74,7 @@ const JobSearchSelect = (
|
||||
filterOption={false}
|
||||
onSearch={handleSearch}
|
||||
//loading={loading || idLoading}
|
||||
suffixIcon={loading && <Spin />}
|
||||
suffixIcon={(loading || idLoading) && <Spin />}
|
||||
notFoundContent={loading ? <LoadingOutlined /> : null}
|
||||
{...restProps}
|
||||
>
|
||||
@@ -86,9 +85,9 @@ const JobSearchSelect = (
|
||||
<span>
|
||||
{`${clm_no && o.clm_no ? `${o.clm_no} | ` : ""}${
|
||||
o.ro_number || t("general.labels.na")
|
||||
} | ${OwnerNameDisplayFunction(o)} | ${
|
||||
o.v_model_yr || ""
|
||||
} ${o.v_make_desc || ""} ${o.v_model_desc || ""}`}
|
||||
} | ${OwnerNameDisplayFunction(o)} | ${o.v_model_yr || ""} ${o.v_make_desc || ""} ${
|
||||
o.v_model_desc || ""
|
||||
}`}
|
||||
</span>
|
||||
<Tag>
|
||||
<strong>{o.status}</strong>
|
||||
|
||||
@@ -165,6 +165,8 @@ export function JobTotalsTableTotals({ bodyshop, job }) {
|
||||
bold: true
|
||||
}
|
||||
];
|
||||
// TODO: was removed by Patrick during a CI bug fix.
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [job.job_totals, job.cieca_pft, t, bodyshop.md_responsibility_centers]);
|
||||
|
||||
const columns = [
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Button, Space, notification } from "antd";
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { DELETE_DELIVERY_CHECKLIST, DELETE_INTAKE_CHECKLIST } from "../../graphql/jobs.queries";
|
||||
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
||||
|
||||
export default function JobAdminDeleteIntake({ job }) {
|
||||
const { t } = useTranslation();
|
||||
@@ -47,16 +48,22 @@ export default function JobAdminDeleteIntake({ job }) {
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
return (
|
||||
const InstanceRender = InstanceRenderManager({
|
||||
imex: true,
|
||||
rome: "USE_IMEX",
|
||||
promanager: false
|
||||
});
|
||||
|
||||
return InstanceRender ? (
|
||||
<>
|
||||
<Space wrap>
|
||||
<Button loading={loading} onClick={handleDelete} disabled={!job.intakechecklist}>
|
||||
{t("jobs.labels.deleteintake")}
|
||||
</Button>
|
||||
<Button loading={loading} onClick={handleDeleteDelivery} disabled={!job.deliverychecklist}>
|
||||
<Button loading={loading} onClick={handleDeleteDelivery} disabled={!job.deliverchecklist}>
|
||||
{t("jobs.labels.deletedelivery")}
|
||||
</Button>
|
||||
</Space>
|
||||
</>
|
||||
);
|
||||
) : null;
|
||||
}
|
||||
|
||||
@@ -15,23 +15,43 @@ export const GetSupplementDelta = async (client, jobId, newLines) => {
|
||||
const linesToUpdate = [];
|
||||
|
||||
newLines.forEach((newLine) => {
|
||||
const matchingIndex = existingLines.findIndex((eL) => eL.unq_seq === newLine.unq_seq);
|
||||
const matchingIndexLines = [];
|
||||
existingLines.forEach((eL, index) => {
|
||||
if (eL.unq_seq === newLine.unq_seq) {
|
||||
matchingIndexLines.push({ index, record: eL });
|
||||
}
|
||||
});
|
||||
|
||||
//Should do a check to make sure there is only 1 matching unq sequence number.
|
||||
|
||||
if (matchingIndex >= 0) {
|
||||
//Found a relevant matching line. Add it to lines to update.
|
||||
linesToUpdate.push({
|
||||
id: existingLines[matchingIndex].id,
|
||||
newData: { ...newLine, removed: false, act_price_before_ppc: null }
|
||||
});
|
||||
|
||||
//Splice out item we found for performance.
|
||||
existingLines.splice(matchingIndex, 1);
|
||||
} else {
|
||||
if (matchingIndexLines.length === 0) {
|
||||
//Didn't find a match. Must be a new line.
|
||||
linesToInsert.push(newLine);
|
||||
}
|
||||
//If we find only 1, we can use the old logic.
|
||||
else if (matchingIndexLines.length === 1) {
|
||||
//Found a relevant matching line. Add it to lines to update.
|
||||
linesToUpdate.push({
|
||||
id: matchingIndexLines[0].record.id,
|
||||
newData: { ...newLine, removed: false, act_price_before_ppc: null }
|
||||
});
|
||||
//Splice out item we found for performance.
|
||||
existingLines.splice(matchingIndexLines[0].index, 1);
|
||||
} else if (matchingIndexLines.length === 2) {
|
||||
//if we find 2, we need to separate it out by the non-refinish lines and splice one out.
|
||||
//Find the mathcing one depending on whether this is the refiniish one or not.
|
||||
const matchingLine = matchingIndexLines.find((i) =>
|
||||
newLine.mod_lbr_ty === "LAR" ? i.record.mod_lbr_ty === "LAR" : i.record.mod_lbr_ty !== "LAR"
|
||||
);
|
||||
|
||||
linesToUpdate.push({
|
||||
id: matchingLine.record.id,
|
||||
newData: { ...newLine, removed: false, act_price_before_ppc: null }
|
||||
});
|
||||
//Splice out item we found for performance.
|
||||
existingLines.splice(matchingLine.index, 1);
|
||||
} else {
|
||||
//We found more than 2 matching lines. We should never get here. Throw a warning and back out!
|
||||
throw new Error("Too many matching lines found. Ensure EMS file is valid.");
|
||||
}
|
||||
});
|
||||
|
||||
//Wahtever is left in the existing lines, are lines that should be removed.
|
||||
|
||||
@@ -237,7 +237,7 @@ export function JobsAvailableContainer({ bodyshop, currentUser, insertAuditTrail
|
||||
executeFunction: true,
|
||||
rome: ResolveCCCLineIssues,
|
||||
promanager: ResolveCCCLineIssues,
|
||||
args: [(supp, bodyshop)]
|
||||
args: [supp, bodyshop]
|
||||
});
|
||||
|
||||
await InstanceRenderManager({
|
||||
@@ -608,13 +608,12 @@ function ResolveCCCLineIssues(estData, bodyshop) {
|
||||
//Web Est seems to have additional costs with UNQ_SEQ 0. Keep them all?
|
||||
if (line.unq_seq === 0) return;
|
||||
if (index0ActPrice !== line.act_price) {
|
||||
line.notes += ` | Price override.`;
|
||||
// line.notes += ` | Price override.`;
|
||||
return;
|
||||
}
|
||||
const indexInEstData = estData.joblines.data.findIndex((l) => l.unq_seq === line.unq_seq);
|
||||
estData.joblines.data[
|
||||
indexInEstData
|
||||
].notes += ` | Scrubbed due to the line_ref issue. (prev act price = ${estData.joblines.data[indexInEstData].act_price})`;
|
||||
//estData.joblines.data[indexInEstData].notes +=
|
||||
// ` | Act Price delete. (prev act price = ${estData.joblines.data[indexInEstData].act_price})`;
|
||||
estData.joblines.data[indexInEstData].act_price = 0;
|
||||
estData.joblines.data[indexInEstData].db_price = 0;
|
||||
});
|
||||
|
||||
@@ -189,7 +189,10 @@ export function JobsDetailGeneral({ bodyshop, jobRO, job, form }) {
|
||||
</Col>
|
||||
<Col {...lossColDamage}>
|
||||
{job.area_of_damage ? (
|
||||
<Car dmg1={job.area_of_damage.impact1} dmg2={job.area_of_damage.impact2} />
|
||||
<Car
|
||||
dmg1={job.area_of_damage.impact1 && job.area_of_damage.impact1.padStart(2, "0")}
|
||||
dmg2={job.area_of_damage.impact2 && job.area_of_damage.impact2.padStart(2, "0")}
|
||||
/>
|
||||
) : (
|
||||
t("jobs.errors.nodamage")
|
||||
)}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { DownCircleFilled } from "@ant-design/icons";
|
||||
import { useApolloClient, useMutation } from "@apollo/client";
|
||||
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
||||
import { Button, Card, Dropdown, Form, Input, Modal, notification, Popconfirm, Popover, Select, Space } from "antd";
|
||||
import axios from "axios";
|
||||
import parsePhoneNumber from "libphonenumber-js";
|
||||
import React, { useMemo, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
@@ -8,27 +11,24 @@ import { Link, useNavigate } from "react-router-dom";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { auth, logImEXEvent } from "../../firebase/firebase.utils";
|
||||
import { CANCEL_APPOINTMENTS_BY_JOB_ID, INSERT_MANUAL_APPT } from "../../graphql/appointments.queries";
|
||||
import { GET_CURRENT_QUESTIONSET_ID, INSERT_CSI } from "../../graphql/csi.queries";
|
||||
import { DELETE_JOB, UPDATE_JOB, VOID_JOB } from "../../graphql/jobs.queries";
|
||||
import { insertAuditTrail } from "../../redux/application/application.actions";
|
||||
import { selectJobReadOnly } from "../../redux/application/application.selectors";
|
||||
import { setEmailOptions } from "../../redux/email/email.actions";
|
||||
import { openChatByPhone, setMessage } from "../../redux/messaging/messaging.actions";
|
||||
import { setModalContext } from "../../redux/modals/modals.actions";
|
||||
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors";
|
||||
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
||||
import { DateTimeFormatter } from "../../utils/DateFormatter";
|
||||
import { TemplateList } from "../../utils/TemplateConstants";
|
||||
import dayjs from "../../utils/day";
|
||||
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
||||
import { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component";
|
||||
import FormDateTimePickerComponent from "../form-date-time-picker/form-date-time-picker.component";
|
||||
import RbacWrapper from "../rbac-wrapper/rbac-wrapper.component";
|
||||
import AddToProduction from "./jobs-detail-header-actions.addtoproduction.util";
|
||||
import DuplicateJob from "./jobs-detail-header-actions.duplicate.util";
|
||||
import axios from "axios";
|
||||
import { setEmailOptions } from "../../redux/email/email.actions";
|
||||
import { openChatByPhone, setMessage } from "../../redux/messaging/messaging.actions";
|
||||
import { GET_CURRENT_QUESTIONSET_ID, INSERT_CSI } from "../../graphql/csi.queries";
|
||||
import { TemplateList } from "../../utils/TemplateConstants";
|
||||
import parsePhoneNumber from "libphonenumber-js";
|
||||
import { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component";
|
||||
import { DateTimeFormatter } from "../../utils/DateFormatter";
|
||||
import FormDateTimePickerComponent from "../form-date-time-picker/form-date-time-picker.component";
|
||||
import dayjs from "../../utils/day";
|
||||
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
||||
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
||||
import JobsDetailHeaderActionsToggleProduction from "./jobs-detail-header-actions.toggle-production";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
@@ -39,13 +39,57 @@ const mapStateToProps = createStructuredSelector({
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
setScheduleContext: (context) => dispatch(setModalContext({ context: context, modal: "schedule" })),
|
||||
setBillEnterContext: (context) => dispatch(setModalContext({ context: context, modal: "billEnter" })),
|
||||
setBillEnterContext: (context) =>
|
||||
dispatch(
|
||||
setModalContext({
|
||||
context: context,
|
||||
modal: "billEnter"
|
||||
})
|
||||
),
|
||||
setPaymentContext: (context) => dispatch(setModalContext({ context: context, modal: "payment" })),
|
||||
setJobCostingContext: (context) => dispatch(setModalContext({ context: context, modal: "jobCosting" })),
|
||||
setTimeTicketContext: (context) => dispatch(setModalContext({ context: context, modal: "timeTicket" })),
|
||||
setCardPaymentContext: (context) => dispatch(setModalContext({ context: context, modal: "cardPayment" })),
|
||||
insertAuditTrail: ({ jobid, operation, type }) => dispatch(insertAuditTrail({ jobid, operation, type })),
|
||||
setTimeTicketTaskContext: (context) => dispatch(setModalContext({ context: context, modal: "timeTicketTask" })),
|
||||
setJobCostingContext: (context) =>
|
||||
dispatch(
|
||||
setModalContext({
|
||||
context: context,
|
||||
modal: "jobCosting"
|
||||
})
|
||||
),
|
||||
setTimeTicketContext: (context) =>
|
||||
dispatch(
|
||||
setModalContext({
|
||||
context: context,
|
||||
modal: "timeTicket"
|
||||
})
|
||||
),
|
||||
setCardPaymentContext: (context) =>
|
||||
dispatch(
|
||||
setModalContext({
|
||||
context: context,
|
||||
modal: "cardPayment"
|
||||
})
|
||||
),
|
||||
insertAuditTrail: ({ jobid, operation, type }) =>
|
||||
dispatch(
|
||||
insertAuditTrail({
|
||||
jobid,
|
||||
operation,
|
||||
type
|
||||
})
|
||||
),
|
||||
setTimeTicketTaskContext: (context) =>
|
||||
dispatch(
|
||||
setModalContext({
|
||||
context: context,
|
||||
modal: "timeTicketTask"
|
||||
})
|
||||
),
|
||||
setTaskUpsertContext: (context) =>
|
||||
dispatch(
|
||||
setModalContext({
|
||||
context: context,
|
||||
modal: "taskUpsert"
|
||||
})
|
||||
),
|
||||
setEmailOptions: (e) => dispatch(setEmailOptions(e)),
|
||||
openChatByPhone: (phone) => dispatch(openChatByPhone(phone)),
|
||||
setMessage: (text) => dispatch(setMessage(text))
|
||||
@@ -67,7 +111,8 @@ export function JobsDetailHeaderActions({
|
||||
setEmailOptions,
|
||||
openChatByPhone,
|
||||
setMessage,
|
||||
setTimeTicketTaskContext
|
||||
setTimeTicketTaskContext,
|
||||
setTaskUpsertContext
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const client = useApolloClient();
|
||||
@@ -596,6 +641,7 @@ export function JobsDetailHeaderActions({
|
||||
const menuItems = [
|
||||
{
|
||||
key: "schedule",
|
||||
id: "job-actions-schedule",
|
||||
disabled: !jobInPreProduction || !job.converted || jobRO,
|
||||
label: t("jobs.actions.schedule"),
|
||||
onClick: () => {
|
||||
@@ -612,6 +658,7 @@ export function JobsDetailHeaderActions({
|
||||
},
|
||||
{
|
||||
key: "cancelallappointments",
|
||||
id: "job-actions-cancelallappointments",
|
||||
onClick: () => {
|
||||
if (job.status !== bodyshop.md_ro_statuses.default_scheduled) {
|
||||
return;
|
||||
@@ -625,6 +672,7 @@ export function JobsDetailHeaderActions({
|
||||
imex: [
|
||||
{
|
||||
key: "intake",
|
||||
id: "job-actions-intake",
|
||||
disabled: !!job.intakechecklist || !jobInPreProduction || !job.converted || jobRO,
|
||||
label:
|
||||
!!job.intakechecklist || !jobInPreProduction || !job.converted || jobRO ? (
|
||||
@@ -635,6 +683,7 @@ export function JobsDetailHeaderActions({
|
||||
},
|
||||
{
|
||||
key: "deliver",
|
||||
id: "job-actions-deliver",
|
||||
disabled: !jobInProduction || jobRO,
|
||||
label: !jobInProduction ? (
|
||||
t("jobs.actions.deliver")
|
||||
@@ -644,6 +693,7 @@ export function JobsDetailHeaderActions({
|
||||
},
|
||||
{
|
||||
key: "checklist",
|
||||
id: "job-actions-checklist",
|
||||
disabled: !job.converted,
|
||||
label: <Link to={`/manage/jobs/${job.id}/checklist`}>{t("jobs.actions.viewchecklist")}</Link>
|
||||
}
|
||||
@@ -652,6 +702,7 @@ export function JobsDetailHeaderActions({
|
||||
promanager: [
|
||||
{
|
||||
key: "toggleproduction",
|
||||
id: "job-actions-toggleproduction",
|
||||
disabled: !job.converted || jobRO,
|
||||
label: <JobsDetailHeaderActionsToggleProduction job={job} refetch={refetch} />
|
||||
}
|
||||
@@ -665,6 +716,7 @@ export function JobsDetailHeaderActions({
|
||||
? [
|
||||
{
|
||||
key: "entertimetickets",
|
||||
id: "job-actions-entertimetickets",
|
||||
disabled: !job.converted || (!bodyshop.tt_allow_post_to_invoiced && job.date_invoiced),
|
||||
label: t("timetickets.actions.enter"),
|
||||
onClick: () => {
|
||||
@@ -688,6 +740,7 @@ export function JobsDetailHeaderActions({
|
||||
if (bodyshop.md_tasks_presets.enable_tasks) {
|
||||
menuItems.push({
|
||||
key: "claimtimetickettasks",
|
||||
id: "job-actions-claimtimetickettasks",
|
||||
disabled: !job.converted || (!bodyshop.tt_allow_post_to_invoiced && job.date_invoiced),
|
||||
onClick: () => {
|
||||
setTimeTicketTaskContext({
|
||||
@@ -701,6 +754,7 @@ export function JobsDetailHeaderActions({
|
||||
|
||||
menuItems.push({
|
||||
key: "enterpayments",
|
||||
id: "job-actions-enterpayments",
|
||||
disabled: !job.converted,
|
||||
label: t("menus.header.enterpayment"),
|
||||
onClick: () => {
|
||||
@@ -716,22 +770,24 @@ export function JobsDetailHeaderActions({
|
||||
if (ImEXPay.treatment === "on") {
|
||||
menuItems.push({
|
||||
key: "entercardpayments",
|
||||
id: "job-actions-entercardpayments",
|
||||
disabled: !job.converted,
|
||||
label: t("menus.header.entercardpayment"),
|
||||
onClick: () => {
|
||||
logImEXEvent("job_header_enter_card_payment");
|
||||
|
||||
setCardPaymentContext({
|
||||
actions: {},
|
||||
actions: { refetch },
|
||||
context: { jobid: job.id }
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (HasFeatureAccess({ featureName: "courtesycars" })) {
|
||||
if (HasFeatureAccess({ featureName: "courtesycars", bodyshop })) {
|
||||
menuItems.push({
|
||||
key: "cccontract",
|
||||
id: "job-actions-cccontract",
|
||||
disabled: jobRO || !job.converted,
|
||||
label: (
|
||||
<Link state={{ jobId: job.id }} to="/manage/courtesycars/contracts/new">
|
||||
@@ -741,16 +797,29 @@ export function JobsDetailHeaderActions({
|
||||
});
|
||||
}
|
||||
|
||||
menuItems.push({
|
||||
key: "createtask",
|
||||
id: "job-actions-createtask",
|
||||
label: t("menus.header.create_task"),
|
||||
onClick: () =>
|
||||
setTaskUpsertContext({
|
||||
actions: {},
|
||||
context: { jobid: job.id }
|
||||
})
|
||||
});
|
||||
|
||||
menuItems.push(
|
||||
job.inproduction
|
||||
? {
|
||||
key: "addtoproduction",
|
||||
key: "removefromproduction",
|
||||
id: "job-actions-removefromproduction",
|
||||
disabled: !job.converted,
|
||||
label: t("jobs.actions.removefromproduction"),
|
||||
onClick: () => AddToProduction(client, job.id, refetch, true)
|
||||
}
|
||||
: {
|
||||
key: "addtoproduction",
|
||||
id: "job-actions-addtoproduction",
|
||||
disabled: !job.converted,
|
||||
label: t("jobs.actions.addtoproduction"),
|
||||
onClick: () => AddToProduction(client, job.id, refetch)
|
||||
@@ -760,12 +829,14 @@ export function JobsDetailHeaderActions({
|
||||
menuItems.push(
|
||||
{
|
||||
key: "togglesuspend",
|
||||
id: "job-actions-togglesuspend",
|
||||
onClick: handleSuspend,
|
||||
label: job.suspended ? t("production.actions.unsuspend") : t("production.actions.suspend")
|
||||
},
|
||||
{
|
||||
key: "toggleAlert",
|
||||
onClick: handleAlertToggle,
|
||||
id: "job-actions-togglealert",
|
||||
label:
|
||||
job.production_vars && job.production_vars.alert
|
||||
? t("production.labels.alertoff")
|
||||
@@ -777,6 +848,7 @@ export function JobsDetailHeaderActions({
|
||||
children: [
|
||||
{
|
||||
key: "duplicate",
|
||||
id: "job-actions-duplicate",
|
||||
label: (
|
||||
<Popconfirm
|
||||
title={t("jobs.labels.duplicateconfirm")}
|
||||
@@ -792,6 +864,7 @@ export function JobsDetailHeaderActions({
|
||||
},
|
||||
{
|
||||
key: "duplicatenolines",
|
||||
id: "job-actions-duplicatenolines",
|
||||
label: (
|
||||
<Popconfirm
|
||||
title={t("jobs.labels.duplicateconfirm")}
|
||||
@@ -815,6 +888,7 @@ export function JobsDetailHeaderActions({
|
||||
? [
|
||||
{
|
||||
key: "postbills",
|
||||
id: "job-actions-postbills",
|
||||
disabled: !job.converted,
|
||||
label: t("jobs.actions.postbills"),
|
||||
onClick: () => {
|
||||
@@ -833,6 +907,7 @@ export function JobsDetailHeaderActions({
|
||||
|
||||
{
|
||||
key: "addtopartsqueue",
|
||||
id: "job-actions-addtopartsqueue",
|
||||
disabled: !job.converted || !jobInProduction || jobRO,
|
||||
label: t("jobs.actions.addtopartsqueue"),
|
||||
onClick: async () => {
|
||||
@@ -858,6 +933,7 @@ export function JobsDetailHeaderActions({
|
||||
},
|
||||
{
|
||||
key: "closejob",
|
||||
id: "job-actions-closejob",
|
||||
disabled: !jobInPostProduction,
|
||||
label: !jobInPostProduction ? (
|
||||
t("menus.jobsactions.closejob")
|
||||
@@ -873,6 +949,7 @@ export function JobsDetailHeaderActions({
|
||||
},
|
||||
{
|
||||
key: "admin",
|
||||
id: "job-actions-admin",
|
||||
label: (
|
||||
<Link
|
||||
to={{
|
||||
@@ -894,6 +971,7 @@ export function JobsDetailHeaderActions({
|
||||
) {
|
||||
menuItems.push({
|
||||
key: "exportcustdata",
|
||||
id: "job-actions-exportcustdata",
|
||||
disabled: !job.converted,
|
||||
label: t("jobs.actions.exportcustdata"),
|
||||
onClick: handleExportCustData
|
||||
@@ -904,18 +982,21 @@ export function JobsDetailHeaderActions({
|
||||
const children = [
|
||||
{
|
||||
key: "email",
|
||||
id: "job-actions-email",
|
||||
disabled: !!!job.ownr_ea,
|
||||
label: t("general.labels.email"),
|
||||
onClick: handleCreateCsi
|
||||
},
|
||||
{
|
||||
key: "text",
|
||||
id: "job-actions-text",
|
||||
disabled: !!!job.ownr_ph1,
|
||||
label: t("general.labels.text"),
|
||||
onClick: handleCreateCsi
|
||||
},
|
||||
{
|
||||
key: "generate",
|
||||
id: "job-actions-generate",
|
||||
disabled: job.csiinvites && job.csiinvites.length > 0,
|
||||
label: t("jobs.actions.generatecsi"),
|
||||
onClick: handleCreateCsi
|
||||
@@ -949,6 +1030,7 @@ export function JobsDetailHeaderActions({
|
||||
}
|
||||
menuItems.push({
|
||||
key: "sendcsi",
|
||||
id: "job-actions-sendcsi",
|
||||
label: t("jobs.actions.sendcsi"),
|
||||
disabled: !job.converted,
|
||||
children
|
||||
@@ -957,6 +1039,7 @@ export function JobsDetailHeaderActions({
|
||||
|
||||
menuItems.push({
|
||||
key: "jobcosting",
|
||||
id: "job-actions-jobcosting",
|
||||
disabled: !job.converted,
|
||||
label: t("jobs.labels.jobcosting"),
|
||||
onClick: () => {
|
||||
@@ -974,6 +1057,7 @@ export function JobsDetailHeaderActions({
|
||||
if (job && !job.converted) {
|
||||
menuItems.push({
|
||||
key: "deletejob",
|
||||
id: "job-actions-deletejob",
|
||||
label: (
|
||||
<Popconfirm
|
||||
title={t("jobs.labels.deleteconfirm")}
|
||||
@@ -990,6 +1074,7 @@ export function JobsDetailHeaderActions({
|
||||
|
||||
menuItems.push({
|
||||
key: "manualevent",
|
||||
id: "job-actions-manualevent",
|
||||
onClick: (e) => {
|
||||
setVisibility(true);
|
||||
},
|
||||
@@ -999,6 +1084,7 @@ export function JobsDetailHeaderActions({
|
||||
if (!jobRO && job.converted) {
|
||||
menuItems.push({
|
||||
key: "voidjob",
|
||||
id: "job-actions-voidjob",
|
||||
label: (
|
||||
<RbacWrapper action="jobs:void" noauth>
|
||||
<Popconfirm
|
||||
|
||||
@@ -96,7 +96,6 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
|
||||
}),
|
||||
onFilter: (value, record) => value.includes(record.status)
|
||||
},
|
||||
|
||||
{
|
||||
title: t("jobs.fields.vehicle"),
|
||||
dataIndex: "vehicle",
|
||||
|
||||
@@ -18,8 +18,6 @@ import ChatOpenButton from "../chat-open-button/chat-open-button.component";
|
||||
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
|
||||
import { setJoyRideSteps } from "../../redux/application/application.actions";
|
||||
import { OwnerNameDisplayFunction } from "./../owner-name-display/owner-name-display.component";
|
||||
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop
|
||||
});
|
||||
@@ -176,7 +174,6 @@ export function JobsList({ bodyshop, setJoyRideSteps }) {
|
||||
[],
|
||||
onFilter: (value, record) => value.includes(record.status)
|
||||
},
|
||||
|
||||
{
|
||||
title: t("jobs.fields.vehicle"),
|
||||
dataIndex: "vehicle",
|
||||
@@ -316,34 +313,6 @@ export function JobsList({ bodyshop, setJoyRideSteps }) {
|
||||
title={t("titles.bc.jobs-active")}
|
||||
extra={
|
||||
<Space wrap>
|
||||
{InstanceRenderManager({
|
||||
promanager: (
|
||||
<Button
|
||||
onClick={() =>
|
||||
setJoyRideSteps([
|
||||
{
|
||||
target: "#active-jobs-list",
|
||||
content: "This is where you will see all work coming in and currently here."
|
||||
},
|
||||
{
|
||||
target: "#header-jobs",
|
||||
spotlightClicks: true,
|
||||
disableOverlayClose: true,
|
||||
content:
|
||||
"The jobs menu lets you access additional pages to see more information. You can import new jobs, search all jobs, or manage your current production."
|
||||
},
|
||||
{
|
||||
target: "#header-jobs-available",
|
||||
content: "You can find jobs to import here."
|
||||
}
|
||||
])
|
||||
}
|
||||
>
|
||||
Start Walk Through
|
||||
</Button>
|
||||
)
|
||||
})}
|
||||
|
||||
<Button onClick={() => refetch()}>
|
||||
<SyncOutlined />
|
||||
</Button>
|
||||
|
||||
@@ -6,7 +6,8 @@ import { Link } from "react-router-dom";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||
import { alphaSort, statusSort } from "../../utils/sorters";
|
||||
import { DateTimeFormatter } from "../../utils/DateFormatter";
|
||||
import { alphaSort, dateSort, statusSort } from "../../utils/sorters";
|
||||
import OwnerDetailUpdateJobsComponent from "../owner-detail-update-jobs/owner-detail-update-jobs.component";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
@@ -75,7 +76,18 @@ function OwnerDetailJobsComponent({ bodyshop, owner }) {
|
||||
})),
|
||||
onFilter: (value, record) => value.includes(record.status)
|
||||
},
|
||||
|
||||
{
|
||||
title: t("jobs.fields.actual_completion"),
|
||||
dataIndex: "actual_completion",
|
||||
key: "actual_completion",
|
||||
render: (text, record) => (
|
||||
<DateTimeFormatter>{record.actual_completion}</DateTimeFormatter>
|
||||
),
|
||||
sorter: (a, b) => dateSort(a.actual_completion, b.actual_completion),
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "actual_completion" &&
|
||||
state.sortedInfo.order,
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.clm_total"),
|
||||
dataIndex: "clm_total",
|
||||
|
||||
@@ -1,23 +1,24 @@
|
||||
import { DeleteFilled, EyeFilled, SyncOutlined } from "@ant-design/icons";
|
||||
import { useMutation } from "@apollo/client";
|
||||
import { useLazyQuery, useMutation } from "@apollo/client";
|
||||
import { Button, Card, Checkbox, Drawer, Grid, Input, Popconfirm, Space, Table } from "antd";
|
||||
import { PageHeader } from "@ant-design/pro-layout";
|
||||
|
||||
import queryString from "query-string";
|
||||
import React, { useState } from "react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { useLocation } from "react-router-dom";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { logImEXEvent } from "../../firebase/firebase.utils";
|
||||
import { QUERY_BILL_BY_PK } from "../../graphql/bills.queries";
|
||||
import { DELETE_PARTS_ORDER } from "../../graphql/parts-orders.queries";
|
||||
import { selectJobReadOnly } from "../../redux/application/application.selectors";
|
||||
import { setModalContext } from "../../redux/modals/modals.actions";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||
import { DateFormatter } from "../../utils/DateFormatter";
|
||||
import { alphaSort } from "../../utils/sorters";
|
||||
import { TemplateList } from "../../utils/TemplateConstants";
|
||||
import { alphaSort } from "../../utils/sorters";
|
||||
import DataLabel from "../data-label/data-label.component";
|
||||
import PartsOrderBackorderEta from "../parts-order-backorder-eta/parts-order-backorder-eta.component";
|
||||
import PartsOrderCmReceived from "../parts-order-cm-received/parts-order-cm-received.component";
|
||||
@@ -26,6 +27,7 @@ import PartsOrderLineBackorderButton from "../parts-order-line-backorder-button/
|
||||
import PartsReceiveModalContainer from "../parts-receive-modal/parts-receive-modal.container";
|
||||
import PrintWrapper from "../print-wrapper/print-wrapper.component";
|
||||
import FeatureWrapperComponent from "../feature-wrapper/feature-wrapper.component";
|
||||
import { FaTasks } from "react-icons/fa";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
jobRO: selectJobReadOnly,
|
||||
@@ -33,8 +35,21 @@ const mapStateToProps = createStructuredSelector({
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
setBillEnterContext: (context) => dispatch(setModalContext({ context: context, modal: "billEnter" })),
|
||||
setPartsReceiveContext: (context) => dispatch(setModalContext({ context: context, modal: "partsReceive" }))
|
||||
setBillEnterContext: (context) =>
|
||||
dispatch(
|
||||
setModalContext({
|
||||
context: context,
|
||||
modal: "billEnter"
|
||||
})
|
||||
),
|
||||
setPartsReceiveContext: (context) =>
|
||||
dispatch(
|
||||
setModalContext({
|
||||
context: context,
|
||||
modal: "partsReceive"
|
||||
})
|
||||
),
|
||||
setTaskUpsertContext: (context) => dispatch(setModalContext({ context, modal: "taskUpsert" }))
|
||||
});
|
||||
|
||||
export function PartsOrderListTableComponent({
|
||||
@@ -44,7 +59,8 @@ export function PartsOrderListTableComponent({
|
||||
job,
|
||||
billsQuery,
|
||||
handleOnRowClick,
|
||||
setPartsReceiveContext
|
||||
setPartsReceiveContext,
|
||||
setTaskUpsertContext
|
||||
}) {
|
||||
const selectedBreakpoint = Object.entries(Grid.useBreakpoint())
|
||||
.filter((screen) => !!screen[1])
|
||||
@@ -66,19 +82,46 @@ export function PartsOrderListTableComponent({
|
||||
const [state, setState] = useState({
|
||||
sortedInfo: {}
|
||||
});
|
||||
|
||||
const [returnfrombill, setReturnFromBill] = useState();
|
||||
const [billData, setBillData] = useState();
|
||||
const search = queryString.parse(useLocation().search);
|
||||
const selectedpartsorder = search.partsorderid;
|
||||
const [searchText, setSearchText] = useState("");
|
||||
|
||||
const [billQuery] = useLazyQuery(QUERY_BILL_BY_PK);
|
||||
const [deletePartsOrder] = useMutation(DELETE_PARTS_ORDER);
|
||||
|
||||
const parts_orders = billsQuery.data ? billsQuery.data.parts_orders : [];
|
||||
const { refetch } = billsQuery;
|
||||
|
||||
useEffect(() => {
|
||||
if (returnfrombill === null) {
|
||||
setBillData(null);
|
||||
} else {
|
||||
const fetchData = async () => {
|
||||
const result = await billQuery({
|
||||
variables: { billid: returnfrombill }
|
||||
});
|
||||
setBillData(result.data);
|
||||
};
|
||||
fetchData();
|
||||
}
|
||||
}, [returnfrombill, billQuery]);
|
||||
|
||||
const recordActions = (record, showView = false) => (
|
||||
<Space wrap>
|
||||
<Space direction="horizontal" wrap>
|
||||
{showView && (
|
||||
<Button onClick={() => handleOnRowClick(record)}>
|
||||
<Button
|
||||
onClick={() => {
|
||||
if (record.returnfrombill) {
|
||||
setReturnFromBill(record.returnfrombill);
|
||||
} else {
|
||||
setReturnFromBill(null);
|
||||
}
|
||||
handleOnRowClick(record);
|
||||
}}
|
||||
>
|
||||
<EyeFilled />
|
||||
</Button>
|
||||
)}
|
||||
@@ -108,7 +151,19 @@ export function PartsOrderListTableComponent({
|
||||
>
|
||||
{t("parts_orders.actions.receive")}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
title={t("tasks.buttons.create")}
|
||||
onClick={() => {
|
||||
setTaskUpsertContext({
|
||||
context: {
|
||||
jobid: job.id,
|
||||
partsorderid: record.id
|
||||
}
|
||||
});
|
||||
}}
|
||||
>
|
||||
<FaTasks />
|
||||
</Button>
|
||||
<Popconfirm
|
||||
title={t("parts_orders.labels.confirmdelete")}
|
||||
disabled={jobRO}
|
||||
@@ -148,7 +203,7 @@ export function PartsOrderListTableComponent({
|
||||
is_credit_memo: record.return,
|
||||
billlines: record.parts_order_lines.map((pol) => {
|
||||
return {
|
||||
joblineid: pol.job_line_id,
|
||||
joblineid: pol.job_line_id || "noline",
|
||||
line_desc: pol.line_desc,
|
||||
quantity: pol.quantity,
|
||||
|
||||
@@ -368,7 +423,14 @@ export function PartsOrderListTableComponent({
|
||||
|
||||
return (
|
||||
<div>
|
||||
<PageHeader title={record && `${record.vendor.name} - ${record.order_number}`} extra={recordActions(record)} />
|
||||
<PageHeader
|
||||
title={
|
||||
billData
|
||||
? `${record.vendor.name} - ${record.order_number} - ${t("bills.labels.returnfrombill")}: ${billData.bills_by_pk.invoice_number}`
|
||||
: `${record.vendor.name} - ${record.order_number}`
|
||||
}
|
||||
extra={recordActions(record)}
|
||||
/>
|
||||
<Table
|
||||
scroll={{
|
||||
x: true //y: "50rem"
|
||||
|
||||
@@ -182,7 +182,7 @@ export function PartsOrderModalComponent({ bodyshop, vendorList, sendTypeState,
|
||||
}
|
||||
]}
|
||||
>
|
||||
<InputNumber />
|
||||
<InputNumber min={1} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("parts_orders.fields.act_price")}
|
||||
|
||||
@@ -158,7 +158,7 @@ export function PartsOrderModalContainer({
|
||||
vendorid: bodyshop.inhousevendorid,
|
||||
invoice_number: "ih",
|
||||
isinhouse: true,
|
||||
date: new dayjs(),
|
||||
date: dayjs(),
|
||||
total: 0,
|
||||
billlines: values.parts_order_lines.data.map((p) => {
|
||||
return {
|
||||
|
||||
@@ -119,8 +119,14 @@ const PaymentExpandedRowComponent = ({ record, bodyshop }) => {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Descriptions title={t("job_payments.titles.descriptions")} contentStyle={{ fontWeight: "600" }} column={4}>
|
||||
<Descriptions.Item label={t("job_payments.titles.payer")}>{record.payer}</Descriptions.Item>
|
||||
<Descriptions
|
||||
title={t("job_payments.titles.descriptions")}
|
||||
contentStyle={{ fontWeight: "600" }}
|
||||
column={4}
|
||||
>
|
||||
<Descriptions.Item label={t("job_payments.titles.hint")}>
|
||||
{payment_response?.response?.methodhint}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label={t("job_payments.titles.payername")}>
|
||||
{payment_response?.response?.nameOnCard ?? ""}
|
||||
</Descriptions.Item>
|
||||
@@ -132,7 +138,7 @@ const PaymentExpandedRowComponent = ({ record, bodyshop }) => {
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label={t("job_payments.titles.transactionid")}>{record.transactionid}</Descriptions.Item>
|
||||
<Descriptions.Item label={t("job_payments.titles.paymentid")}>
|
||||
{payment_response?.response?.paymentreferenceid ?? ""}
|
||||
{payment_response?.ext_paymentid ?? ""}
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label={t("job_payments.titles.paymenttype")}>{record.type}</Descriptions.Item>
|
||||
<Descriptions.Item label={t("job_payments.titles.paymentnum")}>{record.paymentnum}</Descriptions.Item>
|
||||
|
||||
@@ -7,8 +7,8 @@ import { createStructuredSelector } from "reselect";
|
||||
import { INSERT_EXPORT_LOG } from "../../graphql/accounting.queries";
|
||||
import { UPDATE_PAYMENT } from "../../graphql/payments.queries";
|
||||
import { setModalContext } from "../../redux/modals/modals.actions";
|
||||
import { selectCurrentUser } from "../../redux/user/user.selectors";
|
||||
import { selectPayment } from "../../redux/modals/modals.selectors";
|
||||
import { selectCurrentUser } from "../../redux/user/user.selectors";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
currentUser: selectCurrentUser,
|
||||
@@ -58,8 +58,10 @@ const PaymentMarkForExportButton = ({ bodyshop, payment, refetch, setPaymentCont
|
||||
refetch
|
||||
},
|
||||
context: {
|
||||
...paymentModal.context,
|
||||
...paymentModal.context,
|
||||
...payment,
|
||||
smartRefetch: true,
|
||||
exportedat: today
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,32 +1,30 @@
|
||||
import { useMutation } from "@apollo/client";
|
||||
import { Button, Form, Modal, notification, Space } from "antd";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { INSERT_NEW_PAYMENT, UPDATE_PAYMENT } from "../../graphql/payments.queries";
|
||||
import { setEmailOptions } from "../../redux/email/email.actions";
|
||||
import { toggleModalVisible } from "../../redux/modals/modals.actions";
|
||||
import { selectPayment } from "../../redux/modals/modals.selectors";
|
||||
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors";
|
||||
import { GenerateDocument } from "../../utils/RenderTemplate";
|
||||
import { TemplateList } from "../../utils/TemplateConstants";
|
||||
import {useMutation} from "@apollo/client";
|
||||
import {Button, Form, Modal, notification, Space} from "antd";
|
||||
import React, {useEffect, useState} from "react";
|
||||
import {useTranslation} from "react-i18next";
|
||||
import {connect} from "react-redux";
|
||||
import {createStructuredSelector} from "reselect";
|
||||
import {INSERT_NEW_PAYMENT, UPDATE_PAYMENT} from "../../graphql/payments.queries";
|
||||
import {toggleModalVisible} from "../../redux/modals/modals.actions";
|
||||
import {selectPayment} from "../../redux/modals/modals.selectors";
|
||||
import {selectBodyshop} from "../../redux/user/user.selectors";
|
||||
import {GenerateDocument} from "../../utils/RenderTemplate";
|
||||
import {TemplateList} from "../../utils/TemplateConstants";
|
||||
import PaymentForm from "../payment-form/payment-form.component";
|
||||
import PaymentMarkForExportButton from "../payment-mark-export-button/payment-mark-export-button-component";
|
||||
import PaymentMarkForExportButton
|
||||
from "../payment-mark-export-button/payment-mark-export-button-component";
|
||||
import PaymentReexportButton from "../payment-reexport-button/payment-reexport-button.component";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
paymentModal: selectPayment,
|
||||
bodyshop: selectBodyshop,
|
||||
currentUser: selectCurrentUser
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
setEmailOptions: (e) => dispatch(setEmailOptions(e)),
|
||||
toggleModalVisible: () => dispatch(toggleModalVisible("payment"))
|
||||
});
|
||||
|
||||
function PaymentModalContainer({ paymentModal, toggleModalVisible, bodyshop, currentUser, setEmailOptions }) {
|
||||
function PaymentModalContainer({paymentModal, toggleModalVisible, bodyshop }) {
|
||||
const [form] = Form.useForm();
|
||||
const [enterAgain, setEnterAgain] = useState(false);
|
||||
const [insertPayment] = useMutation(INSERT_NEW_PAYMENT);
|
||||
@@ -34,7 +32,6 @@ function PaymentModalContainer({ paymentModal, toggleModalVisible, bodyshop, cur
|
||||
|
||||
const { t } = useTranslation();
|
||||
const { context, actions, open } = paymentModal;
|
||||
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const handleFinish = async (values) => {
|
||||
@@ -84,9 +81,9 @@ function PaymentModalContainer({ paymentModal, toggleModalVisible, bodyshop, cur
|
||||
});
|
||||
|
||||
if (!!!updatedPayment.errors) {
|
||||
notification["success"]({ message: t("payments.successes.payment") });
|
||||
notification["success"]({ message: t("payments.successes.paymentupdate") });
|
||||
} else {
|
||||
notification["error"]({ message: t("payments.errors.payment") });
|
||||
notification["error"]({ message: t("payments.errors.paymentupdate") });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,10 +3,10 @@ import { Button, notification } from "antd";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { UPDATE_PAYMENT } from "../../graphql/payments.queries";
|
||||
import { setModalContext } from "../../redux/modals/modals.actions";
|
||||
import { selectPayment } from "../../redux/modals/modals.selectors";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
paymentModal: selectPayment
|
||||
@@ -40,6 +40,7 @@ const PaymentReexportButton = ({ paymentModal, payment, refetch, setPaymentConte
|
||||
refetch
|
||||
},
|
||||
context: {
|
||||
...paymentModal.context,
|
||||
...paymentModal.context,
|
||||
...payment,
|
||||
exportedat: null
|
||||
|
||||
@@ -14,11 +14,11 @@ import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||
import { DateFormatter, DateTimeFormatter } from "../../utils/DateFormatter";
|
||||
import { TemplateList } from "../../utils/TemplateConstants";
|
||||
import { pageLimit } from "../../utils/config";
|
||||
import { alphaSort } from "../../utils/sorters";
|
||||
import CaBcEtfTableModalContainer from "../ca-bc-etf-table-modal/ca-bc-etf-table-modal.container";
|
||||
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
|
||||
import PrintWrapperComponent from "../print-wrapper/print-wrapper.component";
|
||||
import { pageLimit } from "../../utils/config";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
//currentUser: selectCurrentUser
|
||||
|
||||
@@ -1,4 +1,13 @@
|
||||
import { Button, Card, Form, InputNumber, notification, Popover, Radio } from "antd";
|
||||
import {
|
||||
Button,
|
||||
Card,
|
||||
Form,
|
||||
InputNumber,
|
||||
notification,
|
||||
Popover,
|
||||
Radio,
|
||||
Space,
|
||||
} from "antd";
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
@@ -95,10 +104,16 @@ export function PrintCenterJobsLabels({ bodyshop, jobId }) {
|
||||
>
|
||||
<InputNumber min={1} precision={0} max={99} />
|
||||
</Form.Item>
|
||||
<Button type="primary" loading={loading} onClick={handleOk}>
|
||||
{t("general.actions.print")}
|
||||
</Button>
|
||||
<Button onClick={handleCancel}>{t("general.actions.cancel")}</Button>
|
||||
<div style={{ display: "flex", justifyContent: "flex-end" }}>
|
||||
<Space>
|
||||
<Button type="primary" loading={loading} onClick={handleOk}>
|
||||
{t("general.actions.print")}
|
||||
</Button>
|
||||
<Button onClick={handleCancel}>
|
||||
{t("general.actions.cancel")}
|
||||
</Button>
|
||||
</Space>
|
||||
</div>
|
||||
</Form>
|
||||
</Card>
|
||||
);
|
||||
|
||||
@@ -2,23 +2,24 @@ import { useLazyQuery } from "@apollo/client";
|
||||
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
||||
import { Button, Card, Col, DatePicker, Form, Input, Radio, Row, Typography } from "antd";
|
||||
import _ from "lodash";
|
||||
import dayjs from "../../utils/day";
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { QUERY_ACTIVE_EMPLOYEES } from "../../graphql/employees.queries";
|
||||
import { QUERY_ACTIVE_EMPLOYEES, QUERY_ACTIVE_EMPLOYEES_WITH_EMAIL } from "../../graphql/employees.queries";
|
||||
import { QUERY_ALL_VENDORS } from "../../graphql/vendors.queries";
|
||||
import { selectReportCenter } from "../../redux/modals/modals.selectors";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import DatePickerRanges from "../../utils/DatePickerRanges";
|
||||
import { GenerateDocument } from "../../utils/RenderTemplate";
|
||||
import { TemplateList } from "../../utils/TemplateConstants";
|
||||
import dayjs from "../../utils/day";
|
||||
import EmployeeSearchSelectEmail from "../employee-search-select/employee-search-select-email.component";
|
||||
import EmployeeSearchSelect from "../employee-search-select/employee-search-select.component";
|
||||
import VendorSearchSelect from "../vendor-search-select/vendor-search-select.component";
|
||||
import "./report-center-modal.styles.scss";
|
||||
import ReportCenterModalFiltersSortersComponent from "./report-center-modal-filters-sorters-component";
|
||||
import { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component";
|
||||
import VendorSearchSelect from "../vendor-search-select/vendor-search-select.component";
|
||||
import ReportCenterModalFiltersSortersComponent from "./report-center-modal-filters-sorters-component";
|
||||
import "./report-center-modal.styles.scss";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
reportCenterModal: selectReportCenter,
|
||||
@@ -66,6 +67,13 @@ export function ReportCenterModalComponent({ reportCenterModal, bodyshop }) {
|
||||
skip: !(open && Templates[form.getFieldValue("key")] && Templates[form.getFieldValue("key")].idtype)
|
||||
});
|
||||
|
||||
const [callEmployeeWithEmailQuery, { data: employeeWithEmailData, called: employeeWithEmailCalled }] = useLazyQuery(
|
||||
QUERY_ACTIVE_EMPLOYEES_WITH_EMAIL,
|
||||
{
|
||||
skip: !(open && Templates[form.getFieldValue("key")] && Templates[form.getFieldValue("key")].idtype)
|
||||
}
|
||||
);
|
||||
|
||||
const handleFinish = async (values) => {
|
||||
setLoading(true);
|
||||
const start = values.dates ? values.dates[0] : null;
|
||||
@@ -197,6 +205,7 @@ export function ReportCenterModalComponent({ reportCenterModal, bodyshop }) {
|
||||
}
|
||||
if (!vendorCalled && idtype === "vendor") callVendorQuery();
|
||||
if (!employeeCalled && idtype === "employee") callEmployeeQuery();
|
||||
if (!employeeWithEmailCalled && idtype === "employeeWithEmail") callEmployeeWithEmailQuery();
|
||||
if (idtype === "vendor")
|
||||
return (
|
||||
<Form.Item
|
||||
@@ -227,6 +236,22 @@ export function ReportCenterModalComponent({ reportCenterModal, bodyshop }) {
|
||||
<EmployeeSearchSelect options={employeeData ? employeeData.employees : []} />
|
||||
</Form.Item>
|
||||
);
|
||||
//This was introduced with tasks before assigned_to was shifted to UUID. Keeping in place for reference in the future if needed.
|
||||
if (idtype === "employeeWithEmail")
|
||||
return (
|
||||
<Form.Item
|
||||
name="id"
|
||||
label={t("reportcenter.labels.employee")}
|
||||
rules={[
|
||||
{
|
||||
required: true
|
||||
//message: t("general.validation.required"),
|
||||
}
|
||||
]}
|
||||
>
|
||||
<EmployeeSearchSelectEmail options={employeeWithEmailData ? employeeWithEmailData.employees : []} />
|
||||
</Form.Item>
|
||||
);
|
||||
else return null;
|
||||
}}
|
||||
</Form.Item>
|
||||
|
||||
@@ -59,25 +59,33 @@ export function ScheduleCalendarHeaderComponent({
|
||||
{loadData && loadData.allJobsOut ? (
|
||||
loadData.allJobsOut.map((j) => (
|
||||
<tr key={j.id}>
|
||||
<td>
|
||||
<Link to={`/manage/jobs/${j.id}`}>{j.ro_number}</Link>
|
||||
<td style={{ padding: "2.5px" }}>
|
||||
<Link to={`/manage/jobs/${j.id}`}>{j.ro_number}</Link> (
|
||||
{j.status})
|
||||
</td>
|
||||
<td>
|
||||
<td style={{ padding: "2.5px" }}>
|
||||
<OwnerNameDisplay ownerObject={j} />
|
||||
</td>
|
||||
<td>
|
||||
{`(${(j.labhrs.aggregate.sum.mod_lb_hrs + j.larhrs.aggregate.sum.mod_lb_hrs).toFixed(
|
||||
1
|
||||
)} ${t("general.labels.hours")})`}
|
||||
<td style={{ padding: "2.5px" }}>
|
||||
{`(${j.labhrs?.aggregate?.sum?.mod_lb_hrs?.toFixed(1) || 0}/${
|
||||
j.larhrs?.aggregate?.sum?.mod_lb_hrs?.toFixed(1) || 0
|
||||
}/${(
|
||||
j.labhrs.aggregate?.sum?.mod_lb_hrs +
|
||||
j.larhrs.aggregate?.sum?.mod_lb_hrs
|
||||
).toFixed(1)} ${t("general.labels.hours")})`}
|
||||
</td>
|
||||
<td>
|
||||
<DateTimeFormatter>{j.scheduled_completion}</DateTimeFormatter>
|
||||
<td style={{ padding: "2.5px" }}>
|
||||
<DateTimeFormatter>
|
||||
{j.scheduled_completion}
|
||||
</DateTimeFormatter>
|
||||
</td>
|
||||
</tr>
|
||||
))
|
||||
) : (
|
||||
<tr>
|
||||
<td>{t("appointments.labels.nocompletingjobs")}</td>
|
||||
<td style={{ padding: "2.5px" }}>
|
||||
{t("appointments.labels.nocompletingjobs")}
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
@@ -92,26 +100,30 @@ export function ScheduleCalendarHeaderComponent({
|
||||
{loadData && loadData.allJobsIn ? (
|
||||
loadData.allJobsIn.map((j) => (
|
||||
<tr key={j.id}>
|
||||
<td>
|
||||
<td style={{ padding: "2.5px" }}>
|
||||
<Link to={`/manage/jobs/${j.id}`}>{j.ro_number}</Link>
|
||||
{j.status}
|
||||
</td>
|
||||
<td>
|
||||
<td style={{ padding: "2.5px" }}>
|
||||
<OwnerNameDisplay ownerObject={j} />
|
||||
</td>
|
||||
<td>
|
||||
{`(${(j.labhrs.aggregate.sum.mod_lb_hrs + j.larhrs.aggregate.sum.mod_lb_hrs).toFixed(
|
||||
1
|
||||
)} ${t("general.labels.hours")})`}
|
||||
<td style={{ padding: "2.5px" }}>
|
||||
{`(${j.labhrs?.aggregate?.sum.mod_lb_hrs?.toFixed(1) || 0}/${
|
||||
j.larhrs?.aggregate?.sum?.mod_lb_hrs?.toFixed(1) || 0
|
||||
}/${(
|
||||
j.labhrs?.aggregate?.sum?.mod_lb_hrs +
|
||||
j.larhrs?.aggregate?.sum?.mod_lb_hrs
|
||||
).toFixed(1)} ${t("general.labels.hours")})`}
|
||||
</td>
|
||||
<td>
|
||||
<td style={{ padding: "2.5px" }}>
|
||||
<DateTimeFormatter>{j.scheduled_in}</DateTimeFormatter>
|
||||
</td>
|
||||
</tr>
|
||||
))
|
||||
) : (
|
||||
<tr>
|
||||
<td>{t("appointments.labels.noarrivingjobs")}</td>
|
||||
<td style={{ padding: "2.5px" }}>
|
||||
{t("appointments.labels.noarrivingjobs")}
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
@@ -121,27 +133,33 @@ export function ScheduleCalendarHeaderComponent({
|
||||
|
||||
const LoadComponent = loadData ? (
|
||||
<div>
|
||||
<Space align="center">
|
||||
<Popover
|
||||
placement={"bottom"}
|
||||
content={jobsInPopup}
|
||||
trigger="hover"
|
||||
title={t("appointments.labels.arrivingjobs")}
|
||||
>
|
||||
<Icon component={MdFileDownload} style={{ color: "green" }} />
|
||||
{(loadData.allHoursIn || 0) && loadData.allHoursIn.toFixed(2)}
|
||||
</Popover>
|
||||
<Popover
|
||||
placement={"bottom"}
|
||||
content={jobsOutPopup}
|
||||
trigger="hover"
|
||||
title={t("appointments.labels.completingjobs")}
|
||||
>
|
||||
<Icon component={MdFileUpload} style={{ color: "red" }} />
|
||||
{(loadData.allHoursOut || 0) && loadData.allHoursOut.toFixed(2)}
|
||||
</Popover>
|
||||
<ScheduleCalendarHeaderGraph loadData={loadData} />
|
||||
</Space>
|
||||
<Space align="center">
|
||||
<Popover
|
||||
placement={"bottom"}
|
||||
content={jobsInPopup}
|
||||
trigger="hover"
|
||||
title={t("appointments.labels.arrivingjobs")}
|
||||
>
|
||||
<Icon component={MdFileDownload} style={{ color: "green" }} />
|
||||
{(loadData.allHoursInBody || 0) &&
|
||||
loadData.allHoursInBody.toFixed(1)}
|
||||
/
|
||||
{(loadData.allHoursInRefinish || 0) &&
|
||||
loadData.allHoursInRefinish.toFixed(1)}
|
||||
/{(loadData.allHoursIn || 0) && loadData.allHoursIn.toFixed(1)}
|
||||
</Popover>
|
||||
<Popover
|
||||
placement={"bottom"}
|
||||
content={jobsOutPopup}
|
||||
trigger="hover"
|
||||
title={t("appointments.labels.completingjobs")}
|
||||
>
|
||||
<Icon component={MdFileUpload} style={{ color: "red" }} />
|
||||
{(loadData.allHoursOut || 0) && loadData.allHoursOut.toFixed(1)}
|
||||
</Popover>
|
||||
<ScheduleCalendarHeaderGraph loadData={loadData} />
|
||||
</Space>
|
||||
|
||||
<div>
|
||||
<ul style={{ listStyleType: "none", columns: "2 auto", padding: 0 }}>
|
||||
{Object.keys(ATSToday).map((key, idx) => (
|
||||
|
||||
@@ -63,7 +63,7 @@ export default function ShopEmployeesListComponent({ loading, employees }) {
|
||||
value: false
|
||||
}
|
||||
],
|
||||
onFilter: (value, record) => value === record.flate_rate,
|
||||
onFilter: (value, record) => value === record.flat_rate,
|
||||
render: (text, record) =>
|
||||
record.flat_rate ? t("employees.labels.flat_rate") : t("employees.labels.straight_time")
|
||||
},
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
import { useSplitTreatments } from '@splitsoftware/splitio-react';
|
||||
import { Button, Card, Tabs } from 'antd';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { connect } from 'react-redux';
|
||||
import { createStructuredSelector } from 'reselect';
|
||||
import { selectBodyshop } from '../../redux/user/user.selectors';
|
||||
import ShopInfoGeneral from './shop-info.general.component';
|
||||
import ShopInfoIntakeChecklistComponent from './shop-info.intake.component';
|
||||
import ShopInfoLaborRates from './shop-info.laborrates.component';
|
||||
import ShopInfoOrderStatusComponent from './shop-info.orderstatus.component';
|
||||
import ShopInfoPartsScan from './shop-info.parts-scan';
|
||||
import ShopInfoRbacComponent from './shop-info.rbac.component';
|
||||
import ShopInfoResponsibilityCenterComponent from './shop-info.responsibilitycenters.component';
|
||||
import ShopInfoROStatusComponent from './shop-info.rostatus.component';
|
||||
import ShopInfoSchedulingComponent from './shop-info.scheduling.component';
|
||||
import ShopInfoSpeedPrint from './shop-info.speedprint.component';
|
||||
import { useLocation, useNavigate } from 'react-router-dom';
|
||||
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 { useSplitTreatments } from "@splitsoftware/splitio-react";
|
||||
import { Button, Card, Tabs } from "antd";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import ShopInfoGeneral from "./shop-info.general.component";
|
||||
import ShopInfoIntakeChecklistComponent from "./shop-info.intake.component";
|
||||
import ShopInfoLaborRates from "./shop-info.laborrates.component";
|
||||
import ShopInfoOrderStatusComponent from "./shop-info.orderstatus.component";
|
||||
import ShopInfoPartsScan from "./shop-info.parts-scan";
|
||||
import ShopInfoRbacComponent from "./shop-info.rbac.component";
|
||||
import ShopInfoResponsibilityCenterComponent from "./shop-info.responsibilitycenters.component";
|
||||
import ShopInfoROStatusComponent from "./shop-info.rostatus.component";
|
||||
import ShopInfoSchedulingComponent from "./shop-info.scheduling.component";
|
||||
import ShopInfoSpeedPrint from "./shop-info.speedprint.component";
|
||||
import { useLocation, useNavigate } from "react-router-dom";
|
||||
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";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop
|
||||
@@ -47,44 +47,52 @@ export function ShopInfoComponent({ bodyshop, form, saveLoading }) {
|
||||
{
|
||||
key: "general",
|
||||
label: t("bodyshop.labels.shopinfo"),
|
||||
children: <ShopInfoGeneral form={form} />
|
||||
children: <ShopInfoGeneral form={form} />,
|
||||
id: "tab-shop-general"
|
||||
},
|
||||
{
|
||||
key: "speedprint",
|
||||
label: t("bodyshop.labels.speedprint"),
|
||||
children: <ShopInfoSpeedPrint form={form} />
|
||||
children: <ShopInfoSpeedPrint form={form} />,
|
||||
id: "tab-shop-speedprint"
|
||||
},
|
||||
{
|
||||
key: "rbac",
|
||||
label: t("bodyshop.labels.rbac"),
|
||||
children: <ShopInfoRbacComponent form={form} />
|
||||
children: <ShopInfoRbacComponent form={form} />,
|
||||
id: "tab-shop-rbac"
|
||||
},
|
||||
{
|
||||
key: "roStatus",
|
||||
label: t("bodyshop.labels.jobstatuses"),
|
||||
children: <ShopInfoROStatusComponent form={form} />
|
||||
children: <ShopInfoROStatusComponent form={form} />,
|
||||
id: "tab-shop-rostatus"
|
||||
},
|
||||
{
|
||||
key: "scheduling",
|
||||
label: t("bodyshop.labels.scheduling"),
|
||||
children: <ShopInfoSchedulingComponent form={form} />
|
||||
children: <ShopInfoSchedulingComponent form={form} />,
|
||||
id: "tab-shop-scheduling"
|
||||
},
|
||||
{
|
||||
key: "orderStatus",
|
||||
label: t("bodyshop.labels.orderstatuses"),
|
||||
children: <ShopInfoOrderStatusComponent form={form} />
|
||||
children: <ShopInfoOrderStatusComponent form={form} />,
|
||||
id: "tab-shop-orderstatus"
|
||||
},
|
||||
{
|
||||
key: "responsibilityCenters",
|
||||
label: t("bodyshop.labels.responsibilitycenters.title"),
|
||||
children: <ShopInfoResponsibilityCenterComponent form={form} />
|
||||
children: <ShopInfoResponsibilityCenterComponent form={form} />,
|
||||
id: "tab-shop-responsibilitycenters"
|
||||
},
|
||||
...InstanceRenderManager({
|
||||
imex: [
|
||||
{
|
||||
key: "checklists",
|
||||
label: t("bodyshop.labels.checklists"),
|
||||
children: <ShopInfoIntakeChecklistComponent form={form} />
|
||||
children: <ShopInfoIntakeChecklistComponent form={form} />,
|
||||
id: "tab-shop-checklists"
|
||||
}
|
||||
],
|
||||
rome: "USE_IMEX",
|
||||
@@ -93,14 +101,16 @@ export function ShopInfoComponent({ bodyshop, form, saveLoading }) {
|
||||
{
|
||||
key: "laborrates",
|
||||
label: t("bodyshop.labels.laborrates"),
|
||||
children: <ShopInfoLaborRates form={form} />
|
||||
children: <ShopInfoLaborRates form={form} />,
|
||||
id: "tab-shop-laborrates"
|
||||
},
|
||||
...(CriticalPartsScanning.treatment === "on"
|
||||
? [
|
||||
{
|
||||
key: "partsscan",
|
||||
label: t("bodyshop.labels.partsscan"),
|
||||
children: <ShopInfoPartsScan form={form} />
|
||||
children: <ShopInfoPartsScan form={form} />,
|
||||
id: "tab-shop-partsscan"
|
||||
}
|
||||
]
|
||||
: []),
|
||||
@@ -109,21 +119,23 @@ export function ShopInfoComponent({ bodyshop, form, saveLoading }) {
|
||||
{
|
||||
key: "task-presets",
|
||||
label: t("bodyshop.labels.task-presets"),
|
||||
children: <ShopInfoTaskPresets form={form} />
|
||||
children: <ShopInfoTaskPresets form={form} />,
|
||||
id: "tab-shop-task-presets"
|
||||
}
|
||||
]
|
||||
: []),
|
||||
...InstanceRenderManager({
|
||||
imex: [
|
||||
{
|
||||
key: 'roguard',
|
||||
label: t('bodyshop.labels.roguard.title'),
|
||||
children: <ShopInfoRoGuard form={form} />,
|
||||
},
|
||||
],
|
||||
rome: 'USE_IMEX',
|
||||
promanager: [],
|
||||
}),
|
||||
...InstanceRenderManager({
|
||||
imex: [
|
||||
{
|
||||
key: "roguard",
|
||||
label: t("bodyshop.labels.roguard.title"),
|
||||
children: <ShopInfoRoGuard form={form} />,
|
||||
id: "tab-shop-roguard"
|
||||
}
|
||||
],
|
||||
rome: "USE_IMEX",
|
||||
promanager: []
|
||||
})
|
||||
];
|
||||
return (
|
||||
<Card
|
||||
|
||||
@@ -13,6 +13,7 @@ import PhoneFormItem, { PhoneItemFormatterValidation } from "../form-items-forma
|
||||
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({
|
||||
@@ -144,285 +145,289 @@ export function ShopInfoGeneral({ form, bodyshop }) {
|
||||
<InputNumber min={0} />
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
<LayoutFormRow header={t("bodyshop.labels.accountingsetup")} id="accountingsetup">
|
||||
<Form.Item label={t("bodyshop.labels.qbo")} valuePropName="checked" name={["accountingconfig", "qbo"]}>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
{InstanceRenderManager({
|
||||
imex: (
|
||||
<Form.Item shouldUpdate noStyle>
|
||||
{() => (
|
||||
<FeatureWrapper featureName="export" noauth={() => null}>
|
||||
<LayoutFormRow header={t("bodyshop.labels.accountingsetup")} id="accountingsetup">
|
||||
<Form.Item label={t("bodyshop.labels.qbo")} valuePropName="checked" name={["accountingconfig", "qbo"]}>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
{InstanceRenderManager({
|
||||
imex: (
|
||||
<Form.Item shouldUpdate noStyle>
|
||||
{() => (
|
||||
<Form.Item
|
||||
label={t("bodyshop.labels.qbo_usa")}
|
||||
shouldUpdate
|
||||
valuePropName="checked"
|
||||
name={["accountingconfig", "qbo_usa"]}
|
||||
>
|
||||
<Switch disabled={!form.getFieldValue(["accountingconfig", "qbo"])} />
|
||||
</Form.Item>
|
||||
)}
|
||||
</Form.Item>
|
||||
)
|
||||
})}
|
||||
<Form.Item label={t("bodyshop.labels.qbo_departmentid")} name={["accountingconfig", "qbo_departmentid"]}>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bodyshop.labels.accountingtiers")}
|
||||
rules={[
|
||||
{
|
||||
required: true
|
||||
//message: t("general.validation.required"),
|
||||
}
|
||||
]}
|
||||
name={["accountingconfig", "tiers"]}
|
||||
>
|
||||
<Radio.Group>
|
||||
<Radio value={2}>2</Radio>
|
||||
<Radio value={3}>3</Radio>
|
||||
</Radio.Group>
|
||||
</Form.Item>
|
||||
<Form.Item shouldUpdate>
|
||||
{() => {
|
||||
return (
|
||||
<Form.Item
|
||||
label={t("bodyshop.labels.qbo_usa")}
|
||||
label={t("bodyshop.labels.2tiersetup")}
|
||||
shouldUpdate
|
||||
valuePropName="checked"
|
||||
name={["accountingconfig", "qbo_usa"]}
|
||||
rules={[
|
||||
{
|
||||
required: form.getFieldValue(["accountingconfig", "tiers"]) === 2
|
||||
//message: t("general.validation.required"),
|
||||
}
|
||||
]}
|
||||
name={["accountingconfig", "twotierpref"]}
|
||||
>
|
||||
<Switch disabled={!form.getFieldValue(["accountingconfig", "qbo"])} />
|
||||
<Radio.Group disabled={form.getFieldValue(["accountingconfig", "tiers"]) === 3}>
|
||||
<Radio value="name">{t("bodyshop.labels.2tiername")}</Radio>
|
||||
<Radio value="source">{t("bodyshop.labels.2tiersource")}</Radio>
|
||||
</Radio.Group>
|
||||
</Form.Item>
|
||||
)}
|
||||
</Form.Item>
|
||||
)
|
||||
})}
|
||||
<Form.Item label={t("bodyshop.labels.qbo_departmentid")} name={["accountingconfig", "qbo_departmentid"]}>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bodyshop.labels.accountingtiers")}
|
||||
rules={[
|
||||
{
|
||||
required: true
|
||||
//message: t("general.validation.required"),
|
||||
}
|
||||
]}
|
||||
name={["accountingconfig", "tiers"]}
|
||||
>
|
||||
<Radio.Group>
|
||||
<Radio value={2}>2</Radio>
|
||||
<Radio value={3}>3</Radio>
|
||||
</Radio.Group>
|
||||
</Form.Item>
|
||||
<Form.Item shouldUpdate>
|
||||
{() => {
|
||||
return (
|
||||
);
|
||||
}}
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bodyshop.labels.printlater")}
|
||||
valuePropName="checked"
|
||||
name={["accountingconfig", "printlater"]}
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bodyshop.labels.emaillater")}
|
||||
valuePropName="checked"
|
||||
name={["accountingconfig", "emaillater"]}
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.inhousevendorid")}
|
||||
name={"inhousevendorid"}
|
||||
rules={[
|
||||
{
|
||||
required: true
|
||||
//message: t("general.validation.required"),
|
||||
}
|
||||
]}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.default_adjustment_rate")}
|
||||
name={"default_adjustment_rate"}
|
||||
rules={[
|
||||
{
|
||||
required: true
|
||||
//message: t("general.validation.required"),
|
||||
}
|
||||
]}
|
||||
>
|
||||
<InputNumber min={0} precision={2} />
|
||||
</Form.Item>
|
||||
{InstanceRenderManager({
|
||||
imex: (
|
||||
<Form.Item label={t("bodyshop.fields.federal_tax_id")} name="federal_tax_id">
|
||||
<Input />
|
||||
</Form.Item>
|
||||
)
|
||||
})}
|
||||
<Form.Item label={t("bodyshop.fields.state_tax_id")} name="state_tax_id">
|
||||
<Input />
|
||||
</Form.Item>
|
||||
{InstanceRenderManager({
|
||||
imex: (
|
||||
<Form.Item
|
||||
label={t("bodyshop.labels.2tiersetup")}
|
||||
shouldUpdate
|
||||
label={t("bodyshop.fields.invoice_federal_tax_rate")}
|
||||
name={["bill_tax_rates", "federal_tax_rate"]}
|
||||
rules={[
|
||||
{
|
||||
required: form.getFieldValue(["accountingconfig", "tiers"]) === 2
|
||||
required: true
|
||||
//message: t("general.validation.required"),
|
||||
}
|
||||
]}
|
||||
name={["accountingconfig", "twotierpref"]}
|
||||
>
|
||||
<Radio.Group disabled={form.getFieldValue(["accountingconfig", "tiers"]) === 3}>
|
||||
<Radio value="name">{t("bodyshop.labels.2tiername")}</Radio>
|
||||
<Radio value="source">{t("bodyshop.labels.2tiersource")}</Radio>
|
||||
</Radio.Group>
|
||||
<InputNumber />
|
||||
</Form.Item>
|
||||
);
|
||||
}}
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bodyshop.labels.printlater")}
|
||||
valuePropName="checked"
|
||||
name={["accountingconfig", "printlater"]}
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bodyshop.labels.emaillater")}
|
||||
valuePropName="checked"
|
||||
name={["accountingconfig", "emaillater"]}
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.inhousevendorid")}
|
||||
name={"inhousevendorid"}
|
||||
rules={[
|
||||
{
|
||||
required: true
|
||||
//message: t("general.validation.required"),
|
||||
}
|
||||
]}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.default_adjustment_rate")}
|
||||
name={"default_adjustment_rate"}
|
||||
rules={[
|
||||
{
|
||||
required: true
|
||||
//message: t("general.validation.required"),
|
||||
}
|
||||
]}
|
||||
>
|
||||
<InputNumber min={0} precision={2} />
|
||||
</Form.Item>
|
||||
{InstanceRenderManager({
|
||||
imex: (
|
||||
<Form.Item label={t("bodyshop.fields.federal_tax_id")} name="federal_tax_id">
|
||||
<Input />
|
||||
</Form.Item>
|
||||
)
|
||||
})}
|
||||
<Form.Item label={t("bodyshop.fields.state_tax_id")} name="state_tax_id">
|
||||
<Input />
|
||||
</Form.Item>
|
||||
{InstanceRenderManager({
|
||||
imex: (
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.invoice_federal_tax_rate")}
|
||||
name={["bill_tax_rates", "federal_tax_rate"]}
|
||||
rules={[
|
||||
{
|
||||
required: true
|
||||
//message: t("general.validation.required"),
|
||||
}
|
||||
]}
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>
|
||||
)
|
||||
})}
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.invoice_state_tax_rate")}
|
||||
name={["bill_tax_rates", "state_tax_rate"]}
|
||||
rules={[
|
||||
{
|
||||
required: true
|
||||
//message: t("general.validation.required"),
|
||||
}
|
||||
]}
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.invoice_local_tax_rate")}
|
||||
name={["bill_tax_rates", "local_tax_rate"]}
|
||||
rules={[
|
||||
{
|
||||
required: true
|
||||
//message: t("general.validation.required"),
|
||||
}
|
||||
]}
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name={["md_payment_types"]}
|
||||
label={t("bodyshop.fields.md_payment_types")}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
type: "array"
|
||||
}
|
||||
]}
|
||||
>
|
||||
<Select mode="tags" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name={["md_categories"]}
|
||||
label={t("bodyshop.fields.md_categories")}
|
||||
rules={[
|
||||
{
|
||||
//message: t("general.validation.required"),
|
||||
type: "array"
|
||||
}
|
||||
]}
|
||||
>
|
||||
<Select mode="tags" />
|
||||
</Form.Item>
|
||||
<Form.Item name={["enforce_class"]} label={t("bodyshop.fields.enforce_class")} valuePropName="checked">
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name={["accountingconfig", "ReceivableCustomField1"]}
|
||||
label={t("bodyshop.fields.ReceivableCustomField", { number: 1 })}
|
||||
>
|
||||
{ReceivableCustomFieldSelect}
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name={["accountingconfig", "ReceivableCustomField2"]}
|
||||
label={t("bodyshop.fields.ReceivableCustomField", { number: 2 })}
|
||||
>
|
||||
{ReceivableCustomFieldSelect}
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name={["accountingconfig", "ReceivableCustomField3"]}
|
||||
label={t("bodyshop.fields.ReceivableCustomField", { number: 3 })}
|
||||
>
|
||||
{ReceivableCustomFieldSelect}
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name={["md_classes"]}
|
||||
label={t("bodyshop.fields.md_classes")}
|
||||
rules={[
|
||||
({ getFieldValue }) => {
|
||||
return {
|
||||
required: getFieldValue("enforce_class"),
|
||||
)
|
||||
})}
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.invoice_state_tax_rate")}
|
||||
name={["bill_tax_rates", "state_tax_rate"]}
|
||||
rules={[
|
||||
{
|
||||
required: true
|
||||
//message: t("general.validation.required"),
|
||||
}
|
||||
]}
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.invoice_local_tax_rate")}
|
||||
name={["bill_tax_rates", "local_tax_rate"]}
|
||||
rules={[
|
||||
{
|
||||
required: true
|
||||
//message: t("general.validation.required"),
|
||||
}
|
||||
]}
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name={["md_payment_types"]}
|
||||
label={t("bodyshop.fields.md_payment_types")}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
type: "array"
|
||||
};
|
||||
}
|
||||
]}
|
||||
>
|
||||
<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>
|
||||
</>
|
||||
)}
|
||||
</LayoutFormRow>
|
||||
<LayoutFormRow header={t("bodyshop.labels.scoreboardsetup")} id="scoreboardsetup">
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.dailypainttarget")}
|
||||
name={["scoreboard_target", "dailyPaintTarget"]}
|
||||
rules={[
|
||||
{
|
||||
required: true
|
||||
//message: t("general.validation.required"),
|
||||
}
|
||||
]}
|
||||
>
|
||||
<InputNumber min={0} precision={0} />
|
||||
</Form.Item>
|
||||
}
|
||||
]}
|
||||
>
|
||||
<Select mode="tags" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name={["md_categories"]}
|
||||
label={t("bodyshop.fields.md_categories")}
|
||||
rules={[
|
||||
{
|
||||
//message: t("general.validation.required"),
|
||||
type: "array"
|
||||
}
|
||||
]}
|
||||
>
|
||||
<Select mode="tags" />
|
||||
</Form.Item>
|
||||
<Form.Item name={["enforce_class"]} label={t("bodyshop.fields.enforce_class")} valuePropName="checked">
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name={["accountingconfig", "ReceivableCustomField1"]}
|
||||
label={t("bodyshop.fields.ReceivableCustomField", { number: 1 })}
|
||||
>
|
||||
{ReceivableCustomFieldSelect}
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name={["accountingconfig", "ReceivableCustomField2"]}
|
||||
label={t("bodyshop.fields.ReceivableCustomField", { number: 2 })}
|
||||
>
|
||||
{ReceivableCustomFieldSelect}
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name={["accountingconfig", "ReceivableCustomField3"]}
|
||||
label={t("bodyshop.fields.ReceivableCustomField", { number: 3 })}
|
||||
>
|
||||
{ReceivableCustomFieldSelect}
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name={["md_classes"]}
|
||||
label={t("bodyshop.fields.md_classes")}
|
||||
rules={[
|
||||
({ getFieldValue }) => {
|
||||
return {
|
||||
required: getFieldValue("enforce_class"),
|
||||
//message: t("general.validation.required"),
|
||||
type: "array"
|
||||
};
|
||||
}
|
||||
]}
|
||||
>
|
||||
<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>
|
||||
</>
|
||||
)}
|
||||
</LayoutFormRow>
|
||||
</FeatureWrapper>
|
||||
<FeatureWrapper featureName="scoreboard" noauth={() => null}>
|
||||
<LayoutFormRow header={t("bodyshop.labels.scoreboardsetup")} id="scoreboardsetup">
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.dailypainttarget")}
|
||||
name={["scoreboard_target", "dailyPaintTarget"]}
|
||||
rules={[
|
||||
{
|
||||
required: true
|
||||
//message: t("general.validation.required"),
|
||||
}
|
||||
]}
|
||||
>
|
||||
<InputNumber min={0} precision={0} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.dailybodytarget")}
|
||||
name={["scoreboard_target", "dailyBodyTarget"]}
|
||||
rules={[
|
||||
{
|
||||
required: true
|
||||
//message: t("general.validation.required"),
|
||||
}
|
||||
]}
|
||||
>
|
||||
<InputNumber min={0} precision={0} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.dailybodytarget")}
|
||||
name={["scoreboard_target", "dailyBodyTarget"]}
|
||||
rules={[
|
||||
{
|
||||
required: true
|
||||
//message: t("general.validation.required"),
|
||||
}
|
||||
]}
|
||||
>
|
||||
<InputNumber min={0} precision={0} />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.lastnumberworkingdays")}
|
||||
name={["scoreboard_target", "lastNumberWorkingDays"]}
|
||||
rules={[
|
||||
{
|
||||
required: true
|
||||
//message: t("general.validation.required"),
|
||||
}
|
||||
]}
|
||||
>
|
||||
<InputNumber min={0} max={12} precision={0} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.ignoreblockeddays")}
|
||||
name={["scoreboard_target", "ignoreblockeddays"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.prodtargethrs")}
|
||||
name={["prodtargethrs"]}
|
||||
rules={[
|
||||
{
|
||||
required: true
|
||||
//message: t("general.validation.required"),
|
||||
}
|
||||
]}
|
||||
>
|
||||
<InputNumber min={1} precision={1} />
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.lastnumberworkingdays")}
|
||||
name={["scoreboard_target", "lastNumberWorkingDays"]}
|
||||
rules={[
|
||||
{
|
||||
required: true
|
||||
//message: t("general.validation.required"),
|
||||
}
|
||||
]}
|
||||
>
|
||||
<InputNumber min={0} max={12} precision={0} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.ignoreblockeddays")}
|
||||
name={["scoreboard_target", "ignoreblockeddays"]}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.prodtargethrs")}
|
||||
name={["prodtargethrs"]}
|
||||
rules={[
|
||||
{
|
||||
required: true
|
||||
//message: t("general.validation.required"),
|
||||
}
|
||||
]}
|
||||
>
|
||||
<InputNumber min={1} precision={1} />
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
</FeatureWrapper>
|
||||
<LayoutFormRow header={t("bodyshop.labels.systemsettings")} id="systemsettings">
|
||||
<Form.Item
|
||||
name={["md_referral_sources"]}
|
||||
@@ -567,27 +572,32 @@ export function ShopInfoGeneral({ form, bodyshop }) {
|
||||
>
|
||||
<Select mode="tags" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name={["tt_allow_post_to_invoiced"]}
|
||||
label={t("bodyshop.fields.tt_allow_post_to_invoiced")}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name={["tt_enforce_hours_for_tech_console"]}
|
||||
label={t("bodyshop.fields.tt_enforce_hours_for_tech_console")}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name={["bill_allow_post_to_closed"]}
|
||||
label={t("bodyshop.fields.bill_allow_post_to_closed")}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
|
||||
{HasFeatureAccess({ featureName: "timetickets", bodyshop }) && (
|
||||
<>
|
||||
<Form.Item
|
||||
name={["tt_allow_post_to_invoiced"]}
|
||||
label={t("bodyshop.fields.tt_allow_post_to_invoiced")}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name={["tt_enforce_hours_for_tech_console"]}
|
||||
label={t("bodyshop.fields.tt_enforce_hours_for_tech_console")}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name={["bill_allow_post_to_closed"]}
|
||||
label={t("bodyshop.fields.bill_allow_post_to_closed")}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
</>
|
||||
)}
|
||||
<Form.Item
|
||||
name={["md_ded_notes"]}
|
||||
label={t("bodyshop.fields.md_ded_notes")}
|
||||
@@ -1042,111 +1052,118 @@ export function ShopInfoGeneral({ form, bodyshop }) {
|
||||
}}
|
||||
</Form.List>
|
||||
</LayoutFormRow>
|
||||
<LayoutFormRow grow header={t("bodyshop.fields.md_ccc_rates")} id="md_ccc_rates">
|
||||
<Form.List name={["md_ccc_rates"]}>
|
||||
{(fields, { add, remove, move }) => {
|
||||
return (
|
||||
<div>
|
||||
{fields.map((field, index) => (
|
||||
<Form.Item key={field.key}>
|
||||
<LayoutFormRow noDivider>
|
||||
<Form.Item label={t("general.labels.label")} key={`${index}label`} name={[field.name, "label"]}>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item label={t("contracts.fields.actax")} key={`${index}actax`} name={[field.name, "actax"]}>
|
||||
<InputNumber precision={2} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("contracts.fields.dailyfreekm")}
|
||||
key={`${index}dailyfreekm`}
|
||||
name={[field.name, "dailyfreekm"]}
|
||||
>
|
||||
<InputNumber precision={2} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("contracts.fields.refuelcharge")}
|
||||
key={`${index}refuelcharge`}
|
||||
name={[field.name, "refuelcharge"]}
|
||||
>
|
||||
<InputNumber precision={2} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("contracts.fields.excesskmrate")}
|
||||
key={`${index}excesskmrate`}
|
||||
name={[field.name, "excesskmrate"]}
|
||||
>
|
||||
<InputNumber precision={2} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("contracts.fields.cleanupcharge")}
|
||||
key={`${index}cleanupcharge`}
|
||||
name={[field.name, "cleanupcharge"]}
|
||||
>
|
||||
<InputNumber precision={2} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("contracts.fields.damagewaiver")}
|
||||
key={`${index}damagewaiver`}
|
||||
name={[field.name, "damagewaiver"]}
|
||||
>
|
||||
<InputNumber precision={2} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("contracts.fields.federaltax")}
|
||||
key={`${index}federaltax`}
|
||||
name={[field.name, "federaltax"]}
|
||||
>
|
||||
<InputNumber precision={2} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("contracts.fields.statetax")}
|
||||
key={`${index}statetax`}
|
||||
name={[field.name, "statetax"]}
|
||||
>
|
||||
<InputNumber precision={2} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("contracts.fields.localtax")}
|
||||
key={`${index}localtax`}
|
||||
name={[field.name, "localtax"]}
|
||||
>
|
||||
<InputNumber precision={2} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("contracts.fields.coverage")}
|
||||
key={`${index}coverage`}
|
||||
name={[field.name, "coverage"]}
|
||||
>
|
||||
<InputNumber precision={2} />
|
||||
</Form.Item>
|
||||
<FeatureWrapper featureName="courtesycars" noauth={() => null}>
|
||||
<LayoutFormRow grow header={t("bodyshop.fields.md_ccc_rates")} id="md_ccc_rates">
|
||||
<Form.List name={["md_ccc_rates"]}>
|
||||
{(fields, { add, remove, move }) => {
|
||||
return (
|
||||
<div>
|
||||
{fields.map((field, index) => (
|
||||
<Form.Item key={field.key}>
|
||||
<LayoutFormRow noDivider>
|
||||
<Form.Item label={t("general.labels.label")} key={`${index}label`} name={[field.name, "label"]}>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("contracts.fields.actax")}
|
||||
key={`${index}actax`}
|
||||
name={[field.name, "actax"]}
|
||||
>
|
||||
<InputNumber precision={2} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("contracts.fields.dailyfreekm")}
|
||||
key={`${index}dailyfreekm`}
|
||||
name={[field.name, "dailyfreekm"]}
|
||||
>
|
||||
<InputNumber precision={2} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("contracts.fields.refuelcharge")}
|
||||
key={`${index}refuelcharge`}
|
||||
name={[field.name, "refuelcharge"]}
|
||||
>
|
||||
<InputNumber precision={2} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("contracts.fields.excesskmrate")}
|
||||
key={`${index}excesskmrate`}
|
||||
name={[field.name, "excesskmrate"]}
|
||||
>
|
||||
<InputNumber precision={2} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("contracts.fields.cleanupcharge")}
|
||||
key={`${index}cleanupcharge`}
|
||||
name={[field.name, "cleanupcharge"]}
|
||||
>
|
||||
<InputNumber precision={2} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("contracts.fields.damagewaiver")}
|
||||
key={`${index}damagewaiver`}
|
||||
name={[field.name, "damagewaiver"]}
|
||||
>
|
||||
<InputNumber precision={2} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("contracts.fields.federaltax")}
|
||||
key={`${index}federaltax`}
|
||||
name={[field.name, "federaltax"]}
|
||||
>
|
||||
<InputNumber precision={2} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("contracts.fields.statetax")}
|
||||
key={`${index}statetax`}
|
||||
name={[field.name, "statetax"]}
|
||||
>
|
||||
<InputNumber precision={2} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("contracts.fields.localtax")}
|
||||
key={`${index}localtax`}
|
||||
name={[field.name, "localtax"]}
|
||||
>
|
||||
<InputNumber precision={2} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("contracts.fields.coverage")}
|
||||
key={`${index}coverage`}
|
||||
name={[field.name, "coverage"]}
|
||||
>
|
||||
<InputNumber precision={2} />
|
||||
</Form.Item>
|
||||
|
||||
<Space wrap>
|
||||
<DeleteFilled
|
||||
onClick={() => {
|
||||
remove(field.name);
|
||||
}}
|
||||
/>
|
||||
<FormListMoveArrows move={move} index={index} total={fields.length} />
|
||||
</Space>
|
||||
</LayoutFormRow>
|
||||
<Space wrap>
|
||||
<DeleteFilled
|
||||
onClick={() => {
|
||||
remove(field.name);
|
||||
}}
|
||||
/>
|
||||
<FormListMoveArrows move={move} index={index} total={fields.length} />
|
||||
</Space>
|
||||
</LayoutFormRow>
|
||||
</Form.Item>
|
||||
))}
|
||||
<Form.Item>
|
||||
<Button
|
||||
type="dashed"
|
||||
onClick={() => {
|
||||
add();
|
||||
}}
|
||||
style={{ width: "100%" }}
|
||||
>
|
||||
{t("general.actions.add")}
|
||||
</Button>
|
||||
</Form.Item>
|
||||
))}
|
||||
<Form.Item>
|
||||
<Button
|
||||
type="dashed"
|
||||
onClick={() => {
|
||||
add();
|
||||
}}
|
||||
style={{ width: "100%" }}
|
||||
>
|
||||
{t("general.actions.add")}
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
</Form.List>
|
||||
</LayoutFormRow>
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
</Form.List>
|
||||
</LayoutFormRow>
|
||||
</FeatureWrapper>
|
||||
|
||||
<LayoutFormRow grow header={t("bodyshop.fields.md_jobline_presets")} id="md_jobline_presets">
|
||||
<Form.List name={["md_jobline_presets"]}>
|
||||
{(fields, { add, remove, move }) => {
|
||||
|
||||
@@ -319,6 +319,18 @@ export default function ShopInfoIntakeChecklistComponent({ form }) {
|
||||
))}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name={["deliverchecklist", "actual_delivery"]}
|
||||
label={t("bodyshop.fields.deliver.require_actual_delivery_date")}
|
||||
rules={[
|
||||
{
|
||||
required: true
|
||||
//message: t("general.validation.required"),
|
||||
}
|
||||
]}
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
</SelectorDiv>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
||||
import { Form, InputNumber } from "antd";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component";
|
||||
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
||||
import RbacWrapper from "../rbac-wrapper/rbac-wrapper.component";
|
||||
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop
|
||||
@@ -29,210 +30,219 @@ export function ShopInfoRbacComponent({ form, bodyshop }) {
|
||||
return (
|
||||
<RbacWrapper action="shop:rbac">
|
||||
<LayoutFormRow>
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
<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={[
|
||||
@@ -425,18 +435,6 @@ export function ShopInfoRbacComponent({ form, bodyshop }) {
|
||||
>
|
||||
<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.employees.page")}
|
||||
rules={[
|
||||
@@ -510,18 +508,21 @@ export function ShopInfoRbacComponent({ form, bodyshop }) {
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.production.board")}
|
||||
rules={[
|
||||
{
|
||||
required: true
|
||||
//message: t("general.validation.required"),
|
||||
}
|
||||
]}
|
||||
name={["md_rbac", "production:board"]}
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>
|
||||
{HasFeatureAccess({ featureName: "visualboard", bodyshop }) && (
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.production.board")}
|
||||
rules={[
|
||||
{
|
||||
required: true
|
||||
//message: t("general.validation.required"),
|
||||
}
|
||||
]}
|
||||
name={["md_rbac", "production:board"]}
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>
|
||||
)}
|
||||
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.production.list")}
|
||||
rules={[
|
||||
@@ -546,102 +547,142 @@ export function ShopInfoRbacComponent({ form, bodyshop }) {
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.scoreboard.view")}
|
||||
rules={[
|
||||
{
|
||||
required: true
|
||||
//message: t("general.validation.required"),
|
||||
}
|
||||
]}
|
||||
name={["md_rbac", "scoreboard:view"]}
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>
|
||||
<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>
|
||||
{HasFeatureAccess({ featureName: "scoreboard", bodyshop }) && (
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.scoreboard.view")}
|
||||
rules={[
|
||||
{
|
||||
required: true
|
||||
//message: t("general.validation.required"),
|
||||
}
|
||||
]}
|
||||
name={["md_rbac", "scoreboard:view"]}
|
||||
>
|
||||
<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>
|
||||
]:[]}
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.shop.vendors")}
|
||||
rules={[
|
||||
@@ -690,7 +731,7 @@ export function ShopInfoRbacComponent({ form, bodyshop }) {
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
{/* <Form.Item
|
||||
label={t("bodyshop.fields.rbac.shop.templates")}
|
||||
rules={[
|
||||
{
|
||||
@@ -701,79 +742,22 @@ export function ShopInfoRbacComponent({ form, bodyshop }) {
|
||||
name={["md_rbac", "shop:templates"]}
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.shop.vendors")}
|
||||
rules={[
|
||||
{
|
||||
required: true
|
||||
//message: t("general.validation.required"),
|
||||
}
|
||||
]}
|
||||
name={["md_rbac", "shop:vendors"]}
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.temporarydocs.view")}
|
||||
rules={[
|
||||
{
|
||||
required: true
|
||||
//message: t("general.validation.required"),
|
||||
}
|
||||
]}
|
||||
name={["md_rbac", "temporarydocs:view"]}
|
||||
>
|
||||
<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.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>
|
||||
</Form.Item> */}
|
||||
{HasFeatureAccess({ featureName: "media", bodyshop }) && (
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.temporarydocs.view")}
|
||||
rules={[
|
||||
{
|
||||
required: true
|
||||
//message: t("general.validation.required"),
|
||||
}
|
||||
]}
|
||||
name={["md_rbac", "temporarydocs:view"]}
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>
|
||||
)}
|
||||
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.rbac.users.editaccess")}
|
||||
rules={[
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -339,7 +339,7 @@ export function ShopInfoROStatusComponent({ bodyshop, form }) {
|
||||
{(fields, { add, remove, move }) => {
|
||||
return (
|
||||
<div>
|
||||
<LayoutFormRow>
|
||||
<Space size='large' wrap>
|
||||
{fields.map((field, index) => (
|
||||
<Form.Item key={field.key}>
|
||||
<Space direction="vertical">
|
||||
@@ -386,7 +386,7 @@ export function ShopInfoROStatusComponent({ bodyshop, form }) {
|
||||
</Space>
|
||||
</Form.Item>
|
||||
))}
|
||||
</LayoutFormRow>
|
||||
</Space>
|
||||
<Form.Item>
|
||||
<Button
|
||||
type="dashed"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Button, Result } from "antd";
|
||||
import { Result } from "antd";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
@@ -16,19 +16,5 @@ export default connect(mapStateToProps, mapDispatchToProps)(ShopSubStatus);
|
||||
export function ShopSubStatus({ bodyshop }) {
|
||||
const { t } = useTranslation();
|
||||
const { sub_status } = bodyshop;
|
||||
return (
|
||||
<Result
|
||||
status="403"
|
||||
title={t(`general.labels.sub_status.${sub_status}`)}
|
||||
extra={
|
||||
<Button
|
||||
onClick={() => {
|
||||
alert("Not implemented yet.");
|
||||
}}
|
||||
>
|
||||
{t("general.actions.submitticket")}
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
);
|
||||
return <Result status="403" title={t(`general.labels.sub_status.${sub_status}`)} />;
|
||||
}
|
||||
|
||||
382
client/src/components/task-list/task-list.component.jsx
Normal file
382
client/src/components/task-list/task-list.component.jsx
Normal file
@@ -0,0 +1,382 @@
|
||||
import { Button, Card, Space, Switch, Table } from "antd";
|
||||
import queryString from "query-string";
|
||||
import React, { useCallback, useEffect } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Link, useLocation, useNavigate } from "react-router-dom";
|
||||
import { pageLimit } from "../../utils/config";
|
||||
import dayjs from "../../utils/day";
|
||||
import {
|
||||
CheckCircleFilled,
|
||||
CheckCircleOutlined,
|
||||
DeleteFilled,
|
||||
DeleteOutlined,
|
||||
EditFilled,
|
||||
ExclamationCircleFilled,
|
||||
PlusCircleFilled,
|
||||
SyncOutlined
|
||||
} from "@ant-design/icons";
|
||||
import { DateFormatter, DateTimeFormatter } from "../../utils/DateFormatter.jsx";
|
||||
import { connect } from "react-redux";
|
||||
import { setModalContext } from "../../redux/modals/modals.actions";
|
||||
|
||||
/**
|
||||
* Task List Component
|
||||
* @param dueDate
|
||||
* @returns {Element}
|
||||
* @constructor
|
||||
*/
|
||||
const DueDateRecord = ({ dueDate }) => {
|
||||
if (!dueDate) return <></>;
|
||||
|
||||
const dueDateDayjs = dayjs(dueDate);
|
||||
const relativeDueDate = dueDateDayjs.fromNow();
|
||||
const isBeforeToday = dueDateDayjs.isBefore(dayjs());
|
||||
|
||||
return (
|
||||
<div title={relativeDueDate} style={isBeforeToday ? { color: "red" } : {}}>
|
||||
<DateFormatter>{dueDate}</DateFormatter>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const RemindAtRecord = ({ remindAt }) => {
|
||||
if (!remindAt) return <></>;
|
||||
|
||||
const remindAtDayjs = dayjs(remindAt);
|
||||
const relativeRemindAtDate = remindAtDayjs.fromNow();
|
||||
const isBeforeToday = remindAtDayjs.isBefore(dayjs());
|
||||
|
||||
return (
|
||||
<div title={relativeRemindAtDate} style={isBeforeToday ? { color: "red" } : {}}>
|
||||
<DateTimeFormatter>{remindAt}</DateTimeFormatter>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Priority Label Component
|
||||
* @param priority
|
||||
* @returns {Element}
|
||||
* @constructor
|
||||
*/
|
||||
const PriorityLabel = ({ priority }) => {
|
||||
switch (priority) {
|
||||
case 1:
|
||||
return (
|
||||
<div>
|
||||
High <ExclamationCircleFilled style={{ marginLeft: "5px", color: "red" }} />
|
||||
</div>
|
||||
);
|
||||
case 2:
|
||||
return (
|
||||
<div>
|
||||
Medium <ExclamationCircleFilled style={{ marginLeft: "5px", color: "yellow" }} />
|
||||
</div>
|
||||
);
|
||||
case 3:
|
||||
return (
|
||||
<div>
|
||||
Low <ExclamationCircleFilled style={{ marginLeft: "5px", color: "green" }} />
|
||||
</div>
|
||||
);
|
||||
default:
|
||||
return (
|
||||
<div>
|
||||
None <ExclamationCircleFilled style={{ marginLeft: "5px" }} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
// Existing dispatch props...
|
||||
setTaskUpsertContext: (context) => dispatch(setModalContext({ context, modal: "taskUpsert" }))
|
||||
});
|
||||
|
||||
const mapStateToProps = (state) => ({});
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(TaskListComponent);
|
||||
|
||||
function TaskListComponent({
|
||||
bodyshop,
|
||||
loading,
|
||||
tasks,
|
||||
total,
|
||||
titleTranslation,
|
||||
refetch,
|
||||
toggleCompletedStatus,
|
||||
setTaskUpsertContext,
|
||||
toggleDeletedStatus,
|
||||
relationshipType,
|
||||
relationshipId,
|
||||
onlyMine,
|
||||
parentJobId,
|
||||
query,
|
||||
showRo = true
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const location = useLocation();
|
||||
|
||||
const search = queryString.parse(useLocation().search);
|
||||
|
||||
// Extract Query Params
|
||||
const { page, sortcolumn, sortorder, deleted, completed, mine } = search;
|
||||
|
||||
const history = useNavigate();
|
||||
const columns = [];
|
||||
|
||||
useEffect(() => {
|
||||
// This is a hack to force the page to change if the query params change (partssublet for example)
|
||||
}, [location]);
|
||||
|
||||
columns.push({
|
||||
title: t("tasks.fields.created_at"),
|
||||
dataIndex: "created_at",
|
||||
key: "created_at",
|
||||
width: "10%",
|
||||
defaultSortOrder: "descend",
|
||||
sorter: true,
|
||||
sortOrder: sortcolumn === "created_at" && sortorder,
|
||||
render: (text, record) => <DateTimeFormatter>{record.created_at}</DateTimeFormatter>
|
||||
});
|
||||
|
||||
if (!onlyMine) {
|
||||
columns.push({
|
||||
title: t("tasks.fields.assigned_to"),
|
||||
dataIndex: "assigned_to",
|
||||
key: "assigned_to",
|
||||
width: "8%",
|
||||
sorter: true,
|
||||
sortOrder: sortcolumn === "assigned_to" && sortorder,
|
||||
render: (text, record) => {
|
||||
const employee = bodyshop?.employees?.find((e) => e.id === record.assigned_to);
|
||||
return employee ? `${employee.first_name} ${employee.last_name}` : t("general.labels.na");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (showRo) {
|
||||
columns.push({
|
||||
title: t("tasks.fields.job.ro_number"),
|
||||
dataIndex: ["job", "ro_number"],
|
||||
key: "job.ro_number",
|
||||
width: "8%",
|
||||
render: (text, record) =>
|
||||
record.job ? (
|
||||
<Link to={`/manage/jobs/${record.job.id}?tab=tasks`}>{record.job.ro_number || t("general.labels.na")}</Link>
|
||||
) : (
|
||||
t("general.labels.na")
|
||||
)
|
||||
});
|
||||
}
|
||||
|
||||
columns.push(
|
||||
{
|
||||
title: t("tasks.fields.jobline"),
|
||||
dataIndex: ["jobline", "id"],
|
||||
key: "jobline.id",
|
||||
width: "8%",
|
||||
render: (text, record) => record?.jobline?.line_desc || ""
|
||||
},
|
||||
{
|
||||
title: t("tasks.fields.parts_order"),
|
||||
dataIndex: ["parts_order", "id"],
|
||||
key: "part_order.id",
|
||||
width: "8%",
|
||||
render: (text, record) =>
|
||||
record.parts_order ? (
|
||||
<Link to={`/manage/jobs/${record.job.id}?partsorderid=${record.parts_order.id}&tab=partssublet`}>
|
||||
{record.parts_order.order_number && record.parts_order.vendor && record.parts_order.vendor.name
|
||||
? `${record.parts_order.order_number} - ${record.parts_order.vendor.name}`
|
||||
: t("general.labels.na")}
|
||||
</Link>
|
||||
) : (
|
||||
""
|
||||
)
|
||||
},
|
||||
{
|
||||
title: t("tasks.fields.bill"),
|
||||
dataIndex: ["bill", "id"],
|
||||
key: "bill.id",
|
||||
width: "10%",
|
||||
render: (text, record) =>
|
||||
record.bill ? (
|
||||
<Link to={`/manage/jobs/${record.job.id}?billid=${record.bill.id}&tab=partssublet`}>
|
||||
{record.bill.invoice_number && record.bill.vendor && record.bill.vendor.name
|
||||
? `${record.bill.invoice_number} - ${record.bill.vendor.name}`
|
||||
: t("general.labels.na")}
|
||||
</Link>
|
||||
) : (
|
||||
""
|
||||
)
|
||||
},
|
||||
{
|
||||
title: t("tasks.fields.title"),
|
||||
dataIndex: "title",
|
||||
key: "title",
|
||||
sorter: true,
|
||||
sortOrder: sortcolumn === "title" && sortorder
|
||||
},
|
||||
{
|
||||
title: t("tasks.fields.due_date"),
|
||||
dataIndex: "due_date",
|
||||
key: "due_date",
|
||||
sorter: true,
|
||||
sortOrder: sortcolumn === "due_date" && sortorder,
|
||||
width: "8%",
|
||||
render: (text, record) => <DueDateRecord dueDate={record.due_date} />
|
||||
},
|
||||
{
|
||||
title: t("tasks.fields.remind_at"),
|
||||
dataIndex: "remind_at",
|
||||
key: "remind_at",
|
||||
sorter: true,
|
||||
sortOrder: sortcolumn === "remind_at" && sortorder,
|
||||
width: "10%",
|
||||
render: (text, record) => <RemindAtRecord remindAt={record.remind_at} />
|
||||
},
|
||||
{
|
||||
title: t("tasks.fields.priority"),
|
||||
dataIndex: "priority",
|
||||
key: "priority",
|
||||
sorter: true,
|
||||
sortOrder: sortcolumn === "priority" && sortorder,
|
||||
width: "8%",
|
||||
render: (text, record) => <PriorityLabel priority={record.priority} />
|
||||
},
|
||||
{
|
||||
title: t("tasks.fields.actions"),
|
||||
key: "toggleCompleted",
|
||||
width: "5%",
|
||||
render: (text, record) => (
|
||||
<Space direction="horizontal">
|
||||
<Button
|
||||
title={t("tasks.buttons.edit")}
|
||||
onClick={() => {
|
||||
setTaskUpsertContext({
|
||||
context: {
|
||||
existingTask: record,
|
||||
query
|
||||
}
|
||||
});
|
||||
}}
|
||||
>
|
||||
<EditFilled />
|
||||
</Button>
|
||||
<Button
|
||||
title={t("tasks.buttons.complete")}
|
||||
onClick={() => toggleCompletedStatus(record.id, record.completed)}
|
||||
>
|
||||
{record.completed ? <CheckCircleOutlined /> : <CheckCircleFilled />}
|
||||
</Button>
|
||||
<Button title={t("tasks.buttons.delete")} onClick={() => toggleDeletedStatus(record.id, record.deleted)}>
|
||||
{record.deleted ? <DeleteOutlined /> : <DeleteFilled />}
|
||||
</Button>
|
||||
</Space>
|
||||
)
|
||||
}
|
||||
);
|
||||
|
||||
const handleCreateTask = useCallback(() => {
|
||||
setTaskUpsertContext({
|
||||
actions: {},
|
||||
context: {
|
||||
jobid: parentJobId,
|
||||
[relationshipType]: relationshipId,
|
||||
query
|
||||
}
|
||||
});
|
||||
}, [parentJobId, relationshipId, relationshipType, setTaskUpsertContext, query]);
|
||||
|
||||
const handleTableChange = (pagination, filters, sorter) => {
|
||||
search.page = pagination.current;
|
||||
search.sortcolumn = sorter.columnKey;
|
||||
search.sortorder = sorter.order;
|
||||
history({ search: queryString.stringify(search) });
|
||||
};
|
||||
|
||||
const handleSwitchChange = useCallback(
|
||||
(param, value) => {
|
||||
if (value) {
|
||||
search[param] = "true";
|
||||
} else {
|
||||
delete search[param];
|
||||
}
|
||||
history({ search: queryString.stringify(search) });
|
||||
},
|
||||
[history, search]
|
||||
);
|
||||
|
||||
const expandableRow = (record) => {
|
||||
return (
|
||||
<Card title={t("tasks.fields.description")} size="small">
|
||||
{record.description}
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Extra actions for the tasks
|
||||
* @type {Function}
|
||||
*/
|
||||
const tasksExtra = useCallback(() => {
|
||||
return (
|
||||
<Space direction="horizontal">
|
||||
{!onlyMine && (
|
||||
<Switch
|
||||
checkedChildren={t("tasks.buttons.myTasks")}
|
||||
unCheckedChildren={t("tasks.buttons.allTasks")}
|
||||
title={t("tasks.titles.mine")}
|
||||
checked={mine === "true"}
|
||||
onChange={(value) => handleSwitchChange("mine", value)}
|
||||
/>
|
||||
)}
|
||||
<Switch
|
||||
checkedChildren={<CheckCircleFilled />}
|
||||
unCheckedChildren={<CheckCircleOutlined />}
|
||||
title={t("tasks.titles.completed")}
|
||||
checked={completed === "true"}
|
||||
onChange={(value) => handleSwitchChange("completed", value)}
|
||||
/>
|
||||
<Switch
|
||||
checkedChildren={<DeleteFilled />}
|
||||
unCheckedChildren={<DeleteOutlined />}
|
||||
title={t("tasks.titles.deleted")}
|
||||
checked={deleted === "true"}
|
||||
onChange={(value) => handleSwitchChange("deleted", value)}
|
||||
/>
|
||||
<Button title={t("tasks.buttons.refresh")} onClick={() => refetch()}>
|
||||
<SyncOutlined />
|
||||
</Button>
|
||||
<Button title={t("tasks.buttons.create")} onClick={handleCreateTask}>
|
||||
<PlusCircleFilled />
|
||||
{t("tasks.buttons.create")}
|
||||
</Button>
|
||||
</Space>
|
||||
);
|
||||
}, [refetch, deleted, completed, mine, onlyMine, t, handleSwitchChange, handleCreateTask]);
|
||||
|
||||
return (
|
||||
<Card title={titleTranslation} extra={tasksExtra()}>
|
||||
<Table
|
||||
loading={loading}
|
||||
pagination={{
|
||||
pageSize: pageLimit,
|
||||
current: parseInt(page || 1),
|
||||
total: total,
|
||||
responsive: true,
|
||||
showQuickJumper: true
|
||||
}}
|
||||
columns={columns}
|
||||
rowKey="id"
|
||||
scroll={{ x: true }}
|
||||
dataSource={tasks}
|
||||
onChange={handleTableChange}
|
||||
expandable={{
|
||||
expandedRowRender: expandableRow,
|
||||
rowExpandable: (record) => record.description
|
||||
}}
|
||||
/>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
193
client/src/components/task-list/task-list.container.jsx
Normal file
193
client/src/components/task-list/task-list.container.jsx
Normal file
@@ -0,0 +1,193 @@
|
||||
import queryString from "query-string";
|
||||
import { useLocation } from "react-router-dom";
|
||||
import { useMutation, useQuery } from "@apollo/client";
|
||||
import { MUTATION_TOGGLE_TASK_COMPLETED, MUTATION_TOGGLE_TASK_DELETED } from "../../graphql/tasks.queries.js";
|
||||
import { pageLimit } from "../../utils/config.js";
|
||||
import AlertComponent from "../alert/alert.component.jsx";
|
||||
import React from "react";
|
||||
import TaskListComponent from "./task-list.component.jsx";
|
||||
import { notification } from "antd";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect, useDispatch } from "react-redux";
|
||||
import { insertAuditTrail } from "../../redux/application/application.actions.js";
|
||||
import AuditTrailMapping from "../../utils/AuditTrailMappings.js";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors.js";
|
||||
import dayjs from "../../utils/day";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
currentUser: selectCurrentUser
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({});
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(TaskListContainer);
|
||||
|
||||
export function TaskListContainer({
|
||||
bodyshop,
|
||||
titleTranslation,
|
||||
query,
|
||||
relationshipType,
|
||||
relationshipId,
|
||||
currentUser,
|
||||
onlyMine,
|
||||
parentJobId,
|
||||
showRo = true,
|
||||
disableJobRefetch = false
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const searchParams = queryString.parse(useLocation().search);
|
||||
const {
|
||||
page,
|
||||
sortcolumn,
|
||||
sortorder,
|
||||
deleted,
|
||||
completed //mine
|
||||
} = searchParams;
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const { loading, error, data, refetch } = useQuery(query[Object.keys(query)[0]], {
|
||||
fetchPolicy: "network-only",
|
||||
nextFetchPolicy: "network-only",
|
||||
variables: {
|
||||
bodyshop: bodyshop.id,
|
||||
[relationshipType]: relationshipId,
|
||||
deleted: deleted === "true",
|
||||
completed: completed === "true", //TODO: Find where mine is set.
|
||||
assigned_to: onlyMine ? bodyshop?.employees?.find((e) => e.user_email === currentUser.email)?.id : undefined, // replace currentUserID with the actual ID of the current user
|
||||
offset: page ? (page - 1) * pageLimit : 0,
|
||||
limit: pageLimit,
|
||||
order: [
|
||||
{
|
||||
[sortcolumn || "created_at"]: sortorder ? (sortorder === "descend" ? "desc" : "asc") : "desc"
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Toggle task completed mutation
|
||||
*/
|
||||
const [toggleTaskCompleted] = useMutation(MUTATION_TOGGLE_TASK_COMPLETED);
|
||||
|
||||
/**
|
||||
* Toggle task completed status
|
||||
* @param id
|
||||
* @param currentStatus
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
const toggleCompletedStatus = async (id, currentStatus) => {
|
||||
const completed_at = !currentStatus ? dayjs().toISOString() : null;
|
||||
|
||||
try {
|
||||
const toggledTaskObject = {
|
||||
variables: {
|
||||
id: id,
|
||||
completed: !currentStatus,
|
||||
completed_at: completed_at
|
||||
},
|
||||
refetchQueries: [Object.keys(query)[0]]
|
||||
};
|
||||
|
||||
if (!disableJobRefetch) {
|
||||
toggledTaskObject.refetchQueries.push("GET_JOB_BY_PK");
|
||||
}
|
||||
|
||||
const toggledTask = await toggleTaskCompleted(toggledTaskObject);
|
||||
|
||||
if (!toggledTask.errors) {
|
||||
dispatch(
|
||||
insertAuditTrail({
|
||||
jobid: toggledTask.data.update_tasks_by_pk.jobid,
|
||||
operation: toggledTask?.data?.update_tasks_by_pk?.completed
|
||||
? AuditTrailMapping.tasksCompleted(toggledTask.data.update_tasks_by_pk.title, currentUser.email)
|
||||
: AuditTrailMapping.tasksUncompleted(toggledTask.data.update_tasks_by_pk.title, currentUser.email),
|
||||
type: toggledTask?.data?.update_tasks_by_pk?.completed ? "tasksCompleted" : "tasksUncompleted"
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
notification["success"]({
|
||||
message: t("tasks.successes.completed")
|
||||
});
|
||||
} catch (err) {
|
||||
notification["error"]({
|
||||
message: t("tasks.failures.completed")
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Toggle task deleted mutation
|
||||
*/
|
||||
const [toggleTaskDeleted] = useMutation(MUTATION_TOGGLE_TASK_DELETED);
|
||||
|
||||
/**
|
||||
* Toggle task deleted status
|
||||
* @param id
|
||||
* @param currentStatus
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
|
||||
const toggleDeletedStatus = async (id, currentStatus) => {
|
||||
const deleted_at = !currentStatus ? dayjs().toISOString() : null;
|
||||
try {
|
||||
const toggledTaskObject = {
|
||||
variables: {
|
||||
id: id,
|
||||
deleted: !currentStatus,
|
||||
deleted_at: deleted_at
|
||||
},
|
||||
refetchQueries: [Object.keys(query)[0]]
|
||||
};
|
||||
|
||||
if (!disableJobRefetch) {
|
||||
toggledTaskObject.refetchQueries.push("GET_JOB_BY_PK");
|
||||
}
|
||||
|
||||
const toggledTask = await toggleTaskDeleted(toggledTaskObject);
|
||||
|
||||
if (!toggledTask.errors) {
|
||||
dispatch(
|
||||
insertAuditTrail({
|
||||
jobid: toggledTask.data.update_tasks_by_pk.jobid,
|
||||
operation: toggledTask?.data?.update_tasks_by_pk?.deleted
|
||||
? AuditTrailMapping.tasksDeleted(toggledTask.data.update_tasks_by_pk.title, currentUser.email)
|
||||
: AuditTrailMapping.tasksUndeleted(toggledTask.data.update_tasks_by_pk.title, currentUser.email),
|
||||
type: toggledTask?.data?.update_tasks_by_pk?.deleted ? "tasksDeleted" : "tasksUndeleted"
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
notification["success"]({
|
||||
message: t("tasks.successes.deleted")
|
||||
});
|
||||
} catch (err) {
|
||||
notification["error"]({
|
||||
message: t("tasks.failures.deleted")
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
if (error) return <AlertComponent message={error.message} type="error" />;
|
||||
|
||||
return (
|
||||
<TaskListComponent
|
||||
loading={loading}
|
||||
tasks={data ? data.tasks : null}
|
||||
total={data ? data.tasks_aggregate.aggregate.count : 0}
|
||||
titleTranslation={t(titleTranslation || "tasks.title")}
|
||||
refetch={refetch}
|
||||
toggleCompletedStatus={toggleCompletedStatus}
|
||||
toggleDeletedStatus={toggleDeletedStatus}
|
||||
relationshipType={relationshipType}
|
||||
relationshipId={relationshipId}
|
||||
onlyMine={onlyMine}
|
||||
showRo={showRo}
|
||||
parentJobId={parentJobId}
|
||||
bodyshop={bodyshop}
|
||||
query={query}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,299 @@
|
||||
import { Col, Form, Input, Row, Select, Switch } from "antd";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { FormDatePicker } from "../form-date-picker/form-date-picker.component.jsx";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors.js";
|
||||
import dayjs from "../../utils/day";
|
||||
import { connect } from "react-redux";
|
||||
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component.jsx";
|
||||
import JobSearchSelectComponent from "../job-search-select/job-search-select.component.jsx";
|
||||
import { FormDateTimePickerEnhanced } from "../form-date-time-picker-enhanced/form-date-time-picker-enhanced.component.jsx";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
currentUser: selectCurrentUser
|
||||
});
|
||||
|
||||
const mapDispatchToProps = () => ({});
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(TaskUpsertModalComponent);
|
||||
|
||||
export function TaskUpsertModalComponent({
|
||||
form,
|
||||
bodyshop,
|
||||
currentUser,
|
||||
selectedJobId,
|
||||
setSelectedJobId,
|
||||
selectedJobDetails,
|
||||
existingTask,
|
||||
loading,
|
||||
error
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const datePickerPresets = [
|
||||
{ label: t("tasks.date_presets.today"), value: dayjs().add(1, "hour") },
|
||||
{ label: t("tasks.date_presets.tomorrow"), value: dayjs().add(1, "day").startOf("day") },
|
||||
{ label: t("tasks.date_presets.next_week"), value: dayjs().add(1, "week").startOf("day") },
|
||||
{ label: t("tasks.date_presets.two_weeks"), value: dayjs().add(2, "weeks").startOf("day") },
|
||||
{ label: t("tasks.date_presets.three_weeks"), value: dayjs().add(3, "weeks").startOf("day") },
|
||||
{ label: t("tasks.date_presets.one_month"), value: dayjs().add(1, "month").startOf("day") },
|
||||
{ label: t("tasks.date_presets.three_months"), value: dayjs().add(3, "month").startOf("day") }
|
||||
];
|
||||
|
||||
const generatePresets = (job) => {
|
||||
if (!job || !selectedJobDetails) return datePickerPresets; // return default presets if no job selected
|
||||
const relativePresets = [];
|
||||
|
||||
if (selectedJobDetails?.scheduled_completion) {
|
||||
const scheduledCompletion = dayjs(selectedJobDetails.scheduled_completion);
|
||||
|
||||
if (scheduledCompletion.isAfter(dayjs())) {
|
||||
relativePresets.push(
|
||||
{
|
||||
label: `${t("tasks.date_presets.completion")} -1 ${t("tasks.date_presets.day")}`,
|
||||
value: scheduledCompletion.subtract(1, "day").startOf("day")
|
||||
},
|
||||
{
|
||||
label: `${t("tasks.date_presets.completion")} -2 ${t("tasks.date_presets.days")}`,
|
||||
value: scheduledCompletion.subtract(2, "day").startOf("day")
|
||||
},
|
||||
{
|
||||
label: `${t("tasks.date_presets.completion")} -3 ${t("tasks.date_presets.days")}`,
|
||||
value: scheduledCompletion.subtract(3, "day").startOf("day")
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (selectedJobDetails?.scheduled_delivery) {
|
||||
const scheduledDelivery = dayjs(selectedJobDetails.scheduled_delivery);
|
||||
if (scheduledDelivery.isAfter(dayjs())) {
|
||||
relativePresets.push(
|
||||
{
|
||||
label: `${t("tasks.date_presets.delivery")} -1 ${t("tasks.date_presets.day")}`,
|
||||
value: scheduledDelivery.subtract(1, "day").startOf("day")
|
||||
},
|
||||
{
|
||||
label: `${t("tasks.date_presets.delivery")} -2 ${t("tasks.date_presets.days")}`,
|
||||
value: scheduledDelivery.subtract(2, "day").startOf("day")
|
||||
},
|
||||
{
|
||||
label: `${t("tasks.date_presets.delivery")} -3 ${t("tasks.date_presets.days")}`,
|
||||
value: scheduledDelivery.subtract(3, "day").startOf("day")
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return [...relativePresets, ...datePickerPresets];
|
||||
};
|
||||
|
||||
const clearRelations = () => {
|
||||
form.setFieldsValue({
|
||||
billid: null,
|
||||
partsorderid: null,
|
||||
joblineid: null
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Change the selected job id
|
||||
* @param jobId
|
||||
*/
|
||||
const changeJobId = (jobId) => {
|
||||
setSelectedJobId(jobId || null);
|
||||
// Reset the form fields when selectedJobId changes
|
||||
clearRelations();
|
||||
};
|
||||
|
||||
if (loading || error) return <LoadingSkeleton active />;
|
||||
|
||||
return (
|
||||
<>
|
||||
<Row gutter={[16, 16]}>
|
||||
<Col span={16}>
|
||||
<Form.Item
|
||||
label={t("tasks.fields.title")}
|
||||
name="title"
|
||||
rules={[
|
||||
{
|
||||
required: true
|
||||
}
|
||||
]}
|
||||
>
|
||||
<Input placeholder={t("tasks.fields.title")} />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={4}>
|
||||
<Form.Item label={t("tasks.fields.priority")} name="priority" initialValue={2}>
|
||||
<Select
|
||||
options={[
|
||||
{ value: 3, label: t("tasks.fields.priorities.low") },
|
||||
{ value: 2, label: t("tasks.fields.priorities.medium") },
|
||||
{ value: 1, label: t("tasks.fields.priorities.high") }
|
||||
]}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={4}>
|
||||
<Form.Item
|
||||
label={t("tasks.fields.completed")}
|
||||
name="completed"
|
||||
valuePropName="checked"
|
||||
initialValue={false}
|
||||
rules={[
|
||||
{
|
||||
required: true
|
||||
}
|
||||
]}
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row gutter={[16, 16]}>
|
||||
<Col span={24}>
|
||||
<Form.Item
|
||||
name="jobid"
|
||||
initialValue={selectedJobId}
|
||||
label={t("tasks.fields.jobid")}
|
||||
rules={[
|
||||
{
|
||||
required: true
|
||||
}
|
||||
]}
|
||||
>
|
||||
<JobSearchSelectComponent
|
||||
placeholder={t("tasks.placeholders.jobid")}
|
||||
onSelect={changeJobId}
|
||||
onClear={changeJobId}
|
||||
autoFocus={false}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row gutter={[16, 16]}>
|
||||
<Col span={8}>
|
||||
<Form.Item label={t("tasks.fields.joblineid")} name="joblineid">
|
||||
<Select
|
||||
allowClear
|
||||
placeholder={t("tasks.placeholders.joblineid")}
|
||||
disabled={!selectedJobDetails || !selectedJobId}
|
||||
options={selectedJobDetails?.joblines?.map((jobline) => ({
|
||||
key: jobline.id,
|
||||
value: jobline.id,
|
||||
label: jobline.line_desc
|
||||
}))}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={8}>
|
||||
<Form.Item label={t("tasks.fields.partsorderid")} name="partsorderid">
|
||||
<Select
|
||||
allowClear
|
||||
placeholder={t("tasks.placeholders.partsorderid")}
|
||||
disabled={!selectedJobDetails || !selectedJobId}
|
||||
options={selectedJobDetails?.parts_orders?.map((partsOrder) => ({
|
||||
key: partsOrder.id,
|
||||
value: partsOrder.id,
|
||||
label: `${partsOrder.order_number} - ${partsOrder.vendor.name}`
|
||||
}))}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={8}>
|
||||
<Form.Item label={t("tasks.fields.billid")} name="billid">
|
||||
<Select
|
||||
allowClear
|
||||
placeholder={t("tasks.placeholders.billid")}
|
||||
disabled={!selectedJobDetails || !selectedJobId}
|
||||
options={selectedJobDetails?.bills?.map((bill) => ({
|
||||
key: bill.id,
|
||||
value: bill.id,
|
||||
label: `${bill.invoice_number} - ${bill.vendor.name}`
|
||||
}))}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row gutter={[16, 16]}>
|
||||
<Col span={8}>
|
||||
<Form.Item
|
||||
label={t("tasks.fields.assigned_to")}
|
||||
name="assigned_to"
|
||||
initialValue={
|
||||
bodyshop.employees.find((employee) => employee?.user_email === currentUser.email && employee.active)?.id
|
||||
}
|
||||
rules={[
|
||||
{
|
||||
required: true
|
||||
}
|
||||
]}
|
||||
>
|
||||
<Select
|
||||
placeholder={t("tasks.placeholders.assigned_to")}
|
||||
options={bodyshop.employees
|
||||
.filter((x) => x.active && x.user_email)
|
||||
.map((employee) => ({
|
||||
key: employee.id,
|
||||
value: employee.id,
|
||||
label: `${employee.first_name} ${employee.last_name}`
|
||||
}))}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={8}>
|
||||
<Form.Item label={t("tasks.fields.due_date")} name="due_date">
|
||||
<FormDatePicker
|
||||
onlyFuture
|
||||
format="MM/DD/YYYY"
|
||||
presets={generatePresets(selectedJobDetails)}
|
||||
rules={[
|
||||
{
|
||||
validator: (_, value) => {
|
||||
if (!value || existingTask?.due_date === value || dayjs(value).isAfter(dayjs())) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
return Promise.reject(new Error(t("tasks.validation.due_at_error_message")));
|
||||
}
|
||||
}
|
||||
]}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={8}>
|
||||
<Form.Item
|
||||
label={t("tasks.fields.remind_at")}
|
||||
name="remind_at"
|
||||
rules={[
|
||||
{
|
||||
validator: (_, value) => {
|
||||
if (!value || existingTask?.remind_at === value || dayjs(value).isAfter(dayjs().add(15, "minute"))) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
return Promise.reject(new Error(t("tasks.validation.remind_at_error_message")));
|
||||
}
|
||||
}
|
||||
]}
|
||||
>
|
||||
<FormDateTimePickerEnhanced
|
||||
onlyFuture
|
||||
showTime
|
||||
minuteStep={15}
|
||||
presets={generatePresets(selectedJobDetails)}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row gutter={[16, 16]}>
|
||||
<Col span={24}>
|
||||
<Form.Item label={t("tasks.fields.description")} name="description">
|
||||
<Input.TextArea rows={8} placeholder={t("tasks.fields.description")} />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,296 @@
|
||||
import { useMutation, useQuery } from "@apollo/client";
|
||||
import { Form, Modal, notification } from "antd";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { MUTATION_INSERT_NEW_TASK, MUTATION_UPDATE_TASK, QUERY_GET_TASK_BY_ID } from "../../graphql/tasks.queries";
|
||||
import { GET_JOB_BY_PK, QUERY_GET_TASKS_JOB_DETAILS_BY_ID } from "../../graphql/jobs.queries.js";
|
||||
import { toggleModalVisible } from "../../redux/modals/modals.actions";
|
||||
import { selectTaskUpsert } from "../../redux/modals/modals.selectors";
|
||||
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors";
|
||||
import TaskUpsertModalComponent from "./task-upsert-modal.component";
|
||||
import { replaceUndefinedWithNull } from "../../utils/undefinedtonull.js";
|
||||
import { useLocation, useNavigate } from "react-router-dom";
|
||||
import { insertAuditTrail } from "../../redux/application/application.actions.js";
|
||||
import AuditTrailMapping from "../../utils/AuditTrailMappings.js";
|
||||
import { isEqual } from "lodash";
|
||||
import refetchRouteMappings from "./task-upsert-modal.route.mappings";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
currentUser: selectCurrentUser,
|
||||
bodyshop: selectBodyshop,
|
||||
taskUpsert: selectTaskUpsert
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
toggleModalVisible: () => dispatch(toggleModalVisible("taskUpsert")),
|
||||
insertAuditTrail: ({ jobid, billid, operation, type }) =>
|
||||
dispatch(insertAuditTrail({ jobid, billid, operation, type }))
|
||||
});
|
||||
|
||||
export function TaskUpsertModalContainer({ bodyshop, currentUser, taskUpsert, toggleModalVisible, insertAuditTrail }) {
|
||||
const { t } = useTranslation();
|
||||
const history = useNavigate();
|
||||
const [insertTask] = useMutation(MUTATION_INSERT_NEW_TASK);
|
||||
const [updateTask] = useMutation(MUTATION_UPDATE_TASK);
|
||||
const { open, context } = taskUpsert;
|
||||
const { jobid, joblineid, billid, partsorderid, taskId, existingTask, query } = context;
|
||||
const [form] = Form.useForm();
|
||||
const [selectedJobId, setSelectedJobId] = useState(null);
|
||||
const [selectedJobDetails, setSelectedJobDetails] = useState(null);
|
||||
const [jobIdState, setJobIdState] = useState(null);
|
||||
const [isTouched, setIsTouched] = useState(false);
|
||||
const location = useLocation();
|
||||
|
||||
const { loading, error, data } = useQuery(QUERY_GET_TASKS_JOB_DETAILS_BY_ID, {
|
||||
variables: { id: jobIdState },
|
||||
skip: !jobIdState
|
||||
});
|
||||
|
||||
const {
|
||||
loading: taskLoading,
|
||||
error: taskError,
|
||||
data: taskData
|
||||
} = useQuery(QUERY_GET_TASK_BY_ID, {
|
||||
variables: { id: taskId },
|
||||
skip: !taskId
|
||||
});
|
||||
// Use Effect to hydrate existing task if only a taskid is provided
|
||||
useEffect(() => {
|
||||
if (!taskLoading && !taskError && taskData && taskData?.tasks_by_pk) {
|
||||
form.setFieldsValue(taskData.tasks_by_pk);
|
||||
setSelectedJobId(taskData.tasks_by_pk.jobid);
|
||||
}
|
||||
}, [taskLoading, taskError, taskData, form]);
|
||||
|
||||
// Use Effect to hydrate selected job details
|
||||
useEffect(() => {
|
||||
if (!loading && !error && data) {
|
||||
setSelectedJobDetails(data.jobs_by_pk);
|
||||
}
|
||||
}, [loading, error, data]);
|
||||
|
||||
// Use Effect to toggle to set jobid state
|
||||
useEffect(() => {
|
||||
if (selectedJobId) {
|
||||
setJobIdState(selectedJobId);
|
||||
}
|
||||
}, [selectedJobId]);
|
||||
|
||||
// Use Effect to hydrate form fields
|
||||
useEffect(() => {
|
||||
if (jobid || existingTask?.id) {
|
||||
setSelectedJobId(jobid || existingTask.jobid);
|
||||
}
|
||||
if (existingTask && open) {
|
||||
form.setFieldsValue(existingTask);
|
||||
} else if (!existingTask && open) {
|
||||
form.resetFields();
|
||||
if (joblineid) form.setFieldsValue({ joblineid });
|
||||
if (billid) form.setFieldsValue({ billid });
|
||||
if (partsorderid) form.setFieldsValue({ partsorderid });
|
||||
}
|
||||
return () => {
|
||||
setSelectedJobId(null);
|
||||
setIsTouched(false);
|
||||
};
|
||||
}, [jobid, existingTask, form, open, joblineid, billid, partsorderid]);
|
||||
|
||||
/**
|
||||
* Remove the taskid from the URL
|
||||
*/
|
||||
const removeTaskIdFromUrl = () => {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
if (!urlParams.has("taskid")) return;
|
||||
urlParams.delete("taskid");
|
||||
history(`${window.location.pathname}?${urlParams}`);
|
||||
};
|
||||
|
||||
/**
|
||||
* Generate refetch queries
|
||||
* @param jobId
|
||||
* @returns {*[]}
|
||||
*/
|
||||
const generateRefetchQueries = (jobId) => {
|
||||
const refetchQueries = [];
|
||||
|
||||
if (location.pathname.includes("/manage/jobs") && jobId) {
|
||||
refetchQueries.push({
|
||||
query: GET_JOB_BY_PK,
|
||||
variables: {
|
||||
id: jobId
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// We have a specified query
|
||||
if (query && Object.keys(query).length) {
|
||||
refetchQueries.push(Object.keys(query)[0]);
|
||||
}
|
||||
// We don't have a specified query, check the page
|
||||
else {
|
||||
refetchRouteMappings.forEach((mapping) => {
|
||||
if (location.pathname.includes(mapping.route)) {
|
||||
refetchQueries.push(mapping.query);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return refetchQueries;
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle existing task
|
||||
* @param id
|
||||
* @param jobId
|
||||
* @param values
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
const handleExistingTask = async (id, jobId, values) => {
|
||||
const task = replaceUndefinedWithNull(values);
|
||||
|
||||
// Remind at is dirty so lets clear remind_at_sent
|
||||
if (task?.remind_at) {
|
||||
task.remind_at_sent = null;
|
||||
}
|
||||
|
||||
const taskObject = {
|
||||
variables: {
|
||||
taskId: id,
|
||||
task
|
||||
}
|
||||
};
|
||||
|
||||
taskObject.refetchQueries = generateRefetchQueries(jobId);
|
||||
|
||||
const taskData = await updateTask(taskObject);
|
||||
|
||||
if (!taskData.errors) {
|
||||
const oldTask = taskData?.data?.update_tasks?.returning[0];
|
||||
|
||||
insertAuditTrail({
|
||||
jobid: oldTask.jobid,
|
||||
operation: AuditTrailMapping.tasksUpdated(oldTask.title, currentUser.email),
|
||||
type: "tasksUpdated"
|
||||
});
|
||||
}
|
||||
|
||||
notification["success"]({
|
||||
message: t("tasks.successes.updated")
|
||||
});
|
||||
|
||||
toggleModalVisible();
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle new task
|
||||
* @param values
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
const handleNewTask = async (values) => {
|
||||
const taskObject = {
|
||||
variables: {
|
||||
taskInput: [
|
||||
{
|
||||
...values,
|
||||
created_by: currentUser.email,
|
||||
bodyshopid: bodyshop.id
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
taskObject.refetchQueries = generateRefetchQueries(values.jobid);
|
||||
|
||||
const newTaskData = await insertTask(taskObject);
|
||||
const newTask = newTaskData?.data?.insert_tasks?.returning[0];
|
||||
|
||||
if (!newTaskData.errors) {
|
||||
insertAuditTrail({
|
||||
jobid: newTask.jobid,
|
||||
operation: AuditTrailMapping.tasksCreated(newTask.title, currentUser.email),
|
||||
type: "tasksCreated"
|
||||
});
|
||||
}
|
||||
|
||||
form.resetFields();
|
||||
toggleModalVisible();
|
||||
|
||||
notification["success"]({
|
||||
message: t("tasks.successes.created")
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle the form submit
|
||||
* @param formValues
|
||||
* @returns {Promise<[{jobid, bodyshopid, created_by},...*]>}
|
||||
*/
|
||||
const handleFinish = async (formValues) => {
|
||||
if (existingTask || taskData?.tasks_by_pk) {
|
||||
const taskSource = existingTask || taskData?.tasks_by_pk;
|
||||
const dirtyValues = Object.keys(formValues).reduce((acc, key) => {
|
||||
if (!isEqual(formValues[key], taskSource[key])) {
|
||||
acc[key] = formValues[key];
|
||||
}
|
||||
return acc;
|
||||
}, {});
|
||||
try {
|
||||
await handleExistingTask(taskSource.id, taskSource.jobid, dirtyValues);
|
||||
} catch (e) {
|
||||
notification["error"]({
|
||||
message: t("tasks.failures.updated")
|
||||
});
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
await handleNewTask(formValues);
|
||||
} catch (e) {
|
||||
notification["error"]({
|
||||
message: t("tasks.failures.created")
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title={existingTask ? t("tasks.actions.edit") : t("tasks.actions.new")}
|
||||
open={open}
|
||||
okText={t("general.actions.save")}
|
||||
width="50%"
|
||||
onOk={() => {
|
||||
removeTaskIdFromUrl();
|
||||
form.submit();
|
||||
}}
|
||||
onCancel={() => {
|
||||
removeTaskIdFromUrl();
|
||||
toggleModalVisible();
|
||||
}}
|
||||
okButtonProps={{ disabled: !isTouched }}
|
||||
destroyOnClose
|
||||
>
|
||||
<Form
|
||||
form={form}
|
||||
onFinish={handleFinish}
|
||||
layout="vertical"
|
||||
onFieldsChange={() => {
|
||||
setIsTouched(true);
|
||||
}}
|
||||
>
|
||||
<TaskUpsertModalComponent
|
||||
form={form}
|
||||
loading={loading || (taskId && taskLoading)}
|
||||
error={error}
|
||||
data={data}
|
||||
existingTask={existingTask || taskData?.tasks_by_pk}
|
||||
selectedJobId={selectedJobId}
|
||||
setSelectedJobId={setSelectedJobId}
|
||||
selectedJobDetails={selectedJobDetails}
|
||||
/>
|
||||
</Form>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(TaskUpsertModalContainer);
|
||||
@@ -0,0 +1,15 @@
|
||||
import {
|
||||
QUERY_ALL_TASKS_PAGINATED,
|
||||
QUERY_JOB_TASKS_PAGINATED,
|
||||
QUERY_MY_TASKS_PAGINATED
|
||||
} from "../../graphql/tasks.queries.js";
|
||||
|
||||
const getQueryName = (query) => Object.keys(query)[0];
|
||||
|
||||
const refetchRouteMappings = [
|
||||
{query: getQueryName({QUERY_MY_TASKS_PAGINATED}), route: "/manage/tasks/mytasks"},
|
||||
{query: getQueryName({QUERY_ALL_TASKS_PAGINATED}), route: "/manage/tasks/alltasks"},
|
||||
{query: getQueryName({QUERY_JOB_TASKS_PAGINATED}), route: "/manage/jobs"}
|
||||
];
|
||||
|
||||
export default refetchRouteMappings;
|
||||
@@ -6,7 +6,8 @@ import { Link } from "react-router-dom";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||
import { alphaSort, statusSort } from "../../utils/sorters";
|
||||
import { DateTimeFormatter } from "../../utils/DateFormatter";
|
||||
import { alphaSort, dateSort, statusSort } from "../../utils/sorters";
|
||||
import OwnerNameDisplay, { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component";
|
||||
import VehicleDetailUpdateJobsComponent from "../vehicle-detail-update-jobs/vehicle-detail-update-jobs.component";
|
||||
|
||||
@@ -67,9 +68,20 @@ export function VehicleDetailJobsComponent({ vehicle, bodyshop }) {
|
||||
text: status,
|
||||
value: status
|
||||
})),
|
||||
onFilter: (value, record) => value.includes(record.status)
|
||||
onFilter: (value, record) => value.includes(record.status),
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.actual_completion"),
|
||||
dataIndex: "actual_completion",
|
||||
key: "actual_completion",
|
||||
render: (text, record) => (
|
||||
<DateTimeFormatter>{record.actual_completion}</DateTimeFormatter>
|
||||
),
|
||||
sorter: (a, b) => dateSort(a.actual_completion, b.actual_completion),
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "actual_completion" &&
|
||||
state.sortedInfo.order,
|
||||
},
|
||||
|
||||
{
|
||||
title: t("jobs.fields.clm_total"),
|
||||
dataIndex: "clm_total",
|
||||
|
||||
@@ -59,9 +59,14 @@ export const QUERY_BILLS_BY_JOBID = gql`
|
||||
name
|
||||
email
|
||||
}
|
||||
tasks {
|
||||
id
|
||||
due_date
|
||||
}
|
||||
order_date
|
||||
deliver_by
|
||||
return
|
||||
returnfrombill
|
||||
orderedby
|
||||
parts_order_lines {
|
||||
id
|
||||
|
||||
@@ -67,6 +67,24 @@ export const QUERY_ACTIVE_EMPLOYEES = gql`
|
||||
}
|
||||
`;
|
||||
|
||||
export const QUERY_ACTIVE_EMPLOYEES_WITH_EMAIL = gql`
|
||||
query QUERY_ACTIVE_EMPLOYEES_WITH_EMAIL {
|
||||
employees(where: {active: {_eq: true}, user_email: {_is_null: false}}) {
|
||||
last_name
|
||||
id
|
||||
first_name
|
||||
employee_number
|
||||
active
|
||||
termination_date
|
||||
hire_date
|
||||
flat_rate
|
||||
rates
|
||||
pin
|
||||
user_email
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const INSERT_EMPLOYEES = gql`
|
||||
mutation INSERT_EMPLOYEES($employees: [employees_insert_input!]!) {
|
||||
insert_employees(objects: $employees) {
|
||||
|
||||
@@ -219,6 +219,12 @@ export const UPDATE_JOB_LINE = gql`
|
||||
id
|
||||
notes
|
||||
mod_lbr_ty
|
||||
mod_lb_hrs
|
||||
part_type
|
||||
op_code_desc
|
||||
prt_dsmk_m
|
||||
prt_dsmk_p
|
||||
tax_part
|
||||
part_qty
|
||||
db_price
|
||||
act_price
|
||||
|
||||
@@ -499,6 +499,11 @@ export const QUERY_JOB_COSTING_DETAILS = gql`
|
||||
export const GET_JOB_BY_PK = gql`
|
||||
query GET_JOB_BY_PK($id: uuid!) {
|
||||
jobs_by_pk(id: $id) {
|
||||
tasks_aggregate(where: { completed: { _eq: false }, deleted: { _eq: false } }) {
|
||||
aggregate {
|
||||
count
|
||||
}
|
||||
}
|
||||
actual_completion
|
||||
actual_delivery
|
||||
actual_in
|
||||
@@ -900,7 +905,7 @@ export const QUERY_JOB_CARD_DETAILS = gql`
|
||||
}
|
||||
|
||||
joblines(
|
||||
where: { removed: { _eq: false }, part_type: { _is_null: false, _nin: ["PAE", "PAS", "PASL"] } }
|
||||
where: { removed: { _eq: false } }
|
||||
order_by: { line_no: asc }
|
||||
) {
|
||||
id
|
||||
@@ -1144,6 +1149,7 @@ export const UPDATE_JOB = gql`
|
||||
suspended
|
||||
queued_for_parts
|
||||
scheduled_completion
|
||||
scheduled_delivery
|
||||
actual_in
|
||||
date_repairstarted
|
||||
date_void
|
||||
@@ -1969,19 +1975,19 @@ export const QUERY_JOB_CLOSE_DETAILS = gql`
|
||||
kmout
|
||||
qb_multiple_payers
|
||||
lbr_adjustments
|
||||
payments {
|
||||
amount
|
||||
created_at
|
||||
date
|
||||
exportedat
|
||||
id
|
||||
jobid
|
||||
memo
|
||||
payer
|
||||
paymentnum
|
||||
transactionid
|
||||
type
|
||||
}
|
||||
payments {
|
||||
amount
|
||||
created_at
|
||||
date
|
||||
exportedat
|
||||
id
|
||||
jobid
|
||||
memo
|
||||
payer
|
||||
paymentnum
|
||||
transactionid
|
||||
type
|
||||
}
|
||||
joblines(where: { removed: { _eq: false } }, order_by: { line_no: asc }) {
|
||||
id
|
||||
removed
|
||||
@@ -1994,7 +2000,7 @@ export const QUERY_JOB_CLOSE_DETAILS = gql`
|
||||
db_price
|
||||
act_price
|
||||
part_qty
|
||||
notes
|
||||
notes
|
||||
mod_lbr_ty
|
||||
db_hrs
|
||||
mod_lb_hrs
|
||||
@@ -2006,9 +2012,9 @@ export const QUERY_JOB_CLOSE_DETAILS = gql`
|
||||
prt_dsmk_p
|
||||
convertedtolbr
|
||||
convertedtolbr_data
|
||||
act_price_before_ppc
|
||||
sublet_ignored
|
||||
sublet_completed
|
||||
act_price_before_ppc
|
||||
sublet_ignored
|
||||
sublet_completed
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2056,12 +2062,12 @@ export const generate_UPDATE_JOB_KANBAN = (
|
||||
}`;
|
||||
|
||||
return gql`
|
||||
mutation UPDATE_JOB_KANBAN {
|
||||
${oldChildId ? oldChildQuery : ""}
|
||||
${movedId ? movedQuery : ""}
|
||||
${newChildId ? newChildQuery : ""}
|
||||
}
|
||||
`;
|
||||
mutation UPDATE_JOB_KANBAN {
|
||||
${oldChildId ? oldChildQuery : ""}
|
||||
${movedId ? movedQuery : ""}
|
||||
${newChildId ? newChildQuery : ""}
|
||||
}
|
||||
`;
|
||||
};
|
||||
|
||||
export const QUERY_JOB_LBR_ADJUSTMENTS = gql`
|
||||
@@ -2081,6 +2087,34 @@ export const DELETE_JOB = gql`
|
||||
}
|
||||
`;
|
||||
|
||||
export const QUERY_GET_TASKS_JOB_DETAILS_BY_ID = gql`
|
||||
query QUERY_GET_TASKS_JOB_DETAILS_BY_ID($id: uuid!) {
|
||||
jobs_by_pk(id: $id) {
|
||||
id
|
||||
scheduled_delivery
|
||||
scheduled_completion
|
||||
joblines {
|
||||
id
|
||||
line_desc
|
||||
}
|
||||
bills {
|
||||
id
|
||||
vendor {
|
||||
name
|
||||
}
|
||||
invoice_number
|
||||
}
|
||||
parts_orders {
|
||||
id
|
||||
vendor {
|
||||
name
|
||||
}
|
||||
order_number
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const GET_JOB_FOR_CC_CONTRACT = gql`
|
||||
query GET_JOB_FOR_CC_CONTRACT($id: uuid!) {
|
||||
jobs_by_pk(id: $id) {
|
||||
@@ -2238,6 +2272,8 @@ export const GET_JOB_LINE_ORDERS = gql`
|
||||
parts_order_lines(where: { job_line_id: { _eq: $joblineid } }) {
|
||||
id
|
||||
act_price
|
||||
backordered_eta
|
||||
backordered_on
|
||||
parts_order {
|
||||
id
|
||||
order_date
|
||||
|
||||
@@ -67,6 +67,7 @@ export const QUERY_OWNER_BY_ID = gql`
|
||||
tax_number
|
||||
jobs(order_by: { date_open: desc }) {
|
||||
id
|
||||
actual_completion
|
||||
ro_number
|
||||
clm_no
|
||||
status
|
||||
|
||||
383
client/src/graphql/tasks.queries.js
Normal file
383
client/src/graphql/tasks.queries.js
Normal file
@@ -0,0 +1,383 @@
|
||||
import { gql } from "@apollo/client";
|
||||
|
||||
export const PARTIAL_TASK_FIELDS = gql`
|
||||
fragment TaskFields on tasks {
|
||||
id
|
||||
created_at
|
||||
updated_at
|
||||
title
|
||||
description
|
||||
deleted
|
||||
deleted_at
|
||||
due_date
|
||||
created_by
|
||||
assigned_to
|
||||
assigned_to_employee {
|
||||
id
|
||||
user_email
|
||||
}
|
||||
completed
|
||||
completed_at
|
||||
remind_at
|
||||
priority
|
||||
job {
|
||||
id
|
||||
ro_number
|
||||
joblines {
|
||||
id
|
||||
line_desc
|
||||
}
|
||||
bills {
|
||||
id
|
||||
vendor {
|
||||
name
|
||||
}
|
||||
invoice_number
|
||||
}
|
||||
parts_orders {
|
||||
id
|
||||
vendor {
|
||||
name
|
||||
}
|
||||
order_number
|
||||
}
|
||||
}
|
||||
jobid
|
||||
jobline {
|
||||
id
|
||||
line_desc
|
||||
}
|
||||
joblineid
|
||||
parts_order {
|
||||
id
|
||||
vendor {
|
||||
name
|
||||
}
|
||||
order_number
|
||||
}
|
||||
partsorderid
|
||||
bill {
|
||||
id
|
||||
vendor {
|
||||
name
|
||||
}
|
||||
invoice_number
|
||||
}
|
||||
billid
|
||||
}
|
||||
`;
|
||||
|
||||
export const QUERY_GET_TASK_BY_ID = gql`
|
||||
${PARTIAL_TASK_FIELDS}
|
||||
query QUERY_GET_TASK_BY_ID($id: uuid!) {
|
||||
tasks_by_pk(id: $id) {
|
||||
...TaskFields
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const QUERY_ALL_TASKS_PAGINATED = gql`
|
||||
${PARTIAL_TASK_FIELDS}
|
||||
query QUERY_ALL_TASKS_PAGINATED(
|
||||
$offset: Int
|
||||
$limit: Int
|
||||
$bodyshop: uuid!
|
||||
$deleted: Boolean
|
||||
$completed: Boolean
|
||||
$assigned_to: uuid
|
||||
$order: [tasks_order_by!]!
|
||||
) {
|
||||
tasks(
|
||||
offset: $offset
|
||||
limit: $limit
|
||||
order_by: $order
|
||||
where: {
|
||||
bodyshopid: { _eq: $bodyshop }
|
||||
deleted: { _eq: $deleted }
|
||||
assigned_to: { _eq: $assigned_to }
|
||||
completed: { _eq: $completed }
|
||||
}
|
||||
) {
|
||||
...TaskFields
|
||||
}
|
||||
tasks_aggregate(
|
||||
where: {
|
||||
bodyshopid: { _eq: $bodyshop }
|
||||
deleted: { _eq: $deleted }
|
||||
assigned_to: { _eq: $assigned_to }
|
||||
completed: { _eq: $completed }
|
||||
}
|
||||
) {
|
||||
aggregate {
|
||||
count
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
// Query for joblineid
|
||||
export const QUERY_JOBLINE_TASKS_PAGINATED = gql`
|
||||
${PARTIAL_TASK_FIELDS}
|
||||
query QUERY_JOBLINE_TASKS_PAGINATED(
|
||||
$offset: Int
|
||||
$limit: Int
|
||||
$joblineid: uuid!
|
||||
$bodyshop: uuid!
|
||||
$deleted: Boolean
|
||||
$completed: Boolean
|
||||
$assigned_to: uuid
|
||||
$order: [tasks_order_by!]!
|
||||
) {
|
||||
tasks(
|
||||
offset: $offset
|
||||
limit: $limit
|
||||
order_by: $order
|
||||
where: {
|
||||
joblineid: { _eq: $joblineid }
|
||||
bodyshopid: { _eq: $bodyshop }
|
||||
deleted: { _eq: $deleted }
|
||||
assigned_to: { _eq: $assigned_to }
|
||||
completed: { _eq: $completed }
|
||||
}
|
||||
) {
|
||||
...TaskFields
|
||||
}
|
||||
tasks_aggregate(
|
||||
where: {
|
||||
joblineid: { _eq: $joblineid }
|
||||
bodyshopid: { _eq: $bodyshop }
|
||||
deleted: { _eq: $deleted }
|
||||
assigned_to: { _eq: $assigned_to }
|
||||
completed: { _eq: $completed }
|
||||
}
|
||||
) {
|
||||
aggregate {
|
||||
count
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
// Query for partsorderid
|
||||
export const QUERY_PARTSORDER_TASKS_PAGINATED = gql`
|
||||
${PARTIAL_TASK_FIELDS}
|
||||
query QUERY_PARTSORDER_TASKS_PAGINATED(
|
||||
$offset: Int
|
||||
$limit: Int
|
||||
$partsorderid: uuid!
|
||||
$bodyshop: uuid!
|
||||
$deleted: Boolean
|
||||
$completed: Boolean
|
||||
$assigned_to: uuid
|
||||
$order: [tasks_order_by!]!
|
||||
) {
|
||||
tasks(
|
||||
offset: $offset
|
||||
limit: $limit
|
||||
order_by: $order
|
||||
where: {
|
||||
partsorderid: { _eq: $partsorderid }
|
||||
bodyshopid: { _eq: $bodyshop }
|
||||
deleted: { _eq: $deleted }
|
||||
assigned_to: { _eq: $assigned_to }
|
||||
completed: { _eq: $completed }
|
||||
}
|
||||
) {
|
||||
...TaskFields
|
||||
}
|
||||
tasks_aggregate(
|
||||
where: {
|
||||
partsorderid: { _eq: $partsorderid }
|
||||
bodyshopid: { _eq: $bodyshop }
|
||||
deleted: { _eq: $deleted }
|
||||
assigned_to: { _eq: $assigned_to }
|
||||
completed: { _eq: $completed }
|
||||
}
|
||||
) {
|
||||
aggregate {
|
||||
count
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
// Query for billid
|
||||
export const QUERY_BILL_TASKS_PAGINATED = gql`
|
||||
${PARTIAL_TASK_FIELDS}
|
||||
query QUERY_BILL_TASKS_PAGINATED(
|
||||
$offset: Int
|
||||
$limit: Int
|
||||
$billid: uuid!
|
||||
$bodyshop: uuid!
|
||||
$deleted: Boolean
|
||||
$completed: Boolean
|
||||
$assigned_to: uuid
|
||||
$order: [tasks_order_by!]!
|
||||
) {
|
||||
tasks(
|
||||
offset: $offset
|
||||
limit: $limit
|
||||
order_by: $order
|
||||
where: {
|
||||
billid: { _eq: $billid }
|
||||
bodyshopid: { _eq: $bodyshop }
|
||||
deleted: { _eq: $deleted }
|
||||
assigned_to: { _eq: $assigned_to }
|
||||
completed: { _eq: $completed }
|
||||
}
|
||||
) {
|
||||
...TaskFields
|
||||
}
|
||||
tasks_aggregate(
|
||||
where: {
|
||||
billid: { _eq: $billid }
|
||||
bodyshopid: { _eq: $bodyshop }
|
||||
deleted: { _eq: $deleted }
|
||||
assigned_to: { _eq: $assigned_to }
|
||||
completed: { _eq: $completed }
|
||||
}
|
||||
) {
|
||||
aggregate {
|
||||
count
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
// Use the fragment in your queries
|
||||
export const QUERY_JOB_TASKS_PAGINATED = gql`
|
||||
${PARTIAL_TASK_FIELDS}
|
||||
query QUERY_JOB_TASKS_PAGINATED(
|
||||
$offset: Int
|
||||
$limit: Int
|
||||
$jobid: uuid!
|
||||
$bodyshop: uuid!
|
||||
$deleted: Boolean
|
||||
$completed: Boolean
|
||||
$assigned_to: uuid
|
||||
$order: [tasks_order_by!]!
|
||||
) {
|
||||
tasks(
|
||||
offset: $offset
|
||||
limit: $limit
|
||||
order_by: $order
|
||||
where: {
|
||||
jobid: { _eq: $jobid }
|
||||
bodyshopid: { _eq: $bodyshop }
|
||||
deleted: { _eq: $deleted }
|
||||
assigned_to: { _eq: $assigned_to }
|
||||
completed: { _eq: $completed }
|
||||
}
|
||||
) {
|
||||
...TaskFields
|
||||
}
|
||||
tasks_aggregate(
|
||||
where: {
|
||||
jobid: { _eq: $jobid }
|
||||
bodyshopid: { _eq: $bodyshop }
|
||||
deleted: { _eq: $deleted }
|
||||
assigned_to: { _eq: $assigned_to }
|
||||
completed: { _eq: $completed }
|
||||
}
|
||||
) {
|
||||
aggregate {
|
||||
count
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const QUERY_MY_TASKS_PAGINATED = gql`
|
||||
${PARTIAL_TASK_FIELDS}
|
||||
query QUERY_MY_TASKS_PAGINATED(
|
||||
$offset: Int
|
||||
$limit: Int
|
||||
$assigned_to: uuid!
|
||||
$bodyshop: uuid!
|
||||
$deleted: Boolean
|
||||
$completed: Boolean
|
||||
$order: [tasks_order_by!]!
|
||||
) {
|
||||
tasks(
|
||||
offset: $offset
|
||||
limit: $limit
|
||||
order_by: $order
|
||||
where: {
|
||||
assigned_to: { _eq: $assigned_to }
|
||||
bodyshopid: { _eq: $bodyshop }
|
||||
deleted: { _eq: $deleted }
|
||||
completed: { _eq: $completed }
|
||||
}
|
||||
) {
|
||||
...TaskFields
|
||||
}
|
||||
tasks_aggregate(
|
||||
where: {
|
||||
assigned_to: { _eq: $assigned_to }
|
||||
bodyshopid: { _eq: $bodyshop }
|
||||
deleted: { _eq: $deleted }
|
||||
completed: { _eq: $completed }
|
||||
}
|
||||
) {
|
||||
aggregate {
|
||||
count
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
/**
|
||||
* Toggle task completed mutation
|
||||
* @type {DocumentNode}
|
||||
*/
|
||||
export const MUTATION_TOGGLE_TASK_COMPLETED = gql`
|
||||
${PARTIAL_TASK_FIELDS}
|
||||
mutation MUTATION_TOGGLE_TASK_COMPLETED($id: uuid!, $completed: Boolean!, $completed_at: timestamptz) {
|
||||
update_tasks_by_pk(pk_columns: { id: $id }, _set: { completed: $completed, completed_at: $completed_at }) {
|
||||
...TaskFields
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
/**
|
||||
* Toggle task deleted mutation
|
||||
* @type {DocumentNode}
|
||||
*/
|
||||
export const MUTATION_TOGGLE_TASK_DELETED = gql`
|
||||
${PARTIAL_TASK_FIELDS}
|
||||
mutation MUTATION_TOGGLE_TASK_DELETED($id: uuid!, $deleted: Boolean!, $deleted_at: timestamptz) {
|
||||
update_tasks_by_pk(pk_columns: { id: $id }, _set: { deleted: $deleted, deleted_at: $deleted_at }) {
|
||||
...TaskFields
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
/**
|
||||
* Insert new task mutation
|
||||
* @type {DocumentNode}
|
||||
*/
|
||||
export const MUTATION_INSERT_NEW_TASK = gql`
|
||||
${PARTIAL_TASK_FIELDS}
|
||||
mutation MUTATION_INSERT_NEW_TASK($taskInput: [tasks_insert_input!]!) {
|
||||
insert_tasks(objects: $taskInput) {
|
||||
returning {
|
||||
...TaskFields
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
/**
|
||||
* Update task mutation
|
||||
* @type {DocumentNode}
|
||||
*/
|
||||
export const MUTATION_UPDATE_TASK = gql`
|
||||
${PARTIAL_TASK_FIELDS}
|
||||
mutation MUTATION_UPDATE_TASK($taskId: uuid!, $task: tasks_set_input!) {
|
||||
update_tasks(where: { id: { _eq: $taskId } }, _set: $task) {
|
||||
returning {
|
||||
...TaskFields
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
@@ -30,6 +30,7 @@ export const QUERY_VEHICLE_BY_ID = gql`
|
||||
notes
|
||||
jobs(order_by: { date_open: desc }) {
|
||||
id
|
||||
actual_completion
|
||||
ro_number
|
||||
ownr_co_nm
|
||||
ownr_fn
|
||||
|
||||
@@ -51,6 +51,17 @@ export function JobsAvailablePageContainer({ partnerVersion, setBreadcrumbs, set
|
||||
{!partnerVersion && (
|
||||
<AlertComponent
|
||||
type="warning"
|
||||
action={
|
||||
<a
|
||||
href={InstanceRenderManager({
|
||||
imex: "https://partner.imex.online/Setup.exe",
|
||||
rome: "https://partner.romeonline.io/Setup.exe",
|
||||
promanager: "https://dzaenazwrgg60.cloudfront.net/Setup.exe"
|
||||
})}
|
||||
>
|
||||
<Button size="small">{t("general.actions.download")}</Button>
|
||||
</a>
|
||||
}
|
||||
message={t("general.messages.partnernotrunning", {
|
||||
app: InstanceRenderManager({
|
||||
imex: "$t(titles.imexonline)",
|
||||
|
||||
@@ -41,7 +41,7 @@ import { insertAuditTrail } from "../../redux/application/application.actions";
|
||||
import { selectJobReadOnly } from "../../redux/application/application.selectors";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
||||
import JobCloseRoGuardContainer from '../../components/job-close-ro-guard/job-close-ro-guard.container';
|
||||
import JobCloseRoGuardContainer from "../../components/job-close-ro-guard/job-close-ro-guard.container";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
@@ -120,6 +120,13 @@ export function JobsCloseComponent({ job, bodyshop, jobRO, insertAuditTrail }) {
|
||||
operation: AuditTrailMapping.jobinvoiced(),
|
||||
type: "jobinvoiced"
|
||||
});
|
||||
if (values.masterbypass) {
|
||||
insertAuditTrail({
|
||||
jobid: job.id,
|
||||
operation: AuditTrailMapping.jobclosedwithbypass(),
|
||||
type: "jobclosedwithbypass"
|
||||
});
|
||||
}
|
||||
// history(`/manage/jobs/${job.id}`);
|
||||
} else {
|
||||
setLoading(false);
|
||||
@@ -184,7 +191,7 @@ export function JobsCloseComponent({ job, bodyshop, jobRO, insertAuditTrail }) {
|
||||
</Space>
|
||||
}
|
||||
/>
|
||||
<JobCloseRoGuardContainer form={form} job={job} />
|
||||
<JobCloseRoGuardContainer form={form} job={job} />
|
||||
<Space wrap direction="vertical" style={{ width: "100%" }}>
|
||||
<FormsFieldChanged form={form} />
|
||||
{!job.actual_in && job.scheduled_in && <Alert type="warning" message={t("jobs.labels.actual_in_inferred")} />}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Button, Result, Space, Steps } from "antd";
|
||||
import { PageHeader } from "@ant-design/pro-layout";
|
||||
import { Button, Result, Space, Steps } from "antd";
|
||||
|
||||
import React, { useContext, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
@@ -9,7 +9,6 @@ import JobsCreateJobsInfo from "../../components/jobs-create-jobs-info/jobs-crea
|
||||
import JobsCreateOwnerInfoContainer from "../../components/jobs-create-owner-info/jobs-create-owner-info.container";
|
||||
import JobsCreateVehicleInfoContainer from "../../components/jobs-create-vehicle-info/jobs-create-vehicle-info.container";
|
||||
import JobCreateContext from "../../pages/jobs-create/jobs-create.context";
|
||||
import FormsFieldChanged from "../../components/form-fields-changed-alert/form-fields-changed-alert.component";
|
||||
|
||||
export default function JobsCreateComponent({ form }) {
|
||||
const [pageIndex, setPageIndex] = useState(0);
|
||||
@@ -20,28 +19,32 @@ export default function JobsCreateComponent({ form }) {
|
||||
const steps = [
|
||||
{
|
||||
title: t("jobs.labels.create.vehicleinfo"),
|
||||
id: "step-job-vehicleinfo",
|
||||
content: <JobsCreateVehicleInfoContainer form={form} />,
|
||||
validation: !!state.vehicle.new || !!state.vehicle.selectedid || !!state.vehicle.none,
|
||||
error: t("vehicles.errors.selectexistingornew")
|
||||
},
|
||||
{
|
||||
title: t("jobs.labels.create.ownerinfo"),
|
||||
id: "step-job-ownerinfo",
|
||||
content: <JobsCreateOwnerInfoContainer />,
|
||||
validation: !!state.owner.new || !!state.owner.selectedid,
|
||||
error: t("owners.errors.selectexistingornew")
|
||||
},
|
||||
{
|
||||
title: t("jobs.labels.create.jobinfo"),
|
||||
id: "step-job-jobinfo",
|
||||
content: <JobsCreateJobsInfo form={form} selected={pageIndex === 2} />
|
||||
}
|
||||
];
|
||||
|
||||
const next = () => {
|
||||
setPageIndex(pageIndex + 1);
|
||||
console.log("NExt");
|
||||
console.log("Next");
|
||||
};
|
||||
const prev = () => {
|
||||
setPageIndex(pageIndex - 1);
|
||||
console.log("Previous");
|
||||
};
|
||||
const { Step } = Steps;
|
||||
|
||||
@@ -50,26 +53,26 @@ export default function JobsCreateComponent({ form }) {
|
||||
<PageHeader
|
||||
extra={
|
||||
<Space wrap>
|
||||
{pageIndex > 0 && <Button onClick={() => prev()}>Previous</Button>}
|
||||
{pageIndex > 0 && <Button onClick={() => prev()}>{t("general.actions.previous")}</Button>}
|
||||
{pageIndex < steps.length - 1 && (
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={() => {
|
||||
next();
|
||||
// form
|
||||
// .validateFields()
|
||||
// .then((r) => {
|
||||
// if (steps[pageIndex].validation) {
|
||||
// setErrorMessage(null);
|
||||
// next();
|
||||
// } else {
|
||||
// setErrorMessage(steps[pageIndex].error);
|
||||
// }
|
||||
// })
|
||||
// .catch((error) => console.log("error", error));
|
||||
form
|
||||
.validateFields()
|
||||
.then((r) => {
|
||||
if (steps[pageIndex].validation) {
|
||||
setErrorMessage(null);
|
||||
next();
|
||||
} else {
|
||||
setErrorMessage(steps[pageIndex].error);
|
||||
}
|
||||
})
|
||||
.catch((error) => console.log("error", error));
|
||||
}}
|
||||
>
|
||||
Next
|
||||
{t("general.actions.next")}
|
||||
</Button>
|
||||
)}
|
||||
{pageIndex === steps.length - 1 && (
|
||||
@@ -101,17 +104,17 @@ export default function JobsCreateComponent({ form }) {
|
||||
}}
|
||||
onClick={() => {
|
||||
setPageIndex(idx);
|
||||
// form
|
||||
// .validateFields()
|
||||
// .then((r) => {
|
||||
// if (steps[pageIndex].validation) {
|
||||
// setErrorMessage(null);
|
||||
// setPageIndex(idx);
|
||||
// } else {
|
||||
// setErrorMessage(steps[pageIndex].error);
|
||||
// }
|
||||
// })
|
||||
// .catch((error) => console.log("error", error));
|
||||
form
|
||||
.validateFields()
|
||||
.then((r) => {
|
||||
if (steps[pageIndex].validation) {
|
||||
setErrorMessage(null);
|
||||
setPageIndex(idx);
|
||||
} else {
|
||||
setErrorMessage(steps[pageIndex].error);
|
||||
}
|
||||
})
|
||||
.catch((error) => console.log("error", error));
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
@@ -141,7 +144,7 @@ export default function JobsCreateComponent({ form }) {
|
||||
) : (
|
||||
<div>
|
||||
<ProgressButtons top />
|
||||
<FormsFieldChanged form={form} />
|
||||
|
||||
{errorMessage ? (
|
||||
<div>
|
||||
<AlertComponent message={errorMessage} type="error" />
|
||||
|
||||
@@ -8,7 +8,7 @@ import Icon, {
|
||||
SyncOutlined,
|
||||
ToolFilled
|
||||
} from "@ant-design/icons";
|
||||
import { Button, Divider, Form, notification, Space, Tabs } from "antd";
|
||||
import { Badge, Button, Divider, Form, notification, Space, Tabs } from "antd";
|
||||
import { PageHeader } from "@ant-design/pro-layout";
|
||||
|
||||
import Axios from "axios";
|
||||
@@ -16,7 +16,7 @@ import dayjs from "../../utils/day";
|
||||
import queryString from "query-string";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { FaHardHat, FaRegStickyNote, FaShieldAlt } from "react-icons/fa";
|
||||
import { FaHardHat, FaRegStickyNote, FaShieldAlt, FaTasks } from "react-icons/fa";
|
||||
import { connect } from "react-redux";
|
||||
import { useLocation, useNavigate } from "react-router-dom";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
@@ -53,14 +53,29 @@ import JobProfileDataWarning from "../../components/job-profile-data-warning/job
|
||||
import { DateTimeFormat } from "../../utils/DateFormatter";
|
||||
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
||||
import { HasFeatureAccess } from "../../components/feature-wrapper/feature-wrapper.component";
|
||||
import TaskListContainer from "../../components/task-list/task-list.container.jsx";
|
||||
import { QUERY_JOB_TASKS_PAGINATED } from "../../graphql/tasks.queries.js";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
jobRO: selectJobReadOnly
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
setPrintCenterContext: (context) => dispatch(setModalContext({ context: context, modal: "printCenter" })),
|
||||
insertAuditTrail: ({ jobid, operation, type }) => dispatch(insertAuditTrail({ jobid, operation, type }))
|
||||
setPrintCenterContext: (context) =>
|
||||
dispatch(
|
||||
setModalContext({
|
||||
context: context,
|
||||
modal: "printCenter"
|
||||
})
|
||||
),
|
||||
insertAuditTrail: ({ jobid, operation, type }) =>
|
||||
dispatch(
|
||||
insertAuditTrail({
|
||||
jobid,
|
||||
operation,
|
||||
type
|
||||
})
|
||||
)
|
||||
});
|
||||
|
||||
export function JobsDetailPage({
|
||||
@@ -69,7 +84,6 @@ export function JobsDetailPage({
|
||||
jobRO,
|
||||
job,
|
||||
mutationUpdateJob,
|
||||
handleSubmit,
|
||||
insertAuditTrail,
|
||||
refetch
|
||||
}) {
|
||||
@@ -277,6 +291,7 @@ export function JobsDetailPage({
|
||||
{
|
||||
key: "general",
|
||||
icon: <Icon component={FaShieldAlt} />,
|
||||
id: "job-details-general",
|
||||
label: t("menus.jobsdetail.general"),
|
||||
forceRender: true,
|
||||
children: <JobsDetailGeneral job={job} form={form} />
|
||||
@@ -284,6 +299,7 @@ export function JobsDetailPage({
|
||||
{
|
||||
key: "repairdata",
|
||||
icon: <BarsOutlined />,
|
||||
id: "job-details-repairdata",
|
||||
label: t("menus.jobsdetail.repairdata"),
|
||||
forceRender: true,
|
||||
children: <JobsLinesContainer job={job} joblines={job.joblines} refetch={refetch} form={form} />
|
||||
@@ -291,18 +307,21 @@ export function JobsDetailPage({
|
||||
{
|
||||
key: "rates",
|
||||
icon: <DollarCircleOutlined />,
|
||||
id: "job-details-rates",
|
||||
label: t("menus.jobsdetail.rates"),
|
||||
forceRender: true,
|
||||
children: <JobsDetailRates job={job} form={form} />
|
||||
},
|
||||
{
|
||||
key: "totals",
|
||||
id: "job-details-totals",
|
||||
icon: <DollarCircleOutlined />,
|
||||
label: t("menus.jobsdetail.totals"),
|
||||
children: <JobsDetailTotals job={job} refetch={refetch} />
|
||||
},
|
||||
{
|
||||
key: "partssublet",
|
||||
id: "job-details-partssublet",
|
||||
icon: <ToolFilled />,
|
||||
label: HasFeatureAccess({ featureName: "bills", bodyshop })
|
||||
? t("menus.jobsdetail.partssublet")
|
||||
@@ -317,6 +336,7 @@ export function JobsDetailPage({
|
||||
? [
|
||||
{
|
||||
key: "labor",
|
||||
id: "job-details-labor",
|
||||
icon: <Icon component={FaHardHat} />,
|
||||
label: t("menus.jobsdetail.labor"),
|
||||
children: <JobsDetailLaborContainer job={job} jobId={job.id} />
|
||||
@@ -326,11 +346,13 @@ export function JobsDetailPage({
|
||||
{
|
||||
key: "lifecycle",
|
||||
icon: <BarsOutlined />,
|
||||
id: "job-details-lifecycle",
|
||||
label: t("menus.jobsdetail.lifecycle"),
|
||||
children: <JobLifecycleComponent job={job} statuses={bodyshop.md_ro_statuses} />
|
||||
},
|
||||
{
|
||||
key: "dates",
|
||||
id: "job-details-dates",
|
||||
icon: <CalendarFilled />,
|
||||
label: t("menus.jobsdetail.dates"),
|
||||
forceRender: true,
|
||||
@@ -344,6 +366,7 @@ export function JobsDetailPage({
|
||||
? [
|
||||
{
|
||||
key: "documents",
|
||||
id: "job-details-documents",
|
||||
icon: <FileImageFilled />,
|
||||
label: t("jobs.labels.documents"),
|
||||
children: bodyshop.uselocalmediaserver ? (
|
||||
@@ -356,6 +379,7 @@ export function JobsDetailPage({
|
||||
: []),
|
||||
{
|
||||
key: "notes",
|
||||
id: "job-details-notes",
|
||||
icon: <Icon component={FaRegStickyNote} />,
|
||||
label: t("jobs.labels.notes"),
|
||||
children: <JobNotesContainer jobId={job.id} />
|
||||
@@ -363,8 +387,29 @@ export function JobsDetailPage({
|
||||
{
|
||||
key: "audit",
|
||||
icon: <HistoryOutlined />,
|
||||
id: "job-details-audit",
|
||||
label: t("jobs.labels.audit"),
|
||||
children: <JobAuditTrail jobId={job.id} />
|
||||
},
|
||||
{
|
||||
key: "tasks",
|
||||
icon: <FaTasks />,
|
||||
id: "job-details-tasks",
|
||||
label: (
|
||||
<Space direction="horizontal">
|
||||
{t("jobs.labels.tasks")}
|
||||
{job.tasks_aggregate.aggregate.count > 0 && <Badge count={job.tasks_aggregate.aggregate.count} />}
|
||||
</Space>
|
||||
),
|
||||
children: (
|
||||
<TaskListContainer
|
||||
relationshipType={"jobid"}
|
||||
relationshipId={job.id}
|
||||
query={{ QUERY_JOB_TASKS_PAGINATED }}
|
||||
titleTranslation="tasks.titles.job_tasks"
|
||||
showRo={false}
|
||||
/>
|
||||
)
|
||||
}
|
||||
]}
|
||||
/>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { FloatButton, Layout, Spin, Collapse, Button, Space, Tag } from "antd";
|
||||
import { FloatButton, Layout, Spin } from "antd";
|
||||
// import preval from "preval.macro";
|
||||
import React, { lazy, Suspense, useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
@@ -30,8 +30,8 @@ import "./manage.page.styles.scss";
|
||||
|
||||
const JobsPage = lazy(() => import("../jobs/jobs.page"));
|
||||
|
||||
const CardPaymentModalContainer = lazy(() =>
|
||||
import("../../components/card-payment-modal/card-payment-modal.container.")
|
||||
const CardPaymentModalContainer = lazy(
|
||||
() => import("../../components/card-payment-modal/card-payment-modal.container.")
|
||||
);
|
||||
|
||||
const JobsDetailPage = lazy(() => import("../jobs-detail/jobs-detail.page.container"));
|
||||
@@ -59,8 +59,8 @@ const JobCostingModal = lazy(() => import("../../components/job-costing-modal/jo
|
||||
const ReportCenterModal = lazy(() => import("../../components/report-center-modal/report-center-modal.container"));
|
||||
const BillEnterModalContainer = lazy(() => import("../../components/bill-enter-modal/bill-enter-modal.container"));
|
||||
const TimeTicketModalContainer = lazy(() => import("../../components/time-ticket-modal/time-ticket-modal.container"));
|
||||
const TimeTicketModalTask = lazy(() =>
|
||||
import("../../components/time-ticket-task-modal/time-ticket-task-modal.container")
|
||||
const TimeTicketModalTask = lazy(
|
||||
() => import("../../components/time-ticket-task-modal/time-ticket-task-modal.container")
|
||||
);
|
||||
const PaymentModalContainer = lazy(() => import("../../components/payment-modal/payment-modal.container"));
|
||||
const ProductionListPage = lazy(() => import("../production-list/production-list.container"));
|
||||
@@ -97,7 +97,10 @@ const Dms = lazy(() => import("../dms/dms.container"));
|
||||
const DmsPayables = lazy(() => import("../dms-payables/dms-payables.container"));
|
||||
const ManageRootPage = lazy(() => import("../manage-root/manage-root.page.container"));
|
||||
const TtApprovals = lazy(() => import("../tt-approvals/tt-approvals.page.container"));
|
||||
const MyTasksPage = lazy(() => import("../tasks/myTasksPageContainer.jsx"));
|
||||
const AllTasksPage = lazy(() => import("../tasks/allTasksPageContainer.jsx"));
|
||||
|
||||
const TaskUpsertModalContainer = lazy(() => import("../../components/task-upsert-modal/task-upsert-modal.container"));
|
||||
const { Content, Footer } = Layout;
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
@@ -114,7 +117,6 @@ const mapDispatchToProps = (dispatch) => ({
|
||||
export function Manage({ conflict, bodyshop, enableJoyRide, joyRideSteps, setJoyRideFinished }) {
|
||||
const { t } = useTranslation();
|
||||
const [chatVisible] = useState(false);
|
||||
const [tours, setTours] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
const widgetId = InstanceRenderManager({
|
||||
@@ -147,12 +149,10 @@ export function Manage({ conflict, bodyshop, enableJoyRide, joyRideSteps, setJoy
|
||||
})}
|
||||
/>
|
||||
}
|
||||
This
|
||||
>
|
||||
<PaymentModalContainer />
|
||||
|
||||
<CardPaymentModalContainer />
|
||||
|
||||
<TaskUpsertModalContainer />
|
||||
<BreadCrumbs />
|
||||
<BillEnterModalContainer />
|
||||
<JobCostingModal />
|
||||
@@ -252,6 +252,22 @@ export function Manage({ conflict, bodyshop, enableJoyRide, joyRideSteps, setJoy
|
||||
</Suspense>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/tasks/mytasks"
|
||||
element={
|
||||
<Suspense fallback={<Spin />}>
|
||||
<MyTasksPage />
|
||||
</Suspense>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/tasks/alltasks"
|
||||
element={
|
||||
<Suspense fallback={<Spin />}>
|
||||
<AllTasksPage />
|
||||
</Suspense>
|
||||
}
|
||||
/>
|
||||
<Route
|
||||
path="/inventory/"
|
||||
element={
|
||||
@@ -613,28 +629,6 @@ export function Manage({ conflict, bodyshop, enableJoyRide, joyRideSteps, setJoy
|
||||
Disclaimer & Notices
|
||||
</Link>
|
||||
</div>
|
||||
{InstanceRenderManager({
|
||||
promanager: (
|
||||
<Collapse>
|
||||
<Collapse.Panel header="DEVELOPMENT ONLY - ProductFruits Tours">
|
||||
<Space>
|
||||
<Button
|
||||
onClick={async () => {
|
||||
setTours(await window.productFruits.api.tours.getTours());
|
||||
}}
|
||||
>
|
||||
Get Tours
|
||||
</Button>
|
||||
{tours.map((tour) => (
|
||||
<Tag key={tour.id} onClick={() => window.productFruits.api.tours.tryStartTour(tour.id)}>
|
||||
{tour.name}
|
||||
</Tag>
|
||||
))}
|
||||
</Space>
|
||||
</Collapse.Panel>
|
||||
</Collapse>
|
||||
)
|
||||
})}
|
||||
</Footer>
|
||||
</Layout>
|
||||
</>
|
||||
|
||||
@@ -72,10 +72,17 @@ export function ShopPage({ bodyshop, setSelectedHeader, setBreadcrumbs }) {
|
||||
});
|
||||
}
|
||||
|
||||
items.push({
|
||||
key: "licensing",
|
||||
label: t("bodyshop.labels.licensing"),
|
||||
children: <ShopInfoUsersComponent />
|
||||
InstanceRenderManager({
|
||||
executeFunction: true,
|
||||
args: [],
|
||||
imex: () => {
|
||||
items.push({
|
||||
key: "licensing",
|
||||
label: t("bodyshop.labels.licensing"),
|
||||
children: <ShopInfoUsersComponent />
|
||||
});
|
||||
},
|
||||
rome: "USE_IMEX"
|
||||
});
|
||||
|
||||
if (HasFeatureAccess({ featureName: "csi", bodyshop })) {
|
||||
|
||||
60
client/src/pages/tasks/allTasksPageContainer.jsx
Normal file
60
client/src/pages/tasks/allTasksPageContainer.jsx
Normal file
@@ -0,0 +1,60 @@
|
||||
import React, { useEffect } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import TasksPageComponent from "./tasks.page.component";
|
||||
import queryString from "query-string";
|
||||
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { setBreadcrumbs, setSelectedHeader } from "../../redux/application/application.actions";
|
||||
import InstanceRenderManager from "../../utils/instanceRenderMgr.js";
|
||||
import TaskPageTypes from "./taskPageTypes.jsx";
|
||||
import { setModalContext } from "../../redux/modals/modals.actions.js";
|
||||
import { useLocation } from "react-router-dom";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
|
||||
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
|
||||
setTaskUpsertContext: (context) => dispatch(setModalContext({ context, modal: "taskUpsert" }))
|
||||
});
|
||||
|
||||
export function AllTasksPageContainer({ setBreadcrumbs, setSelectedHeader, setTaskUpsertContext }) {
|
||||
const { t } = useTranslation();
|
||||
const searchParams = queryString.parse(useLocation().search);
|
||||
useEffect(() => {
|
||||
document.title = t("titles.all_tasks", {
|
||||
app: InstanceRenderManager({
|
||||
imex: "$t(titles.imexonline)",
|
||||
rome: "$t(titles.romeonline)",
|
||||
promanager: "$t(titles.promanager)"
|
||||
})
|
||||
});
|
||||
setSelectedHeader("all_tasks");
|
||||
setBreadcrumbs([
|
||||
{
|
||||
link: "/manage/tasks/alltasks",
|
||||
label: t("titles.bc.all_tasks")
|
||||
}
|
||||
]);
|
||||
}, [t, setBreadcrumbs, setSelectedHeader]);
|
||||
|
||||
// This takes care of the ability to deep link a task from the URL (Dispatches the modal)
|
||||
useEffect(() => {
|
||||
// Check for a query string in the URL
|
||||
const urlParams = new URLSearchParams(searchParams);
|
||||
const taskId = urlParams.get("taskid");
|
||||
if (taskId) {
|
||||
setTaskUpsertContext({
|
||||
context: {
|
||||
taskId
|
||||
}
|
||||
});
|
||||
urlParams.delete("taskid");
|
||||
}
|
||||
}, [setTaskUpsertContext, searchParams]);
|
||||
|
||||
return <TasksPageComponent type={TaskPageTypes.ALL_TASKS} />;
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(AllTasksPageContainer);
|
||||
40
client/src/pages/tasks/myTasksPageContainer.jsx
Normal file
40
client/src/pages/tasks/myTasksPageContainer.jsx
Normal file
@@ -0,0 +1,40 @@
|
||||
import React, { useEffect } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import TasksPageComponent from "./tasks.page.component";
|
||||
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { setBreadcrumbs, setSelectedHeader } from "../../redux/application/application.actions";
|
||||
import InstanceRenderManager from "../../utils/instanceRenderMgr.js";
|
||||
import TaskPageTypes from "./taskPageTypes.jsx";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
|
||||
setSelectedHeader: (key) => dispatch(setSelectedHeader(key))
|
||||
});
|
||||
|
||||
export function MyTasksPageContainer({ setBreadcrumbs, setSelectedHeader }) {
|
||||
const { t } = useTranslation();
|
||||
useEffect(() => {
|
||||
document.title = t("titles.my_tasks", {
|
||||
app: InstanceRenderManager({
|
||||
imex: "$t(titles.imexonline)",
|
||||
rome: "$t(titles.romeonline)",
|
||||
promanager: "$t(titles.promanager)"
|
||||
})
|
||||
});
|
||||
setSelectedHeader("my_tasks");
|
||||
setBreadcrumbs([
|
||||
{
|
||||
link: "/manage/tasks/mytasks",
|
||||
label: t("titles.bc.my_tasks")
|
||||
}
|
||||
]);
|
||||
}, [t, setBreadcrumbs, setSelectedHeader]);
|
||||
|
||||
return <TasksPageComponent type={TaskPageTypes.MY_TASKS} />;
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(MyTasksPageContainer);
|
||||
6
client/src/pages/tasks/taskPageTypes.jsx
Normal file
6
client/src/pages/tasks/taskPageTypes.jsx
Normal file
@@ -0,0 +1,6 @@
|
||||
export const TaskPageTypes = {
|
||||
MY_TASKS: "myTasks",
|
||||
ALL_TASKS: "allTasks"
|
||||
};
|
||||
|
||||
export default TaskPageTypes;
|
||||
41
client/src/pages/tasks/tasks.page.component.jsx
Normal file
41
client/src/pages/tasks/tasks.page.component.jsx
Normal file
@@ -0,0 +1,41 @@
|
||||
import React from "react";
|
||||
import TaskListContainer from "../../components/task-list/task-list.container.jsx";
|
||||
import { QUERY_ALL_TASKS_PAGINATED, QUERY_MY_TASKS_PAGINATED } from "../../graphql/tasks.queries.js";
|
||||
import taskPageTypes from "./taskPageTypes.jsx";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors.js";
|
||||
import { connect } from "react-redux";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
currentUser: selectCurrentUser,
|
||||
bodyshop: selectBodyshop
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({});
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(TasksPageComponent);
|
||||
|
||||
export function TasksPageComponent({ bodyshop, currentUser, type }) {
|
||||
switch (type) {
|
||||
case taskPageTypes.MY_TASKS:
|
||||
return (
|
||||
<TaskListContainer
|
||||
onlyMine={true}
|
||||
relationshipId={bodyshop?.employees?.find((e) => e.user_email === currentUser.email)?.id}
|
||||
relationshipType={"assigned_to"}
|
||||
query={{ QUERY_MY_TASKS_PAGINATED }}
|
||||
titleTranslation={"tasks.titles.my_tasks"}
|
||||
disableJobRefetch={true}
|
||||
/>
|
||||
);
|
||||
case taskPageTypes.ALL_TASKS:
|
||||
return (
|
||||
<TaskListContainer
|
||||
query={{ QUERY_ALL_TASKS_PAGINATED }}
|
||||
titleTranslation={"tasks.titles.all_tasks"}
|
||||
disableJobRefetch={true}
|
||||
/>
|
||||
);
|
||||
default:
|
||||
return <></>;
|
||||
}
|
||||
}
|
||||
@@ -111,7 +111,12 @@ export function* calculateScheduleLoad({ payload: end }) {
|
||||
(load[itemDate].allHoursIn || 0) +
|
||||
item.labhrs.aggregate.sum.mod_lb_hrs +
|
||||
item.larhrs.aggregate.sum.mod_lb_hrs;
|
||||
|
||||
load[itemDate].allHoursInBody =
|
||||
(load[itemDate].allHoursInBody || 0) +
|
||||
item.labhrs.aggregate.sum.mod_lb_hrs;
|
||||
load[itemDate].allHoursInRefinish =
|
||||
(load[itemDate].allHoursInRefinish || 0) +
|
||||
item.larhrs.aggregate.sum.mod_lb_hrs;
|
||||
//If the job hasn't already arrived, add it to the jobs in list.
|
||||
// Make sure it also hasn't already been completed, or isn't an in and out job.
|
||||
//This prevents the duplicate counting.
|
||||
@@ -119,7 +124,15 @@ export function* calculateScheduleLoad({ payload: end }) {
|
||||
if (AddJobForSchedulingCalc) {
|
||||
load[itemDate].jobsIn.push(item);
|
||||
load[itemDate].hoursIn =
|
||||
(load[itemDate].hoursIn || 0) + item.labhrs.aggregate.sum.mod_lb_hrs + item.larhrs.aggregate.sum.mod_lb_hrs;
|
||||
(load[itemDate].hoursIn || 0) +
|
||||
item.labhrs.aggregate.sum.mod_lb_hrs +
|
||||
item.larhrs.aggregate.sum.mod_lb_hrs;
|
||||
load[itemDate].hoursInBody =
|
||||
(load[itemDate].hoursInBody || 0) +
|
||||
item.labhrs.aggregate.sum.mod_lb_hrs;
|
||||
load[itemDate].hoursInRefinish =
|
||||
(load[itemDate].hoursInRefinish || 0) +
|
||||
item.larhrs.aggregate.sum.mod_lb_hrs;
|
||||
}
|
||||
} else {
|
||||
load[itemDate] = {
|
||||
@@ -127,10 +140,21 @@ export function* calculateScheduleLoad({ payload: end }) {
|
||||
jobsIn: AddJobForSchedulingCalc ? [item] : [], //Same as above, only add it if it isn't already in production.
|
||||
jobsOut: [],
|
||||
allJobsOut: [],
|
||||
allHoursIn: item.labhrs.aggregate.sum.mod_lb_hrs + item.larhrs.aggregate.sum.mod_lb_hrs,
|
||||
allHoursIn:
|
||||
item.labhrs.aggregate.sum.mod_lb_hrs +
|
||||
item.larhrs.aggregate.sum.mod_lb_hrs,
|
||||
allHoursInBody: item.labhrs.aggregate.sum.mod_lb_hrs,
|
||||
allHoursInRefinish: item.larhrs.aggregate.sum.mod_lb_hrs,
|
||||
hoursIn: AddJobForSchedulingCalc
|
||||
? item.labhrs.aggregate.sum.mod_lb_hrs + item.larhrs.aggregate.sum.mod_lb_hrs
|
||||
: 0
|
||||
? item.labhrs.aggregate.sum.mod_lb_hrs +
|
||||
item.larhrs.aggregate.sum.mod_lb_hrs
|
||||
: 0,
|
||||
hoursInBody: AddJobForSchedulingCalc
|
||||
? item.labhrs.aggregate.sum.mod_lb_hrs
|
||||
: 0,
|
||||
hoursInRefinish: AddJobForSchedulingCalc
|
||||
? item.larhrs.aggregate.sum.mod_lb_hrs
|
||||
: 0,
|
||||
};
|
||||
}
|
||||
});
|
||||
@@ -151,6 +175,12 @@ export function* calculateScheduleLoad({ payload: end }) {
|
||||
(load[itemDate].allHoursOut || 0) +
|
||||
item.labhrs.aggregate.sum.mod_lb_hrs +
|
||||
item.larhrs.aggregate.sum.mod_lb_hrs;
|
||||
load[itemDate].allHoursOutBody =
|
||||
(load[itemDate].allHoursOutBody || 0) +
|
||||
item.labhrs.aggregate.sum.mod_lb_hrs;
|
||||
load[itemDate].allHoursOutRefinish =
|
||||
(load[itemDate].allHoursOutRefinish || 0) +
|
||||
item.larhrs.aggregate.sum.mod_lb_hrs;
|
||||
//Add only the jobs that are still in production to get rid of.
|
||||
//If it's not in production, we'd subtract unnecessarily.
|
||||
load[itemDate].allJobsOut.push(item);
|
||||
@@ -161,6 +191,12 @@ export function* calculateScheduleLoad({ payload: end }) {
|
||||
(load[itemDate].hoursOut || 0) +
|
||||
item.labhrs.aggregate.sum.mod_lb_hrs +
|
||||
item.larhrs.aggregate.sum.mod_lb_hrs;
|
||||
load[itemDate].hoursOutBody =
|
||||
(load[itemDate].hoursOutBody || 0) +
|
||||
item.labhrs.aggregate.sum.mod_lb_hrs;
|
||||
load[itemDate].hoursOutRefinish =
|
||||
(load[itemDate].hoursOutRefinish || 0) +
|
||||
item.larhrs.aggregate.sum.mod_lb_hrs;
|
||||
}
|
||||
} else {
|
||||
load[itemDate] = {
|
||||
@@ -169,7 +205,11 @@ export function* calculateScheduleLoad({ payload: end }) {
|
||||
hoursOut: AddJobForSchedulingCalc
|
||||
? item.labhrs.aggregate.sum.mod_lb_hrs + item.larhrs.aggregate.sum.mod_lb_hrs
|
||||
: 0,
|
||||
allHoursOut: item.labhrs.aggregate.sum.mod_lb_hrs + item.larhrs.aggregate.sum.mod_lb_hrs
|
||||
allHoursOut:
|
||||
item.labhrs.aggregate.sum.mod_lb_hrs +
|
||||
item.larhrs.aggregate.sum.mod_lb_hrs,
|
||||
allHoursOutBody: item.labhrs.aggregate.sum.mod_lb_hrs,
|
||||
allHoursOutRefinish: item.larhrs.aggregate.sum.mod_lb_hrs,
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
@@ -8,7 +8,8 @@ const INITIAL_STATE = {
|
||||
name: "ShopName",
|
||||
address: InstanceRenderManager({
|
||||
imex: "noreply@iemx.online",
|
||||
rome: "noreply@romeonline.io"
|
||||
rome: "noreply@romeonline.io",
|
||||
promanager: "noreply@promanager.web-est.com"
|
||||
})
|
||||
},
|
||||
to: null,
|
||||
|
||||
@@ -13,6 +13,7 @@ const INITIAL_STATE = {
|
||||
billEnter: { ...baseModal },
|
||||
courtesyCarReturn: { ...baseModal },
|
||||
noteUpsert: { ...baseModal },
|
||||
taskUpsert: { ...baseModal },
|
||||
schedule: { ...baseModal },
|
||||
partsOrder: { ...baseModal },
|
||||
timeTicket: { ...baseModal },
|
||||
|
||||
@@ -10,6 +10,8 @@ export const selectCourtesyCarReturn = createSelector([selectModals], (modals) =
|
||||
|
||||
export const selectNoteUpsert = createSelector([selectModals], (modals) => modals.noteUpsert);
|
||||
|
||||
export const selectTaskUpsert = createSelector([selectModals], (modals) => modals.taskUpsert);
|
||||
|
||||
export const selectSchedule = createSelector([selectModals], (modals) => modals.schedule);
|
||||
|
||||
export const selectPartsOrder = createSelector([selectModals], (modals) => modals.partsOrder);
|
||||
|
||||
@@ -15,6 +15,7 @@ import { getToken } from "firebase/messaging";
|
||||
import i18next from "i18next";
|
||||
import LogRocket from "logrocket";
|
||||
import { all, call, delay, put, select, takeLatest } from "redux-saga/effects";
|
||||
import { Userpilot } from "userpilot";
|
||||
import { factory } from "../../App/App.container";
|
||||
import {
|
||||
analytics,
|
||||
@@ -25,6 +26,10 @@ import {
|
||||
messaging,
|
||||
updateCurrentUser
|
||||
} from "../../firebase/firebase.utils";
|
||||
import { QUERY_EULA } from "../../graphql/bodyshop.queries";
|
||||
import client from "../../utils/GraphQLClient";
|
||||
import day from "../../utils/day";
|
||||
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
||||
import {
|
||||
checkInstanceId,
|
||||
sendPasswordResetFailure,
|
||||
@@ -43,11 +48,6 @@ import {
|
||||
validatePasswordResetSuccess
|
||||
} from "./user.actions";
|
||||
import UserActionTypes from "./user.types";
|
||||
import client from "../../utils/GraphQLClient";
|
||||
import { QUERY_EULA } from "../../graphql/bodyshop.queries";
|
||||
import day from "../../utils/day";
|
||||
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
||||
import { Userpilot } from "userpilot";
|
||||
|
||||
const fpPromise = FingerprintJS.load();
|
||||
|
||||
@@ -310,10 +310,41 @@ export function* SetAuthLevelFromShopDetails({ payload }) {
|
||||
updateUserDetailsSuccess(authRecord[0] ? { validemail: authRecord[0].user.validemail } : { validemail: false })
|
||||
);
|
||||
|
||||
const user = yield select((state) => state.user.currentUser);
|
||||
if (payload.features.singleDeviceOnly) {
|
||||
const user = yield select((state) => state.user.currentUser);
|
||||
if (
|
||||
!(
|
||||
user.email.includes("@imex.") ||
|
||||
user.email.includes("@rome.") ||
|
||||
user.email.includes("@rometech.") ||
|
||||
user.email.includes("@promanager.")
|
||||
)
|
||||
)
|
||||
yield put(setInstanceId(user.uid));
|
||||
}
|
||||
|
||||
if (!(user.email.includes("@imex.") || user.email.includes("@rome."))) yield put(setInstanceId(user.uid));
|
||||
//For Rome, check to make sure it's not a PM shop.
|
||||
try {
|
||||
InstanceRenderManager({
|
||||
executeFunction: true,
|
||||
args: [],
|
||||
rome: () => {
|
||||
if (
|
||||
payload.imexshopid.toLowerCase().startsWith("pm_") &&
|
||||
!(
|
||||
user.email.includes("@imex.") ||
|
||||
user.email.includes("@rome.") ||
|
||||
user.email.includes("@rometech.") ||
|
||||
user.email.includes("@promanager.")
|
||||
)
|
||||
) {
|
||||
throw new Error("You are not authorized to use this application.");
|
||||
}
|
||||
},
|
||||
promanager: () => {}
|
||||
});
|
||||
} catch (error) {
|
||||
yield put(setInstanceConflict());
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
@@ -116,6 +116,7 @@
|
||||
"jobassignmentchange": "Employee {{name}} assigned to {{operation}}",
|
||||
"jobassignmentremoved": "Employee assignment removed for {{operation}}",
|
||||
"jobchecklist": "Checklist type \"{{type}}\" completed. In production set to {{inproduction}}. Status set to {{status}}.",
|
||||
"jobclosedwithbypass": "Job was invoiced using the master bypass password. ",
|
||||
"jobconverted": "Job converted and assigned number {{ro_number}}.",
|
||||
"jobdelivery": "Job intake completed. Status set to {{status}}. Actual completion is {{actual_completion}}.",
|
||||
"jobexported": "",
|
||||
@@ -134,7 +135,13 @@
|
||||
"jobstatuschange": "Job status changed to {{status}}.",
|
||||
"jobsupplement": "Job supplement imported.",
|
||||
"jobsuspend": "Suspend Toggle set to {{status}}",
|
||||
"jobvoid": "Job has been voided."
|
||||
"jobvoid": "Job has been voided.",
|
||||
"tasks_completed": "Task '{{title}}' completed by {{completedBy}}",
|
||||
"tasks_created": "Task '{{title}}' created by {{createdBy}}",
|
||||
"tasks_deleted": "Task '{{title}}' deleted by {{deletedBy}}",
|
||||
"tasks_uncompleted": "Task '{{title}}' uncompleted by {{uncompletedBy}}",
|
||||
"tasks_undeleted": "Task '{{title}}' undeleted by {{undeletedBy}}",
|
||||
"tasks_updated": "Task '{{title}}' updated by {{updatedBy}}"
|
||||
}
|
||||
},
|
||||
"billlines": {
|
||||
@@ -223,11 +230,12 @@
|
||||
"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",
|
||||
"retailtotal": "Bills Retail Total",
|
||||
"returnfrombill": "Return From Bill",
|
||||
"savewithdiscrepancy": "You are about to save this bill with a discrepancy. The system will continue to use the calculated amount using the bill lines. Press cancel to return to the bill.",
|
||||
"state_tax": "State Tax",
|
||||
"subtotal": "Subtotal",
|
||||
@@ -287,7 +295,8 @@
|
||||
"dailypainttarget": "Scoreboard - Daily Paint Target",
|
||||
"default_adjustment_rate": "Default Labor Deduction Adjustment Rate",
|
||||
"deliver": {
|
||||
"templates": "Delivery Templates"
|
||||
"templates": "Delivery Templates",
|
||||
"require_actual_delivery_date": "Require Actual Delivery"
|
||||
},
|
||||
"dms": {
|
||||
"apcontrol": "AP Control Number",
|
||||
@@ -638,7 +647,10 @@
|
||||
"payers": "Payers"
|
||||
},
|
||||
"cdk_dealerid": "CDK Dealer ID",
|
||||
"costsmapping": "Costs Mapping",
|
||||
"dms_allocations": "DMS Allocations",
|
||||
"pbs_serialnumber": "PBS Serial Number",
|
||||
"profitsmapping": "Profits Mapping",
|
||||
"title": "DMS"
|
||||
},
|
||||
"emaillater": "Email Later",
|
||||
@@ -858,10 +870,11 @@
|
||||
},
|
||||
"status": {
|
||||
"in": "Available",
|
||||
"inservice": "In Service",
|
||||
"inservice": "Service/Maintenance",
|
||||
"leasereturn": "Lease Returned",
|
||||
"out": "Rented",
|
||||
"sold": "Sold"
|
||||
"sold": "Sold",
|
||||
"unavailable": "Unavailable"
|
||||
},
|
||||
"successes": {
|
||||
"saved": "Courtesy Car saved successfully."
|
||||
@@ -931,7 +944,8 @@
|
||||
"scheduledindate": "Sheduled In Today: {{date}}",
|
||||
"scheduledintoday": "Sheduled In Today",
|
||||
"scheduledoutdate": "Sheduled Out Today: {{date}}",
|
||||
"scheduledouttoday": "Sheduled Out Today"
|
||||
"scheduledouttoday": "Sheduled Out Today",
|
||||
"tasks": "Tasks"
|
||||
}
|
||||
},
|
||||
"dms": {
|
||||
@@ -1126,8 +1140,11 @@
|
||||
"delete": "Delete",
|
||||
"deleteall": "Delete All",
|
||||
"deselectall": "Deselect All",
|
||||
"download": "Download",
|
||||
"edit": "Edit",
|
||||
"login": "Login",
|
||||
"next": "Next",
|
||||
"previous": "Previous",
|
||||
"print": "Print",
|
||||
"refresh": "Refresh",
|
||||
"remove": "Remove",
|
||||
@@ -1351,6 +1368,7 @@
|
||||
"amount": "Amount",
|
||||
"dateOfPayment": "Date of Payment",
|
||||
"descriptions": "Payment Details",
|
||||
"hint": "Hint",
|
||||
"payer": "Payer",
|
||||
"payername": "Payer Name",
|
||||
"paymentid": "Payment Reference ID",
|
||||
@@ -1541,6 +1559,7 @@
|
||||
"voiding": "Error voiding Job. {{error}}"
|
||||
},
|
||||
"fields": {
|
||||
"active_tasks": "Active Tasks",
|
||||
"actual_completion": "Actual Completion",
|
||||
"actual_delivery": "Actual Delivery",
|
||||
"actual_in": "Actual In",
|
||||
@@ -1975,7 +1994,7 @@
|
||||
"outstanding_ar": "A balance is outstanding on this RO. Payments can still be entered when the job is closed. ",
|
||||
"outstanding_credit_memos": "Outstanding credit memos have not been entered against this job. Credit Memos may still be posted once the job is closed.",
|
||||
"outstanding_ppd": "There are outstanding PPDs that may not have been synced back to the estimate.",
|
||||
"outstanding_reconciliation_discrep": "At least one discrepancy is not 0. This may indicate that this job is not properly reconciled and should not be closed.",
|
||||
"outstanding_reconciliation_discrep": "At least one discrepancy is not $0. This may indicate that this job is not properly reconciled and should not be closed.",
|
||||
"outstanding_sublets": "There are sublet lines on the job which have not been marked as completed. ",
|
||||
"outstandinghours": "There are outstanding hours on the job that have not been paid or have been overpaid.",
|
||||
"override_header": "Override estimate header on import?",
|
||||
@@ -2004,6 +2023,7 @@
|
||||
"ppc": "This line contains a part price change.",
|
||||
"ppdnotexported": "PPDs not Exported",
|
||||
"profileadjustments": "Profile Disc./Mkup",
|
||||
"profitbypassrequired": "Minimum gross profit requirements have not been met.",
|
||||
"profits": "Job Profits",
|
||||
"prt_dsmk_total": "Line Item Adjustment",
|
||||
"rates": "Rates",
|
||||
@@ -2025,12 +2045,12 @@
|
||||
"remove_from_ar": "Remove from AR",
|
||||
"returntotals": "Return Totals",
|
||||
"ro_guard": {
|
||||
"eforce_profit": "Profit margins enforced.",
|
||||
"enforce_ar": "AR collection enforced.",
|
||||
"enforce_bills": "Bill discrepancy enforced.",
|
||||
"enforce_cm": "Credit memo entry enforced.",
|
||||
"enforce_labor": "Labor allocations enforced.",
|
||||
"enforce_ppd": "PPD sync enforced.",
|
||||
"enforce_profit": "Profit marginsenforced.",
|
||||
"enforce_sublet": "Sublet completion enforced.",
|
||||
"enforce_validation": "Master Bypass Required: {{message}}",
|
||||
"enforced": "This check has been enforced by your shop manager. Enter the master bypass password to close the Job."
|
||||
@@ -2053,6 +2073,7 @@
|
||||
"supplementnote": "The Job had a supplement imported.",
|
||||
"suspended": "SUSPENDED",
|
||||
"suspense": "Suspense",
|
||||
"tasks": "Tasks",
|
||||
"threshhold": "Max Threshold: ${{amount}}",
|
||||
"total_cost": "Total Cost",
|
||||
"total_cust_payable": "Total Customer Amount Payable",
|
||||
@@ -2150,6 +2171,7 @@
|
||||
"accounting-payments": "Payments",
|
||||
"accounting-receivables": "Receivables",
|
||||
"activejobs": "Active Jobs",
|
||||
"all_tasks": "All Tasks",
|
||||
"alljobs": "All Jobs",
|
||||
"allpayments": "All Payments",
|
||||
"availablejobs": "Available Jobs",
|
||||
@@ -2158,6 +2180,7 @@
|
||||
"courtesycars-all": "All Courtesy Cars",
|
||||
"courtesycars-contracts": "Contracts",
|
||||
"courtesycars-newcontract": "New Contract",
|
||||
"create_task": "Create Task",
|
||||
"customers": "Customers",
|
||||
"dashboard": "Dashboard",
|
||||
"enterbills": "Enter Bills",
|
||||
@@ -2170,6 +2193,7 @@
|
||||
"home": "Home",
|
||||
"inventory": "Inventory",
|
||||
"jobs": "Jobs",
|
||||
"my_tasks": "My Tasks",
|
||||
"newjob": "Create New Job",
|
||||
"owners": "Owners",
|
||||
"parts-queue": "Parts Queue",
|
||||
@@ -2196,6 +2220,7 @@
|
||||
"shop_csi": "CSI",
|
||||
"shop_templates": "Templates",
|
||||
"shop_vendors": "Vendors",
|
||||
"tasks": "Tasks",
|
||||
"temporarydocs": "Temporary Documents",
|
||||
"timetickets": "Time Tickets",
|
||||
"ttapprovals": "Time Ticket Approvals",
|
||||
@@ -2389,6 +2414,7 @@
|
||||
"percent_accepted": "% Accepted"
|
||||
},
|
||||
"labels": {
|
||||
"notyetdispatched": "This part has not been dispatched.",
|
||||
"parts_dispatch": "Parts Dispatch"
|
||||
}
|
||||
},
|
||||
@@ -2505,6 +2531,7 @@
|
||||
"markexported": "Payment(s) marked exported.",
|
||||
"markreexported": "Payment marked for re-export successfully",
|
||||
"payment": "Payment created successfully. ",
|
||||
"paymentupdate": "Payment updated successfully. ",
|
||||
"stripe": "Credit card transaction charged successfully."
|
||||
}
|
||||
},
|
||||
@@ -2606,6 +2633,7 @@
|
||||
"job_costing_ro": "Job Costing",
|
||||
"job_lifecycle_ro": "Job Lifecycle",
|
||||
"job_notes": "Job Notes",
|
||||
"job_tasks": "Job Tasks",
|
||||
"key_tag": "Key Tag",
|
||||
"labels": {
|
||||
"count": "Count",
|
||||
@@ -2806,6 +2834,7 @@
|
||||
"parts_orders": "Parts Orders",
|
||||
"payments": "Payments",
|
||||
"scoreboard": "Scoreboard",
|
||||
"tasks": "Tasks",
|
||||
"timetickets": "Timetickets"
|
||||
},
|
||||
"vendor": "Vendor"
|
||||
@@ -2891,7 +2920,8 @@
|
||||
"parts_not_recieved_vendor": "Parts Not Received by Vendor",
|
||||
"parts_received_not_scheduled": "Parts Received for Jobs Not Scheduled",
|
||||
"payments_by_date": "Payments by Date",
|
||||
"payments_by_date_type": "Payments by Date and Type",
|
||||
"payments_by_date_payment": "Payments by Date and Payment Type",
|
||||
"payments_by_date_type": "Payments by Date and Customer Type",
|
||||
"production_by_category": "Production by Category",
|
||||
"production_by_category_one": "Production filtered by Category",
|
||||
"production_by_csr": "Production by CSR",
|
||||
@@ -2910,6 +2940,8 @@
|
||||
"purchases_by_cost_center_summary": "Purchases by Cost Center (Summary)",
|
||||
"purchases_by_date_range_detail": "Purchases by Date - Detail",
|
||||
"purchases_by_date_range_summary": "Purchases by Date - Summary",
|
||||
"purchases_by_ro_detail_date": "Purchases by RO - Detail",
|
||||
"purchases_by_ro_summary_date": "Purchases by RO - Summary",
|
||||
"purchases_by_vendor_detailed_date_range": "Purchases By Vendor - Detailed",
|
||||
"purchases_by_vendor_summary_date_range": "Purchases by Vendor - Summary",
|
||||
"purchases_grouped_by_vendor_detailed": "Purchases Grouped by Vendor - Detailed",
|
||||
@@ -2921,6 +2953,8 @@
|
||||
"scoreboard_detail": "Scoreboard Detail",
|
||||
"scoreboard_summary": "Scoreboard Summary",
|
||||
"supplement_ratio_ins_co": "Supplement Ratio by Source",
|
||||
"tasks_date": "Tasks by Date",
|
||||
"tasks_date_employee": "Employee Tasks by Date",
|
||||
"thank_you_date": "Thank You Letters",
|
||||
"timetickets": "Time Tickets",
|
||||
"timetickets_employee": "Employee Time Tickets",
|
||||
@@ -3000,6 +3034,92 @@
|
||||
"updated": "Scoreboard updated."
|
||||
}
|
||||
},
|
||||
"tasks": {
|
||||
"actions": {
|
||||
"edit": "Edit Task",
|
||||
"new": "New Task"
|
||||
},
|
||||
"buttons": {
|
||||
"allTasks": "All",
|
||||
"complete": "Toggle Complete",
|
||||
"create": "Create Task",
|
||||
"delete": "Toggle Delete",
|
||||
"edit": "Edit",
|
||||
"myTasks": "Mine",
|
||||
"refresh": "Refresh"
|
||||
},
|
||||
"date_presets": {
|
||||
"completion": "Completion",
|
||||
"day": "Day",
|
||||
"days": "Days",
|
||||
"delivery": "Delivery",
|
||||
"next_week": "Next Week",
|
||||
"one_month": "One Month",
|
||||
"three_months": "Three Months",
|
||||
"three_weeks": "Three Weeks",
|
||||
"today": "Today",
|
||||
"tomorrow": "Tomorrow",
|
||||
"two_weeks": "Two Weeks"
|
||||
},
|
||||
"failures": {
|
||||
"completed": "Failed to toggle Task completion.",
|
||||
"created": "Failed to create Task.",
|
||||
"deleted": "Failed to toggle Task deletion.",
|
||||
"updated": "Failed to update Task."
|
||||
},
|
||||
"fields": {
|
||||
"actions": "Actions",
|
||||
"assigned_to": "Assigned To",
|
||||
"bill": "Bill",
|
||||
"billid": "Bill",
|
||||
"completed": "Completed",
|
||||
"created_at": "Created At",
|
||||
"description": "Description",
|
||||
"due_date": "Due Date",
|
||||
"job": {
|
||||
"ro_number": "RO #"
|
||||
},
|
||||
"jobid": "Job",
|
||||
"jobline": "Job Line",
|
||||
"joblineid": "Job Line",
|
||||
"parts_order": "Parts Order",
|
||||
"partsorderid": "Parts Order",
|
||||
"priorities": {
|
||||
"high": "High",
|
||||
"low": "Low",
|
||||
"medium": "Medium"
|
||||
},
|
||||
"priority": "Priority",
|
||||
"remind_at": "Remind At",
|
||||
"title": "Title"
|
||||
},
|
||||
"placeholders": {
|
||||
"assigned_to": "Select an Employee",
|
||||
"billid": "Select a Bill",
|
||||
"description": "Enter a description",
|
||||
"jobid": "Select a Job",
|
||||
"joblineid": "Select a Job Line",
|
||||
"partsorderid": "Select a Parts Order"
|
||||
},
|
||||
"successes": {
|
||||
"completed": "Toggled Task completion successfully.",
|
||||
"created": "Task created successfully.",
|
||||
"deleted": "Toggled Task deletion successfully.",
|
||||
"updated": "Task updated successfully."
|
||||
},
|
||||
"titles": {
|
||||
"all_tasks": "All Tasks",
|
||||
"completed": "Completed Tasks",
|
||||
"deleted": "Deleted Tasks",
|
||||
"job_tasks": "Job Tasks",
|
||||
"mine": "My Tasks",
|
||||
"my_tasks": "My Tasks"
|
||||
},
|
||||
"validation": {
|
||||
"due_at_error_message": "The due date must be in the future!",
|
||||
"remind_at_error_message": "The reminder date and time must be at least 30 minutes in the future!"
|
||||
}
|
||||
},
|
||||
"tech": {
|
||||
"fields": {
|
||||
"employeeid": "Employee ID",
|
||||
@@ -3104,11 +3224,13 @@
|
||||
"accounting-payables": "Payables | {{app}}",
|
||||
"accounting-payments": "Payments | {{app}}",
|
||||
"accounting-receivables": "Receivables | {{app}}",
|
||||
"all_tasks": "All Tasks",
|
||||
"app": "",
|
||||
"bc": {
|
||||
"accounting-payables": "Payables",
|
||||
"accounting-payments": "Payments",
|
||||
"accounting-receivables": "Receivables",
|
||||
"all_tasks": "All Tasks",
|
||||
"availablejobs": "Available Jobs",
|
||||
"bills-list": "Bills",
|
||||
"contracts": "Contracts",
|
||||
@@ -3132,6 +3254,7 @@
|
||||
"jobs-intake": "Intake",
|
||||
"jobs-new": "Create a New Job",
|
||||
"jobs-ready": "Ready Jobs",
|
||||
"my_tasks": "My Tasks",
|
||||
"owner-detail": "{{name}}",
|
||||
"owners": "Owners",
|
||||
"parts-queue": "Parts Queue",
|
||||
@@ -3146,6 +3269,7 @@
|
||||
"shop-csi": "CSI Responses",
|
||||
"shop-templates": "Shop Templates",
|
||||
"shop-vendors": "Vendors",
|
||||
"tasks": "Tasks",
|
||||
"temporarydocs": "Temporary Documents",
|
||||
"timetickets": "Time Tickets",
|
||||
"ttapprovals": "Time Ticket Approvals",
|
||||
@@ -3176,6 +3300,7 @@
|
||||
"jobsdetail": "Job {{ro_number}} | {{app}}",
|
||||
"jobsdocuments": "Job Documents {{ro_number}} | {{app}}",
|
||||
"manageroot": "Home | {{app}}",
|
||||
"my_tasks": "My Tasks",
|
||||
"owners": "All Owners | {{app}}",
|
||||
"owners-detail": "{{name}} | {{app}}",
|
||||
"parts-queue": "Parts Queue | {{app}}",
|
||||
@@ -3195,6 +3320,7 @@
|
||||
"shop-csi": "CSI Responses | {{app}}",
|
||||
"shop-templates": "Shop Templates | {{app}}",
|
||||
"shop_vendors": "Vendors | {{app}}",
|
||||
"tasks": "Tasks",
|
||||
"techconsole": "Technician Console | {{app}}",
|
||||
"techjobclock": "Technician Job Clock | {{app}}",
|
||||
"techjoblookup": "Technician Job Lookup | {{app}}",
|
||||
|
||||
@@ -116,6 +116,7 @@
|
||||
"jobassignmentchange": "",
|
||||
"jobassignmentremoved": "",
|
||||
"jobchecklist": "",
|
||||
"jobclosedwithbypass": "",
|
||||
"jobconverted": "",
|
||||
"jobdelivery": "",
|
||||
"jobexported": "",
|
||||
@@ -134,7 +135,13 @@
|
||||
"jobstatuschange": "",
|
||||
"jobsupplement": "",
|
||||
"jobsuspend": "",
|
||||
"jobvoid": ""
|
||||
"jobvoid": "",
|
||||
"tasks_completed": "",
|
||||
"tasks_created": "",
|
||||
"tasks_deleted": "",
|
||||
"tasks_uncompleted": "",
|
||||
"tasks_undeleted": "",
|
||||
"tasks_updated": ""
|
||||
}
|
||||
},
|
||||
"billlines": {
|
||||
@@ -223,10 +230,12 @@
|
||||
"markexported": "",
|
||||
"markforreexport": "",
|
||||
"new": "",
|
||||
"nobilllines": "",
|
||||
"noneselected": "",
|
||||
"onlycmforinvoiced": "",
|
||||
"printlabels": "",
|
||||
"retailtotal": "",
|
||||
"returnfrombill": "",
|
||||
"savewithdiscrepancy": "",
|
||||
"state_tax": "",
|
||||
"subtotal": "",
|
||||
@@ -286,7 +295,8 @@
|
||||
"dailypainttarget": "",
|
||||
"default_adjustment_rate": "",
|
||||
"deliver": {
|
||||
"templates": ""
|
||||
"templates": "",
|
||||
"require_actual_delivery_date": ""
|
||||
},
|
||||
"dms": {
|
||||
"apcontrol": "",
|
||||
@@ -637,7 +647,10 @@
|
||||
"payers": ""
|
||||
},
|
||||
"cdk_dealerid": "",
|
||||
"costsmapping": "",
|
||||
"dms_allocations": "",
|
||||
"pbs_serialnumber": "",
|
||||
"profitsmapping": "",
|
||||
"title": ""
|
||||
},
|
||||
"emaillater": "",
|
||||
@@ -860,7 +873,8 @@
|
||||
"inservice": "",
|
||||
"leasereturn": "",
|
||||
"out": "",
|
||||
"sold": ""
|
||||
"sold": "",
|
||||
"unavailable": ""
|
||||
},
|
||||
"successes": {
|
||||
"saved": ""
|
||||
@@ -930,7 +944,8 @@
|
||||
"scheduledindate": "",
|
||||
"scheduledintoday": "",
|
||||
"scheduledoutdate": "",
|
||||
"scheduledouttoday": ""
|
||||
"scheduledouttoday": "",
|
||||
"tasks": ""
|
||||
}
|
||||
},
|
||||
"dms": {
|
||||
@@ -1125,8 +1140,11 @@
|
||||
"delete": "Borrar",
|
||||
"deleteall": "",
|
||||
"deselectall": "",
|
||||
"download": "",
|
||||
"edit": "Editar",
|
||||
"login": "",
|
||||
"next": "",
|
||||
"previous": "",
|
||||
"print": "",
|
||||
"refresh": "",
|
||||
"remove": "",
|
||||
@@ -1350,6 +1368,7 @@
|
||||
"amount": "",
|
||||
"dateOfPayment": "",
|
||||
"descriptions": "",
|
||||
"hint": "",
|
||||
"payer": "",
|
||||
"payername": "",
|
||||
"paymentid": "",
|
||||
@@ -1540,6 +1559,7 @@
|
||||
"voiding": ""
|
||||
},
|
||||
"fields": {
|
||||
"active_tasks": "",
|
||||
"actual_completion": "Realización real",
|
||||
"actual_delivery": "Entrega real",
|
||||
"actual_in": "Real en",
|
||||
@@ -2003,6 +2023,7 @@
|
||||
"ppc": "",
|
||||
"ppdnotexported": "",
|
||||
"profileadjustments": "",
|
||||
"profitbypassrequired": "",
|
||||
"profits": "",
|
||||
"prt_dsmk_total": "",
|
||||
"rates": "Tarifas",
|
||||
@@ -2024,12 +2045,12 @@
|
||||
"remove_from_ar": "",
|
||||
"returntotals": "",
|
||||
"ro_guard": {
|
||||
"eforce_profit": "",
|
||||
"enforce_ar": "",
|
||||
"enforce_bills": "",
|
||||
"enforce_cm": "",
|
||||
"enforce_labor": "",
|
||||
"enforce_ppd": "",
|
||||
"enforce_profit": "",
|
||||
"enforce_sublet": "",
|
||||
"enforce_validation": "",
|
||||
"enforced": ""
|
||||
@@ -2052,6 +2073,7 @@
|
||||
"supplementnote": "",
|
||||
"suspended": "",
|
||||
"suspense": "",
|
||||
"tasks": "",
|
||||
"threshhold": "",
|
||||
"total_cost": "",
|
||||
"total_cust_payable": "",
|
||||
@@ -2149,6 +2171,7 @@
|
||||
"accounting-payments": "",
|
||||
"accounting-receivables": "",
|
||||
"activejobs": "Empleos activos",
|
||||
"all_tasks": "",
|
||||
"alljobs": "",
|
||||
"allpayments": "",
|
||||
"availablejobs": "Trabajos disponibles",
|
||||
@@ -2157,6 +2180,7 @@
|
||||
"courtesycars-all": "",
|
||||
"courtesycars-contracts": "",
|
||||
"courtesycars-newcontract": "",
|
||||
"create_task": "",
|
||||
"customers": "Clientes",
|
||||
"dashboard": "",
|
||||
"enterbills": "",
|
||||
@@ -2169,6 +2193,7 @@
|
||||
"home": "Casa",
|
||||
"inventory": "",
|
||||
"jobs": "Trabajos",
|
||||
"my_tasks": "",
|
||||
"newjob": "",
|
||||
"owners": "propietarios",
|
||||
"parts-queue": "",
|
||||
@@ -2195,6 +2220,7 @@
|
||||
"shop_csi": "",
|
||||
"shop_templates": "",
|
||||
"shop_vendors": "Vendedores",
|
||||
"tasks": "",
|
||||
"temporarydocs": "",
|
||||
"timetickets": "",
|
||||
"ttapprovals": "",
|
||||
@@ -2388,6 +2414,7 @@
|
||||
"percent_accepted": ""
|
||||
},
|
||||
"labels": {
|
||||
"notyetdispatched": "",
|
||||
"parts_dispatch": ""
|
||||
}
|
||||
},
|
||||
@@ -2504,6 +2531,7 @@
|
||||
"markexported": "",
|
||||
"markreexported": "",
|
||||
"payment": "",
|
||||
"paymentupdate": "",
|
||||
"stripe": ""
|
||||
}
|
||||
},
|
||||
@@ -2605,6 +2633,7 @@
|
||||
"job_costing_ro": "",
|
||||
"job_lifecycle_ro": "",
|
||||
"job_notes": "",
|
||||
"job_tasks": "",
|
||||
"key_tag": "",
|
||||
"labels": {
|
||||
"count": "",
|
||||
@@ -2805,6 +2834,7 @@
|
||||
"parts_orders": "",
|
||||
"payments": "",
|
||||
"scoreboard": "",
|
||||
"tasks": "",
|
||||
"timetickets": ""
|
||||
},
|
||||
"vendor": ""
|
||||
@@ -2890,6 +2920,7 @@
|
||||
"parts_not_recieved_vendor": "",
|
||||
"parts_received_not_scheduled": "",
|
||||
"payments_by_date": "",
|
||||
"payments_by_date_payment": "",
|
||||
"payments_by_date_type": "",
|
||||
"production_by_category": "",
|
||||
"production_by_category_one": "",
|
||||
@@ -2909,6 +2940,8 @@
|
||||
"purchases_by_cost_center_summary": "",
|
||||
"purchases_by_date_range_detail": "",
|
||||
"purchases_by_date_range_summary": "",
|
||||
"purchases_by_ro_detail_date": "",
|
||||
"purchases_by_ro_summary_date": "",
|
||||
"purchases_by_vendor_detailed_date_range": "",
|
||||
"purchases_by_vendor_summary_date_range": "",
|
||||
"purchases_grouped_by_vendor_detailed": "",
|
||||
@@ -2920,6 +2953,8 @@
|
||||
"scoreboard_detail": "",
|
||||
"scoreboard_summary": "",
|
||||
"supplement_ratio_ins_co": "",
|
||||
"tasks_date": "",
|
||||
"tasks_date_employee": "",
|
||||
"thank_you_date": "",
|
||||
"timetickets": "",
|
||||
"timetickets_employee": "",
|
||||
@@ -2999,6 +3034,92 @@
|
||||
"updated": ""
|
||||
}
|
||||
},
|
||||
"tasks": {
|
||||
"actions": {
|
||||
"edit": "",
|
||||
"new": ""
|
||||
},
|
||||
"buttons": {
|
||||
"allTasks": "",
|
||||
"complete": "",
|
||||
"create": "",
|
||||
"delete": "",
|
||||
"edit": "",
|
||||
"myTasks": "",
|
||||
"refresh": ""
|
||||
},
|
||||
"date_presets": {
|
||||
"completion": "",
|
||||
"day": "",
|
||||
"days": "",
|
||||
"delivery": "",
|
||||
"next_week": "",
|
||||
"one_month": "",
|
||||
"three_months": "",
|
||||
"three_weeks": "",
|
||||
"today": "",
|
||||
"tomorrow": "",
|
||||
"two_weeks": ""
|
||||
},
|
||||
"failures": {
|
||||
"completed": "",
|
||||
"created": "",
|
||||
"deleted": "",
|
||||
"updated": ""
|
||||
},
|
||||
"fields": {
|
||||
"actions": "",
|
||||
"assigned_to": "",
|
||||
"bill": "",
|
||||
"billid": "",
|
||||
"completed": "",
|
||||
"created_at": "",
|
||||
"description": "",
|
||||
"due_date": "",
|
||||
"job": {
|
||||
"ro_number": ""
|
||||
},
|
||||
"jobid": "",
|
||||
"jobline": "",
|
||||
"joblineid": "",
|
||||
"parts_order": "",
|
||||
"partsorderid": "",
|
||||
"priorities": {
|
||||
"high": "",
|
||||
"low": "",
|
||||
"medium": ""
|
||||
},
|
||||
"priority": "",
|
||||
"remind_at": "",
|
||||
"title": ""
|
||||
},
|
||||
"placeholders": {
|
||||
"assigned_to": "",
|
||||
"billid": "",
|
||||
"description": "",
|
||||
"jobid": "",
|
||||
"joblineid": "",
|
||||
"partsorderid": ""
|
||||
},
|
||||
"successes": {
|
||||
"completed": "",
|
||||
"created": "",
|
||||
"deleted": "",
|
||||
"updated": ""
|
||||
},
|
||||
"titles": {
|
||||
"all_tasks": "",
|
||||
"completed": "",
|
||||
"deleted": "",
|
||||
"job_tasks": "",
|
||||
"mine": "",
|
||||
"my_tasks": ""
|
||||
},
|
||||
"validation": {
|
||||
"due_at_error_message": "",
|
||||
"remind_at_error_message": ""
|
||||
}
|
||||
},
|
||||
"tech": {
|
||||
"fields": {
|
||||
"employeeid": "",
|
||||
@@ -3103,11 +3224,13 @@
|
||||
"accounting-payables": "",
|
||||
"accounting-payments": "",
|
||||
"accounting-receivables": "",
|
||||
"all_tasks": "",
|
||||
"app": "",
|
||||
"bc": {
|
||||
"accounting-payables": "",
|
||||
"accounting-payments": "",
|
||||
"accounting-receivables": "",
|
||||
"all_tasks": "",
|
||||
"availablejobs": "",
|
||||
"bills-list": "",
|
||||
"contracts": "",
|
||||
@@ -3131,6 +3254,7 @@
|
||||
"jobs-intake": "",
|
||||
"jobs-new": "",
|
||||
"jobs-ready": "",
|
||||
"my_tasks": "",
|
||||
"owner-detail": "",
|
||||
"owners": "",
|
||||
"parts-queue": "",
|
||||
@@ -3145,6 +3269,7 @@
|
||||
"shop-csi": "",
|
||||
"shop-templates": "",
|
||||
"shop-vendors": "",
|
||||
"tasks": "",
|
||||
"temporarydocs": "",
|
||||
"timetickets": "",
|
||||
"ttapprovals": "",
|
||||
@@ -3175,6 +3300,7 @@
|
||||
"jobsdetail": "Trabajo {{ro_number}} | {{app}}",
|
||||
"jobsdocuments": "Documentos de trabajo {{ro_number}} | {{app}}",
|
||||
"manageroot": "Casa | {{app}}",
|
||||
"my_tasks": "",
|
||||
"owners": "Todos los propietarios | {{app}}",
|
||||
"owners-detail": "",
|
||||
"parts-queue": "",
|
||||
@@ -3194,6 +3320,7 @@
|
||||
"shop-csi": "",
|
||||
"shop-templates": "",
|
||||
"shop_vendors": "Vendedores | {{app}}",
|
||||
"tasks": "",
|
||||
"techconsole": "{{app}}",
|
||||
"techjobclock": "{{app}}",
|
||||
"techjoblookup": "{{app}}",
|
||||
|
||||
@@ -116,6 +116,7 @@
|
||||
"jobassignmentchange": "",
|
||||
"jobassignmentremoved": "",
|
||||
"jobchecklist": "",
|
||||
"jobclosedwithbypass": "",
|
||||
"jobconverted": "",
|
||||
"jobdelivery": "",
|
||||
"jobexported": "",
|
||||
@@ -134,7 +135,13 @@
|
||||
"jobstatuschange": "",
|
||||
"jobsupplement": "",
|
||||
"jobsuspend": "",
|
||||
"jobvoid": ""
|
||||
"jobvoid": "",
|
||||
"tasks_completed": "",
|
||||
"tasks_created": "",
|
||||
"tasks_deleted": "",
|
||||
"tasks_uncompleted": "",
|
||||
"tasks_undeleted": "",
|
||||
"tasks_updated": ""
|
||||
}
|
||||
},
|
||||
"billlines": {
|
||||
@@ -223,10 +230,12 @@
|
||||
"markexported": "",
|
||||
"markforreexport": "",
|
||||
"new": "",
|
||||
"nobilllines": "",
|
||||
"noneselected": "",
|
||||
"onlycmforinvoiced": "",
|
||||
"printlabels": "",
|
||||
"retailtotal": "",
|
||||
"returnfrombill": "",
|
||||
"savewithdiscrepancy": "",
|
||||
"state_tax": "",
|
||||
"subtotal": "",
|
||||
@@ -286,7 +295,8 @@
|
||||
"dailypainttarget": "",
|
||||
"default_adjustment_rate": "",
|
||||
"deliver": {
|
||||
"templates": ""
|
||||
"templates": "",
|
||||
"require_actual_delivery_date": ""
|
||||
},
|
||||
"dms": {
|
||||
"apcontrol": "",
|
||||
@@ -637,7 +647,10 @@
|
||||
"payers": ""
|
||||
},
|
||||
"cdk_dealerid": "",
|
||||
"costsmapping": "",
|
||||
"dms_allocations": "",
|
||||
"pbs_serialnumber": "",
|
||||
"profitsmapping": "",
|
||||
"title": ""
|
||||
},
|
||||
"emaillater": "",
|
||||
@@ -860,7 +873,8 @@
|
||||
"inservice": "",
|
||||
"leasereturn": "",
|
||||
"out": "",
|
||||
"sold": ""
|
||||
"sold": "",
|
||||
"unavailable": ""
|
||||
},
|
||||
"successes": {
|
||||
"saved": ""
|
||||
@@ -930,7 +944,8 @@
|
||||
"scheduledindate": "",
|
||||
"scheduledintoday": "",
|
||||
"scheduledoutdate": "",
|
||||
"scheduledouttoday": ""
|
||||
"scheduledouttoday": "",
|
||||
"tasks": ""
|
||||
}
|
||||
},
|
||||
"dms": {
|
||||
@@ -1125,8 +1140,11 @@
|
||||
"delete": "Effacer",
|
||||
"deleteall": "",
|
||||
"deselectall": "",
|
||||
"download": "",
|
||||
"edit": "modifier",
|
||||
"login": "",
|
||||
"next": "",
|
||||
"previous": "",
|
||||
"print": "",
|
||||
"refresh": "",
|
||||
"remove": "",
|
||||
@@ -1350,6 +1368,7 @@
|
||||
"amount": "",
|
||||
"dateOfPayment": "",
|
||||
"descriptions": "",
|
||||
"hint": "",
|
||||
"payer": "",
|
||||
"payername": "",
|
||||
"paymentid": "",
|
||||
@@ -1540,6 +1559,7 @@
|
||||
"voiding": ""
|
||||
},
|
||||
"fields": {
|
||||
"active_tasks": "",
|
||||
"actual_completion": "Achèvement réel",
|
||||
"actual_delivery": "Livraison réelle",
|
||||
"actual_in": "En réel",
|
||||
@@ -2003,6 +2023,7 @@
|
||||
"ppc": "",
|
||||
"ppdnotexported": "",
|
||||
"profileadjustments": "",
|
||||
"profitbypassrequired": "",
|
||||
"profits": "",
|
||||
"prt_dsmk_total": "",
|
||||
"rates": "Les taux",
|
||||
@@ -2024,12 +2045,12 @@
|
||||
"remove_from_ar": "",
|
||||
"returntotals": "",
|
||||
"ro_guard": {
|
||||
"eforce_profit": "",
|
||||
"enforce_ar": "",
|
||||
"enforce_bills": "",
|
||||
"enforce_cm": "",
|
||||
"enforce_labor": "",
|
||||
"enforce_ppd": "",
|
||||
"enforce_profit": "",
|
||||
"enforce_sublet": "",
|
||||
"enforce_validation": "",
|
||||
"enforced": ""
|
||||
@@ -2052,6 +2073,7 @@
|
||||
"supplementnote": "",
|
||||
"suspended": "",
|
||||
"suspense": "",
|
||||
"tasks": "",
|
||||
"threshhold": "",
|
||||
"total_cost": "",
|
||||
"total_cust_payable": "",
|
||||
@@ -2149,6 +2171,7 @@
|
||||
"accounting-payments": "",
|
||||
"accounting-receivables": "",
|
||||
"activejobs": "Emplois actifs",
|
||||
"all_tasks": "",
|
||||
"alljobs": "",
|
||||
"allpayments": "",
|
||||
"availablejobs": "Emplois disponibles",
|
||||
@@ -2157,6 +2180,7 @@
|
||||
"courtesycars-all": "",
|
||||
"courtesycars-contracts": "",
|
||||
"courtesycars-newcontract": "",
|
||||
"create_task": "",
|
||||
"customers": "Les clients",
|
||||
"dashboard": "",
|
||||
"enterbills": "",
|
||||
@@ -2169,6 +2193,7 @@
|
||||
"home": "Accueil",
|
||||
"inventory": "",
|
||||
"jobs": "Emplois",
|
||||
"my_tasks": "",
|
||||
"newjob": "",
|
||||
"owners": "Propriétaires",
|
||||
"parts-queue": "",
|
||||
@@ -2195,6 +2220,7 @@
|
||||
"shop_csi": "",
|
||||
"shop_templates": "",
|
||||
"shop_vendors": "Vendeurs",
|
||||
"tasks": "",
|
||||
"temporarydocs": "",
|
||||
"timetickets": "",
|
||||
"ttapprovals": "",
|
||||
@@ -2388,6 +2414,7 @@
|
||||
"percent_accepted": ""
|
||||
},
|
||||
"labels": {
|
||||
"notyetdispatched": "",
|
||||
"parts_dispatch": ""
|
||||
}
|
||||
},
|
||||
@@ -2504,6 +2531,7 @@
|
||||
"markexported": "",
|
||||
"markreexported": "",
|
||||
"payment": "",
|
||||
"paymentupdate": "",
|
||||
"stripe": ""
|
||||
}
|
||||
},
|
||||
@@ -2605,6 +2633,7 @@
|
||||
"job_costing_ro": "",
|
||||
"job_lifecycle_ro": "",
|
||||
"job_notes": "",
|
||||
"job_tasks": "",
|
||||
"key_tag": "",
|
||||
"labels": {
|
||||
"count": "",
|
||||
@@ -2805,6 +2834,7 @@
|
||||
"parts_orders": "",
|
||||
"payments": "",
|
||||
"scoreboard": "",
|
||||
"tasks": "",
|
||||
"timetickets": ""
|
||||
},
|
||||
"vendor": ""
|
||||
@@ -2890,6 +2920,7 @@
|
||||
"parts_not_recieved_vendor": "",
|
||||
"parts_received_not_scheduled": "",
|
||||
"payments_by_date": "",
|
||||
"payments_by_date_payment": "",
|
||||
"payments_by_date_type": "",
|
||||
"production_by_category": "",
|
||||
"production_by_category_one": "",
|
||||
@@ -2909,6 +2940,8 @@
|
||||
"purchases_by_cost_center_summary": "",
|
||||
"purchases_by_date_range_detail": "",
|
||||
"purchases_by_date_range_summary": "",
|
||||
"purchases_by_ro_detail_date": "",
|
||||
"purchases_by_ro_summary_date": "",
|
||||
"purchases_by_vendor_detailed_date_range": "",
|
||||
"purchases_by_vendor_summary_date_range": "",
|
||||
"purchases_grouped_by_vendor_detailed": "",
|
||||
@@ -2920,6 +2953,8 @@
|
||||
"scoreboard_detail": "",
|
||||
"scoreboard_summary": "",
|
||||
"supplement_ratio_ins_co": "",
|
||||
"tasks_date": "",
|
||||
"tasks_date_employee": "",
|
||||
"thank_you_date": "",
|
||||
"timetickets": "",
|
||||
"timetickets_employee": "",
|
||||
@@ -2999,6 +3034,92 @@
|
||||
"updated": ""
|
||||
}
|
||||
},
|
||||
"tasks": {
|
||||
"actions": {
|
||||
"edit": "",
|
||||
"new": ""
|
||||
},
|
||||
"buttons": {
|
||||
"allTasks": "",
|
||||
"complete": "",
|
||||
"create": "",
|
||||
"delete": "",
|
||||
"edit": "",
|
||||
"myTasks": "",
|
||||
"refresh": ""
|
||||
},
|
||||
"date_presets": {
|
||||
"completion": "",
|
||||
"day": "",
|
||||
"days": "",
|
||||
"delivery": "",
|
||||
"next_week": "",
|
||||
"one_month": "",
|
||||
"three_months": "",
|
||||
"three_weeks": "",
|
||||
"today": "",
|
||||
"tomorrow": "",
|
||||
"two_weeks": ""
|
||||
},
|
||||
"failures": {
|
||||
"completed": "",
|
||||
"created": "",
|
||||
"deleted": "",
|
||||
"updated": ""
|
||||
},
|
||||
"fields": {
|
||||
"actions": "",
|
||||
"assigned_to": "",
|
||||
"bill": "",
|
||||
"billid": "",
|
||||
"completed": "",
|
||||
"created_at": "",
|
||||
"description": "",
|
||||
"due_date": "",
|
||||
"job": {
|
||||
"ro_number": ""
|
||||
},
|
||||
"jobid": "",
|
||||
"jobline": "",
|
||||
"joblineid": "",
|
||||
"parts_order": "",
|
||||
"partsorderid": "",
|
||||
"priorities": {
|
||||
"high": "",
|
||||
"low": "",
|
||||
"medium": ""
|
||||
},
|
||||
"priority": "",
|
||||
"remind_at": "",
|
||||
"title": ""
|
||||
},
|
||||
"placeholders": {
|
||||
"assigned_to": "",
|
||||
"billid": "",
|
||||
"description": "",
|
||||
"jobid": "",
|
||||
"joblineid": "",
|
||||
"partsorderid": ""
|
||||
},
|
||||
"successes": {
|
||||
"completed": "",
|
||||
"created": "",
|
||||
"deleted": "",
|
||||
"updated": ""
|
||||
},
|
||||
"titles": {
|
||||
"all_tasks": "",
|
||||
"completed": "",
|
||||
"deleted": "",
|
||||
"job_tasks": "",
|
||||
"mine": "",
|
||||
"my_tasks": ""
|
||||
},
|
||||
"validation": {
|
||||
"due_at_error_message": "",
|
||||
"remind_at_error_message": ""
|
||||
}
|
||||
},
|
||||
"tech": {
|
||||
"fields": {
|
||||
"employeeid": "",
|
||||
@@ -3103,11 +3224,13 @@
|
||||
"accounting-payables": "",
|
||||
"accounting-payments": "",
|
||||
"accounting-receivables": "",
|
||||
"all_tasks": "",
|
||||
"app": "",
|
||||
"bc": {
|
||||
"accounting-payables": "",
|
||||
"accounting-payments": "",
|
||||
"accounting-receivables": "",
|
||||
"all_tasks": "",
|
||||
"availablejobs": "",
|
||||
"bills-list": "",
|
||||
"contracts": "",
|
||||
@@ -3131,6 +3254,7 @@
|
||||
"jobs-intake": "",
|
||||
"jobs-new": "",
|
||||
"jobs-ready": "",
|
||||
"my_tasks": "",
|
||||
"owner-detail": "",
|
||||
"owners": "",
|
||||
"parts-queue": "",
|
||||
@@ -3145,6 +3269,7 @@
|
||||
"shop-csi": "",
|
||||
"shop-templates": "",
|
||||
"shop-vendors": "",
|
||||
"tasks": "",
|
||||
"temporarydocs": "",
|
||||
"timetickets": "",
|
||||
"ttapprovals": "",
|
||||
@@ -3175,6 +3300,7 @@
|
||||
"jobsdetail": "Travail {{ro_number}} | {{app}}",
|
||||
"jobsdocuments": "Documents de travail {{ro_number}} | {{app}}",
|
||||
"manageroot": "Accueil | {{app}}",
|
||||
"my_tasks": "",
|
||||
"owners": "Tous les propriétaires | {{app}}",
|
||||
"owners-detail": "",
|
||||
"parts-queue": "",
|
||||
@@ -3194,6 +3320,7 @@
|
||||
"shop-csi": "",
|
||||
"shop-templates": "",
|
||||
"shop_vendors": "Vendeurs | {{app}}",
|
||||
"tasks": "",
|
||||
"techconsole": "{{app}}",
|
||||
"techjobclock": "{{app}}",
|
||||
"techjoblookup": "{{app}}",
|
||||
|
||||
@@ -24,6 +24,7 @@ const AuditTrailMapping = {
|
||||
jobimported: () => i18n.t("audit_trail.messages.jobimported"),
|
||||
jobinproductionchange: (inproduction) => i18n.t("audit_trail.messages.jobinproductionchange", { inproduction }),
|
||||
jobinvoiced: () => i18n.t("audit_trail.messages.jobinvoiced"),
|
||||
jobclosedwithbypass: () => i18n.t("audit_trail.messages.jobclosedwithbypass"),
|
||||
jobmodifylbradj: ({ mod_lbr_ty, hours }) => i18n.t("audit_trail.messages.jobmodifylbradj", { mod_lbr_ty, hours }),
|
||||
jobnoteadded: () => i18n.t("audit_trail.messages.jobnoteadded"),
|
||||
jobnoteupdated: () => i18n.t("audit_trail.messages.jobnoteupdated"),
|
||||
@@ -39,7 +40,38 @@ const AuditTrailMapping = {
|
||||
jobstatuschange: (status) => i18n.t("audit_trail.messages.jobstatuschange", { status }),
|
||||
jobsupplement: () => i18n.t("audit_trail.messages.jobsupplement"),
|
||||
jobsuspend: (status) => i18n.t("audit_trail.messages.jobsuspend", { status }),
|
||||
jobvoid: () => i18n.t("audit_trail.messages.jobvoid")
|
||||
jobvoid: () => i18n.t("audit_trail.messages.jobvoid"),
|
||||
// Tasks Entries
|
||||
tasksCreated: (title, createdBy) =>
|
||||
i18n.t("audit_trail.messages.tasks_created", {
|
||||
title,
|
||||
createdBy
|
||||
}),
|
||||
tasksUpdated: (title, updatedBy) =>
|
||||
i18n.t("audit_trail.messages.tasks_updated", {
|
||||
title,
|
||||
updatedBy
|
||||
}),
|
||||
tasksDeleted: (title, deletedBy) =>
|
||||
i18n.t("audit_trail.messages.tasks_deleted", {
|
||||
title,
|
||||
deletedBy
|
||||
}),
|
||||
tasksUndeleted: (title, undeletedBy) =>
|
||||
i18n.t("audit_trail.messages.tasks_undeleted", {
|
||||
title,
|
||||
undeletedBy
|
||||
}),
|
||||
tasksCompleted: (title, completedBy) =>
|
||||
i18n.t("audit_trail.messages.tasks_completed", {
|
||||
title,
|
||||
completedBy
|
||||
}),
|
||||
tasksUncompleted: (title, uncompletedBy) =>
|
||||
i18n.t("audit_trail.messages.tasks_uncompleted", {
|
||||
title,
|
||||
uncompletedBy
|
||||
})
|
||||
};
|
||||
|
||||
export default AuditTrailMapping;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import axios from "axios";
|
||||
import { auth } from "../firebase/firebase.utils";
|
||||
import InstanceRenderManager from "./instanceRenderMgr";
|
||||
|
||||
axios.defaults.baseURL =
|
||||
import.meta.env.VITE_APP_AXIOS_BASE_API_URL ||
|
||||
@@ -12,6 +13,15 @@ export const axiosAuthInterceptorId = axios.interceptors.request.use(
|
||||
if (token) {
|
||||
config.headers.Authorization = `Bearer ${token}`;
|
||||
}
|
||||
InstanceRenderManager({
|
||||
executeFunction: true,
|
||||
args: [],
|
||||
promanager: () => {
|
||||
if (!config.url.startsWith("http://localhost:1337")) {
|
||||
config.headers["Convenient-Company"] = "promanager";
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return config;
|
||||
|
||||
@@ -10,6 +10,7 @@ import client from "../utils/GraphQLClient";
|
||||
import cleanAxios from "./CleanAxios";
|
||||
import { TemplateList } from "./TemplateConstants";
|
||||
import { generateTemplate } from "./graphQLmodifier";
|
||||
import InstanceRenderManager from "./instanceRenderMgr";
|
||||
const server = import.meta.env.VITE_APP_REPORTS_SERVER_URL;
|
||||
|
||||
jsreport.serverUrl = server;
|
||||
@@ -71,8 +72,8 @@ export default async function RenderTemplate(
|
||||
...contextData,
|
||||
...templateObject.variables,
|
||||
...templateObject.context,
|
||||
headerpath: `/${bodyshop.imexshopid}/header.html`,
|
||||
footerpath: `/${bodyshop.imexshopid}/footer.html`,
|
||||
headerpath: `/${InstanceRenderManager({ imex: bodyshop.imexshopid, rome: bodyshop.imexshopid, promanager: "GENERIC" })}/header.html`,
|
||||
footerpath: `/${InstanceRenderManager({ imex: bodyshop.imexshopid, rome: bodyshop.imexshopid, promanager: "GENERIC" })}/footer.html`,
|
||||
bodyshop: bodyshop,
|
||||
filters: templateObject?.filters,
|
||||
sorters: templateObject?.sorters,
|
||||
|
||||
@@ -587,6 +587,14 @@ export const TemplateList = (type, context) => {
|
||||
key: "job_lifecycle_ro",
|
||||
disabled: false,
|
||||
group: "post"
|
||||
},
|
||||
job_tasks: {
|
||||
title: i18n.t("printcenter.jobs.job_tasks"),
|
||||
description: "",
|
||||
subject: i18n.t("printcenter.jobs.job_tasks"),
|
||||
key: "job_tasks",
|
||||
disabled: false,
|
||||
group: "ro"
|
||||
}
|
||||
}
|
||||
: {}),
|
||||
@@ -1073,6 +1081,32 @@ export const TemplateList = (type, context) => {
|
||||
},
|
||||
group: "purchases"
|
||||
},
|
||||
purchases_by_ro_detail_date: {
|
||||
title: i18n.t("reportcenter.templates.purchases_by_ro_detail_date"),
|
||||
description: "",
|
||||
subject: i18n.t("reportcenter.templates.purchases_by_ro_detail_date"),
|
||||
key: "purchases_by_ro_detail_date",
|
||||
//idtype: "vendor",
|
||||
disabled: false,
|
||||
rangeFilter: {
|
||||
object: i18n.t("reportcenter.labels.objects.jobs"),
|
||||
field: i18n.t("jobs.fields.date_invoiced")
|
||||
},
|
||||
group: "purchases"
|
||||
},
|
||||
purchases_by_ro_summary_date: {
|
||||
title: i18n.t("reportcenter.templates.purchases_by_ro_summary_date"),
|
||||
description: "",
|
||||
subject: i18n.t("reportcenter.templates.purchases_by_ro_summary_date"),
|
||||
key: "purchases_by_ro_summary_date",
|
||||
//idtype: "vendor",
|
||||
disabled: false,
|
||||
rangeFilter: {
|
||||
object: i18n.t("reportcenter.labels.objects.jobs"),
|
||||
field: i18n.t("jobs.fields.date_invoiced")
|
||||
},
|
||||
group: "purchases"
|
||||
},
|
||||
job_costing_ro_date_summary: {
|
||||
title: i18n.t("reportcenter.templates.job_costing_ro_date_summary"),
|
||||
description: "",
|
||||
@@ -1160,6 +1194,17 @@ export const TemplateList = (type, context) => {
|
||||
},
|
||||
group: "customers"
|
||||
},
|
||||
payments_by_date_payment: {
|
||||
title: i18n.t("reportcenter.templates.payments_by_date_payment"),
|
||||
subject: i18n.t("reportcenter.templates.payments_by_date_payment"),
|
||||
key: "payments_by_date_payment",
|
||||
disabled: false,
|
||||
rangeFilter: {
|
||||
object: i18n.t("reportcenter.labels.objects.payments"),
|
||||
field: i18n.t("payments.fields.date")
|
||||
},
|
||||
group: "customers"
|
||||
},
|
||||
schedule: {
|
||||
title: i18n.t("reportcenter.templates.schedule"),
|
||||
subject: i18n.t("reportcenter.templates.schedule"),
|
||||
@@ -2089,6 +2134,30 @@ export const TemplateList = (type, context) => {
|
||||
field: i18n.t("jobs.fields.date_invoiced")
|
||||
},
|
||||
group: "jobs"
|
||||
},
|
||||
tasks_date: {
|
||||
title: i18n.t("reportcenter.templates.tasks_date"),
|
||||
subject: i18n.t("reportcenter.templates.tasks_date"),
|
||||
key: "tasks_date",
|
||||
//idtype: "vendor",
|
||||
disabled: false,
|
||||
rangeFilter: {
|
||||
object: i18n.t("reportcenter.labels.objects.tasks"),
|
||||
field: i18n.t("tasks.fields.created_at")
|
||||
},
|
||||
group: "jobs"
|
||||
},
|
||||
tasks_date_employee: {
|
||||
title: i18n.t("reportcenter.templates.tasks_date_employee"),
|
||||
subject: i18n.t("reportcenter.templates.tasks_date_employee"),
|
||||
key: "tasks_date_employee",
|
||||
idtype: "employee",
|
||||
disabled: false,
|
||||
rangeFilter: {
|
||||
object: i18n.t("reportcenter.labels.objects.tasks"),
|
||||
field: i18n.t("tasks.fields.created_at")
|
||||
},
|
||||
group: "jobs"
|
||||
}
|
||||
}
|
||||
: {}),
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user