Compare commits
162 Commits
feature/IO
...
feature/IO
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
079dffce4d | ||
|
|
6005eaee6a | ||
|
|
c069600cfd | ||
|
|
186cbf2c97 | ||
|
|
392988ae11 | ||
|
|
2e33b79eb9 | ||
|
|
fa99ef7b37 | ||
|
|
c4aff1b516 | ||
|
|
61276bb2d1 | ||
|
|
8b89e2eb9d | ||
|
|
9ab41308e7 | ||
|
|
f76052ec9b | ||
|
|
b8841e3ded | ||
|
|
a49b3f6496 | ||
|
|
3e17ec3cf8 | ||
|
|
76c0c7c41e | ||
|
|
025b986f60 | ||
|
|
6e6addd62f | ||
|
|
266c3acf34 | ||
|
|
c4631f50e5 | ||
|
|
ca18291425 | ||
|
|
110fad2abc | ||
|
|
b7456cecd4 | ||
|
|
84db1fe81b | ||
|
|
b539111be8 | ||
|
|
8a8bc5a6ed | ||
|
|
020db91105 | ||
|
|
1dd28af752 | ||
|
|
5ba192eee0 | ||
|
|
8109a12898 | ||
|
|
2deb7fd520 | ||
|
|
f6cd136679 | ||
|
|
e50cb86296 | ||
|
|
a5a01c44fa | ||
|
|
947e0705e4 | ||
|
|
aa8a6a837d | ||
|
|
5db440fc9c | ||
|
|
c299b9376a | ||
|
|
e5d530ea3e | ||
|
|
6da9850946 | ||
|
|
f62609f60c | ||
|
|
b2d8c66e5b | ||
|
|
3c4ed3ba0c | ||
|
|
2e7f827c3f | ||
|
|
dc82b39dc8 | ||
|
|
a9814c1eb1 | ||
|
|
bdb741caf8 | ||
|
|
f50b198c21 | ||
|
|
3495326de3 | ||
|
|
b5973085e7 | ||
|
|
8687214420 | ||
|
|
d61b89a1e5 | ||
|
|
468b42abd2 | ||
|
|
fc03e5f983 | ||
|
|
c4742e38ea | ||
|
|
99e1adbe13 | ||
|
|
eb5c797a43 | ||
|
|
0595c5545e | ||
|
|
12c87ed689 | ||
|
|
55944257aa | ||
|
|
03241778fa | ||
|
|
555b81fb14 | ||
|
|
a56b720e09 | ||
|
|
b89eede164 | ||
|
|
c21cc8d6b9 | ||
|
|
d02a6bc197 | ||
|
|
360c1ce82d | ||
|
|
a7ef02976c | ||
|
|
6a9e36ea4d | ||
|
|
37d4c0a40f | ||
|
|
5ebca3ff06 | ||
|
|
1969a92226 | ||
|
|
8840ffc9ba | ||
|
|
19e42ef397 | ||
|
|
c7eb026986 | ||
|
|
b0dcd3618e | ||
|
|
5f23f135f2 | ||
|
|
159ee7364d | ||
|
|
aa6ad109c9 | ||
|
|
f2a896d568 | ||
|
|
546ebba0bd | ||
|
|
0e75f54d6e | ||
|
|
30f34a17ea | ||
|
|
6035d94404 | ||
|
|
0b7a23d555 | ||
|
|
91fe1f4af9 | ||
|
|
f09cb7b247 | ||
|
|
35a7222f5e | ||
|
|
d444821cf7 | ||
|
|
b5cb520944 | ||
|
|
6814a3bc33 | ||
|
|
19c2b19abc | ||
|
|
22b011139d | ||
|
|
5b30daefe5 | ||
|
|
e015d3574a | ||
|
|
60140902d4 | ||
|
|
84f41b2c11 | ||
|
|
e8b9fcbc6e | ||
|
|
5adf591670 | ||
|
|
f55764e859 | ||
|
|
282fa787a9 | ||
|
|
037efff81c | ||
|
|
e26eb17d09 | ||
|
|
fbea9fde27 | ||
|
|
ce7cf6bdbe | ||
|
|
2c47e5d852 | ||
|
|
a6f809b20a | ||
|
|
2bcad68351 | ||
|
|
6b1b393804 | ||
|
|
c5181d1c5d | ||
|
|
e33ff2a45d | ||
|
|
9eb77964db | ||
|
|
11928d9a7e | ||
|
|
c169bb5d5d | ||
|
|
3cc4f1c63e | ||
|
|
5237b1d535 | ||
|
|
cd56c50cf9 | ||
|
|
a18ce18d72 | ||
|
|
5f66488410 | ||
|
|
d1be7f6e09 | ||
|
|
44f02f28a6 | ||
|
|
6d33622b4e | ||
|
|
f8b8e23ef4 | ||
|
|
db09d09428 | ||
|
|
451820a67c | ||
|
|
ba0ce5027e | ||
|
|
f777d26cc1 | ||
|
|
1463037878 | ||
|
|
7ddec0bb0f | ||
|
|
51c2d3351a | ||
|
|
8323fa6696 | ||
|
|
27a3932c08 | ||
|
|
add88659a4 | ||
|
|
320ad065d0 | ||
|
|
a9bc51949a | ||
|
|
39d1397221 | ||
|
|
b44b71072f | ||
|
|
f3e2a83bab | ||
|
|
0ef030bb89 | ||
|
|
3e9e6baf32 | ||
|
|
c03d45b3fc | ||
|
|
0a9b583c4b | ||
|
|
54ac0c84a7 | ||
|
|
4d59798d8d | ||
|
|
f95dab544d | ||
|
|
41e43dda96 | ||
|
|
cec60db78c | ||
|
|
24d47ae1c5 | ||
|
|
09c4662436 | ||
|
|
9bf6ba9cf0 | ||
|
|
c78b9866a3 | ||
|
|
09c1a8ae35 | ||
|
|
0ef2814de3 | ||
|
|
8e105f0b36 | ||
|
|
ba4da3e35c | ||
|
|
1b8be56c15 | ||
|
|
2b26db78eb | ||
|
|
c2d96922c8 | ||
|
|
70b4ec7948 | ||
|
|
a3ec364034 | ||
|
|
e1728b275b | ||
|
|
10d55df461 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -127,4 +127,6 @@ vitest-report*/
|
|||||||
vitest-coverage/
|
vitest-coverage/
|
||||||
*.vitest.log
|
*.vitest.log
|
||||||
test-output.txt
|
test-output.txt
|
||||||
|
server/job/test/fixtures
|
||||||
|
|
||||||
|
.github
|
||||||
|
|||||||
@@ -56,4 +56,5 @@ COPY . .
|
|||||||
EXPOSE 4000 9229
|
EXPOSE 4000 9229
|
||||||
|
|
||||||
# Start the application
|
# Start the application
|
||||||
CMD ["nodemon", "--legacy-watch", "--inspect=0.0.0.0:9229", "server.js"]
|
RUN echo "Starting the application..."
|
||||||
|
CMD ["nodemon", "--ignore", "./server/job/test/fixtures", "--legacy-watch", "--inspect=0.0.0.0:9229", "server.js"]
|
||||||
|
|||||||
764
_reference/localEmailViewer/package-lock.json
generated
764
_reference/localEmailViewer/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -11,8 +11,8 @@
|
|||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"description": "",
|
"description": "",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"express": "^4.21.1",
|
"express": "^5.1.0",
|
||||||
"mailparser": "^3.7.1",
|
"mailparser": "^3.7.2",
|
||||||
"node-fetch": "^3.3.2"
|
"node-fetch": "^3.3.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -74,50 +74,8 @@
|
|||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
<% } %>
|
<% } %>
|
||||||
<script>
|
<script>!function(w,d,i,s){function l(){if(!d.getElementById(i)){var f=d.getElementsByTagName(s)[0],e=d.createElement(s);e.type="text/javascript",e.async=!0,e.src="https://canny.io/sdk.js",f.parentNode.insertBefore(e,f)}}if("function"!=typeof w.Canny){var c=function(){c.q.push(arguments)};c.q=[],w.Canny=c,"complete"===d.readyState?l():w.attachEvent?w.attachEvent("onload",l):w.addEventListener("load",l,!1)}}(window,document,"canny-jssdk","script");</script>
|
||||||
!(function () {
|
|
||||||
"use strict";
|
|
||||||
var e = [
|
|
||||||
"debug",
|
|
||||||
"destroy",
|
|
||||||
"do",
|
|
||||||
"help",
|
|
||||||
"identify",
|
|
||||||
"is",
|
|
||||||
"off",
|
|
||||||
"on",
|
|
||||||
"ready",
|
|
||||||
"render",
|
|
||||||
"reset",
|
|
||||||
"safe",
|
|
||||||
"set"
|
|
||||||
];
|
|
||||||
if (window.noticeable) console.warn("Noticeable SDK code snippet loaded more than once");
|
|
||||||
else {
|
|
||||||
var n = (window.noticeable = window.noticeable || []);
|
|
||||||
|
|
||||||
function t(e) {
|
|
||||||
return function () {
|
|
||||||
var t = Array.prototype.slice.call(arguments);
|
|
||||||
return t.unshift(e), n.push(t), n;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
!(function () {
|
|
||||||
for (var o = 0; o < e.length; o++) {
|
|
||||||
var r = e[o];
|
|
||||||
n[r] = t(r);
|
|
||||||
}
|
|
||||||
})(),
|
|
||||||
(function () {
|
|
||||||
var e = document.createElement("script");
|
|
||||||
(e.async = !0), (e.src = "https://sdk.noticeable.io/l.js");
|
|
||||||
var n = document.head;
|
|
||||||
n.insertBefore(e, n.firstChild);
|
|
||||||
})();
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
</script>
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||||
|
|||||||
3002
client/package-lock.json
generated
3002
client/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -8,25 +8,25 @@
|
|||||||
"private": true,
|
"private": true,
|
||||||
"proxy": "http://localhost:4000",
|
"proxy": "http://localhost:4000",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ant-design/pro-layout": "^7.22.3",
|
"@ant-design/pro-layout": "^7.22.4",
|
||||||
"@apollo/client": "^3.13.5",
|
"@apollo/client": "^3.13.6",
|
||||||
"@emotion/is-prop-valid": "^1.3.1",
|
"@emotion/is-prop-valid": "^1.3.1",
|
||||||
"@fingerprintjs/fingerprintjs": "^4.6.1",
|
"@fingerprintjs/fingerprintjs": "^4.6.1",
|
||||||
"@firebase/analytics": "^0.10.12",
|
"@firebase/analytics": "^0.10.13",
|
||||||
"@firebase/app": "^0.11.3",
|
"@firebase/app": "^0.12.1",
|
||||||
"@firebase/auth": "^1.9.1",
|
"@firebase/auth": "^1.10.2",
|
||||||
"@firebase/firestore": "^4.7.10",
|
"@firebase/firestore": "^4.7.12",
|
||||||
"@firebase/messaging": "^0.12.17",
|
"@firebase/messaging": "^0.12.18",
|
||||||
"@jsreport/browser-client": "^3.1.0",
|
"@jsreport/browser-client": "^3.1.0",
|
||||||
"@reduxjs/toolkit": "^2.6.1",
|
"@reduxjs/toolkit": "^2.8.1",
|
||||||
"@sentry/cli": "^2.42.4",
|
"@sentry/cli": "^2.45.0",
|
||||||
"@sentry/react": "^9.9.0",
|
"@sentry/react": "^9.18.0",
|
||||||
"@sentry/vite-plugin": "^3.2.2",
|
"@sentry/vite-plugin": "^3.4.0",
|
||||||
"@splitsoftware/splitio-react": "^2.0.1",
|
"@splitsoftware/splitio-react": "^2.1.1",
|
||||||
"@tanem/react-nprogress": "^5.0.53",
|
"@tanem/react-nprogress": "^5.0.53",
|
||||||
"antd": "^5.24.5",
|
"antd": "^5.25.1",
|
||||||
"apollo-link-logger": "^2.0.1",
|
"apollo-link-logger": "^2.0.1",
|
||||||
"apollo-link-sentry": "^4.2.0",
|
"apollo-link-sentry": "^4.3.0",
|
||||||
"autosize": "^6.0.1",
|
"autosize": "^6.0.1",
|
||||||
"axios": "^1.8.4",
|
"axios": "^1.8.4",
|
||||||
"classnames": "^2.5.1",
|
"classnames": "^2.5.1",
|
||||||
@@ -37,18 +37,18 @@
|
|||||||
"dotenv": "^16.4.7",
|
"dotenv": "^16.4.7",
|
||||||
"env-cmd": "^10.1.0",
|
"env-cmd": "^10.1.0",
|
||||||
"exifr": "^7.1.3",
|
"exifr": "^7.1.3",
|
||||||
"graphql": "^16.10.0",
|
"graphql": "^16.11.0",
|
||||||
"i18next": "^24.2.3",
|
"i18next": "^24.2.3",
|
||||||
"i18next-browser-languagedetector": "^8.0.4",
|
"i18next-browser-languagedetector": "^8.1.0",
|
||||||
"immutability-helper": "^3.1.1",
|
"immutability-helper": "^3.1.1",
|
||||||
"libphonenumber-js": "^1.12.6",
|
"libphonenumber-js": "^1.12.8",
|
||||||
"logrocket": "^9.0.2",
|
"logrocket": "^9.0.2",
|
||||||
"markerjs2": "^2.32.4",
|
"markerjs2": "^2.32.4",
|
||||||
"memoize-one": "^6.0.0",
|
"memoize-one": "^6.0.0",
|
||||||
"normalize-url": "^8.0.1",
|
"normalize-url": "^8.0.1",
|
||||||
"object-hash": "^3.0.0",
|
"object-hash": "^3.0.0",
|
||||||
"prop-types": "^15.8.1",
|
"prop-types": "^15.8.1",
|
||||||
"query-string": "^9.1.1",
|
"query-string": "^9.1.2",
|
||||||
"raf-schd": "^4.0.3",
|
"raf-schd": "^4.0.3",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-big-calendar": "^1.18.0",
|
"react-big-calendar": "^1.18.0",
|
||||||
@@ -69,17 +69,17 @@
|
|||||||
"react-resizable": "^3.0.5",
|
"react-resizable": "^3.0.5",
|
||||||
"react-router-dom": "^6.30.0",
|
"react-router-dom": "^6.30.0",
|
||||||
"react-sticky": "^6.0.3",
|
"react-sticky": "^6.0.3",
|
||||||
"react-virtuoso": "^4.12.5",
|
"react-virtuoso": "^4.12.7",
|
||||||
"recharts": "^2.15.0",
|
"recharts": "^2.15.2",
|
||||||
"redux": "^5.0.1",
|
"redux": "^5.0.1",
|
||||||
"redux-actions": "^3.0.3",
|
"redux-actions": "^3.0.3",
|
||||||
"redux-persist": "^6.0.0",
|
"redux-persist": "^6.0.0",
|
||||||
"redux-saga": "^1.3.0",
|
"redux-saga": "^1.3.0",
|
||||||
"redux-state-sync": "^3.1.4",
|
"redux-state-sync": "^3.1.4",
|
||||||
"reselect": "^5.1.1",
|
"reselect": "^5.1.1",
|
||||||
"sass": "^1.86.0",
|
"sass": "^1.88.0",
|
||||||
"socket.io-client": "^4.8.1",
|
"socket.io-client": "^4.8.1",
|
||||||
"styled-components": "^6.1.16",
|
"styled-components": "^6.1.18",
|
||||||
"subscriptions-transport-ws": "^0.11.0",
|
"subscriptions-transport-ws": "^0.11.0",
|
||||||
"use-memo-one": "^1.1.3",
|
"use-memo-one": "^1.1.3",
|
||||||
"vite-plugin-ejs": "^1.7.0",
|
"vite-plugin-ejs": "^1.7.0",
|
||||||
@@ -129,38 +129,38 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@ant-design/icons": "^6.0.0",
|
"@ant-design/icons": "^6.0.0",
|
||||||
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
|
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
|
||||||
"@babel/preset-react": "^7.26.3",
|
"@babel/preset-react": "^7.27.1",
|
||||||
"@dotenvx/dotenvx": "^1.39.0",
|
"@dotenvx/dotenvx": "^1.44.0",
|
||||||
"@emotion/babel-plugin": "^11.13.5",
|
"@emotion/babel-plugin": "^11.13.5",
|
||||||
"@emotion/react": "^11.14.0",
|
"@emotion/react": "^11.14.0",
|
||||||
"@eslint/js": "^9.23.0",
|
"@eslint/js": "^9.26.0",
|
||||||
"@playwright/test": "^1.51.1",
|
"@playwright/test": "^1.51.1",
|
||||||
"@sentry/webpack-plugin": "^3.2.2",
|
"@sentry/webpack-plugin": "^3.4.0",
|
||||||
"@testing-library/dom": "^10.4.0",
|
"@testing-library/dom": "^10.4.0",
|
||||||
"@testing-library/jest-dom": "^6.6.3",
|
"@testing-library/jest-dom": "^6.6.3",
|
||||||
"@testing-library/react": "^16.2.0",
|
"@testing-library/react": "^16.3.0",
|
||||||
"@vitejs/plugin-react": "^4.3.4",
|
"@vitejs/plugin-react": "^4.3.4",
|
||||||
"browserslist": "^4.24.4",
|
"browserslist": "^4.24.5",
|
||||||
"browserslist-to-esbuild": "^2.1.1",
|
"browserslist-to-esbuild": "^2.1.1",
|
||||||
"chalk": "^5.4.1",
|
"chalk": "^5.4.1",
|
||||||
"eslint": "^8.57.1",
|
"eslint": "^8.57.1",
|
||||||
"eslint-config-react-app": "^7.0.1",
|
"eslint-config-react-app": "^7.0.1",
|
||||||
"eslint-plugin-react": "^7.37.4",
|
"eslint-plugin-react": "^7.37.5",
|
||||||
"globals": "^15.15.0",
|
"globals": "^15.15.0",
|
||||||
"jsdom": "^26.0.0",
|
"jsdom": "^26.0.0",
|
||||||
"memfs": "^4.17.0",
|
"memfs": "^4.17.1",
|
||||||
"os-browserify": "^0.3.0",
|
"os-browserify": "^0.3.0",
|
||||||
"playwright": "^1.51.1",
|
"playwright": "^1.51.1",
|
||||||
"react-error-overlay": "^6.1.0",
|
"react-error-overlay": "^6.1.0",
|
||||||
"redux-logger": "^3.0.6",
|
"redux-logger": "^3.0.6",
|
||||||
"source-map-explorer": "^2.5.3",
|
"source-map-explorer": "^2.5.3",
|
||||||
"vite": "^6.2.3",
|
"vite": "^6.3.5",
|
||||||
"vite-plugin-babel": "^1.3.0",
|
"vite-plugin-babel": "^1.3.1",
|
||||||
"vite-plugin-eslint": "^1.8.1",
|
"vite-plugin-eslint": "^1.8.1",
|
||||||
"vite-plugin-node-polyfills": "^0.23.0",
|
"vite-plugin-node-polyfills": "^0.23.0",
|
||||||
"vite-plugin-pwa": "^0.21.2",
|
"vite-plugin-pwa": "^1.0.0",
|
||||||
"vite-plugin-style-import": "^2.0.0",
|
"vite-plugin-style-import": "^2.0.0",
|
||||||
"vitest": "^3.0.9",
|
"vitest": "^3.1.3",
|
||||||
"workbox-window": "^7.3.0"
|
"workbox-window": "^7.3.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,8 +21,8 @@ import "./App.styles.scss";
|
|||||||
import Eula from "../components/eula/eula.component";
|
import Eula from "../components/eula/eula.component";
|
||||||
import InstanceRenderMgr from "../utils/instanceRenderMgr";
|
import InstanceRenderMgr from "../utils/instanceRenderMgr";
|
||||||
import ProductFruitsWrapper from "./ProductFruitsWrapper.jsx";
|
import ProductFruitsWrapper from "./ProductFruitsWrapper.jsx";
|
||||||
import { SocketProvider } from "../contexts/SocketIO/useSocket.jsx";
|
|
||||||
import { NotificationProvider } from "../contexts/Notifications/notificationContext.jsx";
|
import { NotificationProvider } from "../contexts/Notifications/notificationContext.jsx";
|
||||||
|
import SocketProvider from "../contexts/SocketIO/socketProvider.jsx";
|
||||||
|
|
||||||
const ResetPassword = lazy(() => import("../pages/reset-password/reset-password.component"));
|
const ResetPassword = lazy(() => import("../pages/reset-password/reset-password.component"));
|
||||||
const ManagePage = lazy(() => import("../pages/manage/manage.page.container"));
|
const ManagePage = lazy(() => import("../pages/manage/manage.page.container"));
|
||||||
@@ -142,11 +142,10 @@ export function App({ bodyshop, checkUserSession, currentUser, online, setOnline
|
|||||||
>
|
>
|
||||||
<ProductFruitsWrapper
|
<ProductFruitsWrapper
|
||||||
currentUser={currentUser}
|
currentUser={currentUser}
|
||||||
workspaceCode={InstanceRenderMgr({
|
bodyshop={bodyshop}
|
||||||
imex: null,
|
workspaceCode={bodyshop?.tours_enabled ? "9BkbEseqNqxw8jUH" : ""}
|
||||||
rome: "9BkbEseqNqxw8jUH"
|
|
||||||
})}
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<NotificationProvider>
|
<NotificationProvider>
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route
|
<Route
|
||||||
|
|||||||
@@ -1,8 +1,16 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { ProductFruits } from "react-product-fruits";
|
|
||||||
import PropTypes from "prop-types";
|
import PropTypes from "prop-types";
|
||||||
|
import { ProductFruits } from "react-product-fruits";
|
||||||
|
import dayjs from "dayjs";
|
||||||
|
|
||||||
|
const ProductFruitsWrapper = React.memo(({ currentUser, bodyshop, workspaceCode }) => {
|
||||||
|
const featureProps = bodyshop?.features
|
||||||
|
? Object.entries(bodyshop.features).reduce((acc, [key, value]) => {
|
||||||
|
acc[key] = value === true || (typeof value === "string" && dayjs(value).isAfter(dayjs()));
|
||||||
|
return acc;
|
||||||
|
}, {})
|
||||||
|
: {};
|
||||||
|
|
||||||
const ProductFruitsWrapper = React.memo(({ currentUser, workspaceCode }) => {
|
|
||||||
return (
|
return (
|
||||||
workspaceCode &&
|
workspaceCode &&
|
||||||
currentUser?.authorized === true &&
|
currentUser?.authorized === true &&
|
||||||
@@ -14,7 +22,8 @@ const ProductFruitsWrapper = React.memo(({ currentUser, workspaceCode }) => {
|
|||||||
language="en"
|
language="en"
|
||||||
user={{
|
user={{
|
||||||
email: currentUser.email,
|
email: currentUser.email,
|
||||||
username: currentUser.email
|
username: currentUser.email,
|
||||||
|
props: featureProps
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
@@ -28,5 +37,6 @@ ProductFruitsWrapper.propTypes = {
|
|||||||
authorized: PropTypes.bool,
|
authorized: PropTypes.bool,
|
||||||
email: PropTypes.string
|
email: PropTypes.string
|
||||||
}),
|
}),
|
||||||
workspaceCode: PropTypes.string
|
workspaceCode: PropTypes.string,
|
||||||
|
bodyshop: PropTypes.object
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -14,8 +14,21 @@ const mapStateToProps = createStructuredSelector({
|
|||||||
bodyshop: selectBodyshop
|
bodyshop: selectBodyshop
|
||||||
});
|
});
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
setPartsOrderContext: (context) => dispatch(setModalContext({ context: context, modal: "partsOrder" })),
|
setPartsOrderContext: (context) =>
|
||||||
insertAuditTrail: ({ jobid, operation, type }) => dispatch(insertAuditTrail({ jobid, operation, type }))
|
dispatch(
|
||||||
|
setModalContext({
|
||||||
|
context: context,
|
||||||
|
modal: "partsOrder"
|
||||||
|
})
|
||||||
|
),
|
||||||
|
insertAuditTrail: ({ jobid, operation, type }) =>
|
||||||
|
dispatch(
|
||||||
|
insertAuditTrail({
|
||||||
|
jobid,
|
||||||
|
operation,
|
||||||
|
type
|
||||||
|
})
|
||||||
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(BillDetailEditReturn);
|
export default connect(mapStateToProps, mapDispatchToProps)(BillDetailEditReturn);
|
||||||
@@ -69,7 +82,7 @@ export function BillDetailEditReturn({ setPartsOrderContext, insertAuditTrail, b
|
|||||||
<Modal
|
<Modal
|
||||||
open={open}
|
open={open}
|
||||||
onCancel={() => setOpen(false)}
|
onCancel={() => setOpen(false)}
|
||||||
destroyOnClose
|
destroyOnHidden
|
||||||
title={t("bills.actions.return")}
|
title={t("bills.actions.return")}
|
||||||
onOk={() => form.submit()}
|
onOk={() => form.submit()}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ export default function BillDetailEditcontainer() {
|
|||||||
delete search.billid;
|
delete search.billid;
|
||||||
history({ search: queryString.stringify(search) });
|
history({ search: queryString.stringify(search) });
|
||||||
}}
|
}}
|
||||||
destroyOnClose
|
destroyOnHidden
|
||||||
open={search.billid}
|
open={search.billid}
|
||||||
>
|
>
|
||||||
<BillDetailEditComponent />
|
<BillDetailEditComponent />
|
||||||
|
|||||||
@@ -412,7 +412,7 @@ function BillEnterModalContainer({ billEnterModal, toggleModalVisible, bodyshop,
|
|||||||
)}
|
)}
|
||||||
</Space>
|
</Space>
|
||||||
}
|
}
|
||||||
destroyOnClose
|
destroyOnHidden
|
||||||
>
|
>
|
||||||
<Form
|
<Form
|
||||||
onFinish={handleFinish}
|
onFinish={handleFinish}
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ export function ContractsFindModalContainer({ caBcEtfTableModal, toggleModalVisi
|
|||||||
title={t("payments.labels.findermodal")}
|
title={t("payments.labels.findermodal")}
|
||||||
onCancel={() => toggleModalVisible()}
|
onCancel={() => toggleModalVisible()}
|
||||||
onOk={() => toggleModalVisible()}
|
onOk={() => toggleModalVisible()}
|
||||||
destroyOnClose
|
destroyOnHidden
|
||||||
forceRender
|
forceRender
|
||||||
>
|
>
|
||||||
<Form form={form} layout="vertical" autoComplete="no" onFinish={handleFinish}>
|
<Form form={form} layout="vertical" autoComplete="no" onFinish={handleFinish}>
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ function CardPaymentModalContainer({ cardPaymentModal, toggleModalVisible, bodys
|
|||||||
</Button>
|
</Button>
|
||||||
]}
|
]}
|
||||||
width="80%"
|
width="80%"
|
||||||
destroyOnClose
|
destroyOnHidden
|
||||||
>
|
>
|
||||||
<CardPaymentModalComponent />
|
<CardPaymentModalComponent />
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|||||||
@@ -3,11 +3,11 @@ import { getToken } from "@firebase/messaging";
|
|||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useSocket } from "../../contexts/SocketIO/useSocket.jsx";
|
|
||||||
import { messaging, requestForToken } from "../../firebase/firebase.utils";
|
import { messaging, requestForToken } from "../../firebase/firebase.utils";
|
||||||
import ChatPopupComponent from "../chat-popup/chat-popup.component";
|
import ChatPopupComponent from "../chat-popup/chat-popup.component";
|
||||||
import "./chat-affix.styles.scss";
|
import "./chat-affix.styles.scss";
|
||||||
import { registerMessagingHandlers, unregisterMessagingHandlers } from "./registerMessagingSocketHandlers";
|
import { registerMessagingHandlers, unregisterMessagingHandlers } from "./registerMessagingSocketHandlers";
|
||||||
|
import { useSocket } from "../../contexts/SocketIO/useSocket.js";
|
||||||
|
|
||||||
export function ChatAffixContainer({ bodyshop, chatVisible }) {
|
export function ChatAffixContainer({ bodyshop, chatVisible }) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|||||||
@@ -3,10 +3,10 @@ import { Button } from "antd";
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { TOGGLE_CONVERSATION_ARCHIVE } from "../../graphql/conversations.queries";
|
import { TOGGLE_CONVERSATION_ARCHIVE } from "../../graphql/conversations.queries";
|
||||||
import { useSocket } from "../../contexts/SocketIO/useSocket.jsx";
|
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors.js";
|
import { selectBodyshop } from "../../redux/user/user.selectors.js";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
|
import { useSocket } from "../../contexts/SocketIO/useSocket.js";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop
|
bodyshop: selectBodyshop
|
||||||
|
|||||||
@@ -4,10 +4,10 @@ import { Link } from "react-router-dom";
|
|||||||
import { logImEXEvent } from "../../firebase/firebase.utils";
|
import { logImEXEvent } from "../../firebase/firebase.utils";
|
||||||
import { REMOVE_CONVERSATION_TAG } from "../../graphql/job-conversations.queries";
|
import { REMOVE_CONVERSATION_TAG } from "../../graphql/job-conversations.queries";
|
||||||
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
|
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
|
||||||
import { useSocket } from "../../contexts/SocketIO/useSocket.jsx";
|
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors.js";
|
import { selectBodyshop } from "../../redux/user/user.selectors.js";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
|
import { useSocket } from "../../contexts/SocketIO/useSocket.js";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop
|
bodyshop: selectBodyshop
|
||||||
|
|||||||
@@ -3,11 +3,11 @@ import axios from "axios";
|
|||||||
import { useCallback, useEffect, useState } from "react";
|
import { useCallback, useEffect, useState } from "react";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import { useSocket } from "../../contexts/SocketIO/useSocket.jsx";
|
|
||||||
import { CONVERSATION_SUBSCRIPTION_BY_PK, GET_CONVERSATION_DETAILS } from "../../graphql/conversations.queries";
|
import { CONVERSATION_SUBSCRIPTION_BY_PK, GET_CONVERSATION_DETAILS } from "../../graphql/conversations.queries";
|
||||||
import { selectSelectedConversation } from "../../redux/messaging/messaging.selectors";
|
import { selectSelectedConversation } from "../../redux/messaging/messaging.selectors";
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
import ChatConversationComponent from "./chat-conversation.component";
|
import ChatConversationComponent from "./chat-conversation.component";
|
||||||
|
import { useSocket } from "../../contexts/SocketIO/useSocket.js";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
selectedConversation: selectSelectedConversation,
|
selectedConversation: selectSelectedConversation,
|
||||||
|
|||||||
@@ -4,11 +4,11 @@ import { Input, Spin, Tag, Tooltip } from "antd";
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { UPDATE_CONVERSATION_LABEL } from "../../graphql/conversations.queries";
|
import { UPDATE_CONVERSATION_LABEL } from "../../graphql/conversations.queries";
|
||||||
import { useSocket } from "../../contexts/SocketIO/useSocket.jsx";
|
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors.js";
|
import { selectBodyshop } from "../../redux/user/user.selectors.js";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
|
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
|
||||||
|
import { useSocket } from "../../contexts/SocketIO/useSocket.js";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop
|
bodyshop: selectBodyshop
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { connect } from "react-redux";
|
|||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import { openChatByPhone } from "../../redux/messaging/messaging.actions";
|
import { openChatByPhone } from "../../redux/messaging/messaging.actions";
|
||||||
import PhoneFormItem, { PhoneItemFormatterValidation } from "../form-items-formatted/phone-form-item.component";
|
import PhoneFormItem, { PhoneItemFormatterValidation } from "../form-items-formatted/phone-form-item.component";
|
||||||
import { useSocket } from "../../contexts/SocketIO/useSocket.jsx";
|
import { useSocket } from "../../contexts/SocketIO/useSocket.js";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
//currentUser: selectCurrentUser
|
//currentUser: selectCurrentUser
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import PhoneNumberFormatter from "../../utils/PhoneFormatter";
|
|||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
import { searchingForConversation } from "../../redux/messaging/messaging.selectors";
|
import { searchingForConversation } from "../../redux/messaging/messaging.selectors";
|
||||||
import { useSocket } from "../../contexts/SocketIO/useSocket.jsx";
|
import { useSocket } from "../../contexts/SocketIO/useSocket.js";
|
||||||
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
|
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
|
|||||||
@@ -12,9 +12,9 @@ import ChatConversationListComponent from "../chat-conversation-list/chat-conver
|
|||||||
import ChatConversationContainer from "../chat-conversation/chat-conversation.container";
|
import ChatConversationContainer from "../chat-conversation/chat-conversation.container";
|
||||||
import ChatNewConversation from "../chat-new-conversation/chat-new-conversation.component";
|
import ChatNewConversation from "../chat-new-conversation/chat-new-conversation.component";
|
||||||
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
|
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
|
||||||
import { useSocket } from "../../contexts/SocketIO/useSocket.jsx";
|
|
||||||
|
|
||||||
import "./chat-popup.styles.scss";
|
import "./chat-popup.styles.scss";
|
||||||
|
import { useSocket } from "../../contexts/SocketIO/useSocket.js";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
selectedConversation: selectSelectedConversation,
|
selectedConversation: selectSelectedConversation,
|
||||||
|
|||||||
@@ -8,10 +8,10 @@ import { logImEXEvent } from "../../firebase/firebase.utils";
|
|||||||
import { INSERT_CONVERSATION_TAG } from "../../graphql/job-conversations.queries";
|
import { INSERT_CONVERSATION_TAG } from "../../graphql/job-conversations.queries";
|
||||||
import { SEARCH_FOR_JOBS } from "../../graphql/jobs.queries";
|
import { SEARCH_FOR_JOBS } from "../../graphql/jobs.queries";
|
||||||
import ChatTagRo from "./chat-tag-ro.component";
|
import ChatTagRo from "./chat-tag-ro.component";
|
||||||
import { useSocket } from "../../contexts/SocketIO/useSocket.jsx";
|
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors.js";
|
import { selectBodyshop } from "../../redux/user/user.selectors.js";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
|
import { useSocket } from "../../contexts/SocketIO/useSocket.js";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop
|
bodyshop: selectBodyshop
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ export function ContractsFindModalContainer({
|
|||||||
title={t("contracts.labels.findermodal")}
|
title={t("contracts.labels.findermodal")}
|
||||||
onCancel={() => toggleModalVisible()}
|
onCancel={() => toggleModalVisible()}
|
||||||
onOk={() => toggleModalVisible()}
|
onOk={() => toggleModalVisible()}
|
||||||
destroyOnClose
|
destroyOnHidden
|
||||||
forceRender
|
forceRender
|
||||||
>
|
>
|
||||||
<Form form={form} layout="vertical" autoComplete="no" onFinish={handleFinish}>
|
<Form form={form} layout="vertical" autoComplete="no" onFinish={handleFinish}>
|
||||||
|
|||||||
@@ -152,7 +152,7 @@ export function EmailOverlayContainer({ emailConfig, modalVisible, toggleEmailOv
|
|||||||
}, [modalVisible]); // eslint-disable-line react-hooks/exhaustive-deps
|
}, [modalVisible]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
destroyOnClose={true}
|
destroyOnHidden
|
||||||
open={modalVisible}
|
open={modalVisible}
|
||||||
maskClosable={false}
|
maskClosable={false}
|
||||||
width={"80%"}
|
width={"80%"}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { useTranslation } from "react-i18next";
|
|||||||
const { Option } = Select;
|
const { Option } = Select;
|
||||||
//To be used as a form element only.
|
//To be used as a form element only.
|
||||||
|
|
||||||
const EmployeeSearchSelect = ({ options, ...props }) => {
|
const EmployeeSearchSelect = ({ options, showEmail, ...props }) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -21,12 +21,16 @@ const EmployeeSearchSelect = ({ options, ...props }) => {
|
|||||||
{options
|
{options
|
||||||
? options.map((o) => (
|
? options.map((o) => (
|
||||||
<Option key={o.id} value={o.id} search={`${o.employee_number} ${o.first_name} ${o.last_name}`}>
|
<Option key={o.id} value={o.id} search={`${o.employee_number} ${o.first_name} ${o.last_name}`}>
|
||||||
<Space>
|
<Space size="small">
|
||||||
{`${o.employee_number} ${o.first_name} ${o.last_name}`}
|
{`${o.employee_number ?? ""} ${o.first_name} ${o.last_name}`}
|
||||||
|
<Tag color="green" style={{ padding: "0.1 0.1rem", marginRight: "1px", marginLeft: "1px" }}>
|
||||||
<Tag color="green">
|
|
||||||
{o.flat_rate ? t("timetickets.labels.flat_rate") : t("timetickets.labels.straight_time")}
|
{o.flat_rate ? t("timetickets.labels.flat_rate") : t("timetickets.labels.straight_time")}
|
||||||
</Tag>
|
</Tag>
|
||||||
|
{showEmail && o.user_email ? (
|
||||||
|
<Tag color="blue" style={{ padding: "0.1 0.1rem", marginRight: "1px", marginLeft: "1px" }}>
|
||||||
|
{o.user_email}
|
||||||
|
</Tag>
|
||||||
|
) : null}
|
||||||
</Space>
|
</Space>
|
||||||
</Option>
|
</Option>
|
||||||
))
|
))
|
||||||
|
|||||||
@@ -81,8 +81,9 @@ export function HasFeatureAccess({ featureName, bodyshop, bypass, debug = false
|
|||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
bodyshop?.features?.allAccess ||
|
bodyshop?.features?.allAccess ||
|
||||||
bodyshop?.features?.[featureName] ||
|
(typeof bodyshop?.features?.[featureName] === "boolean"
|
||||||
dayjs(bodyshop?.features[featureName]).isAfter(dayjs())
|
? bodyshop?.features?.[featureName]
|
||||||
|
: dayjs(bodyshop?.features?.[featureName]).isAfter(dayjs()))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -40,7 +40,6 @@ import { RiSurveyLine } from "react-icons/ri";
|
|||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import { useSocket } from "../../contexts/SocketIO/useSocket.jsx";
|
|
||||||
import { GET_UNREAD_COUNT } from "../../graphql/notifications.queries.js";
|
import { GET_UNREAD_COUNT } from "../../graphql/notifications.queries.js";
|
||||||
import { selectRecentItems, selectSelectedHeader } from "../../redux/application/application.selectors";
|
import { selectRecentItems, selectSelectedHeader } from "../../redux/application/application.selectors";
|
||||||
import { setModalContext } from "../../redux/modals/modals.actions";
|
import { setModalContext } from "../../redux/modals/modals.actions";
|
||||||
@@ -51,6 +50,8 @@ import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
|||||||
import { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component";
|
import { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component";
|
||||||
import LockWrapper from "../lock-wrapper/lock-wrapper.component";
|
import LockWrapper from "../lock-wrapper/lock-wrapper.component";
|
||||||
import NotificationCenterContainer from "../notification-center/notification-center.container.jsx";
|
import NotificationCenterContainer from "../notification-center/notification-center.container.jsx";
|
||||||
|
import { useSocket } from "../../contexts/SocketIO/useSocket.js";
|
||||||
|
import { useIsEmployee } from "../../utils/useIsEmployee.js";
|
||||||
|
|
||||||
// Redux mappings
|
// Redux mappings
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
@@ -98,6 +99,7 @@ function Header({
|
|||||||
const baseTitleRef = useRef(document.title || "");
|
const baseTitleRef = useRef(document.title || "");
|
||||||
const lastSetTitleRef = useRef("");
|
const lastSetTitleRef = useRef("");
|
||||||
const userAssociationId = bodyshop?.associations?.[0]?.id;
|
const userAssociationId = bodyshop?.associations?.[0]?.id;
|
||||||
|
const isEmployee = useIsEmployee(bodyshop, currentUser);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data: unreadData,
|
data: unreadData,
|
||||||
@@ -682,7 +684,7 @@ function Header({
|
|||||||
icon: unreadLoading ? (
|
icon: unreadLoading ? (
|
||||||
<Spin size="small" />
|
<Spin size="small" />
|
||||||
) : (
|
) : (
|
||||||
<Badge offset={[8, 0]} size="small" count={unreadCount}>
|
<Badge offset={[8, 0]} size="small" count={isEmployee ? unreadCount : 0}>
|
||||||
<BellFilled />
|
<BellFilled />
|
||||||
</Badge>
|
</Badge>
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ export function InventoryUpsertModalContainer({ currentUser, bodyshop, inventory
|
|||||||
onCancel={() => {
|
onCancel={() => {
|
||||||
toggleModalVisible();
|
toggleModalVisible();
|
||||||
}}
|
}}
|
||||||
destroyOnClose
|
destroyOnHidden
|
||||||
>
|
>
|
||||||
<Form form={form} onFinish={handleFinish} layout="vertical">
|
<Form form={form} onFinish={handleFinish} layout="vertical">
|
||||||
<InventoryUpsertModal form={form} />
|
<InventoryUpsertModal form={form} />
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { AlertFilled } from "@ant-design/icons";
|
import { AlertFilled } from "@ant-design/icons";
|
||||||
import { useMutation } from "@apollo/client";
|
import { useLazyQuery, useMutation } from "@apollo/client";
|
||||||
import { Button, Divider, Dropdown, Form, Input, Popover, Select, Space } from "antd";
|
import { Button, Divider, Dropdown, Form, Input, Popover, Select, Space } from "antd";
|
||||||
import parsePhoneNumber from "libphonenumber-js";
|
import parsePhoneNumber from "libphonenumber-js";
|
||||||
import queryString from "query-string";
|
import queryString from "query-string";
|
||||||
@@ -8,24 +8,30 @@ import { useTranslation } from "react-i18next";
|
|||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { Link, useLocation, useNavigate } from "react-router-dom";
|
import { Link, useLocation, useNavigate } from "react-router-dom";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import { useSocket } from "../../contexts/SocketIO/useSocket.jsx";
|
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
|
||||||
|
import { useSocket } from "../../contexts/SocketIO/useSocket.js";
|
||||||
import { UPDATE_APPOINTMENT } from "../../graphql/appointments.queries";
|
import { UPDATE_APPOINTMENT } from "../../graphql/appointments.queries";
|
||||||
|
import { GET_JOB_BY_PK_QUICK_INTAKE, JOB_PRODUCTION_TOGGLE } from "../../graphql/jobs.queries";
|
||||||
|
import { insertAuditTrail } from "../../redux/application/application.actions";
|
||||||
import { openChatByPhone, setMessage } from "../../redux/messaging/messaging.actions";
|
import { openChatByPhone, setMessage } from "../../redux/messaging/messaging.actions";
|
||||||
import { setModalContext } from "../../redux/modals/modals.actions";
|
import { setModalContext } from "../../redux/modals/modals.actions";
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
|
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
||||||
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||||
|
import { DateTimeFormatterFunction } from "../../utils/DateFormatter";
|
||||||
import dayjs from "../../utils/day";
|
import dayjs from "../../utils/day";
|
||||||
import { GenerateDocument } from "../../utils/RenderTemplate";
|
import { GenerateDocument } from "../../utils/RenderTemplate";
|
||||||
import { TemplateList } from "../../utils/TemplateConstants";
|
import { TemplateList } from "../../utils/TemplateConstants";
|
||||||
import ChatOpenButton from "../chat-open-button/chat-open-button.component";
|
import ChatOpenButton from "../chat-open-button/chat-open-button.component";
|
||||||
import DataLabel from "../data-label/data-label.component";
|
import DataLabel from "../data-label/data-label.component";
|
||||||
|
import FormDateTimePickerComponent from "../form-date-time-picker/form-date-time-picker.component";
|
||||||
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
|
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
|
||||||
import ProductionListColumnComment from "../production-list-columns/production-list-columns.comment.component";
|
import ProductionListColumnComment from "../production-list-columns/production-list-columns.comment.component";
|
||||||
import ScheduleManualEvent from "../schedule-manual-event/schedule-manual-event.component";
|
import ScheduleManualEvent from "../schedule-manual-event/schedule-manual-event.component";
|
||||||
|
import { HasFeatureAccess } from "./../feature-wrapper/feature-wrapper.component";
|
||||||
import ScheduleAtChange from "./job-at-change.component";
|
import ScheduleAtChange from "./job-at-change.component";
|
||||||
import ScheduleEventColor from "./schedule-event.color.component";
|
import ScheduleEventColor from "./schedule-event.color.component";
|
||||||
import ScheduleEventNote from "./schedule-event.note.component";
|
import ScheduleEventNote from "./schedule-event.note.component";
|
||||||
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
|
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop
|
bodyshop: selectBodyshop
|
||||||
@@ -33,7 +39,8 @@ const mapStateToProps = createStructuredSelector({
|
|||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
setScheduleContext: (context) => dispatch(setModalContext({ context: context, modal: "schedule" })),
|
setScheduleContext: (context) => dispatch(setModalContext({ context: context, modal: "schedule" })),
|
||||||
openChatByPhone: (phone) => dispatch(openChatByPhone(phone)),
|
openChatByPhone: (phone) => dispatch(openChatByPhone(phone)),
|
||||||
setMessage: (text) => dispatch(setMessage(text))
|
setMessage: (text) => dispatch(setMessage(text)),
|
||||||
|
insertAuditTrail: ({ jobid, operation }) => dispatch(insertAuditTrail({ jobid, operation }))
|
||||||
});
|
});
|
||||||
|
|
||||||
export function ScheduleEventComponent({
|
export function ScheduleEventComponent({
|
||||||
@@ -43,16 +50,42 @@ export function ScheduleEventComponent({
|
|||||||
event,
|
event,
|
||||||
refetch,
|
refetch,
|
||||||
handleCancel,
|
handleCancel,
|
||||||
setScheduleContext
|
setScheduleContext,
|
||||||
|
insertAuditTrail
|
||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const history = useNavigate();
|
const history = useNavigate();
|
||||||
const searchParams = queryString.parse(useLocation().search);
|
const searchParams = queryString.parse(useLocation().search);
|
||||||
const [updateAppointment] = useMutation(UPDATE_APPOINTMENT);
|
const [updateAppointment] = useMutation(UPDATE_APPOINTMENT);
|
||||||
|
const [mutationUpdateJob] = useMutation(JOB_PRODUCTION_TOGGLE);
|
||||||
const [title, setTitle] = useState(event.title);
|
const [title, setTitle] = useState(event.title);
|
||||||
const { socket } = useSocket();
|
const { socket } = useSocket();
|
||||||
const notification = useNotification();
|
const notification = useNotification();
|
||||||
|
const [form] = Form.useForm();
|
||||||
|
const [popOverVisible, setPopOverVisible] = useState(false);
|
||||||
|
|
||||||
|
const [getJobDetails] = useLazyQuery(GET_JOB_BY_PK_QUICK_INTAKE, {
|
||||||
|
variables: { id: event.job?.id },
|
||||||
|
onCompleted: (data) => {
|
||||||
|
if (data?.jobs_by_pk) {
|
||||||
|
const totalHours =
|
||||||
|
(data.jobs_by_pk.labhrs?.aggregate?.sum?.mod_lb_hrs || 0) +
|
||||||
|
(data.jobs_by_pk.larhrs?.aggregate?.sum?.mod_lb_hrs || 0);
|
||||||
|
form.setFieldsValue({
|
||||||
|
actual_in: data.jobs_by_pk.actual_in ? data.jobs_by_pk.actual_in : dayjs(),
|
||||||
|
scheduled_completion: data.jobs_by_pk.scheduled_completion
|
||||||
|
? data.jobs_by_pk.scheduled_completion
|
||||||
|
: totalHours && bodyshop.ss_configuration.nobusinessdays
|
||||||
|
? dayjs().businessDaysAdd(totalHours / (bodyshop.target_touchtime || 1), "day")
|
||||||
|
: dayjs().add(totalHours / (bodyshop.target_touchtime || 1), "day"),
|
||||||
|
scheduled_delivery: data.jobs_by_pk.scheduled_delivery
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
fetchPolicy: "network-only"
|
||||||
|
});
|
||||||
|
|
||||||
const blockContent = (
|
const blockContent = (
|
||||||
<Space direction="vertical" wrap>
|
<Space direction="vertical" wrap>
|
||||||
@@ -89,6 +122,74 @@ export function ScheduleEventComponent({
|
|||||||
</Space>
|
</Space>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const handleConvert = async (values) => {
|
||||||
|
const res = await mutationUpdateJob({
|
||||||
|
variables: {
|
||||||
|
jobId: event.job.id,
|
||||||
|
job: {
|
||||||
|
...values,
|
||||||
|
status: bodyshop.md_ro_statuses.default_arrived,
|
||||||
|
inproduction: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!res.errors) {
|
||||||
|
notification["success"]({
|
||||||
|
message: t("jobs.successes.converted")
|
||||||
|
});
|
||||||
|
insertAuditTrail({
|
||||||
|
jobid: event.job.id,
|
||||||
|
operation: AuditTrailMapping.jobintake(
|
||||||
|
res.data.update_jobs.returning[0].status,
|
||||||
|
DateTimeFormatterFunction(values.scheduled_completion)
|
||||||
|
)
|
||||||
|
});
|
||||||
|
setPopOverVisible(false);
|
||||||
|
refetch();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const popMenu = (
|
||||||
|
<div onClick={(e) => e.stopPropagation()}>
|
||||||
|
<Form layout="vertical" form={form} onFinish={handleConvert}>
|
||||||
|
<Form.Item
|
||||||
|
name={["actual_in"]}
|
||||||
|
label={t("jobs.fields.actual_in")}
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: true
|
||||||
|
//message: t("general.validation.required"),
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<FormDateTimePickerComponent disabled={event.ro_number} />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
name={["scheduled_completion"]}
|
||||||
|
label={t("jobs.fields.scheduled_completion")}
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: true
|
||||||
|
//message: t("general.validation.required"),
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<FormDateTimePickerComponent disabled={event.ro_number} />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item name={["scheduled_delivery"]} label={t("jobs.fields.scheduled_delivery")}>
|
||||||
|
<FormDateTimePickerComponent disabled={event.ro_number} />
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Space wrap>
|
||||||
|
<Button type="primary" onClick={() => form.submit()}>
|
||||||
|
{t("general.actions.save")}
|
||||||
|
</Button>
|
||||||
|
</Space>
|
||||||
|
</Form>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
const popoverContent = (
|
const popoverContent = (
|
||||||
<div style={{ maxWidth: "40vw" }}>
|
<div style={{ maxWidth: "40vw" }}>
|
||||||
{!event.isintake ? (
|
{!event.isintake ? (
|
||||||
@@ -294,7 +395,7 @@ export function ScheduleEventComponent({
|
|||||||
) : (
|
) : (
|
||||||
<ScheduleManualEvent event={event} />
|
<ScheduleManualEvent event={event} />
|
||||||
)}
|
)}
|
||||||
{event.isintake ? (
|
{event.isintake && HasFeatureAccess({ featureName: "checklist", bodyshop }) ? (
|
||||||
<Link
|
<Link
|
||||||
to={{
|
to={{
|
||||||
pathname: `/manage/jobs/${event.job && event.job.id}/intake`,
|
pathname: `/manage/jobs/${event.job && event.job.id}/intake`,
|
||||||
@@ -303,7 +404,23 @@ export function ScheduleEventComponent({
|
|||||||
>
|
>
|
||||||
<Button disabled={event.arrived}>{t("appointments.actions.intake")}</Button>
|
<Button disabled={event.arrived}>{t("appointments.actions.intake")}</Button>
|
||||||
</Link>
|
</Link>
|
||||||
) : null}
|
) : (
|
||||||
|
<Popover //open={open}
|
||||||
|
content={popMenu}
|
||||||
|
open={popOverVisible}
|
||||||
|
onOpenChange={setPopOverVisible}
|
||||||
|
onClick={(e) => {
|
||||||
|
if (event.job?.id) {
|
||||||
|
e.stopPropagation();
|
||||||
|
getJobDetails();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
getPopupContainer={(trigger) => trigger.parentNode}
|
||||||
|
trigger="click"
|
||||||
|
>
|
||||||
|
<Button disabled={event.arrived}>{t("jobs.actions.intake_quick")}</Button>
|
||||||
|
</Popover>
|
||||||
|
)}
|
||||||
</Space>
|
</Space>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ export function JobCostingModalContainer({ jobCostingModal, toggleModalVisible }
|
|||||||
}}
|
}}
|
||||||
cancelButtonProps={{ style: { display: "none" } }}
|
cancelButtonProps={{ style: { display: "none" } }}
|
||||||
width="90%"
|
width="90%"
|
||||||
destroyOnClose
|
destroyOnHidden
|
||||||
>
|
>
|
||||||
{!costingData ? (
|
{!costingData ? (
|
||||||
<LoadingSpinner loading={true} />
|
<LoadingSpinner loading={true} />
|
||||||
|
|||||||
@@ -6,7 +6,8 @@ import { useTranslation } from "react-i18next";
|
|||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { Link, useLocation, useNavigate } from "react-router-dom";
|
import { Link, useLocation, useNavigate } from "react-router-dom";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import { useSocket } from "../../contexts/SocketIO/useSocket.jsx";
|
import { useSocket } from "../../contexts/SocketIO/useSocket.js";
|
||||||
|
|
||||||
import { logImEXEvent } from "../../firebase/firebase.utils";
|
import { logImEXEvent } from "../../firebase/firebase.utils";
|
||||||
import { QUERY_JOB_CARD_DETAILS, UPDATE_JOB } from "../../graphql/jobs.queries";
|
import { QUERY_JOB_CARD_DETAILS, UPDATE_JOB } from "../../graphql/jobs.queries";
|
||||||
import { insertAuditTrail } from "../../redux/application/application.actions.js";
|
import { insertAuditTrail } from "../../redux/application/application.actions.js";
|
||||||
@@ -31,7 +32,13 @@ const mapStateToProps = createStructuredSelector({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
setPrintCenterContext: (context) => dispatch(setModalContext({ context: context, modal: "printCenter" })),
|
setPrintCenterContext: (context) =>
|
||||||
|
dispatch(
|
||||||
|
setModalContext({
|
||||||
|
context: context,
|
||||||
|
modal: "printCenter"
|
||||||
|
})
|
||||||
|
),
|
||||||
insertAuditTrail: ({ jobid, operation, type }) =>
|
insertAuditTrail: ({ jobid, operation, type }) =>
|
||||||
dispatch(
|
dispatch(
|
||||||
insertAuditTrail({
|
insertAuditTrail({
|
||||||
@@ -86,7 +93,7 @@ export function JobDetailCards({ bodyshop, setPrintCenterContext, insertAuditTra
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Drawer open={!!selected} destroyOnClose width={drawerPercentage} placement="right" onClose={handleDrawerClose}>
|
<Drawer open={!!selected} destroyOnHidden width={drawerPercentage} placement="right" onClose={handleDrawerClose}>
|
||||||
{loading ? <LoadingSpinner /> : null}
|
{loading ? <LoadingSpinner /> : null}
|
||||||
{error ? <AlertComponent message={error.message} type="error" /> : null}
|
{error ? <AlertComponent message={error.message} type="error" /> : null}
|
||||||
{data ? (
|
{data ? (
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { Col, Row } from "antd";
|
import { Col, Row } from "antd";
|
||||||
import React, { useState } from "react";
|
import { useState } from "react";
|
||||||
|
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
||||||
import JobReconciliationBillsTable from "../job-reconciliation-bills-table/job-reconciliation-bills-table.component";
|
import JobReconciliationBillsTable from "../job-reconciliation-bills-table/job-reconciliation-bills-table.component";
|
||||||
import JobReconciliationPartsTable from "../job-reconciliation-parts-table/job-reconciliation-parts-table.component";
|
import JobReconciliationPartsTable from "../job-reconciliation-parts-table/job-reconciliation-parts-table.component";
|
||||||
import JobReconciliationTotals from "../job-reconciliation-totals/job-reconciliation-totals.component";
|
import JobReconciliationTotals from "../job-reconciliation-totals/job-reconciliation-totals.component";
|
||||||
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
|
||||||
|
|
||||||
export default function JobReconciliationModalComponent({ job, bills }) {
|
export default function JobReconciliationModalComponent({ job, bills }) {
|
||||||
const jobLineState = useState([]);
|
const jobLineState = useState([]);
|
||||||
@@ -20,7 +20,7 @@ export default function JobReconciliationModalComponent({ job, bills }) {
|
|||||||
|
|
||||||
const filterFunction = InstanceRenderManager({
|
const filterFunction = InstanceRenderManager({
|
||||||
imex: (j) =>
|
imex: (j) =>
|
||||||
(j.part_type !== null && j.part_type !== "PAE") ||
|
(j.part_type !== null && j.part_type !== "PAE" && j.act_price !== 0 && j.part_qty !== 0) ||
|
||||||
(j.line_desc && j.line_desc.toLowerCase().includes("towing") && j.lbr_op === "OP13") ||
|
(j.line_desc && j.line_desc.toLowerCase().includes("towing") && j.lbr_op === "OP13") ||
|
||||||
j.db_ref === "936004", //ADD SHIPPING LINE.
|
j.db_ref === "936004", //ADD SHIPPING LINE.
|
||||||
rome: (j) =>
|
rome: (j) =>
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ function JobReconciliationModalContainer({ reconciliationModal, toggleModalVisib
|
|||||||
onOk={handleCancel}
|
onOk={handleCancel}
|
||||||
onCancel={handleCancel}
|
onCancel={handleCancel}
|
||||||
cancelButtonProps={{ display: "none" }}
|
cancelButtonProps={{ display: "none" }}
|
||||||
destroyOnClose
|
destroyOnHidden
|
||||||
className="imex-reconciliation-modal"
|
className="imex-reconciliation-modal"
|
||||||
>
|
>
|
||||||
{loading && <LoadingSpinner loading={loading} />}
|
{loading && <LoadingSpinner loading={loading} />}
|
||||||
|
|||||||
@@ -24,7 +24,8 @@ export default function JobWatcherToggleComponent({
|
|||||||
handleToggleSelf,
|
handleToggleSelf,
|
||||||
handleRemoveWatcher,
|
handleRemoveWatcher,
|
||||||
handleWatcherSelect,
|
handleWatcherSelect,
|
||||||
handleTeamSelect
|
handleTeamSelect,
|
||||||
|
isEmployee
|
||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
@@ -66,22 +67,32 @@ export default function JobWatcherToggleComponent({
|
|||||||
<List>
|
<List>
|
||||||
<List.Item
|
<List.Item
|
||||||
actions={[
|
actions={[
|
||||||
<Button
|
<Tooltip title={!isEmployee ? t("notifications.tooltips.not-employee") : ""} placement="top">
|
||||||
type={isWatching ? "primary" : "default"}
|
<span>
|
||||||
danger={!isWatching}
|
<Button
|
||||||
icon={isWatching ? <EyeOutlined /> : <EyeFilled />}
|
type={isWatching ? "primary" : "default"}
|
||||||
size="medium"
|
danger={!isWatching}
|
||||||
onClick={handleToggleSelf}
|
icon={isWatching ? <EyeOutlined /> : <EyeFilled />}
|
||||||
loading={adding || removing}
|
size="medium"
|
||||||
>
|
onClick={handleToggleSelf}
|
||||||
{isWatching ? t("notifications.labels.unwatch") : t("notifications.labels.watch")}
|
loading={adding || removing}
|
||||||
</Button>
|
disabled={!isEmployee || adding || removing}
|
||||||
|
>
|
||||||
|
{isWatching ? t("notifications.labels.unwatch") : t("notifications.labels.watch")}
|
||||||
|
</Button>
|
||||||
|
</span>
|
||||||
|
</Tooltip>
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<List.Item.Meta>
|
<List.Item.Meta>
|
||||||
<Text type="secondary" style={{ marginBottom: 8, display: "block" }}>
|
<Text type="secondary" style={{ marginBottom: 8, display: "block" }}>
|
||||||
{t("notifications.labels.watching-issue")}
|
{t("notifications.labels.watching-issue")}
|
||||||
</Text>
|
</Text>
|
||||||
|
{!isEmployee && (
|
||||||
|
<Text type="danger" style={{ marginBottom: 8, display: "block" }}>
|
||||||
|
{t("notifications.tooltips.not-employee")}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
</List.Item.Meta>
|
</List.Item.Meta>
|
||||||
</List.Item>
|
</List.Item>
|
||||||
</List>
|
</List>
|
||||||
@@ -98,12 +109,16 @@ export default function JobWatcherToggleComponent({
|
|||||||
<EmployeeSearchSelectComponent
|
<EmployeeSearchSelectComponent
|
||||||
style={{ minWidth: "100%" }}
|
style={{ minWidth: "100%" }}
|
||||||
options={
|
options={
|
||||||
bodyshop?.employees?.filter((e) =>
|
bodyshop?.employees?.filter(
|
||||||
jobWatchers.every((w) => w.user_email !== e.user_email && e.active && e.user_email)
|
(e) =>
|
||||||
|
e.user_email && // Ensure user_email is not null or undefined
|
||||||
|
e.active && // Ensure employee is active
|
||||||
|
jobWatchers.every((w) => w.user_email !== e.user_email) // Ensure not already a watcher
|
||||||
) || []
|
) || []
|
||||||
}
|
}
|
||||||
placeholder={t("notifications.labels.employee-search")}
|
placeholder={t("notifications.labels.employee-search")}
|
||||||
value={selectedWatcher}
|
value={selectedWatcher}
|
||||||
|
showEmail={true}
|
||||||
onChange={(value) => {
|
onChange={(value) => {
|
||||||
setSelectedWatcher(value);
|
setSelectedWatcher(value);
|
||||||
handleWatcherSelect(value);
|
handleWatcherSelect(value);
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { createStructuredSelector } from "reselect";
|
|||||||
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors.js";
|
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors.js";
|
||||||
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
||||||
import JobWatcherToggleComponent from "./job-watcher-toggle.component.jsx";
|
import JobWatcherToggleComponent from "./job-watcher-toggle.component.jsx";
|
||||||
|
import { useIsEmployee } from "../../utils/useIsEmployee.js";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop,
|
bodyshop: selectBodyshop,
|
||||||
@@ -21,13 +22,14 @@ function JobWatcherToggleContainer({ job, currentUser, bodyshop }) {
|
|||||||
splitKey: bodyshop && bodyshop.imexshopid
|
splitKey: bodyshop && bodyshop.imexshopid
|
||||||
});
|
});
|
||||||
|
|
||||||
const userEmail = currentUser.email;
|
const isEmployee = useIsEmployee(bodyshop, currentUser);
|
||||||
const jobid = job.id;
|
|
||||||
|
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const [selectedWatcher, setSelectedWatcher] = useState(null);
|
const [selectedWatcher, setSelectedWatcher] = useState(null);
|
||||||
const [selectedTeam, setSelectedTeam] = useState(null);
|
const [selectedTeam, setSelectedTeam] = useState(null);
|
||||||
|
|
||||||
|
const userEmail = currentUser.email;
|
||||||
|
const jobid = job.id;
|
||||||
|
|
||||||
// Fetch current watchers with refetch capability
|
// Fetch current watchers with refetch capability
|
||||||
const {
|
const {
|
||||||
data: watcherData,
|
data: watcherData,
|
||||||
@@ -139,13 +141,13 @@ function JobWatcherToggleContainer({ job, currentUser, bodyshop }) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const handleToggleSelf = useCallback(async () => {
|
const handleToggleSelf = useCallback(async () => {
|
||||||
if (adding || removing) return;
|
if (adding || removing || !isEmployee) return;
|
||||||
if (isWatching) {
|
if (isWatching) {
|
||||||
await removeWatcher({ variables: { jobid, userEmail } });
|
await removeWatcher({ variables: { jobid, userEmail } });
|
||||||
} else {
|
} else {
|
||||||
await addWatcher({ variables: { jobid, userEmail } });
|
await addWatcher({ variables: { jobid, userEmail } });
|
||||||
}
|
}
|
||||||
}, [isWatching, addWatcher, removeWatcher, jobid, userEmail, adding, removing]);
|
}, [isWatching, addWatcher, removeWatcher, jobid, userEmail, adding, removing, isEmployee]);
|
||||||
|
|
||||||
const handleRemoveWatcher = useCallback(
|
const handleRemoveWatcher = useCallback(
|
||||||
async (email) => {
|
async (email) => {
|
||||||
@@ -187,7 +189,16 @@ function JobWatcherToggleContainer({ job, currentUser, bodyshop }) {
|
|||||||
setSelectedTeam(null);
|
setSelectedTeam(null);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await Promise.all(newWatchers.map((email) => addWatcher({ variables: { jobid, userEmail: email } })));
|
await Promise.all(
|
||||||
|
newWatchers.map((email) =>
|
||||||
|
addWatcher({
|
||||||
|
variables: {
|
||||||
|
jobid,
|
||||||
|
userEmail: email
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
);
|
||||||
},
|
},
|
||||||
[jobWatchers, addWatcher, jobid, adding]
|
[jobWatchers, addWatcher, jobid, adding]
|
||||||
);
|
);
|
||||||
@@ -212,6 +223,7 @@ function JobWatcherToggleContainer({ job, currentUser, bodyshop }) {
|
|||||||
handleWatcherSelect={handleWatcherSelect}
|
handleWatcherSelect={handleWatcherSelect}
|
||||||
handleTeamSelect={handleTeamSelect}
|
handleTeamSelect={handleTeamSelect}
|
||||||
currentUser={currentUser}
|
currentUser={currentUser}
|
||||||
|
isEmployee={isEmployee} // Pass isEmployee to the component
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -106,7 +106,12 @@ export function JobsAdminDatesChange({ insertAuditTrail, job }) {
|
|||||||
<Form.Item label={t("jobs.fields.date_open")} name="date_open">
|
<Form.Item label={t("jobs.fields.date_open")} name="date_open">
|
||||||
<DateTimePicker />
|
<DateTimePicker />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
<Form.Item label={t("jobs.fields.estimate_sent_approval")} name="estimate_sent_approval">
|
||||||
|
<DateTimePicker />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label={t("jobs.fields.estimate_approved")} name="estimate_approved">
|
||||||
|
<DateTimePicker />
|
||||||
|
</Form.Item>
|
||||||
<Form.Item label={t("jobs.fields.date_scheduled")} name="date_scheduled">
|
<Form.Item label={t("jobs.fields.date_scheduled")} name="date_scheduled">
|
||||||
<DateTimePicker />
|
<DateTimePicker />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|||||||
@@ -4,11 +4,12 @@ import { Col, Row } from "antd";
|
|||||||
import Axios from "axios";
|
import Axios from "axios";
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
import queryString from "query-string";
|
import queryString from "query-string";
|
||||||
import React, { useCallback, useEffect, useState } from "react";
|
import { useCallback, useEffect, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { useLocation, useNavigate } from "react-router-dom";
|
import { useLocation, useNavigate } from "react-router-dom";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
|
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
|
||||||
import { logImEXEvent } from "../../firebase/firebase.utils";
|
import { logImEXEvent } from "../../firebase/firebase.utils";
|
||||||
import {
|
import {
|
||||||
DELETE_AVAILABLE_JOB,
|
DELETE_AVAILABLE_JOB,
|
||||||
@@ -33,7 +34,6 @@ import OwnerFindModalContainer from "../owner-find-modal/owner-find-modal.contai
|
|||||||
import { GetSupplementDelta } from "./jobs-available-supplement.estlines.util";
|
import { GetSupplementDelta } from "./jobs-available-supplement.estlines.util";
|
||||||
import HeaderFields from "./jobs-available-supplement.headerfields";
|
import HeaderFields from "./jobs-available-supplement.headerfields";
|
||||||
import JobsAvailableTableComponent from "./jobs-available-table.component";
|
import JobsAvailableTableComponent from "./jobs-available-table.component";
|
||||||
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
|
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop,
|
bodyshop: selectBodyshop,
|
||||||
@@ -195,7 +195,7 @@ export function JobsAvailableContainer({ bodyshop, currentUser, insertAuditTrail
|
|||||||
|
|
||||||
await deleteJob({
|
await deleteJob({
|
||||||
variables: { id: estData.id }
|
variables: { id: estData.id }
|
||||||
}).then((r) => {
|
}).then(() => {
|
||||||
refetch();
|
refetch();
|
||||||
setInsertLoading(false);
|
setInsertLoading(false);
|
||||||
});
|
});
|
||||||
@@ -315,7 +315,7 @@ export function JobsAvailableContainer({ bodyshop, currentUser, insertAuditTrail
|
|||||||
|
|
||||||
deleteJob({
|
deleteJob({
|
||||||
variables: { id: estData.id }
|
variables: { id: estData.id }
|
||||||
}).then((r) => {
|
}).then(() => {
|
||||||
refetch();
|
refetch();
|
||||||
setInsertLoading(false);
|
setInsertLoading(false);
|
||||||
});
|
});
|
||||||
@@ -372,7 +372,7 @@ export function JobsAvailableContainer({ bodyshop, currentUser, insertAuditTrail
|
|||||||
loadEstData({ variables: { id: record.id } });
|
loadEstData({ variables: { id: record.id } });
|
||||||
modalSearchState[1](record.clm_no);
|
modalSearchState[1](record.clm_no);
|
||||||
setJobModalVisible(true);
|
setJobModalVisible(true);
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -456,7 +456,7 @@ function replaceEmpty(someObj, replaceValue = null) {
|
|||||||
return JSON.parse(temp);
|
return JSON.parse(temp);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function CheckTaxRatesUSA(estData, bodyshop) {
|
async function CheckTaxRatesUSA(estData) {
|
||||||
if (!estData.parts_tax_rates?.PAM) {
|
if (!estData.parts_tax_rates?.PAM) {
|
||||||
estData.parts_tax_rates.PAM = estData.parts_tax_rates.PAC;
|
estData.parts_tax_rates.PAM = estData.parts_tax_rates.PAC;
|
||||||
}
|
}
|
||||||
@@ -568,7 +568,7 @@ async function CheckTaxRates(estData, bodyshop) {
|
|||||||
});
|
});
|
||||||
//}
|
//}
|
||||||
}
|
}
|
||||||
function ResolveCCCLineIssues(estData, bodyshop) {
|
function ResolveCCCLineIssues(estData) {
|
||||||
//Find all misc amounts, populate them to the act price.
|
//Find all misc amounts, populate them to the act price.
|
||||||
//This needs to be done before cleansing unq_seq since some misc prices could move over.
|
//This needs to be done before cleansing unq_seq since some misc prices could move over.
|
||||||
estData.joblines.data.forEach((line) => {
|
estData.joblines.data.forEach((line) => {
|
||||||
@@ -585,6 +585,9 @@ function ResolveCCCLineIssues(estData, bodyshop) {
|
|||||||
// line.notes += ` | ET/UT Update (prev = ${line.mod_lbr_ty})`;
|
// line.notes += ` | ET/UT Update (prev = ${line.mod_lbr_ty})`;
|
||||||
line.mod_lbr_ty = "LAR";
|
line.mod_lbr_ty = "LAR";
|
||||||
}
|
}
|
||||||
|
if (line.mod_lbr_ty === "OTSL") {
|
||||||
|
line.mod_lbr_ty = line.mod_lbr_hrs === 0 ? null : "LAB";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { selectJobReadOnly } from "../../redux/application/application.selectors
|
|||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component";
|
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component";
|
||||||
import FormRow from "../layout-form-row/layout-form-row.component";
|
import FormRow from "../layout-form-row/layout-form-row.component";
|
||||||
|
import dayjs from "../../utils/day";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
jobRO: selectJobReadOnly,
|
jobRO: selectJobReadOnly,
|
||||||
@@ -40,6 +41,20 @@ export function JobsDetailDatesComponent({ jobRO, job, bodyshop }) {
|
|||||||
<Form.Item label={t("jobs.fields.date_rentalresp")} name="date_rentalresp">
|
<Form.Item label={t("jobs.fields.date_rentalresp")} name="date_rentalresp">
|
||||||
<DateTimePicker disabled={jobRO} />
|
<DateTimePicker disabled={jobRO} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
<Form.Item label={t("jobs.fields.estimate_sent_approval")} name="estimate_sent_approval">
|
||||||
|
<DateTimePicker
|
||||||
|
disabled={true}
|
||||||
|
value={job.estimate_sent_approval ? dayjs(job.estimate_sent_approval) : null}
|
||||||
|
placeholder={t("general.labels.na")}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label={t("jobs.fields.estimate_approved")} name="estimate_approved">
|
||||||
|
<DateTimePicker
|
||||||
|
disabled={true}
|
||||||
|
value={job.estimate_approved ? dayjs(job.estimate_approved) : null}
|
||||||
|
placeholder={t("general.labels.na")}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
</FormRow>
|
</FormRow>
|
||||||
|
|
||||||
<FormRow header={t("jobs.forms.scheddates")}>
|
<FormRow header={t("jobs.forms.scheddates")}>
|
||||||
@@ -76,21 +91,15 @@ export function JobsDetailDatesComponent({ jobRO, job, bodyshop }) {
|
|||||||
<DateTimePicker disabled={jobRO} />
|
<DateTimePicker disabled={jobRO} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item shouldUpdate>
|
<Form.Item shouldUpdate>
|
||||||
{() => {
|
{() => (
|
||||||
return (
|
<Form.Item
|
||||||
<Form.Item
|
label={t("jobs.fields.actual_completion")}
|
||||||
label={t("jobs.fields.actual_completion")}
|
name="actual_completion"
|
||||||
name="actual_completion"
|
rules={[{ required: jobInPostProduction }]}
|
||||||
rules={[
|
>
|
||||||
{
|
<DateTimePicker disabled={jobRO} />
|
||||||
required: jobInPostProduction
|
</Form.Item>
|
||||||
}
|
)}
|
||||||
]}
|
|
||||||
>
|
|
||||||
<DateTimePicker disabled={jobRO} />
|
|
||||||
</Form.Item>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label={t("jobs.fields.scheduled_delivery")} name="scheduled_delivery">
|
<Form.Item label={t("jobs.fields.scheduled_delivery")} name="scheduled_delivery">
|
||||||
<DateTimePicker disabled={jobRO} />
|
<DateTimePicker disabled={jobRO} />
|
||||||
@@ -103,15 +112,12 @@ export function JobsDetailDatesComponent({ jobRO, job, bodyshop }) {
|
|||||||
<Form.Item label={t("jobs.fields.date_invoiced")} name="date_invoiced">
|
<Form.Item label={t("jobs.fields.date_invoiced")} name="date_invoiced">
|
||||||
<DateTimePicker disabled={true || jobRO} />
|
<DateTimePicker disabled={true || jobRO} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item label={t("jobs.fields.date_exported")} name="date_exported">
|
<Form.Item label={t("jobs.fields.date_exported")} name="date_exported">
|
||||||
<DateTimePicker disabled={true || jobRO} />
|
<DateTimePicker disabled={true || jobRO} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item label={t("jobs.fields.date_void")} name="date_void">
|
<Form.Item label={t("jobs.fields.date_void")} name="date_void">
|
||||||
<DateTimePicker disabled={true || jobRO} />
|
<DateTimePicker disabled={true || jobRO} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item label={t("jobs.fields.date_lost_sale")} name="date_lost_sale">
|
<Form.Item label={t("jobs.fields.date_lost_sale")} name="date_lost_sale">
|
||||||
<DateTimePicker disabled={true || jobRO} />
|
<DateTimePicker disabled={true || jobRO} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { Col, Form, Input, InputNumber, Row, Select, Space, Switch } from "antd";
|
import { Col, Form, Input, InputNumber, Row, Select, Space, Switch } from "antd";
|
||||||
import React from "react";
|
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
@@ -188,6 +187,12 @@ export function JobsDetailGeneral({ bodyshop, jobRO, job, form }) {
|
|||||||
<Form.Item label={t("jobs.fields.tlos_ind")} name="tlos_ind" valuePropName="checked">
|
<Form.Item label={t("jobs.fields.tlos_ind")} name="tlos_ind" valuePropName="checked">
|
||||||
<Switch disabled={jobRO} />
|
<Switch disabled={jobRO} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
<Form.Item label={t("jobs.fields.hit_and_run")} name="hit_and_run" valuePropName="checked">
|
||||||
|
<Switch disabled={jobRO} />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label={t("jobs.fields.acv_amount")} name="acv_amount">
|
||||||
|
<CurrencyInput disabled={jobRO} min={0} />
|
||||||
|
</Form.Item>
|
||||||
</FormRow>
|
</FormRow>
|
||||||
</Col>
|
</Col>
|
||||||
<Col {...lossColDamage}>
|
<Col {...lossColDamage}>
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { useTranslation } from "react-i18next";
|
|||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { Link, useNavigate } from "react-router-dom";
|
import { Link, useNavigate } from "react-router-dom";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import { useSocket } from "../../contexts/SocketIO/useSocket.jsx";
|
import { useSocket } from "../../contexts/SocketIO/useSocket.js";
|
||||||
import { auth, logImEXEvent } from "../../firebase/firebase.utils";
|
import { auth, logImEXEvent } from "../../firebase/firebase.utils";
|
||||||
import { CANCEL_APPOINTMENTS_BY_JOB_ID, INSERT_MANUAL_APPT } from "../../graphql/appointments.queries";
|
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 { GET_CURRENT_QUESTIONSET_ID, INSERT_CSI } from "../../graphql/csi.queries";
|
||||||
@@ -133,6 +133,16 @@ export function JobsDetailHeaderActions({
|
|||||||
const { socket } = useSocket();
|
const { socket } = useSocket();
|
||||||
const notification = useNotification();
|
const notification = useNotification();
|
||||||
|
|
||||||
|
const isDevEnv = import.meta.env.DEV;
|
||||||
|
const isProdEnv = import.meta.env.PROD;
|
||||||
|
const userEmail = currentUser?.email || "";
|
||||||
|
|
||||||
|
const devEmails = ["imex.dev", "rome.dev"];
|
||||||
|
const prodEmails = ["imex.prod", "rome.prod", "imex.test", "rome.test"];
|
||||||
|
|
||||||
|
const hasValidEmail = (emails) => emails.some((email) => userEmail.endsWith(email));
|
||||||
|
const canSubmitForTesting = (isDevEnv && hasValidEmail(devEmails)) || (isProdEnv && hasValidEmail(prodEmails));
|
||||||
|
|
||||||
const {
|
const {
|
||||||
treatments: { ImEXPay }
|
treatments: { ImEXPay }
|
||||||
} = useSplitTreatments({
|
} = useSplitTreatments({
|
||||||
@@ -171,7 +181,7 @@ export function JobsDetailHeaderActions({
|
|||||||
{ defaultOpenStatus: bodyshop.md_ro_statuses.default_imported },
|
{ defaultOpenStatus: bodyshop.md_ro_statuses.default_imported },
|
||||||
(newJobId) => {
|
(newJobId) => {
|
||||||
history(`/manage/jobs/${newJobId}`);
|
history(`/manage/jobs/${newJobId}`);
|
||||||
notification["success"]({
|
notification.success({
|
||||||
message: t("jobs.successes.duplicated")
|
message: t("jobs.successes.duplicated")
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -181,7 +191,7 @@ export function JobsDetailHeaderActions({
|
|||||||
const handleDuplicateConfirm = () =>
|
const handleDuplicateConfirm = () =>
|
||||||
DuplicateJob(client, job.id, { defaultOpenStatus: bodyshop.md_ro_statuses.default_imported }, (newJobId) => {
|
DuplicateJob(client, job.id, { defaultOpenStatus: bodyshop.md_ro_statuses.default_imported }, (newJobId) => {
|
||||||
history(`/manage/jobs/${newJobId}`);
|
history(`/manage/jobs/${newJobId}`);
|
||||||
notification["success"]({
|
notification.success({
|
||||||
message: t("jobs.successes.duplicated")
|
message: t("jobs.successes.duplicated")
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -217,13 +227,13 @@ export function JobsDetailHeaderActions({
|
|||||||
const result = await deleteJob({ variables: { id: job.id } });
|
const result = await deleteJob({ variables: { id: job.id } });
|
||||||
|
|
||||||
if (!result.errors) {
|
if (!result.errors) {
|
||||||
notification["success"]({
|
notification.success({
|
||||||
message: t("jobs.successes.delete")
|
message: t("jobs.successes.delete")
|
||||||
});
|
});
|
||||||
//go back to jobs list.
|
//go back to jobs list.
|
||||||
history(`/manage/`);
|
history(`/manage/`);
|
||||||
} else {
|
} else {
|
||||||
notification["error"]({
|
notification.error({
|
||||||
message: t("jobs.errors.deleted", {
|
message: t("jobs.errors.deleted", {
|
||||||
error: JSON.stringify(result.errors)
|
error: JSON.stringify(result.errors)
|
||||||
})
|
})
|
||||||
@@ -275,9 +285,9 @@ export function JobsDetailHeaderActions({
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!result.errors) {
|
if (!result.errors) {
|
||||||
notification["success"]({ message: t("csi.successes.created") });
|
notification.success({ message: t("csi.successes.created") });
|
||||||
} else {
|
} else {
|
||||||
notification["error"]({
|
notification.error({
|
||||||
message: t("csi.errors.creating", {
|
message: t("csi.errors.creating", {
|
||||||
message: JSON.stringify(result.errors)
|
message: JSON.stringify(result.errors)
|
||||||
})
|
})
|
||||||
@@ -316,7 +326,7 @@ export function JobsDetailHeaderActions({
|
|||||||
`${window.location.protocol}//${window.location.host}/csi/${result.data.insert_csi.returning[0].id}`
|
`${window.location.protocol}//${window.location.host}/csi/${result.data.insert_csi.returning[0].id}`
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
notification["error"]({
|
notification.error({
|
||||||
message: t("messaging.error.invalidphone")
|
message: t("messaging.error.invalidphone")
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -328,7 +338,7 @@ export function JobsDetailHeaderActions({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
notification["error"]({
|
notification.error({
|
||||||
message: t("csi.errors.notconfigured")
|
message: t("csi.errors.notconfigured")
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -358,7 +368,7 @@ export function JobsDetailHeaderActions({
|
|||||||
});
|
});
|
||||||
setMessage(`${window.location.protocol}//${window.location.host}/csi/${job.csiinvites[0].id}`);
|
setMessage(`${window.location.protocol}//${window.location.host}/csi/${job.csiinvites[0].id}`);
|
||||||
} else {
|
} else {
|
||||||
notification["error"]({
|
notification.error({
|
||||||
message: t("messaging.error.invalidphone")
|
message: t("messaging.error.invalidphone")
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -398,7 +408,7 @@ export function JobsDetailHeaderActions({
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!result.errors) {
|
if (!result.errors) {
|
||||||
notification["success"]({
|
notification.success({
|
||||||
message: t("jobs.successes.voided")
|
message: t("jobs.successes.voided")
|
||||||
});
|
});
|
||||||
insertAuditTrail({
|
insertAuditTrail({
|
||||||
@@ -409,7 +419,7 @@ export function JobsDetailHeaderActions({
|
|||||||
//go back to jobs list.
|
//go back to jobs list.
|
||||||
history(`/manage/`);
|
history(`/manage/`);
|
||||||
} else {
|
} else {
|
||||||
notification["error"]({
|
notification.error({
|
||||||
message: t("jobs.errors.voiding", {
|
message: t("jobs.errors.voiding", {
|
||||||
error: JSON.stringify(result.errors)
|
error: JSON.stringify(result.errors)
|
||||||
})
|
})
|
||||||
@@ -442,7 +452,7 @@ export function JobsDetailHeaderActions({
|
|||||||
console.log("handle -> XML", QbXmlResponse);
|
console.log("handle -> XML", QbXmlResponse);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log("Error getting QBXML from Server.", error);
|
console.log("Error getting QBXML from Server.", error);
|
||||||
notification["error"]({
|
notification.error({
|
||||||
message: t("jobs.errors.exporting", {
|
message: t("jobs.errors.exporting", {
|
||||||
error: "Unable to retrieve QBXML. " + JSON.stringify(error.message)
|
error: "Unable to retrieve QBXML. " + JSON.stringify(error.message)
|
||||||
})
|
})
|
||||||
@@ -460,7 +470,7 @@ export function JobsDetailHeaderActions({
|
|||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log("Error connecting to quickbooks or partner.", error);
|
console.log("Error connecting to quickbooks or partner.", error);
|
||||||
notification["error"]({
|
notification.error({
|
||||||
message: t("jobs.errors.exporting-partner")
|
message: t("jobs.errors.exporting-partner")
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -556,7 +566,7 @@ export function JobsDetailHeaderActions({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (!jobUpdate.errors) {
|
if (!jobUpdate.errors) {
|
||||||
notification["success"]({
|
notification.success({
|
||||||
message: t("appointments.successes.canceled")
|
message: t("appointments.successes.canceled")
|
||||||
});
|
});
|
||||||
insertAuditTrail({
|
insertAuditTrail({
|
||||||
@@ -931,11 +941,11 @@ export function JobsDetailHeaderActions({
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!result.errors) {
|
if (!result.errors) {
|
||||||
notification["success"]({
|
notification.success({
|
||||||
message: t("jobs.successes.partsqueue")
|
message: t("jobs.successes.partsqueue")
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
notification["error"]({
|
notification.error({
|
||||||
message: t("jobs.errors.saving", {
|
message: t("jobs.errors.saving", {
|
||||||
error: JSON.stringify(result.errors)
|
error: JSON.stringify(result.errors)
|
||||||
})
|
})
|
||||||
@@ -1111,6 +1121,27 @@ export function JobsDetailHeaderActions({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (canSubmitForTesting) {
|
||||||
|
menuItems.push({
|
||||||
|
key: "submitfortesting",
|
||||||
|
id: "job-actions-submitfortesting",
|
||||||
|
label: t("menus.jobsactions.submit-for-testing"),
|
||||||
|
onClick: async () => {
|
||||||
|
try {
|
||||||
|
await axios.post("/job/totals-recorder", { id: job.id });
|
||||||
|
notification.success({
|
||||||
|
message: t("general.messages.submit-for-testing")
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`Error submitting job for testing: ${err?.message}`);
|
||||||
|
notification.error({
|
||||||
|
message: t("general.errors.submit-for-testing-error")
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const menu = {
|
const menu = {
|
||||||
items: menuItems,
|
items: menuItems,
|
||||||
key: "popovermenu"
|
key: "popovermenu"
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { useEffect, useState } from "react";
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
|
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
|
||||||
import { GET_JOB_BY_PK_QUICK_INTAKE, JOB_PRODUCTION_TOGGLE } from "../../graphql/jobs.queries";
|
import { GET_JOB_BY_PK_QUICK_INTAKE, JOB_PRODUCTION_TOGGLE } from "../../graphql/jobs.queries";
|
||||||
import { insertAuditTrail } from "../../redux/application/application.actions";
|
import { insertAuditTrail } from "../../redux/application/application.actions";
|
||||||
import { selectJobReadOnly } from "../../redux/application/application.selectors";
|
import { selectJobReadOnly } from "../../redux/application/application.selectors";
|
||||||
@@ -12,7 +13,6 @@ import { selectBodyshop } from "../../redux/user/user.selectors";
|
|||||||
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
||||||
import { DateTimeFormatterFunction } from "../../utils/DateFormatter";
|
import { DateTimeFormatterFunction } from "../../utils/DateFormatter";
|
||||||
import FormDateTimePickerComponent from "../form-date-time-picker/form-date-time-picker.component";
|
import FormDateTimePickerComponent from "../form-date-time-picker/form-date-time-picker.component";
|
||||||
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
|
|
||||||
import LoadingSpinner from "../loading-spinner/loading-spinner.component.jsx";
|
import LoadingSpinner from "../loading-spinner/loading-spinner.component.jsx";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
@@ -44,9 +44,16 @@ export function JobsDetailHeaderActionsToggleProduction({
|
|||||||
variables: { id: job.id },
|
variables: { id: job.id },
|
||||||
onCompleted: (data) => {
|
onCompleted: (data) => {
|
||||||
if (data?.jobs_by_pk) {
|
if (data?.jobs_by_pk) {
|
||||||
|
const totalHours =
|
||||||
|
(data.jobs_by_pk.labhrs?.aggregate?.sum?.mod_lb_hrs || 0) +
|
||||||
|
(data.jobs_by_pk.larhrs?.aggregate?.sum?.mod_lb_hrs || 0);
|
||||||
form.setFieldsValue({
|
form.setFieldsValue({
|
||||||
actual_in: data.jobs_by_pk.actual_in ? data.jobs_by_pk.actual_in : dayjs(),
|
actual_in: data.jobs_by_pk.actual_in ? data.jobs_by_pk.actual_in : dayjs(),
|
||||||
scheduled_completion: data.jobs_by_pk.scheduled_completion,
|
scheduled_completion: data.jobs_by_pk.scheduled_completion
|
||||||
|
? data.jobs_by_pk.scheduled_completion
|
||||||
|
: totalHours && bodyshop.ss_configuration.nobusinessdays
|
||||||
|
? dayjs().businessDaysAdd(totalHours / (bodyshop.target_touchtime || 1), "day")
|
||||||
|
: dayjs().add(totalHours / (bodyshop.target_touchtime || 1), "day"),
|
||||||
actual_completion: data.jobs_by_pk.actual_completion,
|
actual_completion: data.jobs_by_pk.actual_completion,
|
||||||
scheduled_delivery: data.jobs_by_pk.scheduled_delivery,
|
scheduled_delivery: data.jobs_by_pk.scheduled_delivery,
|
||||||
actual_delivery: data.jobs_by_pk.actual_delivery
|
actual_delivery: data.jobs_by_pk.actual_delivery
|
||||||
|
|||||||
@@ -1,15 +1,18 @@
|
|||||||
import { BranchesOutlined, ExclamationCircleFilled, PauseCircleOutlined, WarningFilled } from "@ant-design/icons";
|
import { BranchesOutlined, ExclamationCircleFilled, PauseCircleOutlined, WarningFilled } from "@ant-design/icons";
|
||||||
import { Card, Col, Divider, Row, Space, Tag, Tooltip } from "antd";
|
import { Card, Checkbox, Col, Divider, Row, Space, Tag, Tooltip } from "antd";
|
||||||
import React, { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
|
import { useMutation } from "@apollo/client";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import { selectJobReadOnly } from "../../redux/application/application.selectors";
|
import { selectJobReadOnly } from "../../redux/application/application.selectors";
|
||||||
import { setModalContext } from "../../redux/modals/modals.actions";
|
import { setModalContext } from "../../redux/modals/modals.actions";
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
|
import { UPDATE_JOB } from "../../graphql/jobs.queries";
|
||||||
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||||
import { DateTimeFormatter } from "../../utils/DateFormatter";
|
import { DateTimeFormatter } from "../../utils/DateFormatter";
|
||||||
|
import dayjs from "../../utils/day";
|
||||||
import PhoneNumberFormatter from "../../utils/PhoneFormatter";
|
import PhoneNumberFormatter from "../../utils/PhoneFormatter";
|
||||||
import ChatOpenButton from "../chat-open-button/chat-open-button.component";
|
import ChatOpenButton from "../chat-open-button/chat-open-button.component";
|
||||||
import DataLabel from "../data-label/data-label.component";
|
import DataLabel from "../data-label/data-label.component";
|
||||||
@@ -21,7 +24,7 @@ import ProductionListColumnComment from "../production-list-columns/production-l
|
|||||||
import ProductionListColumnProductionNote from "../production-list-columns/production-list-columns.productionnote.component";
|
import ProductionListColumnProductionNote from "../production-list-columns/production-list-columns.productionnote.component";
|
||||||
import VehicleVinDisplay from "../vehicle-vin-display/vehicle-vin-display.component";
|
import VehicleVinDisplay from "../vehicle-vin-display/vehicle-vin-display.component";
|
||||||
import "./jobs-detail-header.styles.scss";
|
import "./jobs-detail-header.styles.scss";
|
||||||
import dayjs from "../../utils/day";
|
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
jobRO: selectJobReadOnly,
|
jobRO: selectJobReadOnly,
|
||||||
@@ -29,41 +32,55 @@ const mapStateToProps = createStructuredSelector({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
setPrintCenterContext: (context) => dispatch(setModalContext({ context: context, modal: "printCenter" }))
|
setPrintCenterContext: (context) =>
|
||||||
|
dispatch(
|
||||||
|
setModalContext({
|
||||||
|
context: context,
|
||||||
|
modal: "printCenter"
|
||||||
|
})
|
||||||
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
const colSpan = {
|
const colSpan = {
|
||||||
xs: {
|
xs: { span: 24 },
|
||||||
span: 24
|
sm: { span: 24 },
|
||||||
},
|
md: { span: 12 },
|
||||||
sm: {
|
lg: { span: 6 },
|
||||||
span: 24
|
xl: { span: 6 }
|
||||||
},
|
|
||||||
md: {
|
|
||||||
span: 12
|
|
||||||
},
|
|
||||||
lg: {
|
|
||||||
span: 6
|
|
||||||
},
|
|
||||||
xl: {
|
|
||||||
span: 6
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export function JobsDetailHeader({ job, bodyshop, disabled }) {
|
export function JobsDetailHeader({ job, bodyshop, disabled }) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const { notification } = useNotification();
|
||||||
const [notesClamped, setNotesClamped] = useState(true);
|
const [notesClamped, setNotesClamped] = useState(true);
|
||||||
const vehicleTitle = `${job.v_model_yr || ""} ${job.v_color || ""}
|
const [updateJob] = useMutation(UPDATE_JOB);
|
||||||
${job.v_make_desc || ""}
|
const vehicleTitle =
|
||||||
${job.v_model_desc || ""}`.trim();
|
`${job.v_model_yr || ""} ${job.v_color || ""} ${job.v_make_desc || ""} ${job.v_model_desc || ""}`.trim();
|
||||||
|
|
||||||
const bodyHrs = job.joblines.filter((j) => j.mod_lbr_ty !== "LAR").reduce((acc, val) => acc + val.mod_lb_hrs, 0);
|
const bodyHrs = job.joblines.filter((j) => j.mod_lbr_ty !== "LAR").reduce((acc, val) => acc + val.mod_lb_hrs, 0);
|
||||||
const refinishHrs = job.joblines
|
const refinishHrs = job.joblines
|
||||||
.filter((line) => line.mod_lbr_ty === "LAR")
|
.filter((line) => line.mod_lbr_ty === "LAR")
|
||||||
.reduce((acc, val) => acc + val.mod_lb_hrs, 0);
|
.reduce((acc, val) => acc + val.mod_lb_hrs, 0);
|
||||||
|
|
||||||
const ownerTitle = OwnerNameDisplayFunction(job).trim();
|
const ownerTitle = OwnerNameDisplayFunction(job).trim();
|
||||||
|
|
||||||
|
// Handle checkbox changes
|
||||||
|
const handleCheckboxChange = async (field, checked) => {
|
||||||
|
const value = checked ? dayjs().toISOString() : null;
|
||||||
|
try {
|
||||||
|
await updateJob({
|
||||||
|
variables: {
|
||||||
|
jobId: job.id,
|
||||||
|
job: { [field]: value }
|
||||||
|
},
|
||||||
|
refetchQueries: ["GET_JOB_BY_PK"],
|
||||||
|
awaitRefetchQueries: true
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
notification.error({
|
||||||
|
message: t("jobs.errors.saving", { error: error.message })
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Row gutter={[16, 16]} style={{ alignItems: "stretch" }}>
|
<Row gutter={[16, 16]} style={{ alignItems: "stretch" }}>
|
||||||
<Col {...colSpan}>
|
<Col {...colSpan}>
|
||||||
@@ -72,11 +89,7 @@ export function JobsDetailHeader({ job, bodyshop, disabled }) {
|
|||||||
<DataLabel label={t("jobs.fields.status")}>
|
<DataLabel label={t("jobs.fields.status")}>
|
||||||
<Space wrap>
|
<Space wrap>
|
||||||
{job.status}
|
{job.status}
|
||||||
{job.inproduction && (
|
{job.inproduction && <Tag color="#f50">{t("jobs.labels.inproduction")}</Tag>}
|
||||||
<Tag color="#f50" key="production">
|
|
||||||
{t("jobs.labels.inproduction")}
|
|
||||||
</Tag>
|
|
||||||
)}
|
|
||||||
{job.suspended && <PauseCircleOutlined style={{ color: "orangered" }} />}
|
{job.suspended && <PauseCircleOutlined style={{ color: "orangered" }} />}
|
||||||
{job.iouparent && (
|
{job.iouparent && (
|
||||||
<Link to={`/manage/jobs/${job.iouparent}`}>
|
<Link to={`/manage/jobs/${job.iouparent}`}>
|
||||||
@@ -110,7 +123,6 @@ export function JobsDetailHeader({ job, bodyshop, disabled }) {
|
|||||||
<span style={{ margin: "0rem .5rem" }}>/</span>
|
<span style={{ margin: "0rem .5rem" }}>/</span>
|
||||||
<CurrencyFormatter>{job.owner_owing}</CurrencyFormatter>
|
<CurrencyFormatter>{job.owner_owing}</CurrencyFormatter>
|
||||||
</DataLabel>
|
</DataLabel>
|
||||||
|
|
||||||
<DataLabel label={t("jobs.fields.alt_transport")}>
|
<DataLabel label={t("jobs.fields.alt_transport")}>
|
||||||
{job.alt_transport}
|
{job.alt_transport}
|
||||||
<JobAltTransportChange job={job} />
|
<JobAltTransportChange job={job} />
|
||||||
@@ -127,11 +139,39 @@ export function JobsDetailHeader({ job, bodyshop, disabled }) {
|
|||||||
))}
|
))}
|
||||||
</DataLabel>
|
</DataLabel>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<DataLabel label={t("jobs.fields.production_vars.note")}>
|
<DataLabel label={t("jobs.fields.production_vars.note")}>
|
||||||
<ProductionListColumnProductionNote record={job} />
|
<ProductionListColumnProductionNote record={job} />
|
||||||
</DataLabel>
|
</DataLabel>
|
||||||
|
<DataLabel label={t("jobs.fields.estimate_sent_approval")}>
|
||||||
|
<Space>
|
||||||
|
<Checkbox
|
||||||
|
checked={!!job.estimate_sent_approval}
|
||||||
|
onChange={(e) => handleCheckboxChange("estimate_sent_approval", e.target.checked)}
|
||||||
|
disabled={disabled}
|
||||||
|
>
|
||||||
|
{job.estimate_sent_approval && (
|
||||||
|
<span style={{ color: "#888" }}>
|
||||||
|
<DateTimeFormatter>{job.estimate_sent_approval}</DateTimeFormatter>
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</Checkbox>
|
||||||
|
</Space>
|
||||||
|
</DataLabel>
|
||||||
|
<DataLabel label={t("jobs.fields.estimate_approved")}>
|
||||||
|
<Space>
|
||||||
|
<Checkbox
|
||||||
|
checked={!!job.estimate_approved}
|
||||||
|
onChange={(e) => handleCheckboxChange("estimate_approved", e.target.checked)}
|
||||||
|
disabled={disabled}
|
||||||
|
>
|
||||||
|
{job.estimate_approved && (
|
||||||
|
<span style={{ color: "#888" }}>
|
||||||
|
<DateTimeFormatter>{job.estimate_approved}</DateTimeFormatter>
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</Checkbox>
|
||||||
|
</Space>
|
||||||
|
</DataLabel>
|
||||||
<Space wrap>
|
<Space wrap>
|
||||||
{job.special_coverage_policy && (
|
{job.special_coverage_policy && (
|
||||||
<Tag color="tomato">
|
<Tag color="tomato">
|
||||||
@@ -149,6 +189,14 @@ export function JobsDetailHeader({ job, bodyshop, disabled }) {
|
|||||||
</Space>
|
</Space>
|
||||||
</Tag>
|
</Tag>
|
||||||
)}
|
)}
|
||||||
|
{job.hit_and_run && (
|
||||||
|
<Tag color="green">
|
||||||
|
<Space>
|
||||||
|
<WarningFilled />
|
||||||
|
<span>{t("jobs.fields.hit_and_run")}</span>
|
||||||
|
</Space>
|
||||||
|
</Tag>
|
||||||
|
)}
|
||||||
</Space>
|
</Space>
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
import { Button, Space } from "antd";
|
import { Button, Space } from "antd";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import React, { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { logImEXEvent } from "../../firebase/firebase.utils";
|
import { logImEXEvent } from "../../firebase/firebase.utils";
|
||||||
import cleanAxios from "../../utils/CleanAxios";
|
import cleanAxios from "../../utils/CleanAxios";
|
||||||
import formatBytes from "../../utils/formatbytes";
|
import formatBytes from "../../utils/formatbytes";
|
||||||
//import yauzl from "yauzl";
|
|
||||||
|
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
@@ -28,7 +26,7 @@ const mapDispatchToProps = (dispatch) => ({
|
|||||||
|
|
||||||
export default connect(mapStateToProps, mapDispatchToProps)(JobsDocumentsImgproxyDownloadButton);
|
export default connect(mapStateToProps, mapDispatchToProps)(JobsDocumentsImgproxyDownloadButton);
|
||||||
|
|
||||||
export function JobsDocumentsImgproxyDownloadButton({ bodyshop, galleryImages, identifier }) {
|
export function JobsDocumentsImgproxyDownloadButton({ bodyshop, galleryImages, identifier, jobId }) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [download, setDownload] = useState(null);
|
const [download, setDownload] = useState(null);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
@@ -46,6 +44,7 @@ export function JobsDocumentsImgproxyDownloadButton({ bodyshop, galleryImages, i
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function standardMediaDownload(bufferData) {
|
function standardMediaDownload(bufferData) {
|
||||||
const a = document.createElement("a");
|
const a = document.createElement("a");
|
||||||
const url = window.URL.createObjectURL(new Blob([bufferData]));
|
const url = window.URL.createObjectURL(new Blob([bufferData]));
|
||||||
@@ -53,13 +52,14 @@ export function JobsDocumentsImgproxyDownloadButton({ bodyshop, galleryImages, i
|
|||||||
a.download = `${identifier || "documents"}.zip`;
|
a.download = `${identifier || "documents"}.zip`;
|
||||||
a.click();
|
a.click();
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleDownload = async () => {
|
const handleDownload = async () => {
|
||||||
logImEXEvent("jobs_documents_download");
|
logImEXEvent("jobs_documents_download");
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const zipUrl = await axios({
|
const zipUrl = await axios({
|
||||||
url: "/media/imgproxy/download",
|
url: "/media/imgproxy/download",
|
||||||
method: "POST",
|
method: "POST",
|
||||||
data: { documentids: imagesToDownload.map((_) => _.id) }
|
data: { jobId, documentids: imagesToDownload.map((_) => _.id) }
|
||||||
});
|
});
|
||||||
|
|
||||||
const theDownloadedZip = await cleanAxios({
|
const theDownloadedZip = await cleanAxios({
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ function JobsDocumentsImgproxyComponent({
|
|||||||
<SyncOutlined />
|
<SyncOutlined />
|
||||||
</Button>
|
</Button>
|
||||||
<JobsDocumentsGallerySelectAllComponent galleryImages={galleryImages} setGalleryImages={setGalleryImages} />
|
<JobsDocumentsGallerySelectAllComponent galleryImages={galleryImages} setGalleryImages={setGalleryImages} />
|
||||||
<JobsDocumentsDownloadButton galleryImages={galleryImages} identifier={downloadIdentifier} />
|
<JobsDocumentsDownloadButton galleryImages={galleryImages} identifier={downloadIdentifier} jobId={jobId} />
|
||||||
<JobsDocumentsDeleteButton
|
<JobsDocumentsDeleteButton
|
||||||
galleryImages={galleryImages}
|
galleryImages={galleryImages}
|
||||||
deletionCallback={billsCallback || fetchThumbnails || refetch}
|
deletionCallback={billsCallback || fetchThumbnails || refetch}
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ export default connect(
|
|||||||
<Modal
|
<Modal
|
||||||
title={t("jobs.labels.existing_jobs")}
|
title={t("jobs.labels.existing_jobs")}
|
||||||
width={"80%"}
|
width={"80%"}
|
||||||
destroyOnClose
|
destroyOnHidden
|
||||||
okButtonProps={{ disabled: selectedJob ? false : true }}
|
okButtonProps={{ disabled: selectedJob ? false : true }}
|
||||||
{...modalProps}
|
{...modalProps}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -20,7 +20,14 @@ const mapStateToProps = createStructuredSelector({
|
|||||||
});
|
});
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
toggleModalVisible: () => dispatch(toggleModalVisible("noteUpsert")),
|
toggleModalVisible: () => dispatch(toggleModalVisible("noteUpsert")),
|
||||||
insertAuditTrail: ({ jobid, operation, type }) => dispatch(insertAuditTrail({ jobid, operation, type }))
|
insertAuditTrail: ({ jobid, operation, type }) =>
|
||||||
|
dispatch(
|
||||||
|
insertAuditTrail({
|
||||||
|
jobid,
|
||||||
|
operation,
|
||||||
|
type
|
||||||
|
})
|
||||||
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
export function NoteUpsertModalContainer({ currentUser, noteUpsertModal, toggleModalVisible, insertAuditTrail }) {
|
export function NoteUpsertModalContainer({ currentUser, noteUpsertModal, toggleModalVisible, insertAuditTrail }) {
|
||||||
@@ -123,7 +130,7 @@ export function NoteUpsertModalContainer({ currentUser, noteUpsertModal, toggleM
|
|||||||
onCancel={() => {
|
onCancel={() => {
|
||||||
toggleModalVisible();
|
toggleModalVisible();
|
||||||
}}
|
}}
|
||||||
destroyOnClose
|
destroyOnHidden
|
||||||
>
|
>
|
||||||
<Form form={form} onFinish={handleFinish} layout="vertical">
|
<Form form={form} onFinish={handleFinish} layout="vertical">
|
||||||
<NoteUpsertModalComponent form={form} />
|
<NoteUpsertModalComponent form={form} />
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import { Virtuoso } from "react-virtuoso";
|
import { Virtuoso } from "react-virtuoso";
|
||||||
import { Badge, Button, Space, Spin, Switch, Tooltip, Typography } from "antd";
|
import { Alert, Badge, Button, Space, Spin, Switch, Tooltip, Typography } from "antd";
|
||||||
import { CheckCircleFilled, CheckCircleOutlined, EyeFilled, EyeOutlined } from "@ant-design/icons";
|
import { CheckCircleFilled, CheckCircleOutlined, EyeFilled, EyeOutlined } from "@ant-design/icons";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import "./notification-center.styles.scss";
|
import "./notification-center.styles.scss";
|
||||||
import day from "../../utils/day.js";
|
import day from "../../utils/day.js";
|
||||||
import { forwardRef, useRef, useEffect } from "react";
|
import { forwardRef, useEffect, useRef } from "react";
|
||||||
import { DateTimeFormat } from "../../utils/DateFormatter.jsx";
|
import { DateTimeFormat } from "../../utils/DateFormatter.jsx";
|
||||||
|
|
||||||
const { Text, Title } = Typography;
|
const { Text, Title } = Typography;
|
||||||
@@ -26,7 +26,8 @@ const NotificationCenterComponent = forwardRef(
|
|||||||
markAllRead,
|
markAllRead,
|
||||||
loadMore,
|
loadMore,
|
||||||
onNotificationClick,
|
onNotificationClick,
|
||||||
unreadCount
|
unreadCount,
|
||||||
|
isEmployee
|
||||||
},
|
},
|
||||||
ref
|
ref
|
||||||
) => {
|
) => {
|
||||||
@@ -93,7 +94,12 @@ const NotificationCenterComponent = forwardRef(
|
|||||||
) : (
|
) : (
|
||||||
<EyeOutlined className="notification-toggle-icon" />
|
<EyeOutlined className="notification-toggle-icon" />
|
||||||
)}
|
)}
|
||||||
<Switch checked={showUnreadOnly} onChange={(checked) => toggleUnreadOnly(checked)} size="small" />
|
<Switch
|
||||||
|
checked={showUnreadOnly}
|
||||||
|
onChange={(checked) => toggleUnreadOnly(checked)}
|
||||||
|
size="small"
|
||||||
|
disabled={!isEmployee}
|
||||||
|
/>
|
||||||
</Space>
|
</Space>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip title={t("notifications.labels.mark-all-read")}>
|
<Tooltip title={t("notifications.labels.mark-all-read")}>
|
||||||
@@ -106,14 +112,20 @@ const NotificationCenterComponent = forwardRef(
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Virtuoso
|
{!isEmployee ? (
|
||||||
ref={virtuosoRef}
|
<div style={{ padding: 10 }}>
|
||||||
style={{ height: "400px", width: "100%" }}
|
<Alert message={t("notifications.labels.employee-notification")} type="warning" />
|
||||||
data={notifications}
|
</div>
|
||||||
totalCount={notifications.length}
|
) : (
|
||||||
endReached={loadMore}
|
<Virtuoso
|
||||||
itemContent={renderNotification}
|
ref={virtuosoRef}
|
||||||
/>
|
style={{ height: "400px", width: "100%" }}
|
||||||
|
data={notifications}
|
||||||
|
totalCount={notifications.length}
|
||||||
|
endReached={loadMore}
|
||||||
|
itemContent={renderNotification}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,10 +3,11 @@ import { useQuery } from "@apollo/client";
|
|||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import NotificationCenterComponent from "./notification-center.component";
|
import NotificationCenterComponent from "./notification-center.component";
|
||||||
import { GET_NOTIFICATIONS } from "../../graphql/notifications.queries";
|
import { GET_NOTIFICATIONS } from "../../graphql/notifications.queries";
|
||||||
import { INITIAL_NOTIFICATIONS, useSocket } from "../../contexts/SocketIO/useSocket.jsx";
|
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors.js";
|
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors.js";
|
||||||
import day from "../../utils/day.js";
|
import day from "../../utils/day.js";
|
||||||
|
import { INITIAL_NOTIFICATIONS, useSocket } from "../../contexts/SocketIO/useSocket.js";
|
||||||
|
import { useIsEmployee } from "../../utils/useIsEmployee.js";
|
||||||
|
|
||||||
// This will be used to poll for notifications when the socket is disconnected
|
// This will be used to poll for notifications when the socket is disconnected
|
||||||
const NOTIFICATION_POLL_INTERVAL_SECONDS = 60;
|
const NOTIFICATION_POLL_INTERVAL_SECONDS = 60;
|
||||||
@@ -17,17 +18,18 @@ const NOTIFICATION_POLL_INTERVAL_SECONDS = 60;
|
|||||||
* @param onClose
|
* @param onClose
|
||||||
* @param bodyshop
|
* @param bodyshop
|
||||||
* @param unreadCount
|
* @param unreadCount
|
||||||
|
* @param currentUser
|
||||||
* @returns {JSX.Element}
|
* @returns {JSX.Element}
|
||||||
* @constructor
|
* @constructor
|
||||||
*/
|
*/
|
||||||
const NotificationCenterContainer = ({ visible, onClose, bodyshop, unreadCount }) => {
|
const NotificationCenterContainer = ({ visible, onClose, bodyshop, unreadCount, currentUser }) => {
|
||||||
const [showUnreadOnly, setShowUnreadOnly] = useState(false);
|
const [showUnreadOnly, setShowUnreadOnly] = useState(false);
|
||||||
const [notifications, setNotifications] = useState([]);
|
const [notifications, setNotifications] = useState([]);
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const { isConnected, markNotificationRead, markAllNotificationsRead } = useSocket();
|
const { isConnected, markNotificationRead, markAllNotificationsRead } = useSocket();
|
||||||
const notificationRef = useRef(null);
|
const notificationRef = useRef(null);
|
||||||
|
|
||||||
const userAssociationId = bodyshop?.associations?.[0]?.id;
|
const userAssociationId = bodyshop?.associations?.[0]?.id;
|
||||||
|
const isEmployee = useIsEmployee(bodyshop, currentUser);
|
||||||
|
|
||||||
const baseWhereClause = useMemo(() => {
|
const baseWhereClause = useMemo(() => {
|
||||||
return { associationid: { _eq: userAssociationId } };
|
return { associationid: { _eq: userAssociationId } };
|
||||||
@@ -51,7 +53,7 @@ const NotificationCenterContainer = ({ visible, onClose, bodyshop, unreadCount }
|
|||||||
fetchPolicy: "cache-and-network",
|
fetchPolicy: "cache-and-network",
|
||||||
notifyOnNetworkStatusChange: true,
|
notifyOnNetworkStatusChange: true,
|
||||||
pollInterval: isConnected ? 0 : day.duration(NOTIFICATION_POLL_INTERVAL_SECONDS, "seconds").asMilliseconds(),
|
pollInterval: isConnected ? 0 : day.duration(NOTIFICATION_POLL_INTERVAL_SECONDS, "seconds").asMilliseconds(),
|
||||||
skip: !userAssociationId,
|
skip: !userAssociationId || !isEmployee,
|
||||||
onError: (err) => {
|
onError: (err) => {
|
||||||
console.error(`Error polling Notifications: ${err?.message || ""}`);
|
console.error(`Error polling Notifications: ${err?.message || ""}`);
|
||||||
setTimeout(() => refetch(), day.duration(2, "seconds").asMilliseconds());
|
setTimeout(() => refetch(), day.duration(2, "seconds").asMilliseconds());
|
||||||
@@ -71,7 +73,7 @@ const NotificationCenterContainer = ({ visible, onClose, bodyshop, unreadCount }
|
|||||||
}, [visible, onClose]);
|
}, [visible, onClose]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (data?.notifications) {
|
if (data?.notifications && isEmployee) {
|
||||||
const processedNotifications = data.notifications
|
const processedNotifications = data.notifications
|
||||||
.map((notif) => {
|
.map((notif) => {
|
||||||
let scenarioText;
|
let scenarioText;
|
||||||
@@ -101,11 +103,13 @@ const NotificationCenterContainer = ({ visible, onClose, bodyshop, unreadCount }
|
|||||||
})
|
})
|
||||||
.sort((a, b) => new Date(b.created_at) - new Date(a.created_at));
|
.sort((a, b) => new Date(b.created_at) - new Date(a.created_at));
|
||||||
setNotifications(processedNotifications);
|
setNotifications(processedNotifications);
|
||||||
|
} else if (!isEmployee) {
|
||||||
|
setNotifications([]); // Clear notifications if not an employee
|
||||||
}
|
}
|
||||||
}, [data]);
|
}, [data, isEmployee]);
|
||||||
|
|
||||||
const loadMore = useCallback(() => {
|
const loadMore = useCallback(() => {
|
||||||
if (!queryLoading && data?.notifications.length) {
|
if (!queryLoading && data?.notifications.length && isEmployee) {
|
||||||
setIsLoading(true); // Show spinner during fetchMore
|
setIsLoading(true); // Show spinner during fetchMore
|
||||||
fetchMore({
|
fetchMore({
|
||||||
variables: { offset: data.notifications.length, where: whereClause },
|
variables: { offset: data.notifications.length, where: whereClause },
|
||||||
@@ -121,13 +125,14 @@ const NotificationCenterContainer = ({ visible, onClose, bodyshop, unreadCount }
|
|||||||
})
|
})
|
||||||
.finally(() => setIsLoading(false)); // Hide spinner when done
|
.finally(() => setIsLoading(false)); // Hide spinner when done
|
||||||
}
|
}
|
||||||
}, [data?.notifications?.length, fetchMore, queryLoading, whereClause]);
|
}, [data?.notifications?.length, fetchMore, queryLoading, whereClause, isEmployee]);
|
||||||
|
|
||||||
const handleToggleUnreadOnly = (value) => {
|
const handleToggleUnreadOnly = (value) => {
|
||||||
setShowUnreadOnly(value);
|
setShowUnreadOnly(value);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleMarkAllRead = useCallback(() => {
|
const handleMarkAllRead = useCallback(() => {
|
||||||
|
if (!isEmployee) return; // Do nothing if not an employee
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
markAllNotificationsRead()
|
markAllNotificationsRead()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
@@ -147,7 +152,7 @@ const NotificationCenterContainer = ({ visible, onClose, bodyshop, unreadCount }
|
|||||||
})
|
})
|
||||||
.catch((e) => console.error(`Error marking all notifications read: ${e?.message || ""}`))
|
.catch((e) => console.error(`Error marking all notifications read: ${e?.message || ""}`))
|
||||||
.finally(() => setIsLoading(false));
|
.finally(() => setIsLoading(false));
|
||||||
}, [markAllNotificationsRead, userAssociationId, showUnreadOnly]);
|
}, [markAllNotificationsRead, userAssociationId, showUnreadOnly, isEmployee]);
|
||||||
|
|
||||||
const handleNotificationClick = useCallback(
|
const handleNotificationClick = useCallback(
|
||||||
(notificationId) => {
|
(notificationId) => {
|
||||||
@@ -170,17 +175,18 @@ const NotificationCenterContainer = ({ visible, onClose, bodyshop, unreadCount }
|
|||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (visible && !isConnected) {
|
if (visible && !isConnected && isEmployee) {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
refetch()
|
refetch()
|
||||||
.catch((err) => console.error(`Error re-fetching notifications: ${err?.message || ""}`))
|
.catch((err) => console.error(`Error re-fetching notifications: ${err?.message || ""}`))
|
||||||
.finally(() => setIsLoading(false));
|
.finally(() => setIsLoading(false));
|
||||||
}
|
}
|
||||||
}, [visible, isConnected, refetch]);
|
}, [visible, isConnected, refetch, isEmployee]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<NotificationCenterComponent
|
<NotificationCenterComponent
|
||||||
ref={notificationRef}
|
ref={notificationRef}
|
||||||
|
isEmployee={isEmployee}
|
||||||
visible={visible}
|
visible={visible}
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
notifications={notifications}
|
notifications={notifications}
|
||||||
@@ -196,7 +202,8 @@ const NotificationCenterContainer = ({ visible, onClose, bodyshop, unreadCount }
|
|||||||
};
|
};
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop
|
bodyshop: selectBodyshop,
|
||||||
|
currentUser: selectCurrentUser
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(mapStateToProps, null)(NotificationCenterContainer);
|
export default connect(mapStateToProps, null)(NotificationCenterContainer);
|
||||||
|
|||||||
@@ -1,32 +1,41 @@
|
|||||||
import { useMutation, useQuery } from "@apollo/client";
|
import { useMutation, useQuery } from "@apollo/client";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { Button, Card, Checkbox, Form, Space, Table } from "antd";
|
import { Alert, Button, Card, Checkbox, Divider, Form, Space, Switch, Table, Typography } from "antd";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import { selectCurrentUser } from "../../redux/user/user.selectors";
|
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors";
|
||||||
import AlertComponent from "../alert/alert.component";
|
import AlertComponent from "../alert/alert.component";
|
||||||
import { QUERY_NOTIFICATION_SETTINGS, UPDATE_NOTIFICATION_SETTINGS } from "../../graphql/user.queries.js";
|
import {
|
||||||
|
QUERY_NOTIFICATION_SETTINGS,
|
||||||
|
UPDATE_NOTIFICATION_SETTINGS,
|
||||||
|
UPDATE_NOTIFICATIONS_AUTOADD
|
||||||
|
} from "../../graphql/user.queries.js";
|
||||||
import { notificationScenarios } from "../../utils/jobNotificationScenarios.js";
|
import { notificationScenarios } from "../../utils/jobNotificationScenarios.js";
|
||||||
import LoadingSpinner from "../loading-spinner/loading-spinner.component.jsx";
|
import LoadingSpinner from "../loading-spinner/loading-spinner.component.jsx";
|
||||||
import PropTypes from "prop-types";
|
import PropTypes from "prop-types";
|
||||||
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
|
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
|
||||||
import ColumnHeaderCheckbox from "../notification-settings/column-header-checkbox.component.jsx";
|
import ColumnHeaderCheckbox from "../notification-settings/column-header-checkbox.component.jsx";
|
||||||
|
import { useIsEmployee } from "../../utils/useIsEmployee.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Notifications Settings Form
|
* Notifications Settings Form
|
||||||
* @param currentUser
|
* @param currentUser
|
||||||
|
* @param bodyshop
|
||||||
* @returns {JSX.Element}
|
* @returns {JSX.Element}
|
||||||
* @constructor
|
* @constructor
|
||||||
*/
|
*/
|
||||||
const NotificationSettingsForm = ({ currentUser }) => {
|
const NotificationSettingsForm = ({ currentUser, bodyshop }) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
const [initialValues, setInitialValues] = useState({});
|
const [initialValues, setInitialValues] = useState({});
|
||||||
const [isDirty, setIsDirty] = useState(false);
|
const [isDirty, setIsDirty] = useState(false);
|
||||||
|
const [autoAddEnabled, setAutoAddEnabled] = useState(false);
|
||||||
|
const [initialAutoAdd, setInitialAutoAdd] = useState(false);
|
||||||
const notification = useNotification();
|
const notification = useNotification();
|
||||||
|
const isEmployee = useIsEmployee(bodyshop, currentUser);
|
||||||
|
|
||||||
// Fetch notification settings.
|
// Fetch notification settings and notifications_autoadd
|
||||||
const { loading, error, data } = useQuery(QUERY_NOTIFICATION_SETTINGS, {
|
const { loading, error, data } = useQuery(QUERY_NOTIFICATION_SETTINGS, {
|
||||||
fetchPolicy: "network-only",
|
fetchPolicy: "network-only",
|
||||||
nextFetchPolicy: "network-only",
|
nextFetchPolicy: "network-only",
|
||||||
@@ -34,13 +43,16 @@ const NotificationSettingsForm = ({ currentUser }) => {
|
|||||||
skip: !currentUser
|
skip: !currentUser
|
||||||
});
|
});
|
||||||
|
|
||||||
const [updateNotificationSettings, { loading: saving }] = useMutation(UPDATE_NOTIFICATION_SETTINGS);
|
const [updateNotificationSettings, { loading: savingSettings }] = useMutation(UPDATE_NOTIFICATION_SETTINGS);
|
||||||
|
const [updateNotificationsAutoAdd, { loading: savingAutoAdd }] = useMutation(UPDATE_NOTIFICATIONS_AUTOADD);
|
||||||
|
|
||||||
// Populate form with fetched data.
|
// Populate form with fetched data
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (data?.associations?.length > 0) {
|
if (data?.associations?.length > 0) {
|
||||||
const settings = data.associations[0].notification_settings || {};
|
const settings = data.associations[0].notification_settings || {};
|
||||||
// Ensure each scenario has an object with { app, email, fcm }.
|
const autoAdd = data.associations[0].notifications_autoadd ?? false;
|
||||||
|
|
||||||
|
// Ensure each scenario has an object with { app, email, fcm }
|
||||||
const formattedValues = notificationScenarios.reduce((acc, scenario) => {
|
const formattedValues = notificationScenarios.reduce((acc, scenario) => {
|
||||||
acc[scenario] = settings[scenario] ?? { app: false, email: false, fcm: false };
|
acc[scenario] = settings[scenario] ?? { app: false, email: false, fcm: false };
|
||||||
return acc;
|
return acc;
|
||||||
@@ -48,32 +60,66 @@ const NotificationSettingsForm = ({ currentUser }) => {
|
|||||||
|
|
||||||
setInitialValues(formattedValues);
|
setInitialValues(formattedValues);
|
||||||
form.setFieldsValue(formattedValues);
|
form.setFieldsValue(formattedValues);
|
||||||
setIsDirty(false); // Reset dirty state when new data loads.
|
setAutoAddEnabled(autoAdd);
|
||||||
|
setInitialAutoAdd(autoAdd);
|
||||||
|
setIsDirty(false); // Reset dirty state when new data loads
|
||||||
}
|
}
|
||||||
}, [data, form]);
|
}, [data, form]);
|
||||||
|
|
||||||
|
// Handle toggle of notifications_autoadd
|
||||||
|
const handleAutoAddToggle = async (checked) => {
|
||||||
|
if (data?.associations?.length > 0) {
|
||||||
|
const userId = data.associations[0].id;
|
||||||
|
try {
|
||||||
|
const result = await updateNotificationsAutoAdd({
|
||||||
|
variables: { id: userId, autoadd: checked }
|
||||||
|
});
|
||||||
|
if (!result?.errors) {
|
||||||
|
setAutoAddEnabled(checked);
|
||||||
|
setInitialAutoAdd(checked);
|
||||||
|
notification.success({ message: t("notifications.labels.auto-add-success") });
|
||||||
|
setIsDirty(false); // Reset dirty state if only auto-add was changed
|
||||||
|
} else {
|
||||||
|
throw new Error("Failed to update auto-add setting");
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
setAutoAddEnabled(!checked); // Revert on error
|
||||||
|
notification.error({ message: t("notifications.labels.auto-add-failure") });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle save of notification settings
|
||||||
const handleSave = async (values) => {
|
const handleSave = async (values) => {
|
||||||
if (data?.associations?.length > 0) {
|
if (data?.associations?.length > 0) {
|
||||||
const userId = data.associations[0].id;
|
const userId = data.associations[0].id;
|
||||||
// Save the updated notification settings.
|
try {
|
||||||
const result = await updateNotificationSettings({ variables: { id: userId, ns: values } });
|
const result = await updateNotificationSettings({ variables: { id: userId, ns: values } });
|
||||||
if (!result?.errors) {
|
if (!result?.errors) {
|
||||||
notification.success({ message: t("notifications.labels.notification-settings-success") });
|
notification.success({ message: t("notifications.labels.notification-settings-success") });
|
||||||
setInitialValues(values);
|
setInitialValues(values);
|
||||||
setIsDirty(false);
|
setIsDirty(false);
|
||||||
} else {
|
} else {
|
||||||
|
throw new Error("Failed to update notification settings");
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
notification.error({ message: t("notifications.labels.notification-settings-failure") });
|
notification.error({ message: t("notifications.labels.notification-settings-failure") });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Mark the form as dirty on any manual change.
|
// Mark the form as dirty on any manual change
|
||||||
const handleFormChange = () => {
|
const handleFormChange = () => {
|
||||||
setIsDirty(true);
|
setIsDirty(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Check if auto-add has changed
|
||||||
|
const isAutoAddDirty = autoAddEnabled !== initialAutoAdd;
|
||||||
|
|
||||||
|
// Handle reset of form and auto-add
|
||||||
const handleReset = () => {
|
const handleReset = () => {
|
||||||
form.setFieldsValue(initialValues);
|
form.setFieldsValue(initialValues);
|
||||||
|
setAutoAddEnabled(initialAutoAdd);
|
||||||
setIsDirty(false);
|
setIsDirty(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -139,17 +185,30 @@ const NotificationSettingsForm = ({ currentUser }) => {
|
|||||||
title={t("notifications.labels.notificationscenarios")}
|
title={t("notifications.labels.notificationscenarios")}
|
||||||
extra={
|
extra={
|
||||||
<Space>
|
<Space>
|
||||||
<Button type="default" onClick={handleReset} disabled={!isDirty}>
|
<Typography.Text type="secondary">{t("notifications.labels.auto-add")}</Typography.Text>
|
||||||
|
<Switch
|
||||||
|
checked={autoAddEnabled}
|
||||||
|
onChange={handleAutoAddToggle}
|
||||||
|
loading={savingAutoAdd}
|
||||||
|
// checkedChildren={t("notifications.labels.auto-add-on")}
|
||||||
|
// unCheckedChildren={t("notifications.labels.auto-add-off")}
|
||||||
|
/>
|
||||||
|
<Button type="default" onClick={handleReset} disabled={!isDirty && !isAutoAddDirty}>
|
||||||
{t("general.actions.clear")}
|
{t("general.actions.clear")}
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button type="primary" htmlType="submit" disabled={!isDirty} loading={savingSettings}>
|
||||||
<Button type="primary" htmlType="submit" disabled={!isDirty} loading={saving}>
|
|
||||||
{t("notifications.labels.save")}
|
{t("notifications.labels.save")}
|
||||||
</Button>
|
</Button>
|
||||||
</Space>
|
</Space>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
{!isEmployee && (
|
||||||
|
<div style={{ width: "100%", marginBottom: "10px" }}>
|
||||||
|
<Alert message={t("notifications.labels.employee-notification")} type="warning" />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<Table dataSource={dataSource} columns={columns} pagination={false} bordered rowKey="key" />
|
<Table dataSource={dataSource} columns={columns} pagination={false} bordered rowKey="key" />
|
||||||
|
<Divider />
|
||||||
</Card>
|
</Card>
|
||||||
</Form>
|
</Form>
|
||||||
);
|
);
|
||||||
@@ -158,11 +217,13 @@ const NotificationSettingsForm = ({ currentUser }) => {
|
|||||||
NotificationSettingsForm.propTypes = {
|
NotificationSettingsForm.propTypes = {
|
||||||
currentUser: PropTypes.shape({
|
currentUser: PropTypes.shape({
|
||||||
email: PropTypes.string.isRequired
|
email: PropTypes.string.isRequired
|
||||||
}).isRequired
|
}).isRequired,
|
||||||
|
bodyshop: PropTypes.object.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
currentUser: selectCurrentUser
|
currentUser: selectCurrentUser,
|
||||||
|
bodyshop: selectBodyshop
|
||||||
});
|
});
|
||||||
|
|
||||||
export default connect(mapStateToProps)(NotificationSettingsForm);
|
export default connect(mapStateToProps)(NotificationSettingsForm);
|
||||||
|
|||||||
@@ -333,7 +333,7 @@ export function PartsOrderModalContainer({
|
|||||||
onOk={() => form.submit()}
|
onOk={() => form.submit()}
|
||||||
okButtonProps={{ loading: saving }}
|
okButtonProps={{ loading: saving }}
|
||||||
cancelButtonProps={{ loading: saving }}
|
cancelButtonProps={{ loading: saving }}
|
||||||
destroyOnClose
|
destroyOnHidden
|
||||||
width="75%"
|
width="75%"
|
||||||
forceRender
|
forceRender
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ export default function PartsQueueDetailCard() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Drawer open={!!selected} destroyOnClose width={drawerPercentage} placement="right" onClose={handleDrawerClose}>
|
<Drawer open={!!selected} destroyOnHidden width={drawerPercentage} placement="right" onClose={handleDrawerClose}>
|
||||||
{loading ? <LoadingSpinner /> : null}
|
{loading ? <LoadingSpinner /> : null}
|
||||||
{error ? <AlertComponent message={error.message} type="error" /> : null}
|
{error ? <AlertComponent message={error.message} type="error" /> : null}
|
||||||
{data ? (
|
{data ? (
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ export function PartsReceiveModalContainer({ partsReceiveModal, toggleModalVisib
|
|||||||
onCancel={() => toggleModalVisible()}
|
onCancel={() => toggleModalVisible()}
|
||||||
onOk={() => form.submit()}
|
onOk={() => form.submit()}
|
||||||
okButtonProps={{ loading: loading }}
|
okButtonProps={{ loading: loading }}
|
||||||
destroyOnClose
|
destroyOnHidden
|
||||||
forceRender
|
forceRender
|
||||||
width="50%"
|
width="50%"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -134,7 +134,7 @@ function PaymentModalContainer({ paymentModal, toggleModalVisible, bodyshop }) {
|
|||||||
<Modal
|
<Modal
|
||||||
title={!context || (context && !context.id) ? t("payments.labels.new") : t("payments.labels.edit")}
|
title={!context || (context && !context.id) ? t("payments.labels.new") : t("payments.labels.edit")}
|
||||||
open={open}
|
open={open}
|
||||||
destroyOnClose
|
destroyOnHidden
|
||||||
okText={t("general.actions.save")}
|
okText={t("general.actions.save")}
|
||||||
onOk={() => form.submit()}
|
onOk={() => form.submit()}
|
||||||
width="50%"
|
width="50%"
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { createStructuredSelector } from "reselect";
|
|||||||
import { openChatByPhone, setMessage } from "../../redux/messaging/messaging.actions";
|
import { openChatByPhone, setMessage } from "../../redux/messaging/messaging.actions";
|
||||||
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors";
|
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors";
|
||||||
import CurrencyFormItemComponent from "../form-items-formatted/currency-form-item.component";
|
import CurrencyFormItemComponent from "../form-items-formatted/currency-form-item.component";
|
||||||
import { useSocket } from "../../contexts/SocketIO/useSocket.jsx";
|
import { useSocket } from "../../contexts/SocketIO/useSocket.js";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop,
|
bodyshop: selectBodyshop,
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ export function PrintCenterModalContainer({ printCenterModal, toggleModalVisible
|
|||||||
okText={t("general.actions.close")}
|
okText={t("general.actions.close")}
|
||||||
width="90%"
|
width="90%"
|
||||||
title={t("printcenter.labels.title")}
|
title={t("printcenter.labels.title")}
|
||||||
destroyOnClose
|
destroyOnHidden
|
||||||
>
|
>
|
||||||
<PrintCenterModalComponent context={context} />
|
<PrintCenterModalComponent context={context} />
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import { QUERY_KANBAN_SETTINGS } from "../../graphql/user.queries";
|
|||||||
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors";
|
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors";
|
||||||
import ProductionBoardKanbanComponent from "./production-board-kanban.component";
|
import ProductionBoardKanbanComponent from "./production-board-kanban.component";
|
||||||
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
||||||
import { useSocket } from "../../contexts/SocketIO/useSocket.jsx";
|
import { useSocket } from "../../contexts/SocketIO/useSocket.js";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop,
|
bodyshop: selectBodyshop,
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import React from "react";
|
|
||||||
import { Card, Form, Select } from "antd";
|
import { Card, Form, Select } from "antd";
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import PropTypes from "prop-types";
|
import PropTypes from "prop-types";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
const FilterSettings = ({
|
const FilterSettings = ({
|
||||||
selectedMdInsCos,
|
selectedMdInsCos,
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
import { Card, Checkbox, Col, Form, Row } from "antd";
|
import { Card, Checkbox, Col, Form, Row } from "antd";
|
||||||
import React from "react";
|
|
||||||
import PropTypes from "prop-types";
|
import PropTypes from "prop-types";
|
||||||
|
|
||||||
const InformationSettings = ({ t }) => (
|
const InformationSettings = ({ t }) => (
|
||||||
<Card title={t("production.settings.information")}>
|
<Card title={t("production.settings.information")} style={{ maxWidth: "100%", overflowX: "auto" }}>
|
||||||
<Row gutter={[16, 16]}>
|
<Row gutter={[16, 16]} wrap>
|
||||||
{[
|
{[
|
||||||
"model_info",
|
"model_info",
|
||||||
"ownr_nm",
|
"ownr_nm",
|
||||||
@@ -21,7 +20,7 @@ const InformationSettings = ({ t }) => (
|
|||||||
"subtotal",
|
"subtotal",
|
||||||
"tasks"
|
"tasks"
|
||||||
].map((item) => (
|
].map((item) => (
|
||||||
<Col span={4} key={item}>
|
<Col xs={24} sm={12} md={8} lg={6} key={item}>
|
||||||
<Form.Item name={item} valuePropName="checked">
|
<Form.Item name={item} valuePropName="checked">
|
||||||
<Checkbox>{t(`production.labels.${item}`)}</Checkbox>
|
<Checkbox>{t(`production.labels.${item}`)}</Checkbox>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import { Card, Col, Form, Radio, Row } from "antd";
|
import { Card, Col, Form, Radio, Row } from "antd";
|
||||||
import React from "react";
|
|
||||||
import PropTypes from "prop-types";
|
import PropTypes from "prop-types";
|
||||||
|
|
||||||
const LayoutSettings = ({ t }) => (
|
const LayoutSettings = ({ t }) => (
|
||||||
<Card title={t("production.settings.layout")}>
|
<Card title={t("production.settings.layout")} style={{ maxWidth: "100%", overflowX: "auto" }}>
|
||||||
<Row gutter={[16, 16]}>
|
<Row gutter={[16, 16]}>
|
||||||
{[
|
{[
|
||||||
{
|
{
|
||||||
@@ -48,9 +47,9 @@ const LayoutSettings = ({ t }) => (
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
].map(({ name, label, options }) => (
|
].map(({ name, label, options }) => (
|
||||||
<Col span={4} key={name}>
|
<Col xs={24} sm={16} md={10} lg={8} key={name}>
|
||||||
<Form.Item name={name} label={label}>
|
<Form.Item name={name} label={label}>
|
||||||
<Radio.Group>
|
<Radio.Group style={{ display: "flex", flexWrap: "nowrap" }}>
|
||||||
{options.map((option) => (
|
{options.map((option) => (
|
||||||
<Radio.Button key={option.value.toString()} value={option.value}>
|
<Radio.Button key={option.value.toString()} value={option.value}>
|
||||||
{option.label}
|
{option.label}
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
|
import { Card, Checkbox, Form } from "antd";
|
||||||
|
import PropTypes from "prop-types";
|
||||||
import { DragDropContext, Draggable, Droppable } from "../trello-board/dnd/lib/index.js";
|
import { DragDropContext, Draggable, Droppable } from "../trello-board/dnd/lib/index.js";
|
||||||
import { statisticsItems } from "./defaultKanbanSettings.js";
|
import { statisticsItems } from "./defaultKanbanSettings.js";
|
||||||
import { Card, Checkbox, Form } from "antd";
|
|
||||||
import React from "react";
|
|
||||||
import PropTypes from "prop-types";
|
|
||||||
|
|
||||||
const StatisticsSettings = ({ t, statisticsOrder, setStatisticsOrder, setHasChanges }) => {
|
const StatisticsSettings = ({ t, statisticsOrder, setStatisticsOrder, setHasChanges }) => {
|
||||||
const onDragEnd = (result) => {
|
const onDragEnd = (result) => {
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
|
import { SettingOutlined } from "@ant-design/icons";
|
||||||
import { useMutation } from "@apollo/client";
|
import { useMutation } from "@apollo/client";
|
||||||
import { Button, Card, Col, Form, Popover, Row, Tabs } from "antd";
|
import { Button, Card, Col, Form, Popover, Row, Tabs } from "antd";
|
||||||
|
import { isFunction } from "lodash";
|
||||||
|
import PropTypes from "prop-types";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { useNotification } from "../../../contexts/Notifications/notificationContext.jsx";
|
||||||
import { UPDATE_KANBAN_SETTINGS } from "../../../graphql/user.queries.js";
|
import { UPDATE_KANBAN_SETTINGS } from "../../../graphql/user.queries.js";
|
||||||
import { defaultKanbanSettings, mergeWithDefaults } from "./defaultKanbanSettings.js";
|
import { defaultKanbanSettings, mergeWithDefaults } from "./defaultKanbanSettings.js";
|
||||||
import LayoutSettings from "./LayoutSettings.jsx";
|
|
||||||
import InformationSettings from "./InformationSettings.jsx";
|
|
||||||
import StatisticsSettings from "./StatisticsSettings.jsx";
|
|
||||||
import FilterSettings from "./FilterSettings.jsx";
|
import FilterSettings from "./FilterSettings.jsx";
|
||||||
import PropTypes from "prop-types";
|
import InformationSettings from "./InformationSettings.jsx";
|
||||||
import { isFunction } from "lodash";
|
import LayoutSettings from "./LayoutSettings.jsx";
|
||||||
import { useNotification } from "../../../contexts/Notifications/notificationContext.jsx";
|
import StatisticsSettings from "./StatisticsSettings.jsx";
|
||||||
import { SettingOutlined } from "@ant-design/icons";
|
|
||||||
|
|
||||||
function ProductionBoardKanbanSettings({ associationSettings, parentLoading, bodyshop, data, onSettingsChange }) {
|
function ProductionBoardKanbanSettings({ associationSettings, parentLoading, bodyshop, data, onSettingsChange }) {
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
@@ -87,7 +87,7 @@ function ProductionBoardKanbanSettings({ associationSettings, parentLoading, bod
|
|||||||
};
|
};
|
||||||
|
|
||||||
const overlay = (
|
const overlay = (
|
||||||
<Card style={{ minWidth: "80vw" }}>
|
<Card style={{ maxWidth: "80vw", width: "100%"}}>
|
||||||
<Form form={form} onFinish={handleFinish} layout="vertical" onValuesChange={handleValuesChange}>
|
<Form form={form} onFinish={handleFinish} layout="vertical" onValuesChange={handleValuesChange}>
|
||||||
<Tabs
|
<Tabs
|
||||||
defaultActiveKey="1"
|
defaultActiveKey="1"
|
||||||
|
|||||||
@@ -100,26 +100,28 @@ const BoardContainer = ({
|
|||||||
const onLaneDrag = useCallback(
|
const onLaneDrag = useCallback(
|
||||||
async ({ draggableId, type, source, reason, mode, destination, combine }) => {
|
async ({ draggableId, type, source, reason, mode, destination, combine }) => {
|
||||||
setIsDragging(false);
|
setIsDragging(false);
|
||||||
setDragTime(source.droppableId);
|
|
||||||
if (!type || type !== "lane" || !source || !destination || isEqual(source, destination)) return;
|
|
||||||
|
|
||||||
setIsProcessing(true);
|
// Only update drag time if it's a valid drop with a different destination
|
||||||
|
if (type === "lane" && source && destination && !isEqual(source, destination)) {
|
||||||
|
setDragTime(source.droppableId);
|
||||||
|
setIsProcessing(true);
|
||||||
|
|
||||||
dispatch(
|
dispatch(
|
||||||
actions.moveCardAcrossLanes({
|
actions.moveCardAcrossLanes({
|
||||||
fromLaneId: source.droppableId,
|
fromLaneId: source.droppableId,
|
||||||
toLaneId: destination.droppableId,
|
toLaneId: destination.droppableId,
|
||||||
cardId: draggableId,
|
cardId: draggableId,
|
||||||
index: destination.index
|
index: destination.index
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await onDragEnd({ draggableId, type, source, reason, mode, destination, combine });
|
await onDragEnd({ draggableId, type, source, reason, mode, destination, combine });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Error in onLaneDrag", err);
|
console.error("Error in onLaneDrag", err);
|
||||||
} finally {
|
} finally {
|
||||||
setIsProcessing(false);
|
setIsProcessing(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[dispatch, onDragEnd, setDragTime]
|
[dispatch, onDragEnd, setDragTime]
|
||||||
|
|||||||
@@ -120,15 +120,14 @@ const Lane = ({
|
|||||||
const Component = orientation === "vertical" ? VirtuosoGrid : Virtuoso;
|
const Component = orientation === "vertical" ? VirtuosoGrid : Virtuoso;
|
||||||
const FinalComponent = collapsed ? "div" : Component;
|
const FinalComponent = collapsed ? "div" : Component;
|
||||||
const commonProps = {
|
const commonProps = {
|
||||||
useWindowScroll: true,
|
data: renderedCards,
|
||||||
data: renderedCards
|
customScrollParent: laneRef.current
|
||||||
};
|
};
|
||||||
|
|
||||||
const verticalProps = {
|
const verticalProps = {
|
||||||
...commonProps,
|
...commonProps,
|
||||||
listClassName: "grid-container",
|
listClassName: "grid-container",
|
||||||
itemClassName: "grid-item",
|
itemClassName: "grid-item",
|
||||||
customScrollParent: laneRef.current,
|
|
||||||
components: {
|
components: {
|
||||||
List: ListComponent,
|
List: ListComponent,
|
||||||
Item: ItemComponent
|
Item: ItemComponent
|
||||||
@@ -142,7 +141,6 @@ const Lane = ({
|
|||||||
components: { Item: HeightPreservingItem },
|
components: { Item: HeightPreservingItem },
|
||||||
overscan: { main: 3, reverse: 3 },
|
overscan: { main: 3, reverse: 3 },
|
||||||
itemContent: (index, item) => renderDraggable(index, item),
|
itemContent: (index, item) => renderDraggable(index, item),
|
||||||
scrollerRef: provided.innerRef,
|
|
||||||
style: {
|
style: {
|
||||||
minWidth: maxCardWidth,
|
minWidth: maxCardWidth,
|
||||||
minHeight: maxLaneHeight
|
minHeight: maxLaneHeight
|
||||||
@@ -180,13 +178,14 @@ const Lane = ({
|
|||||||
override={orientation !== "horizontal" && (collapsed || !renderedCards.length)}
|
override={orientation !== "horizontal" && (collapsed || !renderedCards.length)}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
{...provided.droppableProps}
|
ref={laneRef} // Ensure laneRef is set here
|
||||||
ref={provided.innerRef}
|
style={{ height: "100%", width: "100%" }} // Make it scrollable
|
||||||
className={`react-trello-lane ${collapsed ? "lane-collapsed" : ""}`}
|
className={`react-trello-lane ${collapsed ? "lane-collapsed" : ""}`}
|
||||||
style={{ ...provided.droppableProps.style }}
|
|
||||||
>
|
>
|
||||||
<FinalComponent {...finalComponentProps} />
|
<div {...provided.droppableProps} ref={provided.innerRef} style={{ ...provided.droppableProps.style }}>
|
||||||
{shouldRenderPlaceholder && provided.placeholder}
|
<FinalComponent {...finalComponentProps} />
|
||||||
|
{shouldRenderPlaceholder && provided.placeholder}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</HeightMemoryWrapper>
|
</HeightMemoryWrapper>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { useTranslation } from "react-i18next";
|
|||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { useLocation, useNavigate } from "react-router-dom";
|
import { useLocation, useNavigate } from "react-router-dom";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
import { useSocket } from "../../contexts/SocketIO/useSocket.jsx";
|
import { useSocket } from "../../contexts/SocketIO/useSocket.js";
|
||||||
import { logImEXEvent } from "../../firebase/firebase.utils.js";
|
import { logImEXEvent } from "../../firebase/firebase.utils.js";
|
||||||
import { QUERY_JOB_CARD_DETAILS, UPDATE_JOB } from "../../graphql/jobs.queries";
|
import { QUERY_JOB_CARD_DETAILS, UPDATE_JOB } from "../../graphql/jobs.queries";
|
||||||
import { insertAuditTrail } from "../../redux/application/application.actions.js";
|
import { insertAuditTrail } from "../../redux/application/application.actions.js";
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import {
|
|||||||
import ProductionListTable from "./production-list-table.component";
|
import ProductionListTable from "./production-list-table.component";
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
||||||
import { useSocket } from "../../contexts/SocketIO/useSocket.jsx";
|
import { useSocket } from "../../contexts/SocketIO/useSocket.js";
|
||||||
|
|
||||||
export default function ProductionListTableContainer({ bodyshop, subscriptionType = "direct" }) {
|
export default function ProductionListTableContainer({ bodyshop, subscriptionType = "direct" }) {
|
||||||
const client = useApolloClient();
|
const client = useApolloClient();
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { selectCurrentUser } from "../../redux/user/user.selectors";
|
|||||||
import { logImEXEvent, updateCurrentPassword } from "../../firebase/firebase.utils";
|
import { logImEXEvent, updateCurrentPassword } from "../../firebase/firebase.utils";
|
||||||
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
||||||
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
|
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
|
||||||
import { useSocket } from "../../contexts/SocketIO/useSocket.jsx";
|
import { useSocket } from "../../contexts/SocketIO/useSocket.js";
|
||||||
import NotificationSettingsForm from "../notification-settings/notification-settings-form.component.jsx";
|
import NotificationSettingsForm from "../notification-settings/notification-settings-form.component.jsx";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
|
|||||||
@@ -54,6 +54,9 @@ export function ProfileShopsContainer({ bodyshop, currentUser }) {
|
|||||||
|
|
||||||
//Force window refresh.
|
//Force window refresh.
|
||||||
|
|
||||||
|
//Ping the new partner to refresh.
|
||||||
|
axios.post("http://localhost:1337/refresh");
|
||||||
|
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ export function ReportCenterModalContainer({ reportCenterModal, toggleModalVisib
|
|||||||
onOk={() => toggleModalVisible()}
|
onOk={() => toggleModalVisible()}
|
||||||
onCancel={() => toggleModalVisible()}
|
onCancel={() => toggleModalVisible()}
|
||||||
cancelButtonProps={{ style: { display: "none" } }}
|
cancelButtonProps={{ style: { display: "none" } }}
|
||||||
destroyOnClose
|
destroyOnHidden
|
||||||
width="80%"
|
width="80%"
|
||||||
>
|
>
|
||||||
<RbacWrapperComponent action="shop:reportcenter">
|
<RbacWrapperComponent action="shop:reportcenter">
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { Button, Col, Form, Input, Row, Select, Space, Switch, Typography } from "antd";
|
import { Button, Col, Form, Input, Row, Select, Space, Switch, Typography } from "antd";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import React, { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
@@ -8,16 +8,16 @@ import { calculateScheduleLoad } from "../../redux/application/application.actio
|
|||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||||
import { DateFormatter } from "../../utils/DateFormatter";
|
import { DateFormatter } from "../../utils/DateFormatter";
|
||||||
import dayjs from "../../utils/day";
|
import dayjs from "../../utils/day";
|
||||||
|
import BlurWrapper from "../feature-wrapper/blur-wrapper.component";
|
||||||
import { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component";
|
import { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component";
|
||||||
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component";
|
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component";
|
||||||
import EmailInput from "../form-items-formatted/email-form-item.component";
|
import EmailInput from "../form-items-formatted/email-form-item.component";
|
||||||
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
||||||
|
import LockWrapperComponent from "../lock-wrapper/lock-wrapper.component";
|
||||||
import ScheduleDayViewContainer from "../schedule-day-view/schedule-day-view.container";
|
import ScheduleDayViewContainer from "../schedule-day-view/schedule-day-view.container";
|
||||||
import ScheduleExistingAppointmentsList from "../schedule-existing-appointments-list/schedule-existing-appointments-list.component";
|
import ScheduleExistingAppointmentsList from "../schedule-existing-appointments-list/schedule-existing-appointments-list.component";
|
||||||
import "./schedule-job-modal.scss";
|
|
||||||
import LockWrapperComponent from "../lock-wrapper/lock-wrapper.component";
|
|
||||||
import BlurWrapper from "../feature-wrapper/blur-wrapper.component";
|
|
||||||
import UpsellComponent, { upsellEnum } from "../upsell/upsell.component";
|
import UpsellComponent, { upsellEnum } from "../upsell/upsell.component";
|
||||||
|
import "./schedule-job-modal.scss";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop
|
bodyshop: selectBodyshop
|
||||||
@@ -60,10 +60,12 @@ export function ScheduleJobModalComponent({
|
|||||||
const totalHours =
|
const totalHours =
|
||||||
lbrHrsData.jobs_by_pk.labhrs.aggregate.sum.mod_lb_hrs + lbrHrsData.jobs_by_pk.larhrs.aggregate.sum.mod_lb_hrs;
|
lbrHrsData.jobs_by_pk.labhrs.aggregate.sum.mod_lb_hrs + lbrHrsData.jobs_by_pk.larhrs.aggregate.sum.mod_lb_hrs;
|
||||||
|
|
||||||
if (values.start && !values.scheduled_completion)
|
if (values.start && !values.scheduled_completion) {
|
||||||
form.setFieldsValue({
|
const addDays = bodyshop.ss_configuration.nobusinessdays
|
||||||
scheduled_completion: dayjs(values.start).businessDaysAdd(totalHours / bodyshop.target_touchtime, "day")
|
? dayjs(values.start).add(totalHours / (bodyshop.target_touchtime || 1), "day")
|
||||||
});
|
: dayjs(values.start).businessDaysAdd(totalHours / (bodyshop.target_touchtime || 1), "day");
|
||||||
|
form.setFieldsValue({ scheduled_completion: addDays });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -209,7 +209,7 @@ export function ScheduleJobModalContainer({
|
|||||||
onOk={() => form.submit()}
|
onOk={() => form.submit()}
|
||||||
width={"90%"}
|
width={"90%"}
|
||||||
maskClosable={false}
|
maskClosable={false}
|
||||||
destroyOnClose
|
destroyOnHidden
|
||||||
okButtonProps={{
|
okButtonProps={{
|
||||||
loading: loading
|
loading: loading
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ export default function ScoreboardJobsList({ scoreBoardlist }) {
|
|||||||
<>
|
<>
|
||||||
<Modal
|
<Modal
|
||||||
open={state.open}
|
open={state.open}
|
||||||
destroyOnClose
|
destroyOnHidden
|
||||||
width="80%"
|
width="80%"
|
||||||
closable={false}
|
closable={false}
|
||||||
cancelButtonProps={{ style: { display: "none" } }}
|
cancelButtonProps={{ style: { display: "none" } }}
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
||||||
import { Button, Card, Tabs } from "antd";
|
import { Button, Card, Tabs } from "antd";
|
||||||
import React from "react";
|
import React from "react";
|
||||||
@@ -24,6 +23,8 @@ import ShopInfoRoGuard from "./shop-info.roguard.component";
|
|||||||
import ShopInfoIntellipay from "./shop-intellipay-config.component";
|
import ShopInfoIntellipay from "./shop-intellipay-config.component";
|
||||||
import { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component";
|
import { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component";
|
||||||
import LockWrapperComponent from "../lock-wrapper/lock-wrapper.component";
|
import LockWrapperComponent from "../lock-wrapper/lock-wrapper.component";
|
||||||
|
import { useSocket } from "../../contexts/SocketIO/useSocket.js";
|
||||||
|
import ShopInfoNotificationsAutoadd from "./shop-info.notifications-autoadd.component.jsx";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop
|
bodyshop: selectBodyshop
|
||||||
@@ -41,6 +42,7 @@ export function ShopInfoComponent({ bodyshop, form, saveLoading }) {
|
|||||||
names: ["CriticalPartsScanning", "Enhanced_Payroll"],
|
names: ["CriticalPartsScanning", "Enhanced_Payroll"],
|
||||||
splitKey: bodyshop.imexshopid
|
splitKey: bodyshop.imexshopid
|
||||||
});
|
});
|
||||||
|
const { scenarioNotificationsOn } = useSocket();
|
||||||
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const history = useNavigate();
|
const history = useNavigate();
|
||||||
@@ -137,9 +139,21 @@ export function ShopInfoComponent({ bodyshop, form, saveLoading }) {
|
|||||||
|
|
||||||
{
|
{
|
||||||
key: "intellipay",
|
key: "intellipay",
|
||||||
label: InstanceRenderManager({ rome: t("bodyshop.labels.romepay"), imex: t("bodyshop.labels.imexpay") }),
|
label: InstanceRenderManager({
|
||||||
|
rome: t("bodyshop.labels.romepay"),
|
||||||
|
imex: t("bodyshop.labels.imexpay")
|
||||||
|
}),
|
||||||
children: <ShopInfoIntellipay form={form} />
|
children: <ShopInfoIntellipay form={form} />
|
||||||
}
|
},
|
||||||
|
...(scenarioNotificationsOn
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
key: "notifications_autoadd",
|
||||||
|
label: t("bodyshop.labels.notifications.followers"),
|
||||||
|
children: <ShopInfoNotificationsAutoadd form={form} bodyshop={bodyshop} />
|
||||||
|
}
|
||||||
|
]
|
||||||
|
: [])
|
||||||
];
|
];
|
||||||
return (
|
return (
|
||||||
<Card
|
<Card
|
||||||
|
|||||||
@@ -906,6 +906,7 @@ export function ShopInfoGeneral({ form, bodyshop }) {
|
|||||||
add();
|
add();
|
||||||
}}
|
}}
|
||||||
style={{ width: "100%" }}
|
style={{ width: "100%" }}
|
||||||
|
id="insurancecos-add-button"
|
||||||
>
|
>
|
||||||
{t("general.actions.add")}
|
{t("general.actions.add")}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -0,0 +1,57 @@
|
|||||||
|
import { Form, Typography } from "antd";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import EmployeeSearchSelectComponent from "../employee-search-select/employee-search-select.component.jsx";
|
||||||
|
|
||||||
|
const { Text, Paragraph } = Typography;
|
||||||
|
|
||||||
|
export default function ShopInfoNotificationsAutoadd({ bodyshop }) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
// Filter employee options to ensure active employees with valid IDs
|
||||||
|
const employeeOptions = bodyshop?.employees?.filter((e) => e.active && e.user_email && e.id) || [];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Paragraph>{t("bodyshop.fields.notifications.description")}</Paragraph>
|
||||||
|
<Text type="secondary">{t("bodyshop.labels.notifications.followers")}</Text>
|
||||||
|
{employeeOptions.length > 0 ? (
|
||||||
|
<Form.Item
|
||||||
|
name="notification_followers"
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
type: "array",
|
||||||
|
message: t("general.validation.array")
|
||||||
|
},
|
||||||
|
{
|
||||||
|
validator: async (_, value) => {
|
||||||
|
if (!value || value.length === 0) {
|
||||||
|
return Promise.resolve(); // Allow empty array
|
||||||
|
}
|
||||||
|
const hasInvalid = value.some((id) => id == null || typeof id !== "string" || id.trim() === "");
|
||||||
|
if (hasInvalid) {
|
||||||
|
return Promise.reject(new Error(t("bodyshop.fields.notifications.invalid_followers")));
|
||||||
|
}
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<EmployeeSearchSelectComponent
|
||||||
|
style={{ minWidth: "100%" }}
|
||||||
|
mode="multiple"
|
||||||
|
options={employeeOptions}
|
||||||
|
placeholder={t("bodyshop.fields.notifications.placeholder")}
|
||||||
|
showEmail={true}
|
||||||
|
onChange={(value) => {
|
||||||
|
// Filter out null or invalid values before passing to Form
|
||||||
|
const cleanedValue = value?.filter((id) => id != null && typeof id === "string" && id.trim() !== "");
|
||||||
|
return cleanedValue;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
) : (
|
||||||
|
<Text type="secondary">{t("bodyshop.fields.no_employees_available")}</Text>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,16 +1,15 @@
|
|||||||
import { DeleteFilled } from "@ant-design/icons";
|
import { DeleteFilled } from "@ant-design/icons";
|
||||||
import { Button, Divider, Form, Input, InputNumber, Select, Space, Switch, TimePicker } from "antd";
|
import { Button, Divider, Form, Input, InputNumber, Select, Space, Switch, TimePicker } from "antd";
|
||||||
import React from "react";
|
|
||||||
import { useTranslation } from "react-i18next";
|
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 { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component";
|
||||||
import ColorpickerFormItemComponent from "../form-items-formatted/colorpicker-form-item.component";
|
import ColorpickerFormItemComponent from "../form-items-formatted/colorpicker-form-item.component";
|
||||||
import FormListMoveArrows from "../form-list-move-arrows/form-list-move-arrows.component";
|
import FormListMoveArrows from "../form-list-move-arrows/form-list-move-arrows.component";
|
||||||
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
||||||
import { ColorPicker } from "./shop-info.rostatus.component";
|
import { ColorPicker } from "./shop-info.rostatus.component";
|
||||||
|
|
||||||
import { connect } from "react-redux";
|
|
||||||
import { createStructuredSelector } from "reselect";
|
|
||||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop
|
bodyshop: selectBodyshop
|
||||||
});
|
});
|
||||||
@@ -78,6 +77,13 @@ export function ShopInfoSchedulingComponent({ form, bodyshop }) {
|
|||||||
>
|
>
|
||||||
<InputNumber min={0} />
|
<InputNumber min={0} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
name={["ss_configuration", "nobusinessdays"]}
|
||||||
|
label={t("bodyshop.fields.ss_configuration.nobusinessdays")}
|
||||||
|
valuePropName="checked"
|
||||||
|
>
|
||||||
|
<Switch />
|
||||||
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name={["md_lost_sale_reasons"]}
|
name={["md_lost_sale_reasons"]}
|
||||||
label={t("bodyshop.fields.md_lost_sale_reasons")}
|
label={t("bodyshop.fields.md_lost_sale_reasons")}
|
||||||
|
|||||||
@@ -25,23 +25,6 @@ export function ShopTemplateTestRender({ bodyshop, query, emailEditorRef, style
|
|||||||
|
|
||||||
emailEditorRef.current.exportHtml(async (data) => {
|
emailEditorRef.current.exportHtml(async (data) => {
|
||||||
try {
|
try {
|
||||||
// const inlineHtml = await axios.post("/render/inlinecss", {
|
|
||||||
// html: data.html,
|
|
||||||
// url: `${window.location.protocol}://${window.location.host}/`,
|
|
||||||
// });
|
|
||||||
|
|
||||||
// const { data: contextData } = await client.query({
|
|
||||||
// query: gql(query),
|
|
||||||
// variables: variables,
|
|
||||||
//
|
|
||||||
// });
|
|
||||||
|
|
||||||
// const renderResponse = await axios.post("/render", {
|
|
||||||
// view: inlineHtml.data,
|
|
||||||
// context: { ...contextData, bodyshop: bodyshop },
|
|
||||||
// });
|
|
||||||
// displayTemplateInWindowNoprint(renderResponse.data);
|
|
||||||
|
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
|
|||||||
@@ -275,7 +275,7 @@ export function TaskUpsertModalContainer({ bodyshop, currentUser, taskUpsert, to
|
|||||||
toggleModalVisible();
|
toggleModalVisible();
|
||||||
}}
|
}}
|
||||||
okButtonProps={{ disabled: !isTouched }}
|
okButtonProps={{ disabled: !isTouched }}
|
||||||
destroyOnClose
|
destroyOnHidden
|
||||||
>
|
>
|
||||||
<Form
|
<Form
|
||||||
form={form}
|
form={form}
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ export function TechLookupJobsDrawer({ bodyshop, setPrintCenterContext }) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Drawer open={!!selected} destroyOnClose width={drawerPercentage} placement="right" onClose={handleDrawerClose}>
|
<Drawer open={!!selected} destroyOnHidden width={drawerPercentage} placement="right" onClose={handleDrawerClose}>
|
||||||
{loading ? <LoadingSpinner /> : null}
|
{loading ? <LoadingSpinner /> : null}
|
||||||
{error ? <AlertComponent message={error.message} type="error" /> : null}
|
{error ? <AlertComponent message={error.message} type="error" /> : null}
|
||||||
{data ? (
|
{data ? (
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ export function TimeTicketListTeamPay({ bodyshop, context, actions }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Modal width={"80%"} open={visible} destroyOnClose onOk={handleOk} onCancel={() => setVisible(false)}>
|
<Modal width={"80%"} open={visible} destroyOnHidden onOk={handleOk} onCancel={() => setVisible(false)}>
|
||||||
<Form layout="vertical" form={form} initialValues={{ jobid: jobId }}>
|
<Form layout="vertical" form={form} initialValues={{ jobid: jobId }}>
|
||||||
<LayoutFormRow grow noDivider>
|
<LayoutFormRow grow noDivider>
|
||||||
<Form.Item shouldUpdate>
|
<Form.Item shouldUpdate>
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { useLazyQuery } from "@apollo/client";
|
import { useLazyQuery } from "@apollo/client";
|
||||||
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
||||||
import { Form, Input, InputNumber, Select, Switch } from "antd";
|
import { Card, Form, Input, InputNumber, Select, Space, Switch } from "antd";
|
||||||
import React from "react";
|
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
@@ -19,6 +18,7 @@ import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
|||||||
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component";
|
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component";
|
||||||
import { HasRbacAccess } from "../rbac-wrapper/rbac-wrapper.component";
|
import { HasRbacAccess } from "../rbac-wrapper/rbac-wrapper.component";
|
||||||
import TimeTicketList from "../time-ticket-list/time-ticket-list.component";
|
import TimeTicketList from "../time-ticket-list/time-ticket-list.component";
|
||||||
|
import JobEmployeeAssignmentsContainer from "./../job-employee-assignments/job-employee-assignments.container";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
bodyshop: selectBodyshop,
|
bodyshop: selectBodyshop,
|
||||||
@@ -319,10 +319,15 @@ export function TimeTicketModalComponent({
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function LaborAllocationContainer({ jobid, loading, lineTicketData, hideTimeTickets = false }) {
|
export function LaborAllocationContainer({ jobid, loading, lineTicketData, hideTimeTickets = false }) {
|
||||||
|
const { t } = useTranslation();
|
||||||
if (loading) return <LoadingSkeleton />;
|
if (loading) return <LoadingSkeleton />;
|
||||||
if (!lineTicketData) return null;
|
if (!lineTicketData) return null;
|
||||||
|
if (!jobid) return null;
|
||||||
return (
|
return (
|
||||||
<div>
|
<Space direction="vertical" style={{ width: "100%" }}>
|
||||||
|
<Card style={{ height: "100%" }} title={t("jobs.labels.employeeassignments")}>
|
||||||
|
<JobEmployeeAssignmentsContainer job={lineTicketData.jobs_by_pk} />
|
||||||
|
</Card>
|
||||||
<LaborAllocationsTable
|
<LaborAllocationsTable
|
||||||
jobId={jobid}
|
jobId={jobid}
|
||||||
joblines={lineTicketData.joblines}
|
joblines={lineTicketData.joblines}
|
||||||
@@ -332,6 +337,6 @@ export function LaborAllocationContainer({ jobid, loading, lineTicketData, hideT
|
|||||||
{!hideTimeTickets && (
|
{!hideTimeTickets && (
|
||||||
<TimeTicketList loading={loading} timetickets={jobid ? lineTicketData.timetickets : []} techConsole />
|
<TimeTicketList loading={loading} timetickets={jobid ? lineTicketData.timetickets : []} techConsole />
|
||||||
)}
|
)}
|
||||||
</div>
|
</Space>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,10 +2,11 @@ import { PageHeader } from "@ant-design/pro-layout";
|
|||||||
import { useMutation, useQuery } from "@apollo/client";
|
import { useMutation, useQuery } from "@apollo/client";
|
||||||
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
||||||
import { Button, Form, Modal, Space } from "antd";
|
import { Button, Form, Modal, Space } from "antd";
|
||||||
import React, { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { createStructuredSelector } from "reselect";
|
import { createStructuredSelector } from "reselect";
|
||||||
|
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
|
||||||
import { QUERY_ACTIVE_EMPLOYEES } from "../../graphql/employees.queries";
|
import { QUERY_ACTIVE_EMPLOYEES } from "../../graphql/employees.queries";
|
||||||
import { INSERT_NEW_TIME_TICKET, UPDATE_TIME_TICKET } from "../../graphql/timetickets.queries";
|
import { INSERT_NEW_TIME_TICKET, UPDATE_TIME_TICKET } from "../../graphql/timetickets.queries";
|
||||||
import { toggleModalVisible } from "../../redux/modals/modals.actions";
|
import { toggleModalVisible } from "../../redux/modals/modals.actions";
|
||||||
@@ -14,7 +15,6 @@ import { selectBodyshop } from "../../redux/user/user.selectors";
|
|||||||
import dayjs from "../../utils/day";
|
import dayjs from "../../utils/day";
|
||||||
import TimeTicketsCommitToggleComponent from "../time-tickets-commit-toggle/time-tickets-commit-toggle.component";
|
import TimeTicketsCommitToggleComponent from "../time-tickets-commit-toggle/time-tickets-commit-toggle.component";
|
||||||
import TimeTicketModalComponent from "./time-ticket-modal.component";
|
import TimeTicketModalComponent from "./time-ticket-modal.component";
|
||||||
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
|
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
timeTicketModal: selectTimeTicket,
|
timeTicketModal: selectTimeTicket,
|
||||||
@@ -81,7 +81,7 @@ export function TimeTicketModalContainer({ timeTicketModal, toggleModalVisible,
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleMutationSuccess = (response) => {
|
const handleMutationSuccess = () => {
|
||||||
notification["success"]({
|
notification["success"]({
|
||||||
message: t("timetickets.successes.created")
|
message: t("timetickets.successes.created")
|
||||||
});
|
});
|
||||||
@@ -123,7 +123,7 @@ export function TimeTicketModalContainer({ timeTicketModal, toggleModalVisible,
|
|||||||
if (timeTicketModal.open) form.resetFields();
|
if (timeTicketModal.open) form.resetFields();
|
||||||
}, [timeTicketModal.open, form]);
|
}, [timeTicketModal.open, form]);
|
||||||
|
|
||||||
const handleFieldsChange = (changedFields, allFields) => {
|
const handleFieldsChange = (changedFields) => {
|
||||||
if (!!changedFields.employeeid && !!EmployeeAutoCompleteData) {
|
if (!!changedFields.employeeid && !!EmployeeAutoCompleteData) {
|
||||||
const emps = EmployeeAutoCompleteData.employees.filter((e) => e.id === changedFields.employeeid);
|
const emps = EmployeeAutoCompleteData.employees.filter((e) => e.id === changedFields.employeeid);
|
||||||
form.setFieldsValue({
|
form.setFieldsValue({
|
||||||
@@ -181,7 +181,8 @@ export function TimeTicketModalContainer({ timeTicketModal, toggleModalVisible,
|
|||||||
)}
|
)}
|
||||||
</Space>
|
</Space>
|
||||||
}
|
}
|
||||||
destroyOnClose
|
destroyOnHidden
|
||||||
|
id="time-ticket-modal"
|
||||||
>
|
>
|
||||||
<Form
|
<Form
|
||||||
onFinish={handleFinish}
|
onFinish={handleFinish}
|
||||||
|
|||||||
@@ -119,7 +119,7 @@ export function TimeTickeTaskModalContainer({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
destroyOnClose
|
destroyOnHidden
|
||||||
open={open}
|
open={open}
|
||||||
onCancel={() => {
|
onCancel={() => {
|
||||||
toggleModalVisible();
|
toggleModalVisible();
|
||||||
|
|||||||
@@ -113,7 +113,7 @@ export function UpdateAlert({ updateAvailable }) {
|
|||||||
</Col>
|
</Col>
|
||||||
<Col sm={24} md={8} lg={6}>
|
<Col sm={24} md={8} lg={6}>
|
||||||
<Space wrap>
|
<Space wrap>
|
||||||
<Button onClick={() => window.open("https://imex-online.noticeable.news/", "_blank")}>
|
<Button onClick={() => window.open("https://shopmanagement.canny.io/changelog", "_blank")}>
|
||||||
{i18n.t("general.actions.viewreleasenotes")}
|
{i18n.t("general.actions.viewreleasenotes")}
|
||||||
</Button>
|
</Button>
|
||||||
<Button loading={loading} type="primary" onClick={() => ReloadNewVersion()}>
|
<Button loading={loading} type="primary" onClick={() => ReloadNewVersion()}>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { createContext, useContext, useEffect, useRef, useState } from "react";
|
// SocketProvider.js
|
||||||
|
import { useEffect, useRef, useState } from "react";
|
||||||
import SocketIO from "socket.io-client";
|
import SocketIO from "socket.io-client";
|
||||||
import { auth } from "../../firebase/firebase.utils";
|
import { auth } from "../../firebase/firebase.utils";
|
||||||
import { store } from "../../redux/store";
|
import { store } from "../../redux/store";
|
||||||
@@ -15,10 +16,7 @@ import {
|
|||||||
import { useMutation } from "@apollo/client";
|
import { useMutation } from "@apollo/client";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
||||||
|
import { SocketContext, INITIAL_NOTIFICATIONS } from "./useSocket.js";
|
||||||
const SocketContext = createContext(null);
|
|
||||||
|
|
||||||
const INITIAL_NOTIFICATIONS = 10;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Socket Provider - Scenario Notifications / Web Socket related items
|
* Socket Provider - Scenario Notifications / Web Socket related items
|
||||||
@@ -216,7 +214,6 @@ const SocketProvider = ({ children, bodyshop, navigate, currentUser }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleNotification = (data) => {
|
const handleNotification = (data) => {
|
||||||
// Scenario Notifications have been disabled, bail.
|
|
||||||
if (Realtime_Notifications_UI?.treatment !== "on") {
|
if (Realtime_Notifications_UI?.treatment !== "on") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -336,7 +333,6 @@ const SocketProvider = ({ children, bodyshop, navigate, currentUser }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleSyncNotificationRead = ({ notificationId, timestamp }) => {
|
const handleSyncNotificationRead = ({ notificationId, timestamp }) => {
|
||||||
// Scenario Notifications have been disabled, bail.
|
|
||||||
if (Realtime_Notifications_UI?.treatment !== "on") {
|
if (Realtime_Notifications_UI?.treatment !== "on") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -378,7 +374,6 @@ const SocketProvider = ({ children, bodyshop, navigate, currentUser }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleSyncAllNotificationsRead = ({ timestamp }) => {
|
const handleSyncAllNotificationsRead = ({ timestamp }) => {
|
||||||
// Scenario Notifications have been disabled, bail.
|
|
||||||
if (Realtime_Notifications_UI?.treatment !== "on") {
|
if (Realtime_Notifications_UI?.treatment !== "on") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -490,11 +485,4 @@ const SocketProvider = ({ children, bodyshop, navigate, currentUser }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const useSocket = () => {
|
export default SocketProvider;
|
||||||
const context = useContext(SocketContext);
|
|
||||||
// NOTE: Not sure if we absolutely require this, does cause slipups on dev env
|
|
||||||
if (!context) throw new Error("useSocket must be used within a SocketProvider");
|
|
||||||
return context;
|
|
||||||
};
|
|
||||||
|
|
||||||
export { SocketContext, SocketProvider, INITIAL_NOTIFICATIONS, useSocket };
|
|
||||||
13
client/src/contexts/SocketIO/useSocket.js
Normal file
13
client/src/contexts/SocketIO/useSocket.js
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import { createContext, useContext } from "react";
|
||||||
|
|
||||||
|
const SocketContext = createContext(null);
|
||||||
|
|
||||||
|
const INITIAL_NOTIFICATIONS = 10;
|
||||||
|
|
||||||
|
const useSocket = () => {
|
||||||
|
const context = useContext(SocketContext);
|
||||||
|
if (!context) throw new Error("useSocket must be used within a SocketProvider");
|
||||||
|
return context;
|
||||||
|
};
|
||||||
|
|
||||||
|
export { SocketContext, INITIAL_NOTIFICATIONS, useSocket };
|
||||||
@@ -57,6 +57,7 @@ export const QUERY_BODYSHOP = gql`
|
|||||||
logo_img_path
|
logo_img_path
|
||||||
md_ro_statuses
|
md_ro_statuses
|
||||||
md_order_statuses
|
md_order_statuses
|
||||||
|
tours_enabled
|
||||||
md_functionality_toggles
|
md_functionality_toggles
|
||||||
shopname
|
shopname
|
||||||
state
|
state
|
||||||
@@ -140,6 +141,7 @@ export const QUERY_BODYSHOP = gql`
|
|||||||
use_paint_scale_data
|
use_paint_scale_data
|
||||||
intellipay_config
|
intellipay_config
|
||||||
md_ro_guard
|
md_ro_guard
|
||||||
|
notification_followers
|
||||||
employee_teams(order_by: { name: asc }, where: { active: { _eq: true } }) {
|
employee_teams(order_by: { name: asc }, where: { active: { _eq: true } }) {
|
||||||
id
|
id
|
||||||
name
|
name
|
||||||
@@ -186,6 +188,7 @@ export const UPDATE_SHOP = gql`
|
|||||||
phone
|
phone
|
||||||
federal_tax_id
|
federal_tax_id
|
||||||
id
|
id
|
||||||
|
tours_enabled
|
||||||
insurance_vendor_id
|
insurance_vendor_id
|
||||||
logo_img_path
|
logo_img_path
|
||||||
md_ro_statuses
|
md_ro_statuses
|
||||||
@@ -269,6 +272,7 @@ export const UPDATE_SHOP = gql`
|
|||||||
md_tasks_presets
|
md_tasks_presets
|
||||||
intellipay_config
|
intellipay_config
|
||||||
md_ro_guard
|
md_ro_guard
|
||||||
|
notification_followers
|
||||||
employee_teams(order_by: { name: asc }, where: { active: { _eq: true } }) {
|
employee_teams(order_by: { name: asc }, where: { active: { _eq: true } }) {
|
||||||
id
|
id
|
||||||
name
|
name
|
||||||
|
|||||||
@@ -35,6 +35,30 @@ export const GET_LINE_TICKET_BY_PK = gql`
|
|||||||
lbr_adjustments
|
lbr_adjustments
|
||||||
converted
|
converted
|
||||||
status
|
status
|
||||||
|
employee_body
|
||||||
|
employee_body_rel {
|
||||||
|
id
|
||||||
|
first_name
|
||||||
|
last_name
|
||||||
|
}
|
||||||
|
employee_csr
|
||||||
|
employee_csr_rel {
|
||||||
|
id
|
||||||
|
first_name
|
||||||
|
last_name
|
||||||
|
}
|
||||||
|
employee_prep
|
||||||
|
employee_prep_rel {
|
||||||
|
id
|
||||||
|
first_name
|
||||||
|
last_name
|
||||||
|
}
|
||||||
|
employee_refinish
|
||||||
|
employee_refinish_rel {
|
||||||
|
id
|
||||||
|
first_name
|
||||||
|
last_name
|
||||||
|
}
|
||||||
}
|
}
|
||||||
joblines(where: { jobid: { _eq: $id }, removed: { _eq: false } }) {
|
joblines(where: { jobid: { _eq: $id }, removed: { _eq: false } }) {
|
||||||
id
|
id
|
||||||
|
|||||||
@@ -423,6 +423,7 @@ export const GET_JOB_BY_PK = gql`
|
|||||||
actual_completion
|
actual_completion
|
||||||
actual_delivery
|
actual_delivery
|
||||||
actual_in
|
actual_in
|
||||||
|
acv_amount
|
||||||
adjustment_bottom_line
|
adjustment_bottom_line
|
||||||
alt_transport
|
alt_transport
|
||||||
area_of_damage
|
area_of_damage
|
||||||
@@ -511,6 +512,7 @@ export const GET_JOB_BY_PK = gql`
|
|||||||
est_ph1
|
est_ph1
|
||||||
flat_rate_ats
|
flat_rate_ats
|
||||||
federal_tax_rate
|
federal_tax_rate
|
||||||
|
hit_and_run
|
||||||
id
|
id
|
||||||
inproduction
|
inproduction
|
||||||
ins_addr1
|
ins_addr1
|
||||||
@@ -683,6 +685,8 @@ export const GET_JOB_BY_PK = gql`
|
|||||||
scheduled_delivery
|
scheduled_delivery
|
||||||
scheduled_in
|
scheduled_in
|
||||||
selling_dealer
|
selling_dealer
|
||||||
|
estimate_approved
|
||||||
|
estimate_sent_approval
|
||||||
selling_dealer_contact
|
selling_dealer_contact
|
||||||
servicing_dealer
|
servicing_dealer
|
||||||
servicing_dealer_contact
|
servicing_dealer_contact
|
||||||
@@ -927,6 +931,8 @@ export const QUERY_JOB_CARD_DETAILS = gql`
|
|||||||
date_exported
|
date_exported
|
||||||
date_repairstarted
|
date_repairstarted
|
||||||
date_scheduled
|
date_scheduled
|
||||||
|
estimate_sent_approval
|
||||||
|
estimate_approved
|
||||||
date_estimated
|
date_estimated
|
||||||
employee_body_rel {
|
employee_body_rel {
|
||||||
id
|
id
|
||||||
@@ -1075,6 +1081,8 @@ export const UPDATE_JOB = gql`
|
|||||||
date_repairstarted
|
date_repairstarted
|
||||||
date_void
|
date_void
|
||||||
date_lost_sale
|
date_lost_sale
|
||||||
|
estimate_sent_approval
|
||||||
|
estimate_approved
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2429,6 +2437,8 @@ export const QUERY_PARTS_QUEUE_CARD_DETAILS = gql`
|
|||||||
plate_st
|
plate_st
|
||||||
po_number
|
po_number
|
||||||
production_vars
|
production_vars
|
||||||
|
estimate_sent_approval
|
||||||
|
estimate_approved
|
||||||
ro_number
|
ro_number
|
||||||
scheduled_completion
|
scheduled_completion
|
||||||
scheduled_delivery
|
scheduled_delivery
|
||||||
@@ -2570,6 +2580,20 @@ export const GET_JOB_BY_PK_QUICK_INTAKE = gql`
|
|||||||
actual_completion
|
actual_completion
|
||||||
scheduled_delivery
|
scheduled_delivery
|
||||||
actual_delivery
|
actual_delivery
|
||||||
|
labhrs: joblines_aggregate(where: { _and: [{ mod_lbr_ty: { _neq: "LAR" } }, { removed: { _eq: false } }] }) {
|
||||||
|
aggregate {
|
||||||
|
sum {
|
||||||
|
mod_lb_hrs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
larhrs: joblines_aggregate(where: { _and: [{ mod_lbr_ty: { _eq: "LAR" } }, { removed: { _eq: false } }] }) {
|
||||||
|
aggregate {
|
||||||
|
sum {
|
||||||
|
mod_lb_hrs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -78,6 +78,9 @@ export const QUERY_PARTS_ORDER_OEC = gql`
|
|||||||
}
|
}
|
||||||
ro_number
|
ro_number
|
||||||
clm_no
|
clm_no
|
||||||
|
cieca_stl
|
||||||
|
cieca_ttl
|
||||||
|
cieca_pfl
|
||||||
asgn_no
|
asgn_no
|
||||||
asgn_date
|
asgn_date
|
||||||
state_tax_rate
|
state_tax_rate
|
||||||
@@ -164,6 +167,7 @@ export const QUERY_PARTS_ORDER_OEC = gql`
|
|||||||
loss_desc
|
loss_desc
|
||||||
loss_of_use
|
loss_of_use
|
||||||
loss_type
|
loss_type
|
||||||
|
materials
|
||||||
ownr_addr1
|
ownr_addr1
|
||||||
ownr_addr2
|
ownr_addr2
|
||||||
ownr_city
|
ownr_city
|
||||||
|
|||||||
@@ -91,6 +91,7 @@ export const QUERY_NOTIFICATION_SETTINGS = gql`
|
|||||||
associations(where: { _and: { useremail: { _eq: $email }, active: { _eq: true } } }) {
|
associations(where: { _and: { useremail: { _eq: $email }, active: { _eq: true } } }) {
|
||||||
id
|
id
|
||||||
notification_settings
|
notification_settings
|
||||||
|
notifications_autoadd
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
@@ -103,3 +104,12 @@ export const UPDATE_NOTIFICATION_SETTINGS = gql`
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
export const UPDATE_NOTIFICATIONS_AUTOADD = gql`
|
||||||
|
mutation UPDATE_NOTIFICATIONS_AUTOADD($id: uuid!, $autoadd: Boolean!) {
|
||||||
|
update_associations_by_pk(pk_columns: { id: $id }, _set: { notifications_autoadd: $autoadd }) {
|
||||||
|
id
|
||||||
|
notifications_autoadd
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|||||||
42
client/src/pages/feature-request/feature-request.page.jsx
Normal file
42
client/src/pages/feature-request/feature-request.page.jsx
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
import axios from "axios";
|
||||||
|
import React, { useEffect } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { connect } from "react-redux";
|
||||||
|
import { setBreadcrumbs, setSelectedHeader } from "../../redux/application/application.actions";
|
||||||
|
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
||||||
|
|
||||||
|
const mapDispatchToProps = (dispatch) => ({
|
||||||
|
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
|
||||||
|
setSelectedHeader: (key) => dispatch(setSelectedHeader(key))
|
||||||
|
});
|
||||||
|
|
||||||
|
export function FeedbackPage({ setBreadcrumbs, setSelectedHeader }) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
document.title = t("titles.feature-request", {
|
||||||
|
app: InstanceRenderManager({
|
||||||
|
imex: "$t(titles.imexonline)",
|
||||||
|
rome: "$t(titles.romeonline)"
|
||||||
|
})
|
||||||
|
});
|
||||||
|
setBreadcrumbs([{ link: "/manage/feature-request", label: t("titles.bc.feature-request") }]);
|
||||||
|
}, [t, setBreadcrumbs, setSelectedHeader]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
async function RenderCanny() {
|
||||||
|
const ssoToken = await axios.post("/sso/canny");
|
||||||
|
window.Canny("render", {
|
||||||
|
boardToken: "bba97b06-70db-0334-dee7-8108d73ef614",
|
||||||
|
basePath: `/manage/feature-request`, // See step 2
|
||||||
|
ssoToken: ssoToken.data, // See step 3,
|
||||||
|
theme: "light" // options: light [default], dark, auto
|
||||||
|
});
|
||||||
|
}
|
||||||
|
RenderCanny();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return <div data-canny />;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(null, mapDispatchToProps)(FeedbackPage);
|
||||||
@@ -56,7 +56,7 @@ import { DateTimeFormat } from "../../utils/DateFormatter";
|
|||||||
import dayjs from "../../utils/day";
|
import dayjs from "../../utils/day";
|
||||||
import UndefinedToNull from "../../utils/undefinedtonull";
|
import UndefinedToNull from "../../utils/undefinedtonull";
|
||||||
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
|
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
|
||||||
import { useSocket } from "../../contexts/SocketIO/useSocket.jsx";
|
import { useSocket } from "../../contexts/SocketIO/useSocket.js";
|
||||||
import JobWatcherToggleContainer from "../../components/job-watcher-toggle/job-watcher-toggle.container.jsx";
|
import JobWatcherToggleContainer from "../../components/job-watcher-toggle/job-watcher-toggle.container.jsx";
|
||||||
|
|
||||||
const mapStateToProps = createStructuredSelector({
|
const mapStateToProps = createStructuredSelector({
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { FloatButton, Layout, Spin } from "antd";
|
import { Button, FloatButton, Layout, Space, Spin } from "antd";
|
||||||
|
import { AlertOutlined, BulbOutlined } from "@ant-design/icons";
|
||||||
|
|
||||||
// import preval from "preval.macro";
|
// import preval from "preval.macro";
|
||||||
import React, { lazy, Suspense, useEffect, useState } from "react";
|
import React, { lazy, Suspense, useEffect, useState } from "react";
|
||||||
@@ -19,8 +20,6 @@ import LoadingSpinner from "../../components/loading-spinner/loading-spinner.com
|
|||||||
import PartnerPingComponent from "../../components/partner-ping/partner-ping.component";
|
import PartnerPingComponent from "../../components/partner-ping/partner-ping.component";
|
||||||
import PrintCenterModalContainer from "../../components/print-center-modal/print-center-modal.container";
|
import PrintCenterModalContainer from "../../components/print-center-modal/print-center-modal.container";
|
||||||
import ShopSubStatusComponent from "../../components/shop-sub-status/shop-sub-status.component";
|
import ShopSubStatusComponent from "../../components/shop-sub-status/shop-sub-status.component";
|
||||||
import { requestForToken } from "../../firebase/firebase.utils";
|
|
||||||
import { useSocket } from "../../contexts/SocketIO/useSocket.jsx";
|
|
||||||
import { selectBodyshop, selectInstanceConflict } from "../../redux/user/user.selectors";
|
import { selectBodyshop, selectInstanceConflict } from "../../redux/user/user.selectors";
|
||||||
import UpdateAlert from "../../components/update-alert/update-alert.component";
|
import UpdateAlert from "../../components/update-alert/update-alert.component";
|
||||||
import InstanceRenderManager from "../../utils/instanceRenderMgr.js";
|
import InstanceRenderManager from "../../utils/instanceRenderMgr.js";
|
||||||
@@ -29,6 +28,7 @@ import WssStatusDisplayComponent from "../../components/wss-status-display/wss-s
|
|||||||
import { selectAlerts } from "../../redux/application/application.selectors.js";
|
import { selectAlerts } from "../../redux/application/application.selectors.js";
|
||||||
import { addAlerts } from "../../redux/application/application.actions.js";
|
import { addAlerts } from "../../redux/application/application.actions.js";
|
||||||
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
|
import { useNotification } from "../../contexts/Notifications/notificationContext.jsx";
|
||||||
|
import { useSocket } from "../../contexts/SocketIO/useSocket.js";
|
||||||
|
|
||||||
const JobsPage = lazy(() => import("../jobs/jobs.page"));
|
const JobsPage = lazy(() => import("../jobs/jobs.page"));
|
||||||
|
|
||||||
@@ -56,6 +56,7 @@ const ContractCreatePage = lazy(() => import("../contract-create/contract-create
|
|||||||
const ContractDetailPage = lazy(() => import("../contract-detail/contract-detail.page.container"));
|
const ContractDetailPage = lazy(() => import("../contract-detail/contract-detail.page.container"));
|
||||||
const ContractsList = lazy(() => import("../contracts/contracts.page.container"));
|
const ContractsList = lazy(() => import("../contracts/contracts.page.container"));
|
||||||
const BillsListPage = lazy(() => import("../bills/bills.page.container"));
|
const BillsListPage = lazy(() => import("../bills/bills.page.container"));
|
||||||
|
const FeatureRequestPage = lazy(() => import("../feature-request/feature-request.page.jsx"));
|
||||||
|
|
||||||
const JobCostingModal = lazy(() => import("../../components/job-costing-modal/job-costing-modal.container"));
|
const JobCostingModal = lazy(() => import("../../components/job-costing-modal/job-costing-modal.container"));
|
||||||
const ReportCenterModal = lazy(() => import("../../components/report-center-modal/report-center-modal.container"));
|
const ReportCenterModal = lazy(() => import("../../components/report-center-modal/report-center-modal.container"));
|
||||||
@@ -180,15 +181,12 @@ export function Manage({ conflict, bodyshop, alerts, setAlerts }) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [alerts, displayedAlertIds, notification]);
|
}, [alerts, displayedAlertIds, notification]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const widgetId = InstanceRenderManager({
|
window.Canny("initChangelog", {
|
||||||
imex: "IABVNO4scRKY11XBQkNr",
|
appID: "680bd2c7ee501290377f6686",
|
||||||
rome: "mQdqARMzkZRUVugJ6TdS"
|
position: "top",
|
||||||
});
|
align: "left",
|
||||||
window.noticeable.render("widget", widgetId);
|
theme: "light" // options: light [default], dark, auto
|
||||||
requestForToken().catch((error) => {
|
|
||||||
console.error(`Unable to request for token.`, error);
|
|
||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
@@ -480,6 +478,8 @@ export function Manage({ conflict, bodyshop, alerts, setAlerts }) {
|
|||||||
// element={<ShopTemplates />}
|
// element={<ShopTemplates />}
|
||||||
// />
|
// />
|
||||||
}
|
}
|
||||||
|
<Route path="/feature-request/*" index element={<FeatureRequestPage />} />
|
||||||
|
|
||||||
<Route
|
<Route
|
||||||
path="/shop/vendors"
|
path="/shop/vendors"
|
||||||
element={
|
element={
|
||||||
@@ -669,7 +669,12 @@ export function Manage({ conflict, bodyshop, alerts, setAlerts }) {
|
|||||||
margin: "1rem 0rem"
|
margin: "1rem 0rem"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div style={{ display: "flex" }}>
|
<Link to="/manage/feature-request">
|
||||||
|
<Button icon={<BulbOutlined />} type="text">
|
||||||
|
{t("general.labels.feature-request")}
|
||||||
|
</Button>
|
||||||
|
</Link>
|
||||||
|
<Space>
|
||||||
<WssStatusDisplayComponent />
|
<WssStatusDisplayComponent />
|
||||||
<div onClick={broadcastMessage}>
|
<div onClick={broadcastMessage}>
|
||||||
{`${InstanceRenderManager({
|
{`${InstanceRenderManager({
|
||||||
@@ -677,8 +682,10 @@ export function Manage({ conflict, bodyshop, alerts, setAlerts }) {
|
|||||||
rome: t("titles.romeonline")
|
rome: t("titles.romeonline")
|
||||||
})} - ${import.meta.env.VITE_APP_GIT_SHA_DATE}`}
|
})} - ${import.meta.env.VITE_APP_GIT_SHA_DATE}`}
|
||||||
</div>
|
</div>
|
||||||
<div id="noticeable-widget" style={{ marginLeft: "1rem" }} />
|
<Button icon={<AlertOutlined />} data-canny-changelog type="text">
|
||||||
</div>
|
{t("general.labels.changelog")}
|
||||||
|
</Button>
|
||||||
|
</Space>
|
||||||
<Link to="/disclaimer" target="_blank" style={{ color: "#ccc" }}>
|
<Link to="/disclaimer" target="_blank" style={{ color: "#ccc" }}>
|
||||||
Disclaimer & Notices
|
Disclaimer & Notices
|
||||||
</Link>
|
</Link>
|
||||||
|
|||||||
@@ -1,7 +1,4 @@
|
|||||||
import FingerprintJS from "@fingerprintjs/fingerprintjs";
|
import FingerprintJS from "@fingerprintjs/fingerprintjs";
|
||||||
import * as Sentry from "@sentry/browser";
|
|
||||||
import { notification } from "antd";
|
|
||||||
import axios from "axios";
|
|
||||||
import { setUserId, setUserProperties } from "@firebase/analytics";
|
import { setUserId, setUserProperties } from "@firebase/analytics";
|
||||||
import {
|
import {
|
||||||
checkActionCode,
|
checkActionCode,
|
||||||
@@ -12,6 +9,9 @@ import {
|
|||||||
} from "@firebase/auth";
|
} from "@firebase/auth";
|
||||||
import { arrayUnion, doc, getDoc, setDoc, updateDoc } from "@firebase/firestore";
|
import { arrayUnion, doc, getDoc, setDoc, updateDoc } from "@firebase/firestore";
|
||||||
import { getToken } from "@firebase/messaging";
|
import { getToken } from "@firebase/messaging";
|
||||||
|
import * as Sentry from "@sentry/react";
|
||||||
|
import { notification } from "antd";
|
||||||
|
import axios from "axios";
|
||||||
import i18next from "i18next";
|
import i18next from "i18next";
|
||||||
import LogRocket from "logrocket";
|
import LogRocket from "logrocket";
|
||||||
import { all, call, delay, put, select, takeLatest } from "redux-saga/effects";
|
import { all, call, delay, put, select, takeLatest } from "redux-saga/effects";
|
||||||
@@ -351,7 +351,14 @@ export function* SetAuthLevelFromShopDetails({ payload }) {
|
|||||||
});
|
});
|
||||||
payload.features?.allAccess === true
|
payload.features?.allAccess === true
|
||||||
? window.$crisp.push(["set", "session:segments", [["allAccess"]]])
|
? window.$crisp.push(["set", "session:segments", [["allAccess"]]])
|
||||||
: window.$crisp.push(["set", "session:segments", [["basic"]]]);
|
: (() => {
|
||||||
|
const featureKeys = Object.keys(payload.features).filter(
|
||||||
|
(key) =>
|
||||||
|
payload.features[key] === true ||
|
||||||
|
(typeof payload.features[key] === "string" && !isNaN(Date.parse(payload.features[key])))
|
||||||
|
);
|
||||||
|
window.$crisp.push(["set", "session:segments", [["basic", ...featureKeys]]]);
|
||||||
|
})();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn("Couldnt find $crisp.", error.message);
|
console.warn("Couldnt find $crisp.", error.message);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -335,7 +335,6 @@
|
|||||||
"intellipay_config": {
|
"intellipay_config": {
|
||||||
"cash_discount_percentage": "Cash Discount %",
|
"cash_discount_percentage": "Cash Discount %",
|
||||||
"enable_cash_discount": "Enable Cash Discounting",
|
"enable_cash_discount": "Enable Cash Discounting",
|
||||||
"payment_type": "Payment Type Map",
|
|
||||||
"payment_map": {
|
"payment_map": {
|
||||||
"amex": "American Express",
|
"amex": "American Express",
|
||||||
"disc": "Discover",
|
"disc": "Discover",
|
||||||
@@ -344,7 +343,8 @@
|
|||||||
"jcb": "JCB",
|
"jcb": "JCB",
|
||||||
"mast": "MasterCard",
|
"mast": "MasterCard",
|
||||||
"visa": "Visa"
|
"visa": "Visa"
|
||||||
}
|
},
|
||||||
|
"payment_type": "Payment Type Map"
|
||||||
},
|
},
|
||||||
"invoice_federal_tax_rate": "Invoices - Federal Tax Rate",
|
"invoice_federal_tax_rate": "Invoices - Federal Tax Rate",
|
||||||
"invoice_local_tax_rate": "Invoices - Local Tax Rate",
|
"invoice_local_tax_rate": "Invoices - Local Tax Rate",
|
||||||
@@ -601,7 +601,8 @@
|
|||||||
"templates": "Templates"
|
"templates": "Templates"
|
||||||
},
|
},
|
||||||
"ss_configuration": {
|
"ss_configuration": {
|
||||||
"dailyhrslimit": "Daily Incoming Hours Limit"
|
"dailyhrslimit": "Daily Incoming Hours Limit",
|
||||||
|
"nobusinessdays": "Include Weekends"
|
||||||
},
|
},
|
||||||
"ssbuckets": {
|
"ssbuckets": {
|
||||||
"color": "Job Color",
|
"color": "Job Color",
|
||||||
@@ -647,7 +648,12 @@
|
|||||||
"use_paint_scale_data": "Use Paint Scale Data for Job Costing?",
|
"use_paint_scale_data": "Use Paint Scale Data for Job Costing?",
|
||||||
"uselocalmediaserver": "Use Local Media Server?",
|
"uselocalmediaserver": "Use Local Media Server?",
|
||||||
"website": "Website",
|
"website": "Website",
|
||||||
"zip_post": "Zip/Postal Code"
|
"zip_post": "Zip/Postal Code",
|
||||||
|
"notifications": {
|
||||||
|
"description": "Select employees to automatically follow new jobs and receive notifications for job updates.",
|
||||||
|
"placeholder": "Search for employees",
|
||||||
|
"invalid_followers": "Invalid selection. Please select valid employees."
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"labels": {
|
"labels": {
|
||||||
"2tiername": "Name => RO",
|
"2tiername": "Name => RO",
|
||||||
@@ -727,7 +733,10 @@
|
|||||||
"ssbuckets": "Job Size Definitions",
|
"ssbuckets": "Job Size Definitions",
|
||||||
"systemsettings": "System Settings",
|
"systemsettings": "System Settings",
|
||||||
"task-presets": "Task Presets",
|
"task-presets": "Task Presets",
|
||||||
"workingdays": "Working Days"
|
"workingdays": "Working Days",
|
||||||
|
"notifications": {
|
||||||
|
"followers": "Notifications"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"operations": {
|
"operations": {
|
||||||
"contains": "Contains",
|
"contains": "Contains",
|
||||||
@@ -1220,7 +1229,8 @@
|
|||||||
"errors": {
|
"errors": {
|
||||||
"fcm": "You must allow notification permissions to have real time messaging. Click to try again.",
|
"fcm": "You must allow notification permissions to have real time messaging. Click to try again.",
|
||||||
"notfound": "No record was found.",
|
"notfound": "No record was found.",
|
||||||
"sizelimit": "The selected items exceed the size limit."
|
"sizelimit": "The selected items exceed the size limit.",
|
||||||
|
"submit-for-testing": "Error submitting Job for testing."
|
||||||
},
|
},
|
||||||
"itemtypes": {
|
"itemtypes": {
|
||||||
"contract": "CC Contract",
|
"contract": "CC Contract",
|
||||||
@@ -1234,6 +1244,7 @@
|
|||||||
"areyousure": "Are you sure?",
|
"areyousure": "Are you sure?",
|
||||||
"barcode": "Barcode",
|
"barcode": "Barcode",
|
||||||
"cancel": "Are you sure you want to cancel? Your changes will not be saved.",
|
"cancel": "Are you sure you want to cancel? Your changes will not be saved.",
|
||||||
|
"changelog": "Change Log",
|
||||||
"clear": "Clear",
|
"clear": "Clear",
|
||||||
"confirmpassword": "Confirm Password",
|
"confirmpassword": "Confirm Password",
|
||||||
"created_at": "Created At",
|
"created_at": "Created At",
|
||||||
@@ -1243,6 +1254,7 @@
|
|||||||
"errors": "Errors",
|
"errors": "Errors",
|
||||||
"excel": "Excel",
|
"excel": "Excel",
|
||||||
"exceptiontitle": "An error has occurred.",
|
"exceptiontitle": "An error has occurred.",
|
||||||
|
"feature-request": "Have a feature request?",
|
||||||
"friday": "Friday",
|
"friday": "Friday",
|
||||||
"globalsearch": "Global Search",
|
"globalsearch": "Global Search",
|
||||||
"help": "Help",
|
"help": "Help",
|
||||||
@@ -1321,6 +1333,7 @@
|
|||||||
"notfoundtitle": "We couldn't find what you're looking for...",
|
"notfoundtitle": "We couldn't find what you're looking for...",
|
||||||
"partnernotrunning": "{{app}} has detected that the partner is not running. Please ensure it is running to enable full functionality.",
|
"partnernotrunning": "{{app}} has detected that the partner is not running. Please ensure it is running to enable full functionality.",
|
||||||
"rbacunauth": "You are not authorized to view this content. Please reach out to your shop manager to change your access level.",
|
"rbacunauth": "You are not authorized to view this content. Please reach out to your shop manager to change your access level.",
|
||||||
|
"submit-for-testing": "Submitted Job for testing successfully.",
|
||||||
"unsavedchanges": "You have unsaved changes.",
|
"unsavedchanges": "You have unsaved changes.",
|
||||||
"unsavedchangespopup": "You have unsaved changes. Are you sure you want to leave?"
|
"unsavedchangespopup": "You have unsaved changes. Are you sure you want to leave?"
|
||||||
},
|
},
|
||||||
@@ -1633,9 +1646,12 @@
|
|||||||
"actual_completion": "Actual Completion",
|
"actual_completion": "Actual Completion",
|
||||||
"actual_delivery": "Actual Delivery",
|
"actual_delivery": "Actual Delivery",
|
||||||
"actual_in": "Actual In",
|
"actual_in": "Actual In",
|
||||||
|
"acv_amount": "ACV Amount",
|
||||||
"adjustment_bottom_line": "Adjustments",
|
"adjustment_bottom_line": "Adjustments",
|
||||||
"adjustmenthours": "Adjustment Hours",
|
"adjustmenthours": "Adjustment Hours",
|
||||||
"alt_transport": "Alt. Trans.",
|
"alt_transport": "Alt. Trans.",
|
||||||
|
"estimate_sent_approval": "Estimate Sent for Approval",
|
||||||
|
"estimate_approved": "Estimate Approved",
|
||||||
"area_of_damage_impact": {
|
"area_of_damage_impact": {
|
||||||
"10": "Left Front Side",
|
"10": "Left Front Side",
|
||||||
"11": "Left Front Corner",
|
"11": "Left Front Corner",
|
||||||
@@ -1758,9 +1774,10 @@
|
|||||||
"est_ct_ln": "Estimator Last Name",
|
"est_ct_ln": "Estimator Last Name",
|
||||||
"est_ea": "Estimator Email",
|
"est_ea": "Estimator Email",
|
||||||
"est_ph1": "Estimator Phone #",
|
"est_ph1": "Estimator Phone #",
|
||||||
"flat_rate_ats": "Flat Rate ATS?",
|
|
||||||
"federal_tax_payable": "Federal Tax Payable",
|
"federal_tax_payable": "Federal Tax Payable",
|
||||||
"federal_tax_rate": "Federal Tax Rate",
|
"federal_tax_rate": "Federal Tax Rate",
|
||||||
|
"flat_rate_ats": "Flat Rate ATS?",
|
||||||
|
"hit_and_run": "Hit and Run",
|
||||||
"ins_addr1": "Insurance Co. Address",
|
"ins_addr1": "Insurance Co. Address",
|
||||||
"ins_city": "Insurance Co. City",
|
"ins_city": "Insurance Co. City",
|
||||||
"ins_co_id": "Insurance Co. ID",
|
"ins_co_id": "Insurance Co. ID",
|
||||||
@@ -1940,6 +1957,8 @@
|
|||||||
"scheddates": "Schedule Dates"
|
"scheddates": "Schedule Dates"
|
||||||
},
|
},
|
||||||
"labels": {
|
"labels": {
|
||||||
|
"sent": "",
|
||||||
|
"approved": "",
|
||||||
"accountsreceivable": "Accounts Receivable",
|
"accountsreceivable": "Accounts Receivable",
|
||||||
"act_price_ppc": "New Part Price",
|
"act_price_ppc": "New Part Price",
|
||||||
"actual_completion_inferred": "$t(jobs.fields.actual_completion) inferred using $t(jobs.fields.scheduled_completion).",
|
"actual_completion_inferred": "$t(jobs.fields.actual_completion) inferred using $t(jobs.fields.scheduled_completion).",
|
||||||
@@ -2314,6 +2333,7 @@
|
|||||||
"duplicate": "Duplicate this Job",
|
"duplicate": "Duplicate this Job",
|
||||||
"duplicatenolines": "Duplicate this Job without Repair Data",
|
"duplicatenolines": "Duplicate this Job without Repair Data",
|
||||||
"newcccontract": "Create Courtesy Car Contract",
|
"newcccontract": "Create Courtesy Car Contract",
|
||||||
|
"submit-for-testing": "Submit for Testing",
|
||||||
"void": "Void Job"
|
"void": "Void Job"
|
||||||
},
|
},
|
||||||
"jobsdetail": {
|
"jobsdetail": {
|
||||||
@@ -2420,6 +2440,66 @@
|
|||||||
"updated": "Note updated successfully."
|
"updated": "Note updated successfully."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"notifications": {
|
||||||
|
"actions": {
|
||||||
|
"remove": "Remove"
|
||||||
|
},
|
||||||
|
"aria": {
|
||||||
|
"toggle": "Toggle Watching Job"
|
||||||
|
},
|
||||||
|
"channels": {
|
||||||
|
"app": "App",
|
||||||
|
"email": "Email",
|
||||||
|
"fcm": "Push"
|
||||||
|
},
|
||||||
|
"labels": {
|
||||||
|
"auto-add": "Automatically watch Jobs I import",
|
||||||
|
"auto-add-success": "Auto watcher status successfully changed.",
|
||||||
|
"auto-add-failure": "Something went wrong updating your auto watcher status.",
|
||||||
|
"add-watchers": "Add Watchers",
|
||||||
|
"add-watchers-team": "Add Team Members",
|
||||||
|
"employee-search": "Search for an Employee",
|
||||||
|
"mark-all-read": "Mark All Read",
|
||||||
|
"new-notification-title": "New Notification:",
|
||||||
|
"no-watchers": "No Watchers",
|
||||||
|
"notification-center": "Notification Center",
|
||||||
|
"notification-popup-title": "Changes for Job #{{ro_number}}",
|
||||||
|
"notification-settings-failure": "Error saving Notification Settings. {{error}}",
|
||||||
|
"notification-settings-success": "Notification Settings saved successfully.",
|
||||||
|
"notificationscenarios": "Job Notification Scenarios",
|
||||||
|
"ro-number": "RO #{{ro_number}}",
|
||||||
|
"save": "Save Scenarios",
|
||||||
|
"scenario": "Scenario",
|
||||||
|
"show-unread-only": "Show Unread Only",
|
||||||
|
"teams-search": "Search for a Team",
|
||||||
|
"unwatch": "Unwatch",
|
||||||
|
"watch": "Watch",
|
||||||
|
"watching-issue": "Watching",
|
||||||
|
"employee-notification": "Notifications are disabled because you do not have an associated Employee record."
|
||||||
|
},
|
||||||
|
"scenarios": {
|
||||||
|
"alternate-transport-changed": "Alternate Transport Changed",
|
||||||
|
"bill-posted": "Bill Posted",
|
||||||
|
"critical-parts-status-changed": "Critical Parts Status Changed",
|
||||||
|
"intake-delivery-checklist-completed": "Intake or Delivery Checklist Completed",
|
||||||
|
"job-added-to-production": "Job Added to Production",
|
||||||
|
"job-assigned-to-me": "Job Assigned to Me",
|
||||||
|
"job-status-change": "Job Status Changed",
|
||||||
|
"new-media-added-reassigned": "New Media Added or Reassigned",
|
||||||
|
"new-note-added": "New Note Added",
|
||||||
|
"new-time-ticket-posted": "New Time Ticket Posted",
|
||||||
|
"part-marked-back-ordered": "Part Marked Back Ordered",
|
||||||
|
"payment-collected-completed": "Payment Collected / Completed",
|
||||||
|
"schedule-dates-changed": "Schedule Dates Changed",
|
||||||
|
"supplement-imported": "Supplement Imported",
|
||||||
|
"tasks-updated-created": "Tasks Updated / Created"
|
||||||
|
},
|
||||||
|
"tooltips": {
|
||||||
|
"job-watchers": "Job Watchers",
|
||||||
|
"not-employee": "You need to be an employee to watch this job. Reach out to your admin to get set up!",
|
||||||
|
"not-employee-notifications": "You must be an employee to receive notifications"
|
||||||
|
}
|
||||||
|
},
|
||||||
"owner": {
|
"owner": {
|
||||||
"labels": {
|
"labels": {
|
||||||
"noownerinfo": "No owner information."
|
"noownerinfo": "No owner information."
|
||||||
@@ -3416,6 +3496,7 @@
|
|||||||
"dashboard": "Dashboard",
|
"dashboard": "Dashboard",
|
||||||
"dms": "DMS Export",
|
"dms": "DMS Export",
|
||||||
"export-logs": "Export Logs",
|
"export-logs": "Export Logs",
|
||||||
|
"feature-request": "Feature Requet",
|
||||||
"inventory": "Inventory",
|
"inventory": "Inventory",
|
||||||
"jobs": "Jobs",
|
"jobs": "Jobs",
|
||||||
"jobs-active": "Active Jobs",
|
"jobs-active": "Active Jobs",
|
||||||
@@ -3460,6 +3541,7 @@
|
|||||||
"dashboard": "Dashboard | {{app}}",
|
"dashboard": "Dashboard | {{app}}",
|
||||||
"dms": "DMS Export | {{app}}",
|
"dms": "DMS Export | {{app}}",
|
||||||
"export-logs": "Export Logs | {{app}}",
|
"export-logs": "Export Logs | {{app}}",
|
||||||
|
"feature-request": "Feature Request | {{app}}",
|
||||||
"imexonline": "ImEX Online",
|
"imexonline": "ImEX Online",
|
||||||
"inventory": "Inventory | {{app}}",
|
"inventory": "Inventory | {{app}}",
|
||||||
"jobs": "Active Jobs | {{app}}",
|
"jobs": "Active Jobs | {{app}}",
|
||||||
@@ -3677,10 +3759,10 @@
|
|||||||
"users": {
|
"users": {
|
||||||
"errors": {
|
"errors": {
|
||||||
"signinerror": {
|
"signinerror": {
|
||||||
|
"auth/invalid-email": "A user with this email does not exist.",
|
||||||
"auth/user-disabled": "User account disabled. ",
|
"auth/user-disabled": "User account disabled. ",
|
||||||
"auth/user-not-found": "A user with this email does not exist.",
|
"auth/user-not-found": "A user with this email does not exist.",
|
||||||
"auth/wrong-password": "The email and password combination you provided is incorrect.",
|
"auth/wrong-password": "The email and password combination you provided is incorrect."
|
||||||
"auth/invalid-email": "A user with this email does not exist."
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -3780,60 +3862,6 @@
|
|||||||
"validation": {
|
"validation": {
|
||||||
"unique_vendor_name": "You must enter a unique vendor name."
|
"unique_vendor_name": "You must enter a unique vendor name."
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"notifications": {
|
|
||||||
"labels": {
|
|
||||||
"notification-center": "Notification Center",
|
|
||||||
"scenario": "Scenario",
|
|
||||||
"notificationscenarios": "Job Notification Scenarios",
|
|
||||||
"save": "Save Scenarios",
|
|
||||||
"watching-issue": "Watching",
|
|
||||||
"add-watchers": "Add Watchers",
|
|
||||||
"employee-search": "Search for an Employee",
|
|
||||||
"teams-search": "Search for a Team",
|
|
||||||
"add-watchers-team": "Add Team Members",
|
|
||||||
"new-notification-title": "New Notification:",
|
|
||||||
"show-unread-only": "Show Unread Only",
|
|
||||||
"mark-all-read": "Mark All Read",
|
|
||||||
"notification-popup-title": "Changes for Job #{{ro_number}}",
|
|
||||||
"ro-number": "RO #{{ro_number}}",
|
|
||||||
"no-watchers": "No Watchers",
|
|
||||||
"notification-settings-success": "Notification Settings saved successfully.",
|
|
||||||
"notification-settings-failure": "Error saving Notification Settings. {{error}}",
|
|
||||||
"watch": "Watch",
|
|
||||||
"unwatch": "Unwatch"
|
|
||||||
},
|
|
||||||
"actions": {
|
|
||||||
"remove": "Remove"
|
|
||||||
},
|
|
||||||
"aria": {
|
|
||||||
"toggle": "Toggle Watching Job"
|
|
||||||
},
|
|
||||||
"tooltips": {
|
|
||||||
"job-watchers": "Job Watchers"
|
|
||||||
},
|
|
||||||
"scenarios": {
|
|
||||||
"job-assigned-to-me": "Job Assigned to Me",
|
|
||||||
"bill-posted": "Bill Posted",
|
|
||||||
"critical-parts-status-changed": "Critical Parts Status Changed",
|
|
||||||
"part-marked-back-ordered": "Part Marked Back Ordered",
|
|
||||||
"new-note-added": "New Note Added",
|
|
||||||
"supplement-imported": "Supplement Imported",
|
|
||||||
"schedule-dates-changed": "Schedule Dates Changed",
|
|
||||||
"tasks-updated-created": "Tasks Updated / Created",
|
|
||||||
"new-media-added-reassigned": "New Media Added or Reassigned",
|
|
||||||
"new-time-ticket-posted": "New Time Ticket Posted",
|
|
||||||
"intake-delivery-checklist-completed": "Intake or Delivery Checklist Completed",
|
|
||||||
"job-added-to-production": "Job Added to Production",
|
|
||||||
"job-status-change": "Job Status Changed",
|
|
||||||
"payment-collected-completed": "Payment Collected / Completed",
|
|
||||||
"alternate-transport-changed": "Alternate Transport Changed"
|
|
||||||
},
|
|
||||||
"channels": {
|
|
||||||
"app": "App",
|
|
||||||
"email": "Email",
|
|
||||||
"fcm": "Push"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -335,7 +335,6 @@
|
|||||||
"intellipay_config": {
|
"intellipay_config": {
|
||||||
"cash_discount_percentage": "",
|
"cash_discount_percentage": "",
|
||||||
"enable_cash_discount": "",
|
"enable_cash_discount": "",
|
||||||
"payment_type": "",
|
|
||||||
"payment_map": {
|
"payment_map": {
|
||||||
"amex": "American Express",
|
"amex": "American Express",
|
||||||
"disc": "Discover",
|
"disc": "Discover",
|
||||||
@@ -344,7 +343,8 @@
|
|||||||
"jcb": "JCB",
|
"jcb": "JCB",
|
||||||
"mast": "MasterCard",
|
"mast": "MasterCard",
|
||||||
"visa": "Visa"
|
"visa": "Visa"
|
||||||
}
|
},
|
||||||
|
"payment_type": ""
|
||||||
},
|
},
|
||||||
"invoice_federal_tax_rate": "",
|
"invoice_federal_tax_rate": "",
|
||||||
"invoice_local_tax_rate": "",
|
"invoice_local_tax_rate": "",
|
||||||
@@ -601,7 +601,8 @@
|
|||||||
"templates": ""
|
"templates": ""
|
||||||
},
|
},
|
||||||
"ss_configuration": {
|
"ss_configuration": {
|
||||||
"dailyhrslimit": ""
|
"dailyhrslimit": "",
|
||||||
|
"nobusinessdays": ""
|
||||||
},
|
},
|
||||||
"ssbuckets": {
|
"ssbuckets": {
|
||||||
"color": "",
|
"color": "",
|
||||||
@@ -647,7 +648,12 @@
|
|||||||
"use_paint_scale_data": "",
|
"use_paint_scale_data": "",
|
||||||
"uselocalmediaserver": "",
|
"uselocalmediaserver": "",
|
||||||
"website": "",
|
"website": "",
|
||||||
"zip_post": ""
|
"zip_post": "",
|
||||||
|
"notifications": {
|
||||||
|
"description": "",
|
||||||
|
"placeholder": "",
|
||||||
|
"invalid_followers": ""
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"labels": {
|
"labels": {
|
||||||
"2tiername": "",
|
"2tiername": "",
|
||||||
@@ -727,7 +733,10 @@
|
|||||||
"ssbuckets": "",
|
"ssbuckets": "",
|
||||||
"systemsettings": "",
|
"systemsettings": "",
|
||||||
"task-presets": "",
|
"task-presets": "",
|
||||||
"workingdays": ""
|
"workingdays": "",
|
||||||
|
"notifications": {
|
||||||
|
"followers": ""
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"operations": {
|
"operations": {
|
||||||
"contains": "",
|
"contains": "",
|
||||||
@@ -1220,7 +1229,8 @@
|
|||||||
"errors": {
|
"errors": {
|
||||||
"fcm": "",
|
"fcm": "",
|
||||||
"notfound": "",
|
"notfound": "",
|
||||||
"sizelimit": ""
|
"sizelimit": "",
|
||||||
|
"submit-for-testing": ""
|
||||||
},
|
},
|
||||||
"itemtypes": {
|
"itemtypes": {
|
||||||
"contract": "",
|
"contract": "",
|
||||||
@@ -1234,6 +1244,7 @@
|
|||||||
"areyousure": "",
|
"areyousure": "",
|
||||||
"barcode": "código de barras",
|
"barcode": "código de barras",
|
||||||
"cancel": "",
|
"cancel": "",
|
||||||
|
"changelog": "",
|
||||||
"clear": "",
|
"clear": "",
|
||||||
"confirmpassword": "",
|
"confirmpassword": "",
|
||||||
"created_at": "",
|
"created_at": "",
|
||||||
@@ -1243,6 +1254,7 @@
|
|||||||
"errors": "",
|
"errors": "",
|
||||||
"excel": "",
|
"excel": "",
|
||||||
"exceptiontitle": "",
|
"exceptiontitle": "",
|
||||||
|
"feature-request": "",
|
||||||
"friday": "",
|
"friday": "",
|
||||||
"globalsearch": "",
|
"globalsearch": "",
|
||||||
"help": "",
|
"help": "",
|
||||||
@@ -1321,6 +1333,7 @@
|
|||||||
"notfoundtitle": "",
|
"notfoundtitle": "",
|
||||||
"partnernotrunning": "",
|
"partnernotrunning": "",
|
||||||
"rbacunauth": "",
|
"rbacunauth": "",
|
||||||
|
"submit-for-testing": "",
|
||||||
"unsavedchanges": "Usted tiene cambios no guardados.",
|
"unsavedchanges": "Usted tiene cambios no guardados.",
|
||||||
"unsavedchangespopup": ""
|
"unsavedchangespopup": ""
|
||||||
},
|
},
|
||||||
@@ -1629,10 +1642,13 @@
|
|||||||
"voiding": ""
|
"voiding": ""
|
||||||
},
|
},
|
||||||
"fields": {
|
"fields": {
|
||||||
|
"estimate_sent_approval": "",
|
||||||
|
"estimate_approved": "",
|
||||||
"active_tasks": "",
|
"active_tasks": "",
|
||||||
"actual_completion": "Realización real",
|
"actual_completion": "Realización real",
|
||||||
"actual_delivery": "Entrega real",
|
"actual_delivery": "Entrega real",
|
||||||
"actual_in": "Real en",
|
"actual_in": "Real en",
|
||||||
|
"acv_amount": "",
|
||||||
"adjustment_bottom_line": "Ajustes",
|
"adjustment_bottom_line": "Ajustes",
|
||||||
"adjustmenthours": "",
|
"adjustmenthours": "",
|
||||||
"alt_transport": "",
|
"alt_transport": "",
|
||||||
@@ -1758,9 +1774,10 @@
|
|||||||
"est_ct_ln": "Apellido del tasador",
|
"est_ct_ln": "Apellido del tasador",
|
||||||
"est_ea": "Correo electrónico del tasador",
|
"est_ea": "Correo electrónico del tasador",
|
||||||
"est_ph1": "Número de teléfono del tasador",
|
"est_ph1": "Número de teléfono del tasador",
|
||||||
"flat_rate_ats": "",
|
|
||||||
"federal_tax_payable": "Impuesto federal por pagar",
|
"federal_tax_payable": "Impuesto federal por pagar",
|
||||||
"federal_tax_rate": "",
|
"federal_tax_rate": "",
|
||||||
|
"flat_rate_ats": "",
|
||||||
|
"hit_and_run": "",
|
||||||
"ins_addr1": "Dirección de Insurance Co.",
|
"ins_addr1": "Dirección de Insurance Co.",
|
||||||
"ins_city": "Ciudad de seguros",
|
"ins_city": "Ciudad de seguros",
|
||||||
"ins_co_id": "ID de la compañía de seguros",
|
"ins_co_id": "ID de la compañía de seguros",
|
||||||
@@ -1940,6 +1957,8 @@
|
|||||||
"scheddates": ""
|
"scheddates": ""
|
||||||
},
|
},
|
||||||
"labels": {
|
"labels": {
|
||||||
|
"sent": "",
|
||||||
|
"approved": "",
|
||||||
"accountsreceivable": "",
|
"accountsreceivable": "",
|
||||||
"act_price_ppc": "",
|
"act_price_ppc": "",
|
||||||
"actual_completion_inferred": "",
|
"actual_completion_inferred": "",
|
||||||
@@ -2314,6 +2333,7 @@
|
|||||||
"duplicate": "",
|
"duplicate": "",
|
||||||
"duplicatenolines": "",
|
"duplicatenolines": "",
|
||||||
"newcccontract": "",
|
"newcccontract": "",
|
||||||
|
"submit-for-testing": "",
|
||||||
"void": ""
|
"void": ""
|
||||||
},
|
},
|
||||||
"jobsdetail": {
|
"jobsdetail": {
|
||||||
@@ -2420,6 +2440,67 @@
|
|||||||
"updated": "Nota actualizada con éxito."
|
"updated": "Nota actualizada con éxito."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"notifications": {
|
||||||
|
"actions": {
|
||||||
|
"remove": ""
|
||||||
|
},
|
||||||
|
"aria": {
|
||||||
|
"toggle": ""
|
||||||
|
},
|
||||||
|
"channels": {
|
||||||
|
"app": "",
|
||||||
|
"email": "",
|
||||||
|
"fcm": ""
|
||||||
|
},
|
||||||
|
"labels": {
|
||||||
|
"auto-add-on": "",
|
||||||
|
"auto-add-off": "",
|
||||||
|
"auto-add-success": "",
|
||||||
|
"auto-add-failure": "",
|
||||||
|
"auto-add-description": "",
|
||||||
|
"add-watchers": "",
|
||||||
|
"add-watchers-team": "",
|
||||||
|
"employee-search": "",
|
||||||
|
"mark-all-read": "",
|
||||||
|
"new-notification-title": "",
|
||||||
|
"no-watchers": "",
|
||||||
|
"notification-center": "",
|
||||||
|
"notification-popup-title": "",
|
||||||
|
"notification-settings-failure": "",
|
||||||
|
"notification-settings-success": "",
|
||||||
|
"notificationscenarios": "",
|
||||||
|
"ro-number": "",
|
||||||
|
"save": "",
|
||||||
|
"scenario": "",
|
||||||
|
"show-unread-only": "",
|
||||||
|
"teams-search": "",
|
||||||
|
"unwatch": "",
|
||||||
|
"watch": "",
|
||||||
|
"watching-issue": "",
|
||||||
|
"employee-notification": ""
|
||||||
|
},
|
||||||
|
"scenarios": {
|
||||||
|
"alternate-transport-changed": "",
|
||||||
|
"bill-posted": "",
|
||||||
|
"critical-parts-status-changed": "",
|
||||||
|
"intake-delivery-checklist-completed": "",
|
||||||
|
"job-added-to-production": "",
|
||||||
|
"job-assigned-to-me": "",
|
||||||
|
"job-status-change": "",
|
||||||
|
"new-media-added-reassigned": "",
|
||||||
|
"new-note-added": "",
|
||||||
|
"new-time-ticket-posted": "",
|
||||||
|
"part-marked-back-ordered": "",
|
||||||
|
"payment-collected-completed": "",
|
||||||
|
"schedule-dates-changed": "",
|
||||||
|
"supplement-imported": "",
|
||||||
|
"tasks-updated-created": ""
|
||||||
|
},
|
||||||
|
"tooltips": {
|
||||||
|
"job-watchers": "",
|
||||||
|
"not-employee": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
"owner": {
|
"owner": {
|
||||||
"labels": {
|
"labels": {
|
||||||
"noownerinfo": ""
|
"noownerinfo": ""
|
||||||
@@ -3416,6 +3497,7 @@
|
|||||||
"dashboard": "",
|
"dashboard": "",
|
||||||
"dms": "",
|
"dms": "",
|
||||||
"export-logs": "",
|
"export-logs": "",
|
||||||
|
"feature-request": "",
|
||||||
"inventory": "",
|
"inventory": "",
|
||||||
"jobs": "",
|
"jobs": "",
|
||||||
"jobs-active": "",
|
"jobs-active": "",
|
||||||
@@ -3460,6 +3542,7 @@
|
|||||||
"dashboard": "",
|
"dashboard": "",
|
||||||
"dms": "",
|
"dms": "",
|
||||||
"export-logs": "",
|
"export-logs": "",
|
||||||
|
"feature-request": "",
|
||||||
"imexonline": "",
|
"imexonline": "",
|
||||||
"inventory": "",
|
"inventory": "",
|
||||||
"jobs": "Todos los trabajos | {{app}}",
|
"jobs": "Todos los trabajos | {{app}}",
|
||||||
@@ -3677,10 +3760,10 @@
|
|||||||
"users": {
|
"users": {
|
||||||
"errors": {
|
"errors": {
|
||||||
"signinerror": {
|
"signinerror": {
|
||||||
|
"auth/invalid-email": "",
|
||||||
"auth/user-disabled": "",
|
"auth/user-disabled": "",
|
||||||
"auth/user-not-found": "",
|
"auth/user-not-found": "",
|
||||||
"auth/wrong-password": "",
|
"auth/wrong-password": ""
|
||||||
"auth/invalid-email": ""
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -3780,60 +3863,6 @@
|
|||||||
"validation": {
|
"validation": {
|
||||||
"unique_vendor_name": ""
|
"unique_vendor_name": ""
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"notifications": {
|
|
||||||
"labels": {
|
|
||||||
"notification-center": "",
|
|
||||||
"scenario": "",
|
|
||||||
"notificationscenarios": "",
|
|
||||||
"save": "",
|
|
||||||
"watching-issue": "",
|
|
||||||
"add-watchers": "",
|
|
||||||
"employee-search": "",
|
|
||||||
"teams-search": "",
|
|
||||||
"add-watchers-team": "",
|
|
||||||
"new-notification-title": "",
|
|
||||||
"show-unread-only": "",
|
|
||||||
"mark-all-read": "",
|
|
||||||
"notification-popup-title": "",
|
|
||||||
"ro-number": "",
|
|
||||||
"no-watchers": "",
|
|
||||||
"notification-settings-success": "",
|
|
||||||
"notification-settings-failure": "",
|
|
||||||
"watch": "",
|
|
||||||
"unwatch": ""
|
|
||||||
},
|
|
||||||
"actions": {
|
|
||||||
"remove": ""
|
|
||||||
},
|
|
||||||
"aria": {
|
|
||||||
"toggle": ""
|
|
||||||
},
|
|
||||||
"tooltips": {
|
|
||||||
"job-watchers": ""
|
|
||||||
},
|
|
||||||
"scenarios": {
|
|
||||||
"job-assigned-to-me": "",
|
|
||||||
"bill-posted": "",
|
|
||||||
"critical-parts-status-changed": "",
|
|
||||||
"part-marked-back-ordered": "",
|
|
||||||
"new-note-added": "",
|
|
||||||
"supplement-imported": "",
|
|
||||||
"schedule-dates-changed": "",
|
|
||||||
"tasks-updated-created": "",
|
|
||||||
"new-media-added-reassigned": "",
|
|
||||||
"new-time-ticket-posted": "",
|
|
||||||
"intake-delivery-checklist-completed": "",
|
|
||||||
"job-added-to-production": "",
|
|
||||||
"job-status-change": "",
|
|
||||||
"payment-collected-completed": "",
|
|
||||||
"alternate-transport-changed": ""
|
|
||||||
},
|
|
||||||
"channels": {
|
|
||||||
"app": "",
|
|
||||||
"email": "",
|
|
||||||
"fcm": ""
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -335,7 +335,6 @@
|
|||||||
"intellipay_config": {
|
"intellipay_config": {
|
||||||
"cash_discount_percentage": "",
|
"cash_discount_percentage": "",
|
||||||
"enable_cash_discount": "",
|
"enable_cash_discount": "",
|
||||||
"payment_type": "",
|
|
||||||
"payment_map": {
|
"payment_map": {
|
||||||
"amex": "American Express",
|
"amex": "American Express",
|
||||||
"disc": "Discover",
|
"disc": "Discover",
|
||||||
@@ -344,7 +343,8 @@
|
|||||||
"jcb": "JCB",
|
"jcb": "JCB",
|
||||||
"mast": "MasterCard",
|
"mast": "MasterCard",
|
||||||
"visa": "Visa"
|
"visa": "Visa"
|
||||||
}
|
},
|
||||||
|
"payment_type": ""
|
||||||
},
|
},
|
||||||
"invoice_federal_tax_rate": "",
|
"invoice_federal_tax_rate": "",
|
||||||
"invoice_local_tax_rate": "",
|
"invoice_local_tax_rate": "",
|
||||||
@@ -601,7 +601,8 @@
|
|||||||
"templates": ""
|
"templates": ""
|
||||||
},
|
},
|
||||||
"ss_configuration": {
|
"ss_configuration": {
|
||||||
"dailyhrslimit": ""
|
"dailyhrslimit": "",
|
||||||
|
"nobusinessdays": ""
|
||||||
},
|
},
|
||||||
"ssbuckets": {
|
"ssbuckets": {
|
||||||
"color": "",
|
"color": "",
|
||||||
@@ -647,7 +648,12 @@
|
|||||||
"use_paint_scale_data": "",
|
"use_paint_scale_data": "",
|
||||||
"uselocalmediaserver": "",
|
"uselocalmediaserver": "",
|
||||||
"website": "",
|
"website": "",
|
||||||
"zip_post": ""
|
"zip_post": "",
|
||||||
|
"notifications": {
|
||||||
|
"description": "",
|
||||||
|
"placeholder": "",
|
||||||
|
"invalid_followers": ""
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"labels": {
|
"labels": {
|
||||||
"2tiername": "",
|
"2tiername": "",
|
||||||
@@ -727,7 +733,10 @@
|
|||||||
"ssbuckets": "",
|
"ssbuckets": "",
|
||||||
"systemsettings": "",
|
"systemsettings": "",
|
||||||
"task-presets": "",
|
"task-presets": "",
|
||||||
"workingdays": ""
|
"workingdays": "",
|
||||||
|
"notifications": {
|
||||||
|
"followers": ""
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"operations": {
|
"operations": {
|
||||||
"contains": "",
|
"contains": "",
|
||||||
@@ -1220,7 +1229,8 @@
|
|||||||
"errors": {
|
"errors": {
|
||||||
"fcm": "",
|
"fcm": "",
|
||||||
"notfound": "",
|
"notfound": "",
|
||||||
"sizelimit": ""
|
"sizelimit": "",
|
||||||
|
"submit-for-testing": ""
|
||||||
},
|
},
|
||||||
"itemtypes": {
|
"itemtypes": {
|
||||||
"contract": "",
|
"contract": "",
|
||||||
@@ -1234,6 +1244,7 @@
|
|||||||
"areyousure": "",
|
"areyousure": "",
|
||||||
"barcode": "code à barre",
|
"barcode": "code à barre",
|
||||||
"cancel": "",
|
"cancel": "",
|
||||||
|
"changelog": "",
|
||||||
"clear": "",
|
"clear": "",
|
||||||
"confirmpassword": "",
|
"confirmpassword": "",
|
||||||
"created_at": "",
|
"created_at": "",
|
||||||
@@ -1243,6 +1254,7 @@
|
|||||||
"errors": "",
|
"errors": "",
|
||||||
"excel": "",
|
"excel": "",
|
||||||
"exceptiontitle": "",
|
"exceptiontitle": "",
|
||||||
|
"feature-request": "",
|
||||||
"friday": "",
|
"friday": "",
|
||||||
"globalsearch": "",
|
"globalsearch": "",
|
||||||
"help": "",
|
"help": "",
|
||||||
@@ -1321,6 +1333,7 @@
|
|||||||
"notfoundtitle": "",
|
"notfoundtitle": "",
|
||||||
"partnernotrunning": "",
|
"partnernotrunning": "",
|
||||||
"rbacunauth": "",
|
"rbacunauth": "",
|
||||||
|
"submit-for-testing": "",
|
||||||
"unsavedchanges": "Vous avez des changements non enregistrés.",
|
"unsavedchanges": "Vous avez des changements non enregistrés.",
|
||||||
"unsavedchangespopup": ""
|
"unsavedchangespopup": ""
|
||||||
},
|
},
|
||||||
@@ -1629,10 +1642,13 @@
|
|||||||
"voiding": ""
|
"voiding": ""
|
||||||
},
|
},
|
||||||
"fields": {
|
"fields": {
|
||||||
|
"estimate_sent_approval": "",
|
||||||
|
"estimate_approved": "",
|
||||||
"active_tasks": "",
|
"active_tasks": "",
|
||||||
"actual_completion": "Achèvement réel",
|
"actual_completion": "Achèvement réel",
|
||||||
"actual_delivery": "Livraison réelle",
|
"actual_delivery": "Livraison réelle",
|
||||||
"actual_in": "En réel",
|
"actual_in": "En réel",
|
||||||
|
"acv_amount": "",
|
||||||
"adjustment_bottom_line": "Ajustements",
|
"adjustment_bottom_line": "Ajustements",
|
||||||
"adjustmenthours": "",
|
"adjustmenthours": "",
|
||||||
"alt_transport": "",
|
"alt_transport": "",
|
||||||
@@ -1758,9 +1774,10 @@
|
|||||||
"est_ct_ln": "Nom de l'évaluateur",
|
"est_ct_ln": "Nom de l'évaluateur",
|
||||||
"est_ea": "Courriel de l'évaluateur",
|
"est_ea": "Courriel de l'évaluateur",
|
||||||
"est_ph1": "Numéro de téléphone de l'évaluateur",
|
"est_ph1": "Numéro de téléphone de l'évaluateur",
|
||||||
"flat_rate_ats": "",
|
|
||||||
"federal_tax_payable": "Impôt fédéral à payer",
|
"federal_tax_payable": "Impôt fédéral à payer",
|
||||||
"federal_tax_rate": "",
|
"federal_tax_rate": "",
|
||||||
|
"flat_rate_ats": "",
|
||||||
|
"hit_and_run": "",
|
||||||
"ins_addr1": "Adresse Insurance Co.",
|
"ins_addr1": "Adresse Insurance Co.",
|
||||||
"ins_city": "Insurance City",
|
"ins_city": "Insurance City",
|
||||||
"ins_co_id": "ID de la compagnie d'assurance",
|
"ins_co_id": "ID de la compagnie d'assurance",
|
||||||
@@ -1940,6 +1957,8 @@
|
|||||||
"scheddates": ""
|
"scheddates": ""
|
||||||
},
|
},
|
||||||
"labels": {
|
"labels": {
|
||||||
|
"sent": "",
|
||||||
|
"approved": "",
|
||||||
"accountsreceivable": "",
|
"accountsreceivable": "",
|
||||||
"act_price_ppc": "",
|
"act_price_ppc": "",
|
||||||
"actual_completion_inferred": "",
|
"actual_completion_inferred": "",
|
||||||
@@ -2314,6 +2333,7 @@
|
|||||||
"duplicate": "",
|
"duplicate": "",
|
||||||
"duplicatenolines": "",
|
"duplicatenolines": "",
|
||||||
"newcccontract": "",
|
"newcccontract": "",
|
||||||
|
"submit-for-testing": "",
|
||||||
"void": ""
|
"void": ""
|
||||||
},
|
},
|
||||||
"jobsdetail": {
|
"jobsdetail": {
|
||||||
@@ -2420,6 +2440,67 @@
|
|||||||
"updated": "Remarque mise à jour avec succès."
|
"updated": "Remarque mise à jour avec succès."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"notifications": {
|
||||||
|
"actions": {
|
||||||
|
"remove": ""
|
||||||
|
},
|
||||||
|
"aria": {
|
||||||
|
"toggle": ""
|
||||||
|
},
|
||||||
|
"channels": {
|
||||||
|
"app": "",
|
||||||
|
"email": "",
|
||||||
|
"fcm": ""
|
||||||
|
},
|
||||||
|
"labels": {
|
||||||
|
"auto-add-on": "",
|
||||||
|
"auto-add-off": "",
|
||||||
|
"auto-add-success": "",
|
||||||
|
"auto-add-failure": "",
|
||||||
|
"auto-add-description": "",
|
||||||
|
"add-watchers": "",
|
||||||
|
"add-watchers-team": "",
|
||||||
|
"employee-search": "",
|
||||||
|
"mark-all-read": "",
|
||||||
|
"new-notification-title": "",
|
||||||
|
"no-watchers": "",
|
||||||
|
"notification-center": "",
|
||||||
|
"notification-popup-title": "",
|
||||||
|
"notification-settings-failure": "",
|
||||||
|
"notification-settings-success": "",
|
||||||
|
"notificationscenarios": "",
|
||||||
|
"ro-number": "",
|
||||||
|
"save": "",
|
||||||
|
"scenario": "",
|
||||||
|
"show-unread-only": "",
|
||||||
|
"teams-search": "",
|
||||||
|
"unwatch": "",
|
||||||
|
"watch": "",
|
||||||
|
"watching-issue": "",
|
||||||
|
"employee-notification": ""
|
||||||
|
},
|
||||||
|
"scenarios": {
|
||||||
|
"alternate-transport-changed": "",
|
||||||
|
"bill-posted": "",
|
||||||
|
"critical-parts-status-changed": "",
|
||||||
|
"intake-delivery-checklist-completed": "",
|
||||||
|
"job-added-to-production": "",
|
||||||
|
"job-assigned-to-me": "",
|
||||||
|
"job-status-change": "",
|
||||||
|
"new-media-added-reassigned": "",
|
||||||
|
"new-note-added": "",
|
||||||
|
"new-time-ticket-posted": "",
|
||||||
|
"part-marked-back-ordered": "",
|
||||||
|
"payment-collected-completed": "",
|
||||||
|
"schedule-dates-changed": "",
|
||||||
|
"supplement-imported": "",
|
||||||
|
"tasks-updated-created": ""
|
||||||
|
},
|
||||||
|
"tooltips": {
|
||||||
|
"job-watchers": "",
|
||||||
|
"not-employee": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
"owner": {
|
"owner": {
|
||||||
"labels": {
|
"labels": {
|
||||||
"noownerinfo": ""
|
"noownerinfo": ""
|
||||||
@@ -3416,6 +3497,7 @@
|
|||||||
"dashboard": "",
|
"dashboard": "",
|
||||||
"dms": "",
|
"dms": "",
|
||||||
"export-logs": "",
|
"export-logs": "",
|
||||||
|
"feature-request": "",
|
||||||
"inventory": "",
|
"inventory": "",
|
||||||
"jobs": "",
|
"jobs": "",
|
||||||
"jobs-active": "",
|
"jobs-active": "",
|
||||||
@@ -3460,6 +3542,7 @@
|
|||||||
"dashboard": "",
|
"dashboard": "",
|
||||||
"dms": "",
|
"dms": "",
|
||||||
"export-logs": "",
|
"export-logs": "",
|
||||||
|
"feature-request": "",
|
||||||
"imexonline": "",
|
"imexonline": "",
|
||||||
"inventory": "",
|
"inventory": "",
|
||||||
"jobs": "Tous les emplois | {{app}}",
|
"jobs": "Tous les emplois | {{app}}",
|
||||||
@@ -3677,10 +3760,10 @@
|
|||||||
"users": {
|
"users": {
|
||||||
"errors": {
|
"errors": {
|
||||||
"signinerror": {
|
"signinerror": {
|
||||||
|
"auth/invalid-email": "",
|
||||||
"auth/user-disabled": "",
|
"auth/user-disabled": "",
|
||||||
"auth/user-not-found": "",
|
"auth/user-not-found": "",
|
||||||
"auth/wrong-password": "",
|
"auth/wrong-password": ""
|
||||||
"auth/invalid-email": ""
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -3780,60 +3863,6 @@
|
|||||||
"validation": {
|
"validation": {
|
||||||
"unique_vendor_name": ""
|
"unique_vendor_name": ""
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"notifications": {
|
|
||||||
"labels": {
|
|
||||||
"notification-center": "",
|
|
||||||
"scenario": "",
|
|
||||||
"notificationscenarios": "",
|
|
||||||
"save": "",
|
|
||||||
"watching-issue": "",
|
|
||||||
"add-watchers": "",
|
|
||||||
"employee-search": "",
|
|
||||||
"teams-search": "",
|
|
||||||
"add-watchers-team": "",
|
|
||||||
"new-notification-title": "",
|
|
||||||
"show-unread-only": "",
|
|
||||||
"mark-all-read": "",
|
|
||||||
"notification-popup-title": "",
|
|
||||||
"ro-number": "",
|
|
||||||
"no-watchers": "",
|
|
||||||
"notification-settings-success": "",
|
|
||||||
"notification-settings-failure": "",
|
|
||||||
"watch": "",
|
|
||||||
"unwatch": ""
|
|
||||||
},
|
|
||||||
"actions": {
|
|
||||||
"remove": ""
|
|
||||||
},
|
|
||||||
"aria": {
|
|
||||||
"toggle": ""
|
|
||||||
},
|
|
||||||
"tooltips": {
|
|
||||||
"job-watchers": ""
|
|
||||||
},
|
|
||||||
"scenarios": {
|
|
||||||
"job-assigned-to-me": "",
|
|
||||||
"bill-posted": "",
|
|
||||||
"critical-parts-status-changed": "",
|
|
||||||
"part-marked-back-ordered": "",
|
|
||||||
"new-note-added": "",
|
|
||||||
"supplement-imported": "",
|
|
||||||
"schedule-dates-changed": "",
|
|
||||||
"tasks-updated-created": "",
|
|
||||||
"new-media-added-reassigned": "",
|
|
||||||
"new-time-ticket-posted": "",
|
|
||||||
"intake-delivery-checklist-completed": "",
|
|
||||||
"job-added-to-production": "",
|
|
||||||
"job-status-change": "",
|
|
||||||
"payment-collected-completed": "",
|
|
||||||
"alternate-transport-changed": ""
|
|
||||||
},
|
|
||||||
"channels": {
|
|
||||||
"app": "",
|
|
||||||
"email": "",
|
|
||||||
"fcm": ""
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,8 +15,8 @@ const AuditTrailMapping = {
|
|||||||
jobchecklist: (type, inproduction, status) =>
|
jobchecklist: (type, inproduction, status) =>
|
||||||
i18n.t("audit_trail.messages.jobchecklist", { type, inproduction, status }),
|
i18n.t("audit_trail.messages.jobchecklist", { type, inproduction, status }),
|
||||||
jobconverted: (ro_number) => i18n.t("audit_trail.messages.jobconverted", { ro_number }),
|
jobconverted: (ro_number) => i18n.t("audit_trail.messages.jobconverted", { ro_number }),
|
||||||
jobintake: (status, email, scheduled_completion) =>
|
jobintake: (status, scheduled_completion) =>
|
||||||
i18n.t("audit_trail.messages.jobintake", { status, email, scheduled_completion }),
|
i18n.t("audit_trail.messages.jobintake", { status, scheduled_completion }),
|
||||||
jobdelivery: (status, email, actual_completion) =>
|
jobdelivery: (status, email, actual_completion) =>
|
||||||
i18n.t("audit_trail.messages.jobdelivery", { status, email, actual_completion }),
|
i18n.t("audit_trail.messages.jobdelivery", { status, email, actual_completion }),
|
||||||
jobexported: () => i18n.t("audit_trail.messages.jobexported"),
|
jobexported: () => i18n.t("audit_trail.messages.jobexported"),
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user